-
Notifications
You must be signed in to change notification settings - Fork 148
/
cuberite.lua
302 lines (252 loc) · 10.2 KB
/
cuberite.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
-- Implements Cuberite interpreter description and interface for ZBStudio.
-- Cuberite executable can have a postfix depending on the compilation mode (debug / release).
local function MakeCuberiteInterpreter(a_Self, a_InterpreterPostfix, a_ExePostfix)
assert(a_Self)
assert(type(a_InterpreterPostfix) == "string")
assert(type(a_ExePostfix) == "string")
return
{
name = "Cuberite" .. a_InterpreterPostfix,
description = "Cuberite - the custom C++ minecraft server",
api = {
"baselib",
"mcserver_api", -- Keep the old MCServer name for compatibility reasons
"cuberite_api"
},
frun = function(self, wfilename, withdebug)
-- Cuberite plugins are always in a "Plugins/<PluginName>" subfolder located at the executable level
-- Get to the executable by removing the last two dirs:
local ExePath = wx.wxFileName(wfilename)
ExePath:RemoveLastDir()
ExePath:RemoveLastDir()
ExePath:ClearExt()
ExePath:SetName("")
local ExeName = wx.wxFileName(ExePath)
-- The executable name depends on the debug / non-debug build mode, it can have a postfix
ExeName:SetName("Cuberite" .. a_ExePostfix)
-- Executable has a .exe ext on Windows
if (ide.osname == 'Windows') then
ExeName:SetExt("exe")
end
-- Check if we're in a subfolder inside the plugin folder, try going up one level if executable not found:
if not(ExeName:FileExists()) then
ide:GetOutput():Write("The Cuberite executable cannot be found in \"" .. ExeName:GetFullPath() .. "\". Trying one folder up.\n")
ExeName:RemoveLastDir()
ExePath:RemoveLastDir()
if not(ExeName:FileExists()) then
ide:GetOutput():Write("The Cuberite executable cannot be found in \"" .. ExeName:GetFullPath() .. "\". Aborting the debugger.\n")
return
end
end
-- Start the debugger server:
if withdebug then
DebuggerAttachDefault({
runstart = (ide.config.debugger.runonstart == true),
basedir = ExePath:GetFullPath(),
})
end
-- Add a "nooutbuf" cmdline param to the server, causing it to call setvbuf to disable output buffering:
local Cmd = ExeName:GetFullPath() .. " --no-output-buffering"
-- Force ZBS not to hide Cuberite window, save and restore previous state:
local SavedUnhideConsoleWindow = ide.config.unhidewindow.ConsoleWindowClass
ide.config.unhidewindow.ConsoleWindowClass = 1 -- show if hidden
-- Create the !EnableMobDebug.lua file so that the Cuberite plugin starts the debugging session, when loaded:
local EnablerPath = wx.wxFileName(wfilename)
EnablerPath:SetName("!EnableMobDebug")
EnablerPath:SetExt("lua")
local f = io.open(EnablerPath:GetFullPath(), "w")
if (f ~= nil) then
f:write(
[[
-- !EnableMobDebug.lua
-- This file is created by the ZeroBrane Studio debugger, do NOT commit it to your repository!
-- It is safe to delete this file once the debugger is stopped.
-- When this file is loaded in the ZeroBrane Studio, the debugger will pause when Cuberite detects a problem in your plugin
-- If you close this file, the debugger will no longer pause on problems
local g_mobdebug = require("mobdebug")
g_mobdebug.start()
function BreakIntoDebugger(a_Message)
g_mobdebug:pause()
-- If your plugin breaks here, it means that Cuberite has run into a problem in your plugin
-- Inspect the stack and the server console for the error report
-- If you close this file while the debugger is paused here, Cuberite will be terminated!
LOG("Broken into debugger: " .. a_Message)
end
]]
)
f:close()
end
-- Open the "!EnableMobDebug.lua" file in the editor, if not already open (so that breakpoints work):
local enablerEditor
local fullEnablerPath = EnablerPath:GetFullPath()
if not(ide:FindDocument(fullEnablerPath)) then
enablerEditor = LoadFile(fullEnablerPath)
end
-- When the enabler gets closed, invalidate our enablerEditor variable:
a_Self.onEditorClose = function(self, a_Editor)
if (a_Editor == enablerEditor) then
enablerEditor = nil
end
end
-- Create the closure to call upon debugging finish:
local OnFinished = function()
-- Close the "!EnableMobDebug.lua" file editor:
if (enablerEditor) then
ide:GetDocument(enablerEditor):Close()
end
-- Remove the editor-close watcher:
a_Self.onEditorClose = nil
-- Restore the Unhide status:
ide.config.unhidewindow.ConsoleWindowClass = SavedUnhideConsoleWindow
-- Remove the !EnableMobDebug.lua file:
os.remove(EnablerPath:GetFullPath())
end
-- Run the server:
local pid = CommandLineRun(
Cmd, -- Command to run
ExePath:GetFullPath(), -- Working directory for the debuggee
false, -- Redirect debuggee output to Output pane? (NOTE: This force-hides the Cuberite window, not desirable!)
true, -- Add a no-hide flag to WX
nil, -- StringCallback, whatever that is
nil, -- UID to identify this running program; nil to auto-assign
OnFinished -- Callback to call once the debuggee terminates
)
end,
hasdebugger = true,
}
end
local function analyzeProject()
local projectPath = ide:GetProject()
if not(projectPath) then
ide:GetOutput():Write("No project path has been defined.\n")
return
end
-- Get a list of all the files in the order in which Cuberite loads them (Info.lua is always last):
local files = {}
for _, filePath in ipairs(ide:GetFileList(projectPath, false, "*.lua")) do
table.insert(files, filePath)
end
table.sort(files,
function (a_File1, a_File2)
if (a_File1:match("[/\\]Info.lua")) then
return false
elseif (a_File2:match("[/\\]Info.lua")) then
return true
else
return a_File1 < a_File2
end
end
)
-- List all files in the console:
ide:GetOutput():Write("Files for analysis:\n")
for _, file in ipairs(files) do
ide:GetOutput():Write(file .. "\n")
end
ide:GetOutput():Write("Analyzing...\n")
-- Concatenate all the files, remember their line begin positions:
local lineBegin = {} -- array of {File = "filename", LineBegin = <linenum>, LineEnd = <linenum>}
local whole = {} -- array of individual files' contents
local curLineBegin = 1
for _, file in ipairs(files) do
local curFile = { "do" }
local lastLineNum = 0
for line in io.lines(file) do
table.insert(curFile, line)
lastLineNum = lastLineNum + 1
end
table.insert(curFile, "end")
table.insert(lineBegin, {File = file, LineBegin = curLineBegin + 1, LineEnd = curLineBegin + lastLineNum + 1})
curLineBegin = curLineBegin + lastLineNum + 2
table.insert(whole, table.concat(curFile, "\n"))
end
-- Analyze the concatenated files:
local warn, err, line, pos = ide:AnalyzeString(table.concat(whole, "\n"))
if (err) then
ide:GetOutput():Write("Error: " .. err .. "\n")
return
end
-- Function that translates concatenated-linenums back into source + linenum
local function findSourceByLine(a_LineNum)
for _, begin in ipairs(lineBegin) do
if (a_LineNum < begin.LineEnd) then
return begin.File, a_LineNum - begin.LineBegin + 1
end
end
end
-- Parse the analysis results back to original files:
for _, w in ipairs(warn) do
local wtext = w:gsub("^<string>:(%d*):(.*)",
function (a_LineNum, a_Message)
local srcFile, srcLineNum = findSourceByLine(tonumber(a_LineNum))
ide:GetOutput():Write(srcFile .. ":" .. srcLineNum .. ": " .. a_Message .. "\n")
end
)
end
ide:GetOutput():Write("Analysis completed.\n")
end
local function runInfoDump()
local projectPath = ide:GetProject()
if not(projectPath) then
ide:GetOutput():Write("No project path has been defined.\n")
return
end
-- Get the path to InfoDump.lua file, that is one folder up from the current project:
local dumpScriptPath = wx.wxFileName(projectPath)
local pluginName = dumpScriptPath:GetDirs()[#dumpScriptPath:GetDirs()]
dumpScriptPath:RemoveLastDir()
local dumpScript = wx.wxFileName(dumpScriptPath)
dumpScript:SetName("InfoDump")
dumpScript:SetExt("lua")
local fullPath = dumpScript:GetFullPath()
if not(wx.wxFileExists(fullPath)) then
ide:GetOutput():Write("The InfoDump.lua script was not found (tried " .. fullPath .. ")\n")
return
end
-- Execute the script:
local cmd = "lua " .. fullPath .. " " .. pluginName
CommandLineRun(
cmd, -- Command to run
dumpScriptPath:GetFullPath(), -- Working directory for the debuggee
true, -- Redirect debuggee output to Output pane?
true -- Add a no-hide flag to WX
)
ide:GetOutput():Write("The InfoDump.lua script was executed.\n")
end
return {
name = "Cuberite integration",
description = "Implements integration with Cuberite - the custom C++ minecraft server.",
author = "Mattes D (https://github.com/madmaxoft)",
version = 0.55,
dependencies = "1.70",
AnalysisMenuID = ID("analyze.cuberite_analyzeall"),
InfoDumpMenuID = ID("project.cuberite_infodump"),
onRegister = function(self)
-- Add the interpreters
self.InterpreterDebug = MakeCuberiteInterpreter(self, " - debug mode", "_debug")
self.InterpreterRelease = MakeCuberiteInterpreter(self, " - release mode", "")
ide:AddInterpreter("cuberite_debug", self.InterpreterDebug)
ide:AddInterpreter("cuberite_release", self.InterpreterRelease)
-- Add the analysis menu item:
local _, menu, pos = ide:FindMenuItem(ID.ANALYZE)
if pos then
menu:Insert(pos + 1, self.AnalysisMenuID, TR("Analyze as Cuberite") .. KSC(id), TR("Analyze the project source code as Cuberite"))
ide:GetMainFrame():Connect(self.AnalysisMenuID, wx.wxEVT_COMMAND_MENU_SELECTED, analyzeProject)
end
-- Add the InfoDump menu item:
_, menu, pos = ide:FindMenuItem(ID.INTERPRETER)
if (pos) then
self.Separator1 = menu:AppendSeparator()
menu:Append(self.InfoDumpMenuID, TR("Cuberite InfoDump") .. KSC(id), TR("Run the InfoDump script on the current plugin"))
ide:GetMainFrame():Connect(self.InfoDumpMenuID, wx.wxEVT_COMMAND_MENU_SELECTED, runInfoDump)
end
end,
onUnRegister = function(self)
-- Remove the interpreters:
ide:RemoveInterpreter("cuberite_debug")
ide:RemoveInterpreter("cuberite_release")
-- Remove the menu items:
ide:RemoveMenuItem(self.AnalysisMenuID)
ide:RemoveMenuItem(self.InfoDumpMenuID)
self.Separator1:GetParent():Delete(self.Separator1)
end,
}