1 /** @file system.cpp Action Code Script (ACS) system.
2 *
3 * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright © 2005-2015 Daniel Swanson <danij@dengine.net>
5 * @authors Copyright © 1999 Activision
6 *
7 * @par License
8 * GPL: http://www.gnu.org/licenses/gpl.html
9 *
10 * <small>This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by the
12 * Free Software Foundation; either version 2 of the License, or (at your
13 * option) any later version. This program is distributed in the hope that it
14 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 * Public License for more details. You should have received a copy of the GNU
17 * General Public License along with this program; if not, write to the Free
18 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19 * 02110-1301 USA</small>
20 */
21
22 #include "acs/system.h"
23
24 #include <de/ISerializable>
25 #include <de/Log>
26 #include <de/NativePath>
27 #include "acs/module.h"
28 #include "acs/script.h"
29 #include "gamesession.h"
30
31 using namespace de;
32
33 namespace acs {
34
DENG2_PIMPL_NOREF(System)35 DENG2_PIMPL_NOREF(System)
36 {
37 std::unique_ptr<Module> currentModule;
38 QList<Script *> scripts; ///< Scripts for the current module (if any).
39
40 /**
41 * When a script must be started on a map that is not currently loaded -
42 * a deferred task is enqueued.
43 */
44 class ScriptStartTask : public ISerializable
45 {
46 public:
47 de::Uri mapUri; ///< Unique identifier of the target map.
48 dint32 scriptNumber; ///< Script number to execute on the target map.
49 Script::Args scriptArgs;
50
51 ScriptStartTask() : scriptNumber(-1) {}
52 ScriptStartTask(de::Uri const &mapUri, dint32 scriptNumber, Script::Args const &scriptArgs)
53 : mapUri (mapUri)
54 , scriptNumber(scriptNumber)
55 , scriptArgs (scriptArgs)
56 {}
57
58 static ScriptStartTask *newFromReader(de::Reader &from)
59 {
60 std::unique_ptr<ScriptStartTask> task(new ScriptStartTask);
61 from >> *task;
62 return task.release();
63 }
64
65 void operator >> (de::Writer &to) const
66 {
67 to << mapUri.compose()
68 << scriptNumber;
69 for(dbyte const &arg : scriptArgs) to << arg;
70 }
71
72 void operator << (de::Reader &from)
73 {
74 String mapUriStr;
75 from >> mapUriStr;
76 mapUri = de::makeUri(mapUriStr);
77 if(mapUri.scheme().isEmpty()) mapUri.setScheme("Maps");
78
79 from >> scriptNumber;
80 for(dbyte &arg : scriptArgs) from >> arg;
81 }
82 };
83 QList<ScriptStartTask *> tasks;
84
85 ~Impl()
86 {
87 clearTasks();
88 unloadModule();
89 }
90
91 void unloadModule()
92 {
93 clearScripts();
94 currentModule.release();
95 }
96
97 void clearScripts()
98 {
99 qDeleteAll(scripts); scripts.clear();
100 }
101
102 void makeScripts()
103 {
104 clearScripts();
105
106 currentModule->forAllEntryPoints([this] (Module::EntryPoint const &ep)
107 {
108 scripts << new Script(ep);
109 return LoopContinue;
110 });
111 }
112
113 void clearTasks()
114 {
115 qDeleteAll(tasks); tasks.clear();
116 }
117 };
118
System()119 System::System() : d(new Impl)
120 {
121 mapVars.fill(0);
122 worldVars.fill(0);
123 }
124
reset()125 void System::reset()
126 {
127 d->clearTasks();
128 d->unloadModule();
129 mapVars.fill(0);
130 worldVars.fill(0);
131 }
132
loadModuleForMap(de::Uri const & mapUri)133 void System::loadModuleForMap(de::Uri const &mapUri)
134 {
135 #if __JHEXEN__
136 if(IS_CLIENT) return;
137
138 // Only one module may be loaded at once...
139 d->unloadModule();
140
141 if(mapUri.isEmpty()) return;
142
143 /// @todo Should be using MapManifest here...
144 lumpnum_t const markerLumpNum = CentralLumpIndex().findLast(mapUri.path() + ".lmp");
145 lumpnum_t const moduleLumpNum = markerLumpNum + 11 /*ML_BEHAVIOR*/;
146 if(!CentralLumpIndex().hasLump(moduleLumpNum)) return;
147
148 de::File1 &file = CentralLumpIndex()[moduleLumpNum];
149 if(!Module::recognize(file)) return;
150
151 // Attempt to load the new module.
152 try
153 {
154 d->currentModule.reset(Module::newFromFile(file));
155 d->makeScripts();
156 }
157 catch(Module::FormatError const &er)
158 {
159 // Empty file / invalid bytecode.
160 LOG_SCR_WARNING("File %s:%s does not appear to be valid ACS bytecode\n")
161 << NativePath(file.container().composePath())
162 << file.name()
163 << er.asText();
164 }
165 #else
166 DENG2_UNUSED(mapUri);
167 #endif
168 }
169
module() const170 Module const &System::module() const
171 {
172 DENG2_ASSERT(bool( d->currentModule ));
173 return *d->currentModule;
174 }
175
scriptCount() const176 dint System::scriptCount() const
177 {
178 return d->scripts.count();
179 }
180
hasScript(dint scriptNumber) const181 bool System::hasScript(dint scriptNumber) const
182 {
183 for(Script const *script : d->scripts)
184 {
185 if(script->entryPoint().scriptNumber == scriptNumber)
186 {
187 return true;
188 }
189 }
190 return false;
191 }
192
script(dint scriptNumber) const193 Script &System::script(dint scriptNumber) const
194 {
195 for(Script const *script : d->scripts)
196 {
197 if(script->entryPoint().scriptNumber == scriptNumber)
198 {
199 return *const_cast<Script *>(script);
200 }
201 }
202 /// @throw MissingScriptError Invalid script number specified.
203 throw MissingScriptError("acs::System::script", "Unknown script #" + String::number(scriptNumber));
204 }
205
forAllScripts(std::function<LoopResult (Script &)> func) const206 LoopResult System::forAllScripts(std::function<LoopResult (Script &)> func) const
207 {
208 for(Script *script : d->scripts)
209 {
210 if(auto result = func(*script)) return result;
211 }
212 return LoopContinue;
213 }
214
deferScriptStart(de::Uri const & mapUri,dint scriptNumber,Script::Args const & scriptArgs)215 bool System::deferScriptStart(de::Uri const &mapUri, dint scriptNumber,
216 Script::Args const &scriptArgs)
217 {
218 DENG2_ASSERT(!IS_CLIENT);
219 DENG2_ASSERT(gfw_Session()->mapUri() != mapUri);
220 LOG_AS("acs::System");
221
222 // Don't defer tasks in deathmatch.
223 /// @todo Why the restriction? -ds
224 if (gfw_Rule(deathmatch))
225 return true;
226
227 // Don't allow duplicates.
228 for(Impl::ScriptStartTask const *task : d->tasks)
229 {
230 if(task->scriptNumber == scriptNumber &&
231 task->mapUri == mapUri)
232 {
233 return false;
234 }
235 }
236
237 // Add it to the store to be started when that map is next entered.
238 d->tasks << new Impl::ScriptStartTask(mapUri, scriptNumber, scriptArgs);
239 return true;
240 }
241
serializeWorldState() const242 Block System::serializeWorldState() const
243 {
244 Block data;
245 de::Writer writer(data);
246
247 // Write the world-global variable namespace.
248 for(auto const &var : worldVars) writer << var;
249
250 // Write the deferred task queue.
251 writer << dint32( d->tasks.count() );
252 for(auto const *task : d->tasks) writer << *task;
253
254 return data;
255 }
256
readWorldState(de::Reader & from)257 void System::readWorldState(de::Reader &from)
258 {
259 from.seek(sizeof(duint32)); /// @todo fixme: Where is this being written?
260
261 // Read the world-global variable namespace.
262 for(auto &var : worldVars) from >> var;
263
264 // Read the deferred task queue.
265 d->clearTasks();
266 dint32 numTasks;
267 from >> numTasks;
268 for(dint32 i = 0; i < numTasks; ++i)
269 {
270 d->tasks << Impl::ScriptStartTask::newFromReader(from);
271 }
272 }
273
writeMapState(MapStateWriter * msw) const274 void System::writeMapState(MapStateWriter *msw) const
275 {
276 writer_s *writer = msw->writer();
277
278 // Write each script state.
279 for(auto const *script : d->scripts) script->write(writer);
280
281 // Write each variable.
282 for(auto const &var : mapVars) Writer_WriteInt32(writer, var);
283 }
284
readMapState(MapStateReader * msr)285 void System::readMapState(MapStateReader *msr)
286 {
287 reader_s *reader = msr->reader();
288
289 // Read each script state.
290 for(auto *script : d->scripts) script->read(reader);
291
292 // Read each variable.
293 for(auto &var : mapVars) var = Reader_ReadInt32(reader);
294 }
295
runDeferredTasks(de::Uri const & mapUri)296 void System::runDeferredTasks(de::Uri const &mapUri)
297 {
298 LOG_AS("acs::System");
299 for(dint i = 0; i < d->tasks.count(); ++i)
300 {
301 Impl::ScriptStartTask *task = d->tasks[i];
302 if(task->mapUri != mapUri) continue;
303
304 if(hasScript(task->scriptNumber))
305 {
306 script(task->scriptNumber)
307 .start(task->scriptArgs, nullptr, nullptr, 0, TICSPERSEC);
308 }
309 else
310 {
311 LOG_SCR_WARNING("Unknown script #%i") << task->scriptNumber;
312 }
313
314 delete d->tasks.takeAt(i);
315 i -= 1;
316 }
317 }
318
worldSystemMapChanged()319 void System::worldSystemMapChanged()
320 {
321 mapVars.fill(0);
322
323 for(Script *script : d->scripts)
324 {
325 if(script->entryPoint().startWhenMapBegins)
326 {
327 bool justStarted = script->start(Script::Args()/*default args*/,
328 nullptr, nullptr, 0, TICSPERSEC);
329 DENG2_ASSERT(justStarted);
330 DENG2_UNUSED(justStarted);
331 }
332 }
333 }
334
D_CMD(InspectACScript)335 D_CMD(InspectACScript)
336 {
337 DENG2_UNUSED2(src, argc);
338 System &scriptSys = gfw_Session()->acsSystem();
339 dint const scriptNumber = String(argv[1]).toInt();
340
341 if(!scriptSys.hasScript(scriptNumber))
342 {
343 if(scriptSys.scriptCount())
344 {
345 LOG_SCR_WARNING("Unknown ACScript #%i") << scriptNumber;
346 }
347 else
348 {
349 LOG_SCR_MSG("No ACScripts are currently loaded");
350 }
351 return false;
352 }
353
354 Script const &script = scriptSys.script(scriptNumber);
355 LOG_SCR_MSG("%s\n %s") << script.describe() << script.description();
356 return true;
357 }
358
D_CMD(ListACScripts)359 D_CMD(ListACScripts)
360 {
361 DENG2_UNUSED3(src, argc, argv);
362 System &scriptSys = gfw_Session()->acsSystem();
363
364 if(scriptSys.scriptCount())
365 {
366 LOG_SCR_MSG("Available ACScripts:");
367 scriptSys.forAllScripts([&scriptSys] (Script const &script)
368 {
369 LOG_SCR_MSG(" %s") << script.describe();
370 return LoopContinue;
371 });
372
373 #ifdef DENG2_DEBUG
374 LOG_SCR_MSG("World variables:");
375 dint idx = 0;
376 for(dint const &var : scriptSys.worldVars)
377 {
378 LOG_SCR_MSG(" #%i: %i") << (idx++) << var;
379 }
380
381 LOG_SCR_MSG("Map variables:");
382 idx = 0;
383 for(dint const &var : scriptSys.mapVars)
384 {
385 LOG_SCR_MSG(" #%i: %i") << (idx++) << var;
386 }
387 #endif
388 }
389 else
390 {
391 LOG_SCR_MSG("No ACScripts are currently loaded");
392 }
393 return true;
394 }
395
consoleRegister()396 void System::consoleRegister() // static
397 {
398 C_CMD("inspectacscript", "i", InspectACScript);
399 /* Alias */ C_CMD("scriptinfo", "i", InspectACScript);
400 C_CMD("listacscripts", "", ListACScripts);
401 /* Alias */ C_CMD("scriptinfo", "", ListACScripts);
402 }
403
404 } // namespace acs
405