/* ** p_acs.cpp ** General BEHAVIOR management and ACS execution environment ** **--------------------------------------------------------------------------- ** Copyright 1998-2012 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** ** This code at one time made lots of little-endian assumptions. ** I think it should be fine on big-endian machines now, but I have no ** real way to test it. */ #include #include "templates.h" #include "doomdef.h" #include "p_local.h" #include "d_player.h" #include "p_spec.h" #include "g_level.h" #include "s_sound.h" #include "p_acs.h" #include "p_saveg.h" #include "p_lnspec.h" #include "p_enemy.h" #include "m_random.h" #include "doomstat.h" #include "c_console.h" #include "c_dispatch.h" #include "s_sndseq.h" #include "i_system.h" #include "sbar.h" #include "m_swap.h" #include "a_sharedglobal.h" #include "v_video.h" #include "w_wad.h" #include "r_sky.h" #include "gstrings.h" #include "gi.h" #include "g_game.h" #include "sc_man.h" #include "c_bind.h" #include "info.h" #include "r_data/r_translate.h" #include "cmdlib.h" #include "m_png.h" #include "p_setup.h" #include "po_man.h" #include "actorptrselect.h" #include "serializer.h" #include "decallib.h" #include "p_terrain.h" #include "version.h" #include "p_effect.h" #include "r_utility.h" #include "a_morph.h" #include "i_music.h" #include "serializer.h" #include "thingdef.h" #include "a_pickups.h" #include "r_data/colormaps.h" #include "g_levellocals.h" #include "actorinlines.h" #include "stats.h" #include "types.h" #include "vm.h" #include "scriptutil.h" #include "s_music.h" // P-codes for ACS scripts enum { /* 0*/ PCD_NOP, PCD_TERMINATE, PCD_SUSPEND, PCD_PUSHNUMBER, PCD_LSPEC1, PCD_LSPEC2, PCD_LSPEC3, PCD_LSPEC4, PCD_LSPEC5, PCD_LSPEC1DIRECT, /* 10*/ PCD_LSPEC2DIRECT, PCD_LSPEC3DIRECT, PCD_LSPEC4DIRECT, PCD_LSPEC5DIRECT, PCD_ADD, PCD_SUBTRACT, PCD_MULTIPLY, PCD_DIVIDE, PCD_MODULUS, PCD_EQ, /* 20*/ PCD_NE, PCD_LT, PCD_GT, PCD_LE, PCD_GE, PCD_ASSIGNSCRIPTVAR, PCD_ASSIGNMAPVAR, PCD_ASSIGNWORLDVAR, PCD_PUSHSCRIPTVAR, PCD_PUSHMAPVAR, /* 30*/ PCD_PUSHWORLDVAR, PCD_ADDSCRIPTVAR, PCD_ADDMAPVAR, PCD_ADDWORLDVAR, PCD_SUBSCRIPTVAR, PCD_SUBMAPVAR, PCD_SUBWORLDVAR, PCD_MULSCRIPTVAR, PCD_MULMAPVAR, PCD_MULWORLDVAR, /* 40*/ PCD_DIVSCRIPTVAR, PCD_DIVMAPVAR, PCD_DIVWORLDVAR, PCD_MODSCRIPTVAR, PCD_MODMAPVAR, PCD_MODWORLDVAR, PCD_INCSCRIPTVAR, PCD_INCMAPVAR, PCD_INCWORLDVAR, PCD_DECSCRIPTVAR, /* 50*/ PCD_DECMAPVAR, PCD_DECWORLDVAR, PCD_GOTO, PCD_IFGOTO, PCD_DROP, PCD_DELAY, PCD_DELAYDIRECT, PCD_RANDOM, PCD_RANDOMDIRECT, PCD_THINGCOUNT, /* 60*/ PCD_THINGCOUNTDIRECT, PCD_TAGWAIT, PCD_TAGWAITDIRECT, PCD_POLYWAIT, PCD_POLYWAITDIRECT, PCD_CHANGEFLOOR, PCD_CHANGEFLOORDIRECT, PCD_CHANGECEILING, PCD_CHANGECEILINGDIRECT, PCD_RESTART, /* 70*/ PCD_ANDLOGICAL, PCD_ORLOGICAL, PCD_ANDBITWISE, PCD_ORBITWISE, PCD_EORBITWISE, PCD_NEGATELOGICAL, PCD_LSHIFT, PCD_RSHIFT, PCD_UNARYMINUS, PCD_IFNOTGOTO, /* 80*/ PCD_LINESIDE, PCD_SCRIPTWAIT, PCD_SCRIPTWAITDIRECT, PCD_CLEARLINESPECIAL, PCD_CASEGOTO, PCD_BEGINPRINT, PCD_ENDPRINT, PCD_PRINTSTRING, PCD_PRINTNUMBER, PCD_PRINTCHARACTER, /* 90*/ PCD_PLAYERCOUNT, PCD_GAMETYPE, PCD_GAMESKILL, PCD_TIMER, PCD_SECTORSOUND, PCD_AMBIENTSOUND, PCD_SOUNDSEQUENCE, PCD_SETLINETEXTURE, PCD_SETLINEBLOCKING, PCD_SETLINESPECIAL, /*100*/ PCD_THINGSOUND, PCD_ENDPRINTBOLD, // [RH] End of Hexen p-codes PCD_ACTIVATORSOUND, PCD_LOCALAMBIENTSOUND, PCD_SETLINEMONSTERBLOCKING, PCD_PLAYERBLUESKULL, // [BC] Start of new [Skull Tag] pcodes PCD_PLAYERREDSKULL, PCD_PLAYERYELLOWSKULL, PCD_PLAYERMASTERSKULL, PCD_PLAYERBLUECARD, /*110*/ PCD_PLAYERREDCARD, PCD_PLAYERYELLOWCARD, PCD_PLAYERMASTERCARD, PCD_PLAYERBLACKSKULL, PCD_PLAYERSILVERSKULL, PCD_PLAYERGOLDSKULL, PCD_PLAYERBLACKCARD, PCD_PLAYERSILVERCARD, PCD_ISNETWORKGAME, PCD_PLAYERTEAM, /*120*/ PCD_PLAYERHEALTH, PCD_PLAYERARMORPOINTS, PCD_PLAYERFRAGS, PCD_PLAYEREXPERT, PCD_BLUETEAMCOUNT, PCD_REDTEAMCOUNT, PCD_BLUETEAMSCORE, PCD_REDTEAMSCORE, PCD_ISONEFLAGCTF, PCD_LSPEC6, // These are never used. They should probably /*130*/ PCD_LSPEC6DIRECT, // be given names like PCD_DUMMY. PCD_PRINTNAME, PCD_MUSICCHANGE, PCD_CONSOLECOMMANDDIRECT, PCD_CONSOLECOMMAND, PCD_SINGLEPLAYER, // [RH] End of Skull Tag p-codes PCD_FIXEDMUL, PCD_FIXEDDIV, PCD_SETGRAVITY, PCD_SETGRAVITYDIRECT, /*140*/ PCD_SETAIRCONTROL, PCD_SETAIRCONTROLDIRECT, PCD_CLEARINVENTORY, PCD_GIVEINVENTORY, PCD_GIVEINVENTORYDIRECT, PCD_TAKEINVENTORY, PCD_TAKEINVENTORYDIRECT, PCD_CHECKINVENTORY, PCD_CHECKINVENTORYDIRECT, PCD_SPAWN, /*150*/ PCD_SPAWNDIRECT, PCD_SPAWNSPOT, PCD_SPAWNSPOTDIRECT, PCD_SETMUSIC, PCD_SETMUSICDIRECT, PCD_LOCALSETMUSIC, PCD_LOCALSETMUSICDIRECT, PCD_PRINTFIXED, PCD_PRINTLOCALIZED, PCD_MOREHUDMESSAGE, /*160*/ PCD_OPTHUDMESSAGE, PCD_ENDHUDMESSAGE, PCD_ENDHUDMESSAGEBOLD, PCD_SETSTYLE, PCD_SETSTYLEDIRECT, PCD_SETFONT, PCD_SETFONTDIRECT, PCD_PUSHBYTE, PCD_LSPEC1DIRECTB, PCD_LSPEC2DIRECTB, /*170*/ PCD_LSPEC3DIRECTB, PCD_LSPEC4DIRECTB, PCD_LSPEC5DIRECTB, PCD_DELAYDIRECTB, PCD_RANDOMDIRECTB, PCD_PUSHBYTES, PCD_PUSH2BYTES, PCD_PUSH3BYTES, PCD_PUSH4BYTES, PCD_PUSH5BYTES, /*180*/ PCD_SETTHINGSPECIAL, PCD_ASSIGNGLOBALVAR, PCD_PUSHGLOBALVAR, PCD_ADDGLOBALVAR, PCD_SUBGLOBALVAR, PCD_MULGLOBALVAR, PCD_DIVGLOBALVAR, PCD_MODGLOBALVAR, PCD_INCGLOBALVAR, PCD_DECGLOBALVAR, /*190*/ PCD_FADETO, PCD_FADERANGE, PCD_CANCELFADE, PCD_PLAYMOVIE, PCD_SETFLOORTRIGGER, PCD_SETCEILINGTRIGGER, PCD_GETACTORX, PCD_GETACTORY, PCD_GETACTORZ, PCD_STARTTRANSLATION, /*200*/ PCD_TRANSLATIONRANGE1, PCD_TRANSLATIONRANGE2, PCD_ENDTRANSLATION, PCD_CALL, PCD_CALLDISCARD, PCD_RETURNVOID, PCD_RETURNVAL, PCD_PUSHMAPARRAY, PCD_ASSIGNMAPARRAY, PCD_ADDMAPARRAY, /*210*/ PCD_SUBMAPARRAY, PCD_MULMAPARRAY, PCD_DIVMAPARRAY, PCD_MODMAPARRAY, PCD_INCMAPARRAY, PCD_DECMAPARRAY, PCD_DUP, PCD_SWAP, PCD_WRITETOINI, PCD_GETFROMINI, /*220*/ PCD_SIN, PCD_COS, PCD_VECTORANGLE, PCD_CHECKWEAPON, PCD_SETWEAPON, PCD_TAGSTRING, PCD_PUSHWORLDARRAY, PCD_ASSIGNWORLDARRAY, PCD_ADDWORLDARRAY, PCD_SUBWORLDARRAY, /*230*/ PCD_MULWORLDARRAY, PCD_DIVWORLDARRAY, PCD_MODWORLDARRAY, PCD_INCWORLDARRAY, PCD_DECWORLDARRAY, PCD_PUSHGLOBALARRAY, PCD_ASSIGNGLOBALARRAY, PCD_ADDGLOBALARRAY, PCD_SUBGLOBALARRAY, PCD_MULGLOBALARRAY, /*240*/ PCD_DIVGLOBALARRAY, PCD_MODGLOBALARRAY, PCD_INCGLOBALARRAY, PCD_DECGLOBALARRAY, PCD_SETMARINEWEAPON, PCD_SETACTORPROPERTY, PCD_GETACTORPROPERTY, PCD_PLAYERNUMBER, PCD_ACTIVATORTID, PCD_SETMARINESPRITE, /*250*/ PCD_GETSCREENWIDTH, PCD_GETSCREENHEIGHT, PCD_THING_PROJECTILE2, PCD_STRLEN, PCD_SETHUDSIZE, PCD_GETCVAR, PCD_CASEGOTOSORTED, PCD_SETRESULTVALUE, PCD_GETLINEROWOFFSET, PCD_GETACTORFLOORZ, /*260*/ PCD_GETACTORANGLE, PCD_GETSECTORFLOORZ, PCD_GETSECTORCEILINGZ, PCD_LSPEC5RESULT, PCD_GETSIGILPIECES, PCD_GETLEVELINFO, PCD_CHANGESKY, PCD_PLAYERINGAME, PCD_PLAYERISBOT, PCD_SETCAMERATOTEXTURE, /*270*/ PCD_ENDLOG, PCD_GETAMMOCAPACITY, PCD_SETAMMOCAPACITY, PCD_PRINTMAPCHARARRAY, // [JB] start of new p-codes PCD_PRINTWORLDCHARARRAY, PCD_PRINTGLOBALCHARARRAY, // [JB] end of new p-codes PCD_SETACTORANGLE, // [GRB] PCD_GRABINPUT, // Unused but acc defines them PCD_SETMOUSEPOINTER, // " PCD_MOVEMOUSEPOINTER, // " /*280*/ PCD_SPAWNPROJECTILE, PCD_GETSECTORLIGHTLEVEL, PCD_GETACTORCEILINGZ, PCD_SETACTORPOSITION, PCD_CLEARACTORINVENTORY, PCD_GIVEACTORINVENTORY, PCD_TAKEACTORINVENTORY, PCD_CHECKACTORINVENTORY, PCD_THINGCOUNTNAME, PCD_SPAWNSPOTFACING, /*290*/ PCD_PLAYERCLASS, // [GRB] //[MW] start my p-codes PCD_ANDSCRIPTVAR, PCD_ANDMAPVAR, PCD_ANDWORLDVAR, PCD_ANDGLOBALVAR, PCD_ANDMAPARRAY, PCD_ANDWORLDARRAY, PCD_ANDGLOBALARRAY, PCD_EORSCRIPTVAR, PCD_EORMAPVAR, /*300*/ PCD_EORWORLDVAR, PCD_EORGLOBALVAR, PCD_EORMAPARRAY, PCD_EORWORLDARRAY, PCD_EORGLOBALARRAY, PCD_ORSCRIPTVAR, PCD_ORMAPVAR, PCD_ORWORLDVAR, PCD_ORGLOBALVAR, PCD_ORMAPARRAY, /*310*/ PCD_ORWORLDARRAY, PCD_ORGLOBALARRAY, PCD_LSSCRIPTVAR, PCD_LSMAPVAR, PCD_LSWORLDVAR, PCD_LSGLOBALVAR, PCD_LSMAPARRAY, PCD_LSWORLDARRAY, PCD_LSGLOBALARRAY, PCD_RSSCRIPTVAR, /*320*/ PCD_RSMAPVAR, PCD_RSWORLDVAR, PCD_RSGLOBALVAR, PCD_RSMAPARRAY, PCD_RSWORLDARRAY, PCD_RSGLOBALARRAY, //[MW] end my p-codes PCD_GETPLAYERINFO, // [GRB] PCD_CHANGELEVEL, PCD_SECTORDAMAGE, PCD_REPLACETEXTURES, /*330*/ PCD_NEGATEBINARY, PCD_GETACTORPITCH, PCD_SETACTORPITCH, PCD_PRINTBIND, PCD_SETACTORSTATE, PCD_THINGDAMAGE2, PCD_USEINVENTORY, PCD_USEACTORINVENTORY, PCD_CHECKACTORCEILINGTEXTURE, PCD_CHECKACTORFLOORTEXTURE, /*340*/ PCD_GETACTORLIGHTLEVEL, PCD_SETMUGSHOTSTATE, PCD_THINGCOUNTSECTOR, PCD_THINGCOUNTNAMESECTOR, PCD_CHECKPLAYERCAMERA, // [TN] PCD_MORPHACTOR, // [MH] PCD_UNMORPHACTOR, // [MH] PCD_GETPLAYERINPUT, PCD_CLASSIFYACTOR, PCD_PRINTBINARY, /*350*/ PCD_PRINTHEX, PCD_CALLFUNC, PCD_SAVESTRING, // [FDARI] create string (temporary) PCD_PRINTMAPCHRANGE, // [FDARI] output range (print part of array) PCD_PRINTWORLDCHRANGE, PCD_PRINTGLOBALCHRANGE, PCD_STRCPYTOMAPCHRANGE, // [FDARI] input range (copy string to all/part of array) PCD_STRCPYTOWORLDCHRANGE, PCD_STRCPYTOGLOBALCHRANGE, PCD_PUSHFUNCTION, // from Eternity /*360*/ PCD_CALLSTACK, // from Eternity PCD_SCRIPTWAITNAMED, PCD_TRANSLATIONRANGE3, PCD_GOTOSTACK, PCD_ASSIGNSCRIPTARRAY, PCD_PUSHSCRIPTARRAY, PCD_ADDSCRIPTARRAY, PCD_SUBSCRIPTARRAY, PCD_MULSCRIPTARRAY, PCD_DIVSCRIPTARRAY, /*370*/ PCD_MODSCRIPTARRAY, PCD_INCSCRIPTARRAY, PCD_DECSCRIPTARRAY, PCD_ANDSCRIPTARRAY, PCD_EORSCRIPTARRAY, PCD_ORSCRIPTARRAY, PCD_LSSCRIPTARRAY, PCD_RSSCRIPTARRAY, PCD_PRINTSCRIPTCHARARRAY, PCD_PRINTSCRIPTCHRANGE, /*380*/ PCD_STRCPYTOSCRIPTCHRANGE, PCD_LSPEC5EX, PCD_LSPEC5EXRESULT, PCD_TRANSLATIONRANGE4, PCD_TRANSLATIONRANGE5, /*381*/ PCODE_COMMAND_COUNT }; // Some constants used by ACS scripts enum { LINE_FRONT = 0, LINE_BACK = 1 }; enum { SIDE_FRONT = 0, SIDE_BACK = 1 }; enum { TEXTURE_TOP = 0, TEXTURE_MIDDLE = 1, TEXTURE_BOTTOM = 2 }; enum { GAME_SINGLE_PLAYER = 0, GAME_NET_COOPERATIVE = 1, GAME_NET_DEATHMATCH = 2, GAME_TITLE_MAP = 3 }; enum { CLASS_FIGHTER = 0, CLASS_CLERIC = 1, CLASS_MAGE = 2 }; enum { SKILL_VERY_EASY = 0, SKILL_EASY = 1, SKILL_NORMAL = 2, SKILL_HARD = 3, SKILL_VERY_HARD = 4 }; enum { BLOCK_NOTHING = 0, BLOCK_CREATURES = 1, BLOCK_EVERYTHING = 2, BLOCK_RAILING = 3, BLOCK_PLAYERS = 4 }; enum { LEVELINFO_PAR_TIME, LEVELINFO_CLUSTERNUM, LEVELINFO_LEVELNUM, LEVELINFO_TOTAL_SECRETS, LEVELINFO_FOUND_SECRETS, LEVELINFO_TOTAL_ITEMS, LEVELINFO_FOUND_ITEMS, LEVELINFO_TOTAL_MONSTERS, LEVELINFO_KILLED_MONSTERS, LEVELINFO_SUCK_TIME }; enum { PLAYERINFO_TEAM, PLAYERINFO_AIMDIST, PLAYERINFO_COLOR, PLAYERINFO_GENDER, PLAYERINFO_NEVERSWITCH, PLAYERINFO_MOVEBOB, PLAYERINFO_STILLBOB, PLAYERINFO_PLAYERCLASS, PLAYERINFO_FOV, PLAYERINFO_DESIREDFOV, }; extern FILE *Logfile; FRandom pr_acs ("ACS"); // I imagine this much stack space is probably overkill, but it could // potentially get used with recursive functions. #define STACK_SIZE 4096 #define CLAMPCOLOR(c) (EColorRange)((unsigned)(c) >= NUM_TEXT_COLORS ? CR_UNTRANSLATED : (c)) #define LANGREGIONMASK MAKE_ID(0,0,0xff,0xff) // HUD message flags #define HUDMSG_LOG (0x80000000) #define HUDMSG_COLORSTRING (0x40000000) #define HUDMSG_ADDBLEND (0x20000000) #define HUDMSG_ALPHA (0x10000000) #define HUDMSG_NOWRAP (0x08000000) // HUD message layers; these are not flags #define HUDMSG_LAYER_SHIFT 12 #define HUDMSG_LAYER_MASK (0x0000F000) // See HUDMSGLayer enumerations in sbar.h // HUD message visibility flags #define HUDMSG_VISIBILITY_SHIFT 16 #define HUDMSG_VISIBILITY_MASK (0x00070000) // See HUDMSG visibility enumerations in sbar.h // LineAttack flags #define FHF_NORANDOMPUFFZ 1 #define FHF_NOIMPACTDECAL 2 // SpawnDecal flags #define SDF_ABSANGLE 1 #define SDF_PERMANENT 2 // GetArmorInfo enum { ARMORINFO_CLASSNAME, ARMORINFO_SAVEAMOUNT, ARMORINFO_SAVEPERCENT, ARMORINFO_MAXABSORB, ARMORINFO_MAXFULLABSORB, ARMORINFO_ACTUALSAVEAMOUNT, }; // PickActor // [JP] I've renamed these flags to something else to avoid confusion with the other PAF_ flags enum { // PAF_FORCETID, // PAF_RETURNTID PICKAF_FORCETID = 1, PICKAF_RETURNTID = 2, }; // ACS specific conversion functions to and from fixed point. // These should be used to convert from and to sctipt variables // so that there is a clear distinction between leftover fixed point code // and genuinely needed conversions. inline double ACSToDouble(int acsval) { return acsval / 65536.; } inline float ACSToFloat(int acsval) { return acsval / 65536.f; } inline int DoubleToACS(double val) { return xs_Fix<16>::ToFix(val); } inline DAngle ACSToAngle(int acsval) { return acsval * (360. / 65536.); } inline int AngleToACS(DAngle ang) { return ang.BAMs() >> 16; } inline int PitchToACS(DAngle ang) { return int(ang.Normalized180().Degrees * (65536. / 360)); } struct CallReturn { CallReturn(int pc, ScriptFunction *func, FBehavior *module, const ACSLocalVariables &locals, ACSLocalArrays *arrays, bool discard, unsigned int runaway) : ReturnFunction(func), ReturnModule(module), ReturnLocals(locals), ReturnArrays(arrays), ReturnAddress(pc), bDiscardResult(discard), EntryInstrCount(runaway) {} ScriptFunction *ReturnFunction; FBehavior *ReturnModule; ACSLocalVariables ReturnLocals; ACSLocalArrays *ReturnArrays; int ReturnAddress; int bDiscardResult; unsigned int EntryInstrCount; }; class DLevelScript : public DObject { DECLARE_CLASS(DLevelScript, DObject) HAS_OBJECT_POINTERS public: enum EScriptState { SCRIPT_Running, SCRIPT_Suspended, SCRIPT_Delayed, SCRIPT_TagWait, SCRIPT_PolyWait, SCRIPT_ScriptWaitPre, SCRIPT_ScriptWait, SCRIPT_PleaseRemove, SCRIPT_DivideBy0, SCRIPT_ModulusBy0, }; DLevelScript(AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module, const int *args, int argcount, int flags); ~DLevelScript(); void Serialize(FSerializer &arc); int RunScript(); inline void SetState(EScriptState newstate) { state = newstate; } inline EScriptState GetState() { return state; } DLevelScript *GetNext() const { return next; } void MarkLocalVarStrings() const { GlobalACSStrings.MarkStringArray(&Localvars[0], Localvars.Size()); } void LockLocalVarStrings() const { GlobalACSStrings.LockStringArray(&Localvars[0], Localvars.Size()); } void UnlockLocalVarStrings() const { GlobalACSStrings.UnlockStringArray(&Localvars[0], Localvars.Size()); } protected: DLevelScript *next, *prev; int script; TArray Localvars; int *pc; EScriptState state; int statedata; TObjPtr activator; line_t *activationline; bool backSide; FFont *activefont; int hudwidth, hudheight; int ClipRectLeft, ClipRectTop, ClipRectWidth, ClipRectHeight; int WrapWidth; bool HandleAspect; FBehavior *activeBehavior; int InModuleScriptNumber; TArray ACS_StringBuilderStack; inline void STRINGBUILDER_START(FString &Builder) { if (Builder.IsNotEmpty() || ACS_StringBuilderStack.Size()) { ACS_StringBuilderStack.Push(Builder); Builder = ""; } } inline void STRINGBUILDER_FINISH(FString &Builder) { if (!ACS_StringBuilderStack.Pop(Builder)) { Builder = ""; } } void Link(); void Unlink(); void PutLast(); void PutFirst(); static int Random(int min, int max); static int ThingCount(int type, int stringid, int tid, int tag); static void ChangeFlat(int tag, int name, bool floorOrCeiling); static int CountPlayers(); static void SetLineTexture(int lineid, int side, int position, int name); static int DoSpawn(int type, const DVector3 &pos, int tid, DAngle angle, bool force); static int DoSpawn(int type, int x, int y, int z, int tid, int angle, bool force); static bool DoCheckActorTexture(int tid, AActor *activator, int string, bool floor); int DoSpawnSpot(int type, int spot, int tid, int angle, bool forced); int DoSpawnSpotFacing(int type, int spot, int tid, bool forced); int DoClassifyActor(int tid); int CallFunction(int argCount, int funcIndex, int32_t *args); void DoFadeTo(int r, int g, int b, int a, int time); void DoFadeRange(int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2, int time); void DoSetFont(int fontnum); void SetActorProperty(int tid, int property, int value); void DoSetActorProperty(AActor *actor, int property, int value); int GetActorProperty(int tid, int property); int CheckActorProperty(int tid, int property, int value); int GetPlayerInput(int playernum, int inputnum); int LineFromID(int id); int SideFromID(int id, int side); private: DLevelScript(); friend class DACSThinker; }; class DACSThinker : public DThinker { DECLARE_CLASS(DACSThinker, DThinker) HAS_OBJECT_POINTERS public: DACSThinker(); ~DACSThinker(); void Serialize(FSerializer &arc); void Tick(); typedef TMap ScriptMap; ScriptMap RunningScripts; // Array of all synchronous scripts static TObjPtr ActiveThinker; void DumpScriptStatus(); void StopScriptsFor(AActor *actor); private: DLevelScript *LastScript; DLevelScript *Scripts; // List of all running scripts friend class DLevelScript; friend class FBehavior; }; static DLevelScript *P_GetScriptGoing (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module, const int *args, int argcount, int flags); struct FBehavior::ArrayInfo { uint32_t ArraySize; int32_t *Elements; }; TArray FBehavior::StaticModules; //============================================================================ // // uallong // // Read a possibly unaligned four-byte little endian integer from memory. // //============================================================================ #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__x86_64__) inline int uallong(const int &foo) { return foo; } #else inline int uallong(const int &foo) { const unsigned char *bar = (const unsigned char *)&foo; return bar[0] | (bar[1] << 8) | (bar[2] << 16) | (bar[3] << 24); } #endif //============================================================================ // // Global and world variables // //============================================================================ // ACS variables with world scope static BoundsCheckingArray ACS_WorldVars; static BoundsCheckingArray ACS_WorldArrays; // ACS variables with global scope BoundsCheckingArray ACS_GlobalVars; BoundsCheckingArray ACS_GlobalArrays; //---------------------------------------------------------------------------- // // ACS stack manager // // This is needed so that the garbage collector has access to all active // script stacks // //---------------------------------------------------------------------------- using FACSStackMemory = BoundsCheckingArray; struct FACSStack { FACSStackMemory buffer; int sp; FACSStack *next; FACSStack *prev; static FACSStack *head; FACSStack(); ~FACSStack(); }; FACSStack *FACSStack::head; FACSStack::FACSStack() { sp = 0; next = head; prev = NULL; head = this; } FACSStack::~FACSStack() { if (next != NULL) next->prev = prev; if (prev == NULL) { head = next; } else { prev->next = next; } } //---------------------------------------------------------------------------- // // Global ACS strings (Formerly known as On the fly strings) // // This special string table is part of the global state. Programmatically // generated strings (e.g. those returned by strparam) are stored here. // PCD_TAGSTRING also now stores strings in this table instead of simply // tagging strings with their library ID. // // Identical strings map to identical string identifiers. // // When the string table needs to grow to hold more strings, a garbage // collection is first attempted to see if more room can be made to store // strings without growing. A string is considered in use if any value // in any of these variable blocks contains a valid ID in the global string // table: // * The active area of the ACS stack // * All running scripts' local variables // * All map variables // * All world variables // * All global variables // It's not important whether or not they are really used as strings, only // that they might be. A string is also considered in use if its lock count // is non-zero, even if none of the above variable blocks referenced it. // // To keep track of local and map variables for nonresident maps in a hub, // when a map's state is archived, all strings found in its local and map // variables are locked. When a map is revisited in a hub, all strings found // in its local and map variables are unlocked. Locking and unlocking are // cumulative operations. // // What this all means is that: // * Strings returned by strparam last indefinitely. No longer do they // disappear at the end of the tic they were generated. // * You can pass library strings around freely without having to worry // about always having the same libraries loaded in the same order on // every map that needs to use those strings. // //---------------------------------------------------------------------------- ACSStringPool GlobalACSStrings; void ACSStringPool::PoolEntry::Lock() { if (Locks.Find(level.levelnum) == Locks.Size()) { Locks.Push(level.levelnum); } } void ACSStringPool::PoolEntry::Unlock() { auto ndx = Locks.Find(level.levelnum); if (ndx < Locks.Size()) { Locks.Delete(ndx); } } ACSStringPool::ACSStringPool() { memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); FirstFreeEntry = 0; } //============================================================================ // // ACSStringPool :: Clear // // Remove all strings from the pool. // //============================================================================ void ACSStringPool::Clear() { Pool.Clear(); memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); FirstFreeEntry = 0; } //============================================================================ // // ACSStringPool :: AddString // // Returns a valid string identifier (including library ID) or -1 if we ran // out of room. Identical strings will return identical values. // //============================================================================ int ACSStringPool::AddString(const char *str) { if (str == nullptr) str = ""; size_t len = strlen(str); unsigned int h = SuperFastHash(str, len); unsigned int bucketnum = h % NUM_BUCKETS; int i = FindString(str, len, h, bucketnum); if (i >= 0) { return i | STRPOOL_LIBRARYID_OR; } FString fstr(str); return InsertString(fstr, h, bucketnum); } int ACSStringPool::AddString(FString &str) { unsigned int h = SuperFastHash(str.GetChars(), str.Len()); unsigned int bucketnum = h % NUM_BUCKETS; int i = FindString(str, str.Len(), h, bucketnum); if (i >= 0) { return i | STRPOOL_LIBRARYID_OR; } return InsertString(str, h, bucketnum); } //============================================================================ // // ACSStringPool :: GetString // //============================================================================ const char *ACSStringPool::GetString(int strnum) { assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); strnum &= ~LIBRARYID_MASK; if ((unsigned)strnum < Pool.Size() && Pool[strnum].Next != FREE_ENTRY) { return Pool[strnum].Str; } return NULL; } //============================================================================ // // ACSStringPool :: LockString // // Prevents this string from being purged. // //============================================================================ void ACSStringPool::LockString(int strnum) { assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); strnum &= ~LIBRARYID_MASK; assert((unsigned)strnum < Pool.Size()); Pool[strnum].Lock(); } //============================================================================ // // ACSStringPool :: UnlockString // // When equally mated with LockString, allows this string to be purged. // //============================================================================ void ACSStringPool::UnlockString(int strnum) { assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); strnum &= ~LIBRARYID_MASK; assert((unsigned)strnum < Pool.Size()); Pool[strnum].Unlock(); } //============================================================================ // // ACSStringPool :: MarkString // // Prevent this string from being purged during the next call to PurgeStrings. // This does not carry over to subsequent calls of PurgeStrings. // //============================================================================ void ACSStringPool::MarkString(int strnum) { assert((strnum & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR); strnum &= ~LIBRARYID_MASK; assert((unsigned)strnum < Pool.Size()); Pool[strnum].Mark = true; } //============================================================================ // // ACSStringPool :: LockStringArray // // Prevents several strings from being purged. Entries not in this pool will // be silently ignored. The idea here is to pass this function a block of // ACS variables. Everything that looks like it might be a string in the pool // is locked, even if it's not actually used as such. It's better to keep // more strings than we need than to throw away ones we do need. // //============================================================================ void ACSStringPool::LockStringArray(const int *strnum, unsigned int count) { for (unsigned int i = 0; i < count; ++i) { int num = strnum[i]; if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) { num &= ~LIBRARYID_MASK; if ((unsigned)num < Pool.Size()) { Pool[num].Lock(); } } } } //============================================================================ // // ACSStringPool :: UnlockStringArray // // Reverse of LockStringArray. // //============================================================================ void ACSStringPool::UnlockStringArray(const int *strnum, unsigned int count) { for (unsigned int i = 0; i < count; ++i) { int num = strnum[i]; if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) { num &= ~LIBRARYID_MASK; if ((unsigned)num < Pool.Size()) { Pool[num].Unlock(); } } } } //============================================================================ // // ACSStringPool :: MarkStringArray // // Array version of MarkString. // //============================================================================ void ACSStringPool::MarkStringArray(const int *strnum, unsigned int count) { for (unsigned int i = 0; i < count; ++i) { int num = strnum[i]; if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) { num &= ~LIBRARYID_MASK; if ((unsigned)num < Pool.Size()) { Pool[num].Mark = true; } } } } //============================================================================ // // ACSStringPool :: MarkStringMap // // World/global variables version of MarkString. // //============================================================================ void ACSStringPool::MarkStringMap(const FWorldGlobalArray &aray) { FWorldGlobalArray::ConstIterator it(aray); FWorldGlobalArray::ConstPair *pair; while (it.NextPair(pair)) { int num = pair->Value; if ((num & LIBRARYID_MASK) == STRPOOL_LIBRARYID_OR) { num &= ~LIBRARYID_MASK; if ((unsigned)num < Pool.Size()) { Pool[num].Mark |= true; } } } } //============================================================================ // // ACSStringPool :: UnlockAll // // Resets every entry's lock count to 0. Used when doing a partial reset of // ACS state such as travelling to a new hub. // //============================================================================ void ACSStringPool::UnlockAll() { for (unsigned int i = 0; i < Pool.Size(); ++i) { Pool[i].Mark = false; Pool[i].Locks.Clear(); } } //============================================================================ // // ACSStringPool :: PurgeStrings // // Remove all unlocked strings from the pool. // //============================================================================ void ACSStringPool::PurgeStrings() { // Clear the hash buckets. We'll rebuild them as we decide what strings // to keep and which to toss. memset(PoolBuckets, 0xFF, sizeof(PoolBuckets)); size_t usedcount = 0, freedcount = 0; for (unsigned int i = 0; i < Pool.Size(); ++i) { PoolEntry *entry = &Pool[i]; if (entry->Next != FREE_ENTRY) { if (entry->Locks.Size() == 0 && !entry->Mark) { freedcount++; // Mark this entry as free. entry->Next = FREE_ENTRY; if (i < FirstFreeEntry) { FirstFreeEntry = i; } // And free the string. entry->Str = ""; } else { usedcount++; // Rehash this entry. unsigned int h = entry->Hash % NUM_BUCKETS; entry->Next = PoolBuckets[h]; PoolBuckets[h] = i; // Remove MarkString's mark. entry->Mark = false; } } } } //============================================================================ // // ACSStringPool :: FindString // // Finds a string in the pool. Does not include the library ID in the returned // value. Returns -1 if the string does not exist in the pool. // //============================================================================ int ACSStringPool::FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum) { unsigned int i = PoolBuckets[bucketnum]; while (i != NO_ENTRY) { PoolEntry *entry = &Pool[i]; assert(entry->Next != FREE_ENTRY); if (entry->Hash == h && entry->Str.Len() == len && memcmp(entry->Str.GetChars(), str, len) == 0) { return i; } i = entry->Next; } return -1; } //============================================================================ // // ACSStringPool :: InsertString // // Inserts a new string into the pool. // //============================================================================ int ACSStringPool::InsertString(FString &str, unsigned int h, unsigned int bucketnum) { unsigned int index = FirstFreeEntry; if (index >= MIN_GC_SIZE && index == Pool.Max()) { // We will need to grow the array. Try a garbage collection first. P_CollectACSGlobalStrings(); index = FirstFreeEntry; } if (FirstFreeEntry >= STRPOOL_LIBRARYID_OR) { // If we go any higher, we'll collide with the library ID marker. return -1; } if (index == Pool.Size()) { // There were no free entries; make a new one. Pool.Reserve(MIN_GC_SIZE); FirstFreeEntry++; } else { // Scan for the next free entry FindFirstFreeEntry(FirstFreeEntry + 1); } PoolEntry *entry = &Pool[index]; entry->Str = str; entry->Hash = h; entry->Next = PoolBuckets[bucketnum]; entry->Mark = false; entry->Locks.Clear(); PoolBuckets[bucketnum] = index; return index | STRPOOL_LIBRARYID_OR; } //============================================================================ // // ACSStringPool :: FindFirstFreeEntry // // Finds the first free entry, starting at base. // //============================================================================ void ACSStringPool::FindFirstFreeEntry(unsigned base) { while (base < Pool.Size() && Pool[base].Next != FREE_ENTRY) { base++; } FirstFreeEntry = base; } //============================================================================ // // ACSStringPool :: ReadStrings // // Reads strings from a PNG chunk. // //============================================================================ void ACSStringPool::ReadStrings(FSerializer &file, const char *key) { Clear(); if (file.BeginObject(key)) { int poolsize = 0; file("poolsize", poolsize); Pool.Resize(poolsize); for (auto &p : Pool) { p.Next = FREE_ENTRY; p.Mark = false; p.Locks.Clear(); } if (file.BeginArray("pool")) { int j = file.ArraySize(); for (int i = 0; i < j; i++) { if (file.BeginObject(nullptr)) { unsigned ii = UINT_MAX; file("index", ii); if (ii < Pool.Size()) { file("string", Pool[ii].Str) ("locks", Pool[ii].Locks); unsigned h = SuperFastHash(Pool[ii].Str, Pool[ii].Str.Len()); unsigned bucketnum = h % NUM_BUCKETS; Pool[ii].Hash = h; Pool[ii].Next = PoolBuckets[bucketnum]; PoolBuckets[bucketnum] = ii; } file.EndObject(); } } } } FindFirstFreeEntry(FirstFreeEntry); } //============================================================================ // // ACSStringPool :: WriteStrings // // Writes strings to a serializer // //============================================================================ void ACSStringPool::WriteStrings(FSerializer &file, const char *key) const { int32_t i, poolsize = (int32_t)Pool.Size(); if (poolsize == 0) { // No need to write if we don't have anything. return; } if (file.BeginObject(key)) { file("poolsize", poolsize); if (file.BeginArray("pool")) { for (i = 0; i < poolsize; ++i) { PoolEntry *entry = &Pool[i]; if (entry->Next != FREE_ENTRY) { if (file.BeginObject(nullptr)) { file("index", i) ("string", entry->Str) ("locks", entry->Locks) .EndObject(); } } } file.EndArray(); } file.EndObject(); } } //============================================================================ // // ACSStringPool :: Dump // // Lists all strings in the pool. // //============================================================================ void ACSStringPool::Dump() const { for (unsigned int i = 0; i < Pool.Size(); ++i) { if (Pool[i].Next != FREE_ENTRY) { Printf("%4u. (%2d) \"%s\"\n", i, Pool[i].Locks.Size(), Pool[i].Str.GetChars()); } } Printf("First free %u\n", FirstFreeEntry); } void ACSStringPool::UnlockForLevel(int lnum) { for (unsigned int i = 0; i < Pool.Size(); ++i) { if (Pool[i].Next != FREE_ENTRY) { auto ndx = Pool[i].Locks.Find(lnum); if (ndx < Pool[i].Locks.Size()) { Pool[i].Locks.Delete(ndx); } } } } //============================================================================ // // P_MarkWorldVarStrings // //============================================================================ void P_MarkWorldVarStrings() { GlobalACSStrings.MarkStringArray(ACS_WorldVars.Pointer(), ACS_WorldVars.Size()); for (size_t i = 0; i < ACS_WorldArrays.Size(); ++i) { GlobalACSStrings.MarkStringMap(ACS_WorldArrays.Pointer()[i]); } } //============================================================================ // // P_MarkGlobalVarStrings // //============================================================================ void P_MarkGlobalVarStrings() { GlobalACSStrings.MarkStringArray(ACS_GlobalVars.Pointer(), ACS_GlobalVars.Size()); for (size_t i = 0; i < ACS_GlobalArrays.Size(); ++i) { GlobalACSStrings.MarkStringMap(ACS_GlobalArrays.Pointer()[i]); } } //============================================================================ // // P_CollectACSGlobalStrings // // Garbage collect ACS global strings. // //============================================================================ void P_CollectACSGlobalStrings() { for (FACSStack *stack = FACSStack::head; stack != NULL; stack = stack->next) { const int32_t sp = stack->sp; if (0 == sp) { continue; } else if (sp < 0 && sp >= STACK_SIZE) { I_Error("Corrupted stack pointer in ACS VM"); } else { GlobalACSStrings.MarkStringArray(&stack->buffer[0], sp); } } FBehavior::StaticMarkLevelVarStrings(); P_MarkWorldVarStrings(); P_MarkGlobalVarStrings(); GlobalACSStrings.PurgeStrings(); } #ifdef _DEBUG CCMD(acsgc) { P_CollectACSGlobalStrings(); } CCMD(globstr) { GlobalACSStrings.Dump(); } #endif //============================================================================ // // ScriptPresentation // // Returns a presentable version of the script number. // //============================================================================ static FString ScriptPresentation(int script) { FString out = "script "; if (script < 0) { FName scrname = FName(ENamedName(-script)); if (scrname.IsValidName()) { out << '"' << scrname.GetChars() << '"'; return out; } } out.AppendFormat("%d", script); return out; } //============================================================================ // // P_ClearACSVars // //============================================================================ void P_ClearACSVars(bool alsoglobal) { int i; ACS_WorldVars.Fill(0); for (i = 0; i < NUM_WORLDVARS; ++i) { ACS_WorldArrays[i].Clear (); } if (alsoglobal) { ACS_GlobalVars.Fill(0); for (i = 0; i < NUM_GLOBALVARS; ++i) { ACS_GlobalArrays[i].Clear (); } // Since we cleared all ACS variables, we know nothing refers to them // anymore. GlobalACSStrings.Clear(); } else { // Purge any strings that aren't referenced by global variables, since // they're the only possible references left. P_MarkGlobalVarStrings(); GlobalACSStrings.PurgeStrings(); } } //============================================================================ // // WriteVars // //============================================================================ static void WriteVars (FSerializer &file, int32_t *vars, size_t count, const char *key) { size_t i, j; for (i = 0; i < count; ++i) { if (vars[i] != 0) break; } if (i < count) { // Find last non-zero var. Anything beyond the last stored variable // will be zeroed at load time. for (j = count-1; j > i; --j) { if (vars[j] != 0) break; } file.Array(key, vars, int(j+1)); } } //============================================================================ // // // //============================================================================ static void ReadVars (FSerializer &arc, int32_t *vars, size_t count, const char *key) { memset(&vars[0], 0, count * 4); arc.Array(key, vars, (int)count); } //============================================================================ // // // //============================================================================ static void WriteArrayVars (FSerializer &file, FWorldGlobalArray *vars, unsigned int count, const char *key) { unsigned int i; // Find the first non-empty array. for (i = 0; i < count; ++i) { if (vars[i].CountUsed() != 0) break; } if (i < count) { if (file.BeginObject(key)) { for(;iKey); int v = pair->Value; file(arraykey.GetChars(), v); } file.EndObject(); } } } file.EndObject(); } } } //============================================================================ // // // //============================================================================ static void ReadArrayVars (FSerializer &file, FWorldGlobalArray *vars, size_t count, const char *key) { for (size_t i = 0; i < count; ++i) { vars[i].Clear(); } if (file.BeginObject(key)) { const char *arraykey; while ((arraykey = file.GetKey())) { int i = (int)strtoll(arraykey, nullptr, 10); if (file.BeginObject(nullptr)) { while ((arraykey = file.GetKey())) { int k = (int)strtoll(arraykey, nullptr, 10); int val; file(nullptr, val); vars[i].Insert(k, val); } file.EndObject(); } } file.EndObject(); } } //============================================================================ // // // //============================================================================ void P_ReadACSVars(FSerializer &arc) { ReadVars (arc, ACS_WorldVars.Pointer(), NUM_WORLDVARS, "acsworldvars"); ReadVars (arc, ACS_GlobalVars.Pointer(), NUM_GLOBALVARS, "acsglobalvars"); ReadArrayVars (arc, ACS_WorldArrays.Pointer(), NUM_WORLDVARS, "acsworldarrays"); ReadArrayVars (arc, ACS_GlobalArrays.Pointer(), NUM_GLOBALVARS, "acsglobalarrays"); GlobalACSStrings.ReadStrings(arc, "acsglobalstrings"); } //============================================================================ // // // //============================================================================ void P_WriteACSVars(FSerializer &arc) { WriteVars (arc, ACS_WorldVars.Pointer(), NUM_WORLDVARS, "acsworldvars"); WriteVars (arc, ACS_GlobalVars.Pointer(), NUM_GLOBALVARS, "acsglobalvars"); WriteArrayVars (arc, ACS_WorldArrays.Pointer(), NUM_WORLDVARS, "acsworldarrays"); WriteArrayVars (arc, ACS_GlobalArrays.Pointer(), NUM_GLOBALVARS, "acsglobalarrays"); GlobalACSStrings.WriteStrings(arc, "acsglobalstrings"); } //---- Inventory functions --------------------------------------// // //============================================================================ // // DoUseInv // // Makes a single actor use an inventory item // //============================================================================ static bool DoUseInv (AActor *actor, PClassActor *info) { auto item = actor->FindInventory (info); if (item != NULL) { player_t* const player = actor->player; if (nullptr == player) { return actor->UseInventory(item); } else { int cheats; bool res; // Bypass CF_TOTALLYFROZEN cheats = player->cheats; player->cheats &= ~CF_TOTALLYFROZEN; res = actor->UseInventory(item); player->cheats |= (cheats & CF_TOTALLYFROZEN); return res; } } return false; } //============================================================================ // // UseInventory // // makes one or more actors use an inventory item. // //============================================================================ static int UseInventory (AActor *activator, const char *type) { PClassActor *info; int ret = 0; if (type == NULL) { return 0; } info = PClass::FindActor (type); if (info == NULL) { return 0; } if (activator == NULL) { for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i]) ret += DoUseInv (players[i].mo, info); } } else { ret = DoUseInv (activator, info); } return ret; } //============================================================================ // // CheckInventory // // Returns how much of a particular item an actor has. // //============================================================================ int CheckInventory (AActor *activator, const char *type, bool max) { if (activator == NULL || type == NULL) return 0; if (stricmp (type, "Armor") == 0) { type = "BasicArmor"; } else if (stricmp (type, "Health") == 0) { if (max) { return activator->GetMaxHealth(); } return activator->health; } PClassActor *info = PClass::FindActor (type); if (info == NULL) { DPrintf (DMSG_ERROR, "ACS: '%s': Unknown actor class.\n", type); return 0; } else if (!info->IsDescendantOf(NAME_Inventory)) { DPrintf(DMSG_ERROR, "ACS: '%s' is not an inventory item.\n", type); return 0; } auto item = activator->FindInventory (info); if (max) { if (item) { return item->IntVar(NAME_MaxAmount); } else if (info != nullptr && info->IsDescendantOf(NAME_Inventory)) { return GetDefaultByType(info)->IntVar(NAME_MaxAmount); } } return item ? item->IntVar(NAME_Amount) : 0; } //---- Plane watchers ----// class DPlaneWatcher : public DThinker { DECLARE_CLASS (DPlaneWatcher, DThinker) HAS_OBJECT_POINTERS public: DPlaneWatcher (AActor *it, line_t *line, int lineSide, bool ceiling, int tag, int height, int special, int arg0, int arg1, int arg2, int arg3, int arg4); void Tick (); void Serialize(FSerializer &arc); private: sector_t *Sector; double WatchD, LastD; int Special; int Args[5]; TObjPtr Activator; line_t *Line; bool LineSide; bool bCeiling; DPlaneWatcher() {} }; IMPLEMENT_CLASS(DPlaneWatcher, false, true) IMPLEMENT_POINTERS_START(DPlaneWatcher) IMPLEMENT_POINTER(Activator) IMPLEMENT_POINTERS_END DPlaneWatcher::DPlaneWatcher (AActor *it, line_t *line, int lineSide, bool ceiling, int tag, int height, int special, int arg0, int arg1, int arg2, int arg3, int arg4) : Special (special), Activator (it), Line (line), LineSide (!!lineSide), bCeiling (ceiling) { int secnum; Args[0] = arg0; Args[1] = arg1; Args[2] = arg2; Args[3] = arg3; Args[4] = arg4; secnum = P_FindFirstSectorFromTag (tag); if (secnum >= 0) { secplane_t plane; Sector = &level.sectors[secnum]; if (bCeiling) { plane = Sector->ceilingplane; } else { plane = Sector->floorplane; } LastD = plane.fD(); plane.ChangeHeight (height); WatchD = plane.fD(); } else { Sector = NULL; WatchD = LastD = 0; } } void DPlaneWatcher::Serialize(FSerializer &arc) { Super::Serialize (arc); arc("special", Special) .Args("args", Args, nullptr, Special) ("sector", Sector) ("ceiling", bCeiling) ("watchd", WatchD) ("lastd", LastD) ("activator", Activator) ("line", Line) ("lineside", LineSide); } void DPlaneWatcher::Tick () { if (Sector == NULL) { Destroy (); return; } double newd; if (bCeiling) { newd = Sector->ceilingplane.fD(); } else { newd = Sector->floorplane.fD(); } if ((LastD < WatchD && newd >= WatchD) || (LastD > WatchD && newd <= WatchD)) { P_ExecuteSpecial(Special, Line, Activator, LineSide, Args[0], Args[1], Args[2], Args[3], Args[4]); Destroy (); } } //---- ACS lump manager ----// // Load user-specified default modules. This must be called after the level's // own behavior is loaded (if it has one). void FBehavior::StaticLoadDefaultModules () { // Scan each LOADACS lump and load the specified modules in order int lump, lastlump = 0; while ((lump = Wads.FindLump ("LOADACS", &lastlump)) != -1) { FScanner sc(lump); while (sc.GetString()) { int acslump = Wads.CheckNumForName (sc.String, ns_acslibrary); if (acslump >= 0) { StaticLoadModule (acslump); } else { Printf (TEXTCOLOR_RED "Could not find autoloaded ACS library %s\n", sc.String); } } } } FBehavior *FBehavior::StaticLoadModule (int lumpnum, FileReader *fr, int len, int reallumpnum) { if (lumpnum == -1 && fr == NULL) return NULL; for (unsigned int i = 0; i < StaticModules.Size(); ++i) { if (StaticModules[i]->LumpNum == lumpnum) { return StaticModules[i]; } } FBehavior * behavior = new FBehavior (); if (behavior->Init(lumpnum, fr, len, reallumpnum)) { return behavior; } else { delete behavior; Printf(TEXTCOLOR_RED "%s: invalid ACS module\n", Wads.GetLumpFullName(lumpnum)); return NULL; } } bool FBehavior::StaticCheckAllGood () { for (unsigned int i = 0; i < StaticModules.Size(); ++i) { if (!StaticModules[i]->IsGood()) { return false; } } return true; } void FBehavior::StaticUnloadModules () { for (unsigned int i = StaticModules.Size(); i-- > 0; ) { delete StaticModules[i]; } StaticModules.Clear (); } FBehavior *FBehavior::StaticGetModule (int lib) { if ((size_t)lib >= StaticModules.Size()) { return NULL; } return StaticModules[lib]; } void FBehavior::StaticMarkLevelVarStrings() { // Mark map variables. for (uint32_t modnum = 0; modnum < StaticModules.Size(); ++modnum) { StaticModules[modnum]->MarkMapVarStrings(); } // Mark running scripts' local variables. if (DACSThinker::ActiveThinker != NULL) { for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) { script->MarkLocalVarStrings(); } } } void FBehavior::StaticLockLevelVarStrings() { // Lock map variables. for (uint32_t modnum = 0; modnum < StaticModules.Size(); ++modnum) { StaticModules[modnum]->LockMapVarStrings(); } // Lock running scripts' local variables. if (DACSThinker::ActiveThinker != NULL) { for (DLevelScript *script = DACSThinker::ActiveThinker->Scripts; script != NULL; script = script->GetNext()) { script->LockLocalVarStrings(); } } } void FBehavior::StaticUnlockLevelVarStrings() { GlobalACSStrings.UnlockForLevel(level.levelnum); } void FBehavior::MarkMapVarStrings() const { GlobalACSStrings.MarkStringArray(MapVarStore, NUM_MAPVARS); for (int i = 0; i < NumArrays; ++i) { GlobalACSStrings.MarkStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); } } void FBehavior::LockMapVarStrings() const { GlobalACSStrings.LockStringArray(MapVarStore, NUM_MAPVARS); for (int i = 0; i < NumArrays; ++i) { GlobalACSStrings.LockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); } } void FBehavior::UnlockMapVarStrings() const { GlobalACSStrings.UnlockStringArray(MapVarStore, NUM_MAPVARS); for (int i = 0; i < NumArrays; ++i) { GlobalACSStrings.UnlockStringArray(ArrayStore[i].Elements, ArrayStore[i].ArraySize); } } void FBehavior::StaticSerializeModuleStates (FSerializer &arc) { auto modnum = StaticModules.Size(); if (arc.BeginArray("acsmodules")) { if (arc.isReading()) { auto modnum = arc.ArraySize(); if (modnum != StaticModules.Size()) { I_Error("Level was saved with a different number of ACS modules. (Have %d, save has %d)", StaticModules.Size(), modnum); } } for (modnum = 0; modnum < StaticModules.Size(); ++modnum) { FBehavior *module = StaticModules[modnum]; const char *modname = module->ModuleName; int ModSize = module->GetDataSize(); if (arc.BeginObject(nullptr)) { arc.StringPtr("modname", modname) ("modsize", ModSize); if (arc.isReading()) { if (stricmp(modname, module->ModuleName) != 0) { I_Error("Level was saved with a different set or order of ACS modules. (Have %s, save has %s)", module->ModuleName, modname); } else if (ModSize != module->GetDataSize()) { I_Error("ACS module %s has changed from what was saved. (Have %d bytes, save has %d bytes)", module->ModuleName, module->GetDataSize(), ModSize); } } module->SerializeVars(arc); arc.EndObject(); } } arc.EndArray(); } } void FBehavior::SerializeVars (FSerializer &arc) { if (arc.BeginArray("variables")) { SerializeVarSet(arc, MapVarStore, NUM_MAPVARS); for (int i = 0; i < NumArrays; ++i) { SerializeVarSet(arc, ArrayStore[i].Elements, ArrayStore[i].ArraySize); } arc.EndArray(); } } void FBehavior::SerializeVarSet (FSerializer &arc, int32_t *vars, int max) { int32_t count; int32_t first, last; if (arc.BeginObject(nullptr)) { if (arc.isWriting()) { // Find first non-zero variable for (first = 0; first < max; ++first) { if (vars[first] != 0) { break; } } // Find last non-zero variable for (last = max - 1; last >= first; --last) { if (vars[last] != 0) { break; } } if (last < first) { // no non-zero variables count = 0; arc("count", count); } else { count = last - first + 1; arc("count", count); arc("first", first); arc.Array("values", &vars[first], count); } } else { memset(vars, 0, max * sizeof(*vars)); arc("count", count); if (count != 0) { arc("first", first); if (first + count > max) count = max - first; arc.Array("values", &vars[first], count); } } arc.EndObject(); } } static int ParseLocalArrayChunk(void *chunk, ACSLocalArrays *arrays, int offset) { unsigned count = LittleShort(static_cast(((unsigned *)chunk)[1] - 2)) / 4; int *sizes = (int *)((uint8_t *)chunk + 10); arrays->Count = count; if (count > 0) { ACSLocalArrayInfo *info = new ACSLocalArrayInfo[count]; arrays->Info = info; for (unsigned i = 0; i < count; ++i) { info[i].Size = LittleLong(sizes[i]); info[i].Offset = offset; offset += info[i].Size; } } // Return the new local variable size, with space for the arrays return offset; } FBehavior::FBehavior() { NumScripts = 0; NumFunctions = 0; NumArrays = 0; NumTotalArrays = 0; Scripts = NULL; Functions = NULL; Arrays = NULL; ArrayStore = NULL; Chunks = NULL; Data = NULL; Format = ACS_Unknown; LumpNum = -1; memset (MapVarStore, 0, sizeof(MapVarStore)); ModuleName[0] = 0; FunctionProfileData = NULL; } bool FBehavior::Init(int lumpnum, FileReader * fr, int len, int reallumpnum) { uint8_t *object; int i; LumpNum = lumpnum; // Now that everything is set up, record this module as being among the loaded modules. // We need to do this before resolving any imports, because an import might (indirectly) // need to resolve exports in this module. The only things that can be exported are // functions and map variables, which must already be present if they're exported, so // this is okay. // This must be done first for 2 reasons: // 1. If not, corrupt modules cause memory leaks // 2. Corrupt modules won't be reported when a level is being loaded if this function quits before // adding it to the list. if (fr == NULL) len = Wads.LumpLength (lumpnum); // Any behaviors smaller than 32 bytes cannot possibly contain anything useful. // (16 bytes for a completely empty behavior + 12 bytes for one script header // + 4 bytes for PCD_TERMINATE for an old-style object. A new-style object // has 24 bytes if it is completely empty. An empty SPTR chunk adds 8 bytes.) if (len < 32) { return false; } object = new uint8_t[len]; if (fr == NULL) { Wads.ReadLump (lumpnum, object); } else { fr->Read (object, len); } if (object[0] != 'A' || object[1] != 'C' || object[2] != 'S') { delete[] object; return false; } switch (object[3]) { case 0: Format = ACS_Old; break; case 'E': Format = ACS_Enhanced; break; case 'e': Format = ACS_LittleEnhanced; break; default: delete[] object; return false; } LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT; if (fr == NULL) { Wads.GetLumpName (ModuleName, lumpnum); ModuleName[8] = 0; } else { strcpy(ModuleName, "BEHAVIOR"); } Data = object; DataSize = len; if (Format == ACS_Old) { uint32_t dirofs = LittleLong(((uint32_t *)object)[1]); uint32_t pretag = ((uint32_t *)(object + dirofs))[-1]; Chunks = object + len; // Check for redesigned ACSE/ACSe if (dirofs >= 6*4 && (pretag == MAKE_ID('A','C','S','e') || pretag == MAKE_ID('A','C','S','E'))) { Format = (pretag == MAKE_ID('A','C','S','e')) ? ACS_LittleEnhanced : ACS_Enhanced; Chunks = object + LittleLong(((uint32_t *)(object + dirofs))[-2]); // Forget about the compatibility cruft at the end of the lump DataSize = LittleLong(((uint32_t *)object)[1]) - 8; } ShouldLocalize = false; } else { Chunks = object + LittleLong(((uint32_t *)object)[1]); } LoadScriptsDirectory (); if (Format == ACS_Old) { StringTable = LittleLong(((uint32_t *)Data)[1]); StringTable += LittleLong(((uint32_t *)(Data + StringTable))[0]) * 12 + 4; UnescapeStringTable(Data + StringTable, Data, false); // If this is an original Hexen BEHAVIOR, set up some localization info for it. Original Hexen BEHAVIORs are always in the old format. if ((level.flags2 & LEVEL2_HEXENHACK) && gameinfo.gametype == GAME_Hexen && lumpnum == -1 && reallumpnum > 0) { int fileno = Wads.GetLumpFile(reallumpnum); const char * filename = Wads.GetWadName(fileno); if (!stricmp(filename, "HEXEN.WAD") || !stricmp(filename, "HEXDD.WAD")) { ShouldLocalize = true; } } } else { UnencryptStrings (); uint8_t *strings = FindChunk (MAKE_ID('S','T','R','L')); if (strings != NULL) { StringTable = uint32_t(strings - Data + 8); UnescapeStringTable(strings + 8, NULL, true); } else { StringTable = 0; } } if (Format == ACS_Old) { // Do initialization for old-style behavior lumps for (i = 0; i < NUM_MAPVARS; ++i) { MapVars[i] = &MapVarStore[i]; } //LibraryID = StaticModules.Push (this) << LIBRARYID_SHIFT; } else { uint32_t *chunk; // Load functions uint8_t *funcs; Functions = NULL; funcs = FindChunk (MAKE_ID('F','U','N','C')); if (funcs != NULL) { NumFunctions = LittleLong(((uint32_t *)funcs)[1]) / 8; funcs += 8; FunctionProfileData = new ACSProfileInfo[NumFunctions]; Functions = new ScriptFunction[NumFunctions]; for (i = 0; i < NumFunctions; ++i) { ScriptFunctionInFile *funcf = &((ScriptFunctionInFile *)funcs)[i]; ScriptFunction *funcm = &Functions[i]; funcm->ArgCount = funcf->ArgCount; funcm->HasReturnValue = funcf->HasReturnValue; funcm->ImportNum = funcf->ImportNum; funcm->LocalCount = funcf->LocalCount; funcm->Address = LittleLong(funcf->Address); } } // Load local arrays for functions if (NumFunctions > 0) { for (chunk = (uint32_t *)FindChunk(MAKE_ID('F','A','R','Y')); chunk != NULL; chunk = (uint32_t *)NextChunk((uint8_t *)chunk)) { int size = LittleLong(chunk[1]); if (size >= 6) { unsigned int func_num = LittleShort(((uint16_t *)chunk)[4]); if (func_num < (unsigned int)NumFunctions) { ScriptFunction *func = &Functions[func_num]; // Unlike scripts, functions do not include their arg count in their local count. func->LocalCount = ParseLocalArrayChunk(chunk, &func->LocalArrays, func->LocalCount + func->ArgCount) - func->ArgCount; } } } } // Load JUMP points chunk = (uint32_t *)FindChunk (MAKE_ID('J','U','M','P')); if (chunk != NULL) { for (i = 0;i < (int)LittleLong(chunk[1]);i += 4) JumpPoints.Push(LittleLong(chunk[2 + i/4])); } // Initialize this object's map variables memset (MapVarStore, 0, sizeof(MapVarStore)); chunk = (uint32_t *)FindChunk (MAKE_ID('M','I','N','I')); while (chunk != NULL) { int numvars = LittleLong(chunk[1])/4 - 1; int firstvar = LittleLong(chunk[2]); for (i = 0; i < numvars; ++i) { MapVarStore[i+firstvar] = LittleLong(chunk[3+i]); } chunk = (uint32_t *)NextChunk ((uint8_t *)chunk); } // Initialize this object's map variable pointers to defaults. They can be changed // later once the imported modules are loaded. for (i = 0; i < NUM_MAPVARS; ++i) { MapVars[i] = &MapVarStore[i]; } // Create arrays for this module chunk = (uint32_t *)FindChunk (MAKE_ID('A','R','A','Y')); if (chunk != NULL) { NumArrays = LittleLong(chunk[1])/8; ArrayStore = new ArrayInfo[NumArrays]; memset (ArrayStore, 0, sizeof(*Arrays)*NumArrays); for (i = 0; i < NumArrays; ++i) { MapVarStore[LittleLong(chunk[2+i*2])] = i; ArrayStore[i].ArraySize = LittleLong(chunk[3+i*2]); ArrayStore[i].Elements = new int32_t[ArrayStore[i].ArraySize]; memset(ArrayStore[i].Elements, 0, ArrayStore[i].ArraySize*sizeof(uint32_t)); } } // Initialize arrays for this module chunk = (uint32_t *)FindChunk (MAKE_ID('A','I','N','I')); while (chunk != NULL) { int arraynum = MapVarStore[LittleLong(chunk[2])]; if ((unsigned)arraynum < (unsigned)NumArrays) { // Use unsigned iterator here to avoid issue with GCC 4.9/5.x // optimizer. Might be some undefined behavior in this code, // but I don't know what it is. unsigned int initsize = MIN (ArrayStore[arraynum].ArraySize, (LittleLong(chunk[1])-4)/4); int32_t *elems = ArrayStore[arraynum].Elements; for (unsigned int j = 0; j < initsize; ++j) { elems[j] = LittleLong(chunk[3+j]); } } chunk = (uint32_t *)NextChunk((uint8_t *)chunk); } // Start setting up array pointers NumTotalArrays = NumArrays; chunk = (uint32_t *)FindChunk (MAKE_ID('A','I','M','P')); if (chunk != NULL) { NumTotalArrays += LittleLong(chunk[2]); } if (NumTotalArrays != 0) { Arrays = new ArrayInfo *[NumTotalArrays]; for (i = 0; i < NumArrays; ++i) { Arrays[i] = &ArrayStore[i]; } } // Tag the library ID to any map variables that are initialized with strings if (LibraryID != 0) { chunk = (uint32_t *)FindChunk (MAKE_ID('M','S','T','R')); if (chunk != NULL) { for (uint32_t i = 0; i < LittleLong(chunk[1])/4; ++i) { const char *str = LookupString(MapVarStore[LittleLong(chunk[i+2])]); if (str != NULL) { MapVarStore[LittleLong(chunk[i+2])] = GlobalACSStrings.AddString(str); } } } chunk = (uint32_t *)FindChunk (MAKE_ID('A','S','T','R')); if (chunk != NULL) { for (uint32_t i = 0; i < LittleLong(chunk[1])/4; ++i) { int arraynum = MapVarStore[LittleLong(chunk[i+2])]; if ((unsigned)arraynum < (unsigned)NumArrays) { int32_t *elems = ArrayStore[arraynum].Elements; for (int j = ArrayStore[arraynum].ArraySize; j > 0; --j, ++elems) { // *elems |= LibraryID; const char *str = LookupString(*elems); if (str != NULL) { *elems = GlobalACSStrings.AddString(str); } } } } } // [BL] Newer version of ASTR for structure aware compilers although we only have one array per chunk chunk = (uint32_t *)FindChunk (MAKE_ID('A','T','A','G')); while (chunk != NULL) { const uint8_t* chunkData = (const uint8_t*)(chunk + 2); // First byte is version, it should be 0 if(*chunkData++ == 0) { int arraynum = MapVarStore[uallong(LittleLong(*(const int*)(chunkData)))]; chunkData += 4; if ((unsigned)arraynum < (unsigned)NumArrays) { int32_t *elems = ArrayStore[arraynum].Elements; // Ending zeros may be left out. for (int j = MIN(LittleLong(chunk[1])-5, ArrayStore[arraynum].ArraySize); j > 0; --j, ++elems, ++chunkData) { // For ATAG, a value of 0 = Integer, 1 = String, 2 = FunctionPtr // Our implementation uses the same tags for both String and FunctionPtr if (*chunkData == 2) { *elems |= LibraryID; } else if (*chunkData == 1) { const char *str = LookupString(*elems); if (str != NULL) { *elems = GlobalACSStrings.AddString(str); } } } } } chunk = (uint32_t *)NextChunk ((uint8_t *)chunk); } } // Load required libraries. if (NULL != (chunk = (uint32_t *)FindChunk (MAKE_ID('L','O','A','D')))) { const char *const parse = (char *)&chunk[2]; uint32_t i; for (i = 0; i < LittleLong(chunk[1]); ) { if (parse[i]) { FBehavior *module = NULL; int lump = Wads.CheckNumForName (&parse[i], ns_acslibrary); if (lump < 0) { Printf (TEXTCOLOR_RED "Could not find ACS library %s.\n", &parse[i]); } else { module = StaticLoadModule (lump); } if (module != NULL) Imports.Push (module); do {;} while (parse[++i]); } ++i; } // Go through each imported module in order and resolve all imported functions // and map variables. for (i = 0; i < Imports.Size(); ++i) { FBehavior *lib = Imports[i]; int j; if (lib == NULL) continue; // Resolve functions chunk = (uint32_t *)FindChunk(MAKE_ID('F','N','A','M')); for (j = 0; j < NumFunctions; ++j) { ScriptFunction *func = &((ScriptFunction *)Functions)[j]; if (func->Address == 0 && func->ImportNum == 0) { int libfunc = lib->FindFunctionName ((char *)(chunk + 2) + LittleLong(chunk[3+j])); if (libfunc >= 0) { ScriptFunction *realfunc = &((ScriptFunction *)lib->Functions)[libfunc]; // Make sure that the library really defines this function. It might simply // be importing it itself. if (realfunc->Address != 0 && realfunc->ImportNum == 0) { func->Address = libfunc; func->ImportNum = i+1; if (realfunc->ArgCount != func->ArgCount) { Printf (TEXTCOLOR_ORANGE "Function %s in %s has %d arguments. %s expects it to have %d.\n", (char *)(chunk + 2) + LittleLong(chunk[3+j]), lib->ModuleName, realfunc->ArgCount, ModuleName, func->ArgCount); Format = ACS_Unknown; } // The next two properties do not affect code compatibility, so it is // okay for them to be different in the imported module than they are // in this one, as long as we make sure to use the real values. func->LocalCount = LittleLong(realfunc->LocalCount); func->HasReturnValue = realfunc->HasReturnValue; } } } } // Resolve map variables chunk = (uint32_t *)FindChunk(MAKE_ID('M','I','M','P')); if (chunk != NULL) { char *parse = (char *)&chunk[2]; for (uint32_t j = 0; j < LittleLong(chunk[1]); ) { uint32_t varNum = LittleLong(*(uint32_t *)&parse[j]); j += 4; int impNum = lib->FindMapVarName (&parse[j]); if (impNum >= 0) { MapVars[varNum] = &lib->MapVarStore[impNum]; } do {;} while (parse[++j]); ++j; } } // Resolve arrays if (NumTotalArrays > NumArrays) { chunk = (uint32_t *)FindChunk(MAKE_ID('A','I','M','P')); char *parse = (char *)&chunk[3]; for (uint32_t j = 0; j < LittleLong(chunk[2]); ++j) { uint32_t varNum = LittleLong(*(uint32_t *)parse); parse += 4; uint32_t expectedSize = LittleLong(*(uint32_t *)parse); parse += 4; int impNum = lib->FindMapArray (parse); if (impNum >= 0) { Arrays[NumArrays + j] = &lib->ArrayStore[impNum]; MapVarStore[varNum] = NumArrays + j; if (lib->ArrayStore[impNum].ArraySize != expectedSize) { Format = ACS_Unknown; Printf (TEXTCOLOR_ORANGE "The array %s in %s has %u elements, but %s expects it to only have %u.\n", parse, lib->ModuleName, lib->ArrayStore[impNum].ArraySize, ModuleName, expectedSize); } } do {;} while (*++parse); ++parse; } } } } } DPrintf (DMSG_NOTIFY, "Loaded %d scripts, %d functions\n", NumScripts, NumFunctions); return true; } FBehavior::~FBehavior () { if (Scripts != NULL) { delete[] Scripts; Scripts = NULL; } if (Arrays != NULL) { delete[] Arrays; Arrays = NULL; } if (ArrayStore != NULL) { for (int i = 0; i < NumArrays; ++i) { if (ArrayStore[i].Elements != NULL) { delete[] ArrayStore[i].Elements; ArrayStore[i].Elements = NULL; } } delete[] ArrayStore; ArrayStore = NULL; } if (Functions != NULL) { delete[] Functions; Functions = NULL; } if (FunctionProfileData != NULL) { delete[] FunctionProfileData; FunctionProfileData = NULL; } if (Data != NULL) { delete[] Data; Data = NULL; } } void FBehavior::LoadScriptsDirectory () { union { uint8_t *b; uint32_t *dw; uint16_t *w; int16_t *sw; ScriptPtr2 *po; // Old ScriptPtr1 *pi; // Intermediate ScriptPtr3 *pe; // LittleEnhanced } scripts; int i, max; NumScripts = 0; Scripts = NULL; // Load the main script directory switch (Format) { case ACS_Old: scripts.dw = (uint32_t *)(Data + LittleLong(((uint32_t *)Data)[1])); NumScripts = LittleLong(scripts.dw[0]); if (NumScripts != 0) { scripts.dw++; Scripts = new ScriptPtr[NumScripts]; for (i = 0; i < NumScripts; ++i) { ScriptPtr2 *ptr1 = &scripts.po[i]; ScriptPtr *ptr2 = &Scripts[i]; ptr2->Number = LittleLong(ptr1->Number) % 1000; ptr2->Type = LittleLong(ptr1->Number) / 1000; ptr2->ArgCount = LittleLong(ptr1->ArgCount); ptr2->Address = LittleLong(ptr1->Address); } } break; case ACS_Enhanced: case ACS_LittleEnhanced: scripts.b = FindChunk (MAKE_ID('S','P','T','R')); if (scripts.b == NULL) { // There are no scripts! } else if (*(uint32_t *)Data != MAKE_ID('A','C','S',0)) { NumScripts = LittleLong(scripts.dw[1]) / 12; Scripts = new ScriptPtr[NumScripts]; scripts.dw += 2; for (i = 0; i < NumScripts; ++i) { ScriptPtr1 *ptr1 = &scripts.pi[i]; ScriptPtr *ptr2 = &Scripts[i]; ptr2->Number = LittleShort(ptr1->Number); ptr2->Type = uint8_t(LittleShort(ptr1->Type)); ptr2->ArgCount = LittleLong(ptr1->ArgCount); ptr2->Address = LittleLong(ptr1->Address); } } else { NumScripts = LittleLong(scripts.dw[1]) / 8; Scripts = new ScriptPtr[NumScripts]; scripts.dw += 2; for (i = 0; i < NumScripts; ++i) { ScriptPtr3 *ptr1 = &scripts.pe[i]; ScriptPtr *ptr2 = &Scripts[i]; ptr2->Number = LittleShort(ptr1->Number); ptr2->Type = ptr1->Type; ptr2->ArgCount = ptr1->ArgCount; ptr2->Address = LittleLong(ptr1->Address); } } break; default: break; } // [EP] Clang 3.5.0 optimizer miscompiles this function and causes random // crashes in the program. This is fixed in 3.5.1 onwards. #if defined(__clang__) && __clang_major__ == 3 && __clang_minor__ == 5 && __clang_patchlevel__ == 0 asm("" : "+g" (NumScripts)); #endif for (i = 0; i < NumScripts; ++i) { Scripts[i].Flags = 0; Scripts[i].VarCount = LOCAL_SIZE; } // Sort scripts, so we can use a binary search to find them if (NumScripts > 1) { qsort (Scripts, NumScripts, sizeof(ScriptPtr), SortScripts); // Check for duplicates because ACC originally did not enforce // script number uniqueness across different script types. We // only need to do this for old format lumps, because the ACCs // that produce new format lumps won't let you do this. if (Format == ACS_Old) { for (i = 0; i < NumScripts - 1; ++i) { if (Scripts[i].Number == Scripts[i+1].Number) { Printf(TEXTCOLOR_ORANGE "%s appears more than once.\n", ScriptPresentation(Scripts[i].Number).GetChars()); // Make the closed version the first one. if (Scripts[i+1].Type == SCRIPT_Closed) { swapvalues(Scripts[i], Scripts[i+1]); } } } } } if (Format == ACS_Old) return; // Load script flags scripts.b = FindChunk (MAKE_ID('S','F','L','G')); if (scripts.dw != NULL) { max = LittleLong(scripts.dw[1]) / 4; scripts.dw += 2; for (i = max; i > 0; --i, scripts.w += 2) { ScriptPtr *ptr = const_cast(FindScript (LittleShort(scripts.sw[0]))); if (ptr != NULL) { ptr->Flags = LittleShort(scripts.w[1]); } } } // Load script var counts. (Only recorded for scripts that use more than LOCAL_SIZE variables.) scripts.b = FindChunk (MAKE_ID('S','V','C','T')); if (scripts.dw != NULL) { max = LittleLong(scripts.dw[1]) / 4; scripts.dw += 2; for (i = max; i > 0; --i, scripts.w += 2) { ScriptPtr *ptr = const_cast(FindScript (LittleShort(scripts.sw[0]))); if (ptr != NULL) { ptr->VarCount = LittleShort(scripts.w[1]); } } } // Load script array sizes. (One chunk per script that uses arrays.) for (scripts.b = FindChunk(MAKE_ID('S','A','R','Y')); scripts.dw != NULL; scripts.b = NextChunk(scripts.b)) { int size = LittleLong(scripts.dw[1]); if (size >= 6) { int script_num = LittleShort(scripts.sw[4]); ScriptPtr *ptr = const_cast(FindScript(script_num)); if (ptr != NULL) { ptr->VarCount = ParseLocalArrayChunk(scripts.b, &ptr->LocalArrays, ptr->VarCount); } } } // Load script names (if any) scripts.b = FindChunk(MAKE_ID('S','N','A','M')); if (scripts.dw != NULL) { UnescapeStringTable(scripts.b + 8, NULL, false); for (i = 0; i < NumScripts; ++i) { // ACC stores script names as an index into the SNAM chunk, with the first index as // -1 and counting down from there. We convert this from an index into SNAM into // a negative index into the global name table. if (Scripts[i].Number < 0) { const char *str = (const char *)(scripts.b + 8 + scripts.dw[3 + (-Scripts[i].Number - 1)]); FName name(str); Scripts[i].Number = -name; } } // We need to resort scripts, because the new numbers for named scripts likely // do not match the order they were originally in. qsort (Scripts, NumScripts, sizeof(ScriptPtr), SortScripts); } } int FBehavior::SortScripts (const void *a, const void *b) { ScriptPtr *ptr1 = (ScriptPtr *)a; ScriptPtr *ptr2 = (ScriptPtr *)b; return ptr1->Number - ptr2->Number; } //============================================================================ // // FBehavior :: UnencryptStrings // // Descrambles strings in a STRE chunk to transform it into a STRL chunk. // //============================================================================ void FBehavior::UnencryptStrings () { uint32_t *prevchunk = NULL; uint32_t *chunk = (uint32_t *)FindChunk(MAKE_ID('S','T','R','E')); while (chunk != NULL) { for (uint32_t strnum = 0; strnum < LittleLong(chunk[3]); ++strnum) { int ofs = LittleLong(chunk[5+strnum]); uint8_t *data = (uint8_t *)chunk + ofs + 8, last; int p = (uint8_t)(ofs*157135); int i = 0; do { last = (data[i] ^= (uint8_t)(p+(i>>1))); ++i; } while (last != 0); } prevchunk = chunk; chunk = (uint32_t *)NextChunk ((uint8_t *)chunk); *prevchunk = MAKE_ID('S','T','R','L'); } if (prevchunk != NULL) { *prevchunk = MAKE_ID('S','T','R','L'); } } //============================================================================ // // FBehavior :: UnescapeStringTable // // Processes escape sequences for every string in a string table. // Chunkstart points to the string table. Datastart points to the base address // for offsets in the string table; if NULL, it will use chunkstart. If // has_padding is true, then this is a STRL chunk with four bytes of padding // on either side of the string count. // //============================================================================ void FBehavior::UnescapeStringTable(uint8_t *chunkstart, uint8_t *datastart, bool has_padding) { assert(chunkstart != NULL); uint32_t *chunk = (uint32_t *)chunkstart; if (datastart == NULL) { datastart = chunkstart; } if (!has_padding) { chunk[0] = LittleLong(chunk[0]); for (uint32_t strnum = 0; strnum < chunk[0]; ++strnum) { int ofs = LittleLong(chunk[1 + strnum]); // Byte swap offset, if needed. chunk[1 + strnum] = ofs; strbin((char *)datastart + ofs); } } else { chunk[1] = LittleLong(chunk[1]); for (uint32_t strnum = 0; strnum < chunk[1]; ++strnum) { int ofs = LittleLong(chunk[3 + strnum]); // Byte swap offset, if needed. chunk[3 + strnum] = ofs; strbin((char *)datastart + ofs); } } } //============================================================================ // // FBehavior :: IsGood // //============================================================================ bool FBehavior::IsGood () { bool bad; int i; // Check that the data format was understood if (Format == ACS_Unknown) { return false; } // Check that all functions are resolved bad = false; for (i = 0; i < NumFunctions; ++i) { ScriptFunction *funcdef = (ScriptFunction *)Functions + i; if (funcdef->Address == 0 && funcdef->ImportNum == 0) { uint32_t *chunk = (uint32_t *)FindChunk (MAKE_ID('F','N','A','M')); Printf (TEXTCOLOR_RED "Could not find ACS function %s for use in %s.\n", (char *)(chunk + 2) + chunk[3+i], ModuleName); bad = true; } } // Check that all imported modules were loaded for (i = Imports.Size() - 1; i >= 0; --i) { if (Imports[i] == NULL) { Printf (TEXTCOLOR_RED "Not all the libraries used by %s could be found.\n", ModuleName); return false; } } return !bad; } const ScriptPtr *FBehavior::FindScript (int script) const { const ScriptPtr *ptr = BinarySearch ((ScriptPtr *)Scripts, NumScripts, &ScriptPtr::Number, script); // If the preceding script has the same number, return it instead. // See the note by the script sorting above for why. if (ptr > Scripts) { if (ptr[-1].Number == script) { ptr--; } } return ptr; } const ScriptPtr *FBehavior::StaticFindScript (int script, FBehavior *&module) { for (uint32_t i = 0; i < StaticModules.Size(); ++i) { const ScriptPtr *code = StaticModules[i]->FindScript (script); if (code != NULL) { module = StaticModules[i]; return code; } } return NULL; } ScriptFunction *FBehavior::GetFunction (int funcnum, FBehavior *&module) const { if ((unsigned)funcnum >= (unsigned)NumFunctions) { return NULL; } ScriptFunction *funcdef = (ScriptFunction *)Functions + funcnum; if (funcdef->ImportNum) { return Imports[funcdef->ImportNum - 1]->GetFunction (funcdef->Address, module); } // Should I just un-const this function instead of using a const_cast? module = const_cast(this); return funcdef; } int FBehavior::FindFunctionName (const char *funcname) const { return FindStringInChunk ((uint32_t *)FindChunk (MAKE_ID('F','N','A','M')), funcname); } int FBehavior::FindMapVarName (const char *varname) const { return FindStringInChunk ((uint32_t *)FindChunk (MAKE_ID('M','E','X','P')), varname); } int FBehavior::FindMapArray (const char *arrayname) const { int var = FindMapVarName (arrayname); if (var >= 0) { return MapVarStore[var]; } return -1; } int FBehavior::FindStringInChunk (uint32_t *names, const char *varname) const { if (names != NULL) { uint32_t i; for (i = 0; i < LittleLong(names[2]); ++i) { if (stricmp (varname, (char *)(names + 2) + LittleLong(names[3+i])) == 0) { return (int)i; } } } return -1; } int FBehavior::GetArrayVal (int arraynum, int index) const { if ((unsigned)arraynum >= (unsigned)NumTotalArrays) return 0; const ArrayInfo *array = Arrays[arraynum]; if ((unsigned)index >= (unsigned)array->ArraySize) return 0; return array->Elements[index]; } void FBehavior::SetArrayVal (int arraynum, int index, int value) { if ((unsigned)arraynum >= (unsigned)NumTotalArrays) return; const ArrayInfo *array = Arrays[arraynum]; if ((unsigned)index >= (unsigned)array->ArraySize) return; array->Elements[index] = value; } inline bool FBehavior::CopyStringToArray(int arraynum, int index, int maxLength, const char *string) { // false if the operation was incomplete or unsuccessful if ((unsigned)arraynum >= (unsigned)NumTotalArrays || index < 0) return false; const ArrayInfo *array = Arrays[arraynum]; if ((signed)array->ArraySize - index < maxLength) maxLength = (signed)array->ArraySize - index; while (maxLength-- > 0) { array->Elements[index++] = *string; if (!(*string)) return true; // written terminating 0 string++; } return !(*string); // return true if only terminating 0 was not written } uint8_t *FBehavior::FindChunk (uint32_t id) const { uint8_t *chunk = Chunks; while (chunk != NULL && chunk < Data + DataSize) { if (((uint32_t *)chunk)[0] == id) { return chunk; } chunk += LittleLong(((uint32_t *)chunk)[1]) + 8; } return NULL; } uint8_t *FBehavior::NextChunk (uint8_t *chunk) const { uint32_t id = *(uint32_t *)chunk; chunk += LittleLong(((uint32_t *)chunk)[1]) + 8; while (chunk != NULL && chunk < Data + DataSize) { if (((uint32_t *)chunk)[0] == id) { return chunk; } chunk += LittleLong(((uint32_t *)chunk)[1]) + 8; } return NULL; } const char *FBehavior::StaticLookupString (uint32_t index, bool forprint) { uint32_t lib = index >> LIBRARYID_SHIFT; if (lib == STRPOOL_LIBRARYID) { return GlobalACSStrings.GetString(index); } if (lib >= (uint32_t)StaticModules.Size()) { return NULL; } return StaticModules[lib]->LookupString (index & 0xffff, forprint); } const char *FBehavior::LookupString (uint32_t index, bool forprint) const { if (StringTable == 0) { return NULL; } if (Format == ACS_Old) { uint32_t *list = (uint32_t *)(Data + StringTable); if (index >= list[0]) return NULL; // Out of range for this list; const char *s = (const char *)(Data + list[1 + index]); // Allow translations for Hexen's original strings. // This synthesizes a string label and looks it up. // It will only do this for original Hexen maps and PCD_PRINTSTRING operations. // For localizing user content better solutions exist so this hack won't be available as an editing feature. if (ShouldLocalize && forprint) { FString token = s; token.ToUpper(); token.ReplaceChars(".,-+!?", ' '); token.Substitute(" ", ""); token.Truncate(5); FStringf label("TXT_ACS_%s_%d_%.5s", level.MapName.GetChars(), index, token.GetChars()); auto p = GStrings[label]; if (p) return p; } return s; } else { uint32_t *list = (uint32_t *)(Data + StringTable); if (index >= list[1]) return NULL; // Out of range for this list return (const char *)(Data + StringTable + list[3+index]); } } void FBehavior::StaticStartTypedScripts (uint16_t type, AActor *activator, bool always, int arg1, bool runNow) { static const char *const TypeNames[] = { "Closed", "Open", "Respawn", "Death", "Enter", "Pickup", "BlueReturn", "RedReturn", "WhiteReturn", "Unknown", "Unknown", "Unknown", "Lightning", "Unloading", "Disconnect", "Return", "Event", "Kill", "Reopen" }; DPrintf(DMSG_NOTIFY, "Starting all scripts of type %d (%s)\n", type, type < countof(TypeNames) ? TypeNames[type] : TypeNames[SCRIPT_Lightning - 1]); for (unsigned int i = 0; i < StaticModules.Size(); ++i) { StaticModules[i]->StartTypedScripts (type, activator, always, arg1, runNow); } } void FBehavior::StartTypedScripts (uint16_t type, AActor *activator, bool always, int arg1, bool runNow) { const ScriptPtr *ptr; int i; for (i = 0; i < NumScripts; ++i) { ptr = &Scripts[i]; if (ptr->Type == type) { DLevelScript *runningScript = P_GetScriptGoing (activator, NULL, ptr->Number, ptr, this, &arg1, 1, always ? ACS_ALWAYS : 0); if (nullptr != runningScript && runNow) { runningScript->RunScript (); } } } } // FBehavior :: StaticStopMyScripts // // Stops any scripts started by the specified actor. Used by the net code // when a player disconnects. Should this be used in general whenever an // actor is destroyed? void FBehavior::StaticStopMyScripts (AActor *actor) { DACSThinker *controller = DACSThinker::ActiveThinker; if (controller != NULL) { controller->StopScriptsFor (actor); } } //---- The ACS Interpreter ----// IMPLEMENT_CLASS(DACSThinker, false, true) IMPLEMENT_POINTERS_START(DACSThinker) IMPLEMENT_POINTER(LastScript) IMPLEMENT_POINTER(Scripts) IMPLEMENT_POINTERS_END TObjPtr DACSThinker::ActiveThinker; DACSThinker::DACSThinker () : DThinker(STAT_SCRIPTS) { if (ActiveThinker) { I_Error ("Only one ACSThinker is allowed to exist at a time.\nCheck your code."); } else { ActiveThinker = this; Scripts = NULL; LastScript = NULL; RunningScripts.Clear(); } } DACSThinker::~DACSThinker () { Scripts = NULL; ActiveThinker = NULL; } //========================================================================== // // helper class for the runningscripts serializer // //========================================================================== struct SavingRunningscript { int scriptnum; DLevelScript *lscript; }; FSerializer &Serialize(FSerializer &arc, const char *key, SavingRunningscript &rs, SavingRunningscript *def) { if (arc.BeginObject(key)) { arc.ScriptNum("num", rs.scriptnum) ("script", rs.lscript) .EndObject(); } return arc; } void DACSThinker::Serialize(FSerializer &arc) { Super::Serialize(arc); arc("scripts", Scripts) ("lastscript", LastScript); if (arc.isWriting()) { if (RunningScripts.CountUsed()) { ScriptMap::Iterator it(RunningScripts); ScriptMap::Pair *pair; arc.BeginArray("runningscripts"); while (it.NextPair(pair)) { assert(pair->Value != nullptr); SavingRunningscript srs = { pair->Key, pair->Value }; arc(nullptr, srs); } arc.EndArray(); } } else // Loading { DLevelScript *script = nullptr; RunningScripts.Clear(); if (arc.BeginArray("runningscripts")) { auto cnt = arc.ArraySize(); for (unsigned i = 0; i < cnt; i++) { SavingRunningscript srs; arc(nullptr, srs); RunningScripts[srs.scriptnum] = srs.lscript; } arc.EndArray(); } } } cycle_t ACSTime; void DACSThinker::Tick () { ACSTime.Reset(); ACSTime.Clock(); DLevelScript *script = Scripts; while (script) { DLevelScript *next = script->next; script->RunScript (); script = next; } // GlobalACSStrings.Clear(); ACSTime.Unclock(); } void DACSThinker::StopScriptsFor (AActor *actor) { DLevelScript *script = Scripts; while (script != NULL) { DLevelScript *next = script->next; if (script->activator == actor) { script->SetState (DLevelScript::SCRIPT_PleaseRemove); } script = next; } } IMPLEMENT_CLASS(DLevelScript, false, true) IMPLEMENT_POINTERS_START(DLevelScript) IMPLEMENT_POINTER(next) IMPLEMENT_POINTER(prev) IMPLEMENT_POINTER(activator) IMPLEMENT_POINTERS_END //========================================================================== // // SerializeFFontPtr // //========================================================================== void DLevelScript::Serialize(FSerializer &arc) { Super::Serialize(arc); uint32_t pcofs; uint16_t lib; if (arc.isWriting()) { lib = activeBehavior->GetLibraryID() >> LIBRARYID_SHIFT; pcofs = activeBehavior->PC2Ofs(pc); } arc.ScriptNum("scriptnum", script) ("next", next) ("prev", prev) .Enum("state", state) ("statedata", statedata) ("activator", activator) ("activationline", activationline) ("backside", backSide) ("localvars", Localvars) ("lib", lib) ("pc", pcofs) ("activefont", activefont) ("hudwidth", hudwidth) ("hudheight", hudheight) ("cliprectleft", ClipRectLeft) ("cliprectop", ClipRectTop) ("cliprectwidth", ClipRectWidth) ("cliprectheight", ClipRectHeight) ("wrapwidth", WrapWidth) ("inmodulescriptnum", InModuleScriptNumber); if (arc.isReading()) { activeBehavior = FBehavior::StaticGetModule(lib); if (nullptr == activeBehavior) { I_Error("Could not find ACS module"); } pc = activeBehavior->Ofs2PC(pcofs); } } DLevelScript::DLevelScript () { next = prev = NULL; if (DACSThinker::ActiveThinker == NULL) Create(); activefont = SmallFont; } DLevelScript::~DLevelScript () { } void DLevelScript::Unlink () { DACSThinker *controller = DACSThinker::ActiveThinker; if (controller->LastScript == this) { controller->LastScript = prev; GC::WriteBarrier(controller, prev); } if (controller->Scripts == this) { controller->Scripts = next; GC::WriteBarrier(controller, next); } if (prev) { prev->next = next; GC::WriteBarrier(prev, next); } if (next) { next->prev = prev; GC::WriteBarrier(next, prev); } } void DLevelScript::Link () { DACSThinker *controller = DACSThinker::ActiveThinker; next = controller->Scripts; GC::WriteBarrier(this, next); if (controller->Scripts) { controller->Scripts->prev = this; GC::WriteBarrier(controller->Scripts, this); } prev = NULL; controller->Scripts = this; GC::WriteBarrier(controller, this); if (controller->LastScript == NULL) { controller->LastScript = this; } } void DLevelScript::PutLast () { DACSThinker *controller = DACSThinker::ActiveThinker; if (controller->LastScript == this) return; Unlink (); if (controller->Scripts == NULL) { Link (); } else { if (controller->LastScript) controller->LastScript->next = this; prev = controller->LastScript; next = NULL; controller->LastScript = this; } } void DLevelScript::PutFirst () { DACSThinker *controller = DACSThinker::ActiveThinker; if (controller->Scripts == this) return; Unlink (); Link (); } int DLevelScript::Random (int min, int max) { if (max < min) { swapvalues (max, min); } return min + pr_acs(max - min + 1); } int DLevelScript::ThingCount (int type, int stringid, int tid, int tag) { AActor *actor; PClassActor *kind; int count = 0; bool replacemented = false; if (type > 0) { kind = P_GetSpawnableType(type); if (kind == NULL) return 0; } else if (stringid >= 0) { const char *type_name = FBehavior::StaticLookupString (stringid); if (type_name == NULL) return 0; kind = PClass::FindActor(type_name); if (kind == NULL) return 0; } else { kind = NULL; } do_count: if (tid) { FActorIterator iterator (tid); while ( (actor = iterator.Next ()) ) { if (actor->health > 0 && (kind == NULL || actor->IsA (kind))) { if (tag == -1 || tagManager.SectorHasTag(actor->Sector, tag)) { // Don't count items in somebody's inventory if (actor->IsMapActor()) { count++; } } } } } else { TThinkerIterator iterator; while ( (actor = iterator.Next ()) ) { if (actor->health > 0 && (kind == NULL || actor->IsA (kind))) { if (tag == -1 || tagManager.SectorHasTag(actor->Sector, tag)) { // Don't count items in somebody's inventory if (actor->IsMapActor()) { count++; } } } } } if (!replacemented && kind != NULL) { // Again, with decorate replacements replacemented = true; PClassActor *newkind = kind->GetReplacement(); if (newkind != kind) { kind = newkind; goto do_count; } } return count; } void DLevelScript::ChangeFlat (int tag, int name, bool floorOrCeiling) { FTextureID flat; int secnum = -1; const char *flatname = FBehavior::StaticLookupString (name); if (flatname == NULL) return; flat = TexMan.GetTexture (flatname, ETextureType::Flat, FTextureManager::TEXMAN_Overridable); FSectorTagIterator it(tag); while ((secnum = it.Next()) >= 0) { int pos = floorOrCeiling? sector_t::ceiling : sector_t::floor; level.sectors[secnum].SetTexture(pos, flat); } } int DLevelScript::CountPlayers () { int count = 0, i; for (i = 0; i < MAXPLAYERS; i++) if (playeringame[i]) count++; return count; } void DLevelScript::SetLineTexture (int lineid, int side, int position, int name) { FTextureID texture; int linenum = -1; const char *texname = FBehavior::StaticLookupString (name); if (texname == nullptr) return; side = !!side; texture = TexMan.GetTexture (texname, ETextureType::Wall, FTextureManager::TEXMAN_Overridable); FLineIdIterator itr(lineid); while ((linenum = itr.Next()) >= 0) { side_t *sidedef; sidedef = level.lines[linenum].sidedef[side]; if (sidedef == nullptr) continue; switch (position) { case TEXTURE_TOP: sidedef->SetTexture(side_t::top, texture); break; case TEXTURE_MIDDLE: sidedef->SetTexture(side_t::mid, texture); break; case TEXTURE_BOTTOM: sidedef->SetTexture(side_t::bottom, texture); break; default: break; } } } int DLevelScript::DoSpawn (int type, const DVector3 &pos, int tid, DAngle angle, bool force) { PClassActor *info = PClass::FindActor(FBehavior::StaticLookupString (type)); AActor *actor = NULL; int spawncount = 0; if (info != NULL) { info = info->GetReplacement (); if ((GetDefaultByType (info)->flags3 & MF3_ISMONSTER) && ((dmflags & DF_NO_MONSTERS) || (level.flags2 & LEVEL2_NOMONSTERS))) { return 0; } actor = Spawn (info, pos, ALLOW_REPLACE); if (actor != NULL) { ActorFlags2 oldFlags2 = actor->flags2; actor->flags2 |= MF2_PASSMOBJ; if (force || P_TestMobjLocation (actor)) { actor->Angles.Yaw = angle; actor->SetTID(tid); if (actor->flags & MF_SPECIAL) actor->flags |= MF_DROPPED; // Don't respawn actor->flags2 = oldFlags2; spawncount++; } else { // If this is a monster, subtract it from the total monster // count, because it already added to it during spawning. actor->ClearCounters(); actor->Destroy (); actor = NULL; } } } return spawncount; } int DLevelScript::DoSpawn(int type, int x, int y, int z, int tid, int angle, bool force) { return DoSpawn(type, DVector3(ACSToDouble(x), ACSToDouble(y), ACSToDouble(z)), tid, angle * (360. / 256), force); } int DLevelScript::DoSpawnSpot (int type, int spot, int tid, int angle, bool force) { int spawned = 0; if (spot != 0) { FActorIterator iterator (spot); AActor *aspot; while ( (aspot = iterator.Next ()) ) { spawned += DoSpawn (type, aspot->Pos(), tid, angle * (360. / 256), force); } } else if (activator != NULL) { spawned += DoSpawn (type, activator->Pos(), tid, angle * (360. / 256), force); } return spawned; } int DLevelScript::DoSpawnSpotFacing (int type, int spot, int tid, bool force) { int spawned = 0; if (spot != 0) { FActorIterator iterator (spot); AActor *aspot; while ( (aspot = iterator.Next ()) ) { spawned += DoSpawn (type, aspot->Pos(), tid, aspot->Angles.Yaw, force); } } else if (activator != NULL) { spawned += DoSpawn (type, activator->Pos(), tid, activator->Angles.Yaw, force); } return spawned; } void DLevelScript::DoFadeTo (int r, int g, int b, int a, int time) { DoFadeRange (0, 0, 0, -1, clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), clamp(a, 0, 65536), time); } void DLevelScript::DoFadeRange (int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2, int time) { player_t *viewer; float ftime = (float)time / 65536.f; bool fadingFrom = a1 >= 0; float fr1 = 0, fg1 = 0, fb1 = 0, fa1 = 0; float fr2, fg2, fb2, fa2; int i; fr2 = (float)r2 / 255.f; fg2 = (float)g2 / 255.f; fb2 = (float)b2 / 255.f; fa2 = (float)a2 / 65536.f; if (fadingFrom) { fr1 = (float)r1 / 255.f; fg1 = (float)g1 / 255.f; fb1 = (float)b1 / 255.f; fa1 = (float)a1 / 65536.f; } if (activator != NULL) { viewer = activator->player; if (viewer == NULL) return; i = MAXPLAYERS; goto showme; } else { for (i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i]) { viewer = &players[i]; showme: if (ftime <= 0.f) { viewer->BlendR = fr2; viewer->BlendG = fg2; viewer->BlendB = fb2; viewer->BlendA = fa2; } else { if (!fadingFrom) { if (viewer->BlendA <= 0.f) { fr1 = fr2; fg1 = fg2; fb1 = fb2; fa1 = 0.f; } else { fr1 = viewer->BlendR; fg1 = viewer->BlendG; fb1 = viewer->BlendB; fa1 = viewer->BlendA; } } Create (fr1, fg1, fb1, fa1, fr2, fg2, fb2, fa2, ftime, viewer->mo); } } } } } void DLevelScript::DoSetFont (int fontnum) { const char *fontname = FBehavior::StaticLookupString (fontnum); activefont = V_GetFont (fontname); if (activefont == NULL) { activefont = SmallFont; } } int DoSetMaster (AActor *self, AActor *master) { AActor *defs; if (self->flags3&MF3_ISMONSTER) { if (master) { if (master->flags3&MF3_ISMONSTER) { self->FriendPlayer = 0; self->master = master; level.total_monsters -= self->CountsAsKill(); self->flags = (self->flags & ~MF_FRIENDLY) | (master->flags & MF_FRIENDLY); level.total_monsters += self->CountsAsKill(); // Don't attack your new master if (self->target == self->master) self->target = nullptr; if (self->lastenemy == self->master) self->lastenemy = nullptr; if (self->LastHeard == self->master) self->LastHeard = nullptr; return 1; } else if (master->player) { // [KS] Be friendly to this player self->master = nullptr; level.total_monsters -= self->CountsAsKill(); self->flags|=MF_FRIENDLY; self->SetFriendPlayer(master->player); AActor * attacker=master->player->attacker; if (attacker) { if (!(attacker->flags&MF_FRIENDLY) || (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=self->FriendPlayer)) { self->LastHeard = self->target = attacker; } } // And stop attacking him if necessary. if (self->target == master) self->target = nullptr; if (self->lastenemy == master) self->lastenemy = nullptr; if (self->LastHeard == master) self->LastHeard = nullptr; return 1; } } else { self->master = nullptr; self->FriendPlayer = 0; // Go back to whatever friendliness we usually have... defs = self->GetDefault(); level.total_monsters -= self->CountsAsKill(); self->flags = (self->flags & ~MF_FRIENDLY) | (defs->flags & MF_FRIENDLY); level.total_monsters += self->CountsAsKill(); // ...And re-side with our friends. if (self->target && !self->IsHostile (self->target)) self->target = nullptr; if (self->lastenemy && !self->IsHostile (self->lastenemy)) self->lastenemy = nullptr; if (self->LastHeard && !self->IsHostile (self->LastHeard)) self->LastHeard = nullptr; return 1; } } return 0; } int DoGetMasterTID (AActor *self) { if (self->master) return self->master->tid; else if (self->FriendPlayer) { player_t *player = &players[(self->FriendPlayer)-1]; return player->mo->tid; } else return 0; } AActor *SingleActorFromTID (int tid, AActor *defactor) { if (tid == 0) { return defactor; } else { FActorIterator iterator (tid); return iterator.Next(); } } enum { APROP_Health = 0, APROP_Speed = 1, APROP_Damage = 2, APROP_Alpha = 3, APROP_RenderStyle = 4, APROP_SeeSound = 5, // Sounds can only be set, not gotten APROP_AttackSound = 6, APROP_PainSound = 7, APROP_DeathSound = 8, APROP_ActiveSound = 9, APROP_Ambush = 10, APROP_Invulnerable = 11, APROP_JumpZ = 12, // [GRB] APROP_ChaseGoal = 13, APROP_Frightened = 14, APROP_Gravity = 15, APROP_Friendly = 16, APROP_SpawnHealth = 17, APROP_Dropped = 18, APROP_Notarget = 19, APROP_Species = 20, APROP_NameTag = 21, APROP_Score = 22, APROP_Notrigger = 23, APROP_DamageFactor = 24, APROP_MasterTID = 25, APROP_TargetTID = 26, APROP_TracerTID = 27, APROP_WaterLevel = 28, APROP_ScaleX = 29, APROP_ScaleY = 30, APROP_Dormant = 31, APROP_Mass = 32, APROP_Accuracy = 33, APROP_Stamina = 34, APROP_Height = 35, APROP_Radius = 36, APROP_ReactionTime = 37, APROP_MeleeRange = 38, APROP_ViewHeight = 39, APROP_AttackZOffset = 40, APROP_StencilColor = 41, APROP_Friction = 42, APROP_DamageMultiplier=43, APROP_MaxStepHeight = 44, APROP_MaxDropOffHeight= 45, APROP_DamageType = 46, }; // These are needed for ACS's APROP_RenderStyle static const int LegacyRenderStyleIndices[] = { 0, // STYLE_None, 1, // STYLE_Normal, 2, // STYLE_Fuzzy, 3, // STYLE_SoulTrans, 4, // STYLE_OptFuzzy, 5, // STYLE_Stencil, 64, // STYLE_Translucent 65, // STYLE_Add, 66, // STYLE_Shaded, 67, // STYLE_TranslucentStencil, 68, // STYLE_Shadow, 69, // STYLE_Subtract, 6, // STYLE_AddStencil 7, // STYLE_AddShaded -1 }; void DLevelScript::SetActorProperty (int tid, int property, int value) { if (tid == 0) { DoSetActorProperty (activator, property, value); } else { AActor *actor; FActorIterator iterator (tid); while ((actor = iterator.Next()) != NULL) { DoSetActorProperty (actor, property, value); } } } void DLevelScript::DoSetActorProperty (AActor *actor, int property, int value) { if (actor == NULL) { return; } switch (property) { case APROP_Health: // Don't alter the health of dead things. if (actor->health <= 0 || (actor->player != NULL && actor->player->playerstate == PST_DEAD)) { break; } actor->health = value; if (actor->player != NULL) { actor->player->health = value; } // If the health is set to a non-positive value, properly kill the actor. if (value <= 0) { actor->Die(activator, activator); } break; case APROP_Speed: actor->Speed = ACSToDouble(value); break; case APROP_Damage: actor->SetDamage(value); break; case APROP_Alpha: actor->Alpha = ACSToDouble(value); break; case APROP_RenderStyle: for(int i=0; LegacyRenderStyleIndices[i] >= 0; i++) { if (LegacyRenderStyleIndices[i] == value) { actor->RenderStyle = ERenderStyle(i); break; } } break; case APROP_Ambush: if (value) actor->flags |= MF_AMBUSH; else actor->flags &= ~MF_AMBUSH; break; case APROP_Dropped: if (value) actor->flags |= MF_DROPPED; else actor->flags &= ~MF_DROPPED; break; case APROP_Invulnerable: if (value) actor->flags2 |= MF2_INVULNERABLE; else actor->flags2 &= ~MF2_INVULNERABLE; break; case APROP_Notarget: if (value) actor->flags3 |= MF3_NOTARGET; else actor->flags3 &= ~MF3_NOTARGET; break; case APROP_Notrigger: if (value) actor->flags6 |= MF6_NOTRIGGER; else actor->flags6 &= ~MF6_NOTRIGGER; break; case APROP_JumpZ: if (actor->IsKindOf(NAME_PlayerPawn)) actor->FloatVar(NAME_JumpZ) = ACSToDouble(value); break; // [GRB] case APROP_ChaseGoal: if (value) actor->flags5 |= MF5_CHASEGOAL; else actor->flags5 &= ~MF5_CHASEGOAL; break; case APROP_Frightened: if (value) actor->flags4 |= MF4_FRIGHTENED; else actor->flags4 &= ~MF4_FRIGHTENED; break; case APROP_Friendly: if (actor->CountsAsKill()) level.total_monsters--; if (value) { actor->flags |= MF_FRIENDLY; } else { actor->flags &= ~MF_FRIENDLY; } if (actor->CountsAsKill()) level.total_monsters++; break; case APROP_SpawnHealth: if (actor->IsKindOf(NAME_PlayerPawn)) { actor->IntVar(NAME_MaxHealth) = value; } break; case APROP_Gravity: actor->Gravity = ACSToDouble(value); break; case APROP_SeeSound: actor->SeeSound = FBehavior::StaticLookupString(value); break; case APROP_AttackSound: actor->AttackSound = FBehavior::StaticLookupString(value); break; case APROP_PainSound: actor->PainSound = FBehavior::StaticLookupString(value); break; case APROP_DeathSound: actor->DeathSound = FBehavior::StaticLookupString(value); break; case APROP_ActiveSound: actor->ActiveSound = FBehavior::StaticLookupString(value); break; case APROP_Species: actor->Species = FBehavior::StaticLookupString(value); break; case APROP_Score: actor->Score = value; break; case APROP_NameTag: actor->SetTag(FBehavior::StaticLookupString(value)); break; case APROP_DamageFactor: actor->DamageFactor = ACSToDouble(value); break; case APROP_DamageMultiplier: actor->DamageMultiply = ACSToDouble(value); break; case APROP_MasterTID: AActor *other; other = SingleActorFromTID (value, NULL); DoSetMaster (actor, other); break; case APROP_ScaleX: actor->Scale.X = ACSToDouble(value); break; case APROP_ScaleY: actor->Scale.Y = ACSToDouble(value); break; case APROP_Mass: actor->Mass = value; break; case APROP_Accuracy: actor->accuracy = value; break; case APROP_Stamina: actor->stamina = value; break; case APROP_ReactionTime: actor->reactiontime = value; break; case APROP_MeleeRange: actor->meleerange = ACSToDouble(value); break; case APROP_ViewHeight: if (actor->IsKindOf(NAME_PlayerPawn)) { actor->FloatVar(NAME_ViewHeight) = ACSToDouble(value); if (actor->player != NULL) { actor->player->viewheight = ACSToDouble(value); } } break; case APROP_AttackZOffset: if (actor->IsKindOf(NAME_PlayerPawn)) actor->FloatVar(NAME_AttackZOffset) = ACSToDouble(value); break; case APROP_StencilColor: actor->SetShade(value); break; case APROP_Friction: actor->Friction = ACSToDouble(value); break; case APROP_MaxStepHeight: actor->MaxStepHeight = ACSToDouble(value); break; case APROP_MaxDropOffHeight: actor->MaxDropOffHeight = ACSToDouble(value); break; case APROP_DamageType: actor->DamageType = FBehavior::StaticLookupString(value); break; default: // do nothing. break; } } int DLevelScript::GetActorProperty (int tid, int property) { AActor *actor = SingleActorFromTID (tid, activator); if (actor == NULL) { return 0; } switch (property) { case APROP_Health: return actor->health; case APROP_Speed: return DoubleToACS(actor->Speed); case APROP_Damage: return actor->GetMissileDamage(0,1); case APROP_DamageFactor:return DoubleToACS(actor->DamageFactor); case APROP_DamageMultiplier: return DoubleToACS(actor->DamageMultiply); case APROP_Alpha: return DoubleToACS(actor->Alpha); case APROP_RenderStyle: for (int style = STYLE_None; style < STYLE_Count; ++style) { // Check for a legacy render style that matches. if (LegacyRenderStyles[style] == actor->RenderStyle) { return LegacyRenderStyleIndices[style]; } } // The current render style isn't expressable as a legacy style, // so pretends it's normal. return STYLE_Normal; case APROP_Gravity: return DoubleToACS(actor->Gravity); case APROP_Invulnerable:return !!(actor->flags2 & MF2_INVULNERABLE); case APROP_Ambush: return !!(actor->flags & MF_AMBUSH); case APROP_Dropped: return !!(actor->flags & MF_DROPPED); case APROP_ChaseGoal: return !!(actor->flags5 & MF5_CHASEGOAL); case APROP_Frightened: return !!(actor->flags4 & MF4_FRIGHTENED); case APROP_Friendly: return !!(actor->flags & MF_FRIENDLY); case APROP_Notarget: return !!(actor->flags3 & MF3_NOTARGET); case APROP_Notrigger: return !!(actor->flags6 & MF6_NOTRIGGER); case APROP_Dormant: return !!(actor->flags2 & MF2_DORMANT); case APROP_SpawnHealth: return actor->GetMaxHealth(); case APROP_JumpZ: if (actor->IsKindOf(NAME_PlayerPawn)) { return DoubleToACS(actor->FloatVar(NAME_JumpZ)); } else { return 0; } case APROP_Score: return actor->Score; case APROP_MasterTID: return DoGetMasterTID (actor); case APROP_TargetTID: return (actor->target != NULL)? actor->target->tid : 0; case APROP_TracerTID: return (actor->tracer != NULL)? actor->tracer->tid : 0; case APROP_WaterLevel: return actor->waterlevel; case APROP_ScaleX: return DoubleToACS(actor->Scale.X); case APROP_ScaleY: return DoubleToACS(actor->Scale.Y); case APROP_Mass: return actor->Mass; case APROP_Accuracy: return actor->accuracy; case APROP_Stamina: return actor->stamina; case APROP_Height: return DoubleToACS(actor->Height); case APROP_Radius: return DoubleToACS(actor->radius); case APROP_ReactionTime:return actor->reactiontime; case APROP_MeleeRange: return DoubleToACS(actor->meleerange); case APROP_ViewHeight: if (actor->player) { return DoubleToACS(actor->player->DefaultViewHeight()); } else { return 0; } case APROP_AttackZOffset: // Note that this is inconsistent with every other place, where the return for monsters is 8, not 0! if (actor->IsKindOf(NAME_PlayerPawn)) { return DoubleToACS(actor->FloatVar(NAME_AttackZOffset)); } else { return 0; } case APROP_SeeSound: return GlobalACSStrings.AddString(actor->SeeSound); case APROP_AttackSound: return GlobalACSStrings.AddString(actor->AttackSound); case APROP_PainSound: return GlobalACSStrings.AddString(actor->PainSound); case APROP_DeathSound: return GlobalACSStrings.AddString(actor->DeathSound); case APROP_ActiveSound: return GlobalACSStrings.AddString(actor->ActiveSound); case APROP_Species: return GlobalACSStrings.AddString(actor->GetSpecies()); case APROP_NameTag: return GlobalACSStrings.AddString(actor->GetTag()); case APROP_StencilColor:return actor->fillcolor; case APROP_Friction: return DoubleToACS(actor->Friction); case APROP_MaxStepHeight: return DoubleToACS(actor->MaxStepHeight); case APROP_MaxDropOffHeight: return DoubleToACS(actor->MaxDropOffHeight); case APROP_DamageType: return GlobalACSStrings.AddString(actor->DamageType); default: return 0; } } int DLevelScript::CheckActorProperty (int tid, int property, int value) { AActor *actor = SingleActorFromTID (tid, activator); const char *string = NULL; if (actor == NULL) { return 0; } switch (property) { // Default default: return 0; // Straightforward integer values: case APROP_Health: case APROP_Speed: case APROP_Damage: case APROP_DamageFactor: case APROP_Alpha: case APROP_RenderStyle: case APROP_Gravity: case APROP_SpawnHealth: case APROP_JumpZ: case APROP_Score: case APROP_MasterTID: case APROP_TargetTID: case APROP_TracerTID: case APROP_WaterLevel: case APROP_ScaleX: case APROP_ScaleY: case APROP_Mass: case APROP_Accuracy: case APROP_Stamina: case APROP_Height: case APROP_Radius: case APROP_ReactionTime: case APROP_MeleeRange: case APROP_ViewHeight: case APROP_AttackZOffset: case APROP_MaxStepHeight: case APROP_MaxDropOffHeight: case APROP_StencilColor: return (GetActorProperty(tid, property) == value); // Boolean values need to compare to a binary version of value case APROP_Ambush: case APROP_Invulnerable: case APROP_Dropped: case APROP_ChaseGoal: case APROP_Frightened: case APROP_Friendly: case APROP_Notarget: case APROP_Notrigger: case APROP_Dormant: return (GetActorProperty(tid, property) == (!!value)); // Strings are covered by GetActorProperty, but they're fairly // heavy-duty, so make the check here. case APROP_SeeSound: string = actor->SeeSound; break; case APROP_AttackSound: string = actor->AttackSound; break; case APROP_PainSound: string = actor->PainSound; break; case APROP_DeathSound: string = actor->DeathSound; break; case APROP_ActiveSound: string = actor->ActiveSound; break; case APROP_Species: string = actor->GetSpecies(); break; case APROP_NameTag: string = actor->GetTag(); break; case APROP_DamageType: string = actor->DamageType; break; } if (string == NULL) string = ""; return (!stricmp(string, FBehavior::StaticLookupString(value))); } bool DLevelScript::DoCheckActorTexture(int tid, AActor *activator, int string, bool floor) { AActor *actor = SingleActorFromTID(tid, activator); if (actor == NULL) { return 0; } FTexture *tex = TexMan.FindTexture(FBehavior::StaticLookupString(string), ETextureType::Flat, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_TryAny|FTextureManager::TEXMAN_DontCreate); if (tex == NULL) { // If the texture we want to check against doesn't exist, then // they're obviously not the same. return 0; } FTextureID secpic; sector_t *resultsec; F3DFloor *resffloor; if (floor) { NextLowestFloorAt(actor->Sector, actor->X(), actor->Y(), actor->Z(), 0, actor->MaxStepHeight, &resultsec, &resffloor); secpic = resffloor ? *resffloor->top.texture : resultsec->planes[sector_t::floor].Texture; } else { NextHighestCeilingAt(actor->Sector, actor->X(), actor->Y(), actor->Z(), actor->Top(), 0, &resultsec, &resffloor); secpic = resffloor ? *resffloor->bottom.texture : resultsec->planes[sector_t::ceiling].Texture; } return tex == TexMan[secpic]; } int DLevelScript::GetPlayerInput(int playernum, int inputnum) { player_t *p; if (playernum < 0) { if (activator == NULL) { return 0; } p = activator->player; } else if (playernum >= MAXPLAYERS || !playeringame[playernum]) { return 0; } else { p = &players[playernum]; } if (p == NULL) { return 0; } return P_Thing_CheckInputNum(p, inputnum); } enum { ACTOR_NONE = 0x00000000, ACTOR_WORLD = 0x00000001, ACTOR_PLAYER = 0x00000002, ACTOR_BOT = 0x00000004, ACTOR_VOODOODOLL = 0x00000008, ACTOR_MONSTER = 0x00000010, ACTOR_ALIVE = 0x00000020, ACTOR_DEAD = 0x00000040, ACTOR_MISSILE = 0x00000080, ACTOR_GENERIC = 0x00000100 }; int DLevelScript::DoClassifyActor(int tid) { AActor *actor; int classify; if (tid == 0) { actor = activator; if (actor == NULL) { return ACTOR_WORLD; } } else { FActorIterator it(tid); actor = it.Next(); } if (actor == NULL) { return ACTOR_NONE; } classify = 0; if (actor->player != NULL) { classify |= ACTOR_PLAYER; if (actor->player->playerstate == PST_DEAD) { classify |= ACTOR_DEAD; } else { classify |= ACTOR_ALIVE; } if (actor->player->mo != actor) { classify |= ACTOR_VOODOODOLL; } if (actor->player->Bot != NULL) { classify |= ACTOR_BOT; } } else if (actor->flags3 & MF3_ISMONSTER) { classify |= ACTOR_MONSTER; if (actor->health <= 0) { classify |= ACTOR_DEAD; } else { classify |= ACTOR_ALIVE; } } else if (actor->flags & MF_MISSILE) { classify |= ACTOR_MISSILE; } else { classify |= ACTOR_GENERIC; } return classify; } enum { SOUND_See, SOUND_Attack, SOUND_Pain, SOUND_Death, SOUND_Active, SOUND_Use, SOUND_Bounce, SOUND_WallBounce, SOUND_CrushPain, SOUND_Howl, }; static FSoundID GetActorSound(AActor *actor, int soundtype) { switch (soundtype) { case SOUND_See: return actor->SeeSound; case SOUND_Attack: return actor->AttackSound; case SOUND_Pain: return actor->PainSound; case SOUND_Death: return actor->DeathSound; case SOUND_Active: return actor->ActiveSound; case SOUND_Use: return actor->UseSound; case SOUND_Bounce: return actor->BounceSound; case SOUND_WallBounce: return actor->WallBounceSound; case SOUND_CrushPain: return actor->CrushPainSound; case SOUND_Howl: return actor->SoundVar(NAME_HowlSound); default: return 0; } } enum EACSFunctions { ACSF_GetLineUDMFInt=1, ACSF_GetLineUDMFFixed, ACSF_GetThingUDMFInt, ACSF_GetThingUDMFFixed, ACSF_GetSectorUDMFInt, ACSF_GetSectorUDMFFixed, ACSF_GetSideUDMFInt, ACSF_GetSideUDMFFixed, ACSF_GetActorVelX, ACSF_GetActorVelY, ACSF_GetActorVelZ, ACSF_SetActivator, ACSF_SetActivatorToTarget, ACSF_GetActorViewHeight, ACSF_GetChar, ACSF_GetAirSupply, ACSF_SetAirSupply, ACSF_SetSkyScrollSpeed, ACSF_GetArmorType, ACSF_SpawnSpotForced, ACSF_SpawnSpotFacingForced, ACSF_CheckActorProperty, ACSF_SetActorVelocity, ACSF_SetUserVariable, ACSF_GetUserVariable, ACSF_Radius_Quake2, ACSF_CheckActorClass, ACSF_SetUserArray, ACSF_GetUserArray, ACSF_SoundSequenceOnActor, ACSF_SoundSequenceOnSector, ACSF_SoundSequenceOnPolyobj, ACSF_GetPolyobjX, ACSF_GetPolyobjY, ACSF_CheckSight, ACSF_SpawnForced, ACSF_AnnouncerSound, // Skulltag ACSF_SetPointer, ACSF_ACS_NamedExecute, ACSF_ACS_NamedSuspend, ACSF_ACS_NamedTerminate, ACSF_ACS_NamedLockedExecute, ACSF_ACS_NamedLockedExecuteDoor, ACSF_ACS_NamedExecuteWithResult, ACSF_ACS_NamedExecuteAlways, ACSF_UniqueTID, ACSF_IsTIDUsed, ACSF_Sqrt, ACSF_FixedSqrt, ACSF_VectorLength, ACSF_SetHUDClipRect, ACSF_SetHUDWrapWidth, ACSF_SetCVar, ACSF_GetUserCVar, ACSF_SetUserCVar, ACSF_GetCVarString, ACSF_SetCVarString, ACSF_GetUserCVarString, ACSF_SetUserCVarString, ACSF_LineAttack, ACSF_PlaySound, ACSF_StopSound, ACSF_strcmp, ACSF_stricmp, ACSF_StrLeft, ACSF_StrRight, ACSF_StrMid, ACSF_GetActorClass, ACSF_GetWeapon, ACSF_SoundVolume, ACSF_PlayActorSound, ACSF_SpawnDecal, ACSF_CheckFont, ACSF_DropItem, ACSF_CheckFlag, ACSF_SetLineActivation, ACSF_GetLineActivation, ACSF_GetActorPowerupTics, ACSF_ChangeActorAngle, ACSF_ChangeActorPitch, // 80 ACSF_GetArmorInfo, ACSF_DropInventory, ACSF_PickActor, ACSF_IsPointerEqual, ACSF_CanRaiseActor, ACSF_SetActorTeleFog, // 86 ACSF_SwapActorTeleFog, ACSF_SetActorRoll, ACSF_ChangeActorRoll, ACSF_GetActorRoll, ACSF_QuakeEx, ACSF_Warp, // 92 ACSF_GetMaxInventory, ACSF_SetSectorDamage, ACSF_SetSectorTerrain, ACSF_SpawnParticle, ACSF_SetMusicVolume, ACSF_CheckProximity, ACSF_CheckActorState, // 99 /* Zandronum's - these must be skipped when we reach 99! -100:ResetMap(0), -101 : PlayerIsSpectator(1), -102 : ConsolePlayerNumber(0), -103 : GetTeamProperty(2), -104 : GetPlayerLivesLeft(1), -105 : SetPlayerLivesLeft(2), -106 : KickFromGame(2), */ ACSF_CheckClass = 200, ACSF_DamageActor, // [arookas] ACSF_SetActorFlag, ACSF_SetTranslation, ACSF_GetActorFloorTexture, ACSF_GetActorFloorTerrain, ACSF_StrArg, ACSF_Floor, ACSF_Round, ACSF_Ceil, ACSF_ScriptCall, ACSF_StartSlideshow, ACSF_GetSectorHealth, ACSF_GetLineHealth, // Eternity's ACSF_GetLineX = 300, ACSF_GetLineY, // OpenGL stuff ACSF_SetSectorGlow = 400, ACSF_SetFogDensity, // ZDaemon ACSF_GetTeamScore = 19620, // (int team) ACSF_SetTeamScore, // (int team, int value }; int DLevelScript::SideFromID(int id, int side) { if (side != 0 && side != 1) return -1; if (id == 0) { if (activationline == NULL) return -1; if (activationline->sidedef[side] == NULL) return -1; return activationline->sidedef[side]->UDMFIndex; } else { int line = P_FindFirstLineFromID(id); if (line == -1) return -1; if (level.lines[line].sidedef[side] == NULL) return -1; return level.lines[line].sidedef[side]->UDMFIndex; } } int DLevelScript::LineFromID(int id) { if (id == 0) { if (activationline == NULL) return -1; return activationline->Index(); } else { return P_FindFirstLineFromID(id); } } bool GetVarAddrType(AActor *self, FName varname, int index, void *&addr, PType *&type, bool readonly) { PField *var = dyn_cast(self->GetClass()->FindSymbol(varname, true)); if (var == NULL || (!readonly && (var->Flags & VARF_Native))) { return false; } type = var->Type; uint8_t *baddr = reinterpret_cast(self) + var->Offset; if (type->isArray()) { PArray *arraytype = static_cast(type); // unwrap contained type type = arraytype->ElementType; // offset by index (if in bounds) if ((unsigned)index >= arraytype->ElementCount) { // out of bounds return false; } baddr += arraytype->ElementSize * index; } else if (index != 0) { // ignore attempts to set indexed values on non-arrays return false; } addr = baddr; // We don't want Int subclasses like Name or Color to be accessible here. if (!type->isInt() && !type->isFloat() && type != TypeBool && type != TypeString) { // For reading, we also support Name types. if (readonly && (type == TypeName)) { return true; } return false; } return true; } static void SetUserVariable(AActor *self, FName varname, int index, int value) { void *addr; PType *type; if (GetVarAddrType(self, varname, index, addr, type, false)) { if (type == TypeString) { FString str = FBehavior::StaticLookupString(value); type->InitializeValue(addr, &str); } else if (type->isFloat()) { type->SetValue(addr, ACSToDouble(value)); } else { type->SetValue(addr, value); } } } static int GetUserVariable(AActor *self, FName varname, int index) { void *addr; PType *type; if (GetVarAddrType(self, varname, index, addr, type, true)) { if (type->isFloat()) { return DoubleToACS(type->GetValueFloat(addr)); } else if (type == TypeName) { return GlobalACSStrings.AddString(FName(ENamedName(type->GetValueInt(addr))).GetChars()); } else if (type == TypeString) { return GlobalACSStrings.AddString(*(FString *)addr); } else { return type->GetValueInt(addr); } } return 0; } // Converts fixed- to floating-point as required. static void DoSetCVar(FBaseCVar *cvar, int value, bool is_string, bool force=false) { UCVarValue val; ECVarType type; // For serverinfo variables, only the arbitrator should set it. // The actual change to this cvar will not show up until it's // been replicated to all peers. if ((cvar->GetFlags() & CVAR_SERVERINFO) && consoleplayer != Net_Arbitrator) { return; } if (is_string) { val.String = FBehavior::StaticLookupString(value); type = CVAR_String; } else if (cvar->GetRealType() == CVAR_Float) { val.Float = ACSToFloat(value); type = CVAR_Float; } else { val.Int = value; type = CVAR_Int; } if (force) { cvar->ForceSet(val, type, true); } else { cvar->SetGenericRep(val, type); } } // Converts floating- to fixed-point as required. static int DoGetCVar(FBaseCVar *cvar, bool is_string) { UCVarValue val; if (cvar == nullptr) { return 0; } else if (is_string) { val = cvar->GetGenericRep(CVAR_String); return GlobalACSStrings.AddString(val.String); } else if (cvar->GetRealType() == CVAR_Float) { val = cvar->GetGenericRep(CVAR_Float); return DoubleToACS(val.Float); } else { val = cvar->GetGenericRep(CVAR_Int); return val.Int; } } static int SetUserCVar(int playernum, const char *cvarname, int value, bool is_string) { if ((unsigned)playernum >= MAXPLAYERS || !playeringame[playernum]) { return 0; } FBaseCVar **cvar_p = players[playernum].userinfo.CheckKey(FName(cvarname, true)); FBaseCVar *cvar; // Only mod-created cvars may be set. if (cvar_p == NULL || (cvar = *cvar_p) == NULL || (cvar->GetFlags() & CVAR_IGNORE) || !(cvar->GetFlags() & CVAR_MOD)) { return 0; } DoSetCVar(cvar, value, is_string); // If we are this player, then also reflect this change in the local version of this cvar. if (playernum == consoleplayer) { FBaseCVar *cvar = FindCVar(cvarname, NULL); // If we can find it in the userinfo, then we should also be able to find it in the normal cvar list, // but check just to be safe. if (cvar != NULL) { DoSetCVar(cvar, value, is_string, true); } } return 1; } static int SetCVar(AActor *activator, const char *cvarname, int value, bool is_string) { FBaseCVar *cvar = FindCVar(cvarname, NULL); // Only mod-created cvars may be set. if (cvar == NULL || (cvar->GetFlags() & (CVAR_IGNORE|CVAR_NOSET)) || !(cvar->GetFlags() & CVAR_MOD)) { return 0; } // For userinfo cvars, redirect to SetUserCVar if (cvar->GetFlags() & CVAR_USERINFO) { if (activator == NULL || activator->player == NULL) { return 0; } return SetUserCVar(int(activator->player - players), cvarname, value, is_string); } DoSetCVar(cvar, value, is_string); return 1; } static bool DoSpawnDecal(AActor *actor, const FDecalTemplate *tpl, int flags, DAngle angle, double zofs, double distance) { if (!(flags & SDF_ABSANGLE)) { angle += actor->Angles.Yaw; } return NULL != ShootDecal(tpl, actor, actor->Sector, actor->X(), actor->Y(), actor->Center() - actor->Floorclip + actor->GetBobOffset() + zofs, angle, distance, !!(flags & SDF_PERMANENT)); } static void SetActorAngle(AActor *activator, int tid, int angle, bool interpolate) { DAngle an = ACSToAngle(angle); if (tid == 0) { if (activator != NULL) { activator->SetAngle(an, interpolate); } } else { FActorIterator iterator(tid); AActor *actor; while ((actor = iterator.Next())) { actor->SetAngle(an, interpolate); } } } static void SetActorPitch(AActor *activator, int tid, int angle, bool interpolate) { DAngle an = ACSToAngle(angle).Normalized180(); if (tid == 0) { if (activator != NULL) { activator->SetPitch(an, interpolate); } } else { FActorIterator iterator(tid); AActor *actor; while ((actor = iterator.Next())) { actor->SetPitch(an, interpolate); } } } static void SetActorRoll(AActor *activator, int tid, int angle, bool interpolate) { DAngle an = ACSToAngle(angle); if (tid == 0) { if (activator != NULL) { activator->SetRoll(an, interpolate); } } else { FActorIterator iterator(tid); AActor *actor; while ((actor = iterator.Next())) { actor->SetRoll(an, interpolate); } } } static void SetActorTeleFog(AActor *activator, int tid, FString telefogsrc, FString telefogdest) { // Set the actor's telefog to the specified actor. Handle "" as "don't // change" since "None" should work just fine for disabling the fog (given // that it will resolve to NAME_None which is not a valid actor name). if (tid == 0) { if (activator != NULL) { if (telefogsrc.IsNotEmpty()) activator->TeleFogSourceType = PClass::FindActor(telefogsrc); if (telefogdest.IsNotEmpty()) activator->TeleFogDestType = PClass::FindActor(telefogdest); } } else { FActorIterator iterator(tid); AActor *actor; PClassActor * src = PClass::FindActor(telefogsrc); PClassActor * dest = PClass::FindActor(telefogdest); while ((actor = iterator.Next())) { if (telefogsrc.IsNotEmpty()) actor->TeleFogSourceType = src; if (telefogdest.IsNotEmpty()) actor->TeleFogDestType = dest; } } } static int SwapActorTeleFog(AActor *activator, int tid) { int count = 0; if (tid == 0) { if ((activator == NULL) || (activator->TeleFogSourceType == activator->TeleFogDestType)) return 0; //Does nothing if they're the same. swapvalues (activator->TeleFogSourceType, activator->TeleFogDestType); return 1; } else { FActorIterator iterator(tid); AActor *actor; while ((actor = iterator.Next())) { if (actor->TeleFogSourceType == actor->TeleFogDestType) continue; //They're the same. Save the effort. swapvalues (actor->TeleFogSourceType, actor->TeleFogDestType); count++; } } return count; } static int ScriptCall(AActor *activator, unsigned argc, int32_t *args) { int retval = 0; if (argc >= 2) { auto clsname = FBehavior::StaticLookupString(args[0]); auto funcname = FBehavior::StaticLookupString(args[1]); auto cls = PClass::FindClass(clsname); if (!cls) { I_Error("ACS call to unknown class in script function %s.%s", clsname, funcname); } auto funcsym = dyn_cast(cls->FindSymbol(funcname, true)); if (funcsym == nullptr) { I_Error("ACS call to unknown script function %s.%s", clsname, funcname); } auto func = funcsym->Variants[0].Implementation; if (func->ImplicitArgs > 0) { I_Error("ACS call to non-static script function %s.%s", clsname, funcname); } // Note that this array may not be reallocated so its initial size must be the maximum possible elements. TArray strings(argc); TArray params; int p = 1; if (func->Proto->ArgumentTypes.Size() > 0 && func->Proto->ArgumentTypes[0] == NewPointer(RUNTIME_CLASS(AActor))) { params.Push(activator); p = 0; } for (unsigned i = 2; i < argc; i++) { if (func->Proto->ArgumentTypes.Size() < i - p) { I_Error("Too many parameters in call to %s.%s", clsname, funcname); } auto argtype = func->Proto->ArgumentTypes[i - p - 1]; // The only types allowed are int, bool, double, Name, Sound, Color and String if (argtype == TypeSInt32 || argtype == TypeColor) { params.Push(args[i]); } else if (argtype == TypeBool) { params.Push(!!args[i]); } else if (argtype == TypeFloat64) { params.Push(ACSToDouble(args[i])); } else if (argtype == TypeName) { params.Push(FName(FBehavior::StaticLookupString(args[i])).GetIndex()); } else if (argtype == TypeString) { strings.Push(FBehavior::StaticLookupString(args[i])); params.Push(&strings.Last()); } else if (argtype == TypeSound) { params.Push(int(FSoundID(FBehavior::StaticLookupString(args[i])))); } else { I_Error("Invalid type %s in call to %s.%s", argtype->DescriptiveName(), clsname, funcname); } } if (func->Proto->ArgumentTypes.Size() > params.Size()) { // Check if we got enough parameters. That means we either cover the full argument list of the function or the next argument is optional. if (!(funcsym->Variants[0].ArgFlags[params.Size()] & VARF_Optional)) { I_Error("Insufficient parameters in call to %s.%s", clsname, funcname); } } // The return value can be the same types as the parameter types, plus void if (func->Proto->ReturnTypes.Size() == 0) { VMCallWithDefaults(func, params, nullptr, 0); } else { auto rettype = func->Proto->ReturnTypes[0]; if (rettype == TypeSInt32 || rettype == TypeBool || rettype == TypeColor || rettype == TypeName || rettype == TypeSound) { VMReturn ret(&retval); VMCall(func, ¶ms[0], params.Size(), &ret, 1); if (rettype == TypeName) { retval = GlobalACSStrings.AddString(FName(ENamedName(retval))); } else if (rettype == TypeSound) { retval = GlobalACSStrings.AddString(FSoundID(retval)); } } else if (rettype == TypeFloat64) { double d; VMReturn ret(&d); VMCallWithDefaults(func, params, &ret, 1); retval = DoubleToACS(d); } else if (rettype == TypeString) { FString d; VMReturn ret(&d); VMCallWithDefaults(func, params, &ret, 1); retval = GlobalACSStrings.AddString(d); } else { // All other return values can not be handled so ignore them. VMCallWithDefaults(func, params, nullptr, 0); } } } return retval; } int DLevelScript::CallFunction(int argCount, int funcIndex, int32_t *args) { AActor *actor; switch(funcIndex) { case ACSF_GetLineUDMFInt: return GetUDMFInt(UDMF_Line, LineFromID(args[0]), FBehavior::StaticLookupString(args[1])); case ACSF_GetLineUDMFFixed: return DoubleToACS(GetUDMFFloat(UDMF_Line, LineFromID(args[0]), FBehavior::StaticLookupString(args[1]))); case ACSF_GetThingUDMFInt: case ACSF_GetThingUDMFFixed: return 0; // Not implemented yet case ACSF_GetSectorUDMFInt: return GetUDMFInt(UDMF_Sector, P_FindFirstSectorFromTag(args[0]), FBehavior::StaticLookupString(args[1])); case ACSF_GetSectorUDMFFixed: return DoubleToACS(GetUDMFFloat(UDMF_Sector, P_FindFirstSectorFromTag(args[0]), FBehavior::StaticLookupString(args[1]))); case ACSF_GetSideUDMFInt: return GetUDMFInt(UDMF_Side, SideFromID(args[0], args[1]), FBehavior::StaticLookupString(args[2])); case ACSF_GetSideUDMFFixed: return DoubleToACS(GetUDMFFloat(UDMF_Side, SideFromID(args[0], args[1]), FBehavior::StaticLookupString(args[2]))); case ACSF_GetActorVelX: actor = SingleActorFromTID(args[0], activator); return actor != NULL? DoubleToACS(actor->Vel.X) : 0; case ACSF_GetActorVelY: actor = SingleActorFromTID(args[0], activator); return actor != NULL? DoubleToACS(actor->Vel.Y) : 0; case ACSF_GetActorVelZ: actor = SingleActorFromTID(args[0], activator); return actor != NULL? DoubleToACS(actor->Vel.Z) : 0; case ACSF_SetPointer: if (activator) { AActor *ptr = SingleActorFromTID(args[1], activator); if (argCount > 2) { ptr = COPY_AAPTR(ptr, args[2]); } if (ptr == activator) ptr = NULL; ASSIGN_AAPTR(activator, args[0], ptr, (argCount > 3) ? args[3] : 0); return ptr != NULL; } return 0; case ACSF_SetActivator: if (argCount > 1 && args[1] != AAPTR_DEFAULT) // condition (x != AAPTR_DEFAULT) is essentially condition (x). { activator = COPY_AAPTR(SingleActorFromTID(args[0], activator), args[1]); } else { activator = SingleActorFromTID(args[0], NULL); } return activator != NULL; case ACSF_SetActivatorToTarget: // [KS] I revised this a little bit actor = SingleActorFromTID(args[0], activator); if (actor != NULL) { if (actor->player != NULL && actor->player->playerstate == PST_LIVE) { FTranslatedLineTarget t; P_BulletSlope(actor, &t, ALF_PORTALRESTRICT); actor = t.linetarget; } else { actor = actor->target; } if (actor != NULL) // [FDARI] moved this (actor != NULL)-branch inside the other, so that it is only tried when it can be true { activator = actor; return 1; } } return 0; case ACSF_GetActorViewHeight: actor = SingleActorFromTID(args[0], activator); if (actor != NULL) { if (actor->player != NULL) { return DoubleToACS(actor->player->DefaultViewHeight() + actor->player->crouchviewdelta); } else { return DoubleToACS(actor->GetCameraHeight()); } } else return 0; case ACSF_GetChar: { const char *p = FBehavior::StaticLookupString(args[0]); if (p != NULL && args[1] >= 0 && args[1] < int(strlen(p))) { return p[args[1]]; } else { return 0; } } case ACSF_GetAirSupply: { if (args[0] < 0 || args[0] >= MAXPLAYERS || !playeringame[args[0]]) { return 0; } else { return players[args[0]].air_finished - level.time; } } case ACSF_SetAirSupply: { if (args[0] < 0 || args[0] >= MAXPLAYERS || !playeringame[args[0]]) { return 0; } else { players[args[0]].air_finished = args[1] + level.time; return 1; } } case ACSF_SetSkyScrollSpeed: { if (args[0] == 1) level.skyspeed1 = ACSToFloat(args[1]); else if (args[0] == 2) level.skyspeed2 = ACSToFloat(args[1]); return 1; } case ACSF_GetArmorType: { if (args[1] < 0 || args[1] >= MAXPLAYERS || !playeringame[args[1]]) { return 0; } else { FName p(FBehavior::StaticLookupString(args[0])); auto armor = players[args[1]].mo->FindInventory(NAME_BasicArmor); if (armor && armor->NameVar(NAME_ArmorType) == p) return armor->IntVar(NAME_Amount); } return 0; } case ACSF_GetArmorInfo: { if (activator == NULL || activator->player == NULL) return 0; auto equippedarmor = activator->FindInventory(NAME_BasicArmor); if (equippedarmor && equippedarmor->IntVar(NAME_Amount) != 0) { switch(args[0]) { case ARMORINFO_CLASSNAME: return GlobalACSStrings.AddString(equippedarmor->NameVar(NAME_ArmorType).GetChars()); case ARMORINFO_SAVEAMOUNT: return equippedarmor->IntVar(NAME_MaxAmount); case ARMORINFO_SAVEPERCENT: return DoubleToACS(equippedarmor->FloatVar(NAME_SavePercent)); case ARMORINFO_MAXABSORB: return equippedarmor->IntVar(NAME_MaxAbsorb); case ARMORINFO_MAXFULLABSORB: return equippedarmor->IntVar(NAME_MaxFullAbsorb); case ARMORINFO_ACTUALSAVEAMOUNT: return equippedarmor->IntVar(NAME_ActualSaveAmount); default: return 0; } } return args[0] == ARMORINFO_CLASSNAME ? GlobalACSStrings.AddString("None") : 0; } case ACSF_SpawnSpotForced: return DoSpawnSpot(args[0], args[1], args[2], args[3], true); case ACSF_SpawnSpotFacingForced: return DoSpawnSpotFacing(args[0], args[1], args[2], true); case ACSF_CheckActorProperty: return (CheckActorProperty(args[0], args[1], args[2])); case ACSF_SetActorVelocity: { DVector3 vel(ACSToDouble(args[1]), ACSToDouble(args[2]), ACSToDouble(args[3])); if (args[0] == 0) { P_Thing_SetVelocity(activator, vel, !!args[4], !!args[5]); } else { TActorIterator iterator(args[0]); while ((actor = iterator.Next())) { P_Thing_SetVelocity(actor, vel, !!args[4], !!args[5]); } } return 0; } case ACSF_SetUserVariable: { int cnt = 0; FName varname(FBehavior::StaticLookupString(args[1]), true); if (varname != NAME_None) { if (args[0] == 0) { if (activator != NULL) { SetUserVariable(activator, varname, 0, args[2]); } cnt++; } else { TActorIterator iterator(args[0]); while ( (actor = iterator.Next()) ) { SetUserVariable(actor, varname, 0, args[2]); cnt++; } } } return cnt; } case ACSF_GetUserVariable: { FName varname(FBehavior::StaticLookupString(args[1]), true); if (varname != NAME_None) { AActor *a = SingleActorFromTID(args[0], activator); return a != NULL ? GetUserVariable(a, varname, 0) : 0; } return 0; } case ACSF_SetUserArray: { int cnt = 0; FName varname(FBehavior::StaticLookupString(args[1]), true); if (varname != NAME_None) { if (args[0] == 0) { if (activator != NULL) { SetUserVariable(activator, varname, args[2], args[3]); } cnt++; } else { TActorIterator iterator(args[0]); while ( (actor = iterator.Next()) ) { SetUserVariable(actor, varname, args[2], args[3]); cnt++; } } } return cnt; } case ACSF_GetUserArray: { FName varname(FBehavior::StaticLookupString(args[1]), true); if (varname != NAME_None) { AActor *a = SingleActorFromTID(args[0], activator); return a != NULL ? GetUserVariable(a, varname, args[2]) : 0; } return 0; } case ACSF_Radius_Quake2: P_StartQuake(activator, args[0], args[1], args[2], args[3], args[4], FBehavior::StaticLookupString(args[5])); break; case ACSF_CheckActorClass: { AActor *a = SingleActorFromTID(args[0], activator); return a == NULL ? false : a->GetClass()->TypeName == FName(FBehavior::StaticLookupString(args[1])); } case ACSF_GetActorClass: { AActor *a = SingleActorFromTID(args[0], activator); return GlobalACSStrings.AddString(a == NULL ? "None" : a->GetClass()->TypeName.GetChars()); } case ACSF_SoundSequenceOnActor: { const char *seqname = FBehavior::StaticLookupString(args[1]); if (seqname != NULL) { if (args[0] == 0) { if (activator != NULL) { SN_StartSequence(activator, seqname, 0); } } else { FActorIterator it(args[0]); AActor *actor; while ( (actor = it.Next()) ) { SN_StartSequence(actor, seqname, 0); } } } } break; case ACSF_SoundSequenceOnSector: { const char *seqname = FBehavior::StaticLookupString(args[1]); int space = args[2] < CHAN_FLOOR || args[2] > CHAN_INTERIOR ? CHAN_FULLHEIGHT : args[2]; if (seqname != NULL) { FSectorTagIterator it(args[0]); int s; while ((s = it.Next()) >= 0) { SN_StartSequence(&level.sectors[s], args[2], seqname, 0); } } } break; case ACSF_SoundSequenceOnPolyobj: { const char *seqname = FBehavior::StaticLookupString(args[1]); if (seqname != NULL) { FPolyObj *poly = PO_GetPolyobj(args[0]); if (poly != NULL) { SN_StartSequence(poly, seqname, 0); } } } break; case ACSF_GetPolyobjX: { FPolyObj *poly = PO_GetPolyobj(args[0]); if (poly != NULL) { return DoubleToACS(poly->StartSpot.pos.X); } } return FIXED_MAX; case ACSF_GetPolyobjY: { FPolyObj *poly = PO_GetPolyobj(args[0]); if (poly != NULL) { return DoubleToACS(poly->StartSpot.pos.Y); } } return FIXED_MAX; case ACSF_CheckSight: { AActor *source; AActor *dest; int flags = SF_IGNOREVISIBILITY; if (args[2] & 1) flags |= SF_IGNOREWATERBOUNDARY; if (args[2] & 2) flags |= SF_SEEPASTBLOCKEVERYTHING | SF_SEEPASTSHOOTABLELINES; if (args[0] == 0) { source = (AActor *) activator; if (args[1] == 0) return 1; // [KS] I'm sure the activator can see itself. TActorIterator dstiter (args[1]); while ( (dest = dstiter.Next ()) ) { if (P_CheckSight(source, dest, flags)) return 1; } } else { TActorIterator srciter (args[0]); while ( (source = srciter.Next ()) ) { if (args[1] != 0) { TActorIterator dstiter (args[1]); while ( (dest = dstiter.Next ()) ) { if (P_CheckSight(source, dest, flags)) return 1; } } else { if (P_CheckSight(source, activator, flags)) return 1; } } } return 0; } case ACSF_SpawnForced: return DoSpawn(args[0], args[1], args[2], args[3], args[4], args[5], true); case ACSF_ACS_NamedExecute: case ACSF_ACS_NamedSuspend: case ACSF_ACS_NamedTerminate: case ACSF_ACS_NamedLockedExecute: case ACSF_ACS_NamedLockedExecuteDoor: case ACSF_ACS_NamedExecuteWithResult: case ACSF_ACS_NamedExecuteAlways: { int scriptnum = -FName(FBehavior::StaticLookupString(args[0])); int arg1 = argCount > 1 ? args[1] : 0; int arg2 = argCount > 2 ? args[2] : 0; int arg3 = argCount > 3 ? args[3] : 0; int arg4 = argCount > 4 ? args[4] : 0; return P_ExecuteSpecial(NamedACSToNormalACS[funcIndex - ACSF_ACS_NamedExecute], activationline, activator, backSide, scriptnum, arg1, arg2, arg3, arg4); } break; case ACSF_UniqueTID: return P_FindUniqueTID(argCount > 0 ? args[0] : 0, (argCount > 1 && args[1] >= 0) ? args[1] : 0); case ACSF_IsTIDUsed: return P_IsTIDUsed(args[0]); case ACSF_Sqrt: return xs_FloorToInt(g_sqrt(double(args[0]))); case ACSF_FixedSqrt: return DoubleToACS(g_sqrt(ACSToDouble(args[0]))); case ACSF_VectorLength: return DoubleToACS(DVector2(ACSToDouble(args[0]), ACSToDouble(args[1])).Length()); case ACSF_SetHUDClipRect: ClipRectLeft = argCount > 0 ? args[0] : 0; ClipRectTop = argCount > 1 ? args[1] : 0; ClipRectWidth = argCount > 2 ? args[2] : 0; ClipRectHeight = argCount > 3 ? args[3] : 0; WrapWidth = argCount > 4 ? args[4] : 0; HandleAspect = argCount > 5 ? !!args[5] : true; break; case ACSF_SetHUDWrapWidth: WrapWidth = argCount > 0 ? args[0] : 0; break; case ACSF_GetCVarString: if (argCount == 1) { return DoGetCVar(GetCVar(activator && activator->player ? int(activator->player - players) : -1, FBehavior::StaticLookupString(args[0])), true); } break; case ACSF_SetCVar: if (argCount == 2) { return SetCVar(activator, FBehavior::StaticLookupString(args[0]), args[1], false); } break; case ACSF_SetCVarString: if (argCount == 2) { return SetCVar(activator, FBehavior::StaticLookupString(args[0]), args[1], true); } break; case ACSF_GetUserCVar: if (argCount == 2) { return DoGetCVar(GetUserCVar(args[0], FBehavior::StaticLookupString(args[1])), false); } break; case ACSF_GetUserCVarString: if (argCount == 2) { return DoGetCVar(GetUserCVar(args[0], FBehavior::StaticLookupString(args[1])), true); } break; case ACSF_SetUserCVar: if (argCount == 3) { return SetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), args[2], false); } break; case ACSF_SetUserCVarString: if (argCount == 3) { return SetUserCVar(args[0], FBehavior::StaticLookupString(args[1]), args[2], true); } break; //[RC] A bullet firing function for ACS. Thanks to DavidPH. case ACSF_LineAttack: { DAngle angle = ACSToAngle(args[1]); DAngle pitch = ACSToAngle(args[2]); int damage = args[3]; FName pufftype = argCount > 4 && args[4]? FName(FBehavior::StaticLookupString(args[4])) : NAME_BulletPuff; FName damagetype = argCount > 5 && args[5]? FName(FBehavior::StaticLookupString(args[5])) : NAME_None; double range = argCount > 6 && args[6]? ACSToDouble(args[6]) : MISSILERANGE; int flags = argCount > 7 && args[7]? args[7] : 0; int pufftid = argCount > 8 && args[8]? args[8] : 0; int fhflags = 0; if (flags & FHF_NORANDOMPUFFZ) fhflags |= LAF_NORANDOMPUFFZ; if (flags & FHF_NOIMPACTDECAL) fhflags |= LAF_NOIMPACTDECAL; if (args[0] == 0) { AActor *puff = P_LineAttack(activator, angle, range, pitch, damage, damagetype, pufftype, fhflags); if (puff != NULL && pufftid != 0) { puff->SetTID(pufftid); } } else { AActor *source; FActorIterator it(args[0]); while ((source = it.Next()) != NULL) { AActor *puff = P_LineAttack(source, angle, range, pitch, damage, damagetype, pufftype, fhflags); if (puff != NULL && pufftid != 0) { puff->SetTID(pufftid); } } } } break; case ACSF_PlaySound: case ACSF_PlayActorSound: // PlaySound(tid, "SoundName", channel, volume, looping, attenuation, local) { FSoundID sid; if (funcIndex == ACSF_PlaySound) { const char *lookup = FBehavior::StaticLookupString(args[1]); if (lookup != NULL) { sid = lookup; } } if (sid != 0 || funcIndex == ACSF_PlayActorSound) { FActorIterator it(args[0]); AActor *spot; int chan = argCount > 2 ? args[2] : CHAN_BODY; float vol = argCount > 3 ? ACSToFloat(args[3]) : 1.f; INTBOOL looping = argCount > 4 ? args[4] : false; float atten = argCount > 5 ? ACSToFloat(args[5]) : ATTN_NORM; INTBOOL local = argCount > 6 ? args[6] : false; if (args[0] == 0) { spot = activator; goto doplaysound; } while ((spot = it.Next()) != NULL) { doplaysound: if (funcIndex == ACSF_PlayActorSound) { sid = GetActorSound(spot, args[1]); } if (sid != 0) { if (!looping) { S_PlaySound(spot, chan, sid, vol, atten, !!local); } else if (!S_IsActorPlayingSomething(spot, chan & 7, sid)) { S_PlaySound(spot, chan | CHAN_LOOP, sid, vol, atten, !!local); } } } } } break; case ACSF_StopSound: { int chan = argCount > 1 ? args[1] : CHAN_BODY; if (args[0] == 0) { S_StopSound(activator, chan); } else { FActorIterator it(args[0]); AActor *spot; while ((spot = it.Next()) != NULL) { S_StopSound(spot, chan); } } } break; case ACSF_SoundVolume: // SoundVolume(int tid, int channel, fixed volume) { int chan = args[1]; double volume = ACSToDouble(args[2]); if (args[0] == 0) { S_ChangeSoundVolume(activator, chan, volume); } else { FActorIterator it(args[0]); AActor *spot; while ((spot = it.Next()) != NULL) { S_ChangeSoundVolume(spot, chan, volume); } } } break; case ACSF_strcmp: case ACSF_stricmp: if (argCount >= 2) { const char *a, *b; // If the string indicies are the same, then they are the same string. if (args[0] == args[1]) { return 0; } a = FBehavior::StaticLookupString(args[0]); b = FBehavior::StaticLookupString(args[1]); // Don't crash on invalid strings. if (a == NULL) a = ""; if (b == NULL) b = ""; if (argCount > 2) { int n = args[2]; return (funcIndex == ACSF_strcmp) ? strncmp(a, b, n) : strnicmp(a, b, n); } else { return (funcIndex == ACSF_strcmp) ? strcmp(a, b) : stricmp(a, b); } } break; case ACSF_StrLeft: case ACSF_StrRight: if (argCount >= 2) { const char *oldstr = FBehavior::StaticLookupString(args[0]); if (oldstr == NULL || *oldstr == '\0') { return GlobalACSStrings.AddString(""); } size_t oldlen = strlen(oldstr); size_t newlen = args[1]; if (oldlen < newlen) { newlen = oldlen; } FString newstr(funcIndex == ACSF_StrLeft ? oldstr : oldstr + oldlen - newlen, newlen); return GlobalACSStrings.AddString(newstr); } break; case ACSF_StrMid: if (argCount >= 3) { const char *oldstr = FBehavior::StaticLookupString(args[0]); if (oldstr == NULL || *oldstr == '\0') { return GlobalACSStrings.AddString(""); } size_t oldlen = strlen(oldstr); size_t pos = args[1]; size_t newlen = args[2]; if (pos >= oldlen) { return GlobalACSStrings.AddString(""); } if (pos + newlen > oldlen || pos + newlen < pos) { newlen = oldlen - pos; } return GlobalACSStrings.AddString(FString(oldstr + pos, newlen)); } break; case ACSF_GetWeapon: if (activator == NULL || activator->player == NULL || // Non-players do not have weapons activator->player->ReadyWeapon == NULL) { return GlobalACSStrings.AddString("None"); } else { return GlobalACSStrings.AddString(activator->player->ReadyWeapon->GetClass()->TypeName.GetChars()); } case ACSF_SpawnDecal: // int SpawnDecal(int tid, str decalname, int flags, fixed angle, int zoffset, int distance) // Returns number of decals spawned (not including spreading) { int count = 0; const FDecalTemplate *tpl = DecalLibrary.GetDecalByName(FBehavior::StaticLookupString(args[1])); if (tpl != NULL) { int flags = (argCount > 2) ? args[2] : 0; DAngle angle = ACSToAngle((argCount > 3) ? args[3] : 0); int zoffset = (argCount > 4) ? args[4]: 0; int distance = (argCount > 5) ? args[5] : 64; if (args[0] == 0) { if (activator != NULL) { count += DoSpawnDecal(activator, tpl, flags, angle, zoffset, distance); } } else { FActorIterator it(args[0]); AActor *actor; while ((actor = it.Next()) != NULL) { count += DoSpawnDecal(actor, tpl, flags, angle, zoffset, distance); } } } return count; } break; case ACSF_CheckFont: // bool CheckFont(str fontname) return V_GetFont(FBehavior::StaticLookupString(args[0])) != NULL; case ACSF_DropItem: { const char *type = FBehavior::StaticLookupString(args[1]); int amount = argCount >= 3? args[2] : -1; int chance = argCount >= 4? args[3] : 256; PClassActor *cls = PClass::FindActor(type); int cnt = 0; if (cls != NULL) { if (args[0] == 0) { if (activator != NULL) { P_DropItem(activator, cls, amount, chance); cnt++; } } else { FActorIterator it(args[0]); AActor *actor; while ((actor = it.Next()) != NULL) { P_DropItem(actor, cls, amount, chance); cnt++; } } return cnt; } break; } case ACSF_DropInventory: { const char *type = FBehavior::StaticLookupString(args[1]); AActor *inv; if (type != NULL) { if (args[0] == 0) { if (activator != NULL) { inv = activator->FindInventory(type); if (inv) { activator->DropInventory(inv); } } } else { FActorIterator it(args[0]); AActor *actor; while ((actor = it.Next()) != NULL) { inv = actor->FindInventory(type); if (inv) { actor->DropInventory(inv); } } } } break; } case ACSF_CheckFlag: { AActor *actor = SingleActorFromTID(args[0], activator); if (actor != NULL) { return !!CheckActorFlag(actor, FBehavior::StaticLookupString(args[1])); } break; } case ACSF_QuakeEx: { return P_StartQuakeXYZ(activator, args[0], args[1], args[2], args[3], args[4], args[5], args[6], FBehavior::StaticLookupString(args[7]), argCount > 8 ? args[8] : 0, argCount > 9 ? ACSToDouble(args[9]) : 1.0, argCount > 10 ? ACSToDouble(args[10]) : 1.0, argCount > 11 ? ACSToDouble(args[11]) : 1.0, argCount > 12 ? args[12] : 0, argCount > 13 ? args[13] : 0, argCount > 14 ? ACSToDouble(args[14]) : 0, argCount > 15 ? ACSToDouble(args[15]) : 0); } case ACSF_SetLineActivation: if (argCount >= 2) { int line; FLineIdIterator itr(args[0]); while ((line = itr.Next()) >= 0) { level.lines[line].activation = args[1]; } } break; case ACSF_GetLineActivation: if (argCount > 0) { int line = P_FindFirstLineFromID(args[0]); return line >= 0 ? level.lines[line].activation : 0; } break; case ACSF_GetActorPowerupTics: if (argCount >= 2) { PClassActor *powerupclass = PClass::FindActor(FBehavior::StaticLookupString(args[1])); if (powerupclass == NULL || !powerupclass->IsDescendantOf(NAME_Powerup)) { Printf("'%s' is not a type of Powerup.\n", FBehavior::StaticLookupString(args[1])); return 0; } AActor *actor = SingleActorFromTID(args[0], activator); if (actor != NULL) { auto powerup = actor->FindInventory(powerupclass); if (powerup != NULL) return powerup->IntVar(NAME_EffectTics); } return 0; } break; case ACSF_ChangeActorAngle: if (argCount >= 2) { SetActorAngle(activator, args[0], args[1], argCount > 2 ? !!args[2] : false); } break; case ACSF_ChangeActorPitch: if (argCount >= 2) { SetActorPitch(activator, args[0], args[1], argCount > 2 ? !!args[2] : false); } break; case ACSF_SetActorTeleFog: if (argCount >= 3) { SetActorTeleFog(activator, args[0], FBehavior::StaticLookupString(args[1]), FBehavior::StaticLookupString(args[2])); } break; case ACSF_SwapActorTeleFog: if (argCount >= 1) { return SwapActorTeleFog(activator, args[0]); } break; case ACSF_PickActor: if (argCount >= 5) { actor = SingleActorFromTID(args[0], activator); if (actor == NULL) { return 0; } ActorFlags actorMask = MF_SHOOTABLE; if (argCount >= 6) { actorMask = ActorFlags::FromInt(args[5]); } uint32_t wallMask = ML_BLOCKEVERYTHING | ML_BLOCKHITSCAN; if (argCount >= 7) { wallMask = args[6]; } int flags = 0; if (argCount >= 8) { flags = args[7]; } AActor* pickedActor = P_LinePickActor(actor, ACSToAngle(args[1]), ACSToDouble(args[3]), ACSToAngle(args[2]), actorMask, wallMask); if (pickedActor == NULL) { return 0; } if (!(flags & PICKAF_FORCETID) && (args[4] == 0) && (pickedActor->tid == 0)) return 0; if ((pickedActor->tid == 0) || (flags & PICKAF_FORCETID)) { pickedActor->SetTID(args[4]); } if (flags & PICKAF_RETURNTID) { return pickedActor->tid; } return 1; } break; case ACSF_IsPointerEqual: { int tid1 = 0, tid2 = 0; switch (argCount) { case 4: tid2 = args[3]; case 3: tid1 = args[2]; } actor = SingleActorFromTID(tid1, activator); AActor * actor2 = tid2 == tid1 ? actor : SingleActorFromTID(tid2, activator); return COPY_AAPTR(actor, args[0]) == COPY_AAPTR(actor2, args[1]); } break; case ACSF_CanRaiseActor: if (argCount >= 1) { if (args[0] == 0) { actor = SingleActorFromTID(args[0], activator); if (actor != NULL) { return P_Thing_CanRaise(actor); } } FActorIterator iterator(args[0]); bool canraiseall = true; while ((actor = iterator.Next())) { canraiseall = P_Thing_CanRaise(actor) & canraiseall; } return canraiseall; } break; // [Nash] Actor roll functions. Let's roll! case ACSF_SetActorRoll: SetActorRoll(activator, args[0], args[1], false); return 0; case ACSF_ChangeActorRoll: if (argCount >= 2) { SetActorRoll(activator, args[0], args[1], argCount > 2 ? !!args[2] : false); } break; case ACSF_GetActorRoll: actor = SingleActorFromTID(args[0], activator); return actor != NULL? AngleToACS(actor->Angles.Roll) : 0; // [ZK] A_Warp in ACS case ACSF_Warp: { if (nullptr == activator) { return false; } const int dest = args[0]; const int flags = args[5]; AActor* const reference = ((flags & WARPF_USEPTR) && (AAPTR_DEFAULT != dest)) ? COPY_AAPTR(activator, dest) : SingleActorFromTID(dest, activator); if (nullptr == reference) { // there is no actor to warp to return false; } const double xofs = ACSToDouble(args[1]); const double yofs = ACSToDouble(args[2]); const double zofs = ACSToDouble(args[3]); const DAngle angle = ACSToAngle(args[4]); const double heightoffset = argCount > 8 ? ACSToDouble(args[8]) : 0.0; const double radiusoffset = argCount > 9 ? ACSToDouble(args[9]) : 0.0; const DAngle pitch = ACSToAngle(argCount > 10 ? args[10] : 0); if (!P_Thing_Warp(activator, reference, xofs, yofs, zofs, angle, flags, heightoffset, radiusoffset, pitch)) { return false; } if (argCount > 6) { const char* const statename = FBehavior::StaticLookupString(args[6]); if (nullptr != statename) { const bool exact = argCount > 7 && !!args[7]; FState* const state = activator->GetClass()->FindStateByString(statename, exact); if (nullptr != state) { activator->SetState(state); } } } return true; } case ACSF_GetMaxInventory: actor = SingleActorFromTID(args[0], activator); if (actor != NULL) { return CheckInventory(actor, FBehavior::StaticLookupString(args[1]), true); } break; case ACSF_SetSectorDamage: if (argCount >= 2) { FSectorTagIterator it(args[0]); int s; while ((s = it.Next()) >= 0) { sector_t *sec = &level.sectors[s]; sec->damageamount = args[1]; sec->damagetype = argCount >= 3 ? FName(FBehavior::StaticLookupString(args[2])) : FName(NAME_None); sec->damageinterval = argCount >= 4 ? clamp(args[3], 1, INT_MAX) : 32; sec->leakydamage = argCount >= 5 ? args[4] : 0; } } break; case ACSF_SetSectorTerrain: if (argCount >= 3) { if (args[1] == sector_t::floor || args[1] == sector_t::ceiling) { int terrain = P_FindTerrain(FBehavior::StaticLookupString(args[2])); FSectorTagIterator it(args[0]); int s; while ((s = it.Next()) >= 0) { level.sectors[s].terrainnum[args[1]] = terrain; } } } break; case ACSF_SpawnParticle: { PalEntry color = args[0]; bool fullbright = argCount > 1 ? !!args[1] : false; int lifetime = argCount > 2 ? args[2] : 35; double size = argCount > 3 ? args[3] : 1.; int x = argCount > 4 ? args[4] : 0; int y = argCount > 5 ? args[5] : 0; int z = argCount > 6 ? args[6] : 0; int xvel = argCount > 7 ? args[7] : 0; int yvel = argCount > 8 ? args[8] : 0; int zvel = argCount > 9 ? args[9] : 0; int accelx = argCount > 10 ? args[10] : 0; int accely = argCount > 11 ? args[11] : 0; int accelz = argCount > 12 ? args[12] : 0; int startalpha = argCount > 13 ? args[13] : 0xFF; // Byte trans int fadestep = argCount > 14 ? args[14] : -1; double endsize = argCount > 15 ? args[15] : -1.; startalpha = clamp(startalpha, 0, 255); // Clamp to byte lifetime = clamp(lifetime, 0, 255); // Clamp to byte fadestep = clamp(fadestep, -1, 255); // Clamp to byte inc. -1 (indicating automatic) size = fabs(size); if (lifetime != 0) P_SpawnParticle(DVector3(ACSToDouble(x), ACSToDouble(y), ACSToDouble(z)), DVector3(ACSToDouble(xvel), ACSToDouble(yvel), ACSToDouble(zvel)), DVector3(ACSToDouble(accelx), ACSToDouble(accely), ACSToDouble(accelz)), color, startalpha/255., lifetime, size, endsize, fadestep/255., fullbright); } break; case ACSF_SetMusicVolume: level.SetMusicVolume(ACSToFloat(args[0])); break; case ACSF_CheckProximity: { // [zombie] ACS version of A_CheckProximity actor = SingleActorFromTID(args[0], activator); PClass *classname = PClass::FindClass(FBehavior::StaticLookupString(args[1])); double distance = ACSToDouble(args[2]); int count = argCount >= 4 ? args[3] : 1; int flags = argCount >= 5 ? args[4] : 0; int ptr = argCount >= 6 ? args[5] : AAPTR_DEFAULT; return P_Thing_CheckProximity(actor, classname, distance, count, flags, ptr); } case ACSF_CheckActorState: { actor = SingleActorFromTID(args[0], activator); const char *statename = FBehavior::StaticLookupString(args[1]); bool exact = (argCount > 2) ? !!args[2] : false; if (actor && statename) { return (actor->GetClass()->FindStateByString(statename, exact) != nullptr); } return false; } case ACSF_CheckClass: { const char *clsname = FBehavior::StaticLookupString(args[0]); return !!PClass::FindActor(clsname); } case ACSF_DamageActor: // [arookas] wrapper around P_DamageMobj { // (target, ptr_select1, inflictor, ptr_select2, amount, damagetype) AActor* target = COPY_AAPTR(SingleActorFromTID(args[0], activator), args[1]); AActor* inflictor = COPY_AAPTR(SingleActorFromTID(args[2], activator), args[3]); FName damagetype(FBehavior::StaticLookupString(args[5])); return P_DamageMobj(target, inflictor, inflictor, args[4], damagetype); } case ACSF_SetActorFlag: { int tid = args[0]; FString flagname = FBehavior::StaticLookupString(args[1]); bool flagvalue = !!args[2]; int count = 0; // Return value; number of actors affected if (tid == 0) { if (ModActorFlag(activator, flagname, flagvalue)) { ++count; } } else { FActorIterator it(tid); while ((actor = it.Next()) != nullptr) { // Don't log errors when affecting many actors because things might share a TID but not share the flag if (ModActorFlag(actor, flagname, flagvalue, false)) { ++count; } } } return count; } case ACSF_SetTranslation: { int tid = args[0]; const char *trname = FBehavior::StaticLookupString(args[1]); if (tid == 0) { if (activator != nullptr) activator->SetTranslation(trname); } else { FActorIterator it(tid); while ((actor = it.Next()) != nullptr) { actor->SetTranslation(trname); } } return 1; } // OpenGL exclusive functions case ACSF_SetSectorGlow: { int which = !!args[1]; PalEntry color(args[2], args[3], args[4]); float height = float(args[5]); if (args[2] == -1) color = -1; FSectorTagIterator it(args[0]); int s; while ((s = it.Next()) >= 0) { level.sectors[s].planes[which].GlowColor = color; level.sectors[s].planes[which].GlowHeight = height; } break; } case ACSF_SetFogDensity: { FSectorTagIterator it(args[0]); int s; int d = clamp(args[1]/2, 0, 255); while ((s = it.Next()) >= 0) { level.sectors[s].SetFogDensity(d); } break; } case ACSF_GetActorFloorTexture: { auto a = SingleActorFromTID(args[0], activator); if (a != nullptr) { return GlobalACSStrings.AddString(TexMan[a->floorpic]->Name); } else { return GlobalACSStrings.AddString(""); } break; } case ACSF_GetActorFloorTerrain: { auto a = SingleActorFromTID(args[0], activator); if (a != nullptr) { return GlobalACSStrings.AddString(Terrains[a->floorterrain].Name); } else { return GlobalACSStrings.AddString(""); } break; } case ACSF_StrArg: return -FName(FBehavior::StaticLookupString(args[0])); case ACSF_Floor: return args[0] & ~0xffff; case ACSF_Ceil: return (args[0] & ~0xffff) + 0x10000; case ACSF_Round: return (args[0] + 32768) & ~0xffff; case ACSF_ScriptCall: return ScriptCall(activator, argCount, args); case ACSF_StartSlideshow: G_StartSlideshow(FName(FBehavior::StaticLookupString(args[0]))); break; case ACSF_GetSectorHealth: { int part = args[1]; FSectorTagIterator it(args[0]); int s = it.Next(); if (s < 0) return 0; sector_t* ss = &level.sectors[s]; FHealthGroup* grp; if (part == SECPART_Ceiling) { return (ss->healthceilinggroup && (grp = P_GetHealthGroup(ss->healthceilinggroup))) ? grp->health : ss->healthceiling; } else if (part == SECPART_Floor) { return (ss->healthfloorgroup && (grp = P_GetHealthGroup(ss->healthfloorgroup))) ? grp->health : ss->healthfloor; } else if (part == SECPART_3D) { return (ss->health3dgroup && (grp = P_GetHealthGroup(ss->health3dgroup))) ? grp->health : ss->health3d; } return 0; } case ACSF_GetLineHealth: { FLineIdIterator it(args[0]); int l = it.Next(); if (l < 0) return 0; line_t* ll = &level.lines[l]; if (ll->healthgroup > 0) { FHealthGroup* grp = P_GetHealthGroup(ll->healthgroup); if (grp) return grp->health; } return ll->health; } case ACSF_GetLineX: case ACSF_GetLineY: { FLineIdIterator it(args[0]); int lineno = it.Next(); if (lineno < 0) return 0; DVector2 delta = level.lines[lineno].Delta(); double result = delta[funcIndex - ACSF_GetLineX] * ACSToDouble(args[1]); if (args[2]) { DVector2 normal = DVector2(delta.Y, -delta.X).Unit(); result += normal[funcIndex - ACSF_GetLineX] * ACSToDouble(args[2]); } return DoubleToACS(result); } default: break; } return 0; } enum { PRINTNAME_LEVELNAME = -1, PRINTNAME_LEVEL = -2, PRINTNAME_SKILL = -3, PRINTNAME_NEXTLEVEL = -4, PRINTNAME_NEXTSECRET = -5, }; #define NEXTWORD (LittleLong(*pc++)) #define NEXTBYTE (fmt==ACS_LittleEnhanced?getbyte(pc):NEXTWORD) #define NEXTSHORT (fmt==ACS_LittleEnhanced?getshort(pc):NEXTWORD) #define STACK(a) (Stack[sp - (a)]) #define PushToStack(a) (Stack[sp++] = (a)) // Direct instructions that take strings need to have the tag applied. #define TAGSTR(a) (a|activeBehavior->GetLibraryID()) inline int getbyte (int *&pc) { int res = *(uint8_t *)pc; pc = (int *)((uint8_t *)pc+1); return res; } inline int getshort (int *&pc) { int res = LittleShort( *(int16_t *)pc); pc = (int *)((uint8_t *)pc+2); return res; } static bool CharArrayParms(int &capacity, int &offset, int &a, FACSStackMemory& Stack, int &sp, bool ranged) { if (ranged) { capacity = STACK(1); offset = STACK(2); if (capacity < 1 || offset < 0) { sp -= 4; return false; } sp -= 2; } else { capacity = INT_MAX; offset = 0; } a = STACK(1); offset += STACK(2); sp -= 2; return true; } int DLevelScript::RunScript () { DACSThinker *controller = DACSThinker::ActiveThinker; ACSLocalVariables locals(Localvars); ACSLocalArrays noarrays; ACSLocalArrays *localarrays = &noarrays; ScriptFunction *activeFunction = NULL; FRemapTable *translation = 0; int resultValue = 1; if (InModuleScriptNumber >= 0) { ScriptPtr *ptr = activeBehavior->GetScriptPtr(InModuleScriptNumber); assert(ptr != NULL); if (ptr != NULL) { localarrays = &ptr->LocalArrays; } } // Hexen truncates all special arguments to bytes (only when using an old MAPINFO and old ACS format const int specialargmask = ((level.flags2 & LEVEL2_HEXENHACK) && activeBehavior->GetFormat() == ACS_Old) ? 255 : ~0; switch (state) { case SCRIPT_Delayed: // Decrement the delay counter and enter state running // if it hits 0 if (--statedata == 0) state = SCRIPT_Running; break; case SCRIPT_TagWait: // Wait for tagged sector(s) to go inactive, then enter // state running { int secnum; FSectorTagIterator it(statedata); while ((secnum = it.Next()) >= 0) { if (level.sectors[secnum].floordata || level.sectors[secnum].ceilingdata) return resultValue; } // If we got here, none of the tagged sectors were busy state = SCRIPT_Running; } break; case SCRIPT_PolyWait: // Wait for polyobj(s) to stop moving, then enter state running if (!PO_Busy (statedata)) { state = SCRIPT_Running; } break; case SCRIPT_ScriptWaitPre: // Wait for a script to start running, then enter state scriptwait if (controller->RunningScripts.CheckKey(statedata) != NULL) state = SCRIPT_ScriptWait; break; case SCRIPT_ScriptWait: // Wait for a script to stop running, then enter state running if (controller->RunningScripts.CheckKey(statedata) != NULL) return resultValue; state = SCRIPT_Running; PutFirst (); break; default: break; } FACSStack stackobj; FACSStackMemory& Stack = stackobj.buffer; int &sp = stackobj.sp; int *pc = this->pc; ACSFormat fmt = activeBehavior->GetFormat(); FBehavior* const savedActiveBehavior = activeBehavior; unsigned int runaway = 0; // used to prevent infinite loops int pcd; FString work; const char *lookup; int optstart = -1; int temp; while (state == SCRIPT_Running) { if (++runaway > 2000000) { Printf ("Runaway %s terminated\n", ScriptPresentation(script).GetChars()); state = SCRIPT_PleaseRemove; break; } if (fmt == ACS_LittleEnhanced) { pcd = getbyte(pc); if (pcd >= 256-16) { pcd = (256-16) + ((pcd - (256-16)) << 8) + getbyte(pc); } } else { pcd = NEXTWORD; } switch (pcd) { default: Printf ("Unknown P-Code %d in %s\n", pcd, ScriptPresentation(script).GetChars()); activeBehavior = savedActiveBehavior; // fall through case PCD_TERMINATE: DPrintf (DMSG_NOTIFY, "%s finished\n", ScriptPresentation(script).GetChars()); state = SCRIPT_PleaseRemove; break; case PCD_NOP: break; case PCD_SUSPEND: state = SCRIPT_Suspended; break; case PCD_TAGSTRING: //Stack[sp-1] |= activeBehavior->GetLibraryID(); Stack[sp-1] = GlobalACSStrings.AddString(activeBehavior->LookupString(Stack[sp-1])); break; case PCD_PUSHNUMBER: PushToStack (uallong(pc[0])); pc++; break; case PCD_PUSHBYTE: PushToStack (*(uint8_t *)pc); pc = (int *)((uint8_t *)pc + 1); break; case PCD_PUSH2BYTES: Stack[sp] = ((uint8_t *)pc)[0]; Stack[sp+1] = ((uint8_t *)pc)[1]; sp += 2; pc = (int *)((uint8_t *)pc + 2); break; case PCD_PUSH3BYTES: Stack[sp] = ((uint8_t *)pc)[0]; Stack[sp+1] = ((uint8_t *)pc)[1]; Stack[sp+2] = ((uint8_t *)pc)[2]; sp += 3; pc = (int *)((uint8_t *)pc + 3); break; case PCD_PUSH4BYTES: Stack[sp] = ((uint8_t *)pc)[0]; Stack[sp+1] = ((uint8_t *)pc)[1]; Stack[sp+2] = ((uint8_t *)pc)[2]; Stack[sp+3] = ((uint8_t *)pc)[3]; sp += 4; pc = (int *)((uint8_t *)pc + 4); break; case PCD_PUSH5BYTES: Stack[sp] = ((uint8_t *)pc)[0]; Stack[sp+1] = ((uint8_t *)pc)[1]; Stack[sp+2] = ((uint8_t *)pc)[2]; Stack[sp+3] = ((uint8_t *)pc)[3]; Stack[sp+4] = ((uint8_t *)pc)[4]; sp += 5; pc = (int *)((uint8_t *)pc + 5); break; case PCD_PUSHBYTES: temp = *(uint8_t *)pc; pc = (int *)((uint8_t *)pc + temp + 1); for (temp = -temp; temp; temp++) { PushToStack (*((uint8_t *)pc + temp)); } break; case PCD_DUP: Stack[sp] = Stack[sp-1]; sp++; break; case PCD_SWAP: swapvalues(Stack[sp-2], Stack[sp-1]); break; case PCD_LSPEC1: P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide, STACK(1) & specialargmask, 0, 0, 0, 0); sp -= 1; break; case PCD_LSPEC2: P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide, STACK(2) & specialargmask, STACK(1) & specialargmask, 0, 0, 0); sp -= 2; break; case PCD_LSPEC3: P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide, STACK(3) & specialargmask, STACK(2) & specialargmask, STACK(1) & specialargmask, 0, 0); sp -= 3; break; case PCD_LSPEC4: P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide, STACK(4) & specialargmask, STACK(3) & specialargmask, STACK(2) & specialargmask, STACK(1) & specialargmask, 0); sp -= 4; break; case PCD_LSPEC5: P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide, STACK(5) & specialargmask, STACK(4) & specialargmask, STACK(3) & specialargmask, STACK(2) & specialargmask, STACK(1) & specialargmask); sp -= 5; break; case PCD_LSPEC5RESULT: STACK(5) = P_ExecuteSpecial(NEXTBYTE, activationline, activator, backSide, STACK(5) & specialargmask, STACK(4) & specialargmask, STACK(3) & specialargmask, STACK(2) & specialargmask, STACK(1) & specialargmask); sp -= 4; break; case PCD_LSPEC5EX: P_ExecuteSpecial(NEXTWORD, activationline, activator, backSide, STACK(5) & specialargmask, STACK(4) & specialargmask, STACK(3) & specialargmask, STACK(2) & specialargmask, STACK(1) & specialargmask); sp -= 5; break; case PCD_LSPEC5EXRESULT: STACK(5) = P_ExecuteSpecial(NEXTWORD, activationline, activator, backSide, STACK(5) & specialargmask, STACK(4) & specialargmask, STACK(3) & specialargmask, STACK(2) & specialargmask, STACK(1) & specialargmask); sp -= 4; break; case PCD_LSPEC1DIRECT: temp = NEXTBYTE; P_ExecuteSpecial(temp, activationline, activator, backSide, uallong(pc[0]) & specialargmask ,0, 0, 0, 0); pc += 1; break; case PCD_LSPEC2DIRECT: temp = NEXTBYTE; P_ExecuteSpecial(temp, activationline, activator, backSide, uallong(pc[0]) & specialargmask, uallong(pc[1]) & specialargmask, 0, 0, 0); pc += 2; break; case PCD_LSPEC3DIRECT: temp = NEXTBYTE; P_ExecuteSpecial(temp, activationline, activator, backSide, uallong(pc[0]) & specialargmask, uallong(pc[1]) & specialargmask, uallong(pc[2]) & specialargmask, 0, 0); pc += 3; break; case PCD_LSPEC4DIRECT: temp = NEXTBYTE; P_ExecuteSpecial(temp, activationline, activator, backSide, uallong(pc[0]) & specialargmask, uallong(pc[1]) & specialargmask, uallong(pc[2]) & specialargmask, uallong(pc[3]) & specialargmask, 0); pc += 4; break; case PCD_LSPEC5DIRECT: temp = NEXTBYTE; P_ExecuteSpecial(temp, activationline, activator, backSide, uallong(pc[0]) & specialargmask, uallong(pc[1]) & specialargmask, uallong(pc[2]) & specialargmask, uallong(pc[3]) & specialargmask, uallong(pc[4]) & specialargmask); pc += 5; break; // Parameters for PCD_LSPEC?DIRECTB are by definition bytes so never need and-ing. case PCD_LSPEC1DIRECTB: P_ExecuteSpecial(((uint8_t *)pc)[0], activationline, activator, backSide, ((uint8_t *)pc)[1], 0, 0, 0, 0); pc = (int *)((uint8_t *)pc + 2); break; case PCD_LSPEC2DIRECTB: P_ExecuteSpecial(((uint8_t *)pc)[0], activationline, activator, backSide, ((uint8_t *)pc)[1], ((uint8_t *)pc)[2], 0, 0, 0); pc = (int *)((uint8_t *)pc + 3); break; case PCD_LSPEC3DIRECTB: P_ExecuteSpecial(((uint8_t *)pc)[0], activationline, activator, backSide, ((uint8_t *)pc)[1], ((uint8_t *)pc)[2], ((uint8_t *)pc)[3], 0, 0); pc = (int *)((uint8_t *)pc + 4); break; case PCD_LSPEC4DIRECTB: P_ExecuteSpecial(((uint8_t *)pc)[0], activationline, activator, backSide, ((uint8_t *)pc)[1], ((uint8_t *)pc)[2], ((uint8_t *)pc)[3], ((uint8_t *)pc)[4], 0); pc = (int *)((uint8_t *)pc + 5); break; case PCD_LSPEC5DIRECTB: P_ExecuteSpecial(((uint8_t *)pc)[0], activationline, activator, backSide, ((uint8_t *)pc)[1], ((uint8_t *)pc)[2], ((uint8_t *)pc)[3], ((uint8_t *)pc)[4], ((uint8_t *)pc)[5]); pc = (int *)((uint8_t *)pc + 6); break; case PCD_CALLFUNC: { int argCount = NEXTBYTE; int funcIndex = NEXTSHORT; int retval = CallFunction(argCount, funcIndex, &STACK(argCount)); sp -= argCount-1; STACK(1) = retval; } break; case PCD_PUSHFUNCTION: { int funcnum = NEXTBYTE; // Not technically a string, but since we use the same tagging mechanism PushToStack(TAGSTR(funcnum)); break; } case PCD_CALL: case PCD_CALLDISCARD: case PCD_CALLSTACK: { int funcnum; int i; ScriptFunction *func; FBehavior *module; if(pcd == PCD_CALLSTACK) { funcnum = STACK(1); module = FBehavior::StaticGetModule(funcnum>>LIBRARYID_SHIFT); --sp; funcnum &= 0xFFFF; // Clear out tag } else { module = activeBehavior; funcnum = NEXTBYTE; } func = module->GetFunction (funcnum, module); if (func == NULL) { Printf ("Function %d in %s out of range\n", funcnum, ScriptPresentation(script).GetChars()); state = SCRIPT_PleaseRemove; break; } if (sp + func->LocalCount + 64 > STACK_SIZE) { // 64 is the margin for the function's working space Printf ("Out of stack space in %s\n", ScriptPresentation(script).GetChars()); state = SCRIPT_PleaseRemove; break; } const ACSLocalVariables mylocals = locals; // The function's first argument is also its first local variable. locals.Reset(&Stack[sp - func->ArgCount], func->ArgCount + func->LocalCount); // Make space on the stack for any other variables the function uses. for (i = 0; i < func->LocalCount; ++i) { Stack[sp+i] = 0; } sp += i; ::new(&Stack[sp]) CallReturn(activeBehavior->PC2Ofs(pc), activeFunction, activeBehavior, mylocals, localarrays, pcd == PCD_CALLDISCARD, runaway); sp += (sizeof(CallReturn) + sizeof(int) - 1) / sizeof(int); pc = module->Ofs2PC (func->Address); localarrays = &func->LocalArrays; activeFunction = func; activeBehavior = module; fmt = module->GetFormat(); } break; case PCD_RETURNVOID: case PCD_RETURNVAL: { int value; union { int32_t *retsp; CallReturn *ret; }; if (pcd == PCD_RETURNVAL) { value = Stack[--sp]; } else { value = 0; } sp -= sizeof(CallReturn)/sizeof(int); retsp = &Stack[sp]; activeBehavior->GetFunctionProfileData(activeFunction)->AddRun(runaway - ret->EntryInstrCount); sp = int(locals.GetPointer() - &Stack[0]); pc = ret->ReturnModule->Ofs2PC(ret->ReturnAddress); activeFunction = ret->ReturnFunction; activeBehavior = ret->ReturnModule; fmt = activeBehavior->GetFormat(); locals = ret->ReturnLocals; localarrays = ret->ReturnArrays; if (!ret->bDiscardResult) { Stack[sp++] = value; } ret->~CallReturn(); } break; case PCD_ADD: STACK(2) = STACK(2) + STACK(1); sp--; break; case PCD_SUBTRACT: STACK(2) = STACK(2) - STACK(1); sp--; break; case PCD_MULTIPLY: STACK(2) = STACK(2) * STACK(1); sp--; break; case PCD_DIVIDE: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { STACK(2) = STACK(2) / STACK(1); sp--; } break; case PCD_MODULUS: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { STACK(2) = STACK(2) % STACK(1); sp--; } break; case PCD_EQ: STACK(2) = (STACK(2) == STACK(1)); sp--; break; case PCD_NE: STACK(2) = (STACK(2) != STACK(1)); sp--; break; case PCD_LT: STACK(2) = (STACK(2) < STACK(1)); sp--; break; case PCD_GT: STACK(2) = (STACK(2) > STACK(1)); sp--; break; case PCD_LE: STACK(2) = (STACK(2) <= STACK(1)); sp--; break; case PCD_GE: STACK(2) = (STACK(2) >= STACK(1)); sp--; break; case PCD_ASSIGNSCRIPTVAR: locals[NEXTBYTE] = STACK(1); sp--; break; case PCD_ASSIGNMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) = STACK(1); sp--; break; case PCD_ASSIGNWORLDVAR: ACS_WorldVars[NEXTBYTE] = STACK(1); sp--; break; case PCD_ASSIGNGLOBALVAR: ACS_GlobalVars[NEXTBYTE] = STACK(1); sp--; break; case PCD_ASSIGNSCRIPTARRAY: localarrays->Set(locals, NEXTBYTE, STACK(2), STACK(1)); sp -= 2; break; case PCD_ASSIGNMAPARRAY: activeBehavior->SetArrayVal (*(activeBehavior->MapVars[NEXTBYTE]), STACK(2), STACK(1)); sp -= 2; break; case PCD_ASSIGNWORLDARRAY: ACS_WorldArrays[NEXTBYTE][STACK(2)] = STACK(1); sp -= 2; break; case PCD_ASSIGNGLOBALARRAY: ACS_GlobalArrays[NEXTBYTE][STACK(2)] = STACK(1); sp -= 2; break; case PCD_PUSHSCRIPTVAR: PushToStack (locals[NEXTBYTE]); break; case PCD_PUSHMAPVAR: PushToStack (*(activeBehavior->MapVars[NEXTBYTE])); break; case PCD_PUSHWORLDVAR: PushToStack (ACS_WorldVars[NEXTBYTE]); break; case PCD_PUSHGLOBALVAR: PushToStack (ACS_GlobalVars[NEXTBYTE]); break; case PCD_PUSHSCRIPTARRAY: STACK(1) = localarrays->Get(locals, NEXTBYTE, STACK(1)); break; case PCD_PUSHMAPARRAY: STACK(1) = activeBehavior->GetArrayVal (*(activeBehavior->MapVars[NEXTBYTE]), STACK(1)); break; case PCD_PUSHWORLDARRAY: STACK(1) = ACS_WorldArrays[NEXTBYTE][STACK(1)]; break; case PCD_PUSHGLOBALARRAY: STACK(1) = ACS_GlobalArrays[NEXTBYTE][STACK(1)]; break; case PCD_ADDSCRIPTVAR: locals[NEXTBYTE] += STACK(1); sp--; break; case PCD_ADDMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) += STACK(1); sp--; break; case PCD_ADDWORLDVAR: ACS_WorldVars[NEXTBYTE] += STACK(1); sp--; break; case PCD_ADDGLOBALVAR: ACS_GlobalVars[NEXTBYTE] += STACK(1); sp--; break; case PCD_ADDSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) + STACK(1)); sp -= 2; } break; case PCD_ADDMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) + STACK(1)); sp -= 2; } break; case PCD_ADDWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] += STACK(1); sp -= 2; } break; case PCD_ADDGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] += STACK(1); sp -= 2; } break; case PCD_SUBSCRIPTVAR: locals[NEXTBYTE] -= STACK(1); sp--; break; case PCD_SUBMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) -= STACK(1); sp--; break; case PCD_SUBWORLDVAR: ACS_WorldVars[NEXTBYTE] -= STACK(1); sp--; break; case PCD_SUBGLOBALVAR: ACS_GlobalVars[NEXTBYTE] -= STACK(1); sp--; break; case PCD_SUBSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) - STACK(1)); sp -= 2; } break; case PCD_SUBMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) - STACK(1)); sp -= 2; } break; case PCD_SUBWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] -= STACK(1); sp -= 2; } break; case PCD_SUBGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] -= STACK(1); sp -= 2; } break; case PCD_MULSCRIPTVAR: locals[NEXTBYTE] *= STACK(1); sp--; break; case PCD_MULMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) *= STACK(1); sp--; break; case PCD_MULWORLDVAR: ACS_WorldVars[NEXTBYTE] *= STACK(1); sp--; break; case PCD_MULGLOBALVAR: ACS_GlobalVars[NEXTBYTE] *= STACK(1); sp--; break; case PCD_MULSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) * STACK(1)); sp -= 2; } break; case PCD_MULMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) * STACK(1)); sp -= 2; } break; case PCD_MULWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] *= STACK(1); sp -= 2; } break; case PCD_MULGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] *= STACK(1); sp -= 2; } break; case PCD_DIVSCRIPTVAR: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { locals[NEXTBYTE] /= STACK(1); sp--; } break; case PCD_DIVMAPVAR: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { *(activeBehavior->MapVars[NEXTBYTE]) /= STACK(1); sp--; } break; case PCD_DIVWORLDVAR: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { ACS_WorldVars[NEXTBYTE] /= STACK(1); sp--; } break; case PCD_DIVGLOBALVAR: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { ACS_GlobalVars[NEXTBYTE] /= STACK(1); sp--; } break; case PCD_DIVSCRIPTARRAY: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) / STACK(1)); sp -= 2; } break; case PCD_DIVMAPARRAY: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) / STACK(1)); sp -= 2; } break; case PCD_DIVWORLDARRAY: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] /= STACK(1); sp -= 2; } break; case PCD_DIVGLOBALARRAY: if (STACK(1) == 0) { state = SCRIPT_DivideBy0; } else { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] /= STACK(1); sp -= 2; } break; case PCD_MODSCRIPTVAR: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { locals[NEXTBYTE] %= STACK(1); sp--; } break; case PCD_MODMAPVAR: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { *(activeBehavior->MapVars[NEXTBYTE]) %= STACK(1); sp--; } break; case PCD_MODWORLDVAR: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { ACS_WorldVars[NEXTBYTE] %= STACK(1); sp--; } break; case PCD_MODGLOBALVAR: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { ACS_GlobalVars[NEXTBYTE] %= STACK(1); sp--; } break; case PCD_MODSCRIPTARRAY: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) % STACK(1)); sp -= 2; } break; case PCD_MODMAPARRAY: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) % STACK(1)); sp -= 2; } break; case PCD_MODWORLDARRAY: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] %= STACK(1); sp -= 2; } break; case PCD_MODGLOBALARRAY: if (STACK(1) == 0) { state = SCRIPT_ModulusBy0; } else { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] %= STACK(1); sp -= 2; } break; //[MW] start case PCD_ANDSCRIPTVAR: locals[NEXTBYTE] &= STACK(1); sp--; break; case PCD_ANDMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) &= STACK(1); sp--; break; case PCD_ANDWORLDVAR: ACS_WorldVars[NEXTBYTE] &= STACK(1); sp--; break; case PCD_ANDGLOBALVAR: ACS_GlobalVars[NEXTBYTE] &= STACK(1); sp--; break; case PCD_ANDSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) & STACK(1)); sp -= 2; } break; case PCD_ANDMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) & STACK(1)); sp -= 2; } break; case PCD_ANDWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] &= STACK(1); sp -= 2; } break; case PCD_ANDGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] &= STACK(1); sp -= 2; } break; case PCD_EORSCRIPTVAR: locals[NEXTBYTE] ^= STACK(1); sp--; break; case PCD_EORMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) ^= STACK(1); sp--; break; case PCD_EORWORLDVAR: ACS_WorldVars[NEXTBYTE] ^= STACK(1); sp--; break; case PCD_EORGLOBALVAR: ACS_GlobalVars[NEXTBYTE] ^= STACK(1); sp--; break; case PCD_EORSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) ^ STACK(1)); sp -= 2; } break; case PCD_EORMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) ^ STACK(1)); sp -= 2; } break; case PCD_EORWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] ^= STACK(1); sp -= 2; } break; case PCD_EORGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] ^= STACK(1); sp -= 2; } break; case PCD_ORSCRIPTVAR: locals[NEXTBYTE] |= STACK(1); sp--; break; case PCD_ORMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) |= STACK(1); sp--; break; case PCD_ORWORLDVAR: ACS_WorldVars[NEXTBYTE] |= STACK(1); sp--; break; case PCD_ORGLOBALVAR: ACS_GlobalVars[NEXTBYTE] |= STACK(1); sp--; break; case PCD_ORSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) | STACK(1)); sp -= 2; } break; case PCD_ORMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) | STACK(1)); sp -= 2; } break; case PCD_ORWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] |= STACK(1); sp -= 2; } break; case PCD_ORGLOBALARRAY: { int a = NEXTBYTE; int i = STACK(2); ACS_GlobalArrays[a][STACK(2)] |= STACK(1); sp -= 2; } break; case PCD_LSSCRIPTVAR: locals[NEXTBYTE] <<= STACK(1); sp--; break; case PCD_LSMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) <<= STACK(1); sp--; break; case PCD_LSWORLDVAR: ACS_WorldVars[NEXTBYTE] <<= STACK(1); sp--; break; case PCD_LSGLOBALVAR: ACS_GlobalVars[NEXTBYTE] <<= STACK(1); sp--; break; case PCD_LSSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) << STACK(1)); sp -= 2; } break; case PCD_LSMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) << STACK(1)); sp -= 2; } break; case PCD_LSWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] <<= STACK(1); sp -= 2; } break; case PCD_LSGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] <<= STACK(1); sp -= 2; } break; case PCD_RSSCRIPTVAR: locals[NEXTBYTE] >>= STACK(1); sp--; break; case PCD_RSMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) >>= STACK(1); sp--; break; case PCD_RSWORLDVAR: ACS_WorldVars[NEXTBYTE] >>= STACK(1); sp--; break; case PCD_RSGLOBALVAR: ACS_GlobalVars[NEXTBYTE] >>= STACK(1); sp--; break; case PCD_RSSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(2); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) >> STACK(1)); sp -= 2; } break; case PCD_RSMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(2); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) >> STACK(1)); sp -= 2; } break; case PCD_RSWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(2)] >>= STACK(1); sp -= 2; } break; case PCD_RSGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(2)] >>= STACK(1); sp -= 2; } break; //[MW] end case PCD_INCSCRIPTVAR: ++locals[NEXTBYTE]; break; case PCD_INCMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) += 1; break; case PCD_INCWORLDVAR: ++ACS_WorldVars[NEXTBYTE]; break; case PCD_INCGLOBALVAR: ++ACS_GlobalVars[NEXTBYTE]; break; case PCD_INCSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(1); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) + 1); sp--; } break; case PCD_INCMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(1); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) + 1); sp--; } break; case PCD_INCWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(1)] += 1; sp--; } break; case PCD_INCGLOBALARRAY: { int a = NEXTBYTE; ACS_GlobalArrays[a][STACK(1)] += 1; sp--; } break; case PCD_DECSCRIPTVAR: --locals[NEXTBYTE]; break; case PCD_DECMAPVAR: *(activeBehavior->MapVars[NEXTBYTE]) -= 1; break; case PCD_DECWORLDVAR: --ACS_WorldVars[NEXTBYTE]; break; case PCD_DECGLOBALVAR: --ACS_GlobalVars[NEXTBYTE]; break; case PCD_DECSCRIPTARRAY: { int a = NEXTBYTE, i = STACK(1); localarrays->Set(locals, a, i, localarrays->Get(locals, a, i) - 1); sp--; } break; case PCD_DECMAPARRAY: { int a = *(activeBehavior->MapVars[NEXTBYTE]); int i = STACK(1); activeBehavior->SetArrayVal (a, i, activeBehavior->GetArrayVal (a, i) - 1); sp--; } break; case PCD_DECWORLDARRAY: { int a = NEXTBYTE; ACS_WorldArrays[a][STACK(1)] -= 1; sp--; } break; case PCD_DECGLOBALARRAY: { int a = NEXTBYTE; int i = STACK(1); ACS_GlobalArrays[a][STACK(1)] -= 1; sp--; } break; case PCD_GOTO: pc = activeBehavior->Ofs2PC (LittleLong(*pc)); break; case PCD_GOTOSTACK: pc = activeBehavior->Jump2PC (STACK(1)); sp--; break; case PCD_IFGOTO: if (STACK(1)) pc = activeBehavior->Ofs2PC (LittleLong(*pc)); else pc++; sp--; break; case PCD_SETRESULTVALUE: resultValue = STACK(1); case PCD_DROP: //fall through. sp--; break; case PCD_DELAY: statedata = STACK(1) + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen); if (statedata > 0) { state = SCRIPT_Delayed; } sp--; break; case PCD_DELAYDIRECT: statedata = uallong(pc[0]) + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen); pc++; if (statedata > 0) { state = SCRIPT_Delayed; } break; case PCD_DELAYDIRECTB: statedata = *(uint8_t *)pc + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen); if (statedata > 0) { state = SCRIPT_Delayed; } pc = (int *)((uint8_t *)pc + 1); break; case PCD_RANDOM: STACK(2) = Random (STACK(2), STACK(1)); sp--; break; case PCD_RANDOMDIRECT: PushToStack (Random (uallong(pc[0]), uallong(pc[1]))); pc += 2; break; case PCD_RANDOMDIRECTB: PushToStack (Random (((uint8_t *)pc)[0], ((uint8_t *)pc)[1])); pc = (int *)((uint8_t *)pc + 2); break; case PCD_THINGCOUNT: STACK(2) = ThingCount (STACK(2), -1, STACK(1), -1); sp--; break; case PCD_THINGCOUNTDIRECT: PushToStack (ThingCount (uallong(pc[0]), -1, uallong(pc[1]), -1)); pc += 2; break; case PCD_THINGCOUNTNAME: STACK(2) = ThingCount (-1, STACK(2), STACK(1), -1); sp--; break; case PCD_THINGCOUNTNAMESECTOR: STACK(3) = ThingCount (-1, STACK(3), STACK(2), STACK(1)); sp -= 2; break; case PCD_THINGCOUNTSECTOR: STACK(3) = ThingCount (STACK(3), -1, STACK(2), STACK(1)); sp -= 2; break; case PCD_TAGWAIT: state = SCRIPT_TagWait; statedata = STACK(1); sp--; break; case PCD_TAGWAITDIRECT: state = SCRIPT_TagWait; statedata = uallong(pc[0]); pc++; break; case PCD_POLYWAIT: state = SCRIPT_PolyWait; statedata = STACK(1); sp--; break; case PCD_POLYWAITDIRECT: state = SCRIPT_PolyWait; statedata = uallong(pc[0]); pc++; break; case PCD_CHANGEFLOOR: ChangeFlat (STACK(2), STACK(1), 0); sp -= 2; break; case PCD_CHANGEFLOORDIRECT: ChangeFlat (uallong(pc[0]), TAGSTR(uallong(pc[1])), 0); pc += 2; break; case PCD_CHANGECEILING: ChangeFlat (STACK(2), STACK(1), 1); sp -= 2; break; case PCD_CHANGECEILINGDIRECT: ChangeFlat (uallong(pc[0]), TAGSTR(uallong(pc[1])), 1); pc += 2; break; case PCD_RESTART: { const ScriptPtr *scriptp; scriptp = activeBehavior->FindScript (script); pc = activeBehavior->GetScriptAddress (scriptp); } break; case PCD_ANDLOGICAL: STACK(2) = (STACK(2) && STACK(1)); sp--; break; case PCD_ORLOGICAL: STACK(2) = (STACK(2) || STACK(1)); sp--; break; case PCD_ANDBITWISE: STACK(2) = (STACK(2) & STACK(1)); sp--; break; case PCD_ORBITWISE: STACK(2) = (STACK(2) | STACK(1)); sp--; break; case PCD_EORBITWISE: STACK(2) = (STACK(2) ^ STACK(1)); sp--; break; case PCD_NEGATELOGICAL: STACK(1) = !STACK(1); break; case PCD_NEGATEBINARY: STACK(1) = ~STACK(1); break; case PCD_LSHIFT: STACK(2) = (STACK(2) << STACK(1)); sp--; break; case PCD_RSHIFT: STACK(2) = (STACK(2) >> STACK(1)); sp--; break; case PCD_UNARYMINUS: STACK(1) = -STACK(1); break; case PCD_IFNOTGOTO: if (!STACK(1)) pc = activeBehavior->Ofs2PC (LittleLong(*pc)); else pc++; sp--; break; case PCD_LINESIDE: PushToStack (backSide); break; case PCD_SCRIPTWAIT: statedata = STACK(1); sp--; scriptwait: if (controller->RunningScripts.CheckKey(statedata) != NULL) state = SCRIPT_ScriptWait; else state = SCRIPT_ScriptWaitPre; PutLast (); break; case PCD_SCRIPTWAITDIRECT: if (!(i_compatflags2 & COMPATF2_SCRIPTWAIT)) { statedata = uallong(pc[0]); pc++; goto scriptwait; } else { // Old implementation for compatibility with Daedalus MAP19 state = SCRIPT_ScriptWait; statedata = uallong(pc[0]); pc++; PutLast(); break; } case PCD_SCRIPTWAITNAMED: statedata = -FName(FBehavior::StaticLookupString(STACK(1))); sp--; goto scriptwait; case PCD_CLEARLINESPECIAL: if (activationline != NULL) { activationline->special = 0; DPrintf(DMSG_SPAMMY, "Cleared line special on line %d\n", activationline->Index()); } break; case PCD_CASEGOTO: if (STACK(1) == uallong(pc[0])) { pc = activeBehavior->Ofs2PC (uallong(pc[1])); sp--; } else { pc += 2; } break; case PCD_CASEGOTOSORTED: // The count and jump table are 4-byte aligned pc = (int *)(((size_t)pc + 3) & ~3); { int numcases = uallong(pc[0]); pc++; int min = 0, max = numcases-1; while (min <= max) { int mid = (min + max) / 2; int32_t caseval = LittleLong(pc[mid*2]); if (caseval == STACK(1)) { pc = activeBehavior->Ofs2PC (LittleLong(pc[mid*2+1])); sp--; break; } else if (caseval < STACK(1)) { min = mid + 1; } else { max = mid - 1; } } if (min > max) { // The case was not found, so go to the next instruction. pc += numcases * 2; } } break; case PCD_BEGINPRINT: STRINGBUILDER_START(work); break; case PCD_PRINTSTRING: case PCD_PRINTLOCALIZED: lookup = FBehavior::StaticLookupString (STACK(1), true); if (pcd == PCD_PRINTLOCALIZED) { lookup = GStrings(lookup); } if (lookup != NULL) { work += lookup; } --sp; break; case PCD_PRINTNUMBER: work.AppendFormat ("%d", STACK(1)); --sp; break; case PCD_PRINTBINARY: IGNORE_FORMAT_PRE work.AppendFormat ("%B", STACK(1)); IGNORE_FORMAT_POST --sp; break; case PCD_PRINTHEX: work.AppendFormat ("%X", STACK(1)); --sp; break; case PCD_PRINTCHARACTER: work += (char)STACK(1); --sp; break; case PCD_PRINTFIXED: work.AppendFormat ("%g", ACSToDouble(STACK(1))); --sp; break; // [BC] Print activator's name // [RH] Fancied up a bit case PCD_PRINTNAME: { player_t *player = NULL; if (STACK(1) < 0) { switch (STACK(1)) { case PRINTNAME_LEVELNAME: work += level.LevelName; break; case PRINTNAME_LEVEL: { FString uppername = level.MapName; uppername.ToUpper(); work += uppername; break; } case PRINTNAME_NEXTLEVEL: { FString uppername = level.NextMap; uppername.ToUpper(); work += uppername; break; } case PRINTNAME_NEXTSECRET: { FString uppername = level.NextSecretMap; uppername.ToUpper(); work += uppername; break; } case PRINTNAME_SKILL: work += G_SkillName(); break; default: work += ' '; break; } sp--; break; } else if (STACK(1) == 0 || (unsigned)STACK(1) > MAXPLAYERS) { if (activator) { player = activator->player; } } else if (playeringame[STACK(1)-1]) { player = &players[STACK(1)-1]; } else { work.AppendFormat ("Player %d", STACK(1)); sp--; break; } if (player) { work += player->userinfo.GetName(); } else if (activator) { work += activator->GetTag(); } else { work += ' '; } sp--; } break; // Print script character array case PCD_PRINTSCRIPTCHARARRAY: case PCD_PRINTSCRIPTCHRANGE: { int capacity, offset, a, c; if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTSCRIPTCHRANGE)) { while (capacity-- && (c = localarrays->Get(locals, a, offset)) != '\0') { work += (char)c; offset++; } } } break; // [JB] Print map character array case PCD_PRINTMAPCHARARRAY: case PCD_PRINTMAPCHRANGE: { int capacity, offset, a, c; if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTMAPCHRANGE)) { while (capacity-- && (c = activeBehavior->GetArrayVal (a, offset)) != '\0') { work += (char)c; offset++; } } } break; // [JB] Print world character array case PCD_PRINTWORLDCHARARRAY: case PCD_PRINTWORLDCHRANGE: { int capacity, offset, a, c; if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTWORLDCHRANGE)) { while (capacity-- && (c = ACS_WorldArrays[a][offset]) != '\0') { work += (char)c; offset++; } } } break; // [JB] Print global character array case PCD_PRINTGLOBALCHARARRAY: case PCD_PRINTGLOBALCHRANGE: { int capacity, offset, a, c; if (CharArrayParms(capacity, offset, a, Stack, sp, pcd == PCD_PRINTGLOBALCHRANGE)) { while (capacity-- && (c = ACS_GlobalArrays[a][offset]) != '\0') { work += (char)c; offset++; } } } break; // [GRB] Print key name(s) for a command case PCD_PRINTBIND: lookup = FBehavior::StaticLookupString (STACK(1)); if (lookup != NULL) { int key1 = 0, key2 = 0; Bindings.GetKeysForCommand ((char *)lookup, &key1, &key2); if (key2) work << KeyNames[key1] << " or " << KeyNames[key2]; else if (key1) work << KeyNames[key1]; else work << "??? (" << (char *)lookup << ')'; } --sp; break; case PCD_ENDPRINT: case PCD_ENDPRINTBOLD: case PCD_MOREHUDMESSAGE: case PCD_ENDLOG: if (pcd == PCD_ENDLOG) { Printf ("%s\n", work.GetChars()); STRINGBUILDER_FINISH(work); } else if (pcd != PCD_MOREHUDMESSAGE) { AActor *screen = activator; // If a missile is the activator, make the thing that // launched the missile the target of the print command. if (screen != NULL && screen->player == NULL && (screen->flags & MF_MISSILE) && screen->target != NULL) { screen = screen->target; } if (pcd == PCD_ENDPRINTBOLD || screen == NULL || screen->CheckLocalView()) { C_MidPrint(activefont, work, pcd == PCD_ENDPRINTBOLD && (gameinfo.correctprintbold || (level.flags2 & LEVEL2_HEXENHACK))); } STRINGBUILDER_FINISH(work); } else { optstart = -1; } break; case PCD_OPTHUDMESSAGE: optstart = sp; break; case PCD_ENDHUDMESSAGE: case PCD_ENDHUDMESSAGEBOLD: if (optstart == -1) { optstart = sp; } { AActor *screen = activator; if (screen != NULL && screen->player == NULL && (screen->flags & MF_MISSILE) && screen->target != NULL) { screen = screen->target; } if (pcd == PCD_ENDHUDMESSAGEBOLD || screen == NULL || players[consoleplayer].mo == screen) { int type = Stack[optstart-6]; int id = Stack[optstart-5]; EColorRange color; float x = ACSToFloat(Stack[optstart-3]); float y = ACSToFloat(Stack[optstart-2]); float holdTime = ACSToFloat(Stack[optstart-1]); float alpha; DHUDMessage *msg; if (type & HUDMSG_COLORSTRING) { color = V_FindFontColor(FBehavior::StaticLookupString(Stack[optstart-4])); } else { color = CLAMPCOLOR(Stack[optstart-4]); } switch (type & 0xFF) { default: // normal alpha = (optstart < sp) ? ACSToFloat(Stack[optstart]) : 1.f; msg = Create (activefont, work, x, y, hudwidth, hudheight, color, holdTime); break; case 1: // fade out { float fadeTime = (optstart < sp) ? ACSToFloat(Stack[optstart]) : 0.5f; alpha = (optstart < sp-1) ? ACSToFloat(Stack[optstart+1]) : 1.f; msg = Create (activefont, work, x, y, hudwidth, hudheight, color, holdTime, fadeTime); } break; case 2: // type on, then fade out { float typeTime = (optstart < sp) ? ACSToFloat(Stack[optstart]) : 0.05f; float fadeTime = (optstart < sp-1) ? ACSToFloat(Stack[optstart+1]) : 0.5f; alpha = (optstart < sp-2) ? ACSToFloat(Stack[optstart+2]) : 1.f; msg = Create (activefont, work, x, y, hudwidth, hudheight, color, typeTime, holdTime, fadeTime); } break; case 3: // fade in, then fade out { float inTime = (optstart < sp) ? ACSToFloat(Stack[optstart]) : 0.5f; float outTime = (optstart < sp-1) ? ACSToFloat(Stack[optstart+1]) : 0.5f; alpha = (optstart < sp-2) ? ACSToFloat(Stack[optstart + 2]) : 1.f; msg = Create (activefont, work, x, y, hudwidth, hudheight, color, holdTime, inTime, outTime); } break; } msg->SetClipRect(ClipRectLeft, ClipRectTop, ClipRectWidth, ClipRectHeight, HandleAspect); if (WrapWidth != 0) { msg->SetWrapWidth(WrapWidth); } msg->SetVisibility((type & HUDMSG_VISIBILITY_MASK) >> HUDMSG_VISIBILITY_SHIFT); if (type & HUDMSG_NOWRAP) { msg->SetNoWrap(true); } if (type & HUDMSG_ALPHA) { msg->SetAlpha(alpha); } if (type & HUDMSG_ADDBLEND) { msg->SetRenderStyle(STYLE_Add); } StatusBar->AttachMessage (msg, id ? 0xff000000|id : 0, (type & HUDMSG_LAYER_MASK) >> HUDMSG_LAYER_SHIFT); if (type & HUDMSG_LOG) { static const char bar[] = TEXTCOLOR_ORANGE "\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36" "\36\36\36\36\36\36\36\36\36\36\36\36\37" TEXTCOLOR_NORMAL "\n"; char consolecolor[3]; consolecolor[0] = '\x1c'; consolecolor[1] = color >= CR_BRICK && color <= CR_YELLOW ? color + 'A' : '-'; consolecolor[2] = '\0'; AddToConsole (-1, bar); AddToConsole (-1, consolecolor); AddToConsole (-1, work); AddToConsole (-1, bar); } } } STRINGBUILDER_FINISH(work); sp = optstart-6; break; case PCD_SETFONT: DoSetFont (STACK(1)); sp--; break; case PCD_SETFONTDIRECT: DoSetFont (TAGSTR(uallong(pc[0]))); pc++; break; case PCD_PLAYERCOUNT: PushToStack (CountPlayers ()); break; case PCD_GAMETYPE: if (gamestate == GS_TITLELEVEL) PushToStack (GAME_TITLE_MAP); else if (deathmatch) PushToStack (GAME_NET_DEATHMATCH); else if (multiplayer) PushToStack (GAME_NET_COOPERATIVE); else PushToStack (GAME_SINGLE_PLAYER); break; case PCD_GAMESKILL: PushToStack (G_SkillProperty(SKILLP_ACSReturn)); break; // [BC] Start ST PCD's case PCD_ISNETWORKGAME: PushToStack(netgame); break; case PCD_PLAYERTEAM: if ( activator && activator->player ) PushToStack( activator->player->userinfo.GetTeam() ); else PushToStack( 0 ); break; case PCD_PLAYERHEALTH: if (activator) PushToStack (activator->health); else PushToStack (0); break; case PCD_PLAYERARMORPOINTS: if (activator) { auto armor = activator->FindInventory(NAME_BasicArmor); PushToStack (armor ? armor->IntVar(NAME_Amount) : 0); } else { PushToStack (0); } break; case PCD_PLAYERFRAGS: if (activator && activator->player) PushToStack (activator->player->fragcount); else PushToStack (0); break; case PCD_MUSICCHANGE: lookup = FBehavior::StaticLookupString (STACK(2)); if (lookup != NULL) { S_ChangeMusic (lookup, STACK(1)); } sp -= 2; break; case PCD_SINGLEPLAYER: PushToStack (!multiplayer); break; // [BC] End ST PCD's case PCD_TIMER: PushToStack (level.time); break; case PCD_SECTORSOUND: lookup = FBehavior::StaticLookupString (STACK(2)); if (lookup != NULL) { if (activationline) { S_Sound ( activationline->frontsector, CHAN_AUTO, // Not CHAN_AREA, because that'd probably break existing scripts. lookup, (float)(STACK(1)) / 127.f, ATTN_NORM); } else { S_Sound ( CHAN_AUTO, lookup, (float)(STACK(1)) / 127.f, ATTN_NORM); } } sp -= 2; break; case PCD_AMBIENTSOUND: lookup = FBehavior::StaticLookupString (STACK(2)); if (lookup != NULL) { S_Sound (CHAN_AUTO, lookup, (float)(STACK(1)) / 127.f, ATTN_NONE); } sp -= 2; break; case PCD_LOCALAMBIENTSOUND: lookup = FBehavior::StaticLookupString (STACK(2)); if (lookup != NULL && activator && activator->CheckLocalView()) { S_Sound (CHAN_AUTO, lookup, (float)(STACK(1)) / 127.f, ATTN_NONE); } sp -= 2; break; case PCD_ACTIVATORSOUND: lookup = FBehavior::StaticLookupString (STACK(2)); if (lookup != NULL) { if (activator != NULL) { S_Sound (activator, CHAN_AUTO, lookup, (float)(STACK(1)) / 127.f, ATTN_NORM); } else { S_Sound (CHAN_AUTO, lookup, (float)(STACK(1)) / 127.f, ATTN_NONE); } } sp -= 2; break; case PCD_SOUNDSEQUENCE: lookup = FBehavior::StaticLookupString (STACK(1)); if (lookup != NULL) { if (activationline != NULL) { SN_StartSequence (activationline->frontsector, CHAN_FULLHEIGHT, lookup, 0); } } sp--; break; case PCD_SETLINETEXTURE: SetLineTexture (STACK(4), STACK(3), STACK(2), STACK(1)); sp -= 4; break; case PCD_REPLACETEXTURES: { const char *fromname = FBehavior::StaticLookupString(STACK(3)); const char *toname = FBehavior::StaticLookupString(STACK(2)); P_ReplaceTextures(fromname, toname, STACK(1)); sp -= 3; break; } case PCD_SETLINEBLOCKING: { int lineno; FLineIdIterator itr(STACK(2)); while ((lineno = itr.Next()) >= 0) { auto &line = level.lines[lineno]; switch (STACK(1)) { case BLOCK_NOTHING: line.flags &= ~(ML_BLOCKING|ML_BLOCKEVERYTHING|ML_RAILING|ML_BLOCK_PLAYERS); break; case BLOCK_CREATURES: default: line.flags &= ~(ML_BLOCKEVERYTHING|ML_RAILING|ML_BLOCK_PLAYERS); line.flags |= ML_BLOCKING; break; case BLOCK_EVERYTHING: line.flags &= ~(ML_RAILING|ML_BLOCK_PLAYERS); line.flags |= ML_BLOCKING|ML_BLOCKEVERYTHING; break; case BLOCK_RAILING: line.flags &= ~(ML_BLOCKEVERYTHING|ML_BLOCK_PLAYERS); line.flags |= ML_RAILING|ML_BLOCKING; break; case BLOCK_PLAYERS: line.flags &= ~(ML_BLOCKEVERYTHING|ML_BLOCKING|ML_RAILING); line.flags |= ML_BLOCK_PLAYERS; break; } } sp -= 2; } break; case PCD_SETLINEMONSTERBLOCKING: { int line; FLineIdIterator itr(STACK(2)); while ((line = itr.Next()) >= 0) { if (STACK(1)) level.lines[line].flags |= ML_BLOCKMONSTERS; else level.lines[line].flags &= ~ML_BLOCKMONSTERS; } sp -= 2; } break; case PCD_SETLINESPECIAL: { int linenum = -1; int specnum = STACK(6); int arg0 = STACK(5); // Convert named ACS "specials" into real specials. if (specnum >= -ACSF_ACS_NamedExecuteAlways && specnum <= -ACSF_ACS_NamedExecute) { specnum = NamedACSToNormalACS[-specnum - ACSF_ACS_NamedExecute]; arg0 = -FName(FBehavior::StaticLookupString(arg0)); } FLineIdIterator itr(STACK(7)); while ((linenum = itr.Next()) >= 0) { line_t *line = &level.lines[linenum]; line->special = specnum; line->args[0] = arg0; line->args[1] = STACK(4); line->args[2] = STACK(3); line->args[3] = STACK(2); line->args[4] = STACK(1); DPrintf(DMSG_SPAMMY, "Set special on line %d (id %d) to %d(%d,%d,%d,%d,%d)\n", linenum, STACK(7), specnum, arg0, STACK(4), STACK(3), STACK(2), STACK(1)); } sp -= 7; } break; case PCD_SETTHINGSPECIAL: { int specnum = STACK(6); int arg0 = STACK(5); // Convert named ACS "specials" into real specials. if (specnum >= -ACSF_ACS_NamedExecuteAlways && specnum <= -ACSF_ACS_NamedExecute) { specnum = NamedACSToNormalACS[-specnum - ACSF_ACS_NamedExecute]; arg0 = -FName(FBehavior::StaticLookupString(arg0)); } if (STACK(7) != 0) { FActorIterator iterator (STACK(7)); AActor *actor; while ( (actor = iterator.Next ()) ) { actor->special = specnum; actor->args[0] = arg0; actor->args[1] = STACK(4); actor->args[2] = STACK(3); actor->args[3] = STACK(2); actor->args[4] = STACK(1); } } else if (activator != NULL) { activator->special = specnum; activator->args[0] = arg0; activator->args[1] = STACK(4); activator->args[2] = STACK(3); activator->args[3] = STACK(2); activator->args[4] = STACK(1); } sp -= 7; } break; case PCD_THINGSOUND: lookup = FBehavior::StaticLookupString (STACK(2)); if (lookup != NULL) { FActorIterator iterator (STACK(3)); AActor *spot; while ( (spot = iterator.Next ()) ) { S_Sound (spot, CHAN_AUTO, lookup, (float)(STACK(1))/127.f, ATTN_NORM); } } sp -= 3; break; case PCD_FIXEDMUL: STACK(2) = FixedMul (STACK(2), STACK(1)); sp--; break; case PCD_FIXEDDIV: STACK(2) = FixedDiv (STACK(2), STACK(1)); sp--; break; case PCD_SETGRAVITY: level.gravity = ACSToDouble(STACK(1)); sp--; break; case PCD_SETGRAVITYDIRECT: level.gravity = ACSToDouble(uallong(pc[0])); pc++; break; case PCD_SETAIRCONTROL: level.aircontrol = ACSToDouble(STACK(1)); sp--; G_AirControlChanged (); break; case PCD_SETAIRCONTROLDIRECT: level.aircontrol = ACSToDouble(uallong(pc[0])); pc++; G_AirControlChanged (); break; case PCD_SPAWN: STACK(6) = DoSpawn (STACK(6), STACK(5), STACK(4), STACK(3), STACK(2), STACK(1), false); sp -= 5; break; case PCD_SPAWNDIRECT: PushToStack (DoSpawn (TAGSTR(uallong(pc[0])), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), uallong(pc[4]), uallong(pc[5]), false)); pc += 6; break; case PCD_SPAWNSPOT: STACK(4) = DoSpawnSpot (STACK(4), STACK(3), STACK(2), STACK(1), false); sp -= 3; break; case PCD_SPAWNSPOTDIRECT: PushToStack (DoSpawnSpot (TAGSTR(uallong(pc[0])), uallong(pc[1]), uallong(pc[2]), uallong(pc[3]), false)); pc += 4; break; case PCD_SPAWNSPOTFACING: STACK(3) = DoSpawnSpotFacing (STACK(3), STACK(2), STACK(1), false); sp -= 2; break; case PCD_CLEARINVENTORY: ScriptUtil::Exec(NAME_ClearInventory, ScriptUtil::Pointer, activator.Get(), ScriptUtil::End); break; case PCD_CLEARACTORINVENTORY: if (STACK(1) == 0) { ScriptUtil::Exec(NAME_ClearInventory, ScriptUtil::Pointer, nullptr, ScriptUtil::End); } else { FActorIterator it(STACK(1)); AActor *actor; for (actor = it.Next(); actor != NULL; actor = it.Next()) { ScriptUtil::Exec(NAME_ClearInventory, ScriptUtil::Pointer, actor , ScriptUtil::End); } } sp--; break; case PCD_GIVEINVENTORY: { int typeindex = FName(FBehavior::StaticLookupString(STACK(2))).GetIndex(); ScriptUtil::Exec(NAME_GiveInventory, ScriptUtil::Pointer, activator.Get(), ScriptUtil::Int, typeindex, ScriptUtil::Int, STACK(1), ScriptUtil::End); sp -= 2; break; } case PCD_GIVEACTORINVENTORY: { int typeindex = FName(FBehavior::StaticLookupString(STACK(2))).GetIndex(); FName type = FName(FBehavior::StaticLookupString(STACK(2))); if (STACK(3) == 0) { ScriptUtil::Exec(NAME_GiveInventory, ScriptUtil::Pointer, nullptr, ScriptUtil::Int, typeindex, ScriptUtil::Int, STACK(1), ScriptUtil::End); } else { FActorIterator it(STACK(3)); AActor *actor; for (actor = it.Next(); actor != NULL; actor = it.Next()) { ScriptUtil::Exec(NAME_GiveInventory, ScriptUtil::Pointer, actor, ScriptUtil::Int, typeindex, ScriptUtil::Int, STACK(1), ScriptUtil::End); } } sp -= 3; break; } case PCD_GIVEINVENTORYDIRECT: { int typeindex = FName(FBehavior::StaticLookupString(TAGSTR(uallong(pc[0])))).GetIndex(); ScriptUtil::Exec(NAME_GiveInventory, ScriptUtil::Pointer, activator.Get(), ScriptUtil::Int, typeindex, ScriptUtil::Int, uallong(pc[1]), ScriptUtil::End); pc += 2; break; } case PCD_TAKEINVENTORY: { int typeindex = FName(FBehavior::StaticLookupString(STACK(2))).GetIndex(); ScriptUtil::Exec(NAME_TakeInventory, ScriptUtil::Pointer, activator.Get(), ScriptUtil::Int, typeindex, ScriptUtil::Int, STACK(1), ScriptUtil::End); sp -= 2; break; } case PCD_TAKEACTORINVENTORY: { int typeindex = FName(FBehavior::StaticLookupString(STACK(2))).GetIndex(); FName type = FName(FBehavior::StaticLookupString(STACK(2))); if (STACK(3) == 0) { ScriptUtil::Exec(NAME_TakeInventory, ScriptUtil::Pointer, nullptr, ScriptUtil::Int, typeindex, ScriptUtil::Int, STACK(1), ScriptUtil::End); } else { FActorIterator it(STACK(3)); AActor *actor; for (actor = it.Next(); actor != NULL; actor = it.Next()) { ScriptUtil::Exec(NAME_TakeInventory, ScriptUtil::Pointer, actor, ScriptUtil::Int, typeindex, ScriptUtil::Int, STACK(1), ScriptUtil::End); } } sp -= 3; break; } case PCD_TAKEINVENTORYDIRECT: { int typeindex = FName(FBehavior::StaticLookupString(TAGSTR(uallong(pc[0])))).GetIndex(); ScriptUtil::Exec(NAME_TakeInventory, ScriptUtil::Pointer, activator.Get(), ScriptUtil::Int, typeindex, ScriptUtil::Int, uallong(pc[1]), ScriptUtil::End); pc += 2; break; } case PCD_CHECKINVENTORY: STACK(1) = CheckInventory (activator, FBehavior::StaticLookupString (STACK(1)), false); break; case PCD_CHECKACTORINVENTORY: STACK(2) = CheckInventory (SingleActorFromTID(STACK(2), NULL), FBehavior::StaticLookupString (STACK(1)), false); sp--; break; case PCD_CHECKINVENTORYDIRECT: PushToStack (CheckInventory (activator, FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), false)); pc += 1; break; case PCD_USEINVENTORY: STACK(1) = UseInventory (activator, FBehavior::StaticLookupString (STACK(1))); break; case PCD_USEACTORINVENTORY: { int ret = 0; const char *type = FBehavior::StaticLookupString(STACK(1)); if (STACK(2) == 0) { ret = UseInventory(NULL, type); } else { FActorIterator it(STACK(2)); AActor *actor; for (actor = it.Next(); actor != NULL; actor = it.Next()) { ret += UseInventory(actor, type); } } STACK(2) = ret; sp--; } break; case PCD_GETSIGILPIECES: { AActor *sigil; if (activator == NULL || (sigil = activator->FindInventory(NAME_Sigil)) == NULL) { PushToStack (0); } else { PushToStack (sigil->health); } } break; case PCD_GETAMMOCAPACITY: if (activator != NULL) { PClass *type = PClass::FindClass (FBehavior::StaticLookupString (STACK(1))); if (type != NULL && type->ParentClass == PClass::FindActor(NAME_Ammo)) { auto item = activator->FindInventory (static_cast(type)); if (item != NULL) { STACK(1) = item->IntVar(NAME_MaxAmount); } else { STACK(1) = GetDefaultByType (type)->IntVar(NAME_MaxAmount); } } else { STACK(1) = 0; } } else { STACK(1) = 0; } break; case PCD_SETAMMOCAPACITY: if (activator != NULL) { PClassActor *type = PClass::FindActor (FBehavior::StaticLookupString (STACK(2))); if (type != NULL && type->ParentClass == PClass::FindActor(NAME_Ammo)) { auto item = activator->FindInventory (type); if (item != NULL) { item->IntVar(NAME_MaxAmount) = STACK(1); } else { item = activator->GiveInventoryType (type); if (item != NULL) { item->IntVar(NAME_MaxAmount) = STACK(1); item->IntVar(NAME_Amount) = 0; } } } } sp -= 2; break; case PCD_SETMUSIC: S_ChangeMusic (FBehavior::StaticLookupString (STACK(3)), STACK(2)); sp -= 3; break; case PCD_SETMUSICDIRECT: S_ChangeMusic (FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), uallong(pc[1])); pc += 3; break; case PCD_LOCALSETMUSIC: if (activator == players[consoleplayer].mo) { S_ChangeMusic (FBehavior::StaticLookupString (STACK(3)), STACK(2)); } sp -= 3; break; case PCD_LOCALSETMUSICDIRECT: if (activator == players[consoleplayer].mo) { S_ChangeMusic (FBehavior::StaticLookupString (TAGSTR(uallong(pc[0]))), uallong(pc[1])); } pc += 3; break; case PCD_FADETO: DoFadeTo (STACK(5), STACK(4), STACK(3), STACK(2), STACK(1)); sp -= 5; break; case PCD_FADERANGE: DoFadeRange (STACK(9), STACK(8), STACK(7), STACK(6), STACK(5), STACK(4), STACK(3), STACK(2), STACK(1)); sp -= 9; break; case PCD_CANCELFADE: { TThinkerIterator iterator; DFlashFader *fader; while ( (fader = iterator.Next()) ) { if (activator == NULL || fader->WhoFor() == activator) { fader->Cancel (); } } } break; case PCD_PLAYMOVIE: STACK(1) = -1; break; case PCD_SETACTORPOSITION: { bool result = false; AActor *actor = SingleActorFromTID (STACK(5), activator); if (actor != NULL) result = P_MoveThing(actor, DVector3(ACSToDouble(STACK(4)), ACSToDouble(STACK(3)), ACSToDouble(STACK(2))), !!STACK(1)); sp -= 4; STACK(1) = result; } break; case PCD_GETACTORX: case PCD_GETACTORY: case PCD_GETACTORZ: { AActor *actor = SingleActorFromTID(STACK(1), activator); if (actor == NULL) { STACK(1) = 0; } else if (pcd == PCD_GETACTORZ) { STACK(1) = DoubleToACS(actor->Z() + actor->GetBobOffset()); } else { STACK(1) = DoubleToACS(pcd == PCD_GETACTORX ? actor->X() : actor->Y()); } } break; case PCD_GETACTORFLOORZ: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : DoubleToACS(actor->floorz); } break; case PCD_GETACTORCEILINGZ: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : DoubleToACS(actor->ceilingz); } break; case PCD_GETACTORANGLE: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : AngleToACS(actor->Angles.Yaw); } break; case PCD_GETACTORPITCH: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : PitchToACS(actor->Angles.Pitch); } break; case PCD_GETLINEROWOFFSET: if (activationline != NULL) { PushToStack (int(activationline->sidedef[0]->GetTextureYOffset(side_t::mid))); } else { PushToStack (0); } break; case PCD_GETSECTORFLOORZ: case PCD_GETSECTORCEILINGZ: // Arguments are (tag, x, y). If you don't use slopes, then (x, y) don't // really matter and can be left as (0, 0) if you like. // [Dusk] If tag = 0, then this returns the z height at whatever sector // is in x, y. { int tag = STACK(3); int secnum; double x = double(STACK(2)); double y = double(STACK(1)); double z = 0; if (tag != 0) secnum = P_FindFirstSectorFromTag (tag); else secnum = P_PointInSector (x, y)->sectornum; if (secnum >= 0) { if (pcd == PCD_GETSECTORFLOORZ) { z = level.sectors[secnum].floorplane.ZatPoint (x, y); } else { z = level.sectors[secnum].ceilingplane.ZatPoint (x, y); } } sp -= 2; STACK(1) = DoubleToACS(z); } break; case PCD_GETSECTORLIGHTLEVEL: { int secnum = P_FindFirstSectorFromTag (STACK(1)); int z = -1; if (secnum >= 0) { z = level.sectors[secnum].lightlevel; } STACK(1) = z; } break; case PCD_SETFLOORTRIGGER: Create (activator, activationline, backSide, false, STACK(8), STACK(7), STACK(6), STACK(5), STACK(4), STACK(3), STACK(2), STACK(1)); sp -= 8; break; case PCD_SETCEILINGTRIGGER: Create (activator, activationline, backSide, true, STACK(8), STACK(7), STACK(6), STACK(5), STACK(4), STACK(3), STACK(2), STACK(1)); sp -= 8; break; case PCD_STARTTRANSLATION: { int i = STACK(1); sp--; if (i >= 1 && i <= MAX_ACS_TRANSLATIONS) { translation = translationtables[TRANSLATION_LevelScripted].GetVal(i - 1); if (translation == NULL) { translation = new FRemapTable; translationtables[TRANSLATION_LevelScripted].SetVal(i - 1, translation); } translation->MakeIdentity(); } } break; case PCD_TRANSLATIONRANGE1: { // translation using palette shifting int start = STACK(4); int end = STACK(3); int pal1 = STACK(2); int pal2 = STACK(1); sp -= 4; if (translation != NULL) translation->AddIndexRange(start, end, pal1, pal2); } break; case PCD_TRANSLATIONRANGE2: { // translation using RGB values // (would HSV be a good idea too?) int start = STACK(8); int end = STACK(7); int r1 = STACK(6); int g1 = STACK(5); int b1 = STACK(4); int r2 = STACK(3); int g2 = STACK(2); int b2 = STACK(1); sp -= 8; if (translation != NULL) translation->AddColorRange(start, end, r1, g1, b1, r2, g2, b2); } break; case PCD_TRANSLATIONRANGE3: { // translation using desaturation int start = STACK(8); int end = STACK(7); int r1 = STACK(6); int g1 = STACK(5); int b1 = STACK(4); int r2 = STACK(3); int g2 = STACK(2); int b2 = STACK(1); sp -= 8; if (translation != NULL) translation->AddDesaturation(start, end, ACSToDouble(r1), ACSToDouble(g1), ACSToDouble(b1), ACSToDouble(r2), ACSToDouble(g2), ACSToDouble(b2)); } break; case PCD_TRANSLATIONRANGE4: { // Colourise translation int start = STACK(5); int end = STACK(4); int r = STACK(3); int g = STACK(2); int b = STACK(1); sp -= 5; if (translation != NULL) translation->AddColourisation(start, end, r, g, b); } break; case PCD_TRANSLATIONRANGE5: { // Tint translation int start = STACK(6); int end = STACK(5); int a = STACK(4); int r = STACK(3); int g = STACK(2); int b = STACK(1); sp -= 6; if (translation != NULL) translation->AddTint(start, end, r, g, b, a); } break; case PCD_ENDTRANSLATION: if (translation != NULL) { translation->UpdateNative(); translation = NULL; } break; case PCD_SIN: STACK(1) = DoubleToACS(ACSToAngle(STACK(1)).Sin()); break; case PCD_COS: STACK(1) = DoubleToACS(ACSToAngle(STACK(1)).Cos()); break; case PCD_VECTORANGLE: STACK(2) = AngleToACS(VecToAngle(STACK(2), STACK(1)).Degrees); sp--; break; case PCD_CHECKWEAPON: if (activator == NULL || activator->player == NULL || // Non-players do not have weapons activator->player->ReadyWeapon == NULL) { STACK(1) = 0; } else { STACK(1) = activator->player->ReadyWeapon->GetClass()->TypeName == FName(FBehavior::StaticLookupString (STACK(1)), true); } break; case PCD_SETWEAPON: STACK(1) = ScriptUtil::Exec(NAME_SetWeapon, ScriptUtil::Pointer, activator.Get(), ScriptUtil::ACSClass, STACK(1), ScriptUtil::End); break; case PCD_SETMARINEWEAPON: ScriptUtil::Exec(NAME_SetMarineWeapon, ScriptUtil::Pointer, activator.Get(), ScriptUtil::Int, STACK(2), ScriptUtil::Int, STACK(1), ScriptUtil::End); sp -= 2; break; case PCD_SETMARINESPRITE: ScriptUtil::Exec(NAME_SetMarineSprite, ScriptUtil::Pointer, activator.Get(), ScriptUtil::Int, STACK(2), ScriptUtil::ACSClass, STACK(1), ScriptUtil::End); sp -= 2; break; case PCD_SETACTORPROPERTY: SetActorProperty (STACK(3), STACK(2), STACK(1)); sp -= 3; break; case PCD_GETACTORPROPERTY: STACK(2) = GetActorProperty (STACK(2), STACK(1)); sp -= 1; break; case PCD_GETPLAYERINPUT: STACK(2) = GetPlayerInput (STACK(2), STACK(1)); sp -= 1; break; case PCD_PLAYERNUMBER: if (activator == NULL || activator->player == NULL) { PushToStack (-1); } else { PushToStack (int(activator->player - players)); } break; case PCD_PLAYERINGAME: if (STACK(1) < 0 || STACK(1) >= MAXPLAYERS) { STACK(1) = false; } else { STACK(1) = playeringame[STACK(1)]; } break; case PCD_PLAYERISBOT: if (STACK(1) < 0 || STACK(1) >= MAXPLAYERS || !playeringame[STACK(1)]) { STACK(1) = false; } else { STACK(1) = (players[STACK(1)].Bot != NULL); } break; case PCD_ACTIVATORTID: if (activator == NULL) { PushToStack (0); } else { PushToStack (activator->tid); } break; case PCD_GETSCREENWIDTH: PushToStack (SCREENWIDTH); break; case PCD_GETSCREENHEIGHT: PushToStack (SCREENHEIGHT); break; case PCD_THING_PROJECTILE2: // Like Thing_Projectile(Gravity) specials, but you can give the // projectile a TID. // Thing_Projectile2 (tid, type, angle, speed, vspeed, gravity, newtid); P_Thing_Projectile(STACK(7), activator, STACK(6), NULL, STACK(5) * (360. / 256.), STACK(4) / 8., STACK(3) / 8., 0, NULL, STACK(2), STACK(1), false); sp -= 7; break; case PCD_SPAWNPROJECTILE: // Same, but takes an actor name instead of a spawn ID. P_Thing_Projectile(STACK(7), activator, 0, FBehavior::StaticLookupString(STACK(6)), STACK(5) * (360. / 256.), STACK(4) / 8., STACK(3) / 8., 0, NULL, STACK(2), STACK(1), false); sp -= 7; break; case PCD_STRLEN: { const char *str = FBehavior::StaticLookupString(STACK(1)); if (str != NULL) { STACK(1) = int32_t(strlen(str)); break; } static bool StrlenInvalidPrintedAlready = false; if (!StrlenInvalidPrintedAlready) { Printf(PRINT_BOLD, "Warning: ACS function strlen called with invalid string argument.\n"); StrlenInvalidPrintedAlready = true; } STACK(1) = 0; } break; case PCD_GETCVAR: STACK(1) = DoGetCVar(GetCVar(activator && activator->player? int(activator->player - players) : -1, FBehavior::StaticLookupString(STACK(1))), false); break; case PCD_SETHUDSIZE: hudwidth = abs (STACK(3)); hudheight = abs (STACK(2)); if (STACK(1) != 0) { // Negative height means to cover the status bar hudheight = -hudheight; } sp -= 3; break; case PCD_GETLEVELINFO: switch (STACK(1)) { case LEVELINFO_PAR_TIME: STACK(1) = level.partime; break; case LEVELINFO_SUCK_TIME: STACK(1) = level.sucktime; break; case LEVELINFO_CLUSTERNUM: STACK(1) = level.cluster; break; case LEVELINFO_LEVELNUM: STACK(1) = level.levelnum; break; case LEVELINFO_TOTAL_SECRETS: STACK(1) = level.total_secrets; break; case LEVELINFO_FOUND_SECRETS: STACK(1) = level.found_secrets; break; case LEVELINFO_TOTAL_ITEMS: STACK(1) = level.total_items; break; case LEVELINFO_FOUND_ITEMS: STACK(1) = level.found_items; break; case LEVELINFO_TOTAL_MONSTERS: STACK(1) = level.total_monsters; break; case LEVELINFO_KILLED_MONSTERS: STACK(1) = level.killed_monsters; break; default: STACK(1) = 0; break; } break; case PCD_CHANGESKY: { const char *sky1name, *sky2name; sky1name = FBehavior::StaticLookupString (STACK(2)); sky2name = FBehavior::StaticLookupString (STACK(1)); if (sky1name[0] != 0) { sky1texture = level.skytexture1 = TexMan.GetTexture (sky1name, ETextureType::Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_ReturnFirst); } if (sky2name[0] != 0) { sky2texture = level.skytexture2 = TexMan.GetTexture (sky2name, ETextureType::Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_ReturnFirst); } R_InitSkyMap (); sp -= 2; } break; case PCD_SETCAMERATOTEXTURE: { const char *picname = FBehavior::StaticLookupString (STACK(2)); AActor *camera; if (STACK(3) == 0) { camera = activator; } else { FActorIterator it (STACK(3)); camera = it.Next (); } if (camera != NULL) { FTextureID picnum = TexMan.CheckForTexture (picname, ETextureType::Wall, FTextureManager::TEXMAN_Overridable); if (!picnum.Exists()) { Printf ("SetCameraToTexture: %s is not a texture\n", picname); } else { FCanvasTextureInfo::Add (camera, picnum, STACK(1)); } } sp -= 3; } break; case PCD_SETACTORANGLE: // [GRB] SetActorAngle(activator, STACK(2), STACK(1), false); sp -= 2; break; case PCD_SETACTORPITCH: SetActorPitch(activator, STACK(2), STACK(1), false); sp -= 2; break; case PCD_SETACTORSTATE: { const char *statename = FBehavior::StaticLookupString (STACK(2)); FState *state; if (STACK(3) == 0) { if (activator != NULL) { state = activator->GetClass()->FindStateByString (statename, !!STACK(1)); if (state != NULL) { activator->SetState (state); STACK(3) = 1; } else { STACK(3) = 0; } } } else { FActorIterator iterator (STACK(3)); AActor *actor; int count = 0; while ( (actor = iterator.Next ()) ) { state = actor->GetClass()->FindStateByString (statename, !!STACK(1)); if (state != NULL) { actor->SetState (state); count++; } } STACK(3) = count; } sp -= 2; } break; case PCD_PLAYERCLASS: // [GRB] if (STACK(1) < 0 || STACK(1) >= MAXPLAYERS || !playeringame[STACK(1)]) { STACK(1) = -1; } else { STACK(1) = players[STACK(1)].CurrentPlayerClass; } break; case PCD_GETPLAYERINFO: // [GRB] if (STACK(2) < 0 || STACK(2) >= MAXPLAYERS || !playeringame[STACK(2)]) { STACK(2) = -1; } else { player_t *pl = &players[STACK(2)]; userinfo_t *userinfo = &pl->userinfo; switch (STACK(1)) { case PLAYERINFO_TEAM: STACK(2) = userinfo->GetTeam(); break; case PLAYERINFO_AIMDIST: STACK(2) = (int32_t)(userinfo->GetAimDist() * (0x40000000/90.)); break; // Yes, this has been returning a BAM since its creation. case PLAYERINFO_COLOR: STACK(2) = userinfo->GetColor(); break; case PLAYERINFO_GENDER: STACK(2) = userinfo->GetGender(); break; case PLAYERINFO_NEVERSWITCH: STACK(2) = userinfo->GetNeverSwitch(); break; case PLAYERINFO_MOVEBOB: STACK(2) = DoubleToACS(userinfo->GetMoveBob()); break; case PLAYERINFO_STILLBOB: STACK(2) = DoubleToACS(userinfo->GetStillBob()); break; case PLAYERINFO_PLAYERCLASS: STACK(2) = userinfo->GetPlayerClassNum(); break; case PLAYERINFO_DESIREDFOV: STACK(2) = (int)pl->DesiredFOV; break; case PLAYERINFO_FOV: STACK(2) = (int)pl->FOV; break; default: STACK(2) = 0; break; } } sp -= 1; break; case PCD_CHANGELEVEL: { G_ChangeLevel(FBehavior::StaticLookupString(STACK(4)), STACK(3), STACK(2), STACK(1)); sp -= 4; } break; case PCD_SECTORDAMAGE: { int tag = STACK(5); int amount = STACK(4); FName type = FBehavior::StaticLookupString(STACK(3)); FName protection = FName (FBehavior::StaticLookupString(STACK(2)), true); PClassActor *protectClass = PClass::FindActor (protection); int flags = STACK(1); sp -= 5; P_SectorDamage(tag, amount, type, protectClass, flags); } break; case PCD_THINGDAMAGE2: STACK(3) = P_Thing_Damage (STACK(3), activator, STACK(2), FName(FBehavior::StaticLookupString(STACK(1)))); sp -= 2; break; case PCD_CHECKACTORCEILINGTEXTURE: STACK(2) = DoCheckActorTexture(STACK(2), activator, STACK(1), false); sp--; break; case PCD_CHECKACTORFLOORTEXTURE: STACK(2) = DoCheckActorTexture(STACK(2), activator, STACK(1), true); sp--; break; case PCD_GETACTORLIGHTLEVEL: { AActor *actor = SingleActorFromTID(STACK(1), activator); if (actor != NULL) { sector_t *sector = actor->Sector; if (sector->e->XFloor.lightlist.Size()) { unsigned i; TArray &lightlist = sector->e->XFloor.lightlist; STACK(1) = *lightlist.Last().p_lightlevel; for (i = 1; i < lightlist.Size(); i++) { if (lightlist[i].plane.ZatPoint(actor) <= actor->Z()) { STACK(1) = *lightlist[i - 1].p_lightlevel; break; } } } else { STACK(1) = actor->Sector->lightlevel; } } else STACK(1) = 0; break; } case PCD_SETMUGSHOTSTATE: if (!multiplayer || (activator != nullptr && activator->CheckLocalView())) { StatusBar->SetMugShotState(FBehavior::StaticLookupString(STACK(1))); } sp--; break; case PCD_CHECKPLAYERCAMERA: { int playernum = STACK(1); if (playernum < 0 || playernum >= MAXPLAYERS || !playeringame[playernum] || players[playernum].camera == NULL || players[playernum].camera->player != NULL) { STACK(1) = -1; } else { STACK(1) = players[playernum].camera->tid; } } break; case PCD_CLASSIFYACTOR: STACK(1) = DoClassifyActor(STACK(1)); break; case PCD_MORPHACTOR: { int tag = STACK(7); FName playerclass_name = FBehavior::StaticLookupString(STACK(6)); auto playerclass = PClass::FindActor (playerclass_name); FName monsterclass_name = FBehavior::StaticLookupString(STACK(5)); PClassActor *monsterclass = PClass::FindActor(monsterclass_name); int duration = STACK(4); int style = STACK(3); FName morphflash_name = FBehavior::StaticLookupString(STACK(2)); PClassActor *morphflash = PClass::FindActor(morphflash_name); FName unmorphflash_name = FBehavior::StaticLookupString(STACK(1)); PClassActor *unmorphflash = PClass::FindActor(unmorphflash_name); int changes = 0; if (tag == 0) { changes = P_MorphActor(activator, activator, playerclass, monsterclass, duration, style, morphflash, unmorphflash); } else { FActorIterator iterator (tag); AActor *actor; while ( (actor = iterator.Next ()) ) { changes += P_MorphActor(activator, actor, playerclass, monsterclass, duration, style, morphflash, unmorphflash); } } STACK(7) = changes; sp -= 6; } break; case PCD_UNMORPHACTOR: { int tag = STACK(2); bool force = !!STACK(1); int changes = 0; if (tag == 0) { changes += P_UnmorphActor(activator, activator, 0, force); } else { FActorIterator iterator (tag); AActor *actor; while ( (actor = iterator.Next ()) ) { changes += P_UnmorphActor(activator, actor, 0, force); } } STACK(2) = changes; sp -= 1; } break; case PCD_SAVESTRING: // Saves the string { const int str = GlobalACSStrings.AddString(work); PushToStack(str); STRINGBUILDER_FINISH(work); } break; case PCD_STRCPYTOSCRIPTCHRANGE: case PCD_STRCPYTOMAPCHRANGE: case PCD_STRCPYTOWORLDCHRANGE: case PCD_STRCPYTOGLOBALCHRANGE: // source: stringid(2); stringoffset(1) // destination: capacity (3); stringoffset(4); arrayid (5); offset(6) { int index = STACK(4); int capacity = STACK(3); if (index < 0 || STACK(1) < 0) { // no writable destination, or negative offset to source string sp -= 5; Stack[sp-1] = 0; // false break; } index += STACK(6); lookup = FBehavior::StaticLookupString (STACK(2)); if (!lookup) { // no data, operation complete STRCPYTORANGECOMPLETE: sp -= 5; Stack[sp-1] = 1; // true break; } for (int i = 0; i < STACK(1); i++) { if (! (*(lookup++))) { // no data, operation complete goto STRCPYTORANGECOMPLETE; } } switch (pcd) { case PCD_STRCPYTOSCRIPTCHRANGE: { int a = STACK(5); while (capacity-- > 0) { localarrays->Set(locals, a, index++, *lookup); if (! (*(lookup++))) goto STRCPYTORANGECOMPLETE; // complete with terminating 0 } Stack[sp-6] = !(*lookup); // true/success if only terminating 0 was not copied } break; case PCD_STRCPYTOMAPCHRANGE: { int a = STACK(5); if (a < NUM_MAPVARS && a > 0 && activeBehavior->MapVars[a]) { Stack[sp-6] = activeBehavior->CopyStringToArray(*(activeBehavior->MapVars[a]), index, capacity, lookup); } } break; case PCD_STRCPYTOWORLDCHRANGE: { int a = STACK(5); while (capacity-- > 0) { ACS_WorldArrays[a][index++] = *lookup; if (! (*(lookup++))) goto STRCPYTORANGECOMPLETE; // complete with terminating 0 } Stack[sp-6] = !(*lookup); // true/success if only terminating 0 was not copied } break; case PCD_STRCPYTOGLOBALCHRANGE: { int a = STACK(5); while (capacity-- > 0) { ACS_GlobalArrays[a][index++] = *lookup; if (! (*(lookup++))) goto STRCPYTORANGECOMPLETE; // complete with terminating 0 } Stack[sp-6] = !(*lookup); // true/success if only terminating 0 was not copied } break; } sp -= 5; } break; case PCD_CONSOLECOMMAND: case PCD_CONSOLECOMMANDDIRECT: Printf (TEXTCOLOR_RED GAMENAME " doesn't support execution of console commands from scripts\n"); if (pcd == PCD_CONSOLECOMMAND) sp -= 3; else pc += 3; break; } } if (runaway != 0 && InModuleScriptNumber >= 0) { auto scriptptr = activeBehavior->GetScriptPtr(InModuleScriptNumber); if (scriptptr != nullptr) { scriptptr->ProfileData.AddRun(runaway); } else { // It is pointless to continue execution. The script is broken and needs to be aborted. I_Error("Bad script definition encountered. Script %d is reported running but not present.\nThe most likely cause for this message is using 'delay' inside a function which is not supported.\nPlease check the ACS compiler used for compiling the script!", InModuleScriptNumber); } } if (state == SCRIPT_DivideBy0) { Printf ("Divide by zero in %s\n", ScriptPresentation(script).GetChars()); state = SCRIPT_PleaseRemove; } else if (state == SCRIPT_ModulusBy0) { Printf ("Modulus by zero in %s\n", ScriptPresentation(script).GetChars()); state = SCRIPT_PleaseRemove; } if (state == SCRIPT_PleaseRemove) { Unlink (); DLevelScript **running; if ((running = controller->RunningScripts.CheckKey(script)) != NULL && *running == this) { controller->RunningScripts.Remove(script); } } else { this->pc = pc; assert (sp == 0); } return resultValue; } #undef PushtoStack static DLevelScript *P_GetScriptGoing (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module, const int *args, int argcount, int flags) { DACSThinker *controller = DACSThinker::ActiveThinker; DLevelScript **running; if (controller && !(flags & ACS_ALWAYS) && (running = controller->RunningScripts.CheckKey(num)) != NULL) { if ((*running)->GetState() == DLevelScript::SCRIPT_Suspended) { (*running)->SetState(DLevelScript::SCRIPT_Running); return *running; } return NULL; } return Create (who, where, num, code, module, args, argcount, flags); } DLevelScript::DLevelScript (AActor *who, line_t *where, int num, const ScriptPtr *code, FBehavior *module, const int *args, int argcount, int flags) : activeBehavior (module) { if (DACSThinker::ActiveThinker == NULL) Create(); script = num; assert(code->VarCount >= code->ArgCount); Localvars.Resize(code->VarCount); memset(&Localvars[0], 0, code->VarCount * sizeof(int32_t)); for (int i = 0; i < MIN(argcount, code->ArgCount); ++i) { Localvars[i] = args[i]; } pc = module->GetScriptAddress(code); InModuleScriptNumber = module->GetScriptIndex(code); activator = who; activationline = where; backSide = flags & ACS_BACKSIDE; activefont = SmallFont; hudwidth = hudheight = 0; ClipRectLeft = ClipRectTop = ClipRectWidth = ClipRectHeight = WrapWidth = 0; HandleAspect = true; state = SCRIPT_Running; // Hexen waited one second before executing any open scripts. I didn't realize // this when I wrote my ACS implementation. Now that I know, it's still best to // run them right away because there are several map properties that can't be // set in an editor. If an open script sets them, it looks dumb if a second // goes by while they're in their default state. if (!(flags & ACS_ALWAYS)) DACSThinker::ActiveThinker->RunningScripts[num] = this; Link(); if (level.flags2 & LEVEL2_HEXENHACK) { PutLast(); } DPrintf(DMSG_SPAMMY, "%s started.\n", ScriptPresentation(num).GetChars()); } static void SetScriptState (int script, DLevelScript::EScriptState state) { DACSThinker *controller = DACSThinker::ActiveThinker; DLevelScript **running; if (controller != NULL && (running = controller->RunningScripts.CheckKey(script)) != NULL) { (*running)->SetState (state); } } void P_DoDeferedScripts () { const ScriptPtr *scriptdata; FBehavior *module; // Handle defered scripts in this step, too for(int i = level.info->deferred.Size()-1; i>=0; i--) { acsdefered_t *def = &level.info->deferred[i]; switch (def->type) { case acsdefered_t::defexecute: case acsdefered_t::defexealways: scriptdata = FBehavior::StaticFindScript (def->script, module); if (scriptdata) { P_GetScriptGoing ((unsigned)def->playernum < MAXPLAYERS && playeringame[def->playernum] ? players[def->playernum].mo : NULL, NULL, def->script, scriptdata, module, def->args, 3, def->type == acsdefered_t::defexealways ? ACS_ALWAYS : 0); } else { Printf ("P_DoDeferredScripts: Unknown %s\n", ScriptPresentation(def->script).GetChars()); } break; case acsdefered_t::defsuspend: SetScriptState (def->script, DLevelScript::SCRIPT_Suspended); DPrintf (DMSG_SPAMMY, "Deferred suspend of %s\n", ScriptPresentation(def->script).GetChars()); break; case acsdefered_t::defterminate: SetScriptState (def->script, DLevelScript::SCRIPT_PleaseRemove); DPrintf (DMSG_SPAMMY, "Deferred terminate of %s\n", ScriptPresentation(def->script).GetChars()); break; } } level.info->deferred.Clear(); } static void addDefered (level_info_t *i, acsdefered_t::EType type, int script, const int *args, int argcount, AActor *who) { if (i) { acsdefered_t &def = i->deferred[i->deferred.Reserve(1)]; int j; def.type = type; def.script = script; for (j = 0; (size_t)j < countof(def.args) && j < argcount; ++j) { def.args[j] = args[j]; } while ((size_t)j < countof(def.args)) { def.args[j++] = 0; } if (who != NULL && who->player != NULL) { def.playernum = int(who->player - players); } else { def.playernum = -1; } DPrintf (DMSG_SPAMMY, "%s on map %s deferred\n", ScriptPresentation(script).GetChars(), i->MapName.GetChars()); } } EXTERN_CVAR (Bool, sv_cheats) int P_StartScript (AActor *who, line_t *where, int script, const char *map, const int *args, int argcount, int flags) { if (map == NULL || 0 == strnicmp (level.MapName, map, 8)) { FBehavior *module = NULL; const ScriptPtr *scriptdata; if ((scriptdata = FBehavior::StaticFindScript (script, module)) != NULL) { if ((flags & ACS_NET) && netgame && !sv_cheats) { // If playing multiplayer and cheats are disallowed, check to // make sure only net scripts are run. if (!(scriptdata->Flags & SCRIPTF_Net)) { Printf(PRINT_BOLD, "%s tried to puke %s (\n", who->player->userinfo.GetName(), ScriptPresentation(script).GetChars()); for (int i = 0; i < argcount; ++i) { Printf(PRINT_BOLD, "%d%s", args[i], i == argcount-1 ? "" : ", "); } Printf(PRINT_BOLD, ")\n"); return false; } } DLevelScript *runningScript = P_GetScriptGoing (who, where, script, scriptdata, module, args, argcount, flags); if (runningScript != NULL) { if (flags & ACS_WANTRESULT) { return runningScript->RunScript(); } return true; } return false; } else { if (!(flags & ACS_NET) || (who && who->player == &players[consoleplayer])) { Printf("P_StartScript: Unknown %s\n", ScriptPresentation(script).GetChars()); } } } else { addDefered (FindLevelInfo (map), (flags & ACS_ALWAYS) ? acsdefered_t::defexealways : acsdefered_t::defexecute, script, args, argcount, who); return true; } return false; } void P_SuspendScript (int script, const char *map) { if (strnicmp (level.MapName, map, 8)) addDefered (FindLevelInfo (map), acsdefered_t::defsuspend, script, NULL, 0, NULL); else SetScriptState (script, DLevelScript::SCRIPT_Suspended); } void P_TerminateScript (int script, const char *map) { if (strnicmp (level.MapName, map, 8)) addDefered (FindLevelInfo (map), acsdefered_t::defterminate, script, NULL, 0, NULL); else SetScriptState (script, DLevelScript::SCRIPT_PleaseRemove); } FSerializer &Serialize(FSerializer &arc, const char *key, acsdefered_t &defer, acsdefered_t *def) { if (arc.BeginObject(key)) { arc.Enum("type", defer.type) .ScriptNum("script", defer.script) .Array("args", defer.args, 3) ("player", defer.playernum) .EndObject(); } return arc; } CCMD (scriptstat) { if (DACSThinker::ActiveThinker == NULL) { Printf ("No scripts are running.\n"); } else { DACSThinker::ActiveThinker->DumpScriptStatus (); } } void DACSThinker::DumpScriptStatus () { static const char *stateNames[] = { "Running", "Suspended", "Delayed", "TagWait", "PolyWait", "ScriptWaitPre", "ScriptWait", "PleaseRemove" }; DLevelScript *script = Scripts; while (script != NULL) { Printf("%s: %s\n", ScriptPresentation(script->script).GetChars(), stateNames[script->state]); script = script->next; } } // Profiling support -------------------------------------------------------- ACSProfileInfo::ACSProfileInfo() { Reset(); } void ACSProfileInfo::Reset() { TotalInstr = 0; NumRuns = 0; MinInstrPerRun = UINT_MAX; MaxInstrPerRun = 0; } void ACSProfileInfo::AddRun(unsigned int num_instr) { TotalInstr += num_instr; NumRuns++; if (num_instr < MinInstrPerRun) { MinInstrPerRun = num_instr; } if (num_instr > MaxInstrPerRun) { MaxInstrPerRun = num_instr; } } void ArrangeScriptProfiles(TArray &profiles) { for (unsigned int mod_num = 0; mod_num < FBehavior::StaticModules.Size(); ++mod_num) { FBehavior *module = FBehavior::StaticModules[mod_num]; ProfileCollector prof; prof.Module = module; for (int i = 0; i < module->NumScripts; ++i) { prof.Index = i; prof.ProfileData = &module->Scripts[i].ProfileData; profiles.Push(prof); } } } void ArrangeFunctionProfiles(TArray &profiles) { for (unsigned int mod_num = 0; mod_num < FBehavior::StaticModules.Size(); ++mod_num) { FBehavior *module = FBehavior::StaticModules[mod_num]; ProfileCollector prof; prof.Module = module; for (int i = 0; i < module->NumFunctions; ++i) { ScriptFunction *func = (ScriptFunction *)module->Functions + i; if (func->ImportNum == 0) { prof.Index = i; prof.ProfileData = module->FunctionProfileData + i; profiles.Push(prof); } } } } void ClearProfiles(TArray &profiles) { for (unsigned int i = 0; i < profiles.Size(); ++i) { profiles[i].ProfileData->Reset(); } } static int sort_by_total_instr(const void *a_, const void *b_) { const ProfileCollector *a = (const ProfileCollector *)a_; const ProfileCollector *b = (const ProfileCollector *)b_; assert(a != NULL && a->ProfileData != NULL); assert(b != NULL && b->ProfileData != NULL); return (int)(b->ProfileData->TotalInstr - a->ProfileData->TotalInstr); } static int sort_by_min(const void *a_, const void *b_) { const ProfileCollector *a = (const ProfileCollector *)a_; const ProfileCollector *b = (const ProfileCollector *)b_; return b->ProfileData->MinInstrPerRun - a->ProfileData->MinInstrPerRun; } static int sort_by_max(const void *a_, const void *b_) { const ProfileCollector *a = (const ProfileCollector *)a_; const ProfileCollector *b = (const ProfileCollector *)b_; return b->ProfileData->MaxInstrPerRun - a->ProfileData->MaxInstrPerRun; } static int sort_by_avg(const void *a_, const void *b_) { const ProfileCollector *a = (const ProfileCollector *)a_; const ProfileCollector *b = (const ProfileCollector *)b_; int a_avg = a->ProfileData->NumRuns == 0 ? 0 : int(a->ProfileData->TotalInstr / a->ProfileData->NumRuns); int b_avg = b->ProfileData->NumRuns == 0 ? 0 : int(b->ProfileData->TotalInstr / b->ProfileData->NumRuns); return b_avg - a_avg; } static int sort_by_runs(const void *a_, const void *b_) { const ProfileCollector *a = (const ProfileCollector *)a_; const ProfileCollector *b = (const ProfileCollector *)b_; return b->ProfileData->NumRuns - a->ProfileData->NumRuns; } static void ShowProfileData(TArray &profiles, long ilimit, int (*sorter)(const void *, const void *), bool functions) { static const char *const typelabels[2] = { "script", "function" }; if (profiles.Size() == 0) { return; } unsigned int limit; char modname[13]; char scriptname[21]; qsort(&profiles[0], profiles.Size(), sizeof(ProfileCollector), sorter); if (ilimit > 0) { Printf(TEXTCOLOR_ORANGE "Top %ld %ss:\n", ilimit, typelabels[functions]); limit = (unsigned int)ilimit; } else { Printf(TEXTCOLOR_ORANGE "All %ss:\n", typelabels[functions]); limit = UINT_MAX; } Printf(TEXTCOLOR_YELLOW "Module %-20s Total Runs Avg Min Max\n", typelabels[functions]); Printf(TEXTCOLOR_YELLOW "------------ -------------------- ---------- ------- ------- ------- -------\n"); for (unsigned int i = 0; i < limit && i < profiles.Size(); ++i) { ProfileCollector *prof = &profiles[i]; if (prof->ProfileData->NumRuns == 0) { // Don't list ones that haven't run. continue; } // Module name mysnprintf(modname, sizeof(modname), "%s", prof->Module->GetModuleName()); // Script/function name if (functions) { uint32_t *fnames = (uint32_t *)prof->Module->FindChunk(MAKE_ID('F','N','A','M')); if (prof->Index >= 0 && prof->Index < (int)LittleLong(fnames[2])) { mysnprintf(scriptname, sizeof(scriptname), "%s", (char *)(fnames + 2) + LittleLong(fnames[3+prof->Index])); } else { mysnprintf(scriptname, sizeof(scriptname), "Function %d", prof->Index); } } else { mysnprintf(scriptname, sizeof(scriptname), "%s", ScriptPresentation(prof->Module->GetScriptPtr(prof->Index)->Number).GetChars() + 7); } Printf("%-12s %-20s%11llu%8u%8u%8u%8u\n", modname, scriptname, prof->ProfileData->TotalInstr, prof->ProfileData->NumRuns, unsigned(prof->ProfileData->TotalInstr / prof->ProfileData->NumRuns), prof->ProfileData->MinInstrPerRun, prof->ProfileData->MaxInstrPerRun ); } } CCMD(acsprofile) { static int (*sort_funcs[])(const void*, const void *) = { sort_by_total_instr, sort_by_min, sort_by_max, sort_by_avg, sort_by_runs }; static const char *sort_names[] = { "total", "min", "max", "avg", "runs" }; static const uint8_t sort_match_len[] = { 1, 2, 2, 1, 1 }; TArray ScriptProfiles, FuncProfiles; long limit = 10; int (*sorter)(const void *, const void *) = sort_by_total_instr; assert(countof(sort_names) == countof(sort_match_len)); ArrangeScriptProfiles(ScriptProfiles); ArrangeFunctionProfiles(FuncProfiles); if (argv.argc() > 1) { // `acsprofile clear` will zero all profiling information collected so far. if (stricmp(argv[1], "clear") == 0) { ClearProfiles(ScriptProfiles); ClearProfiles(FuncProfiles); return; } for (int i = 1; i < argv.argc(); ++i) { // If it's a number, set the display limit. char *endptr; long num = strtol(argv[i], &endptr, 0); if (endptr != argv[i]) { limit = num; continue; } // If it's a name, set the sort method. We accept partial matches for // options that are shorter than the sort name. size_t optlen = strlen(argv[i]); unsigned int j; for (j = 0; j < countof(sort_names); ++j) { if (optlen < sort_match_len[j] || optlen > strlen(sort_names[j])) { // Too short or long to match. continue; } if (strnicmp(argv[i], sort_names[j], optlen) == 0) { sorter = sort_funcs[j]; break; } } if (j == countof(sort_names)) { Printf("Unknown option '%s'\n", argv[i]); Printf("acsprofile clear : Reset profiling information\n"); Printf("acsprofile [total|min|max|avg|runs] []\n"); return; } } } ShowProfileData(ScriptProfiles, limit, sorter, false); ShowProfileData(FuncProfiles, limit, sorter, true); } ADD_STAT(ACS) { return FStringf("ACS time: %f ms", ACSTime.TimeMS()); }