mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-12-14 14:31:53 +00:00
546d3d1bf5
This way of looking up strings is intentionally limited to content from Hexen.wad and Hexdd.wad.
488 lines
12 KiB
C++
488 lines
12 KiB
C++
/*
|
|
** p_acs.h
|
|
** ACS script stuff
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** 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.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#ifndef __P_ACS_H__
|
|
#define __P_ACS_H__
|
|
|
|
#include "doomtype.h"
|
|
#include "dthinker.h"
|
|
#include "doomerrors.h"
|
|
|
|
#define LOCAL_SIZE 20
|
|
#define NUM_MAPVARS 128
|
|
|
|
class FFont;
|
|
class FileReader;
|
|
struct line_t;
|
|
class FSerializer;
|
|
|
|
|
|
enum
|
|
{
|
|
NUM_WORLDVARS = 256,
|
|
NUM_GLOBALVARS = 64
|
|
};
|
|
|
|
struct InitIntToZero
|
|
{
|
|
void Init(int &v)
|
|
{
|
|
v = 0;
|
|
}
|
|
};
|
|
typedef TMap<int32_t, int32_t, THashTraits<int32_t>, InitIntToZero> FWorldGlobalArray;
|
|
|
|
// Type of elements count is unsigned int instead of size_t to match ACSStringPool interface
|
|
template <typename T, unsigned int N>
|
|
struct BoundsCheckingArray
|
|
{
|
|
T &operator[](const unsigned int index)
|
|
{
|
|
if (index >= N)
|
|
{
|
|
I_Error("Out of bounds memory access in ACS VM");
|
|
}
|
|
|
|
return buffer[index];
|
|
}
|
|
|
|
T *Pointer() { return buffer; }
|
|
unsigned int Size() const { return N; }
|
|
|
|
void Fill(const T &value) { std::fill(std::begin(buffer), std::end(buffer), value); }
|
|
|
|
private:
|
|
T buffer[N];
|
|
};
|
|
|
|
// ACS variables with global scope
|
|
extern BoundsCheckingArray<int32_t, NUM_GLOBALVARS> ACS_GlobalVars;
|
|
extern BoundsCheckingArray<FWorldGlobalArray, NUM_GLOBALVARS> ACS_GlobalArrays;
|
|
|
|
#define LIBRARYID_MASK 0xFFF00000
|
|
#define LIBRARYID_SHIFT 20
|
|
|
|
// Global ACS string table
|
|
#define STRPOOL_LIBRARYID (INT_MAX >> LIBRARYID_SHIFT)
|
|
#define STRPOOL_LIBRARYID_OR (STRPOOL_LIBRARYID << LIBRARYID_SHIFT)
|
|
|
|
class ACSStringPool
|
|
{
|
|
public:
|
|
ACSStringPool();
|
|
int AddString(const char *str);
|
|
int AddString(FString &str);
|
|
const char *GetString(int strnum);
|
|
void LockString(int levelnum, int strnum);
|
|
void UnlockAll();
|
|
void MarkString(int strnum);
|
|
void LockStringArray(int levelnum, const int *strnum, unsigned int count);
|
|
void MarkStringArray(const int *strnum, unsigned int count);
|
|
void MarkStringMap(const FWorldGlobalArray &array);
|
|
void PurgeStrings();
|
|
void Clear();
|
|
void Dump() const;
|
|
void UnlockForLevel(int level) ;
|
|
void ReadStrings(FSerializer &file, const char *key);
|
|
void WriteStrings(FSerializer &file, const char *key) const;
|
|
|
|
private:
|
|
int FindString(const char *str, size_t len, unsigned int h, unsigned int bucketnum);
|
|
int InsertString(FString &str, unsigned int h, unsigned int bucketnum);
|
|
void FindFirstFreeEntry(unsigned int base);
|
|
|
|
enum { NUM_BUCKETS = 251 };
|
|
enum { FREE_ENTRY = 0xFFFFFFFE }; // Stored in PoolEntry's Next field
|
|
enum { NO_ENTRY = 0xFFFFFFFF };
|
|
enum { MIN_GC_SIZE = 100 }; // Don't auto-collect until there are this many strings
|
|
struct PoolEntry
|
|
{
|
|
FString Str;
|
|
unsigned int Hash;
|
|
unsigned int Next = FREE_ENTRY;
|
|
bool Mark;
|
|
TArray<int> Locks;
|
|
|
|
void Lock(int levelnum);
|
|
void Unlock(int levelnum);
|
|
};
|
|
TArray<PoolEntry> Pool;
|
|
unsigned int PoolBuckets[NUM_BUCKETS];
|
|
unsigned int FirstFreeEntry;
|
|
};
|
|
extern ACSStringPool GlobalACSStrings;
|
|
|
|
void P_CollectACSGlobalStrings();
|
|
void P_ReadACSVars(FSerializer &);
|
|
void P_WriteACSVars(FSerializer &);
|
|
void P_ClearACSVars(bool);
|
|
|
|
struct ACSProfileInfo
|
|
{
|
|
unsigned long long TotalInstr;
|
|
unsigned int NumRuns;
|
|
unsigned int MinInstrPerRun;
|
|
unsigned int MaxInstrPerRun;
|
|
|
|
ACSProfileInfo();
|
|
void AddRun(unsigned int num_instr);
|
|
void Reset();
|
|
};
|
|
|
|
struct ProfileCollector
|
|
{
|
|
ACSProfileInfo *ProfileData;
|
|
class FBehavior *Module;
|
|
int Index;
|
|
};
|
|
|
|
class ACSLocalVariables
|
|
{
|
|
public:
|
|
ACSLocalVariables(TArray<int32_t> &variables)
|
|
: memory(&variables[0])
|
|
, count(variables.Size())
|
|
{
|
|
}
|
|
|
|
void Reset(int32_t *const memory, const size_t count)
|
|
{
|
|
// TODO: pointer sanity check?
|
|
// TODO: constraints on count?
|
|
|
|
this->memory = memory;
|
|
this->count = count;
|
|
}
|
|
|
|
int32_t& operator[](const size_t index)
|
|
{
|
|
if (index >= count)
|
|
{
|
|
I_Error("Out of bounds access to local variables in ACS VM");
|
|
}
|
|
|
|
return memory[index];
|
|
}
|
|
|
|
const int32_t *GetPointer() const
|
|
{
|
|
return memory;
|
|
}
|
|
|
|
private:
|
|
int32_t *memory;
|
|
size_t count;
|
|
};
|
|
|
|
struct ACSLocalArrayInfo
|
|
{
|
|
unsigned int Size;
|
|
int Offset;
|
|
};
|
|
|
|
struct ACSLocalArrays
|
|
{
|
|
unsigned int Count;
|
|
ACSLocalArrayInfo *Info;
|
|
|
|
ACSLocalArrays()
|
|
{
|
|
Count = 0;
|
|
Info = NULL;
|
|
}
|
|
~ACSLocalArrays()
|
|
{
|
|
if (Info != NULL)
|
|
{
|
|
delete[] Info;
|
|
Info = NULL;
|
|
}
|
|
}
|
|
|
|
// Bounds-checking Set and Get for local arrays
|
|
void Set(ACSLocalVariables &locals, int arraynum, int arrayentry, int value)
|
|
{
|
|
if ((unsigned int)arraynum < Count &&
|
|
(unsigned int)arrayentry < Info[arraynum].Size)
|
|
{
|
|
locals[Info[arraynum].Offset + arrayentry] = value;
|
|
}
|
|
}
|
|
int Get(ACSLocalVariables &locals, int arraynum, int arrayentry)
|
|
{
|
|
if ((unsigned int)arraynum < Count &&
|
|
(unsigned int)arrayentry < Info[arraynum].Size)
|
|
{
|
|
return locals[Info[arraynum].Offset + arrayentry];
|
|
}
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
// The in-memory version
|
|
struct ScriptPtr
|
|
{
|
|
int Number;
|
|
uint32_t Address;
|
|
uint8_t Type;
|
|
uint8_t ArgCount;
|
|
uint16_t VarCount;
|
|
uint16_t Flags;
|
|
ACSLocalArrays LocalArrays;
|
|
|
|
ACSProfileInfo ProfileData;
|
|
};
|
|
|
|
// The present ZDoom version
|
|
struct ScriptPtr3
|
|
{
|
|
int16_t Number;
|
|
uint8_t Type;
|
|
uint8_t ArgCount;
|
|
uint32_t Address;
|
|
};
|
|
|
|
// The intermediate ZDoom version
|
|
struct ScriptPtr1
|
|
{
|
|
int16_t Number;
|
|
uint16_t Type;
|
|
uint32_t Address;
|
|
uint32_t ArgCount;
|
|
};
|
|
|
|
// The old Hexen version
|
|
struct ScriptPtr2
|
|
{
|
|
uint32_t Number; // Type is Number / 1000
|
|
uint32_t Address;
|
|
uint32_t ArgCount;
|
|
};
|
|
|
|
struct ScriptFlagsPtr
|
|
{
|
|
uint16_t Number;
|
|
uint16_t Flags;
|
|
};
|
|
|
|
struct ScriptFunctionInFile
|
|
{
|
|
uint8_t ArgCount;
|
|
uint8_t LocalCount;
|
|
uint8_t HasReturnValue;
|
|
uint8_t ImportNum;
|
|
uint32_t Address;
|
|
};
|
|
|
|
struct ScriptFunction
|
|
{
|
|
uint8_t ArgCount;
|
|
uint8_t HasReturnValue;
|
|
uint8_t ImportNum;
|
|
int LocalCount;
|
|
uint32_t Address;
|
|
ACSLocalArrays LocalArrays;
|
|
};
|
|
|
|
// Script types
|
|
enum
|
|
{
|
|
SCRIPT_Closed = 0,
|
|
SCRIPT_Open = 1,
|
|
SCRIPT_Respawn = 2,
|
|
SCRIPT_Death = 3,
|
|
SCRIPT_Enter = 4,
|
|
SCRIPT_Pickup = 5,
|
|
SCRIPT_BlueReturn = 6,
|
|
SCRIPT_RedReturn = 7,
|
|
SCRIPT_WhiteReturn = 8,
|
|
SCRIPT_Lightning = 12,
|
|
SCRIPT_Unloading = 13,
|
|
SCRIPT_Disconnect = 14,
|
|
SCRIPT_Return = 15,
|
|
SCRIPT_Event = 16, // [BB]
|
|
SCRIPT_Kill = 17, // [JM]
|
|
SCRIPT_Reopen = 18, // [Nash]
|
|
};
|
|
|
|
// Script flags
|
|
enum
|
|
{
|
|
SCRIPTF_Net = 0x0001 // Safe to "puke" in multiplayer
|
|
};
|
|
|
|
enum ACSFormat { ACS_Old, ACS_Enhanced, ACS_LittleEnhanced, ACS_Unknown };
|
|
|
|
|
|
class FBehavior
|
|
{
|
|
public:
|
|
FBehavior ();
|
|
~FBehavior ();
|
|
bool Init(FLevelLocals *l, int lumpnum, FileReader * fr = NULL, int len = 0, int reallumpnum = -1);
|
|
|
|
bool IsGood ();
|
|
uint8_t *FindChunk (uint32_t id) const;
|
|
uint8_t *NextChunk (uint8_t *chunk) const;
|
|
const ScriptPtr *FindScript (int number) const;
|
|
void StartTypedScripts (uint16_t type, AActor *activator, bool always, int arg1, bool runNow);
|
|
uint32_t PC2Ofs (int *pc) const { return (uint32_t)((uint8_t *)pc - Data); }
|
|
int *Ofs2PC (uint32_t ofs) const { return (int *)(Data + ofs); }
|
|
int *Jump2PC (uint32_t jumpPoint) const { return Ofs2PC(JumpPoints[jumpPoint]); }
|
|
ACSFormat GetFormat() const { return Format; }
|
|
ScriptFunction *GetFunction (int funcnum, FBehavior *&module) const;
|
|
int GetArrayVal (int arraynum, int index) const;
|
|
void SetArrayVal (int arraynum, int index, int value);
|
|
inline bool CopyStringToArray(int arraynum, int index, int maxLength, const char * string);
|
|
|
|
int FindFunctionName (const char *funcname) const;
|
|
int FindMapVarName (const char *varname) const;
|
|
int FindMapArray (const char *arrayname) const;
|
|
int GetLibraryID () const { return LibraryID; }
|
|
int *GetScriptAddress (const ScriptPtr *ptr) const { return (int *)(ptr->Address + Data); }
|
|
int GetScriptIndex (const ScriptPtr *ptr) const { ptrdiff_t index = ptr - Scripts; return index >= NumScripts ? -1 : (int)index; }
|
|
ScriptPtr *GetScriptPtr(int index) const { return index >= 0 && index < NumScripts ? &Scripts[index] : NULL; }
|
|
int GetLumpNum() const { return LumpNum; }
|
|
int GetDataSize() const { return DataSize; }
|
|
const char *GetModuleName() const { return ModuleName; }
|
|
ACSProfileInfo *GetFunctionProfileData(int index) { return index >= 0 && index < NumFunctions ? &FunctionProfileData[index] : NULL; }
|
|
ACSProfileInfo *GetFunctionProfileData(ScriptFunction *func) { return GetFunctionProfileData((int)(func - (ScriptFunction *)Functions)); }
|
|
const char *LookupString (uint32_t index, bool forprint = false) const;
|
|
|
|
BoundsCheckingArray<int32_t *, NUM_MAPVARS> MapVars;
|
|
|
|
|
|
private:
|
|
struct ArrayInfo;
|
|
|
|
FLevelLocals *Level;
|
|
uint8_t *Data;
|
|
uint8_t *Chunks;
|
|
ScriptPtr *Scripts;
|
|
ScriptFunction *Functions;
|
|
ACSProfileInfo *FunctionProfileData;
|
|
ArrayInfo *ArrayStore;
|
|
ArrayInfo **Arrays;
|
|
|
|
ACSFormat Format;
|
|
int LumpNum;
|
|
int DataSize;
|
|
int NumScripts;
|
|
int NumFunctions;
|
|
int NumArrays;
|
|
int NumTotalArrays;
|
|
uint32_t StringTable;
|
|
uint32_t LibraryID;
|
|
bool ShouldLocalize;
|
|
|
|
int32_t MapVarStore[NUM_MAPVARS];
|
|
TArray<FBehavior *> Imports;
|
|
char ModuleName[9];
|
|
TArray<int> JumpPoints;
|
|
|
|
void LoadScriptsDirectory ();
|
|
|
|
static int SortScripts (const void *a, const void *b);
|
|
void UnencryptStrings ();
|
|
void UnescapeStringTable(uint8_t *chunkstart, uint8_t *datastart, bool haspadding);
|
|
int FindStringInChunk (uint32_t *chunk, const char *varname) const;
|
|
|
|
void SerializeVars (FSerializer &arc);
|
|
void SerializeVarSet (FSerializer &arc, int32_t *vars, int max);
|
|
|
|
void MarkMapVarStrings() const;
|
|
void LockMapVarStrings(int levelnum) const;
|
|
|
|
friend struct FBehaviorContainer;
|
|
};
|
|
|
|
struct FBehaviorContainer
|
|
{
|
|
FLevelLocals *Level;
|
|
TArray<FBehavior *> StaticModules;
|
|
|
|
FBehaviorContainer(FLevelLocals *l) : Level(l) {}
|
|
|
|
FBehavior *LoadModule(int lumpnum, FileReader *fr = nullptr, int len = 0, int reallumpnum = -1);
|
|
void LoadDefaultModules();
|
|
void UnloadModules();
|
|
bool CheckAllGood();
|
|
FBehavior *GetModule(int lib);
|
|
void SerializeModuleStates(FSerializer &arc);
|
|
void MarkLevelVarStrings();
|
|
void LockLevelVarStrings(int levelnum);
|
|
void UnlockLevelVarStrings(int levelnum);
|
|
|
|
const ScriptPtr *FindScript(int script, FBehavior *&module);
|
|
const char *LookupString(uint32_t index, bool forprint = false);
|
|
void StartTypedScripts(uint16_t type, AActor *activator, bool always, int arg1 = 0, bool runNow = false);
|
|
void StopMyScripts(AActor *actor);
|
|
void ArrangeScriptProfiles(TArray<ProfileCollector> &profiles);
|
|
void ArrangeFunctionProfiles(TArray<ProfileCollector> &profiles);
|
|
|
|
};
|
|
|
|
class DLevelScript;
|
|
|
|
class DACSThinker : public DThinker
|
|
{
|
|
DECLARE_CLASS(DACSThinker, DThinker)
|
|
HAS_OBJECT_POINTERS
|
|
public:
|
|
static const int DEFAULT_STAT = STAT_SCRIPTS;
|
|
void Construct() {}
|
|
~DACSThinker();
|
|
|
|
void Serialize(FSerializer &arc);
|
|
void Tick();
|
|
|
|
typedef TMap<int, DLevelScript *> ScriptMap;
|
|
ScriptMap RunningScripts; // Array of all synchronous scripts
|
|
|
|
void DumpScriptStatus();
|
|
void StopScriptsFor(AActor *actor);
|
|
|
|
private:
|
|
DLevelScript *LastScript = nullptr;
|
|
DLevelScript *Scripts = nullptr; // List of all running scripts
|
|
|
|
friend class DLevelScript;
|
|
friend class FBehavior;
|
|
friend struct FBehaviorContainer;
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif //__P_ACS_H__
|