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