diff --git a/doomsday/api/doomsday.h b/doomsday/api/doomsday.h index 4a726c7008..f6c17bd492 100644 --- a/doomsday/api/doomsday.h +++ b/doomsday/api/doomsday.h @@ -69,6 +69,7 @@ #include "api_server.h" #include "api_sound.h" #include "api_svg.h" +#include "api_uri.h" #include #include diff --git a/doomsday/plugins/common/common.pri b/doomsday/plugins/common/common.pri index a6f8a87e98..0c10981712 100644 --- a/doomsday/plugins/common/common.pri +++ b/doomsday/plugins/common/common.pri @@ -9,6 +9,9 @@ INCLUDEPATH += \ $$DENG_LZSS_DIR/portable/include HEADERS += \ + $$common_inc/acs/interpreter.h \ + $$common_inc/acs/script.h \ + $$common_inc/acs/system.h \ $$common_inc/am_map.h \ $$common_inc/animdefs.h \ $$common_inc/common.h \ @@ -93,6 +96,9 @@ HEADERS += \ $$common_inc/menu/widgets/widget.h SOURCES += \ + $$common_src/acs/interpreter.cpp \ + $$common_src/acs/script.cpp \ + $$common_src/acs/system.cpp \ $$common_src/am_map.c \ $$common_src/animdefs.cpp \ $$common_src/common.c \ @@ -132,7 +138,7 @@ SOURCES += \ $$common_src/p_iterlist.c \ $$common_src/p_map.cpp \ $$common_src/p_mapsetup.cpp \ - $$common_src/p_mapspec.c \ + $$common_src/p_mapspec.cpp \ $$common_src/p_plat.cpp \ $$common_src/p_saveg.cpp \ $$common_src/p_saveio.cpp \ diff --git a/doomsday/plugins/common/include/acs/interpreter.h b/doomsday/plugins/common/include/acs/interpreter.h new file mode 100644 index 0000000000..76e71edefd --- /dev/null +++ b/doomsday/plugins/common/include/acs/interpreter.h @@ -0,0 +1,112 @@ +/** @file interpreter.h Action Code Script (ACS) interpreter. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2005-2015 Daniel Swanson + * @authors Copyright © 1999 Activision + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 2 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, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LIBCOMMON_ACS_INTERPRETER_H +#define LIBCOMMON_ACS_INTERPRETER_H + +#if __cplusplus +# include "acs/script.h" +# include "mapstatereader.h" +# include "mapstatewriter.h" +#endif + +#define ACS_INTERPRETER_MAX_SCRIPT_ARGS 10 +#define ACS_INTERPRETER_SCRIPT_STACK_DEPTH 32 + +#ifdef __cplusplus + +namespace acs { + +class System; + +/** + * Action Code Script (ACS) interpreter (thinker). + * + * @ingroup playsim + */ +struct Interpreter +{ + thinker_t thinker; + struct mobj_s *activator; + Line *line; + int side; + void *_script; + int delayCount; + struct Stack { // Local value stack. + int values[ACS_INTERPRETER_SCRIPT_STACK_DEPTH]; + int height; + + void push(int value); + int pop(); + int top() const; + void drop(); + } locals; + int args[ACS_INTERPRETER_MAX_SCRIPT_ARGS]; + int const *pcodePtr; + + acs::System &scriptSys() const; + + /** + * Returns the Script data for the thinker. + */ + acs::Script &script() const; + + void think(); + + /** + * Deserialize the thinker from the currently open save file. + */ + int read(MapStateReader *msr); + + /** + * Serialize the thinker to the currently open save file. + */ + void write(MapStateWriter *msw) const; + + /** + * @param script Logical ACS script-state instance. + * @param scriptArgs Args passed to the script. + * @param delayCount Delay in tics to wait before interpretation begins. + */ + static thinker_s *newThinker(acs::Script &script, acs::Script::Args const &scriptArgs, + struct mobj_s *activator = nullptr, Line *line = nullptr, + int side = 0, int delayCount = 0); +}; + +} // namespace acs +#endif // __cplusplus + +// C wrapper API --------------------------------------------------------------- + +// Opaque type for interpreter instances. +struct acs_Interpreter; + +#ifdef __cplusplus +extern "C" { +#endif + +void acs_Interpreter_Think(acs_Interpreter *interp); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // LIBCOMMON_ACS_INTERPRETER_H diff --git a/doomsday/plugins/common/include/acs/script.h b/doomsday/plugins/common/include/acs/script.h new file mode 100644 index 0000000000..a99a5fc776 --- /dev/null +++ b/doomsday/plugins/common/include/acs/script.h @@ -0,0 +1,174 @@ +/** @file script.h Action Code Script (ACS), script model. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2005-2015 Daniel Swanson + * @authors Copyright © 1999 Activision + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 2 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, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LIBCOMMON_ACS_SCRIPT_H +#define LIBCOMMON_ACS_SCRIPT_H + +#include "common.h" +#include +#include +#include +#include + +namespace acs { + +class System; + +/** + * Models the high-level state of an Action Code Script (ACS). + * + * @ingroup playsim + */ +class Script +{ +public: + /** + * Stores information about an ACS byte/p-code entry point. + */ + struct EntryPoint + { + int const *pcodePtr = nullptr; + bool startWhenMapBegins = false; + int scriptNumber = 0; + int scriptArgCount = 0; + }; + + /** + * Script arguments. + */ + class Args : public std::array + { + public: + Args(); + Args(de::dbyte const *cArr, de::dint length); + }; + + /** + * Logical script states. + */ + enum State { + Inactive, + Running, + Suspended, + + WaitingForSector, + WaitingForPolyobj, + WaitingForScript, + + Terminating + }; + static de::String stateAsText(State state); + +public: + Script(); + Script(EntryPoint const &ep); + + /** + * Composes the human-friendly, styled, textual name of the object. + */ + de::String describe() const; + + /** + * Composes a human-friendly, styled, textual description of the script. + */ + de::String description() const; + + /** + * Start/resume script interpretation if inactive/suspended. + * + * If currently suspended the script is instructed to resume (deferred). + * + * Otherwise instantiate a new script Interpreter and add it to the list of + * thinkers for the @em current map. + * + * @param args Script argument values. + * @param activator Mobj activator, if any (can be @c nullptr). + * @param line Line activator, if any (can be @c nullptr). + * @param side Line side number. + * @param delayTics Number of tics to wait before interpretation begins. + * (Can be used to delay processing during map startup). + * + * @return @c true if started/resumed. + */ + bool start(Args const &args, mobj_t *activator = nullptr, + Line *line = nullptr, int side = 0, int delayTics = 0); + + /** + * Instruct the script to self-suspend if running (deferred). + * + * @return @c true if marked for suspension. + */ + bool suspend(); + + /** + * Instruct the script to self-terminate if running (deferred). + * + * @return @c true if marked for termination. + */ + bool terminate(); + + /** + * Returns the current logical state of the script (FYI). + */ + State state() const; + + bool isRunning() const; + bool isSuspended() const; + bool isWaiting() const; + + void waitForPolyobj(int tag); + void waitForScript (int number); + void waitForSector (int tag); + + void polyobjFinished(int tag); + void sectorFinished (int tag); + + /** + * Returns the entry point info for the script. + */ + EntryPoint const &entryPoint() const; + + /** + * Reconfigure the entry point info for the script. + */ + void applyEntryPoint(EntryPoint const &epToCopy); + + void read(Reader *reader); + void write(Writer *writer) const; + +public: /// @todo make private: + + /** + * Resume @em this script if it is waiting on @a other (which has just terminated). + * + * @param other Script to be considered. + */ + void resumeIfWaitingForScript(Script const &other); + + void setState(State newState); + +private: + DENG2_PRIVATE(d) +}; + +} // namespace acs + +#endif // LIBCOMMON_ACS_SCRIPT_H diff --git a/doomsday/plugins/common/include/acs/system.h b/doomsday/plugins/common/include/acs/system.h new file mode 100644 index 0000000000..c6bcd0ad4e --- /dev/null +++ b/doomsday/plugins/common/include/acs/system.h @@ -0,0 +1,161 @@ +/** @file system.h Action Code Script (ACS) system. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2005-2015 Daniel Swanson + * @authors Copyright © 1999 Activision + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 2 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, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef COMMON_ACS_SYSTEM_H +#define COMMON_ACS_SYSTEM_H + +#include "common.h" +#ifdef __cplusplus +# include +# include +# include +# include +# include +# include +# include +# include +# include "acs/interpreter.h" +# include "acs/script.h" +# include "mapstatereader.h" +# include "mapstatewriter.h" + +namespace acs { + +/** + * Action Code Script (ACS) system. + * + * @ingroup playsim + */ +class System +{ +public: /// @todo make private: + std::array mapVars; + std::array worldVars; + +public: + /// Required/referenced script is missing. @ingroup errors + DENG2_ERROR(MissingScriptError); + + /// Required/referenced string-constant is missing. @ingroup errors + DENG2_ERROR(MissingStringError); + +public: + System(); + + /** + * To be called when a new game session begins to reset the system. All + * global scripting variables are discarded and deferred-tasks purged. + */ + void reset(); + + /** + * Load new ACS bytecode from the specified @a file. + */ + void loadBytecode(de::File1 &file); + + /** + * Returns the total number of script entry points in the loaded bytecode. + */ + int scriptCount() const; + + /** + * Returns @c true iff @a scriptNumber is a known entry point. + */ + bool hasScript(int scriptNumber); + + /** + * Lookup the Script info for the given @a scriptNumber. + */ + Script &script(int scriptNumber) const; + + /** + * Iterate through the Scripts of the loaded bytecode. + * + * @param func Callback to make for each Script. + */ + de::LoopResult forAllScripts(std::function func) const; + + /** + * Defer a script start task until the identified map is next current. + * + * @param mapUri Unique identifier of the map on which to start the script. + * + * @return @c true iff a script was newly started (or deferred). + */ + bool deferScriptStart(de::Uri const &mapUri, int scriptNumber, Script::Args const &args); + + /** + * Provides readonly access to the loaded bytecode. + */ + byte const *pcode() const; + + /** + * Provides readonly access to a string constant from the loaded bytecode. + */ + de::String stringConstant(int stringNumber) const; + + de::Block serializeWorldState() const; + void readWorldState(de::Reader &from); + + void writeMapState(MapStateWriter *msw) const; + void readMapState(MapStateReader *msr); + +public: /// @todo make private: ----------------------------------------------- + + /** + * To be called when the current map changes to activate any deferred scripts + * which should now begin/resume. + */ + void runDeferredTasks(de::Uri const &mapUri); + +public: + /** + * Register the console commands and variables of this module. + */ + static void consoleRegister(); + +private: + DENG2_PRIVATE(d) +}; + +} // namespace acs + +/** + * Returns the game's global acs::System. + */ +acs::System &Game_ACScriptSystem(); + +#endif // __cplusplus + +// C wrapper API: -------------------------------------------------------------- + +#ifdef __cplusplus +extern "C" { +#endif + +dd_bool Game_ACScriptSystem_StartScript(int scriptNumber, Uri const *mapUri, + byte const args[4], struct mobj_s *activator, Line *line, int side); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // COMMON_ACS_SYSTEM_H diff --git a/doomsday/plugins/common/include/common.h b/doomsday/plugins/common/include/common.h index a2cdbc8304..49e61afa30 100644 --- a/doomsday/plugins/common/include/common.h +++ b/doomsday/plugins/common/include/common.h @@ -46,13 +46,13 @@ # include "jhexen.h" #endif +#ifdef __cplusplus +# include +#endif #include "gamerules.h" #include "g_defs.h" #include "pause.h" #include "p_mapsetup.h" -#ifdef __cplusplus -# include -#endif #ifdef __cplusplus extern "C" { diff --git a/doomsday/plugins/common/include/d_net.h b/doomsday/plugins/common/include/d_net.h index 5b6e50bf71..a48afc516e 100644 --- a/doomsday/plugins/common/include/d_net.h +++ b/doomsday/plugins/common/include/d_net.h @@ -23,15 +23,14 @@ #ifndef LIBCOMMON_NETWORK_DEF_H #define LIBCOMMON_NETWORK_DEF_H -#include "dd_share.h" +#include "doomsday.h" #include #include #ifdef __cplusplus # include +# include #endif -#include "common.h" - #define NETBUFFER_MAXMESSAGE 255 #ifdef __JHEXEN__ @@ -231,7 +230,7 @@ long int D_NetPlayerEvent(int plrNumber, int peType, void *data); * @return @c true = no further processing of the damage should be done else, process the * damage as normally. */ -dd_bool D_NetDamageMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, int damage); +dd_bool D_NetDamageMobj(struct mobj_s *target, struct mobj_s *inflictor, struct mobj_s *source, int damage); int D_NetWorldEvent(int type, int tic, void *data); @@ -267,7 +266,4 @@ de::String D_NetDefaultEpisode(); de::Uri D_NetDefaultMap(); #endif -#include "d_netsv.h" -#include "d_netcl.h" - -#endif // LIBCOMMON_NETWORK_DEF_H +#endif // LIBCOMMON_NETWORK_DEF_H diff --git a/doomsday/plugins/common/include/d_netcl.h b/doomsday/plugins/common/include/d_netcl.h index b7cb7f29d7..5d2953b27f 100644 --- a/doomsday/plugins/common/include/d_netcl.h +++ b/doomsday/plugins/common/include/d_netcl.h @@ -22,7 +22,8 @@ #ifndef LIBCOMMON_D_NETCL_H #define LIBCOMMON_D_NETCL_H -#include "d_net.h" +#include "common.h" +#include #ifdef __cplusplus extern "C" { diff --git a/doomsday/plugins/common/include/g_common.h b/doomsday/plugins/common/include/g_common.h index 483129afd3..342c71c3a4 100644 --- a/doomsday/plugins/common/include/g_common.h +++ b/doomsday/plugins/common/include/g_common.h @@ -22,10 +22,10 @@ #define LIBCOMMON_GAME_H #include "dd_share.h" +#include #include "fi_lib.h" #include "mobj.h" #include "player.h" -#include #if __cplusplus class SaveSlots; @@ -216,4 +216,4 @@ D_CMD( CCmdExitLevel ); DENG_EXTERN_C dd_bool singledemo; -#endif // LIBCOMMON_GAME_H +#endif // LIBCOMMON_GAME_H diff --git a/doomsday/plugins/common/include/mapstatereader.h b/doomsday/plugins/common/include/mapstatereader.h index c9e7154dd9..968cbb23ab 100644 --- a/doomsday/plugins/common/include/mapstatereader.h +++ b/doomsday/plugins/common/include/mapstatereader.h @@ -21,10 +21,11 @@ #ifndef LIBCOMMON_MAPSTATEREADER_H #define LIBCOMMON_MAPSTATEREADER_H -#include "common.h" -#include "thingarchive.h" +#include #include #include +#include "common.h" +#include "thingarchive.h" /** * Performs native saved game map state deserialization. diff --git a/doomsday/plugins/common/include/mobj.h b/doomsday/plugins/common/include/mobj.h index 85b601c572..1a01f34feb 100644 --- a/doomsday/plugins/common/include/mobj.h +++ b/doomsday/plugins/common/include/mobj.h @@ -22,7 +22,7 @@ #ifndef LIBCOMMON_MOBJ #define LIBCOMMON_MOBJ -#include "g_common.h" +#include "common.h" #ifdef __cplusplus extern "C" { diff --git a/doomsday/plugins/common/include/p_mapspec.h b/doomsday/plugins/common/include/p_mapspec.h index 208d2c7c1e..78e26fbc1f 100644 --- a/doomsday/plugins/common/include/p_mapspec.h +++ b/doomsday/plugins/common/include/p_mapspec.h @@ -1,7 +1,9 @@ -/** @file p_mapspec.h Crossed line special list utilities. +/** @file p_mapspec.h Crossed line special list utilities. + * + * Line Tag handling. Line and Sector groups. Specialized iterators, etc... * * @authors Copyright © 2003-2013 Jaakko Keränen - * @authors Copyright © 2005-2013 Daniel Swanson + * @authors Copyright © 2005-2015 Daniel Swanson * * @par License * GPL: http://www.gnu.org/licenses/gpl.html @@ -34,8 +36,13 @@ extern "C" { /// Recursively traverse adjacent sectors, sound blocking lines cut off traversal. void P_RecursiveSound(struct mobj_s *soundTarget, Sector *sec, int soundBlocks); +dd_bool P_SectorTagIsBusy(int tag); + +void P_NotifySectorFinished(int tag); +void P_NotifyPolyobjFinished(int tag); + #ifdef __cplusplus } // extern "C" #endif -#endif // LIBCOMMON_PLAYSIM_MAP_SPECIAL_H +#endif // LIBCOMMON_PLAYSIM_MAP_SPECIAL_H diff --git a/doomsday/plugins/common/include/p_xgline.h b/doomsday/plugins/common/include/p_xgline.h index bb9616de42..d55d33c68f 100644 --- a/doomsday/plugins/common/include/p_xgline.h +++ b/doomsday/plugins/common/include/p_xgline.h @@ -32,9 +32,10 @@ #include "doomsday.h" #include "xgclass.h" + #ifdef __cplusplus -# include "mapstatereader.h" -# include "mapstatewriter.h" +class MapStateReader; +class MapStateWriter; #endif // Line type classes. Add new classes to the end! diff --git a/doomsday/plugins/common/include/p_xgsec.h b/doomsday/plugins/common/include/p_xgsec.h index c9d6d59946..0c47ea21a2 100644 --- a/doomsday/plugins/common/include/p_xgsec.h +++ b/doomsday/plugins/common/include/p_xgsec.h @@ -24,8 +24,11 @@ #include "g_common.h" #ifdef __cplusplus # include -# include "mapstatereader.h" -# include "mapstatewriter.h" +#endif + +#ifdef __cplusplus +class MapStateReader; +class MapStateWriter; #endif // Sector chain event types. diff --git a/doomsday/plugins/common/include/saveslots.h b/doomsday/plugins/common/include/saveslots.h index 3c19f954a0..7761856b34 100644 --- a/doomsday/plugins/common/include/saveslots.h +++ b/doomsday/plugins/common/include/saveslots.h @@ -23,6 +23,7 @@ #include #include #include +#include /** * Maps saved game session file names into a finite set of "save slots". @@ -193,4 +194,4 @@ class SaveSlots typedef SaveSlots::Slot SaveSlot; -#endif // LIBCOMMON_SAVESLOTS_H +#endif // LIBCOMMON_SAVESLOTS_H diff --git a/doomsday/plugins/common/src/acs/interpreter.cpp b/doomsday/plugins/common/src/acs/interpreter.cpp new file mode 100644 index 0000000000..ee3742ccee --- /dev/null +++ b/doomsday/plugins/common/src/acs/interpreter.cpp @@ -0,0 +1,1299 @@ +/** @file interpreter.cpp Action Code Script (ACS), interpreter. + * + * @authors Copyright © 2003-2013 Jaakko Keränen + * @authors Copyright © 2005-2013 Daniel Swanson + * @authors Copyright © 1999 Activision + * + * @par License + * GPL: http://www.gnu.org/licenses/gpl.html + * + * 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 2 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, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "acs/interpreter.h" + +#include +#include "acs/system.h" +#include "dmu_lib.h" +#include "g_common.h" +#include "gamesession.h" +#include "player.h" +#include "p_map.h" +#include "p_saveg.h" +#include "p_saveio.h" +#include "p_sound.h" + +using namespace de; + +namespace internal { + + /// Bytecode command return value. + enum CommandResult + { + Continue, + Stop, + Terminate + }; + + typedef CommandResult (*CommandFunc) (acs::Interpreter &); + +/// Helper macro for declaring an ACS command (function callback). +#define ACS_COMMAND(Name) CommandResult cmd##Name(acs::Interpreter &interp) + + static String printBuffer; + static byte specArgs[5]; + +#ifdef __JHEXEN__ + + ACS_COMMAND(NOP) + { + DENG2_UNUSED(interp); + return Continue; + } + + ACS_COMMAND(Terminate) + { + DENG2_UNUSED(interp); + return Terminate; + } + + ACS_COMMAND(Suspend) + { + interp.script().setState(acs::Script::Suspended); + return Stop; + } + + ACS_COMMAND(PushNumber) + { + interp.locals.push(LONG(*interp.pcodePtr++)); + return Continue; + } + + ACS_COMMAND(LSpec1) + { + int special = LONG(*interp.pcodePtr++); + specArgs[0] = interp.locals.pop(); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec2) + { + int special = LONG(*interp.pcodePtr++); + specArgs[1] = interp.locals.pop(); + specArgs[0] = interp.locals.pop(); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec3) + { + int special = LONG(*interp.pcodePtr++); + specArgs[2] = interp.locals.pop(); + specArgs[1] = interp.locals.pop(); + specArgs[0] = interp.locals.pop(); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec4) + { + int special = LONG(*interp.pcodePtr++); + specArgs[3] = interp.locals.pop(); + specArgs[2] = interp.locals.pop(); + specArgs[1] = interp.locals.pop(); + specArgs[0] = interp.locals.pop(); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec5) + { + int special = LONG(*interp.pcodePtr++); + specArgs[4] = interp.locals.pop(); + specArgs[3] = interp.locals.pop(); + specArgs[2] = interp.locals.pop(); + specArgs[1] = interp.locals.pop(); + specArgs[0] = interp.locals.pop(); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, + interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec1Direct) + { + int special = LONG(*interp.pcodePtr++); + specArgs[0] = LONG(*interp.pcodePtr++); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, + interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec2Direct) + { + int special = LONG(*interp.pcodePtr++); + specArgs[0] = LONG(*interp.pcodePtr++); + specArgs[1] = LONG(*interp.pcodePtr++); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, + interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec3Direct) + { + int special = LONG(*interp.pcodePtr++); + specArgs[0] = LONG(*interp.pcodePtr++); + specArgs[1] = LONG(*interp.pcodePtr++); + specArgs[2] = LONG(*interp.pcodePtr++); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, + interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec4Direct) + { + int special = LONG(*interp.pcodePtr++); + specArgs[0] = LONG(*interp.pcodePtr++); + specArgs[1] = LONG(*interp.pcodePtr++); + specArgs[2] = LONG(*interp.pcodePtr++); + specArgs[3] = LONG(*interp.pcodePtr++); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, + interp.activator); + + return Continue; + } + + ACS_COMMAND(LSpec5Direct) + { + int special = LONG(*interp.pcodePtr++); + specArgs[0] = LONG(*interp.pcodePtr++); + specArgs[1] = LONG(*interp.pcodePtr++); + specArgs[2] = LONG(*interp.pcodePtr++); + specArgs[3] = LONG(*interp.pcodePtr++); + specArgs[4] = LONG(*interp.pcodePtr++); + P_ExecuteLineSpecial(special, specArgs, interp.line, interp.side, + interp.activator); + + return Continue; + } + + ACS_COMMAND(Add) + { + interp.locals.push(interp.locals.pop() + interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(Subtract) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() - operand2); + return Continue; + } + + ACS_COMMAND(Multiply) + { + interp.locals.push(interp.locals.pop() * interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(Divide) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() / operand2); + return Continue; + } + + ACS_COMMAND(Modulus) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() % operand2); + return Continue; + } + + ACS_COMMAND(EQ) + { + interp.locals.push(interp.locals.pop() == interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(NE) + { + interp.locals.push(interp.locals.pop() != interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(LT) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() < operand2); + return Continue; + } + + ACS_COMMAND(GT) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() > operand2); + return Continue; + } + + ACS_COMMAND(LE) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() <= operand2); + return Continue; + } + + ACS_COMMAND(GE) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() >= operand2); + return Continue; + } + + ACS_COMMAND(AssignScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)] = interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(AssignMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)] = interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(AssignWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)] = interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(PushScriptVar) + { + interp.locals.push(interp.args[LONG(*interp.pcodePtr++)]); + return Continue; + } + + ACS_COMMAND(PushMapVar) + { + interp.locals.push(interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)]); + return Continue; + } + + ACS_COMMAND(PushWorldVar) + { + interp.locals.push(interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)]); + return Continue; + } + + ACS_COMMAND(AddScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)] += interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(AddMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)] += interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(AddWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)] += interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(SubScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)] -= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(SubMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)] -= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(SubWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)] -= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(MulScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)] *= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(MulMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)] *= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(MulWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)] *= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(DivScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)] /= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(DivMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)] /= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(DivWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)] /= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(ModScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)] %= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(ModMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)] %= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(ModWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)] %= interp.locals.pop(); + return Continue; + } + + ACS_COMMAND(IncScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)]++; + return Continue; + } + + ACS_COMMAND(IncMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)]++; + return Continue; + } + + ACS_COMMAND(IncWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)]++; + return Continue; + } + + ACS_COMMAND(DecScriptVar) + { + interp.args[LONG(*interp.pcodePtr++)]--; + return Continue; + } + + ACS_COMMAND(DecMapVar) + { + interp.scriptSys().mapVars[LONG(*interp.pcodePtr++)]--; + return Continue; + } + + ACS_COMMAND(DecWorldVar) + { + interp.scriptSys().worldVars[LONG(*interp.pcodePtr++)]--; + return Continue; + } + + ACS_COMMAND(Goto) + { + interp.pcodePtr = (int *) (interp.scriptSys().pcode() + LONG(*interp.pcodePtr)); + return Continue; + } + + ACS_COMMAND(IfGoto) + { + if(interp.locals.pop()) + { + interp.pcodePtr = (int *) (interp.scriptSys().pcode() + LONG(*interp.pcodePtr)); + } + else + { + interp.pcodePtr++; + } + return Continue; + } + + ACS_COMMAND(Drop) + { + interp.locals.drop(); + return Continue; + } + + ACS_COMMAND(Delay) + { + interp.delayCount = interp.locals.pop(); + return Stop; + } + + ACS_COMMAND(DelayDirect) + { + interp.delayCount = LONG(*interp.pcodePtr++); + return Stop; + } + + ACS_COMMAND(Random) + { + int high = interp.locals.pop(); + int low = interp.locals.pop(); + interp.locals.push(low + (P_Random() % (high - low + 1))); + return Continue; + } + + ACS_COMMAND(RandomDirect) + { + int low = LONG(*interp.pcodePtr++); + int high = LONG(*interp.pcodePtr++); + interp.locals.push(low + (P_Random() % (high - low + 1))); + return Continue; + } + + ACS_COMMAND(ThingCount) + { + int tid = interp.locals.pop(); + int type = interp.locals.pop(); + // Anything to count? + if(type + tid) + { + interp.locals.push(P_MobjCount(type, tid)); + } + return Continue; + } + + ACS_COMMAND(ThingCountDirect) + { + int type = LONG(*interp.pcodePtr++); + int tid = LONG(*interp.pcodePtr++); + // Anything to count? + if(type + tid) + { + interp.locals.push(P_MobjCount(type, tid)); + } + return Continue; + } + + ACS_COMMAND(TagWait) + { + interp.script().waitForSector(interp.locals.pop()); + return Stop; + } + + ACS_COMMAND(TagWaitDirect) + { + interp.script().waitForSector(LONG(*interp.pcodePtr++)); + return Stop; + } + + ACS_COMMAND(PolyWait) + { + interp.script().waitForPolyobj(interp.locals.pop()); + return Stop; + } + + ACS_COMMAND(PolyWaitDirect) + { + interp.script().waitForPolyobj(LONG(*interp.pcodePtr++)); + return Stop; + } + + ACS_COMMAND(ChangeFloor) + { + AutoStr *path = Str_PercentEncode(AutoStr_FromTextStd(interp.scriptSys().stringConstant(interp.locals.pop()).toUtf8().constData())); + uri_s *uri = Uri_NewWithPath3("Flats", Str_Text(path)); + + Material *mat = (Material *) P_ToPtr(DMU_MATERIAL, Materials_ResolveUri(uri)); + Uri_Delete(uri); + + int tag = interp.locals.pop(); + + if(iterlist_t *list = P_GetSectorIterListForTag(tag, false)) + { + IterList_SetIteratorDirection(list, ITERLIST_FORWARD); + IterList_RewindIterator(list); + + Sector *sec; + while((sec = (Sector *) IterList_MoveIterator(list))) + { + P_SetPtrp(sec, DMU_FLOOR_MATERIAL, mat); + } + } + + return Continue; + } + + ACS_COMMAND(ChangeFloorDirect) + { + int tag = LONG(*interp.pcodePtr++); + + AutoStr *path = Str_PercentEncode(AutoStr_FromTextStd(interp.scriptSys().stringConstant(LONG(*interp.pcodePtr++)).toUtf8().constData())); + uri_s *uri = Uri_NewWithPath3("Flats", Str_Text(path)); + + Material *mat = (Material *) P_ToPtr(DMU_MATERIAL, Materials_ResolveUri(uri)); + Uri_Delete(uri); + + if(iterlist_t *list = P_GetSectorIterListForTag(tag, false)) + { + IterList_SetIteratorDirection(list, ITERLIST_FORWARD); + IterList_RewindIterator(list); + + Sector *sec; + while((sec = (Sector *) IterList_MoveIterator(list))) + { + P_SetPtrp(sec, DMU_FLOOR_MATERIAL, mat); + } + } + + return Continue; + } + + ACS_COMMAND(ChangeCeiling) + { + AutoStr *path = Str_PercentEncode(AutoStr_FromTextStd(interp.scriptSys().stringConstant(interp.locals.pop()).toUtf8().constData())); + uri_s *uri = Uri_NewWithPath3("Flats", Str_Text(path)); + + Material *mat = (Material *) P_ToPtr(DMU_MATERIAL, Materials_ResolveUri(uri)); + Uri_Delete(uri); + + int tag = interp.locals.pop(); + + if(iterlist_t *list = P_GetSectorIterListForTag(tag, false)) + { + IterList_SetIteratorDirection(list, ITERLIST_FORWARD); + IterList_RewindIterator(list); + + Sector *sec; + while((sec = (Sector *) IterList_MoveIterator(list))) + { + P_SetPtrp(sec, DMU_CEILING_MATERIAL, mat); + } + } + + return Continue; + } + + ACS_COMMAND(ChangeCeilingDirect) + { + int tag = LONG(*interp.pcodePtr++); + + AutoStr *path = Str_PercentEncode(AutoStr_FromTextStd(interp.scriptSys().stringConstant(LONG(*interp.pcodePtr++)).toUtf8().constData())); + uri_s *uri = Uri_NewWithPath3("Flats", Str_Text(path)); + + Material *mat = (Material *) P_ToPtr(DMU_MATERIAL, Materials_ResolveUri(uri)); + Uri_Delete(uri); + + if(iterlist_t *list = P_GetSectorIterListForTag(tag, false)) + { + IterList_SetIteratorDirection(list, ITERLIST_FORWARD); + IterList_RewindIterator(list); + + Sector *sec; + while((sec = (Sector *) IterList_MoveIterator(list))) + { + P_SetPtrp(sec, DMU_CEILING_MATERIAL, mat); + } + } + + return Continue; + } + + ACS_COMMAND(Restart) + { + interp.pcodePtr = interp.script().entryPoint().pcodePtr; + return Continue; + } + + ACS_COMMAND(AndLogical) + { + interp.locals.push(interp.locals.pop() && interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(OrLogical) + { + interp.locals.push(interp.locals.pop() || interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(AndBitwise) + { + interp.locals.push(interp.locals.pop() & interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(OrBitwise) + { + interp.locals.push(interp.locals.pop() | interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(EorBitwise) + { + interp.locals.push(interp.locals.pop() ^ interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(NegateLogical) + { + interp.locals.push(!interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(LShift) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() << operand2); + return Continue; + } + + ACS_COMMAND(RShift) + { + int operand2 = interp.locals.pop(); + interp.locals.push(interp.locals.pop() >> operand2); + return Continue; + } + + ACS_COMMAND(UnaryMinus) + { + interp.locals.push(-interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(IfNotGoto) + { + if(interp.locals.pop()) + { + interp.pcodePtr++; + } + else + { + interp.pcodePtr = (int *) (interp.scriptSys().pcode() + LONG(*interp.pcodePtr)); + } + return Continue; + } + + ACS_COMMAND(LineSide) + { + interp.locals.push(interp.side); + return Continue; + } + + ACS_COMMAND(ScriptWait) + { + interp.script().waitForScript(interp.locals.pop()); + return Stop; + } + + ACS_COMMAND(ScriptWaitDirect) + { + interp.script().waitForScript(LONG(*interp.pcodePtr++)); + return Stop; + } + + ACS_COMMAND(ClearLineSpecial) + { + if(interp.line) + { + P_ToXLine(interp.line)->special = 0; + } + return Continue; + } + + ACS_COMMAND(CaseGoto) + { + if(interp.locals.top() == LONG(*interp.pcodePtr++)) + { + interp.pcodePtr = (int *) (interp.scriptSys().pcode() + LONG(*interp.pcodePtr)); + interp.locals.drop(); + } + else + { + interp.pcodePtr++; + } + return Continue; + } + + ACS_COMMAND(BeginPrint) + { + DENG2_UNUSED(interp); + printBuffer.clear(); + return Continue; + } + + ACS_COMMAND(EndPrint) + { + if(interp.activator && interp.activator->player) + { + P_SetMessage(interp.activator->player, 0, printBuffer.toUtf8().constData()); + } + else + { + // Send to everybody. + for(int i = 0; i < MAXPLAYERS; ++i) + { + if(players[i].plr->inGame) + { + P_SetMessage(&players[i], 0, printBuffer.toUtf8().constData()); + } + } + } + + return Continue; + } + + ACS_COMMAND(EndPrintBold) + { + DENG2_UNUSED(interp); + for(int i = 0; i < MAXPLAYERS; ++i) + { + if(players[i].plr->inGame) + { + P_SetYellowMessage(&players[i], 0, printBuffer.toUtf8().constData()); + } + } + return Continue; + } + + ACS_COMMAND(PrintString) + { + printBuffer += interp.scriptSys().stringConstant(interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(PrintNumber) + { + printBuffer += String::number(interp.locals.pop()); + return Continue; + } + + ACS_COMMAND(PrintCharacter) + { + char ch[2]; + ch[0] = interp.locals.pop(); + ch[1] = 0; + printBuffer += String(ch); + return Continue; + } + + ACS_COMMAND(PlayerCount) + { + int count = 0; + for(int i = 0; i < MAXPLAYERS; ++i) + { + count += players[i].plr->inGame; + } + interp.locals.push(count); + return Continue; + } + + ACS_COMMAND(GameType) + { + int gametype; + + if(!IS_NETGAME) + { + gametype = 0; // singleplayer + } + else if(COMMON_GAMESESSION->rules().deathmatch) + { + gametype = 2; // deathmatch + } + else + { + gametype = 1; // cooperative + } + interp.locals.push(gametype); + + return Continue; + } + + ACS_COMMAND(GameSkill) + { + interp.locals.push((int)COMMON_GAMESESSION->rules().skill); + return Continue; + } + + ACS_COMMAND(Timer) + { + interp.locals.push(mapTime); + return Continue; + } + + ACS_COMMAND(SectorSound) + { + mobj_t *emitter = nullptr; + if(interp.line) + { + auto *sector = (Sector *) P_GetPtrp(interp.line, DMU_FRONT_SECTOR); + emitter = (mobj_t *) P_GetPtrp(sector, DMU_EMITTER); + } + int volume = interp.locals.pop(); + + S_StartSoundAtVolume(S_GetSoundID(interp.scriptSys().stringConstant(interp.locals.pop()).toUtf8().constData()), + emitter, volume / 127.0f); + return Continue; + } + + ACS_COMMAND(ThingSound) + { + int volume = interp.locals.pop(); + int sound = S_GetSoundID(interp.scriptSys().stringConstant(interp.locals.pop()).toUtf8().constData()); + int tid = interp.locals.pop(); + int searcher = -1; + + mobj_t *emitter; + while(sound && (emitter = P_FindMobjFromTID(tid, &searcher))) + { + S_StartSoundAtVolume(sound, emitter, volume / 127.0f); + } + + return Continue; + } + + ACS_COMMAND(AmbientSound) + { + mobj_t *emitter = nullptr; // For 3D positioning. + mobj_t *plrMo = players[DISPLAYPLAYER].plr->mo; + + int volume = interp.locals.pop(); + + // If we are playing 3D sounds, create a temporary source mobj for the sound. + if(Con_GetInteger("sound-3d") && plrMo) + { + // SpawnMobj calls P_Random. We don't want that the random generator gets + // out of sync. + if((emitter = P_SpawnMobjXYZ(MT_CAMERA, + plrMo->origin[VX] + ((M_Random() - 127) * 2), + plrMo->origin[VY] + ((M_Random() - 127) * 2), + plrMo->origin[VZ] + ((M_Random() - 127) * 2), + 0, 0))) + { + emitter->tics = 5 * TICSPERSEC; // Five seconds should be enough. + } + } + + int sound = S_GetSoundID(interp.scriptSys().stringConstant(interp.locals.pop()).toUtf8().constData()); + S_StartSoundAtVolume(sound, emitter, volume / 127.0f); + + return Continue; + } + + ACS_COMMAND(SoundSequence) + { + mobj_t *emitter = nullptr; + if(interp.line) + { + auto *sector = (Sector *) P_GetPtrp(interp.line, DMU_FRONT_SECTOR); + emitter = (mobj_t *) P_GetPtrp(sector, DMU_EMITTER); + } + SN_StartSequenceName(emitter, interp.scriptSys().stringConstant(interp.locals.pop()).toUtf8().constData()); + + return Continue; + } + + ACS_COMMAND(SetLineTexture) + { +#define TEXTURE_TOP 0 +#define TEXTURE_MIDDLE 1 +#define TEXTURE_BOTTOM 2 + + AutoStr *path = Str_PercentEncode(AutoStr_FromTextStd(interp.scriptSys().stringConstant(interp.locals.pop()).toUtf8().constData())); + uri_s *uri = Uri_NewWithPath3("Textures", Str_Text(path)); + + Material *mat = (Material *) P_ToPtr(DMU_MATERIAL, Materials_ResolveUri(uri)); + Uri_Delete(uri); + + int position = interp.locals.pop(); + int side = interp.locals.pop(); + int lineTag = interp.locals.pop(); + + if(iterlist_t *list = P_GetLineIterListForTag(lineTag, false)) + { + IterList_SetIteratorDirection(list, ITERLIST_FORWARD); + IterList_RewindIterator(list); + + Line *line; + while((line = (Line *) IterList_MoveIterator(list))) + { + Side *sdef = (Side *) P_GetPtrp(line, (side == 0? DMU_FRONT : DMU_BACK)); + + if(position == TEXTURE_MIDDLE) + { + P_SetPtrp(sdef, DMU_MIDDLE_MATERIAL, mat); + } + else if(position == TEXTURE_BOTTOM) + { + P_SetPtrp(sdef, DMU_BOTTOM_MATERIAL, mat); + } + else // TEXTURE_TOP + { + P_SetPtrp(sdef, DMU_TOP_MATERIAL, mat); + } + } + } + + return Continue; + +#undef TEXTURE_BOTTOM +#undef TEXTURE_MIDDLE +#undef TEXTURE_TOP + } + + ACS_COMMAND(SetLineBlocking) + { + int lineFlags = interp.locals.pop()? DDLF_BLOCKING : 0; + int lineTag = interp.locals.pop(); + + if(iterlist_t *list = P_GetLineIterListForTag(lineTag, false)) + { + IterList_SetIteratorDirection(list, ITERLIST_FORWARD); + IterList_RewindIterator(list); + + Line *line; + while((line = (Line *) IterList_MoveIterator(list))) + { + P_SetIntp(line, DMU_FLAGS, (P_GetIntp(line, DMU_FLAGS) & ~DDLF_BLOCKING) | lineFlags); + } + } + + return Continue; + } + + ACS_COMMAND(SetLineSpecial) + { + int arg5 = interp.locals.pop(); + int arg4 = interp.locals.pop(); + int arg3 = interp.locals.pop(); + int arg2 = interp.locals.pop(); + int arg1 = interp.locals.pop(); + int special = interp.locals.pop(); + int lineTag = interp.locals.pop(); + + if(iterlist_t *list = P_GetLineIterListForTag(lineTag, false)) + { + IterList_SetIteratorDirection(list, ITERLIST_FORWARD); + IterList_RewindIterator(list); + + Line *line; + while((line = (Line *) IterList_MoveIterator(list))) + { + xline_t *xline = P_ToXLine(line); + + xline->special = special; + xline->arg1 = arg1; + xline->arg2 = arg2; + xline->arg3 = arg3; + xline->arg4 = arg4; + xline->arg5 = arg5; + } + } + + return Continue; + } + + static CommandFunc const &findCommand(int name) + { + static CommandFunc const cmds[] = + { + cmdNOP, cmdTerminate, cmdSuspend, cmdPushNumber, cmdLSpec1, cmdLSpec2, + cmdLSpec3, cmdLSpec4, cmdLSpec5, cmdLSpec1Direct, cmdLSpec2Direct, + cmdLSpec3Direct, cmdLSpec4Direct, cmdLSpec5Direct, cmdAdd, + cmdSubtract, cmdMultiply, cmdDivide, cmdModulus, cmdEQ, cmdNE, + cmdLT, cmdGT, cmdLE, cmdGE, cmdAssignScriptVar, cmdAssignMapVar, + cmdAssignWorldVar, cmdPushScriptVar, cmdPushMapVar, + cmdPushWorldVar, cmdAddScriptVar, cmdAddMapVar, cmdAddWorldVar, + cmdSubScriptVar, cmdSubMapVar, cmdSubWorldVar, cmdMulScriptVar, + cmdMulMapVar, cmdMulWorldVar, cmdDivScriptVar, cmdDivMapVar, + cmdDivWorldVar, cmdModScriptVar, cmdModMapVar, cmdModWorldVar, + cmdIncScriptVar, cmdIncMapVar, cmdIncWorldVar, cmdDecScriptVar, + cmdDecMapVar, cmdDecWorldVar, cmdGoto, cmdIfGoto, cmdDrop, + cmdDelay, cmdDelayDirect, cmdRandom, cmdRandomDirect, + cmdThingCount, cmdThingCountDirect, cmdTagWait, cmdTagWaitDirect, + cmdPolyWait, cmdPolyWaitDirect, cmdChangeFloor, + cmdChangeFloorDirect, cmdChangeCeiling, cmdChangeCeilingDirect, + cmdRestart, cmdAndLogical, cmdOrLogical, cmdAndBitwise, + cmdOrBitwise, cmdEorBitwise, cmdNegateLogical, cmdLShift, + cmdRShift, cmdUnaryMinus, cmdIfNotGoto, cmdLineSide, cmdScriptWait, + cmdScriptWaitDirect, cmdClearLineSpecial, cmdCaseGoto, + cmdBeginPrint, cmdEndPrint, cmdPrintString, cmdPrintNumber, + cmdPrintCharacter, cmdPlayerCount, cmdGameType, cmdGameSkill, + cmdTimer, cmdSectorSound, cmdAmbientSound, cmdSoundSequence, + cmdSetLineTexture, cmdSetLineBlocking, cmdSetLineSpecial, + cmdThingSound, cmdEndPrintBold + }; + static int const numCmds = sizeof(cmds) / sizeof(cmds[0]); + if(name >= 0 && name < numCmds) return cmds[name]; + /// @throw Error Invalid command name specified. + throw Error("acs::Interpreter::findCommand", "Unknown command #" + String::number(name)); + } + +#endif // __JHEXEN__ + +} // namespace internal +using namespace internal; + +namespace acs { + +thinker_t *Interpreter::newThinker(Script &script, Script::Args const &scriptArgs, + mobj_t *activator, Line *line, int side, int delayCount) +{ + Script::EntryPoint const &ep = script.entryPoint(); + + Interpreter *th = (Interpreter *) Z_Calloc(sizeof(*th), PU_MAP, nullptr); + th->thinker.function = (thinkfunc_t) acs_Interpreter_Think; + + th->_script = &script; + th->pcodePtr = ep.pcodePtr; + th->delayCount = delayCount; + th->activator = activator; + th->line = line; + th->side = side; + th->delayCount = delayCount; + + for(int i = 0; i < ep.scriptArgCount; ++i) + { + th->args[i] = scriptArgs[i]; + } + + Thinker_Add(&th->thinker); + + return &th->thinker; +} + +void Interpreter::think() +{ +#ifdef __JHEXEN__ + int action = (script().state() == Script::Terminating? Terminate : Continue); + + if(script().isRunning()) + { + if(delayCount) + { + delayCount--; + return; + } + + while((action = findCommand(LONG(*pcodePtr++))(*this)) == Continue) + {} + } + + if(action == Terminate) + { + // This script has now finished - notify interested parties. + /// @todo Use a de::Observers -based mechanism for this. + script().setState(Script::Inactive); + + // Notify any scripts which are waiting for this script to finish. + scriptSys().forAllScripts([this] (Script &otherScript) + { + otherScript.resumeIfWaitingForScript(script()); + return LoopContinue; + }); + + Thinker_Remove(&thinker); + } +#endif +} + +System &Interpreter::scriptSys() const +{ + return Game_ACScriptSystem(); +} + +Script &Interpreter::script() const +{ + DENG2_ASSERT(_script); + return *static_cast