Module.lua
11.2 KB
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
-- TE4 - T-Engine 4
-- Copyright (C) 2009, 2010, 2011 Nicolas Casalini
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
--
-- Nicolas Casalini "DarkGod"
-- darkgod@te4.org
require "engine.class"
local lanes = require "lanes"
local Dialog = require "engine.ui.Dialog"
local Savefile = require "engine.Savefile"
require "engine.PlayerProfile"
--- Handles dialog windows
module(..., package.seeall, class.make)
--- Create a version string for the module version
-- Static
function _M:versionString(mod)
return ("%s-%d.%d.%d"):format(mod.short_name, mod.version[1], mod.version[2], mod.version[3])
end
--- List all available modules
-- Static
function _M:listModules(incompatible)
local ms = {}
fs.mount(engine.homepath, "/")
-- print("Search Path: ") for k,e in ipairs(fs.getSearchPath()) do print("*",k,e) end
local knowns = {}
for i, short_name in ipairs(fs.list("/modules/")) do
local mod = self:createModule(short_name, incompatible)
if mod then
if not knowns[mod.short_name] then
table.insert(ms, {short_name=mod.short_name, name=mod.name, versions={}})
knowns[mod.short_name] = ms[#ms]
end
local v = knowns[mod.short_name].versions
v[#v+1] = mod
end
end
table.sort(ms, function(a, b)
print(a.short_name,b.short_name)
if a.short_name == "tome" then return 1
elseif b.short_name == "tome" then return nil
else return a.name < b.name
end
end)
for i, m in ipairs(ms) do
table.sort(m.versions, function(b, a)
return a.version[1] * 1000000 + a.version[2] * 1000 + a.version[3] * 1 < b.version[1] * 1000000 + b.version[2] * 1000 + b.version[3] * 1
end)
print("* Module: "..m.short_name)
for i, mod in ipairs(m.versions) do
print(" ** "..mod.version[1].."."..mod.version[2].."."..mod.version[3])
ms[mod.version_string] = mod
end
ms[m.short_name] = m.versions[1]
end
-- fs.umount(engine.homepath)
return ms
end
function _M:createModule(short_name, incompatible)
local dir = "/modules/"..short_name
print("Creating module", short_name, ":: (as dir)", fs.exists(dir.."/init.lua"), ":: (as team)", short_name:find(".team$"), "")
if fs.exists(dir.."/init.lua") then
local mod = self:loadDefinition(dir, nil, incompatible)
if mod and mod.short_name then
return mod
end
elseif short_name:find(".team$") then
fs.mount(fs.getRealPath(dir), "/testload", false)
local mod
if fs.exists("/testload/mod/init.lua") then
mod = self:loadDefinition("/testload", dir, incompatible)
end
fs.umount(fs.getRealPath(dir))
if mod and mod.short_name then return mod end
end
end
--- Get a module definition from the module init.lua file
function _M:loadDefinition(dir, team, incompatible)
local mod_def = loadfile(team and (dir.."/mod/init.lua") or (dir.."/init.lua"))
-- print("Loading module definition from", team and (dir.."/mod/init.lua") or (dir.."/init.lua"))
if mod_def then
-- Call the file body inside its own private environment
local mod = {rng=rng}
setfenv(mod_def, mod)
mod_def()
mod.rng = nil
if not mod.long_name or not mod.name or not mod.short_name or not mod.version or not mod.starter then
print("Bad module definition", mod.long_name, mod.name, mod.short_name, mod.version, mod.starter)
return
end
-- Test engine version
local eng_req = engine.version_string(mod.engine)
mod.version_string = self:versionString(mod)
if not __available_engines.__byname[eng_req] then
print("Module mismatch engine version "..mod.version_string.." using engine "..eng_req)
if incompatible then mod.incompatible = true
else return end
end
-- Make a function to activate it
mod.load = function(mode)
if mode == "setup" then
core.display.setWindowTitle(mod.long_name)
self:setupWrite(mod)
if not team then
fs.mount(fs.getRealPath(dir), "/mod", false)
fs.mount(fs.getRealPath(dir).."/data/", "/data", false)
if fs.exists(dir.."/engine") then fs.mount(fs.getRealPath(dir).."/engine/", "/engine", false) end
else
local src = fs.getRealPath(team)
fs.mount(src, "/", false)
end
elseif mode == "init" then
local m = require(mod.starter)
m[1].__session_time_played_start = os.time()
m[1].__mod_info = mod
print("[MODULE LOADER] loading module", mod.long_name, "["..mod.starter.."]", "::", m[1] and m[1].__CLASSNAME, m[2] and m[2].__CLASSNAME)
return m[1], m[2]
end
end
print("Loaded module definition for "..mod.version_string.." using engine "..eng_req)
return mod
end
end
--- List all available savefiles
-- Static
function _M:listSavefiles()
-- fs.mount(engine.homepath, "/tmp/listsaves")
local mods = self:listModules()
for _, mod in ipairs(mods) do
local lss = {}
for i, short_name in ipairs(fs.list("/"..mod.short_name.."/save/")) do
local dir = "/"..mod.short_name.."/save/"..short_name
if fs.exists(dir.."/game.teag") then
local def = self:loadSavefileDescription(dir)
if def then
table.insert(lss, def)
end
end
end
mod.savefiles = lss
table.sort(lss, function(a, b)
return a.name < b.name
end)
end
-- fs.umount(engine.homepath)
return mods
end
--- Instanciate the given module, loading it and creating a new game / loading an existing one
-- @param mod the module definition as given by Module:loadDefinition()
-- @param name the savefile name
-- @param new_game true if the game must be created (aka new character)
function _M:instanciate(mod, name, new_game, no_reboot)
if not no_reboot then
local popup = Dialog:simplePopup("Loading module", "Please wait while loading "..mod.long_name.."...", nil, true)
popup.__showup = nil
core.display.forceRedraw()
local eng_v = nil
if not mod.incompatible then eng_v = ("%d.%d.%d"):format(mod.engine[1], mod.engine[2], mod.engine[3]) end
util.showMainMenu(false, mod.engine[4], eng_v, mod.version_string, name, new_game)
return
end
if mod.short_name == "boot" then profile.hash_valid = true end
mod.version_name = ("%s-%d.%d.%d"):format(mod.short_name, mod.version[1], mod.version[2], mod.version[3])
profile.generic.modules_loaded = profile.generic.modules_loaded or {}
profile.generic.modules_loaded[mod.short_name] = (profile.generic.modules_loaded[mod.short_name] or 0) + 1
profile:saveGenericProfile("modules_loaded", profile.generic.modules_loaded)
-- Turn based by default
core.game.setRealtime(0)
-- Init the module directories
mod.load("setup")
-- Check MD5sum with the server
local md5 = require "md5"
local md5s = {}
local function fp(dir)
for i, file in ipairs(fs.list(dir)) do
local f = dir.."/"..file
if fs.isdir(f) then
fp(f)
elseif f:find("%.lua$") then
local fff = fs.open(f, "r")
if fff then
local data = fff:read(10485760)
if data and data ~= "" then
md5s[#md5s+1] = f..":"..md5.sumhexa(data)
end
fff:close()
end
end
end
end
local t = core.game.getTime()
fp("/mod")
fp("/data")
fp("/engine")
table.sort(md5s)
local fmd5 = md5.sumhexa(table.concat(md5s))
print("[MODULE LOADER] module MD5", fmd5, "computed in ", core.game.getTime() - t)
local hash_valid, hash_err
if mod.short_name ~= "boot" then
hash_valid, hash_err = profile:checkModuleHash(mod.version_name, fmd5)
end
profile:addStatFields(unpack(mod.profile_stats_fields or {}))
profile:loadModuleProfile(mod.short_name)
profile:currentCharacter(mod.name, "game did not tell us")
-- Init the module code
local M, W = mod.load("init")
_G.game = M.new()
_G.game:setPlayerName(name)
-- Load the world, or make a new one
if W then
local save = Savefile.new("")
_G.world = save:loadWorld()
save:close()
if not _G.world then
_G.world = W.new()
end
_G.world:run()
end
-- Load the savefile if it exists, or create a new one if not (or if requested)
local save = engine.Savefile.new(_G.game.save_name)
if save:check() and not new_game then
local delay
_G.game, delay = save:loadGame()
delay()
else
save:delete()
end
save:close()
-- And now run it!
_G.game:run()
-- Add user chat if needed
if mod.allow_userchat and _G.game.key then
profile.chat:setupOnGame()
profile.chat:join("global")
profile.chat:join(mod.short_name)
end
-- Disable the profile if ungood
if mod.short_name ~= "boot" then
if not hash_valid then
game.log("#LIGHT_RED#Online profile disabled(switching to offline profile) due to %s.", hash_err or "???")
end
end
print("[MODULE LOADER] done loading module", mod.long_name)
end
--- Setup write dir for a module
-- Static
function _M:setupWrite(mod)
-- Create module directory
fs.setWritePath(engine.homepath)
fs.mkdir(mod.short_name)
fs.mkdir(mod.short_name.."/save")
-- Enter module directory
local base = engine.homepath .. fs.getPathSeparator() .. mod.short_name
fs.setWritePath(base)
fs.mount(base, "/", false)
return base
end
--- Get a savefile description from the savefile desc.lua file
function _M:loadSavefileDescription(dir)
local ls_def = loadfile(dir.."/desc.lua")
if ls_def then
-- Call the file body inside its own private environment
local ls = {}
setfenv(ls_def, ls)
ls_def()
if not ls.name or not ls.description then return end
ls.dir = dir
return ls
end
end
--- Loads a list of modules from te4.org/modules.lualist
-- Calling this function starts a background thread, which can be waited on by the returned lina object
-- @param src the url to load the list from, if nil it will default to te4.org
-- @return a linda object (see lua lanes documentation) which should be waited upon like this <code>local mylist = l:receive("moduleslist")</code>. Also returns a thread handle
function _M:loadRemoteList(src)
--[[
local l = lanes.linda()
function list_handler(src)
local http = require "socket.http"
local ltn12 = require "ltn12"
local t = {}
print("Downloading modules list from", src)
http.request{url = src, sink = ltn12.sink.table(t)}
local f, err = loadstring(table.concat(t))
if err then
print("Could not load modules list from ", src, ":", err)
l:send("moduleslist", {})
return
end
local list = {}
local dmods = {}
dmods.installableModule = function (t)
local ok = true
if not t.name or not t.long_name or not t.short_name or not t.author then ok = false end
if ok then
list[#list+1] = t
end
end
setfenv(f, dmods)
local ok, err = pcall(f)
if not ok and err then
print("Could not read modules list from ", src, ":", err)
l:send("moduleslist", {})
return
end
for k, e in ipairs(list) do print("[INSTALLABLE MODULE] ", e.name) end
l:send("moduleslist", list)
end
local h = lanes.gen("*", list_handler)(src or "http://te4.org/modules.lualist")
return l, h
]]
local DownloadDialog = require "engine.dialogs.DownloadDialog"
local d = DownloadDialog.new("Fetching updates", "http://te4.org/dl/t-engine/t-engine4-windows-1.0.0beta21.zip")
d:startDownload()
end