/* ** 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 "p_spec.h" #include "g_level.h" #include "s_sound.h" #include "p_acs.h" #include "p_saveg.h" #include "p_lnspec.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 "i_movie.h" #include "sbar.h" #include "m_swap.h" #include "a_sharedglobal.h" #include "a_doomglobal.h" #include "a_strifeglobal.h" #include "v_video.h" #include "w_wad.h" #include "r_sky.h" #include "gstrings.h" #include "gi.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 "farchive.h" #include "g_shared/a_pickups.h" 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 HUDMSG_LOG (0x80000000) #define HUDMSG_COLORSTRING (0x40000000) #define LANGREGIONMASK MAKE_ID(0,0,0xff,0xff) // Flags for ReplaceTextures #define NOT_BOTTOM 1 #define NOT_MIDDLE 2 #define NOT_TOP 4 #define NOT_FLOOR 8 #define NOT_CEILING 16 // Flags for SectorDamage #define DAMAGE_PLAYERS 1 #define DAMAGE_NONPLAYERS 2 #define DAMAGE_IN_AIR 4 #define DAMAGE_SUBCLASSES_PROTECT 8 struct CallReturn { CallReturn(int pc, ScriptFunction *func, FBehavior *module, SDWORD *locals, bool discard, FString &str) : ReturnFunction(func), ReturnModule(module), ReturnLocals(locals), ReturnAddress(pc), bDiscardResult(discard), StringBuilder(str) {} ScriptFunction *ReturnFunction; FBehavior *ReturnModule; SDWORD *ReturnLocals; int ReturnAddress; int bDiscardResult; FString StringBuilder; }; 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 { DWORD ArraySize; SDWORD *Elements; }; TArray FBehavior::StaticModules; //============================================================================ // // Global and world variables // //============================================================================ // ACS variables with world scope SDWORD ACS_WorldVars[NUM_WORLDVARS]; FWorldGlobalArray ACS_WorldArrays[NUM_WORLDVARS]; // ACS variables with global scope SDWORD ACS_GlobalVars[NUM_GLOBALVARS]; FWorldGlobalArray ACS_GlobalArrays[NUM_GLOBALVARS]; //============================================================================ // // On the fly strings // //============================================================================ #define LIB_ACSSTRINGS_ONTHEFLY 0x7fff #define ACSSTRING_OR_ONTHEFLY (LIB_ACSSTRINGS_ONTHEFLY<<16) TArray ACS_StringsOnTheFly, ACS_StringBuilderStack; #define STRINGBUILDER_START(Builder) if (*Builder.GetChars() || ACS_StringBuilderStack.Size()) { ACS_StringBuilderStack.Push(Builder); Builder = ""; } #define STRINGBUILDER_FINISH(Builder) if (!ACS_StringBuilderStack.Pop(Builder)) Builder = ""; //============================================================================ // // 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; } //============================================================================ // // // //============================================================================ void P_ClearACSVars(bool alsoglobal) { int i; memset (ACS_WorldVars, 0, sizeof(ACS_WorldVars)); for (i = 0; i < NUM_WORLDVARS; ++i) { ACS_WorldArrays[i].Clear (); } if (alsoglobal) { memset (ACS_GlobalVars, 0, sizeof(ACS_GlobalVars)); for (i = 0; i < NUM_GLOBALVARS; ++i) { ACS_GlobalArrays[i].Clear (); } } } //============================================================================ // // // //============================================================================ static void WriteVars (FILE *file, SDWORD *vars, size_t count, DWORD id) { 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; } FPNGChunkArchive arc (file, id); for (i = 0; i <= j; ++i) { DWORD var = vars[i]; arc << var; } } } //============================================================================ // // // //============================================================================ static void ReadVars (PNGHandle *png, SDWORD *vars, size_t count, DWORD id) { size_t len = M_FindPNGChunk (png, id); size_t used = 0; if (len != 0) { DWORD var; size_t i; FPNGChunkArchive arc (png->File->GetFile(), id, len); used = len / 4; for (i = 0; i < used; ++i) { arc << var; vars[i] = var; } png->File->ResetFilePtr(); } if (used < count) { memset (&vars[used], 0, (count-used)*4); } } //============================================================================ // // // //============================================================================ static void WriteArrayVars (FILE *file, FWorldGlobalArray *vars, unsigned int count, DWORD id) { unsigned int i, j; // Find the first non-empty array. for (i = 0; i < count; ++i) { if (vars[i].CountUsed() != 0) break; } if (i < count) { // Find last non-empty array. Anything beyond the last stored array // will be emptied at load time. for (j = count-1; j > i; --j) { if (vars[j].CountUsed() != 0) break; } FPNGChunkArchive arc (file, id); arc.WriteCount (i); arc.WriteCount (j); for (; i <= j; ++i) { arc.WriteCount (vars[i].CountUsed()); FWorldGlobalArray::ConstIterator it(vars[i]); const FWorldGlobalArray::Pair *pair; while (it.NextPair (pair)) { arc.WriteCount (pair->Key); arc.WriteCount (pair->Value); } } } } //============================================================================ // // // //============================================================================ static void ReadArrayVars (PNGHandle *png, FWorldGlobalArray *vars, size_t count, DWORD id) { size_t len = M_FindPNGChunk (png, id); unsigned int i, k; for (i = 0; i < count; ++i) { vars[i].Clear (); } if (len != 0) { DWORD max, size; FPNGChunkArchive arc (png->File->GetFile(), id, len); i = arc.ReadCount (); max = arc.ReadCount (); for (; i <= max; ++i) { size = arc.ReadCount (); for (k = 0; k < size; ++k) { SDWORD key, val; key = arc.ReadCount(); val = arc.ReadCount(); vars[i].Insert (key, val); } } png->File->ResetFilePtr(); } } //============================================================================ // // // //============================================================================ void P_ReadACSVars(PNGHandle *png) { ReadVars (png, ACS_WorldVars, NUM_WORLDVARS, MAKE_ID('w','v','A','r')); ReadVars (png, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); ReadArrayVars (png, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); ReadArrayVars (png, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); } //============================================================================ // // // //============================================================================ void P_WriteACSVars(FILE *stdfile) { WriteVars (stdfile, ACS_WorldVars, NUM_WORLDVARS, MAKE_ID('w','v','A','r')); WriteVars (stdfile, ACS_GlobalVars, NUM_GLOBALVARS, MAKE_ID('g','v','A','r')); WriteArrayVars (stdfile, ACS_WorldArrays, NUM_WORLDVARS, MAKE_ID('w','a','R','r')); WriteArrayVars (stdfile, ACS_GlobalArrays, NUM_GLOBALVARS, MAKE_ID('g','a','R','r')); } //---- Inventory functions --------------------------------------// // //============================================================================ // // DoClearInv // // Clears the inventory of a single actor. // //============================================================================ static void DoClearInv (AActor *actor) { // In case destroying an inventory item causes another to be destroyed // (e.g. Weapons destroy their sisters), keep track of the pointer to // the next inventory item rather than the next inventory item itself. // For example, if a weapon is immediately followed by its sister, the // next weapon we had tracked would be to the sister, so it is now // invalid and we won't be able to find the complete inventory by // following it. // // When we destroy an item, we leave invp alone, since the destruction // process will leave it pointing to the next item we want to check. If // we don't destroy an item, then we move invp to point to its Inventory // pointer. // // It should be safe to assume that an item being destroyed will only // destroy items further down in the chain, because if it was going to // destroy something we already processed, we've already destroyed it, // so it won't have anything to destroy. AInventory **invp = &actor->Inventory; while (*invp != NULL) { AInventory *inv = *invp; if (!(inv->ItemFlags & IF_UNDROPPABLE)) { // For the sake of undroppable weapons, never remove ammo once // it has been acquired; just set its amount to 0. if (inv->IsKindOf(RUNTIME_CLASS(AAmmo))) { AAmmo *ammo = static_cast(inv); ammo->Amount = 0; invp = &inv->Inventory; } else { inv->Destroy (); } } else if (inv->GetClass() == RUNTIME_CLASS(AHexenArmor)) { AHexenArmor *harmor = static_cast (inv); harmor->Slots[3] = harmor->Slots[2] = harmor->Slots[1] = harmor->Slots[0] = 0; invp = &inv->Inventory; } else { invp = &inv->Inventory; } } if (actor->player != NULL) { actor->player->ReadyWeapon = NULL; actor->player->PendingWeapon = WP_NOCHANGE; actor->player->psprites[ps_weapon].state = NULL; actor->player->psprites[ps_flash].state = NULL; } } //============================================================================ // // ClearInventory // // Clears the inventory for one or more actors. // //============================================================================ static void ClearInventory (AActor *activator) { if (activator == NULL) { for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i]) DoClearInv (players[i].mo); } } else { DoClearInv (activator); } } //============================================================================ // // DoGiveInv // // Gives an item to a single actor. // //============================================================================ static void DoGiveInv (AActor *actor, const PClass *info, int amount) { AWeapon *savedPendingWeap = actor->player != NULL ? actor->player->PendingWeapon : NULL; bool hadweap = actor->player != NULL ? actor->player->ReadyWeapon != NULL : true; AInventory *item = static_cast(Spawn (info, 0,0,0, NO_REPLACE)); // This shouldn't count for the item statistics! item->ClearCounters(); if (info->IsDescendantOf (RUNTIME_CLASS(ABasicArmorPickup))) { if (static_cast(item)->SaveAmount != 0) { static_cast(item)->SaveAmount *= amount; } else { static_cast(item)->SaveAmount *= amount; } } else if (info->IsDescendantOf (RUNTIME_CLASS(ABasicArmorBonus))) { static_cast(item)->SaveAmount *= amount; } else { item->Amount = amount; } if (!item->CallTryPickup (actor)) { item->Destroy (); } // If the item was a weapon, don't bring it up automatically // unless the player was not already using a weapon. if (savedPendingWeap != NULL && hadweap && actor->player != NULL) { actor->player->PendingWeapon = savedPendingWeap; } } //============================================================================ // // GiveInventory // // Gives an item to one or more actors. // //============================================================================ static void GiveInventory (AActor *activator, const char *type, int amount) { const PClass *info; if (amount <= 0 || type == NULL) { return; } if (stricmp (type, "Armor") == 0) { type = "BasicArmorPickup"; } info = PClass::FindClass (type); if (info == NULL) { Printf ("ACS: I don't know what %s is.\n", type); } else if (!info->IsDescendantOf (RUNTIME_CLASS(AInventory))) { Printf ("ACS: %s is not an inventory item.\n", type); } else if (activator == NULL) { for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i]) DoGiveInv (players[i].mo, info, amount); } } else { DoGiveInv (activator, info, amount); } } //============================================================================ // // DoTakeInv // // Takes an item from a single actor. // //============================================================================ static void DoTakeInv (AActor *actor, const PClass *info, int amount) { AInventory *item = actor->FindInventory (info); if (item != NULL) { item->Amount -= amount; if (item->Amount <= 0) { // If it's not ammo or an internal armor, destroy it. // Ammo needs to stick around, even when it's zero for the benefit // of the weapons that use it and to maintain the maximum ammo // amounts a backpack might have given. // Armor shouldn't be removed because they only work properly when // they are the last items in the inventory. if (item->ItemFlags & IF_KEEPDEPLETED) { item->Amount = 0; } else { item->Destroy (); } } } } //============================================================================ // // TakeInventory // // Takes an item from one or more actors. // //============================================================================ static void TakeInventory (AActor *activator, const char *type, int amount) { const PClass *info; if (type == NULL) { return; } if (strcmp (type, "Armor") == 0) { type = "BasicArmor"; } if (amount <= 0) { return; } info = PClass::FindClass (type); if (info == NULL) { return; } if (activator == NULL) { for (int i = 0; i < MAXPLAYERS; ++i) { if (playeringame[i]) DoTakeInv (players[i].mo, info, amount); } } else { DoTakeInv (activator, info, amount); } } //============================================================================ // // DoUseInv // // Makes a single actor use an inventory item // //============================================================================ static bool DoUseInv (AActor *actor, const PClass *info) { AInventory *item = actor->FindInventory (info); if (item != NULL) { if (actor->player == NULL) { return actor->UseInventory(item); } else { int cheats; bool res; // Bypass CF_TOTALLYFROZEN cheats = actor->player->cheats; actor->player->cheats &= ~CF_TOTALLYFROZEN; res = actor->UseInventory(item); actor->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) { const PClass *info; int ret = 0; if (type == NULL) { return 0; } info = PClass::FindClass (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. // //============================================================================ static int CheckInventory (AActor *activator, const char *type) { if (activator == NULL || type == NULL) return 0; if (stricmp (type, "Armor") == 0) { type = "BasicArmor"; } else if (stricmp (type, "Health") == 0) { return activator->health; } const PClass *info = PClass::FindClass (type); AInventory *item = activator->FindInventory (info); return item ? item->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 (FArchive &arc); private: sector_t *Sector; fixed_t WatchD, LastD; int Special, Arg0, Arg1, Arg2, Arg3, Arg4; TObjPtr Activator; line_t *Line; bool LineSide; bool bCeiling; DPlaneWatcher() {} }; IMPLEMENT_POINTY_CLASS (DPlaneWatcher) DECLARE_POINTER (Activator) END_POINTERS 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), Arg0 (arg0), Arg1 (arg1), Arg2 (arg2), Arg3 (arg3), Arg4 (arg4), Activator (it), Line (line), LineSide (!!lineSide), bCeiling (ceiling) { int secnum; secnum = P_FindSectorFromTag (tag, -1); if (secnum >= 0) { secplane_t plane; Sector = §ors[secnum]; if (bCeiling) { plane = Sector->ceilingplane; } else { plane = Sector->floorplane; } LastD = plane.d; plane.ChangeHeight (height << FRACBITS); WatchD = plane.d; } else { Sector = NULL; WatchD = LastD = 0; } } void DPlaneWatcher::Serialize (FArchive &arc) { Super::Serialize (arc); arc << Special << Arg0 << Arg1 << Arg2 << Arg3 << Arg4 << Sector << bCeiling << WatchD << LastD << Activator << Line << LineSide << bCeiling; } void DPlaneWatcher::Tick () { if (Sector == NULL) { Destroy (); return; } fixed_t newd; if (bCeiling) { newd = Sector->ceilingplane.d; } else { newd = Sector->floorplane.d; } if ((LastD < WatchD && newd >= WatchD) || (LastD > WatchD && newd <= WatchD)) { P_ExecuteSpecial(Special, Line, Activator, LineSide, Arg0, Arg1, Arg2, Arg3, Arg4); 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 () { // When playing Strife, STRFHELP is always loaded. if (gameinfo.gametype == GAME_Strife) { StaticLoadModule (Wads.CheckNumForName ("STRFHELP", ns_acslibrary)); } // 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 ("Could not find autoloaded ACS library %s\n", sc.String); } } } } FBehavior *FBehavior::StaticLoadModule (int lumpnum, FileReader *fr, int len) { if (lumpnum == -1 && fr == NULL) return NULL; for (unsigned int i = 0; i < StaticModules.Size(); ++i) { if (StaticModules[i]->LumpNum == lumpnum) { return StaticModules[i]; } } return new FBehavior (lumpnum, fr, len); } 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::StaticSerializeModuleStates (FArchive &arc) { DWORD modnum; modnum = StaticModules.Size(); arc << modnum; if (modnum != StaticModules.Size()) { I_Error ("Level was saved with a different number of ACS modules."); } for (modnum = 0; modnum < StaticModules.Size(); ++modnum) { FBehavior *module = StaticModules[modnum]; if (arc.IsStoring()) { arc.WriteString (module->ModuleName); } else { char *modname = NULL; arc << modname; if (stricmp (modname, module->ModuleName) != 0) { delete[] modname; I_Error ("Level was saved with a different set of ACS modules."); } delete[] modname; } module->SerializeVars (arc); } } void FBehavior::SerializeVars (FArchive &arc) { SerializeVarSet (arc, MapVarStore, NUM_MAPVARS); for (int i = 0; i < NumArrays; ++i) { SerializeVarSet (arc, ArrayStore[i].Elements, ArrayStore[i].ArraySize); } } void FBehavior::SerializeVarSet (FArchive &arc, SDWORD *vars, int max) { SDWORD arcval; SDWORD first, last; if (arc.IsStoring ()) { // 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 arcval = 0; arc << arcval; return; } arcval = last - first + 1; arc << arcval; arcval = first; arc << arcval; while (first <= last) { arc << vars[first]; ++first; } } else { SDWORD truelast; memset (vars, 0, max*sizeof(*vars)); arc << last; if (last == 0) { return; } arc << first; last += first; truelast = last; if (last > max) { last = max; } while (first < last) { arc << vars[first]; ++first; } while (first < truelast) { arc << arcval; ++first; } } } FBehavior::FBehavior (int lumpnum, FileReader * fr, int len) { BYTE *object; int i; NumScripts = 0; NumFunctions = 0; NumArrays = 0; NumTotalArrays = 0; Scripts = NULL; Functions = NULL; Arrays = NULL; ArrayStore = NULL; Chunks = NULL; Data = NULL; Format = ACS_Unknown; LumpNum = lumpnum; memset (MapVarStore, 0, sizeof(MapVarStore)); ModuleName[0] = 0; // 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. LibraryID = StaticModules.Push (this) << 16; 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; } object = new BYTE[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; } 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; } if (fr == NULL) { Wads.GetLumpName (ModuleName, lumpnum); ModuleName[8] = 0; } else { strcpy(ModuleName, "BEHAVIOR"); } Data = object; DataSize = len; if (Format == ACS_Old) { DWORD dirofs = LittleLong(((DWORD *)object)[1]); DWORD pretag = ((DWORD *)(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(((DWORD *)(object + dirofs))[-2]); // Forget about the compatibility cruft at the end of the lump DataSize = LittleLong(((DWORD *)object)[1]) - 8; } } else { Chunks = object + LittleLong(((DWORD *)object)[1]); } LoadScriptsDirectory (); if (Format == ACS_Old) { StringTable = LittleLong(((DWORD *)Data)[1]); StringTable += LittleLong(((DWORD *)(Data + StringTable))[0]) * 12 + 4; UnescapeStringTable(Data + StringTable, Data, false); } else { UnencryptStrings (); BYTE *strings = FindChunk (MAKE_ID('S','T','R','L')); if (strings != NULL) { StringTable = DWORD(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) << 16; } else { DWORD *chunk; Functions = FindChunk (MAKE_ID('F','U','N','C')); if (Functions != NULL) { NumFunctions = LittleLong(((DWORD *)Functions)[1]) / 8; Functions += 8; } // Initialize this object's map variables memset (MapVarStore, 0, sizeof(MapVarStore)); chunk = (DWORD *)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 = (DWORD *)NextChunk ((BYTE *)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 = (DWORD *)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 SDWORD[ArrayStore[i].ArraySize]; memset(ArrayStore[i].Elements, 0, ArrayStore[i].ArraySize*sizeof(DWORD)); } } // Initialize arrays for this module chunk = (DWORD *)FindChunk (MAKE_ID('A','I','N','I')); while (chunk != NULL) { int arraynum = MapVarStore[LittleLong(chunk[2])]; if ((unsigned)arraynum < (unsigned)NumArrays) { int initsize = MIN (ArrayStore[arraynum].ArraySize, (LittleLong(chunk[1])-4)/4); SDWORD *elems = ArrayStore[arraynum].Elements; for (i = 0; i < initsize; ++i) { elems[i] = LittleLong(chunk[3+i]); } } chunk = (DWORD *)NextChunk((BYTE *)chunk); } // Start setting up array pointers NumTotalArrays = NumArrays; chunk = (DWORD *)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 = (DWORD *)FindChunk (MAKE_ID('M','S','T','R')); if (chunk != NULL) { for (DWORD i = 0; i < chunk[1]/4; ++i) { MapVarStore[chunk[i+2]] |= LibraryID; } } chunk = (DWORD *)FindChunk (MAKE_ID('A','S','T','R')); if (chunk != NULL) { for (DWORD i = 0; i < chunk[1]/4; ++i) { int arraynum = MapVarStore[LittleLong(chunk[i+2])]; if ((unsigned)arraynum < (unsigned)NumArrays) { SDWORD *elems = ArrayStore[arraynum].Elements; for (int j = ArrayStore[arraynum].ArraySize; j > 0; --j, ++elems) { *elems |= LibraryID; } } } } } // Load required libraries. if (NULL != (chunk = (DWORD *)FindChunk (MAKE_ID('L','O','A','D')))) { const char *const parse = (char *)&chunk[2]; DWORD i; for (i = 0; i < chunk[1]; ) { if (parse[i]) { FBehavior *module = NULL; int lump = Wads.CheckNumForName (&parse[i], ns_acslibrary); if (lump < 0) { Printf ("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 = (DWORD *)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) + 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 ("Function %s in %s has %d arguments. %s expects it to have %d.\n", (char *)(chunk + 2) + 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 = realfunc->LocalCount; func->HasReturnValue = realfunc->HasReturnValue; } } } } // Resolve map variables chunk = (DWORD *)FindChunk(MAKE_ID('M','I','M','P')); if (chunk != NULL) { char *parse = (char *)&chunk[2]; for (DWORD j = 0; j < chunk[1]; ) { DWORD varNum = LittleLong(*(DWORD *)&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 = (DWORD *)FindChunk(MAKE_ID('A','I','M','P')); char *parse = (char *)&chunk[3]; for (DWORD j = 0; j < LittleLong(chunk[2]); ++j) { DWORD varNum = LittleLong(*(DWORD *)parse); parse += 4; DWORD expectedSize = LittleLong(*(DWORD *)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 ("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 ("Loaded %d scripts, %d functions\n", NumScripts, NumFunctions); } 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 (Data != NULL) { delete[] Data; Data = NULL; } } void FBehavior::LoadScriptsDirectory () { union { BYTE *b; DWORD *dw; WORD *w; SWORD *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 = (DWORD *)(Data + LittleLong(((DWORD *)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 (*(DWORD *)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 = BYTE(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; } 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("%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 = scripts.dw[1]; 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 = 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 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 STACK_ARGS 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 () { DWORD *prevchunk = NULL; DWORD *chunk = (DWORD *)FindChunk(MAKE_ID('S','T','R','E')); while (chunk != NULL) { for (DWORD strnum = 0; strnum < LittleLong(chunk[3]); ++strnum) { int ofs = LittleLong(chunk[5+strnum]); BYTE *data = (BYTE *)chunk + ofs + 8, last; int p = (BYTE)(ofs*157135); int i = 0; do { last = (data[i] ^= (BYTE)(p+(i>>1))); ++i; } while (last != 0); } prevchunk = chunk; chunk = (DWORD *)NextChunk ((BYTE *)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(BYTE *chunkstart, BYTE *datastart, bool has_padding) { assert(chunkstart != NULL); DWORD *chunk = (DWORD *)chunkstart; if (datastart == NULL) { datastart = chunkstart; } if (!has_padding) { chunk[0] = LittleLong(chunk[0]); for (DWORD 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 (DWORD 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) { DWORD *chunk = (DWORD *)FindChunk (MAKE_ID('F','N','A','M')); Printf ("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 ("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 (DWORD 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 ((DWORD *)FindChunk (MAKE_ID('F','N','A','M')), funcname); } int FBehavior::FindMapVarName (const char *varname) const { return FindStringInChunk ((DWORD *)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 (DWORD *names, const char *varname) const { if (names != NULL) { DWORD 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 } BYTE *FBehavior::FindChunk (DWORD id) const { BYTE *chunk = Chunks; while (chunk != NULL && chunk < Data + DataSize) { if (((DWORD *)chunk)[0] == id) { return chunk; } chunk += ((DWORD *)chunk)[1] + 8; } return NULL; } BYTE *FBehavior::NextChunk (BYTE *chunk) const { DWORD id = *(DWORD *)chunk; chunk += ((DWORD *)chunk)[1] + 8; while (chunk != NULL && chunk < Data + DataSize) { if (((DWORD *)chunk)[0] == id) { return chunk; } chunk += ((DWORD *)chunk)[1] + 8; } return NULL; } const char *FBehavior::StaticLookupString (DWORD index) { DWORD lib = index >> 16; switch (lib) { case LIB_ACSSTRINGS_ONTHEFLY: index &= 0xffff; return (ACS_StringsOnTheFly.Size() > index) ? ACS_StringsOnTheFly[index].GetChars() : NULL; } if (lib >= (DWORD)StaticModules.Size()) { return NULL; } return StaticModules[lib]->LookupString (index & 0xffff); } const char *FBehavior::LookupString (DWORD index) const { if (StringTable == 0) { return NULL; } if (Format == ACS_Old) { DWORD *list = (DWORD *)(Data + StringTable); if (index >= list[0]) return NULL; // Out of range for this list; return (const char *)(Data + list[1+index]); } else { DWORD *list = (DWORD *)(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 (WORD 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" }; DPrintf("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 (WORD 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 (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); } } //========================================================================== // // P_SerializeACSScriptNumber // // Serializes a script number. If it's negative, it's really a name, so // that will get serialized after it. // //========================================================================== void P_SerializeACSScriptNumber(FArchive &arc, int &scriptnum, bool was2byte) { if (SaveVersion < 3359) { if (was2byte) { WORD oldver; arc << oldver; scriptnum = oldver; } else { arc << scriptnum; } } else { arc << scriptnum; // If the script number is negative, then it's really a name. // So read/store the name after it. if (scriptnum < 0) { if (arc.IsStoring()) { arc.WriteName(FName(ENamedName(-scriptnum)).GetChars()); } else { const char *nam = arc.ReadName(); scriptnum = -FName(nam); } } } } //---- The ACS Interpreter ----// IMPLEMENT_POINTY_CLASS (DACSThinker) DECLARE_POINTER(LastScript) DECLARE_POINTER(Scripts) END_POINTERS 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; } void DACSThinker::Serialize (FArchive &arc) { int scriptnum; Super::Serialize (arc); arc << Scripts << LastScript; if (arc.IsStoring ()) { ScriptMap::Iterator it(RunningScripts); ScriptMap::Pair *pair; while (it.NextPair(pair)) { assert(pair->Value != NULL); arc << pair->Value; scriptnum = pair->Key; P_SerializeACSScriptNumber(arc, scriptnum, true); } DLevelScript *nilptr = NULL; arc << nilptr; } else // Loading { DLevelScript *script = NULL; RunningScripts.Clear(); arc << script; while (script) { P_SerializeACSScriptNumber(arc, scriptnum, true); RunningScripts[scriptnum] = script; arc << script; } } } void DACSThinker::Tick () { DLevelScript *script = Scripts; while (script) { DLevelScript *next = script->next; script->RunScript (); script = next; } ACS_StringsOnTheFly.Clear(); if (ACS_StringBuilderStack.Size()) { int size = ACS_StringBuilderStack.Size(); ACS_StringBuilderStack.Clear(); I_Error("Error: %d garbage entries on ACS string builder stack.", size); } } 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_POINTY_CLASS (DLevelScript) DECLARE_POINTER(next) DECLARE_POINTER(prev) DECLARE_POINTER(activator) END_POINTERS inline FArchive &operator<< (FArchive &arc, DLevelScript::EScriptState &state) { BYTE val = (BYTE)state; arc << val; state = (DLevelScript::EScriptState)val; return arc; } void DLevelScript::Serialize (FArchive &arc) { DWORD i; Super::Serialize (arc); arc << next << prev; P_SerializeACSScriptNumber(arc, script, false); arc << state << statedata << activator << activationline << backSide << numlocalvars; if (arc.IsLoading()) { localvars = new SDWORD[numlocalvars]; } for (i = 0; i < (DWORD)numlocalvars; i++) { arc << localvars[i]; } if (arc.IsStoring ()) { WORD lib = activeBehavior->GetLibraryID() >> 16; arc << lib; i = activeBehavior->PC2Ofs (pc); arc << i; } else { WORD lib; arc << lib << i; activeBehavior = FBehavior::StaticGetModule (lib); pc = activeBehavior->Ofs2PC (i); } arc << activefont << hudwidth << hudheight; } DLevelScript::DLevelScript () { next = prev = NULL; if (DACSThinker::ActiveThinker == NULL) new DACSThinker; activefont = SmallFont; localvars = NULL; } DLevelScript::~DLevelScript () { if (localvars != NULL) delete[] localvars; localvars = NULL; } 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; const PClass *kind; int count = 0; bool replacemented = false; if (type >= MAX_SPAWNABLES) { return 0; } else if (type > 0) { kind = SpawnableThings[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::FindClass (type_name); if (kind == NULL || kind->ActorInfo == 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 (actor->Sector->tag == tag || tag == -1) { // Don't count items in somebody's inventory if (!actor->IsKindOf (RUNTIME_CLASS(AInventory)) || static_cast(actor)->Owner == NULL) { count++; } } } } } else { TThinkerIterator iterator; while ( (actor = iterator.Next ()) ) { if (actor->health > 0 && (kind == NULL || actor->IsA (kind))) { if (actor->Sector->tag == tag || tag == -1) { // Don't count items in somebody's inventory if (!actor->IsKindOf (RUNTIME_CLASS(AInventory)) || static_cast(actor)->Owner == NULL) { count++; } } } } } if (!replacemented && kind != NULL) { // Again, with decorate replacements replacemented = true; PClass *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, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable); while ((secnum = P_FindSectorFromTag (tag, secnum)) >= 0) { int pos = floorOrCeiling? sector_t::ceiling : sector_t::floor; 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 == NULL) return; side = !!side; texture = TexMan.GetTexture (texname, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable); while ((linenum = P_FindLineFromID (lineid, linenum)) >= 0) { side_t *sidedef; sidedef = lines[linenum].sidedef[side]; if (sidedef == NULL) 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; } } } void DLevelScript::ReplaceTextures (int fromnamei, int tonamei, int flags) { const char *fromname = FBehavior::StaticLookupString (fromnamei); const char *toname = FBehavior::StaticLookupString (tonamei); FTextureID picnum1, picnum2; if (fromname == NULL) return; if ((flags ^ (NOT_BOTTOM | NOT_MIDDLE | NOT_TOP)) != 0) { picnum1 = TexMan.GetTexture (fromname, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable); picnum2 = TexMan.GetTexture (toname, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable); for (int i = 0; i < numsides; ++i) { side_t *wal = &sides[i]; for(int j=0;j<3;j++) { static BYTE bits[]={NOT_TOP, NOT_MIDDLE, NOT_BOTTOM}; if (!(flags & bits[j]) && wal->GetTexture(j) == picnum1) { wal->SetTexture(j, picnum2); } } } } if ((flags ^ (NOT_FLOOR | NOT_CEILING)) != 0) { picnum1 = TexMan.GetTexture (fromname, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable); picnum2 = TexMan.GetTexture (toname, FTexture::TEX_Flat, FTextureManager::TEXMAN_Overridable); for (int i = 0; i < numsectors; ++i) { sector_t *sec = §ors[i]; if (!(flags & NOT_FLOOR) && sec->GetTexture(sector_t::floor) == picnum1) sec->SetTexture(sector_t::floor, picnum2); if (!(flags & NOT_CEILING) && sec->GetTexture(sector_t::ceiling) == picnum1) sec->SetTexture(sector_t::ceiling, picnum2); } } } int DLevelScript::DoSpawn (int type, fixed_t x, fixed_t y, fixed_t z, int tid, int angle, bool force) { const PClass *info = PClass::FindClass (FBehavior::StaticLookupString (type)); AActor *actor = NULL; int spawncount = 0; if (info != NULL) { actor = Spawn (info, x, y, z, ALLOW_REPLACE); if (actor != NULL) { DWORD oldFlags2 = actor->flags2; actor->flags2 |= MF2_PASSMOBJ; if (force || P_TestMobjLocation (actor)) { actor->angle = angle << 24; actor->tid = tid; actor->AddToHash (); 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::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->x, aspot->y, aspot->z, tid, angle, force); } } else if (activator != NULL) { spawned += DoSpawn (type, activator->x, activator->y, activator->z, tid, angle, 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->x, aspot->y, aspot->z, tid, aspot->angle >> 24, force); } } else if (activator != NULL) { spawned += DoSpawn (type, activator->x, activator->y, activator->z, tid, activator->angle >> 24, force); } return spawned; } void DLevelScript::DoFadeTo (int r, int g, int b, int a, fixed_t time) { DoFadeRange (0, 0, 0, -1, clamp(r, 0, 255), clamp(g, 0, 255), clamp(b, 0, 255), clamp(a, 0, FRACUNIT), time); } void DLevelScript::DoFadeRange (int r1, int g1, int b1, int a1, int r2, int g2, int b2, int a2, fixed_t 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; } } new DFlashFader (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 = NULL; if (self->lastenemy == self->master) self->lastenemy = NULL; if (self->LastHeard == self->master) self->LastHeard = NULL; return 1; } else if (master->player) { // [KS] Be friendly to this player self->master = NULL; level.total_monsters -= self->CountsAsKill(); self->flags|=MF_FRIENDLY; self->FriendPlayer = int(master->player-players+1); 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 = NULL; if (self->lastenemy == master) self->lastenemy = NULL; if (self->LastHeard == master) self->LastHeard = NULL; return 1; } } else { self->master = NULL; 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 = NULL; if (self->lastenemy && !self->IsHostile (self->lastenemy)) self->lastenemy = NULL; if (self->LastHeard && !self->IsHostile (self->LastHeard)) self->LastHeard = NULL; 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; } static 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, }; // 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, -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 = value; break; case APROP_Damage: actor->Damage = value; break; case APROP_Alpha: actor->alpha = 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 (RUNTIME_CLASS (APlayerPawn))) static_cast(actor)->JumpZ = 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 (value) { if (actor->CountsAsKill()) level.total_monsters--; actor->flags |= MF_FRIENDLY; } else { actor->flags &= ~MF_FRIENDLY; if (actor->CountsAsKill()) level.total_monsters++; } break; case APROP_SpawnHealth: if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn))) { static_cast(actor)->MaxHealth = value; } break; case APROP_Gravity: actor->gravity = 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 = value; break; case APROP_MasterTID: AActor *other; other = SingleActorFromTID (value, NULL); DoSetMaster (actor, other); break; case APROP_ScaleX: actor->scaleX = value; break; case APROP_ScaleY: actor->scaleY = value; break; case APROP_Mass: actor->Mass = value; break; case APROP_Accuracy: actor->accuracy = value; break; case APROP_Stamina: actor->stamina = 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 actor->Speed; case APROP_Damage: return actor->Damage; // Should this call GetMissileDamage() instead? case APROP_DamageFactor:return actor->DamageFactor; case APROP_Alpha: return 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 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: if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn))) { return static_cast(actor)->MaxHealth; } else { return actor->SpawnHealth(); } case APROP_JumpZ: if (actor->IsKindOf (RUNTIME_CLASS (APlayerPawn))) { return static_cast(actor)->JumpZ; // [GRB] } 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 actor->scaleX; case APROP_ScaleY: return actor->scaleY; case APROP_Mass: return actor->Mass; case APROP_Accuracy: return actor->accuracy; case APROP_Stamina: return actor->stamina; 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: 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 not covered by GetActorProperty, 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; } 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)); if (tex == NULL) { // If the texture we want to check against doesn't exist, then // they're obviously not the same. return 0; } int i, numff; FTextureID secpic; sector_t *sec = actor->Sector; numff = sec->e->XFloor.ffloors.Size(); if (floor) { // Looking through planes from top to bottom for (i = 0; i < numff; ++i) { F3DFloor *ff = sec->e->XFloor.ffloors[i]; if ((ff->flags & (FF_EXISTS | FF_SOLID)) == (FF_EXISTS | FF_SOLID) && actor->z >= ff->top.plane->ZatPoint(actor->x, actor->y)) { // This floor is beneath our feet. secpic = *ff->top.texture; break; } } if (i == numff) { // Use sector's floor secpic = sec->GetTexture(sector_t::floor); } } else { fixed_t z = actor->z + actor->height; // Looking through planes from bottom to top for (i = numff-1; i >= 0; --i) { F3DFloor *ff = sec->e->XFloor.ffloors[i]; if ((ff->flags & (FF_EXISTS | FF_SOLID)) == (FF_EXISTS | FF_SOLID) && z <= ff->bottom.plane->ZatPoint(actor->x, actor->y)) { // This floor is above our eyes. secpic = *ff->bottom.texture; break; } } if (i < 0) { // Use sector's ceiling secpic = sec->GetTexture(sector_t::ceiling); } } return tex == TexMan[secpic]; } enum { // These are the original inputs sent by the player. INPUT_OLDBUTTONS, INPUT_BUTTONS, INPUT_PITCH, INPUT_YAW, INPUT_ROLL, INPUT_FORWARDMOVE, INPUT_SIDEMOVE, INPUT_UPMOVE, // These are the inputs, as modified by P_PlayerThink(). // Most of the time, these will match the original inputs, but // they can be different if a player is frozen or using a // chainsaw. MODINPUT_OLDBUTTONS, MODINPUT_BUTTONS, MODINPUT_PITCH, MODINPUT_YAW, MODINPUT_ROLL, MODINPUT_FORWARDMOVE, MODINPUT_SIDEMOVE, MODINPUT_UPMOVE }; 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; } switch (inputnum) { case INPUT_OLDBUTTONS: return p->original_oldbuttons; break; case INPUT_BUTTONS: return p->original_cmd.buttons; break; case INPUT_PITCH: return p->original_cmd.pitch; break; case INPUT_YAW: return p->original_cmd.yaw; break; case INPUT_ROLL: return p->original_cmd.roll; break; case INPUT_FORWARDMOVE: return p->original_cmd.forwardmove; break; case INPUT_SIDEMOVE: return p->original_cmd.sidemove; break; case INPUT_UPMOVE: return p->original_cmd.upmove; break; case MODINPUT_OLDBUTTONS: return p->oldbuttons; break; case MODINPUT_BUTTONS: return p->cmd.ucmd.buttons; break; case MODINPUT_PITCH: return p->cmd.ucmd.pitch; break; case MODINPUT_YAW: return p->cmd.ucmd.yaw; break; case MODINPUT_ROLL: return p->cmd.ucmd.roll; break; case MODINPUT_FORWARDMOVE: return p->cmd.ucmd.forwardmove; break; case MODINPUT_SIDEMOVE: return p->cmd.ucmd.sidemove; break; case MODINPUT_UPMOVE: return p->cmd.ucmd.upmove; break; default: return 0; break; } } 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->isbot) { 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 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, }; 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]->Index; } else { int line = P_FindLineFromID(id, -1); if (line == -1) return -1; if (lines[line].sidedef[side] == NULL) return -1; return lines[line].sidedef[side]->Index; } } int DLevelScript::LineFromID(int id) { if (id == 0) { if (activationline == NULL) return -1; return int(activationline - lines); } else { return P_FindLineFromID(id, -1); } } static void SetUserVariable(AActor *self, FName varname, int index, int value) { PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); int max; PSymbolVariable *var; if (sym == NULL || sym->SymbolType != SYM_Variable || !(var = static_cast(sym))->bUserVar) { return; } if (var->ValueType.Type == VAL_Int) { max = 1; } else if (var->ValueType.Type == VAL_Array && var->ValueType.BaseType == VAL_Int) { max = var->ValueType.size; } else { return; } // Set the value of the specified user variable. if (index >= 0 && index < max) { ((int *)(reinterpret_cast(self) + var->offset))[index] = value; } } static int GetUserVariable(AActor *self, FName varname, int index) { PSymbol *sym = self->GetClass()->Symbols.FindSymbol(varname, true); int max; PSymbolVariable *var; if (sym == NULL || sym->SymbolType != SYM_Variable || !(var = static_cast(sym))->bUserVar) { return 0; } if (var->ValueType.Type == VAL_Int) { max = 1; } else if (var->ValueType.Type == VAL_Array && var->ValueType.BaseType == VAL_Int) { max = var->ValueType.size; } else { return 0; } // Get the value of the specified user variable. if (index >= 0 && index < max) { return ((int *)(reinterpret_cast(self) + var->offset))[index]; } return 0; } int DLevelScript::CallFunction(int argCount, int funcIndex, SDWORD *args) { AActor *actor; switch(funcIndex) { case ACSF_GetLineUDMFInt: return GetUDMFInt(UDMF_Line, LineFromID(args[0]), FBehavior::StaticLookupString(args[1])); case ACSF_GetLineUDMFFixed: return GetUDMFFixed(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_FindSectorFromTag(args[0], -1), FBehavior::StaticLookupString(args[1])); case ACSF_GetSectorUDMFFixed: return GetUDMFFixed(UDMF_Sector, P_FindSectorFromTag(args[0], -1), FBehavior::StaticLookupString(args[1])); case ACSF_GetSideUDMFInt: return GetUDMFInt(UDMF_Side, SideFromID(args[0], args[1]), FBehavior::StaticLookupString(args[2])); case ACSF_GetSideUDMFFixed: return GetUDMFFixed(UDMF_Side, SideFromID(args[0], args[1]), FBehavior::StaticLookupString(args[2])); case ACSF_GetActorVelX: actor = SingleActorFromTID(args[0], activator); return actor != NULL? actor->velx : 0; case ACSF_GetActorVelY: actor = SingleActorFromTID(args[0], activator); return actor != NULL? actor->vely : 0; case ACSF_GetActorVelZ: actor = SingleActorFromTID(args[0], activator); return actor != NULL? actor->velz : 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) { P_BulletSlope(actor, &actor); } 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 actor->player->mo->ViewHeight + actor->player->crouchviewdelta; } else { return actor->GetClass()->Meta.GetMetaFixed(AMETA_CameraHeight, actor->height/2); } } 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 = FIXED2FLOAT(args[1]); else if (args[0] == 2) level.skyspeed2 = FIXED2FLOAT(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])); ABasicArmor * armor = (ABasicArmor *) players[args[1]].mo->FindInventory(NAME_BasicArmor); if (armor && armor->ArmorType == p) return armor->Amount; } return 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: if (args[0] == 0) { P_Thing_SetVelocity(activator, args[1], args[2], args[3], !!args[4], !!args[5]); } else { TActorIterator iterator (args[0]); while ( (actor = iterator.Next ()) ) { P_Thing_SetVelocity(actor, args[1], args[2], args[3], !!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 = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); 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 = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); 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 = args[0] == 0 ? (AActor *)activator : SingleActorFromTID(args[0], NULL); return a == NULL ? false : a->GetClass()->TypeName == FName(FBehavior::StaticLookupString(args[1])); } 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) { int secnum = -1; while ((secnum = P_FindSectorFromTag(args[0], secnum)) >= 0) { SN_StartSequence(§ors[secnum], 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 poly->StartSpot.x; } } return FIXED_MAX; case ACSF_GetPolyobjY: { FPolyObj *poly = PO_GetPolyobj(args[0]); if (poly != NULL) { return poly->StartSpot.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; default: break; } return 0; } enum { PRINTNAME_LEVELNAME = -1, PRINTNAME_LEVEL = -2, PRINTNAME_SKILL = -3, }; #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)) inline int getbyte (int *&pc) { int res = *(BYTE *)pc; pc = (int *)((BYTE *)pc+1); return res; } inline int getshort (int *&pc) { int res = LittleShort( *(SWORD *)pc); pc = (int *)((BYTE *)pc+2); return res; } // Read a possibly unaligned four-byte little endian integer. #if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) inline int uallong(int &foo) { return foo; } #else inline int uallong(int &foo) { unsigned char *bar = (unsigned char *)&foo; return bar[0] | (bar[1] << 8) | (bar[2] << 16) | (bar[3] << 24); } #endif int DLevelScript::RunScript () { DACSThinker *controller = DACSThinker::ActiveThinker; SDWORD *locals = localvars; ScriptFunction *activeFunction = NULL; FRemapTable *translation = 0; int resultValue = 1; // 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 = -1; while ((secnum = P_FindSectorFromTag (statedata, secnum)) >= 0) if (sectors[secnum].floordata || 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; } SDWORD Stack[STACK_SIZE]; int sp = 0; int *pc = this->pc; ACSFormat fmt = activeBehavior->GetFormat(); 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 > 500000) { 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()); // fall through case PCD_TERMINATE: DPrintf ("%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(); break; case PCD_PUSHNUMBER: PushToStack (uallong(pc[0])); pc++; break; case PCD_PUSHBYTE: PushToStack (*(BYTE *)pc); pc = (int *)((BYTE *)pc + 1); break; case PCD_PUSH2BYTES: Stack[sp] = ((BYTE *)pc)[0]; Stack[sp+1] = ((BYTE *)pc)[1]; sp += 2; pc = (int *)((BYTE *)pc + 2); break; case PCD_PUSH3BYTES: Stack[sp] = ((BYTE *)pc)[0]; Stack[sp+1] = ((BYTE *)pc)[1]; Stack[sp+2] = ((BYTE *)pc)[2]; sp += 3; pc = (int *)((BYTE *)pc + 3); break; case PCD_PUSH4BYTES: Stack[sp] = ((BYTE *)pc)[0]; Stack[sp+1] = ((BYTE *)pc)[1]; Stack[sp+2] = ((BYTE *)pc)[2]; Stack[sp+3] = ((BYTE *)pc)[3]; sp += 4; pc = (int *)((BYTE *)pc + 4); break; case PCD_PUSH5BYTES: Stack[sp] = ((BYTE *)pc)[0]; Stack[sp+1] = ((BYTE *)pc)[1]; Stack[sp+2] = ((BYTE *)pc)[2]; Stack[sp+3] = ((BYTE *)pc)[3]; Stack[sp+4] = ((BYTE *)pc)[4]; sp += 5; pc = (int *)((BYTE *)pc + 5); break; case PCD_PUSHBYTES: temp = *(BYTE *)pc; pc = (int *)((BYTE *)pc + temp + 1); for (temp = -temp; temp; temp++) { PushToStack (*((BYTE *)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_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(((BYTE *)pc)[0], activationline, activator, backSide, ((BYTE *)pc)[1], 0, 0, 0, 0); pc = (int *)((BYTE *)pc + 2); break; case PCD_LSPEC2DIRECTB: P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide, ((BYTE *)pc)[1], ((BYTE *)pc)[2], 0, 0, 0); pc = (int *)((BYTE *)pc + 3); break; case PCD_LSPEC3DIRECTB: P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide, ((BYTE *)pc)[1], ((BYTE *)pc)[2], ((BYTE *)pc)[3], 0, 0); pc = (int *)((BYTE *)pc + 4); break; case PCD_LSPEC4DIRECTB: P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide, ((BYTE *)pc)[1], ((BYTE *)pc)[2], ((BYTE *)pc)[3], ((BYTE *)pc)[4], 0); pc = (int *)((BYTE *)pc + 5); break; case PCD_LSPEC5DIRECTB: P_ExecuteSpecial(((BYTE *)pc)[0], activationline, activator, backSide, ((BYTE *)pc)[1], ((BYTE *)pc)[2], ((BYTE *)pc)[3], ((BYTE *)pc)[4], ((BYTE *)pc)[5]); pc = (int *)((BYTE *)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_CALL: case PCD_CALLDISCARD: { int funcnum; int i; ScriptFunction *func; FBehavior *module = activeBehavior; SDWORD *mylocals; funcnum = NEXTBYTE; func = activeBehavior->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; } mylocals = locals; // The function's first argument is also its first local variable. locals = &Stack[sp - func->ArgCount]; // 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, pcd == PCD_CALLDISCARD, work); sp += (sizeof(CallReturn) + sizeof(int) - 1) / sizeof(int); pc = module->Ofs2PC (func->Address); activeFunction = func; activeBehavior = module; fmt = module->GetFormat(); } break; case PCD_RETURNVOID: case PCD_RETURNVAL: { int value; union { SDWORD *retsp; CallReturn *ret; }; if (pcd == PCD_RETURNVAL) { value = Stack[--sp]; } else { value = 0; } sp -= sizeof(CallReturn)/sizeof(int); retsp = &Stack[sp]; sp = int(locals - Stack); pc = ret->ReturnModule->Ofs2PC(ret->ReturnAddress); activeFunction = ret->ReturnFunction; activeBehavior = ret->ReturnModule; fmt = activeBehavior->GetFormat(); locals = ret->ReturnLocals; if (!ret->bDiscardResult) { Stack[sp++] = value; } work = ret->StringBuilder; 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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_IFGOTO: if (STACK(1)) pc = activeBehavior->Ofs2PC (LittleLong(*pc)); else pc++; sp--; break; case PCD_DROP: case PCD_SETRESULTVALUE: resultValue = STACK(1); 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 = *(BYTE *)pc + (fmt == ACS_Old && gameinfo.gametype == GAME_Hexen); if (statedata > 0) { state = SCRIPT_Delayed; } pc = (int *)((BYTE *)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 (((BYTE *)pc)[0], ((BYTE *)pc)[1])); pc = (int *)((BYTE *)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]), 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]), 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); if (controller->RunningScripts.CheckKey(statedata) != NULL) state = SCRIPT_ScriptWait; else state = SCRIPT_ScriptWaitPre; sp--; PutLast (); break; case PCD_SCRIPTWAITDIRECT: state = SCRIPT_ScriptWait; statedata = uallong(pc[0]); pc++; PutLast (); break; case PCD_CLEARLINESPECIAL: if (activationline != NULL) { activationline->special = 0; DPrintf("Cleared line special on line %d\n", activationline - lines); } 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; SDWORD caseval = 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)); 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: work.AppendFormat ("%B", STACK(1)); --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", FIXED2FLOAT(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: work += level.mapname; 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.netname; } else if (activator) { work += activator->GetTag(); } else { work += ' '; } sp--; } break; // [JB] Print map character array case PCD_PRINTMAPCHARARRAY: case PCD_PRINTMAPCHRANGE: { int capacity, offset; if (pcd == PCD_PRINTMAPCHRANGE) { capacity = STACK(1); offset = STACK(2); if (capacity < 1 || offset < 0) { sp -= 4; break; } sp -= 2; } else { capacity = 0x7FFFFFFF; offset = 0; } int a = *(activeBehavior->MapVars[STACK(1)]); offset += STACK(2); int c; while(capacity-- && (c = activeBehavior->GetArrayVal (a, offset)) != '\0') { work += (char)c; offset++; } sp-= 2; } break; // [JB] Print world character array case PCD_PRINTWORLDCHARARRAY: case PCD_PRINTWORLDCHRANGE: { int capacity, offset; if (pcd == PCD_PRINTWORLDCHRANGE) { capacity = STACK(1); offset = STACK(2); if (capacity < 1 || offset < 0) { sp -= 4; break; } sp -= 2; } else { capacity = 0x7FFFFFFF; offset = 0; } int a = STACK(1); offset += STACK(2); int c; while(capacity-- && (c = ACS_WorldArrays[a][offset]) != '\0') { work += (char)c; offset++; } sp-= 2; } break; // [JB] Print global character array case PCD_PRINTGLOBALCHARARRAY: case PCD_PRINTGLOBALCHRANGE: { int capacity, offset; if (pcd == PCD_PRINTGLOBALCHRANGE) { capacity = STACK(1); offset = STACK(2); if (capacity < 1 || offset < 0) { sp -= 4; break; } sp -= 2; } else { capacity = 0x7FFFFFFF; offset = 0; } int a = STACK(1); offset += STACK(2); int c; while(capacity-- && (c = ACS_GlobalArrays[a][offset]) != '\0') { work += (char)c; offset++; } sp-= 2; } 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 (consoleplayer)) { C_MidPrint (activefont, work); } 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 = FIXED2FLOAT(Stack[optstart-3]); float y = FIXED2FLOAT(Stack[optstart-2]); float holdTime = FIXED2FLOAT(Stack[optstart-1]); DHUDMessage *msg; if (type & HUDMSG_COLORSTRING) { color = V_FindFontColor(FBehavior::StaticLookupString(Stack[optstart-4])); } else { color = CLAMPCOLOR(Stack[optstart-4]); } switch (type & 0xFFFF) { default: // normal msg = new DHUDMessage (activefont, work, x, y, hudwidth, hudheight, color, holdTime); break; case 1: // fade out { float fadeTime = (optstart < sp) ? FIXED2FLOAT(Stack[optstart]) : 0.5f; msg = new DHUDMessageFadeOut (activefont, work, x, y, hudwidth, hudheight, color, holdTime, fadeTime); } break; case 2: // type on, then fade out { float typeTime = (optstart < sp) ? FIXED2FLOAT(Stack[optstart]) : 0.05f; float fadeTime = (optstart < sp-1) ? FIXED2FLOAT(Stack[optstart+1]) : 0.5f; msg = new DHUDMessageTypeOnFadeOut (activefont, work, x, y, hudwidth, hudheight, color, typeTime, holdTime, fadeTime); } break; case 3: // fade in, then fade out { float inTime = (optstart < sp) ? FIXED2FLOAT(Stack[optstart]) : 0.5f; float outTime = (optstart < sp-1) ? FIXED2FLOAT(Stack[optstart+1]) : 0.5f; msg = new DHUDMessageFadeInOut (activefont, work, x, y, hudwidth, hudheight, color, holdTime, inTime, outTime); } break; } StatusBar->AttachMessage (msg, id ? 0xff000000|id : 0); 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"; static const char logbar[] = "\n<------------------------------->\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); if (Logfile) { fputs (logbar, Logfile); fputs (work, Logfile); fputs (logbar, Logfile); fflush (Logfile); } } } } STRINGBUILDER_FINISH(work); sp = optstart-6; break; case PCD_SETFONT: DoSetFont (STACK(1)); sp--; break; case PCD_SETFONTDIRECT: DoSetFont (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_PLAYERHEALTH: if (activator) PushToStack (activator->health); else PushToStack (0); break; case PCD_PLAYERARMORPOINTS: if (activator) { ABasicArmor *armor = activator->FindInventory(); PushToStack (armor ? armor->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 (!netgame); 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->CheckLocalView (consoleplayer)) { 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: ReplaceTextures (STACK(3), STACK(2), STACK(1)); sp -= 3; break; case PCD_SETLINEBLOCKING: { int line = -1; while ((line = P_FindLineFromID (STACK(2), line)) >= 0) { switch (STACK(1)) { case BLOCK_NOTHING: lines[line].flags &= ~(ML_BLOCKING|ML_BLOCKEVERYTHING|ML_RAILING|ML_BLOCK_PLAYERS); break; case BLOCK_CREATURES: default: lines[line].flags &= ~(ML_BLOCKEVERYTHING|ML_RAILING|ML_BLOCK_PLAYERS); lines[line].flags |= ML_BLOCKING; break; case BLOCK_EVERYTHING: lines[line].flags &= ~(ML_RAILING|ML_BLOCK_PLAYERS); lines[line].flags |= ML_BLOCKING|ML_BLOCKEVERYTHING; break; case BLOCK_RAILING: lines[line].flags &= ~(ML_BLOCKEVERYTHING|ML_BLOCK_PLAYERS); lines[line].flags |= ML_RAILING|ML_BLOCKING; break; case BLOCK_PLAYERS: lines[line].flags &= ~(ML_BLOCKEVERYTHING|ML_BLOCKING|ML_RAILING); lines[line].flags |= ML_BLOCK_PLAYERS; break; } } sp -= 2; } break; case PCD_SETLINEMONSTERBLOCKING: { int line = -1; while ((line = P_FindLineFromID (STACK(2), line)) >= 0) { if (STACK(1)) lines[line].flags |= ML_BLOCKMONSTERS; else 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)); } while ((linenum = P_FindLineFromID (STACK(7), linenum)) >= 0) { line_t *line = &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("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 = (float)STACK(1) / 65536.f; sp--; break; case PCD_SETGRAVITYDIRECT: level.gravity = (float)uallong(pc[0]) / 65536.f; pc++; break; case PCD_SETAIRCONTROL: level.aircontrol = STACK(1); sp--; G_AirControlChanged (); break; case PCD_SETAIRCONTROLDIRECT: level.aircontrol = 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 (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 (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: ClearInventory (activator); break; case PCD_CLEARACTORINVENTORY: if (STACK(1) == 0) { ClearInventory(NULL); } else { FActorIterator it(STACK(1)); AActor *actor; for (actor = it.Next(); actor != NULL; actor = it.Next()) { ClearInventory(actor); } } sp--; break; case PCD_GIVEINVENTORY: GiveInventory (activator, FBehavior::StaticLookupString (STACK(2)), STACK(1)); sp -= 2; break; case PCD_GIVEACTORINVENTORY: { const char *type = FBehavior::StaticLookupString(STACK(2)); if (STACK(3) == 0) { GiveInventory(NULL, FBehavior::StaticLookupString(STACK(2)), STACK(1)); } else { FActorIterator it(STACK(3)); AActor *actor; for (actor = it.Next(); actor != NULL; actor = it.Next()) { GiveInventory(actor, type, STACK(1)); } } sp -= 3; } break; case PCD_GIVEINVENTORYDIRECT: GiveInventory (activator, FBehavior::StaticLookupString (uallong(pc[0])), uallong(pc[1])); pc += 2; break; case PCD_TAKEINVENTORY: TakeInventory (activator, FBehavior::StaticLookupString (STACK(2)), STACK(1)); sp -= 2; break; case PCD_TAKEACTORINVENTORY: { const char *type = FBehavior::StaticLookupString(STACK(2)); if (STACK(3) == 0) { TakeInventory(NULL, type, STACK(1)); } else { FActorIterator it(STACK(3)); AActor *actor; for (actor = it.Next(); actor != NULL; actor = it.Next()) { TakeInventory(actor, type, STACK(1)); } } sp -= 3; } break; case PCD_TAKEINVENTORYDIRECT: TakeInventory (activator, FBehavior::StaticLookupString (uallong(pc[0])), uallong(pc[1])); pc += 2; break; case PCD_CHECKINVENTORY: STACK(1) = CheckInventory (activator, FBehavior::StaticLookupString (STACK(1))); break; case PCD_CHECKACTORINVENTORY: STACK(2) = CheckInventory (SingleActorFromTID(STACK(2), NULL), FBehavior::StaticLookupString (STACK(1))); sp--; break; case PCD_CHECKINVENTORYDIRECT: PushToStack (CheckInventory (activator, FBehavior::StaticLookupString (uallong(pc[0])))); 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: { ASigil *sigil; if (activator == NULL || (sigil = activator->FindInventory()) == NULL) { PushToStack (0); } else { PushToStack (sigil->NumPieces); } } break; case PCD_GETAMMOCAPACITY: if (activator != NULL) { const PClass *type = PClass::FindClass (FBehavior::StaticLookupString (STACK(1))); AInventory *item; if (type != NULL && type->ParentClass == RUNTIME_CLASS(AAmmo)) { item = activator->FindInventory (type); if (item != NULL) { STACK(1) = item->MaxAmount; } else { STACK(1) = ((AInventory *)GetDefaultByType (type))->MaxAmount; } } else { STACK(1) = 0; } } else { STACK(1) = 0; } break; case PCD_SETAMMOCAPACITY: if (activator != NULL) { const PClass *type = PClass::FindClass (FBehavior::StaticLookupString (STACK(2))); AInventory *item; if (type != NULL && type->ParentClass == RUNTIME_CLASS(AAmmo)) { item = activator->FindInventory (type); if (item != NULL) { item->MaxAmount = STACK(1); } else { item = activator->GiveInventoryType (type); item->MaxAmount = STACK(1); item->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 (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 (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) = I_PlayMovie (FBehavior::StaticLookupString (STACK(1))); break; case PCD_SETACTORPOSITION: { bool result = false; AActor *actor = SingleActorFromTID (STACK(5), activator); if (actor != NULL) result = P_MoveThing(actor, STACK(4), STACK(3), 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); STACK(1) = actor == NULL ? 0 : (&actor->x)[pcd - PCD_GETACTORX]; } break; case PCD_GETACTORFLOORZ: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : actor->floorz; } break; case PCD_GETACTORCEILINGZ: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : actor->ceilingz; } break; case PCD_GETACTORANGLE: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : actor->angle >> 16; } break; case PCD_GETACTORPITCH: { AActor *actor = SingleActorFromTID(STACK(1), activator); STACK(1) = actor == NULL ? 0 : actor->pitch >> 16; } break; case PCD_GETLINEROWOFFSET: if (activationline != NULL) { PushToStack (activationline->sidedef[0]->GetTextureYOffset(side_t::mid) >> FRACBITS); } 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. { int secnum = P_FindSectorFromTag (STACK(3), -1); fixed_t z = 0; if (secnum >= 0) { fixed_t x = STACK(2) << FRACBITS; fixed_t y = STACK(1) << FRACBITS; if (pcd == PCD_GETSECTORFLOORZ) { z = sectors[secnum].floorplane.ZatPoint (x, y); } else { z = sectors[secnum].ceilingplane.ZatPoint (x, y); } } sp -= 2; STACK(1) = z; } break; case PCD_GETSECTORLIGHTLEVEL: { int secnum = P_FindSectorFromTag (STACK(1), -1); int z = -1; if (secnum >= 0) { z = sectors[secnum].lightlevel; } STACK(1) = z; } break; case PCD_SETFLOORTRIGGER: new DPlaneWatcher (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: new DPlaneWatcher (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_ENDTRANSLATION: // This might be useful for hardware rendering, but // for software it is superfluous. translation->UpdateNative(); translation = NULL; break; case PCD_SIN: STACK(1) = finesine[angle_t(STACK(1)<<16)>>ANGLETOFINESHIFT]; break; case PCD_COS: STACK(1) = finecosine[angle_t(STACK(1)<<16)>>ANGLETOFINESHIFT]; break; case PCD_VECTORANGLE: STACK(2) = R_PointToAngle2 (0, 0, STACK(2), STACK(1)) >> 16; 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: if (activator == NULL || activator->player == NULL) { STACK(1) = 0; } else { AInventory *item = activator->FindInventory (PClass::FindClass ( FBehavior::StaticLookupString (STACK(1)))); if (item == NULL || !item->IsKindOf (RUNTIME_CLASS(AWeapon))) { STACK(1) = 0; } else if (activator->player->ReadyWeapon == item) { // The weapon is already selected, so setweapon succeeds by default, // but make sure the player isn't switching away from it. activator->player->PendingWeapon = WP_NOCHANGE; STACK(1) = 1; } else { AWeapon *weap = static_cast (item); if (weap->CheckAmmo (AWeapon::EitherFire, false)) { // There's enough ammo, so switch to it. STACK(1) = 1; activator->player->PendingWeapon = weap; } else { STACK(1) = 0; } } } break; case PCD_SETMARINEWEAPON: if (STACK(2) != 0) { AScriptedMarine *marine; TActorIterator iterator (STACK(2)); while ((marine = iterator.Next()) != NULL) { marine->SetWeapon ((AScriptedMarine::EMarineWeapon)STACK(1)); } } else { if (activator != NULL && activator->IsKindOf (RUNTIME_CLASS(AScriptedMarine))) { barrier_cast(activator)->SetWeapon ( (AScriptedMarine::EMarineWeapon)STACK(1)); } } sp -= 2; break; case PCD_SETMARINESPRITE: { const PClass *type = PClass::FindClass (FBehavior::StaticLookupString (STACK(1))); if (type != NULL) { if (STACK(2) != 0) { AScriptedMarine *marine; TActorIterator iterator (STACK(2)); while ((marine = iterator.Next()) != NULL) { marine->SetSprite (type); } } else { if (activator != NULL && activator->IsKindOf (RUNTIME_CLASS(AScriptedMarine))) { barrier_cast(activator)->SetSprite (type); } } } else { Printf ("Unknown actor type: %s\n", FBehavior::StaticLookupString (STACK(1))); } } 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)].isbot; } 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, ((angle_t)(STACK(5)<<24)), STACK(4)<<(FRACBITS-3), STACK(3)<<(FRACBITS-3), 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)), ((angle_t)(STACK(5)<<24)), STACK(4)<<(FRACBITS-3), STACK(3)<<(FRACBITS-3), 0, NULL, STACK(2), STACK(1), false); sp -= 7; break; case PCD_STRLEN: STACK(1) = SDWORD(strlen(FBehavior::StaticLookupString (STACK(1)))); break; case PCD_GETCVAR: { FBaseCVar *cvar = FindCVar (FBehavior::StaticLookupString (STACK(1)), NULL); if (cvar == NULL) { STACK(1) = 0; } else { UCVarValue val = cvar->GetGenericRep (CVAR_Int); STACK(1) = val.Int; } } 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) { strncpy (level.skypic1, sky1name, 8); sky1texture = TexMan.GetTexture (sky1name, FTexture::TEX_Wall, FTextureManager::TEXMAN_Overridable|FTextureManager::TEXMAN_ReturnFirst); } if (sky2name[0] != 0) { strncpy (level.skypic2, sky2name, 8); sky2texture = TexMan.GetTexture (sky2name, FTexture::TEX_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, FTexture::TEX_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] if (STACK(2) == 0) { if (activator != NULL) activator->angle = STACK(1) << 16; } else { FActorIterator iterator (STACK(2)); AActor *actor; while ( (actor = iterator.Next ()) ) { actor->angle = STACK(1) << 16; } } sp -= 2; break; case PCD_SETACTORPITCH: if (STACK(2) == 0) { if (activator != NULL) activator->pitch = STACK(1) << 16; } else { FActorIterator iterator (STACK(2)); AActor *actor; while ( (actor = iterator.Next ()) ) { actor->pitch = STACK(1) << 16; } } 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()->ActorInfo->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()->ActorInfo->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 { userinfo_t *userinfo = &players[STACK(2)].userinfo; switch (STACK(1)) { case PLAYERINFO_TEAM: STACK(2) = userinfo->team; break; case PLAYERINFO_AIMDIST: STACK(2) = userinfo->GetAimDist(); break; case PLAYERINFO_COLOR: STACK(2) = userinfo->color; break; case PLAYERINFO_GENDER: STACK(2) = userinfo->gender; break; case PLAYERINFO_NEVERSWITCH: STACK(2) = userinfo->neverswitch; break; case PLAYERINFO_MOVEBOB: STACK(2) = userinfo->MoveBob; break; case PLAYERINFO_STILLBOB: STACK(2) = userinfo->StillBob; break; case PLAYERINFO_PLAYERCLASS: STACK(2) = userinfo->PlayerClass; 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); const PClass *protectClass = PClass::FindClass (protection); int flags = STACK(1); sp -= 5; int secnum = -1; while ((secnum = P_FindSectorFromTag (tag, secnum)) >= 0) { AActor *actor, *next; sector_t *sec = §ors[secnum]; for (actor = sec->thinglist; actor != NULL; actor = next) { next = actor->snext; if (!(actor->flags & MF_SHOOTABLE)) continue; if (!(flags & DAMAGE_NONPLAYERS) && actor->player == NULL) continue; if (!(flags & DAMAGE_PLAYERS) && actor->player != NULL) continue; if (!(flags & DAMAGE_IN_AIR) && actor->z != sec->floorplane.ZatPoint (actor->x, actor->y) && !actor->waterlevel) continue; if (protectClass != NULL) { if (!(flags & DAMAGE_SUBCLASSES_PROTECT)) { if (actor->FindInventory (protectClass)) continue; } else { AInventory *item; for (item = actor->Inventory; item != NULL; item = item->Inventory) { if (item->IsKindOf (protectClass)) break; } if (item != NULL) continue; } } P_DamageMobj (actor, NULL, NULL, amount, type); } } } 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) { STACK(1) = actor->Sector->lightlevel; } else STACK(1) = 0; break; } case PCD_SETMUGSHOTSTATE: 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) { 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)); const PClass *playerclass = PClass::FindClass (playerclass_name); FName monsterclass_name = FBehavior::StaticLookupString(STACK(5)); const PClass *monsterclass = PClass::FindClass (monsterclass_name); int duration = STACK(4); int style = STACK(3); FName morphflash_name = FBehavior::StaticLookupString(STACK(2)); const PClass *morphflash = PClass::FindClass (morphflash_name); FName unmorphflash_name = FBehavior::StaticLookupString(STACK(1)); const PClass *unmorphflash = PClass::FindClass (unmorphflash_name); int changes = 0; if (tag == 0) { if (activator->player) { if (P_MorphPlayer(activator->player, activator->player, playerclass, duration, style, morphflash, unmorphflash)) { changes++; } } else { if (P_MorphMonster(activator, monsterclass, duration, style, morphflash, unmorphflash)) { changes++; } } } else { FActorIterator iterator (tag); AActor *actor; while ( (actor = iterator.Next ()) ) { if (actor->player) { if (P_MorphPlayer(activator->player, actor->player, playerclass, duration, style, morphflash, unmorphflash)) { changes++; } } else { if (P_MorphMonster(actor, monsterclass, duration, style, morphflash, unmorphflash)) { changes++; } } } } STACK(7) = changes; sp -= 6; } break; case PCD_UNMORPHACTOR: { int tag = STACK(2); bool force = !!STACK(1); int changes = 0; if (tag == 0) { if (activator->player) { if (P_UndoPlayerMorph(activator->player, activator->player, force)) { changes++; } } else { if (activator->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) { AMorphedMonster *morphed_actor = barrier_cast(activator); if (P_UndoMonsterMorph(morphed_actor, force)) { changes++; } } } } else { FActorIterator iterator (tag); AActor *actor; while ( (actor = iterator.Next ()) ) { if (actor->player) { if (P_UndoPlayerMorph(activator->player, actor->player, force)) { changes++; } } else { if (actor->GetClass()->IsDescendantOf(RUNTIME_CLASS(AMorphedMonster))) { AMorphedMonster *morphed_actor = static_cast(actor); if (P_UndoMonsterMorph(morphed_actor, force)) { changes++; } } } } } STACK(2) = changes; sp -= 1; } break; case PCD_SAVESTRING: // Saves the string { unsigned int str_otf = ACS_StringsOnTheFly.Push(work); if (str_otf > 0xffff) { PushToStack(-1); } else { PushToStack((SDWORD)str_otf|ACSSTRING_OR_ONTHEFLY); } STRINGBUILDER_FINISH(work); } break; 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_STRCPYTOMAPCHRANGE: { Stack[sp-6] = activeBehavior->CopyStringToArray(STACK(5), 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; } } 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 new DLevelScript (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) new DACSThinker; script = num; assert(code->VarCount >= code->ArgCount); numlocalvars = code->VarCount; localvars = new SDWORD[code->VarCount]; memset(localvars, 0, code->VarCount * sizeof(SDWORD)); for (int i = 0; i < MIN(argcount, code->ArgCount); ++i) { localvars[i] = args[i]; } pc = module->GetScriptAddress(code); activator = who; activationline = where; backSide = flags & ACS_BACKSIDE; activefont = SmallFont; hudwidth = hudheight = 0; 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("%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 () { acsdefered_t *def; const ScriptPtr *scriptdata; FBehavior *module; // Handle defered scripts in this step, too def = level.info->defered; while (def) { acsdefered_t *next = def->next; 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 ("Deferred suspend of %s\n", ScriptPresentation(def->script).GetChars()); break; case acsdefered_t::defterminate: SetScriptState (def->script, DLevelScript::SCRIPT_PleaseRemove); DPrintf ("Deferred terminate of %s\n", ScriptPresentation(def->script).GetChars()); break; } delete def; def = next; } level.info->defered = NULL; } 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 = new acsdefered_t; int j; def->next = i->defered; 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; } i->defered = def; DPrintf ("%s on map %s deferred\n", ScriptPresentation(script).GetChars(), i->mapname); } } 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.netname, 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, 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, char *map) { if (strnicmp (level.mapname, map, 8)) addDefered (FindLevelInfo (map), acsdefered_t::defterminate, script, NULL, 0, NULL); else SetScriptState (script, DLevelScript::SCRIPT_PleaseRemove); } FArchive &operator<< (FArchive &arc, acsdefered_t *&defertop) { BYTE more; if (arc.IsStoring ()) { acsdefered_t *defer = defertop; more = 1; while (defer) { BYTE type; arc << more; type = (BYTE)defer->type; arc << type; P_SerializeACSScriptNumber(arc, defer->script, false); arc << defer->playernum << defer->args[0] << defer->args[1] << defer->args[2]; defer = defer->next; } more = 0; arc << more; } else { acsdefered_t **defer = &defertop; arc << more; while (more) { *defer = new acsdefered_t; arc << more; (*defer)->type = (acsdefered_t::EType)more; P_SerializeACSScriptNumber(arc, (*defer)->script, false); arc << (*defer)->playernum << (*defer)->args[0] << (*defer)->args[1] << (*defer)->args[2]; defer = &((*defer)->next); arc << more; } *defer = NULL; } 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; } } #undef STRINGBUILDER_START #undef STRINGBUILDER_FINISH