gzdoom-gles/src/p_acs.cpp
2018-03-11 19:29:37 +01:00

11054 lines
255 KiB
C++

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