mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-15 16:51:31 +00:00
- Added NULL checks to all places where class names are passed as DECORATE
parameters. - All DECORATE parameters are passed as expressions now. This change allows for compile time checks of all class names being used in DECORATE so many incorrect definitions may output warnings now. - Changed DECORATE sound and color parameters to use expressions. - Changed: S_StopChannel now resets the actor's sound flags. The previous bug made me think that delaying this until FMod calls the end of sound callback may simply be too late. SVN r1276 (trunk)
This commit is contained in:
parent
4c6b7f6752
commit
d753d41752
23 changed files with 1476 additions and 976 deletions
|
@ -1,8 +1,26 @@
|
|||
October 25, 2008 (Changes by Graf Zahl)
|
||||
- Added NULL checks to all places where class names are passed as DECORATE
|
||||
parameters.
|
||||
- All DECORATE parameters are passed as expressions now. This change allows
|
||||
for compile time checks of all class names being used in DECORATE so many
|
||||
incorrect definitions may output warnings now.
|
||||
|
||||
October 23, 2008 (Changes by Graf Zahl)
|
||||
- Changed: S_StopChannel now resets the actor's sound flags. The previous bug
|
||||
made me think that delaying this until FMod calls the end of sound callback
|
||||
may simply be too late.
|
||||
- Fixed: The high level sound code must not rely on FMod immediately returning
|
||||
the sound channel data when a sound is being stopped. This caused
|
||||
an endless loop when changing levels with Strife's Flamethrower active.
|
||||
|
||||
October 22, 2008 (Changes by Graf Zahl)
|
||||
- Changed DECORATE sound and color parameters to use expressions.
|
||||
|
||||
October 21, 2008 (Changes by Graf Zahl)
|
||||
- Added a proper function parser to the expression evaluator and converted
|
||||
sin/cos and action specials to use it. The old evaluator is gone now.
|
||||
- fixed some GCC problems with autosegs.
|
||||
|
||||
October 20, 2008
|
||||
- Game time is now frozen during screen wipes. This obsoletes the DEM_WIPEON
|
||||
and DEM_WIPEOFF commands. Fixes multimap demos desyncing when played back
|
||||
|
|
|
@ -627,7 +627,6 @@ add_executable( zdoom WIN32
|
|||
thingdef/thingdef_data.cpp
|
||||
thingdef/thingdef_exp.cpp
|
||||
thingdef/thingdef_expression.cpp
|
||||
thingdef/thingdef_main.cpp
|
||||
thingdef/thingdef_parse.cpp
|
||||
thingdef/thingdef_properties.cpp
|
||||
thingdef/thingdef_states.cpp
|
||||
|
|
|
@ -81,6 +81,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_StaffAttack)
|
|||
if (!weapon->DepleteAmmo (weapon->bAltFire))
|
||||
return;
|
||||
}
|
||||
if (puff == NULL) puff = PClass::FindClass(NAME_BulletPuff); // just to be sure
|
||||
angle = self->angle;
|
||||
angle += pr_sap.Random2() << 18;
|
||||
slope = P_AimLineAttack (self, angle, MELEERANGE, &linetarget);
|
||||
|
|
|
@ -654,11 +654,10 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireGrenade)
|
|||
if (!weapon->DepleteAmmo (weapon->bAltFire))
|
||||
return;
|
||||
|
||||
// Make it flash
|
||||
FState *jumpto = P_GetState(weapon, NULL, flash);
|
||||
|
||||
P_SetPsprite (player, ps_flash, jumpto);
|
||||
P_SetPsprite (player, ps_flash, flash);
|
||||
|
||||
if (grenadetype != NULL)
|
||||
{
|
||||
self->z += 32*FRACUNIT;
|
||||
grenade = P_SpawnSubMissile (self, grenadetype, self);
|
||||
self->z -= 32*FRACUNIT;
|
||||
|
@ -682,6 +681,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_FireGrenade)
|
|||
grenade->x += FixedMul (finecosine[an], 15*FRACUNIT);
|
||||
grenade->y += FixedMul (finesine[an], 15*FRACUNIT);
|
||||
}
|
||||
}
|
||||
|
||||
// The Almighty Sigil! ------------------------------------------------------
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
#include "templates.h"
|
||||
#include "cmdlib.h"
|
||||
|
||||
extern void LoadDecorations ();
|
||||
extern void LoadActors ();
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
@ -102,8 +102,8 @@ void FActorInfo::StaticInit ()
|
|||
sprites.Push (temp);
|
||||
}
|
||||
|
||||
Printf ("LoadDecorations: Load external actors.\n");
|
||||
LoadDecorations ();
|
||||
Printf ("LoadActors: Load actor definitions.\n");
|
||||
LoadActors ();
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
|
|
@ -2232,15 +2232,12 @@ enum ChaseFlags
|
|||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Chase)
|
||||
{
|
||||
ACTION_PARAM_START(3);
|
||||
ACTION_PARAM_STATE(i_melee, 0);
|
||||
ACTION_PARAM_STATE(i_missile, 1);
|
||||
ACTION_PARAM_STATE(melee, 0);
|
||||
ACTION_PARAM_STATE(missile, 1);
|
||||
ACTION_PARAM_INT(flags, 2);
|
||||
|
||||
if (i_melee != INT_MIN)
|
||||
if (melee != (FState*)-1)
|
||||
{
|
||||
FState *melee = P_GetState(self, CallingState, i_melee);
|
||||
FState *missile = P_GetState(self, CallingState, i_missile);
|
||||
|
||||
if (flags & CHF_RESURRECT && P_CheckForResurrection(self, false)) return;
|
||||
|
||||
A_DoChase(self, !!(flags&CHF_FASTCHASE), melee, missile, !(flags&CHF_NOPLAYACTIVE),
|
||||
|
|
|
@ -719,9 +719,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_LookEx)
|
|||
ACTION_PARAM_FIXED(maxseedist, 2);
|
||||
ACTION_PARAM_FIXED(maxheardist, 3);
|
||||
ACTION_PARAM_ANGLE(fov, 4);
|
||||
ACTION_PARAM_STATE(i_state, 5);
|
||||
|
||||
FState *seestate = P_GetState(self, CallingState, i_state);
|
||||
ACTION_PARAM_STATE(seestate, 5);
|
||||
|
||||
AActor *targ = NULL; // Shuts up gcc
|
||||
fixed_t dist;
|
||||
|
|
|
@ -53,8 +53,6 @@
|
|||
|
||||
#define NULL_STATE_INDEX 127
|
||||
|
||||
TArray<FName> JumpParameters;
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
@ -210,51 +208,6 @@ bool AActor::HasSpecialDeathStates () const
|
|||
return false;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Resolves a label parameter
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FState *P_GetState(AActor *self, FState *CallingState, int offset)
|
||||
{
|
||||
if (offset == 0 || offset == INT_MIN)
|
||||
{
|
||||
return NULL; // 0 means 'no state'
|
||||
}
|
||||
else if (offset>0)
|
||||
{
|
||||
if (CallingState == NULL) return NULL;
|
||||
return CallingState + offset;
|
||||
}
|
||||
else if (self != NULL)
|
||||
{
|
||||
FName *params = &JumpParameters[-offset];
|
||||
|
||||
FName classname = params[0];
|
||||
const PClass *cls;
|
||||
cls = classname==NAME_None? RUNTIME_TYPE(self) : PClass::FindClass(classname);
|
||||
if (cls==NULL || cls->ActorInfo==NULL) return NULL; // shouldn't happen
|
||||
|
||||
int numnames = (int)params[1];
|
||||
FState *jumpto = cls->ActorInfo->FindState(numnames, ¶ms[2]);
|
||||
if (jumpto == NULL)
|
||||
{
|
||||
const char *dot="";
|
||||
Printf("Jump target '");
|
||||
if (classname != NAME_None) Printf("%s::", classname.GetChars());
|
||||
for (int i=0;i<numnames;i++)
|
||||
{
|
||||
Printf("%s%s", dot, params[2+i].GetChars());
|
||||
dot = ".";
|
||||
}
|
||||
Printf("' not found in %s\n", self->GetClass()->TypeName.GetChars());
|
||||
}
|
||||
return jumpto;
|
||||
}
|
||||
else return NULL;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Creates a list of names from a string. Dots are used as separator
|
||||
|
|
|
@ -1910,6 +1910,13 @@ void S_StopChannel(FSoundChan *chan)
|
|||
{
|
||||
chan->ChanFlags |= CHAN_FORGETTABLE;
|
||||
}
|
||||
|
||||
if (chan->SourceType == SOURCE_Actor && chan->Actor != NULL)
|
||||
{
|
||||
chan->Actor->SoundChans &= ~(1 << chan->EntChannel);
|
||||
chan->Actor = NULL;
|
||||
}
|
||||
|
||||
GSnd->StopChannel(chan);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "cmdlib.h"
|
||||
#include "m_misc.h"
|
||||
#include "templates.h"
|
||||
#include "doomstat.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
|
@ -1023,3 +1024,91 @@ void FScanner::CheckOpen()
|
|||
I_FatalError ("SC_ call before SC_Open().");
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// a class that remembers a parser position
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FScriptPosition::FScriptPosition(const FScriptPosition &other)
|
||||
{
|
||||
FileName = other.FileName;
|
||||
ScriptLine = other.ScriptLine;
|
||||
}
|
||||
|
||||
FScriptPosition::FScriptPosition(FString fname, int line)
|
||||
{
|
||||
FileName = fname;
|
||||
ScriptLine = line;
|
||||
}
|
||||
|
||||
FScriptPosition::FScriptPosition(FScanner &sc)
|
||||
{
|
||||
FileName = sc.ScriptName;
|
||||
ScriptLine = sc.GetMessageLine();
|
||||
}
|
||||
|
||||
FScriptPosition &FScriptPosition::operator=(const FScriptPosition &other)
|
||||
{
|
||||
FileName = other.FileName;
|
||||
ScriptLine = other.ScriptLine;
|
||||
return *this;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FScriptPosition::Message
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void STACK_ARGS FScriptPosition::Message (int severity, const char *message, ...) const
|
||||
{
|
||||
FString composed;
|
||||
|
||||
if ((severity == MSG_DEBUG || severity == MSG_DEBUGLOG) && !developer) return;
|
||||
|
||||
if (message == NULL)
|
||||
{
|
||||
composed = "Bad syntax.";
|
||||
}
|
||||
else
|
||||
{
|
||||
va_list arglist;
|
||||
va_start (arglist, message);
|
||||
composed.VFormat (message, arglist);
|
||||
va_end (arglist);
|
||||
}
|
||||
const char *type = "";
|
||||
int level = PRINT_HIGH;
|
||||
|
||||
switch (severity)
|
||||
{
|
||||
default:
|
||||
return;
|
||||
|
||||
case MSG_WARNING:
|
||||
type = "warning";
|
||||
break;
|
||||
|
||||
case MSG_ERROR:
|
||||
type = "error";
|
||||
break;
|
||||
|
||||
case MSG_DEBUG:
|
||||
type = "message";
|
||||
break;
|
||||
|
||||
case MSG_DEBUGLOG:
|
||||
case MSG_LOG:
|
||||
type = "message";
|
||||
level = PRINT_LOG;
|
||||
break;
|
||||
|
||||
case MSG_FATAL:
|
||||
I_Error ("Script error, \"%s\" line %d:\n%s\n",
|
||||
FileName.GetChars(), ScriptLine, composed.GetChars());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
40
src/sc_man.h
40
src/sc_man.h
|
@ -215,4 +215,44 @@ enum
|
|||
TK_LastToken
|
||||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
enum
|
||||
{
|
||||
MSG_WARNING,
|
||||
MSG_FATAL,
|
||||
MSG_ERROR,
|
||||
MSG_DEBUG,
|
||||
MSG_LOG,
|
||||
MSG_DEBUGLOG
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// a class that remembers a parser position
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct FScriptPosition
|
||||
{
|
||||
FString FileName;
|
||||
int ScriptLine;
|
||||
|
||||
FScriptPosition()
|
||||
{
|
||||
ScriptLine=0;
|
||||
}
|
||||
FScriptPosition(const FScriptPosition &other);
|
||||
FScriptPosition(FString fname, int line);
|
||||
FScriptPosition(FScanner &sc);
|
||||
FScriptPosition &operator=(const FScriptPosition &other);
|
||||
void Message(int severity, const char *message,...) const;
|
||||
};
|
||||
|
||||
|
||||
#endif //__SC_MAN_H__
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
** Actor definitions
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2002-2007 Christoph Oelckers
|
||||
** Copyright 2004-2007 Randy Heit
|
||||
** Copyright 2002-2008 Christoph Oelckers
|
||||
** Copyright 2004-2008 Randy Heit
|
||||
** All rights reserved.
|
||||
**
|
||||
** Redistribution and use in source and binary forms, with or without
|
||||
|
@ -56,302 +56,29 @@
|
|||
#include "m_random.h"
|
||||
#include "i_system.h"
|
||||
#include "p_local.h"
|
||||
#include "v_palette.h"
|
||||
#include "doomerrors.h"
|
||||
#include "a_hexenglobal.h"
|
||||
#include "a_weaponpiece.h"
|
||||
#include "p_conversation.h"
|
||||
#include "v_text.h"
|
||||
#include "thingdef.h"
|
||||
#include "thingdef_exp.h"
|
||||
#include "a_sharedglobal.h"
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
void InitThingdef();
|
||||
void ParseDecorate (FScanner &sc);
|
||||
|
||||
// STATIC FUNCTION PROTOTYPES --------------------------------------------
|
||||
const PClass *QuestItemClasses[31];
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ParseParameter
|
||||
//
|
||||
// Parses aparameter - either a default in a function declaration
|
||||
// or an argument in a function call.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int ParseParameter(FScanner &sc, PClass *cls, char type, bool constant)
|
||||
{
|
||||
int v;
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case 'S':
|
||||
case 's': // Sound name
|
||||
sc.MustGetString();
|
||||
return S_FindSound(sc.String);
|
||||
|
||||
case 'M':
|
||||
case 'm': // Actor name
|
||||
case 'T':
|
||||
case 't': // String
|
||||
sc.SetEscape(true);
|
||||
sc.MustGetString();
|
||||
sc.SetEscape(false);
|
||||
return (int)(sc.String[0] ? FName(sc.String) : NAME_None);
|
||||
|
||||
case 'C':
|
||||
case 'c': // Color
|
||||
sc.MustGetString ();
|
||||
if (sc.Compare("none"))
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
else if (sc.Compare(""))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = V_GetColor (NULL, sc.String);
|
||||
// 0 needs to be the default so we have to mark the color.
|
||||
return MAKEARGB(1, RPART(c), GPART(c), BPART(c));
|
||||
}
|
||||
|
||||
case 'L':
|
||||
case 'l':
|
||||
{
|
||||
if (JumpParameters.Size()==0) JumpParameters.Push(NAME_None);
|
||||
|
||||
v = -(int)JumpParameters.Size();
|
||||
// This forces quotation marks around the state name.
|
||||
sc.MustGetToken(TK_StringConst);
|
||||
if (sc.String[0] == 0 || sc.Compare("None"))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (sc.Compare("*"))
|
||||
{
|
||||
if (constant) return INT_MIN;
|
||||
else sc.ScriptError("Invalid state name '*'");
|
||||
}
|
||||
FString statestring = sc.String; // ParseStateString(sc);
|
||||
const PClass *stype=NULL;
|
||||
int scope = statestring.IndexOf("::");
|
||||
if (scope >= 0)
|
||||
{
|
||||
FName scopename = FName(statestring, scope, false);
|
||||
if (scopename == NAME_Super)
|
||||
{
|
||||
// Super refers to the direct superclass
|
||||
scopename = cls->ParentClass->TypeName;
|
||||
}
|
||||
JumpParameters.Push(scopename);
|
||||
statestring = statestring.Right(statestring.Len()-scope-2);
|
||||
|
||||
stype = PClass::FindClass (scopename);
|
||||
if (stype == NULL)
|
||||
{
|
||||
sc.ScriptError ("%s is an unknown class.", scopename.GetChars());
|
||||
}
|
||||
if (!stype->IsDescendantOf (RUNTIME_CLASS(AActor)))
|
||||
{
|
||||
sc.ScriptError ("%s is not an actor class, so it has no states.", stype->TypeName.GetChars());
|
||||
}
|
||||
if (!stype->IsAncestorOf (cls))
|
||||
{
|
||||
sc.ScriptError ("%s is not derived from %s so cannot access its states.",
|
||||
cls->TypeName.GetChars(), stype->TypeName.GetChars());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No class name is stored. This allows 'virtual' jumps to
|
||||
// labels in subclasses.
|
||||
// It also means that the validity of the given state cannot
|
||||
// be checked here.
|
||||
JumpParameters.Push(NAME_None);
|
||||
}
|
||||
|
||||
TArray<FName> &names = MakeStateNameList(statestring);
|
||||
|
||||
if (stype != NULL)
|
||||
{
|
||||
if (!stype->ActorInfo->FindState(names.Size(), &names[0]))
|
||||
{
|
||||
sc.ScriptError("Jump to unknown state '%s' in class '%s'",
|
||||
statestring.GetChars(), stype->TypeName.GetChars());
|
||||
}
|
||||
}
|
||||
JumpParameters.Push((ENamedName)names.Size());
|
||||
for(unsigned i=0;i<names.Size();i++)
|
||||
{
|
||||
JumpParameters.Push(names[i]);
|
||||
}
|
||||
// No offsets here. The point of jumping to labels is to avoid such things!
|
||||
return v;
|
||||
}
|
||||
|
||||
case 'X':
|
||||
case 'x':
|
||||
v = ParseExpression (sc, false, cls);
|
||||
if (constant && !IsExpressionConst(v))
|
||||
{
|
||||
sc.ScriptError("Default parameter must be constant.");
|
||||
}
|
||||
return v;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ActorActionDef
|
||||
//
|
||||
// Parses an action function definition. A lot of this is essentially
|
||||
// documentation in the declaration for when I have a proper language
|
||||
// ready.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void ParseActionDef (FScanner &sc, PClass *cls)
|
||||
{
|
||||
enum
|
||||
{
|
||||
OPTIONAL = 1
|
||||
};
|
||||
|
||||
AFuncDesc *afd;
|
||||
FName funcname;
|
||||
FString args;
|
||||
TArray<int> DefaultParams;
|
||||
bool hasdefaults = false;
|
||||
|
||||
if (sc.LumpNum == -1 || Wads.GetLumpFile(sc.LumpNum) > 0)
|
||||
{
|
||||
sc.ScriptError ("action functions can only be imported by internal class and actor definitions!");
|
||||
}
|
||||
|
||||
sc.MustGetToken(TK_Native);
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
funcname = sc.String;
|
||||
afd = FindFunction(sc.String);
|
||||
if (afd == NULL)
|
||||
{
|
||||
sc.ScriptError ("The function '%s' has not been exported from the executable.", sc.String);
|
||||
}
|
||||
sc.MustGetToken('(');
|
||||
if (!sc.CheckToken(')'))
|
||||
{
|
||||
while (sc.TokenType != ')')
|
||||
{
|
||||
int flags = 0;
|
||||
char type = '@';
|
||||
|
||||
// Retrieve flags before type name
|
||||
for (;;)
|
||||
{
|
||||
if (sc.CheckToken(TK_Coerce) || sc.CheckToken(TK_Native))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Read the variable type
|
||||
sc.MustGetAnyToken();
|
||||
switch (sc.TokenType)
|
||||
{
|
||||
case TK_Bool:
|
||||
case TK_Int:
|
||||
case TK_Float:
|
||||
type = 'x';
|
||||
break;
|
||||
|
||||
case TK_Sound: type = 's'; break;
|
||||
case TK_String: type = 't'; break;
|
||||
case TK_Name: type = 't'; break;
|
||||
case TK_State: type = 'l'; break;
|
||||
case TK_Color: type = 'c'; break;
|
||||
case TK_Class:
|
||||
sc.MustGetToken('<');
|
||||
sc.MustGetToken(TK_Identifier); // Skip class name, since the parser doesn't care
|
||||
sc.MustGetToken('>');
|
||||
type = 'm';
|
||||
break;
|
||||
case TK_Ellipsis:
|
||||
type = '+';
|
||||
sc.MustGetToken(')');
|
||||
sc.UnGet();
|
||||
break;
|
||||
default:
|
||||
sc.ScriptError ("Unknown variable type %s", sc.TokenName(sc.TokenType, sc.String).GetChars());
|
||||
break;
|
||||
}
|
||||
// Read the optional variable name
|
||||
if (!sc.CheckToken(',') && !sc.CheckToken(')'))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
sc.UnGet();
|
||||
}
|
||||
|
||||
int def;
|
||||
if (sc.CheckToken('='))
|
||||
{
|
||||
hasdefaults = true;
|
||||
flags|=OPTIONAL;
|
||||
def = ParseParameter(sc, cls, type, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
def = 0;
|
||||
}
|
||||
DefaultParams.Push(def);
|
||||
|
||||
if (!(flags & OPTIONAL) && type != '+')
|
||||
{
|
||||
type -= 'a' - 'A';
|
||||
}
|
||||
args += type;
|
||||
sc.MustGetAnyToken();
|
||||
if (sc.TokenType != ',' && sc.TokenType != ')')
|
||||
{
|
||||
sc.ScriptError ("Expected ',' or ')' but got %s instead", sc.TokenName(sc.TokenType, sc.String).GetChars());
|
||||
}
|
||||
}
|
||||
}
|
||||
sc.MustGetToken(';');
|
||||
PSymbolActionFunction *sym = new PSymbolActionFunction(funcname);
|
||||
sym->Arguments = args;
|
||||
sym->Function = afd->Function;
|
||||
if (hasdefaults)
|
||||
{
|
||||
sym->defaultparameterindex = StateParameters.Size();
|
||||
for(unsigned int i = 0; i < DefaultParams.Size(); i++)
|
||||
StateParameters.Push(DefaultParams[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
sym->defaultparameterindex = -1;
|
||||
}
|
||||
if (cls->Symbols.AddSymbol (sym) == NULL)
|
||||
{
|
||||
delete sym;
|
||||
sc.ScriptError ("'%s' is already defined in class '%s'.",
|
||||
funcname.GetChars(), cls->TypeName.GetChars());
|
||||
}
|
||||
}
|
||||
PSymbolTable GlobalSymbols;
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Starts a new actor definition
|
||||
//
|
||||
//==========================================================================
|
||||
static FActorInfo *CreateNewActor(FName typeName, FName parentName, FName replaceName,
|
||||
FActorInfo *CreateNewActor(const FScriptPosition &sc, FName typeName, FName parentName, FName replaceName,
|
||||
int DoomEdNum, bool native)
|
||||
{
|
||||
const PClass *replacee = NULL;
|
||||
|
@ -366,15 +93,15 @@ static FActorInfo *CreateNewActor(FName typeName, FName parentName, FName replac
|
|||
|
||||
if (parent == NULL)
|
||||
{
|
||||
I_Error ("Parent type '%s' not found in %s", parentName.GetChars(), typeName.GetChars());
|
||||
sc.Message(MSG_FATAL, "Parent type '%s' not found in %s", parentName.GetChars(), typeName.GetChars());
|
||||
}
|
||||
else if (!parent->IsDescendantOf(RUNTIME_CLASS(AActor)))
|
||||
{
|
||||
I_Error ("Parent type '%s' is not an actor in %s", parentName.GetChars(), typeName.GetChars());
|
||||
sc.Message(MSG_FATAL, "Parent type '%s' is not an actor in %s", parentName.GetChars(), typeName.GetChars());
|
||||
}
|
||||
else if (parent->ActorInfo == NULL)
|
||||
{
|
||||
I_Error ("uninitialized parent type '%s' in %s", parentName.GetChars(), typeName.GetChars());
|
||||
sc.Message(MSG_FATAL, "uninitialized parent type '%s' in %s", parentName.GetChars(), typeName.GetChars());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -386,11 +113,11 @@ static FActorInfo *CreateNewActor(FName typeName, FName parentName, FName replac
|
|||
|
||||
if (replacee == NULL)
|
||||
{
|
||||
I_Error ("Replaced type '%s' not found in %s", replaceName.GetChars(), typeName.GetChars());
|
||||
sc.Message(MSG_FATAL, "Replaced type '%s' not found in %s", replaceName.GetChars(), typeName.GetChars());
|
||||
}
|
||||
else if (replacee->ActorInfo == NULL)
|
||||
{
|
||||
I_Error ("Replaced type '%s' is not an actor in %s", replaceName.GetChars(), typeName.GetChars());
|
||||
sc.Message(MSG_FATAL, "Replaced type '%s' is not an actor in %s", replaceName.GetChars(), typeName.GetChars());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,15 +126,15 @@ static FActorInfo *CreateNewActor(FName typeName, FName parentName, FName replac
|
|||
ti = (PClass*)PClass::FindClass(typeName);
|
||||
if (ti == NULL)
|
||||
{
|
||||
I_Error("Unknown native class '%s'", typeName.GetChars());
|
||||
sc.Message(MSG_FATAL, "Unknown native class '%s'", typeName.GetChars());
|
||||
}
|
||||
else if (ti != RUNTIME_CLASS(AActor) && ti->ParentClass->NativeClass() != parent->NativeClass())
|
||||
{
|
||||
I_Error("Native class '%s' does not inherit from '%s'", typeName.GetChars(), parentName.GetChars());
|
||||
sc.Message(MSG_FATAL, "Native class '%s' does not inherit from '%s'", typeName.GetChars(), parentName.GetChars());
|
||||
}
|
||||
else if (ti->ActorInfo != NULL)
|
||||
{
|
||||
I_Error("Redefinition of internal class '%s'", typeName.GetChars());
|
||||
sc.Message(MSG_FATAL, "Redefinition of internal class '%s'", typeName.GetChars());
|
||||
}
|
||||
ti->InitializeActorInfo();
|
||||
info = ti->ActorInfo;
|
||||
|
@ -444,145 +171,43 @@ static FActorInfo *CreateNewActor(FName typeName, FName parentName, FName replac
|
|||
|
||||
//==========================================================================
|
||||
//
|
||||
// Starts a new actor definition
|
||||
// Finalizes an actor definition
|
||||
//
|
||||
//==========================================================================
|
||||
static FActorInfo *ParseActorHeader(FScanner &sc, Baggage *bag)
|
||||
{
|
||||
FName typeName;
|
||||
FName parentName;
|
||||
FName replaceName;
|
||||
bool native = false;
|
||||
int DoomEdNum = -1;
|
||||
|
||||
// Get actor name
|
||||
sc.MustGetString();
|
||||
|
||||
char *colon = strchr(sc.String, ':');
|
||||
if (colon != NULL)
|
||||
void FinishActor(const FScriptPosition &sc, FActorInfo *info, Baggage &bag)
|
||||
{
|
||||
*colon++ = 0;
|
||||
}
|
||||
|
||||
typeName = sc.String;
|
||||
|
||||
// Do some tweaking so that a definition like 'Actor:Parent' which is read as a single token is recognized as well
|
||||
// without having resort to C-mode (which disallows periods in actor names.)
|
||||
if (colon == NULL)
|
||||
{
|
||||
sc.MustGetString ();
|
||||
if (sc.String[0]==':')
|
||||
{
|
||||
colon = sc.String + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (colon != NULL)
|
||||
{
|
||||
if (colon[0] == 0)
|
||||
{
|
||||
sc.MustGetString ();
|
||||
colon = sc.String;
|
||||
}
|
||||
}
|
||||
|
||||
if (colon == NULL)
|
||||
{
|
||||
sc.UnGet();
|
||||
}
|
||||
|
||||
parentName = colon;
|
||||
|
||||
// Check for "replaces"
|
||||
if (sc.CheckString ("replaces"))
|
||||
{
|
||||
// Get actor name
|
||||
sc.MustGetString ();
|
||||
replaceName = sc.String;
|
||||
}
|
||||
|
||||
// Now, after the actor names have been parsed, it is time to switch to C-mode
|
||||
// for the rest of the actor definition.
|
||||
sc.SetCMode (true);
|
||||
if (sc.CheckNumber())
|
||||
{
|
||||
if (sc.Number>=-1 && sc.Number<32768) DoomEdNum = sc.Number;
|
||||
else sc.ScriptError ("DoomEdNum must be in the range [-1,32767]");
|
||||
}
|
||||
|
||||
if (sc.CheckString("native"))
|
||||
{
|
||||
native = true;
|
||||
}
|
||||
AActor *defaults = (AActor*)info->Class->Defaults;
|
||||
|
||||
try
|
||||
{
|
||||
FActorInfo *info = CreateNewActor(typeName, parentName, replaceName, DoomEdNum, native);
|
||||
ResetBaggage (bag, info->Class->ParentClass);
|
||||
bag->Info = info;
|
||||
bag->Lumpnum = sc.LumpNum;
|
||||
#ifdef _DEBUG
|
||||
bag->ClassName = typeName;
|
||||
#endif
|
||||
return info;
|
||||
bag.statedef.FinishStates (info, defaults, bag.StateArray);
|
||||
}
|
||||
catch (CRecoverableError &err)
|
||||
{
|
||||
sc.ScriptError("%s", err.GetMessage());
|
||||
return NULL;
|
||||
sc.Message(MSG_FATAL, "%s", err.GetMessage());
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Reads an actor definition
|
||||
//
|
||||
//==========================================================================
|
||||
void ParseActor(FScanner &sc)
|
||||
bag.statedef.InstallStates (info, defaults);
|
||||
bag.StateArray.Clear ();
|
||||
if (bag.DropItemSet)
|
||||
{
|
||||
FActorInfo * info=NULL;
|
||||
Baggage bag;
|
||||
|
||||
info = ParseActorHeader(sc, &bag);
|
||||
sc.MustGetToken('{');
|
||||
while (sc.MustGetAnyToken(), sc.TokenType != '}')
|
||||
if (bag.DropItemList == NULL)
|
||||
{
|
||||
switch (sc.TokenType)
|
||||
if (info->Class->Meta.GetMetaInt (ACMETA_DropItems) != 0)
|
||||
{
|
||||
case TK_Action:
|
||||
ParseActionDef (sc, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Const:
|
||||
ParseConstant (sc, &info->Class->Symbols, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Enum:
|
||||
ParseEnum (sc, &info->Class->Symbols, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Native:
|
||||
ParseVariable (sc, &info->Class->Symbols, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Identifier:
|
||||
// other identifier related checks here
|
||||
case TK_Projectile: // special case: both keyword and property name
|
||||
ParseActorProperty(sc, bag);
|
||||
break;
|
||||
|
||||
case '+':
|
||||
case '-':
|
||||
ParseActorFlag(sc, bag, sc.TokenType);
|
||||
break;
|
||||
|
||||
default:
|
||||
sc.ScriptError("Unexpected '%s' in definition of '%s'", sc.String, bag.Info->Class->TypeName.GetChars());
|
||||
break;
|
||||
info->Class->Meta.SetMetaInt (ACMETA_DropItems, 0);
|
||||
}
|
||||
}
|
||||
FinishActor(sc, info, bag);
|
||||
sc.SetCMode (false);
|
||||
else
|
||||
{
|
||||
info->Class->Meta.SetMetaInt (ACMETA_DropItems,
|
||||
StoreDropItemChain(bag.DropItemList));
|
||||
}
|
||||
}
|
||||
if (info->Class->IsDescendantOf (RUNTIME_CLASS(AInventory)))
|
||||
{
|
||||
defaults->flags |= MF_SPECIAL;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
@ -617,10 +242,12 @@ static int ResolvePointer(const PClass **pPtr, const PClass *owner, const PClass
|
|||
}
|
||||
|
||||
|
||||
void FinishThingdef()
|
||||
static void FinishThingdef()
|
||||
{
|
||||
unsigned int i;
|
||||
int errorcount=0;
|
||||
int errorcount;
|
||||
|
||||
errorcount = StateParams.ResolveAll();
|
||||
|
||||
for (i = 0;i < PClass::m_Types.Size(); i++)
|
||||
{
|
||||
|
@ -747,3 +374,27 @@ void FinishThingdef()
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// LoadActors
|
||||
//
|
||||
// Called from FActor::StaticInit()
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void LoadActors ()
|
||||
{
|
||||
int lastlump, lump;
|
||||
|
||||
InitThingdef();
|
||||
lastlump = 0;
|
||||
while ((lump = Wads.FindLump ("DECORATE", &lastlump)) != -1)
|
||||
{
|
||||
FScanner sc(lump);
|
||||
ParseDecorate (sc);
|
||||
}
|
||||
FinishThingdef();
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
#include "doomtype.h"
|
||||
#include "info.h"
|
||||
#include "s_sound.h"
|
||||
#include "sc_man.h"
|
||||
|
||||
|
||||
class FScanner;
|
||||
|
@ -55,9 +57,7 @@ public:
|
|||
// State parser
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
extern TArray<int> StateParameters;
|
||||
extern TArray<FName> JumpParameters;
|
||||
class FxExpression;
|
||||
|
||||
struct FStateLabels;
|
||||
|
||||
|
@ -112,6 +112,38 @@ public:
|
|||
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct FStateExpression
|
||||
{
|
||||
FxExpression *expr;
|
||||
const PClass *owner;
|
||||
bool constant;
|
||||
bool cloned;
|
||||
};
|
||||
|
||||
class FStateExpressions
|
||||
{
|
||||
TArray<FStateExpression> expressions;
|
||||
|
||||
public:
|
||||
~FStateExpressions();
|
||||
int Add(FxExpression *x, const PClass *o, bool c);
|
||||
int Reserve(int num, const PClass *cls);
|
||||
void Set(int num, FxExpression *x);
|
||||
void Copy(int dest, int src, int cnt);
|
||||
int ResolveAll();
|
||||
FxExpression *Get(int no);
|
||||
int Size() { return expressions.Size(); }
|
||||
};
|
||||
|
||||
extern FStateExpressions StateParams;
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Extra info maintained while defining an actor.
|
||||
|
@ -161,7 +193,6 @@ AFuncDesc * FindFunction(const char * string);
|
|||
|
||||
|
||||
|
||||
FState *P_GetState(AActor *self, FState *CallingState, int offset);
|
||||
int ParseStates(FScanner &sc, FActorInfo *actor, AActor *defaults, Baggage &bag);
|
||||
|
||||
PSymbolActionFunction *FindGlobalActionFunction(const char *name);
|
||||
|
@ -172,23 +203,13 @@ PSymbolActionFunction *FindGlobalActionFunction(const char *name);
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
void ParseActorProperty(FScanner &sc, Baggage &bag);
|
||||
FActorInfo *CreateNewActor(const FScriptPosition &sc, FName typeName, FName parentName, FName replaceName,
|
||||
int DoomEdNum, bool native);
|
||||
|
||||
void HandleActorFlag(FScanner &sc, Baggage &bag, const char *part1, const char *part2, int mod);
|
||||
void ParseActorFlag (FScanner &sc, Baggage &bag, int mod);
|
||||
void FinishActor(FScanner &sc, FActorInfo *info, Baggage &bag);
|
||||
void FinishActor(const FScriptPosition &sc, FActorInfo *info, Baggage &bag);
|
||||
FxExpression *ParseParameter(FScanner &sc, PClass *cls, char type, bool constant);
|
||||
|
||||
void ParseConstant (FScanner &sc, PSymbolTable *symt, PClass *cls);
|
||||
void ParseVariable (FScanner &sc, PSymbolTable *symt, PClass *cls);
|
||||
void ParseEnum (FScanner &sc, PSymbolTable *symt, PClass *cls);
|
||||
int ParseParameter(FScanner &sc, PClass *cls, char type, bool constant);
|
||||
|
||||
|
||||
int ParseExpression (FScanner &sc, bool _not, PClass *cls);
|
||||
|
||||
bool IsExpressionConst(int id);
|
||||
int EvalExpressionI (int id, AActor *self);
|
||||
double EvalExpressionF (int id, AActor *self);
|
||||
fixed_t EvalExpressionFix (int id, AActor *self);
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -360,40 +381,45 @@ struct StateCallData
|
|||
MSVC_ASEG AFuncDesc *infoptr_##cls##_##name GCC_ASEG = &info_##cls##_##name; \
|
||||
void AFP_##name (AActor *self, FState *CallingState, int ParameterIndex, StateCallData *statecall)
|
||||
|
||||
#define DECLARE_PARAMINFO FState *CallingState, int ParameterIndex, StateCallData *statecall
|
||||
#define PUSH_PARAMINFO CallingState, ParameterIndex, statecall
|
||||
#define DECLARE_PARAMINFO AActor *self, FState *CallingState, int ParameterIndex, StateCallData *statecall
|
||||
#define PUSH_PARAMINFO self, CallingState, ParameterIndex, statecall
|
||||
|
||||
#define CALL_ACTION(name,self) AF_##name(self, NULL, 0, NULL)
|
||||
|
||||
|
||||
int EvalExpressionI (DWORD x, AActor *self);
|
||||
int EvalExpressionCol (DWORD x, AActor *self);
|
||||
FSoundID EvalExpressionSnd (DWORD x, AActor *self);
|
||||
double EvalExpressionF (DWORD x, AActor *self);
|
||||
fixed_t EvalExpressionFix (DWORD x, AActor *self);
|
||||
FState *EvalExpressionState (DWORD x, AActor *self);
|
||||
const PClass *EvalExpressionClass (DWORD x, AActor *self);
|
||||
FName EvalExpressionName (DWORD x, AActor *self);
|
||||
|
||||
#define ACTION_PARAM_START(count)
|
||||
|
||||
#define ACTION_PARAM_CONST(var, i) \
|
||||
int var = StateParameters[ParameterIndex+i];
|
||||
#define ACTION_PARAM_INT(var, i) \
|
||||
int var = EvalExpressionI(StateParameters[ParameterIndex+i], self);
|
||||
int var = EvalExpressionI(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_BOOL(var,i) \
|
||||
bool var = !!EvalExpressionI(StateParameters[ParameterIndex+i], self);
|
||||
bool var = !!EvalExpressionI(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_FIXED(var,i) \
|
||||
fixed_t var = EvalExpressionFix(StateParameters[ParameterIndex+i], self);
|
||||
fixed_t var = EvalExpressionFix(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_FLOAT(var,i) \
|
||||
float var = EvalExpressionF(StateParameters[ParameterIndex+i], self);
|
||||
float var = EvalExpressionF(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_CLASS(var,i) \
|
||||
const PClass *var = PClass::FindClass(ENamedName(StateParameters[ParameterIndex+i]));
|
||||
const PClass *var = EvalExpressionClass(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_STATE(var,i) \
|
||||
int var = StateParameters[ParameterIndex+i];
|
||||
FState *var = EvalExpressionState(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_COLOR(var,i) \
|
||||
PalEntry var = StateParameters[ParameterIndex+i];
|
||||
PalEntry var = EvalExpressionCol(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_SOUND(var,i) \
|
||||
FSoundID var = StateParameters[ParameterIndex+i];
|
||||
FSoundID var = EvalExpressionSnd(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_STRING(var,i) \
|
||||
const char *var = FName(ENamedName(StateParameters[ParameterIndex+i]));
|
||||
const char *var = EvalExpressionName(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_NAME(var,i) \
|
||||
FName var = ENamedName(StateParameters[ParameterIndex+i]);
|
||||
#define ACTION_PARAM_VARARG(var, i) \
|
||||
int *var = &StateParameters[ParameterIndex+i];
|
||||
|
||||
FName var = EvalExpressionName(ParameterIndex+i, self);
|
||||
#define ACTION_PARAM_ANGLE(var,i) \
|
||||
angle_t var = angle_t(EvalExpressionF(StateParameters[ParameterIndex+i], self)*ANGLE_90/90.f);
|
||||
angle_t var = angle_t(EvalExpressionF(ParameterIndex+i, self)*ANGLE_90/90.f);
|
||||
|
||||
#define ACTION_SET_RESULT(v) if (statecall != NULL) statecall->Result = v;
|
||||
|
||||
|
|
|
@ -232,6 +232,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_BasicAttack)
|
|||
ACTION_PARAM_CLASS(MissileType, 2);
|
||||
ACTION_PARAM_FIXED(MissileHeight, 3);
|
||||
|
||||
if (MissileType == NULL) return;
|
||||
DoAttack(self, true, true, MeleeDamage, MeleeSound, MissileType, MissileHeight);
|
||||
}
|
||||
|
||||
|
@ -359,31 +360,24 @@ DEFINE_ACTION_FUNCTION(AActor, A_BulletAttack)
|
|||
// Do the state jump
|
||||
//
|
||||
//==========================================================================
|
||||
static void DoJump(AActor * self, FState * CallingState, int offset, StateCallData *statecall)
|
||||
static void DoJump(AActor * self, FState * CallingState, FState *jumpto, StateCallData *statecall)
|
||||
{
|
||||
if (jumpto == NULL) return;
|
||||
|
||||
if (statecall != NULL)
|
||||
{
|
||||
FState *jumpto = P_GetState(statecall->Item, CallingState, offset);
|
||||
if (jumpto == NULL) return;
|
||||
statecall->State = jumpto;
|
||||
}
|
||||
else if (self->player != NULL && CallingState == self->player->psprites[ps_weapon].state)
|
||||
{
|
||||
FState *jumpto = P_GetState(self->player->ReadyWeapon, CallingState, offset);
|
||||
if (jumpto == NULL) return;
|
||||
P_SetPsprite(self->player, ps_weapon, jumpto);
|
||||
}
|
||||
else if (self->player != NULL && CallingState == self->player->psprites[ps_flash].state)
|
||||
{
|
||||
FState *jumpto = P_GetState(self->player->ReadyWeapon, CallingState, offset);
|
||||
if (jumpto == NULL) return;
|
||||
P_SetPsprite(self->player, ps_flash, jumpto);
|
||||
}
|
||||
else if (CallingState == self->state)
|
||||
{
|
||||
FState *jumpto = P_GetState(self, CallingState, offset);
|
||||
if (jumpto == NULL) return;
|
||||
self->SetState (jumpto);
|
||||
}
|
||||
else
|
||||
|
@ -405,20 +399,14 @@ static void DoJump(AActor * self, FState * CallingState, int offset, StateCallDa
|
|||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_Jump)
|
||||
{
|
||||
ACTION_PARAM_START(3);
|
||||
ACTION_PARAM_CONST(count, 0);
|
||||
ACTION_PARAM_INT(count, 0);
|
||||
ACTION_PARAM_INT(maxchance, 1);
|
||||
ACTION_PARAM_VARARG(jumps, 2);
|
||||
|
||||
if (count >= 2 && (maxchance >= 256 || pr_cajump() < maxchance))
|
||||
{
|
||||
if (count == 2)
|
||||
{
|
||||
ACTION_JUMP(*jumps);
|
||||
}
|
||||
else
|
||||
{
|
||||
ACTION_JUMP(jumps[(pr_cajump() % (count - 1))]);
|
||||
}
|
||||
int jumps = 2 + (count == 2? 0 : (pr_cajump() % (count - 1)));
|
||||
ACTION_PARAM_STATE(jumpto, jumps);
|
||||
ACTION_JUMP(jumpto);
|
||||
}
|
||||
ACTION_SET_RESULT(false); // Jumps should never set the result for inventory state chains!
|
||||
}
|
||||
|
@ -482,7 +470,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfCloser)
|
|||
// State jump function
|
||||
//
|
||||
//==========================================================================
|
||||
void DoJumpIfInventory(AActor * self, AActor * owner, DECLARE_PARAMINFO)
|
||||
void DoJumpIfInventory(AActor * owner, DECLARE_PARAMINFO)
|
||||
{
|
||||
ACTION_PARAM_START(3);
|
||||
ACTION_PARAM_CLASS(Type, 0);
|
||||
|
@ -504,12 +492,12 @@ void DoJumpIfInventory(AActor * self, AActor * owner, DECLARE_PARAMINFO)
|
|||
|
||||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInventory)
|
||||
{
|
||||
DoJumpIfInventory(self, self, PUSH_PARAMINFO);
|
||||
DoJumpIfInventory(self, PUSH_PARAMINFO);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_JumpIfInTargetInventory)
|
||||
{
|
||||
DoJumpIfInventory(self, self->target, PUSH_PARAMINFO);
|
||||
DoJumpIfInventory(self->target, PUSH_PARAMINFO);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
|
@ -1143,7 +1131,7 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun)
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
static void DoGiveInventory(AActor * self, AActor * receiver, DECLARE_PARAMINFO)
|
||||
static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO)
|
||||
{
|
||||
ACTION_PARAM_START(2);
|
||||
ACTION_PARAM_CLASS(mi, 0);
|
||||
|
@ -1184,12 +1172,12 @@ static void DoGiveInventory(AActor * self, AActor * receiver, DECLARE_PARAMINFO)
|
|||
|
||||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory)
|
||||
{
|
||||
DoGiveInventory(self, self, PUSH_PARAMINFO);
|
||||
DoGiveInventory(self, PUSH_PARAMINFO);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget)
|
||||
{
|
||||
DoGiveInventory(self, self->target, PUSH_PARAMINFO);
|
||||
DoGiveInventory(self->target, PUSH_PARAMINFO);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -1198,13 +1186,13 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveToTarget)
|
|||
//
|
||||
//===========================================================================
|
||||
|
||||
void DoTakeInventory(AActor * self, AActor * receiver, DECLARE_PARAMINFO)
|
||||
void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO)
|
||||
{
|
||||
ACTION_PARAM_START(2);
|
||||
ACTION_PARAM_CLASS(item, 0);
|
||||
ACTION_PARAM_INT(amount, 1);
|
||||
|
||||
if (receiver == NULL) return;
|
||||
if (item == NULL || receiver == NULL) return;
|
||||
|
||||
bool res = false;
|
||||
|
||||
|
@ -1228,12 +1216,12 @@ void DoTakeInventory(AActor * self, AActor * receiver, DECLARE_PARAMINFO)
|
|||
|
||||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory)
|
||||
{
|
||||
DoTakeInventory(self, self, PUSH_PARAMINFO);
|
||||
DoTakeInventory(self, PUSH_PARAMINFO);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeFromTarget)
|
||||
{
|
||||
DoTakeInventory(self, self->target, PUSH_PARAMINFO);
|
||||
DoTakeInventory(self->target, PUSH_PARAMINFO);
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
@ -1463,6 +1451,8 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_ThrowGrenade)
|
|||
ACTION_PARAM_FIXED(zmom, 3);
|
||||
ACTION_PARAM_BOOL(useammo, 4);
|
||||
|
||||
if (missile == NULL) return;
|
||||
|
||||
if (ACTION_CALL_FROM_WEAPON())
|
||||
{
|
||||
// Used from a weapon so use some ammo
|
||||
|
@ -1529,7 +1519,11 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_SelectWeapon)
|
|||
ACTION_PARAM_START(1);
|
||||
ACTION_PARAM_CLASS(cls, 0);
|
||||
|
||||
if (self->player == NULL) return;
|
||||
if (cls == NULL || self->player == NULL)
|
||||
{
|
||||
ACTION_SET_RESULT(false);
|
||||
return;
|
||||
}
|
||||
|
||||
AWeapon * weaponitem = static_cast<AWeapon*>(self->FindInventory(cls));
|
||||
|
||||
|
@ -1714,12 +1708,15 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_DropInventory)
|
|||
ACTION_PARAM_START(1);
|
||||
ACTION_PARAM_CLASS(drop, 0);
|
||||
|
||||
if (drop)
|
||||
{
|
||||
AInventory * inv = self->FindInventory(drop);
|
||||
if (inv)
|
||||
{
|
||||
self->DropInventory(inv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//===========================================================================
|
||||
|
|
|
@ -84,11 +84,6 @@ FxExpression *ParseExpression (FScanner &sc, PClass *cls)
|
|||
return data;
|
||||
}
|
||||
|
||||
int ParseExpression (FScanner &sc, bool _not, PClass *cls)
|
||||
{
|
||||
return AddExpression(ParseExpression (sc, cls));
|
||||
}
|
||||
|
||||
static FxExpression *ParseExpressionM (FScanner &sc, const PClass *cls)
|
||||
{
|
||||
FxExpression *condition = ParseExpressionL (sc, cls);
|
||||
|
|
|
@ -56,65 +56,11 @@ extern PSymbolTable GlobalSymbols;
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
enum
|
||||
{
|
||||
MSG_WARNING,
|
||||
MSG_ERROR,
|
||||
MSG_DEBUG,
|
||||
MSG_LOG,
|
||||
MSG_DEBUGLOG
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct FScriptPosition
|
||||
{
|
||||
FString FileName;
|
||||
int ScriptLine;
|
||||
|
||||
FScriptPosition()
|
||||
{
|
||||
ScriptLine=0;
|
||||
}
|
||||
FScriptPosition(const FScriptPosition &other)
|
||||
{
|
||||
FileName = other.FileName;
|
||||
ScriptLine = other.ScriptLine;
|
||||
}
|
||||
FScriptPosition(FString fname, int line)
|
||||
{
|
||||
FileName = fname;
|
||||
ScriptLine = line;
|
||||
}
|
||||
FScriptPosition(FScanner &sc)
|
||||
{
|
||||
FileName = sc.ScriptName;
|
||||
ScriptLine = sc.GetMessageLine();
|
||||
}
|
||||
FScriptPosition &operator=(const FScriptPosition &other)
|
||||
{
|
||||
FileName = other.FileName;
|
||||
ScriptLine = other.ScriptLine;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Message(int severity, const char *message,...) const;
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
struct FCompileContext
|
||||
{
|
||||
const PClass *cls;
|
||||
bool lax;
|
||||
bool isconst;
|
||||
|
||||
|
||||
PSymbol *FindInClass(FName identifier)
|
||||
|
@ -143,26 +89,51 @@ struct ExpVal
|
|||
void *pointer;
|
||||
};
|
||||
|
||||
int GetInt()
|
||||
int GetInt() const
|
||||
{
|
||||
return Type == VAL_Int? Int : Type == VAL_Float? int(Float) : 0;
|
||||
}
|
||||
|
||||
double GetFloat()
|
||||
double GetFloat() const
|
||||
{
|
||||
return Type == VAL_Int? double(Int) : Type == VAL_Float? Float : 0;
|
||||
}
|
||||
|
||||
bool GetBool()
|
||||
bool GetBool() const
|
||||
{
|
||||
return Type == VAL_Int? !!Int : Type == VAL_Float? Float!=0. : false;
|
||||
return (Type == VAL_Int || Type == VAL_Sound) ? !!Int : Type == VAL_Float? Float!=0. : false;
|
||||
}
|
||||
|
||||
template<class T> T *GetPointer()
|
||||
template<class T> T *GetPointer() const
|
||||
{
|
||||
return Type == VAL_Object || Type == VAL_Pointer? (T*)pointer : NULL;
|
||||
}
|
||||
|
||||
FSoundID GetSoundID() const
|
||||
{
|
||||
return Type == VAL_Sound? Int : 0;
|
||||
}
|
||||
|
||||
int GetColor() const
|
||||
{
|
||||
return Type == VAL_Color? Int : 0;
|
||||
}
|
||||
|
||||
FName GetName() const
|
||||
{
|
||||
return Type == VAL_Name? ENamedName(Int) : NAME_None;
|
||||
}
|
||||
|
||||
FState *GetState() const
|
||||
{
|
||||
return Type == VAL_State? (FState*)pointer : NULL;
|
||||
}
|
||||
|
||||
const PClass *GetClass() const
|
||||
{
|
||||
return Type == VAL_Class? (const PClass *)pointer : NULL;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
@ -172,7 +143,7 @@ struct ExpVal
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
struct FxExpression
|
||||
class FxExpression
|
||||
{
|
||||
protected:
|
||||
FxExpression(const FScriptPosition &pos)
|
||||
|
@ -181,12 +152,9 @@ protected:
|
|||
ScriptPosition = pos;
|
||||
}
|
||||
public:
|
||||
virtual ~FxExpression() {}
|
||||
virtual FxExpression *Resolve(FCompileContext &ctx);
|
||||
FxExpression *ResolveAsBoolean(FCompileContext &ctx)
|
||||
{
|
||||
// This will need more handling if other types than Int and Float are added
|
||||
return Resolve(ctx);
|
||||
}
|
||||
FxExpression *ResolveAsBoolean(FCompileContext &ctx);
|
||||
|
||||
virtual ExpVal EvalExpression (AActor *self);
|
||||
virtual bool isConstant() const;
|
||||
|
@ -194,7 +162,7 @@ public:
|
|||
|
||||
FScriptPosition ScriptPosition;
|
||||
FExpressionType ValueType;
|
||||
protected:
|
||||
|
||||
bool isresolved;
|
||||
};
|
||||
|
||||
|
@ -263,18 +231,50 @@ public:
|
|||
{
|
||||
ValueType = value.Type = VAL_Int;
|
||||
value.Int = val;
|
||||
isresolved = true;
|
||||
}
|
||||
|
||||
FxConstant(double val, const FScriptPosition &pos) : FxExpression(pos)
|
||||
{
|
||||
ValueType = value.Type = VAL_Float;
|
||||
value.Float = val;
|
||||
isresolved = true;
|
||||
}
|
||||
|
||||
FxConstant(FSoundID val, const FScriptPosition &pos) : FxExpression(pos)
|
||||
{
|
||||
ValueType = value.Type = VAL_Sound;
|
||||
value.Int = val;
|
||||
isresolved = true;
|
||||
}
|
||||
|
||||
FxConstant(FName val, const FScriptPosition &pos) : FxExpression(pos)
|
||||
{
|
||||
ValueType = value.Type = VAL_Name;
|
||||
value.Int = val;
|
||||
isresolved = true;
|
||||
}
|
||||
|
||||
FxConstant(ExpVal cv, const FScriptPosition &pos) : FxExpression(pos)
|
||||
{
|
||||
value = cv;
|
||||
ValueType = cv.Type;
|
||||
isresolved = true;
|
||||
}
|
||||
|
||||
FxConstant(const PClass *val, const FScriptPosition &pos) : FxExpression(pos)
|
||||
{
|
||||
value.pointer = (void*)val;
|
||||
ValueType = val;
|
||||
value.Type = VAL_Class;
|
||||
isresolved = true;
|
||||
}
|
||||
|
||||
FxConstant(FState *state, const FScriptPosition &pos) : FxExpression(pos)
|
||||
{
|
||||
value.pointer = state;
|
||||
ValueType = value.Type = VAL_State;
|
||||
isresolved = true;
|
||||
}
|
||||
|
||||
static FxExpression *MakeConstant(PSymbol *sym, const FScriptPosition &pos);
|
||||
|
@ -704,10 +704,64 @@ public:
|
|||
};
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class FxClassTypeCast : public FxExpression
|
||||
{
|
||||
const PClass *desttype;
|
||||
FxExpression *basex;
|
||||
|
||||
public:
|
||||
|
||||
FxClassTypeCast(const PClass *dtype, FxExpression *x);
|
||||
~FxClassTypeCast();
|
||||
FxExpression *Resolve(FCompileContext&);
|
||||
ExpVal EvalExpression (AActor *self);
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Only used to resolve the old jump by index feature of DECORATE
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class FxStateByIndex : public FxExpression
|
||||
{
|
||||
int index;
|
||||
|
||||
public:
|
||||
|
||||
FxStateByIndex(int i, const FScriptPosition &pos) : FxExpression(pos)
|
||||
{
|
||||
index = i;
|
||||
}
|
||||
FxExpression *Resolve(FCompileContext&);
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
class FxMultiNameState : public FxExpression
|
||||
{
|
||||
const PClass *scope;
|
||||
TArray<FName> names;
|
||||
public:
|
||||
|
||||
FxMultiNameState(const char *statestring, const FScriptPosition &pos);
|
||||
FxExpression *Resolve(FCompileContext&);
|
||||
ExpVal EvalExpression (AActor *self);
|
||||
};
|
||||
|
||||
|
||||
|
||||
FxExpression *ParseExpression (FScanner &sc, PClass *cls);
|
||||
int AddExpression (FxExpression *data);
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -51,7 +51,6 @@
|
|||
#include "thingdef_exp.h"
|
||||
#include "autosegs.h"
|
||||
|
||||
extern int thingdef_terminate;
|
||||
int testglobalvar = 1337; // just for having one global variable to test with
|
||||
DEFINE_GLOBAL_VARIABLE(testglobalvar)
|
||||
|
||||
|
@ -74,19 +73,6 @@ DEFINE_MEMBER_VARIABLE(momx, AActor)
|
|||
DEFINE_MEMBER_VARIABLE(momy, AActor)
|
||||
DEFINE_MEMBER_VARIABLE(momz, AActor)
|
||||
|
||||
static TDeletingArray<FxExpression *> StateExpressions;
|
||||
|
||||
int AddExpression (FxExpression *data)
|
||||
{
|
||||
if (StateExpressions.Size()==0)
|
||||
{
|
||||
// StateExpressions[0] always is const 0;
|
||||
FxExpression *data = new FxConstant(0, FScriptPosition());
|
||||
StateExpressions.Push (data);
|
||||
}
|
||||
return StateExpressions.Push (data);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// EvalExpression
|
||||
|
@ -95,32 +81,44 @@ int AddExpression (FxExpression *data)
|
|||
//==========================================================================
|
||||
|
||||
|
||||
bool IsExpressionConst(int id)
|
||||
int EvalExpressionI (DWORD xi, AActor *self)
|
||||
{
|
||||
if (StateExpressions.Size() <= (unsigned int)id) return false;
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
return StateExpressions[id]->isConstant();
|
||||
return x->EvalExpression (self).GetInt();
|
||||
}
|
||||
|
||||
int EvalExpressionI (int id, AActor *self)
|
||||
int EvalExpressionCol (DWORD xi, AActor *self)
|
||||
{
|
||||
if (StateExpressions.Size() <= (unsigned int)id) return 0;
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
return StateExpressions[id]->EvalExpression (self).GetInt();
|
||||
return x->EvalExpression (self).GetColor();
|
||||
}
|
||||
|
||||
double EvalExpressionF (int id, AActor *self)
|
||||
FSoundID EvalExpressionSnd (DWORD xi, AActor *self)
|
||||
{
|
||||
if (StateExpressions.Size() <= (unsigned int)id) return 0.f;
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
return StateExpressions[id]->EvalExpression (self).GetFloat();
|
||||
return x->EvalExpression (self).GetSoundID();
|
||||
}
|
||||
|
||||
fixed_t EvalExpressionFix (int id, AActor *self)
|
||||
double EvalExpressionF (DWORD xi, AActor *self)
|
||||
{
|
||||
if (StateExpressions.Size() <= (unsigned int)id) return 0;
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
ExpVal val = StateExpressions[id]->EvalExpression (self);
|
||||
return x->EvalExpression (self).GetFloat();
|
||||
}
|
||||
|
||||
fixed_t EvalExpressionFix (DWORD xi, AActor *self)
|
||||
{
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
ExpVal val = x->EvalExpression (self);
|
||||
|
||||
switch (val.Type)
|
||||
{
|
||||
|
@ -133,6 +131,31 @@ fixed_t EvalExpressionFix (int id, AActor *self)
|
|||
}
|
||||
}
|
||||
|
||||
FName EvalExpressionName (DWORD xi, AActor *self)
|
||||
{
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
return x->EvalExpression (self).GetName();
|
||||
}
|
||||
|
||||
const PClass * EvalExpressionClass (DWORD xi, AActor *self)
|
||||
{
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
return x->EvalExpression (self).GetClass();
|
||||
}
|
||||
|
||||
FState *EvalExpressionState (DWORD xi, AActor *self)
|
||||
{
|
||||
FxExpression *x = StateParams.Get(xi);
|
||||
if (x == NULL) return 0;
|
||||
|
||||
return x->EvalExpression (self).GetState();
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
@ -152,6 +175,21 @@ static ExpVal GetVariableValue (void *address, FExpressionType &type)
|
|||
ret.Int = *(int*)address;
|
||||
break;
|
||||
|
||||
case VAL_Sound:
|
||||
ret.Type = VAL_Sound;
|
||||
ret.Int = *(FSoundID*)address;
|
||||
break;
|
||||
|
||||
case VAL_Name:
|
||||
ret.Type = VAL_Name;
|
||||
ret.Int = *(FName*)address;
|
||||
break;
|
||||
|
||||
case VAL_Color:
|
||||
ret.Type = VAL_Color;
|
||||
ret.Int = *(int*)address;
|
||||
break;
|
||||
|
||||
case VAL_Bool:
|
||||
ret.Type = VAL_Int;
|
||||
ret.Int = *(bool*)address;
|
||||
|
@ -186,62 +224,6 @@ static ExpVal GetVariableValue (void *address, FExpressionType &type)
|
|||
return ret;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// FScriptPosition::Message
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void STACK_ARGS FScriptPosition::Message (int severity, const char *message, ...) const
|
||||
{
|
||||
FString composed;
|
||||
|
||||
if ((severity == MSG_DEBUG || severity == MSG_DEBUGLOG) && !developer) return;
|
||||
|
||||
if (message == NULL)
|
||||
{
|
||||
composed = "Bad syntax.";
|
||||
}
|
||||
else
|
||||
{
|
||||
va_list arglist;
|
||||
va_start (arglist, message);
|
||||
composed.VFormat (message, arglist);
|
||||
va_end (arglist);
|
||||
}
|
||||
const char *type = "";
|
||||
int level = PRINT_HIGH;
|
||||
|
||||
switch (severity)
|
||||
{
|
||||
default:
|
||||
return;
|
||||
|
||||
case MSG_WARNING:
|
||||
type = "warning";
|
||||
break;
|
||||
|
||||
case MSG_ERROR:
|
||||
thingdef_terminate++;
|
||||
type = "error";
|
||||
break;
|
||||
|
||||
case MSG_DEBUG:
|
||||
type = "message";
|
||||
break;
|
||||
|
||||
case MSG_DEBUGLOG:
|
||||
case MSG_LOG:
|
||||
type = "message";
|
||||
level = PRINT_LOG;
|
||||
break;
|
||||
}
|
||||
|
||||
Printf (level, "Script %s, \"%s\" line %d:\n%s\n", type,
|
||||
FileName.GetChars(), ScriptLine, composed.GetChars());
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
@ -278,9 +260,37 @@ bool FxExpression::isConstant() const
|
|||
|
||||
FxExpression *FxExpression::Resolve(FCompileContext &ctx)
|
||||
{
|
||||
isresolved = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxExpression *FxExpression::ResolveAsBoolean(FCompileContext &ctx)
|
||||
{
|
||||
FxExpression *x = Resolve(ctx);
|
||||
if (x != NULL)
|
||||
{
|
||||
switch (x->ValueType.Type)
|
||||
{
|
||||
case VAL_Sound:
|
||||
case VAL_Color:
|
||||
case VAL_Name:
|
||||
x->ValueType = VAL_Int;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
|
@ -1113,11 +1123,22 @@ FxExpression *FxCompareEq::Resolve(FCompileContext& ctx)
|
|||
|
||||
if (!ValueType.isNumeric() && !ValueType.isPointer())
|
||||
{
|
||||
if (left->ValueType.Type == right->ValueType.Type)
|
||||
{
|
||||
// compare other types?
|
||||
if (left->ValueType == VAL_Sound || left->ValueType == VAL_Color || left->ValueType == VAL_Name)
|
||||
{
|
||||
left->ValueType = right->ValueType = VAL_Int;
|
||||
goto cont;
|
||||
}
|
||||
}
|
||||
|
||||
ScriptPosition.Message(MSG_ERROR, "Numeric type expected");
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
else if (left->isConstant() && right->isConstant())
|
||||
cont:
|
||||
if (left->isConstant() && right->isConstant())
|
||||
{
|
||||
int v;
|
||||
|
||||
|
@ -2406,3 +2427,402 @@ ExpVal FxGlobalFunctionCall::EvalExpression (AActor *self)
|
|||
else ret.Float = FIXED2FLOAT (finecosine[angle>>ANGLETOFINESHIFT]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxClassTypeCast::FxClassTypeCast(const PClass *dtype, FxExpression *x)
|
||||
: FxExpression(x->ScriptPosition)
|
||||
{
|
||||
desttype = dtype;
|
||||
basex=x;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxClassTypeCast::~FxClassTypeCast()
|
||||
{
|
||||
SAFE_DELETE(basex);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxExpression *FxClassTypeCast::Resolve(FCompileContext &ctx)
|
||||
{
|
||||
CHECKRESOLVED();
|
||||
SAFE_RESOLVE(basex, ctx);
|
||||
|
||||
if (basex->ValueType != VAL_Name)
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "Cannot convert to class type");
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (basex->isConstant())
|
||||
{
|
||||
FName clsname = basex->EvalExpression(NULL).GetName();
|
||||
const PClass *cls;
|
||||
|
||||
if (clsname != NAME_None || !ctx.isconst)
|
||||
{
|
||||
cls= PClass::FindClass(clsname);
|
||||
if (cls == NULL)
|
||||
{
|
||||
if (!ctx.lax)
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR,"Unknown class name '%s'", clsname.GetChars());
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
// Since this happens in released WADs it must pass without a terminal error... :(
|
||||
ScriptPosition.Message(MSG_WARNING,
|
||||
"Unknown class name '%s'",
|
||||
clsname.GetChars(), desttype->TypeName.GetChars());
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!cls->IsDescendantOf(desttype))
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR,"class '%s' is not compatible with '%s'", clsname.GetChars(), desttype->TypeName.GetChars());
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
ScriptPosition.Message(MSG_DEBUG,"resolving '%s' as class name", clsname.GetChars());
|
||||
}
|
||||
FxExpression *x = new FxConstant(cls, ScriptPosition);
|
||||
delete this;
|
||||
return x;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ExpVal FxClassTypeCast::EvalExpression (AActor *self)
|
||||
{
|
||||
FName clsname = basex->EvalExpression(NULL).GetName();
|
||||
const PClass *cls = PClass::FindClass(clsname);
|
||||
|
||||
if (!cls->IsDescendantOf(desttype))
|
||||
{
|
||||
Printf("class '%s' is not compatible with '%s'", clsname.GetChars(), desttype->TypeName.GetChars());
|
||||
cls = NULL;
|
||||
}
|
||||
|
||||
ExpVal ret;
|
||||
ret.Type = VAL_Class;
|
||||
ret.pointer = (void*)cls;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxExpression *FxStateByIndex::Resolve(FCompileContext &ctx)
|
||||
{
|
||||
CHECKRESOLVED();
|
||||
if (ctx.cls->ActorInfo == NULL || ctx.cls->ActorInfo->NumOwnedStates == 0)
|
||||
{
|
||||
// This can't really happen
|
||||
assert(false);
|
||||
}
|
||||
if (ctx.cls->ActorInfo->NumOwnedStates <= index)
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "%s: Attempt to jump to non existing state index %d",
|
||||
ctx.cls->TypeName.GetChars(), index);
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
FxExpression *x = new FxConstant(ctx.cls->ActorInfo->OwnedStates + index, ScriptPosition);
|
||||
delete this;
|
||||
return x;
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxMultiNameState::FxMultiNameState(const char *_statestring, const FScriptPosition &pos)
|
||||
:FxExpression(pos)
|
||||
{
|
||||
FName scopename;
|
||||
FString statestring = _statestring;
|
||||
int scopeindex = statestring.IndexOf("::");
|
||||
|
||||
if (scopeindex >= 0)
|
||||
{
|
||||
scopename = FName(statestring, scopeindex, false);
|
||||
statestring = statestring.Right(statestring.Len() - scopeindex - 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
scopename = NULL;
|
||||
}
|
||||
names = MakeStateNameList(statestring);
|
||||
names.Insert(0, scopename);
|
||||
scope = NULL;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxExpression *FxMultiNameState::Resolve(FCompileContext &ctx)
|
||||
{
|
||||
CHECKRESOLVED();
|
||||
if (names[0] == NAME_None)
|
||||
{
|
||||
scope = NULL;
|
||||
}
|
||||
else if (names[0] == NAME_Super)
|
||||
{
|
||||
scope = ctx.cls->ParentClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
scope = PClass::FindClass(names[0]);
|
||||
if (scope == NULL)
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "Unknown class '%s' in state label", names[0].GetChars());
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
else if (!scope->IsDescendantOf(ctx.cls))
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "'%s' is not an ancestor of '%s'", names[0].GetChars(),ctx.cls->TypeName.GetChars());
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (scope != NULL)
|
||||
{
|
||||
// If the label is class specific we can resolve it right here
|
||||
if (scope->ActorInfo == NULL)
|
||||
{
|
||||
ScriptPosition.Message(MSG_ERROR, "'%s' has no actorinfo", names[0].GetChars());
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
FState *destination = scope->ActorInfo->FindState(names.Size()-1, &names[1], false);
|
||||
if (destination == NULL)
|
||||
{
|
||||
ScriptPosition.Message(ctx.lax? MSG_WARNING:MSG_ERROR, "Unknown state jump destination");
|
||||
delete this;
|
||||
return NULL;
|
||||
}
|
||||
FxExpression *x = new FxConstant(destination, ScriptPosition);
|
||||
delete this;
|
||||
return x;
|
||||
}
|
||||
names.Delete(0);
|
||||
names.ShrinkToFit();
|
||||
return this;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
ExpVal FxMultiNameState::EvalExpression (AActor *self)
|
||||
{
|
||||
ExpVal ret;
|
||||
ret.Type = VAL_State;
|
||||
ret.pointer = self->GetClass()->ActorInfo->FindState(names.Size(), &names[0]);
|
||||
if (ret.pointer == NULL)
|
||||
{
|
||||
const char *dot="";
|
||||
Printf("Jump target '");
|
||||
for (int i=0;i<names.Size();i++)
|
||||
{
|
||||
Printf("%s%s", dot, names[i].GetChars());
|
||||
dot = ".";
|
||||
}
|
||||
Printf("' not found in %s\n", self->GetClass()->TypeName.GetChars());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// NOTE: I don't expect any of the following to survive Doomscript ;)
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FStateExpressions StateParams;
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FStateExpressions::~FStateExpressions()
|
||||
{
|
||||
for(unsigned i=0; i<Size(); i++)
|
||||
{
|
||||
if (expressions[i].expr != NULL && !expressions[i].cloned)
|
||||
{
|
||||
delete expressions[i].expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int FStateExpressions::Add(FxExpression *x, const PClass *o, bool c)
|
||||
{
|
||||
int idx = expressions.Reserve(1);
|
||||
FStateExpression &exp = expressions[idx];
|
||||
exp.expr = x;
|
||||
exp.owner = o;
|
||||
exp.constant = c;
|
||||
exp.cloned = false;
|
||||
return idx;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int FStateExpressions::Reserve(int num, const PClass *cls)
|
||||
{
|
||||
int idx = expressions.Reserve(num);
|
||||
FStateExpression *exp = &expressions[idx];
|
||||
for(int i=0; i<num; i++)
|
||||
{
|
||||
exp[i].expr = NULL;
|
||||
exp[i].owner = cls;
|
||||
exp[i].constant = false;
|
||||
exp[i].cloned = false;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FStateExpressions::Set(int num, FxExpression *x)
|
||||
{
|
||||
if (num >= 0 && num < int(Size()))
|
||||
{
|
||||
assert(expressions[num].expr == NULL || expressions[num].cloned);
|
||||
expressions[num].expr = x;
|
||||
expressions[num].cloned = false;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FStateExpressions::Copy(int dest, int src, int cnt)
|
||||
{
|
||||
for(int i=0; i<cnt; i++)
|
||||
{
|
||||
expressions[dest+i].expr = expressions[src+i].expr;
|
||||
expressions[dest+i].cloned = true;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int FStateExpressions::ResolveAll()
|
||||
{
|
||||
int errorcount = 0;
|
||||
|
||||
FCompileContext ctx;
|
||||
ctx.lax = true;
|
||||
for(unsigned i=0; i<Size(); i++)
|
||||
{
|
||||
if (expressions[i].expr != NULL && !expressions[i].cloned)
|
||||
{
|
||||
ctx.cls = expressions[i].owner;
|
||||
ctx.isconst = expressions[i].constant;
|
||||
expressions[i].expr = expressions[i].expr->Resolve(ctx);
|
||||
if (expressions[i].expr == NULL)
|
||||
{
|
||||
errorcount++;
|
||||
}
|
||||
else if (expressions[i].constant && !expressions[i].expr->isConstant())
|
||||
{
|
||||
expressions[i].expr->ScriptPosition.Message(MSG_ERROR, "Constant expression expected");
|
||||
errorcount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(unsigned i=0; i<Size(); i++)
|
||||
{
|
||||
if (expressions[i].expr != NULL)
|
||||
{
|
||||
if (!expressions[i].expr->isresolved)
|
||||
{
|
||||
expressions[i].expr->ScriptPosition.Message(MSG_ERROR, "Expression at index %d not resolved\n", i);
|
||||
errorcount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return errorcount;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
//
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxExpression *FStateExpressions::Get(int num)
|
||||
{
|
||||
if (num >= 0 && num < int(Size()))
|
||||
return expressions[num].expr;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,166 +0,0 @@
|
|||
/*
|
||||
** decorations.cpp
|
||||
** Loads custom actors out of DECORATE lumps.
|
||||
**
|
||||
**---------------------------------------------------------------------------
|
||||
** Copyright 2002-2007 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.
|
||||
**---------------------------------------------------------------------------
|
||||
**
|
||||
*/
|
||||
|
||||
// HEADER FILES ------------------------------------------------------------
|
||||
|
||||
#include "actor.h"
|
||||
#include "info.h"
|
||||
#include "sc_man.h"
|
||||
#include "tarray.h"
|
||||
#include "w_wad.h"
|
||||
#include "templates.h"
|
||||
#include "s_sound.h"
|
||||
#include "cmdlib.h"
|
||||
#include "thingdef.h"
|
||||
#include "i_system.h"
|
||||
|
||||
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
||||
|
||||
void InitThingdef();
|
||||
void ParseActor(FScanner &sc);
|
||||
void FinishThingdef();
|
||||
void ParseOldDecoration(FScanner &sc, EDefinitionType def);
|
||||
|
||||
// STATIC FUNCTION PROTOTYPES --------------------------------------------
|
||||
PSymbolTable GlobalSymbols;
|
||||
|
||||
int thingdef_terminate;
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ParseDecorate
|
||||
//
|
||||
// Parses a single DECORATE lump
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
static void ParseDecorate (FScanner &sc)
|
||||
{
|
||||
// Get actor class name.
|
||||
for(;;)
|
||||
{
|
||||
FScanner::SavedPos pos = sc.SavePos();
|
||||
if (!sc.GetToken ())
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (sc.TokenType)
|
||||
{
|
||||
case TK_Include:
|
||||
{
|
||||
sc.MustGetString();
|
||||
FScanner newscanner;
|
||||
newscanner.Open(sc.String);
|
||||
ParseDecorate(newscanner);
|
||||
break;
|
||||
}
|
||||
|
||||
case TK_Const:
|
||||
ParseConstant (sc, &GlobalSymbols, NULL);
|
||||
break;
|
||||
|
||||
case TK_Enum:
|
||||
ParseEnum (sc, &GlobalSymbols, NULL);
|
||||
break;
|
||||
|
||||
case TK_Pickup:
|
||||
ParseOldDecoration (sc, DEF_Pickup);
|
||||
break;
|
||||
|
||||
case TK_Breakable:
|
||||
ParseOldDecoration (sc, DEF_BreakableDecoration);
|
||||
break;
|
||||
|
||||
case TK_Projectile:
|
||||
ParseOldDecoration (sc, DEF_Projectile);
|
||||
break;
|
||||
|
||||
case TK_Native:
|
||||
ParseVariable(sc, &GlobalSymbols, NULL);
|
||||
break;
|
||||
|
||||
case ';':
|
||||
// ';' is the start of a comment in the non-cmode parser which
|
||||
// is used to parse parts of the DECORATE lump. If we don't add
|
||||
// a check here the user will only get weird non-informative
|
||||
// error messages if a semicolon is found.
|
||||
sc.ScriptError("Unexpected ';'");
|
||||
break;
|
||||
|
||||
case TK_Identifier:
|
||||
// 'ACTOR' cannot be a keyword because it is also needed as a class identifier
|
||||
// so let's do a special case for this.
|
||||
if (sc.Compare("ACTOR"))
|
||||
{
|
||||
ParseActor (sc);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// without the option of game filters following, anything but an opening brace
|
||||
// here means a syntax error.
|
||||
sc.MustGetStringName("{");
|
||||
sc.RestorePos(pos);
|
||||
ParseOldDecoration(sc, DEF_Decoration);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// LoadDecorations
|
||||
//
|
||||
// Called from FActor::StaticInit()
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void LoadDecorations ()
|
||||
{
|
||||
int lastlump, lump;
|
||||
|
||||
InitThingdef();
|
||||
lastlump = 0;
|
||||
while ((lump = Wads.FindLump ("DECORATE", &lastlump)) != -1)
|
||||
{
|
||||
FScanner sc(lump);
|
||||
ParseDecorate (sc);
|
||||
}
|
||||
if (thingdef_terminate)
|
||||
{
|
||||
I_Error("%d errors found", thingdef_terminate);
|
||||
}
|
||||
FinishThingdef();
|
||||
}
|
||||
|
|
@ -52,7 +52,112 @@
|
|||
#include "i_system.h"
|
||||
#include "thingdef_exp.h"
|
||||
#include "w_wad.h"
|
||||
#include "v_video.h"
|
||||
|
||||
void ParseOldDecoration(FScanner &sc, EDefinitionType def);
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ParseParameter
|
||||
//
|
||||
// Parses aparameter - either a default in a function declaration
|
||||
// or an argument in a function call.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
FxExpression *ParseParameter(FScanner &sc, PClass *cls, char type, bool constant)
|
||||
{
|
||||
FxExpression *x = NULL;
|
||||
int v;
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case 'S':
|
||||
case 's': // Sound name
|
||||
sc.MustGetString();
|
||||
x = new FxConstant(FSoundID(sc.String), sc);
|
||||
break;
|
||||
|
||||
case 'M':
|
||||
case 'm': // Actor name
|
||||
sc.SetEscape(true);
|
||||
sc.MustGetString();
|
||||
sc.SetEscape(false);
|
||||
x = new FxClassTypeCast(RUNTIME_CLASS(AActor), new FxConstant(FName(sc.String), sc));
|
||||
break;
|
||||
|
||||
case 'T':
|
||||
case 't': // String
|
||||
sc.SetEscape(true);
|
||||
sc.MustGetString();
|
||||
sc.SetEscape(false);
|
||||
x = new FxConstant(sc.String[0]? FName(sc.String) : NAME_None, sc);
|
||||
break;
|
||||
|
||||
case 'C':
|
||||
case 'c': // Color
|
||||
sc.MustGetString ();
|
||||
if (sc.Compare("none"))
|
||||
{
|
||||
v = -1;
|
||||
}
|
||||
else if (sc.Compare(""))
|
||||
{
|
||||
v = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int c = V_GetColor (NULL, sc.String);
|
||||
// 0 needs to be the default so we have to mark the color.
|
||||
v = MAKEARGB(1, RPART(c), GPART(c), BPART(c));
|
||||
}
|
||||
{
|
||||
ExpVal val;
|
||||
val.Type = VAL_Color;
|
||||
val.Int = v;
|
||||
x = new FxConstant(val, sc);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'L':
|
||||
case 'l':
|
||||
{
|
||||
// This forces quotation marks around the state name.
|
||||
sc.MustGetToken(TK_StringConst);
|
||||
if (sc.String[0] == 0 || sc.Compare("None"))
|
||||
{
|
||||
x = new FxConstant((FState*)NULL, sc);
|
||||
}
|
||||
else if (sc.Compare("*"))
|
||||
{
|
||||
if (constant)
|
||||
{
|
||||
x = new FxConstant((FState*)(intptr_t)-1, sc);
|
||||
}
|
||||
else sc.ScriptError("Invalid state name '*'");
|
||||
}
|
||||
else
|
||||
{
|
||||
x = new FxMultiNameState(sc.String, sc);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'X':
|
||||
case 'x':
|
||||
x = ParseExpression (sc, cls);
|
||||
if (constant && !x->isConstant())
|
||||
{
|
||||
sc.ScriptError("Default parameter must be constant.");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
return NULL;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
|
@ -62,7 +167,7 @@
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
void ParseConstant (FScanner &sc, PSymbolTable * symt, PClass *cls)
|
||||
static void ParseConstant (FScanner &sc, PSymbolTable * symt, PClass *cls)
|
||||
{
|
||||
// Read the type and make sure it's int or float.
|
||||
if (sc.CheckToken(TK_Int) || sc.CheckToken(TK_Float))
|
||||
|
@ -108,7 +213,7 @@ void ParseConstant (FScanner &sc, PSymbolTable * symt, PClass *cls)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
void ParseEnum (FScanner &sc, PSymbolTable *symt, PClass *cls)
|
||||
static void ParseEnum (FScanner &sc, PSymbolTable *symt, PClass *cls)
|
||||
{
|
||||
int currvalue = 0;
|
||||
|
||||
|
@ -148,7 +253,7 @@ void ParseEnum (FScanner &sc, PSymbolTable *symt, PClass *cls)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
void ParseVariable (FScanner &sc, PSymbolTable * symt, PClass *cls)
|
||||
static void ParseVariable (FScanner &sc, PSymbolTable * symt, PClass *cls)
|
||||
{
|
||||
FExpressionType valuetype;
|
||||
|
||||
|
@ -227,7 +332,7 @@ void ParseVariable (FScanner &sc, PSymbolTable * symt, PClass *cls)
|
|||
// Parses a flag name
|
||||
//
|
||||
//==========================================================================
|
||||
void ParseActorFlag (FScanner &sc, Baggage &bag, int mod)
|
||||
static void ParseActorFlag (FScanner &sc, Baggage &bag, int mod)
|
||||
{
|
||||
sc.MustGetString ();
|
||||
|
||||
|
@ -335,7 +440,7 @@ static int ParseMorphStyle (FScanner &sc)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
FState *CheckState(FScanner &sc, PClass *type)
|
||||
static FState *CheckState(FScanner &sc, PClass *type)
|
||||
{
|
||||
int v=0;
|
||||
|
||||
|
@ -388,7 +493,7 @@ FState *CheckState(FScanner &sc, PClass *type)
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defaults, Baggage &bag)
|
||||
static bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defaults, Baggage &bag)
|
||||
{
|
||||
static TArray<FPropParam> params;
|
||||
static TArray<FString> strings;
|
||||
|
@ -417,7 +522,8 @@ bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defaults, Ba
|
|||
|
||||
if (sc.CheckString ("("))
|
||||
{
|
||||
conv.i = 0x40000000 | ParseExpression (sc, false, bag.Info->Class);
|
||||
FxExpression *x = ParseExpression(sc, bag.Info->Class);
|
||||
conv.i = 0x40000000 | StateParams.Add(x, bag.Info->Class, false);
|
||||
params.Push(conv);
|
||||
sc.MustGetStringName(")");
|
||||
break;
|
||||
|
@ -564,7 +670,7 @@ bool ParsePropertyParams(FScanner &sc, FPropertyInfo *prop, AActor *defaults, Ba
|
|||
//
|
||||
//==========================================================================
|
||||
|
||||
void ParseActorProperty(FScanner &sc, Baggage &bag)
|
||||
static void ParseActorProperty(FScanner &sc, Baggage &bag)
|
||||
{
|
||||
static const char *statenames[] = {
|
||||
"Spawn", "See", "Melee", "Missile", "Pain", "Death", "XDeath", "Burn",
|
||||
|
@ -615,44 +721,372 @@ void ParseActorProperty(FScanner &sc, Baggage &bag)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Finalizes an actor definition
|
||||
// ActorActionDef
|
||||
//
|
||||
// Parses an action function definition. A lot of this is essentially
|
||||
// documentation in the declaration for when I have a proper language
|
||||
// ready.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void FinishActor(FScanner &sc, FActorInfo *info, Baggage &bag)
|
||||
static void ParseActionDef (FScanner &sc, PClass *cls)
|
||||
{
|
||||
AActor *defaults = (AActor*)info->Class->Defaults;
|
||||
enum
|
||||
{
|
||||
OPTIONAL = 1
|
||||
};
|
||||
|
||||
try
|
||||
AFuncDesc *afd;
|
||||
FName funcname;
|
||||
FString args;
|
||||
TArray<FxExpression *> DefaultParams;
|
||||
bool hasdefaults = false;
|
||||
|
||||
if (sc.LumpNum == -1 || Wads.GetLumpFile(sc.LumpNum) > 0)
|
||||
{
|
||||
bag.statedef.FinishStates (info, defaults, bag.StateArray);
|
||||
sc.ScriptError ("action functions can only be imported by internal class and actor definitions!");
|
||||
}
|
||||
catch (CRecoverableError &err)
|
||||
|
||||
sc.MustGetToken(TK_Native);
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
funcname = sc.String;
|
||||
afd = FindFunction(sc.String);
|
||||
if (afd == NULL)
|
||||
{
|
||||
sc.ScriptError(err.GetMessage());
|
||||
sc.ScriptError ("The function '%s' has not been exported from the executable.", sc.String);
|
||||
}
|
||||
bag.statedef.InstallStates (info, defaults);
|
||||
bag.StateArray.Clear ();
|
||||
if (bag.DropItemSet)
|
||||
sc.MustGetToken('(');
|
||||
if (!sc.CheckToken(')'))
|
||||
{
|
||||
if (bag.DropItemList == NULL)
|
||||
while (sc.TokenType != ')')
|
||||
{
|
||||
if (info->Class->Meta.GetMetaInt (ACMETA_DropItems) != 0)
|
||||
int flags = 0;
|
||||
char type = '@';
|
||||
|
||||
// Retrieve flags before type name
|
||||
for (;;)
|
||||
{
|
||||
info->Class->Meta.SetMetaInt (ACMETA_DropItems, 0);
|
||||
if (sc.CheckToken(TK_Coerce) || sc.CheckToken(TK_Native))
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Read the variable type
|
||||
sc.MustGetAnyToken();
|
||||
switch (sc.TokenType)
|
||||
{
|
||||
case TK_Bool:
|
||||
case TK_Int:
|
||||
case TK_Float:
|
||||
type = 'x';
|
||||
break;
|
||||
|
||||
case TK_Sound: type = 's'; break;
|
||||
case TK_String: type = 't'; break;
|
||||
case TK_Name: type = 't'; break;
|
||||
case TK_State: type = 'l'; break;
|
||||
case TK_Color: type = 'c'; break;
|
||||
case TK_Class:
|
||||
sc.MustGetToken('<');
|
||||
sc.MustGetToken(TK_Identifier); // Skip class name, since the parser doesn't care
|
||||
sc.MustGetToken('>');
|
||||
type = 'm';
|
||||
break;
|
||||
case TK_Ellipsis:
|
||||
type = '+';
|
||||
sc.MustGetToken(')');
|
||||
sc.UnGet();
|
||||
break;
|
||||
default:
|
||||
sc.ScriptError ("Unknown variable type %s", sc.TokenName(sc.TokenType, sc.String).GetChars());
|
||||
break;
|
||||
}
|
||||
// Read the optional variable name
|
||||
if (!sc.CheckToken(',') && !sc.CheckToken(')'))
|
||||
{
|
||||
sc.MustGetToken(TK_Identifier);
|
||||
}
|
||||
else
|
||||
{
|
||||
sc.UnGet();
|
||||
}
|
||||
|
||||
FxExpression *def;
|
||||
if (sc.CheckToken('='))
|
||||
{
|
||||
hasdefaults = true;
|
||||
flags |= OPTIONAL;
|
||||
def = ParseParameter(sc, cls, type, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
def = NULL;
|
||||
}
|
||||
DefaultParams.Push(def);
|
||||
|
||||
if (!(flags & OPTIONAL) && type != '+')
|
||||
{
|
||||
type -= 'a' - 'A';
|
||||
}
|
||||
args += type;
|
||||
sc.MustGetAnyToken();
|
||||
if (sc.TokenType != ',' && sc.TokenType != ')')
|
||||
{
|
||||
sc.ScriptError ("Expected ',' or ')' but got %s instead", sc.TokenName(sc.TokenType, sc.String).GetChars());
|
||||
}
|
||||
}
|
||||
}
|
||||
sc.MustGetToken(';');
|
||||
PSymbolActionFunction *sym = new PSymbolActionFunction(funcname);
|
||||
sym->Arguments = args;
|
||||
sym->Function = afd->Function;
|
||||
if (hasdefaults)
|
||||
{
|
||||
sym->defaultparameterindex = StateParams.Size();
|
||||
for(unsigned int i = 0; i < DefaultParams.Size(); i++)
|
||||
{
|
||||
StateParams.Add(DefaultParams[i], cls, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
info->Class->Meta.SetMetaInt (ACMETA_DropItems,
|
||||
StoreDropItemChain(bag.DropItemList));
|
||||
sym->defaultparameterindex = -1;
|
||||
}
|
||||
}
|
||||
if (info->Class->IsDescendantOf (RUNTIME_CLASS(AInventory)))
|
||||
if (cls->Symbols.AddSymbol (sym) == NULL)
|
||||
{
|
||||
defaults->flags |= MF_SPECIAL;
|
||||
delete sym;
|
||||
sc.ScriptError ("'%s' is already defined in class '%s'.",
|
||||
funcname.GetChars(), cls->TypeName.GetChars());
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Starts a new actor definition
|
||||
//
|
||||
//==========================================================================
|
||||
static FActorInfo *ParseActorHeader(FScanner &sc, Baggage *bag)
|
||||
{
|
||||
FName typeName;
|
||||
FName parentName;
|
||||
FName replaceName;
|
||||
bool native = false;
|
||||
int DoomEdNum = -1;
|
||||
|
||||
// Get actor name
|
||||
sc.MustGetString();
|
||||
|
||||
char *colon = strchr(sc.String, ':');
|
||||
if (colon != NULL)
|
||||
{
|
||||
*colon++ = 0;
|
||||
}
|
||||
|
||||
typeName = sc.String;
|
||||
|
||||
// Do some tweaking so that a definition like 'Actor:Parent' which is read as a single token is recognized as well
|
||||
// without having resort to C-mode (which disallows periods in actor names.)
|
||||
if (colon == NULL)
|
||||
{
|
||||
sc.MustGetString ();
|
||||
if (sc.String[0]==':')
|
||||
{
|
||||
colon = sc.String + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (colon != NULL)
|
||||
{
|
||||
if (colon[0] == 0)
|
||||
{
|
||||
sc.MustGetString ();
|
||||
colon = sc.String;
|
||||
}
|
||||
}
|
||||
|
||||
if (colon == NULL)
|
||||
{
|
||||
sc.UnGet();
|
||||
}
|
||||
|
||||
parentName = colon;
|
||||
|
||||
// Check for "replaces"
|
||||
if (sc.CheckString ("replaces"))
|
||||
{
|
||||
// Get actor name
|
||||
sc.MustGetString ();
|
||||
replaceName = sc.String;
|
||||
}
|
||||
|
||||
// Now, after the actor names have been parsed, it is time to switch to C-mode
|
||||
// for the rest of the actor definition.
|
||||
sc.SetCMode (true);
|
||||
if (sc.CheckNumber())
|
||||
{
|
||||
if (sc.Number>=-1 && sc.Number<32768) DoomEdNum = sc.Number;
|
||||
else sc.ScriptError ("DoomEdNum must be in the range [-1,32767]");
|
||||
}
|
||||
|
||||
if (sc.CheckString("native"))
|
||||
{
|
||||
native = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
FActorInfo *info = CreateNewActor(sc, typeName, parentName, replaceName, DoomEdNum, native);
|
||||
ResetBaggage (bag, info->Class->ParentClass);
|
||||
bag->Info = info;
|
||||
bag->Lumpnum = sc.LumpNum;
|
||||
#ifdef _DEBUG
|
||||
bag->ClassName = typeName;
|
||||
#endif
|
||||
return info;
|
||||
}
|
||||
catch (CRecoverableError &err)
|
||||
{
|
||||
sc.ScriptError("%s", err.GetMessage());
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Reads an actor definition
|
||||
//
|
||||
//==========================================================================
|
||||
static void ParseActor(FScanner &sc)
|
||||
{
|
||||
FActorInfo * info=NULL;
|
||||
Baggage bag;
|
||||
|
||||
info = ParseActorHeader(sc, &bag);
|
||||
sc.MustGetToken('{');
|
||||
while (sc.MustGetAnyToken(), sc.TokenType != '}')
|
||||
{
|
||||
switch (sc.TokenType)
|
||||
{
|
||||
case TK_Action:
|
||||
ParseActionDef (sc, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Const:
|
||||
ParseConstant (sc, &info->Class->Symbols, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Enum:
|
||||
ParseEnum (sc, &info->Class->Symbols, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Native:
|
||||
ParseVariable (sc, &info->Class->Symbols, info->Class);
|
||||
break;
|
||||
|
||||
case TK_Identifier:
|
||||
// other identifier related checks here
|
||||
case TK_Projectile: // special case: both keyword and property name
|
||||
ParseActorProperty(sc, bag);
|
||||
break;
|
||||
|
||||
case '+':
|
||||
case '-':
|
||||
ParseActorFlag(sc, bag, sc.TokenType);
|
||||
break;
|
||||
|
||||
default:
|
||||
sc.ScriptError("Unexpected '%s' in definition of '%s'", sc.String, bag.Info->Class->TypeName.GetChars());
|
||||
break;
|
||||
}
|
||||
}
|
||||
FinishActor(sc, info, bag);
|
||||
sc.SetCMode (false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// ParseDecorate
|
||||
//
|
||||
// Parses a single DECORATE lump
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void ParseDecorate (FScanner &sc)
|
||||
{
|
||||
// Get actor class name.
|
||||
for(;;)
|
||||
{
|
||||
FScanner::SavedPos pos = sc.SavePos();
|
||||
if (!sc.GetToken ())
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch (sc.TokenType)
|
||||
{
|
||||
case TK_Include:
|
||||
{
|
||||
sc.MustGetString();
|
||||
FScanner newscanner;
|
||||
newscanner.Open(sc.String);
|
||||
ParseDecorate(newscanner);
|
||||
break;
|
||||
}
|
||||
|
||||
case TK_Const:
|
||||
ParseConstant (sc, &GlobalSymbols, NULL);
|
||||
break;
|
||||
|
||||
case TK_Enum:
|
||||
ParseEnum (sc, &GlobalSymbols, NULL);
|
||||
break;
|
||||
|
||||
case TK_Pickup:
|
||||
ParseOldDecoration (sc, DEF_Pickup);
|
||||
break;
|
||||
|
||||
case TK_Breakable:
|
||||
ParseOldDecoration (sc, DEF_BreakableDecoration);
|
||||
break;
|
||||
|
||||
case TK_Projectile:
|
||||
ParseOldDecoration (sc, DEF_Projectile);
|
||||
break;
|
||||
|
||||
case TK_Native:
|
||||
ParseVariable(sc, &GlobalSymbols, NULL);
|
||||
break;
|
||||
|
||||
case ';':
|
||||
// ';' is the start of a comment in the non-cmode parser which
|
||||
// is used to parse parts of the DECORATE lump. If we don't add
|
||||
// a check here the user will only get weird non-informative
|
||||
// error messages if a semicolon is found.
|
||||
sc.ScriptError("Unexpected ';'");
|
||||
break;
|
||||
|
||||
case TK_Identifier:
|
||||
// 'ACTOR' cannot be a keyword because it is also needed as a class identifier
|
||||
// so let's do a special case for this.
|
||||
if (sc.Compare("ACTOR"))
|
||||
{
|
||||
ParseActor (sc);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// without the option of game filters following, anything but an opening brace
|
||||
// here means a syntax error.
|
||||
sc.MustGetStringName("{");
|
||||
sc.RestorePos(pos);
|
||||
ParseOldDecoration(sc, DEF_Decoration);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,10 +55,7 @@
|
|||
#include "s_sound.h"
|
||||
#include "i_system.h"
|
||||
#include "colormatcher.h"
|
||||
|
||||
TArray<int> StateParameters;
|
||||
|
||||
|
||||
#include "thingdef_exp.h"
|
||||
|
||||
//==========================================================================
|
||||
//***
|
||||
|
@ -66,13 +63,9 @@ TArray<int> StateParameters;
|
|||
// creates an empty parameter list for a parameterized function call
|
||||
//
|
||||
//==========================================================================
|
||||
int PrepareStateParameters(FState * state, int numparams)
|
||||
static int PrepareStateParameters(FState * state, int numparams, const PClass *cls)
|
||||
{
|
||||
int paramindex=StateParameters.Size();
|
||||
int i, v;
|
||||
|
||||
v=0;
|
||||
for(i=0;i<numparams;i++) StateParameters.Push(v);
|
||||
int paramindex=StateParams.Reserve(numparams, cls);
|
||||
state->ParameterIndex = paramindex+1;
|
||||
return paramindex;
|
||||
}
|
||||
|
@ -94,21 +87,16 @@ bool DoActionSpecials(FScanner &sc, FState & state, bool multistate, int * state
|
|||
if (special > 0 && min_args >= 0)
|
||||
{
|
||||
|
||||
int paramindex=PrepareStateParameters(&state, 6);
|
||||
int paramindex=PrepareStateParameters(&state, 6, bag.Info->Class);
|
||||
|
||||
// The function expects the special to be passed as expression so we
|
||||
// have to convert it.
|
||||
specname.Format("%d", special);
|
||||
FScanner sc2;
|
||||
sc2.OpenMem("", (char*)specname.GetChars(), int(specname.Len()));
|
||||
StateParameters[paramindex] = ParseExpression(sc2, false, bag.Info->Class);
|
||||
StateParams.Set(paramindex, new FxConstant(special, sc));
|
||||
|
||||
// Make this consistent with all other parameter parsing
|
||||
if (sc.CheckToken('('))
|
||||
{
|
||||
for (i = 0; i < 5;)
|
||||
{
|
||||
StateParameters[paramindex+i+1] = ParseExpression (sc, false, bag.Info->Class);
|
||||
StateParams.Set(paramindex+i+1, ParseExpression (sc, bag.Info->Class));
|
||||
i++;
|
||||
if (!sc.CheckToken (',')) break;
|
||||
}
|
||||
|
@ -351,23 +339,25 @@ do_stop:
|
|||
}
|
||||
}
|
||||
|
||||
int paramindex = PrepareStateParameters(&state, numparams);
|
||||
int paramindex = PrepareStateParameters(&state, numparams, bag.Info->Class);
|
||||
int paramstart = paramindex;
|
||||
bool varargs = params[numparams - 1] == '+';
|
||||
int varargcount=0;
|
||||
|
||||
|
||||
if (varargs)
|
||||
{
|
||||
StateParameters[paramindex++] = 0;
|
||||
varargcount++;
|
||||
paramindex++;
|
||||
}
|
||||
else if (afd->defaultparameterindex > -1)
|
||||
{
|
||||
memcpy(&StateParameters[paramindex], &StateParameters[afd->defaultparameterindex],
|
||||
afd->Arguments.Len() * sizeof (StateParameters[0]));
|
||||
StateParams.Copy(paramindex, afd->defaultparameterindex, int(afd->Arguments.Len()));
|
||||
}
|
||||
|
||||
while (*params)
|
||||
{
|
||||
FxExpression *x;
|
||||
if ((*params == 'l' || *params == 'L') && sc.CheckNumber())
|
||||
{
|
||||
// Special case: State label as an offset
|
||||
|
@ -382,19 +372,18 @@ do_stop:
|
|||
sc.ScriptError("Negative jump offsets are not allowed");
|
||||
}
|
||||
|
||||
int minreq=count+v;
|
||||
if (minreq>minrequiredstate) minrequiredstate=minreq;
|
||||
x = new FxStateByIndex(count+v, sc);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Use the generic parameter parser for everything else
|
||||
v = ParseParameter(sc, bag.Info->Class, *params, false);
|
||||
x = ParseParameter(sc, bag.Info->Class, *params, false);
|
||||
}
|
||||
StateParameters[paramindex++] = v;
|
||||
StateParams.Set(paramindex++, x);
|
||||
params++;
|
||||
if (varargs)
|
||||
{
|
||||
StateParameters[paramstart]++;
|
||||
varargcount++;
|
||||
}
|
||||
if (*params)
|
||||
{
|
||||
|
@ -402,11 +391,11 @@ do_stop:
|
|||
{
|
||||
if (sc.CheckString(")"))
|
||||
{
|
||||
StateParams.Set(paramstart, new FxConstant(varargcount, sc));
|
||||
goto endofstate;
|
||||
}
|
||||
params--;
|
||||
v = 0;
|
||||
StateParameters.Push(v);
|
||||
StateParams.Reserve(1, bag.Info->Class);
|
||||
}
|
||||
else if ((islower(*params) || *params=='!') && sc.CheckString(")"))
|
||||
{
|
||||
|
@ -451,10 +440,6 @@ endofstate:
|
|||
count++;
|
||||
}
|
||||
}
|
||||
if (count<=minrequiredstate)
|
||||
{
|
||||
sc.ScriptError("A_Jump offset out of range in %s", actor->Class->TypeName.GetChars());
|
||||
}
|
||||
sc.SetEscape(true); // re-enable escape sequences
|
||||
return count;
|
||||
}
|
||||
|
|
|
@ -8,13 +8,18 @@
|
|||
|
||||
enum ExpValType
|
||||
{
|
||||
VAL_Int,
|
||||
VAL_Float,
|
||||
VAL_Unknown,
|
||||
VAL_Array,
|
||||
VAL_Object,
|
||||
VAL_Class,
|
||||
VAL_Pointer,
|
||||
VAL_Int, // integer number
|
||||
VAL_Float, // floating point number
|
||||
VAL_Unknown, // nothing
|
||||
VAL_Array, // Array (very limited right now)
|
||||
VAL_Object, // Object reference
|
||||
VAL_Class, // Class reference
|
||||
VAL_Pointer, // Dereferenced variable (only used for addressing arrays for now.)
|
||||
VAL_Sound, // Sound identifier. Internally it's an int.
|
||||
VAL_Name, // A Name
|
||||
VAL_MultiName, // Multiple names for multi-label states
|
||||
VAL_Color, // A color.
|
||||
VAL_State, // A State pointer
|
||||
|
||||
// only used for accessing external variables to ensure proper conversion
|
||||
VAL_Fixed,
|
||||
|
|
|
@ -83,10 +83,11 @@ actor PhasingZorcher : PlasmaRifle 2004
|
|||
PLSG B 20 A_ReFire
|
||||
Goto Ready
|
||||
Flash:
|
||||
PLSF A 0 A_Jump(128, 2)
|
||||
PLSF A 4 Bright A_Light1
|
||||
PLSF A 0 A_Jump(128, "Flash2")
|
||||
PLSF A 3 Bright A_Light1
|
||||
Goto LightDone
|
||||
PLSF B 4 Bright A_Light1
|
||||
Flash2:
|
||||
PLSF B 3 Bright A_Light1
|
||||
Goto LightDone
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1075,10 +1075,6 @@
|
|||
RelativePath=".\src\thingdef\thingdef_expression.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\thingdef\thingdef_main.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath=".\src\thingdef\thingdef_parse.cpp"
|
||||
>
|
||||
|
|
Loading…
Reference in a new issue