mirror of
https://github.com/ZDoom/qzdoom-gpl.git
synced 2024-11-17 09:41:21 +00:00
ee69e7bf03
line special definition with something that automatically gets updated if new specials are added. SVN r814 (trunk)
1157 lines
31 KiB
C++
1157 lines
31 KiB
C++
/*
|
|
** thingdef_states.cpp
|
|
**
|
|
** Actor definitions - the state parser
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 2002-2007 Christoph Oelckers
|
|
** Copyright 2004-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.
|
|
** 4. When not used as part of ZDoom or a ZDoom derivative, this code will be
|
|
** covered by the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation; either version 2 of the License, or (at
|
|
** your option) any later version.
|
|
**
|
|
** 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.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
#include "actor.h"
|
|
#include "info.h"
|
|
#include "sc_man.h"
|
|
#include "tarray.h"
|
|
#include "templates.h"
|
|
#include "cmdlib.h"
|
|
#include "p_lnspec.h"
|
|
#include "a_action.h"
|
|
#include "p_local.h"
|
|
#include "p_effect.h"
|
|
#include "v_palette.h"
|
|
#include "doomerrors.h"
|
|
#include "thingdef.h"
|
|
#include "a_sharedglobal.h"
|
|
#include "s_sound.h"
|
|
#include "i_system.h"
|
|
|
|
TArray<int> StateParameters;
|
|
TArray<FName> JumpParameters;
|
|
|
|
//==========================================================================
|
|
//
|
|
// Action functions
|
|
//
|
|
//==========================================================================
|
|
|
|
|
|
|
|
#define FROM_THINGDEF
|
|
// Prototype the code pointers
|
|
#define WEAPON(x) void A_##x(AActor*);
|
|
#define ACTOR(x) void A_##x(AActor*);
|
|
#include "codepointers.h"
|
|
#include "d_dehackedactions.h"
|
|
|
|
AFuncDesc AFTable[] =
|
|
{
|
|
#define WEAPON(x) { "A_" #x, A_##x },
|
|
#define ACTOR(x) { "A_" #x, A_##x },
|
|
#include "codepointers.h"
|
|
#include "d_dehackedactions.h"
|
|
{ "A_Fall", A_NoBlocking },
|
|
{ "A_BasicAttack", A_ComboAttack },
|
|
{ "A_Explode", A_ExplodeParms }
|
|
};
|
|
|
|
|
|
static TArray<FState> StateArray;
|
|
|
|
//==========================================================================
|
|
//
|
|
// Find a function by name using a binary search
|
|
//
|
|
//==========================================================================
|
|
static int STACK_ARGS funccmp(const void * a, const void * b)
|
|
{
|
|
return stricmp( ((AFuncDesc*)a)->Name, ((AFuncDesc*)b)->Name);
|
|
}
|
|
|
|
AFuncDesc * FindFunction(const char * string)
|
|
{
|
|
static bool funcsorted=false;
|
|
|
|
if (!funcsorted)
|
|
{
|
|
qsort(AFTable, countof(AFTable), sizeof(AFTable[0]), funccmp);
|
|
funcsorted=true;
|
|
}
|
|
|
|
int min = 0, max = countof(AFTable)-1;
|
|
|
|
while (min <= max)
|
|
{
|
|
int mid = (min + max) / 2;
|
|
int lexval = stricmp (string, AFTable[mid].Name);
|
|
if (lexval == 0)
|
|
{
|
|
return &AFTable[mid];
|
|
}
|
|
else if (lexval > 0)
|
|
{
|
|
min = mid + 1;
|
|
}
|
|
else
|
|
{
|
|
max = mid - 1;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// Find a state address
|
|
//
|
|
//==========================================================================
|
|
|
|
struct FStateDefine
|
|
{
|
|
FName Label;
|
|
TArray<FStateDefine> Children;
|
|
FState *State;
|
|
};
|
|
|
|
static TArray<FStateDefine> StateLabels;
|
|
|
|
void ClearStateLabels()
|
|
{
|
|
StateLabels.Clear();
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Search one list of state definitions for the given name
|
|
//
|
|
//==========================================================================
|
|
|
|
static FStateDefine * FindStateLabelInList(TArray<FStateDefine> & list, FName name, bool create)
|
|
{
|
|
for(unsigned i = 0; i<list.Size(); i++)
|
|
{
|
|
if (list[i].Label == name) return &list[i];
|
|
}
|
|
if (create)
|
|
{
|
|
FStateDefine def;
|
|
def.Label=name;
|
|
def.State=NULL;
|
|
return &list[list.Push(def)];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Finds the address of a state given by name.
|
|
// Adds the state if it doesn't exist
|
|
//
|
|
//==========================================================================
|
|
|
|
static FStateDefine * FindStateAddress(const char * name)
|
|
{
|
|
static TArray<FName> namelist(3);
|
|
FStateDefine * statedef=NULL;
|
|
|
|
MakeStateNameList(name, &namelist);
|
|
|
|
TArray<FStateDefine> * statelist = &StateLabels;
|
|
for(unsigned i=0;i<namelist.Size();i++)
|
|
{
|
|
statedef = FindStateLabelInList(*statelist, namelist[i], true);
|
|
statelist = &statedef->Children;
|
|
}
|
|
return statedef;
|
|
}
|
|
|
|
void AddState (const char * statename, FState * state)
|
|
{
|
|
FStateDefine * std = FindStateAddress(statename);
|
|
std->State = state;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Finds the state associated with the given name
|
|
//
|
|
//==========================================================================
|
|
|
|
FState * FindState(AActor * actor, const PClass * type, const char * name)
|
|
{
|
|
static TArray<FName> namelist(3);
|
|
FStateDefine * statedef=NULL;
|
|
|
|
MakeStateNameList(name, &namelist);
|
|
|
|
TArray<FStateDefine> * statelist = &StateLabels;
|
|
for(unsigned i=0;i<namelist.Size();i++)
|
|
{
|
|
statedef = FindStateLabelInList(*statelist, namelist[i], false);
|
|
if (statedef == NULL) return NULL;
|
|
statelist = &statedef->Children;
|
|
}
|
|
return statedef? statedef->State : NULL;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Finds the state associated with the given name
|
|
//
|
|
//==========================================================================
|
|
|
|
FState * FindStateInClass(AActor * actor, const PClass * type, const char * name)
|
|
{
|
|
static TArray<FName> namelist(3);
|
|
|
|
MakeStateNameList(name, &namelist);
|
|
FActorInfo * info = type->ActorInfo;
|
|
if (info) return info->FindState(namelist.Size(), &namelist[0], true);
|
|
return NULL;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Checks if a state list is empty
|
|
// A list is empty if it doesn't contain any states and no children
|
|
// that contain any states
|
|
//
|
|
//==========================================================================
|
|
|
|
static bool IsStateListEmpty(TArray<FStateDefine> & statelist)
|
|
{
|
|
for(unsigned i=0;i<statelist.Size();i++)
|
|
{
|
|
if (statelist[i].State!=NULL || !IsStateListEmpty(statelist[i].Children)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Creates the final list of states from the state definitions
|
|
//
|
|
//==========================================================================
|
|
|
|
static int STACK_ARGS labelcmp(const void * a, const void * b)
|
|
{
|
|
FStateLabel * A = (FStateLabel *)a;
|
|
FStateLabel * B = (FStateLabel *)b;
|
|
return ((int)A->Label - (int)B->Label);
|
|
}
|
|
|
|
static FStateLabels * CreateStateLabelList(TArray<FStateDefine> & statelist)
|
|
{
|
|
// First delete all empty labels from the list
|
|
for (int i=statelist.Size()-1;i>=0;i--)
|
|
{
|
|
if (statelist[i].Label == NAME_None || (statelist[i].State == NULL && statelist[i].Children.Size() == 0))
|
|
{
|
|
statelist.Delete(i);
|
|
}
|
|
}
|
|
|
|
int count=statelist.Size();
|
|
|
|
if (count == 0) return NULL;
|
|
|
|
FStateLabels * list = (FStateLabels*)M_Malloc(sizeof(FStateLabels)+(count-1)*sizeof(FStateLabel));
|
|
list->NumLabels = count;
|
|
|
|
for (int i=0;i<count;i++)
|
|
{
|
|
list->Labels[i].Label = statelist[i].Label;
|
|
list->Labels[i].State = statelist[i].State;
|
|
list->Labels[i].Children = CreateStateLabelList(statelist[i].Children);
|
|
}
|
|
qsort(list->Labels, count, sizeof(FStateLabel), labelcmp);
|
|
return list;
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// InstallStates
|
|
//
|
|
// Creates the actor's state list from the current definition
|
|
//
|
|
//===========================================================================
|
|
|
|
void InstallStates(FActorInfo *info, AActor *defaults)
|
|
{
|
|
// First ensure we have a valid spawn state.
|
|
FState * state = FindState(defaults, info->Class, "Spawn");
|
|
|
|
// Stateless actors that are direct subclasses of AActor
|
|
// have their spawnstate default to something that won't
|
|
// immediately destroy them.
|
|
if (state == &AActor::States[2] && info->Class->ParentClass == RUNTIME_CLASS(AActor))
|
|
{
|
|
AddState("Spawn", &AActor::States[0]);
|
|
}
|
|
else if (state == NULL)
|
|
{
|
|
// A NULL spawn state will crash the engine so set it to something that will make
|
|
// the actor disappear as quickly as possible.
|
|
AddState("Spawn", &AActor::States[2]);
|
|
}
|
|
|
|
if (info->StateList != NULL)
|
|
{
|
|
info->StateList->Destroy();
|
|
M_Free(info->StateList);
|
|
}
|
|
info->StateList = CreateStateLabelList(StateLabels);
|
|
|
|
// Cache these states as member veriables.
|
|
defaults->SpawnState = info->FindState(NAME_Spawn);
|
|
defaults->SeeState = info->FindState(NAME_See);
|
|
// Melee and Missile states are manipulated by the scripted marines so they
|
|
// have to be stored locally
|
|
defaults->MeleeState = info->FindState(NAME_Melee);
|
|
defaults->MissileState = info->FindState(NAME_Missile);
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
//
|
|
// MakeStateDefines
|
|
//
|
|
// Creates a list of state definitions from an existing actor
|
|
// Used by Dehacked to modify an actor's state list
|
|
//
|
|
//===========================================================================
|
|
|
|
static void MakeStateList(const FStateLabels *list, TArray<FStateDefine> &dest)
|
|
{
|
|
dest.Clear();
|
|
if (list != NULL) for(int i=0;i<list->NumLabels;i++)
|
|
{
|
|
FStateDefine def;
|
|
|
|
def.Label = list->Labels[i].Label;
|
|
def.State = list->Labels[i].State;
|
|
dest.Push(def);
|
|
if (list->Labels[i].Children != NULL)
|
|
{
|
|
MakeStateList(list->Labels[i].Children, dest[dest.Size()-1].Children);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MakeStateDefines(const FStateLabels *list)
|
|
{
|
|
MakeStateList(list, StateLabels);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// PrepareStateParameters
|
|
// creates an empty parameter list for a parameterized function call
|
|
//
|
|
//==========================================================================
|
|
int PrepareStateParameters(FState * state, int numparams)
|
|
{
|
|
int paramindex=StateParameters.Size();
|
|
int i, v;
|
|
|
|
v=0;
|
|
for(i=0;i<numparams;i++) StateParameters.Push(v);
|
|
state->ParameterIndex = paramindex+1;
|
|
return paramindex;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// DoActionSpecials
|
|
// handles action specials as code pointers
|
|
//
|
|
//==========================================================================
|
|
bool DoActionSpecials(FScanner &sc, FState & state, bool multistate, int * statecount, Baggage &bag)
|
|
{
|
|
int i;
|
|
int min_args, max_args;
|
|
FString specname = sc.String;
|
|
|
|
int special = P_FindLineSpecial(sc.String, &min_args, &max_args);
|
|
|
|
if (special > 0 && min_args >= 0)
|
|
{
|
|
|
|
int paramindex=PrepareStateParameters(&state, 6);
|
|
|
|
StateParameters[paramindex]=special;
|
|
|
|
// 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);
|
|
i++;
|
|
if (!sc.CheckToken (',')) break;
|
|
}
|
|
sc.MustGetToken (')');
|
|
}
|
|
else i=0;
|
|
|
|
if (i < min_args)
|
|
{
|
|
sc.ScriptError ("Too few arguments to %s", specname.GetChars());
|
|
}
|
|
if (i > max_args)
|
|
{
|
|
sc.ScriptError ("Too many arguments to %s", specname.GetChars());
|
|
}
|
|
state.Action = A_CallSpecial;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// RetargetState(Pointer)s
|
|
//
|
|
// These functions are used when a goto follows one or more labels.
|
|
// Because multiple labels are permitted to occur consecutively with no
|
|
// intervening states, it is not enough to remember the last label defined
|
|
// and adjust it. So these functions search for all labels that point to
|
|
// the current position in the state array and give them a copy of the
|
|
// target string instead.
|
|
//
|
|
//==========================================================================
|
|
|
|
static void RetargetStatePointers (intptr_t count, const char *target, TArray<FStateDefine> & statelist)
|
|
{
|
|
for(unsigned i = 0;i<statelist.Size(); i++)
|
|
{
|
|
if (statelist[i].State == (FState*)count)
|
|
{
|
|
statelist[i].State = target == NULL ? NULL : (FState *)copystring (target);
|
|
}
|
|
if (statelist[i].Children.Size() > 0)
|
|
{
|
|
RetargetStatePointers(count, target, statelist[i].Children);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void RetargetStates (intptr_t count, const char *target)
|
|
{
|
|
RetargetStatePointers(count, target, StateLabels);
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// Reads a state label that may contain '.'s.
|
|
// processes a state block
|
|
//
|
|
//==========================================================================
|
|
static FString ParseStateString(FScanner &sc)
|
|
{
|
|
FString statestring;
|
|
|
|
sc.MustGetString();
|
|
statestring = sc.String;
|
|
if (sc.CheckString("::"))
|
|
{
|
|
sc.MustGetString ();
|
|
statestring << "::" << sc.String;
|
|
}
|
|
while (sc.CheckString ("."))
|
|
{
|
|
sc.MustGetString ();
|
|
statestring << "." << sc.String;
|
|
}
|
|
return statestring;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ParseStates
|
|
// parses a state block
|
|
//
|
|
//==========================================================================
|
|
int ParseStates(FScanner &sc, FActorInfo * actor, AActor * defaults, Baggage &bag)
|
|
{
|
|
FString statestring;
|
|
intptr_t count = 0;
|
|
FState state;
|
|
FState * laststate = NULL;
|
|
intptr_t lastlabel = -1;
|
|
int minrequiredstate = -1;
|
|
|
|
sc.MustGetStringName ("{");
|
|
sc.SetEscape(false); // disable escape sequences in the state parser
|
|
while (!sc.CheckString ("}") && !sc.End)
|
|
{
|
|
memset(&state,0,sizeof(state));
|
|
statestring = ParseStateString(sc);
|
|
if (!statestring.CompareNoCase("GOTO"))
|
|
{
|
|
do_goto:
|
|
statestring = ParseStateString(sc);
|
|
if (sc.CheckString ("+"))
|
|
{
|
|
sc.MustGetNumber ();
|
|
statestring += '+';
|
|
statestring += sc.String;
|
|
}
|
|
// copy the text - this must be resolved later!
|
|
if (laststate != NULL)
|
|
{ // Following a state definition: Modify it.
|
|
laststate->NextState = (FState*)copystring(statestring);
|
|
}
|
|
else if (lastlabel >= 0)
|
|
{ // Following a label: Retarget it.
|
|
RetargetStates (count+1, statestring);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError("GOTO before first state");
|
|
}
|
|
}
|
|
else if (!statestring.CompareNoCase("STOP"))
|
|
{
|
|
do_stop:
|
|
if (laststate!=NULL)
|
|
{
|
|
laststate->NextState=(FState*)-1;
|
|
}
|
|
else if (lastlabel >=0)
|
|
{
|
|
RetargetStates (count+1, NULL);
|
|
}
|
|
else
|
|
{
|
|
sc.ScriptError("STOP before first state");
|
|
continue;
|
|
}
|
|
}
|
|
else if (!statestring.CompareNoCase("WAIT") || !statestring.CompareNoCase("FAIL"))
|
|
{
|
|
if (!laststate)
|
|
{
|
|
sc.ScriptError("%s before first state", sc.String);
|
|
continue;
|
|
}
|
|
laststate->NextState=(FState*)-2;
|
|
}
|
|
else if (!statestring.CompareNoCase("LOOP"))
|
|
{
|
|
if (!laststate)
|
|
{
|
|
sc.ScriptError("LOOP before first state");
|
|
continue;
|
|
}
|
|
laststate->NextState=(FState*)(lastlabel+1);
|
|
}
|
|
else
|
|
{
|
|
const char * statestrp;
|
|
|
|
sc.MustGetString();
|
|
if (sc.Compare (":"))
|
|
{
|
|
laststate = NULL;
|
|
do
|
|
{
|
|
lastlabel = count;
|
|
AddState(statestring, (FState *) (count+1));
|
|
statestring = ParseStateString(sc);
|
|
if (!statestring.CompareNoCase("GOTO"))
|
|
{
|
|
goto do_goto;
|
|
}
|
|
else if (!statestring.CompareNoCase("STOP"))
|
|
{
|
|
goto do_stop;
|
|
}
|
|
sc.MustGetString ();
|
|
} while (sc.Compare (":"));
|
|
// continue;
|
|
}
|
|
|
|
sc.UnGet ();
|
|
|
|
if (statestring.Len() != 4)
|
|
{
|
|
sc.ScriptError ("Sprite names must be exactly 4 characters\n");
|
|
}
|
|
|
|
memcpy(state.sprite.name, statestring, 4);
|
|
state.Misc1 = state.Misc2 = 0;
|
|
state.ParameterIndex = 0;
|
|
sc.MustGetString();
|
|
statestring = (sc.String+1);
|
|
statestrp = statestring;
|
|
state.Frame = (*sc.String & 223)-'A';
|
|
if ((*sc.String & 223)<'A' || (*sc.String & 223)>']')
|
|
{
|
|
sc.ScriptError ("Frames must be A-Z, [, \\, or ]");
|
|
state.Frame=0;
|
|
}
|
|
|
|
sc.MustGetNumber();
|
|
sc.Number++;
|
|
state.Tics = sc.Number & 255;
|
|
state.Misc1 = (sc.Number >> 8) & 255;
|
|
if (state.Misc1)
|
|
{
|
|
state.Frame |= SF_BIGTIC;
|
|
}
|
|
|
|
while (sc.GetString() && !sc.Crossed)
|
|
{
|
|
if (sc.Compare("BRIGHT"))
|
|
{
|
|
state.Frame |= SF_FULLBRIGHT;
|
|
continue;
|
|
}
|
|
if (sc.Compare("OFFSET"))
|
|
{
|
|
if (state.Frame & SF_BIGTIC)
|
|
{
|
|
sc.ScriptError("You cannot use OFFSET with a state duration larger than 254!");
|
|
}
|
|
// specify a weapon offset
|
|
sc.MustGetStringName("(");
|
|
sc.MustGetNumber();
|
|
state.Misc1 = sc.Number;
|
|
sc.MustGetStringName (",");
|
|
sc.MustGetNumber();
|
|
state.Misc2 = sc.Number;
|
|
sc.MustGetStringName(")");
|
|
continue;
|
|
}
|
|
|
|
// Make the action name lowercase to satisfy the gperf hashers
|
|
strlwr (sc.String);
|
|
|
|
int minreq = count;
|
|
if (DoActionSpecials(sc, state, !statestring.IsEmpty(), &minreq, bag))
|
|
{
|
|
if (minreq>minrequiredstate) minrequiredstate=minreq;
|
|
goto endofstate;
|
|
}
|
|
|
|
PSymbol *sym = bag.Info->Class->Symbols.FindSymbol (FName(sc.String, true), true);
|
|
if (sym != NULL && sym->SymbolType == SYM_ActionFunction)
|
|
{
|
|
PSymbolActionFunction *afd = static_cast<PSymbolActionFunction *>(sym);
|
|
state.Action = afd->Function;
|
|
if (!afd->Arguments.IsEmpty())
|
|
{
|
|
const char *params = afd->Arguments.GetChars();
|
|
int numparams = (int)afd->Arguments.Len();
|
|
|
|
int v;
|
|
|
|
if (!islower(*params))
|
|
{
|
|
sc.MustGetStringName("(");
|
|
}
|
|
else
|
|
{
|
|
if (!sc.CheckString("(")) goto endofstate;
|
|
}
|
|
|
|
int paramindex = PrepareStateParameters(&state, numparams);
|
|
int paramstart = paramindex;
|
|
bool varargs = params[numparams - 1] == '+';
|
|
|
|
if (varargs)
|
|
{
|
|
StateParameters[paramindex++] = 0;
|
|
}
|
|
|
|
while (*params)
|
|
{
|
|
switch(*params)
|
|
{
|
|
case 'I':
|
|
case 'i': // Integer
|
|
sc.MustGetNumber();
|
|
v = sc.Number;
|
|
break;
|
|
|
|
case 'F':
|
|
case 'f': // Fixed point
|
|
sc.MustGetFloat();
|
|
v = fixed_t(sc.Float*FRACUNIT);
|
|
break;
|
|
|
|
|
|
case 'S':
|
|
case 's': // Sound name
|
|
sc.MustGetString();
|
|
v = S_FindSound(sc.String);
|
|
break;
|
|
|
|
case 'M':
|
|
case 'm': // Actor name
|
|
case 'T':
|
|
case 't': // String
|
|
sc.SetEscape(true);
|
|
sc.MustGetString();
|
|
sc.SetEscape(false);
|
|
v = (int)(sc.String[0] ? FName(sc.String) : NAME_None);
|
|
break;
|
|
|
|
case 'L':
|
|
case 'l': // Jump label
|
|
|
|
if (sc.CheckNumber())
|
|
{
|
|
if (sc.Number > 0 && strlen(statestring)>0)
|
|
{
|
|
sc.ScriptError("You cannot use state jumps commands with a jump offset on multistate definitions\n");
|
|
}
|
|
|
|
v=sc.Number;
|
|
if (v<0)
|
|
{
|
|
sc.ScriptError("Negative jump offsets are not allowed");
|
|
}
|
|
|
|
{
|
|
int minreq=count+v;
|
|
if (minreq>minrequiredstate) minrequiredstate=minreq;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
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"))
|
|
{
|
|
v = 0; // an empty string means 'no state'.
|
|
break;
|
|
}
|
|
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 = actor->Class->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 (actor->Class))
|
|
{
|
|
sc.ScriptError ("%s is not derived from %s so cannot access its states.",
|
|
actor->Class->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, &names);
|
|
|
|
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!
|
|
}
|
|
|
|
break;
|
|
|
|
case 'C':
|
|
case 'c': // Color
|
|
sc.MustGetString ();
|
|
if (sc.Compare("none"))
|
|
{
|
|
v = -1;
|
|
}
|
|
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));
|
|
}
|
|
break;
|
|
|
|
case 'X':
|
|
case 'x':
|
|
v = ParseExpression (sc, false, bag.Info->Class);
|
|
break;
|
|
|
|
case 'Y':
|
|
case 'y':
|
|
v = ParseExpression (sc, true, bag.Info->Class);
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
v = -1;
|
|
break;
|
|
}
|
|
StateParameters[paramindex++] = v;
|
|
params++;
|
|
if (varargs)
|
|
{
|
|
StateParameters[paramstart]++;
|
|
}
|
|
if (*params)
|
|
{
|
|
if (*params == '+')
|
|
{
|
|
if (sc.CheckString(")"))
|
|
{
|
|
goto endofstate;
|
|
}
|
|
params--;
|
|
v = 0;
|
|
StateParameters.Push(v);
|
|
}
|
|
else if ((islower(*params) || *params=='!') && sc.CheckString(")"))
|
|
{
|
|
goto endofstate;
|
|
}
|
|
sc.MustGetStringName (",");
|
|
}
|
|
}
|
|
sc.MustGetStringName(")");
|
|
}
|
|
else
|
|
{
|
|
sc.MustGetString();
|
|
if (sc.Compare("("))
|
|
{
|
|
sc.ScriptError("You cannot pass parameters to '%s'\n",sc.String);
|
|
}
|
|
sc.UnGet();
|
|
}
|
|
goto endofstate;
|
|
}
|
|
sc.ScriptError("Invalid state parameter %s\n", sc.String);
|
|
}
|
|
sc.UnGet();
|
|
endofstate:
|
|
StateArray.Push(state);
|
|
while (*statestrp)
|
|
{
|
|
int frame=((*statestrp++)&223)-'A';
|
|
|
|
if (frame<0 || frame>28)
|
|
{
|
|
sc.ScriptError ("Frames must be A-Z, [, \\, or ]");
|
|
frame=0;
|
|
}
|
|
|
|
state.Frame=(state.Frame&(SF_FULLBRIGHT|SF_BIGTIC))|frame;
|
|
StateArray.Push(state);
|
|
count++;
|
|
}
|
|
laststate=&StateArray[count];
|
|
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;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// ResolveGotoLabel
|
|
//
|
|
//==========================================================================
|
|
|
|
static FState *ResolveGotoLabel (FScanner &sc, AActor *actor, const PClass *mytype, char *name)
|
|
{
|
|
const PClass *type=mytype;
|
|
FState *state;
|
|
char *namestart = name;
|
|
char *label, *offset, *pt;
|
|
int v;
|
|
|
|
// Check for classname
|
|
if ((pt = strstr (name, "::")) != NULL)
|
|
{
|
|
const char *classname = name;
|
|
*pt = '\0';
|
|
name = pt + 2;
|
|
|
|
// The classname may either be "Super" to identify this class's immediate
|
|
// superclass, or it may be the name of any class that this one derives from.
|
|
if (stricmp (classname, "Super") == 0)
|
|
{
|
|
type = type->ParentClass;
|
|
actor = GetDefaultByType (type);
|
|
}
|
|
else
|
|
{
|
|
// first check whether a state of the desired name exists
|
|
const PClass *stype = PClass::FindClass (classname);
|
|
if (stype == NULL)
|
|
{
|
|
sc.ScriptError ("%s is an unknown class.", classname);
|
|
}
|
|
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 (type))
|
|
{
|
|
sc.ScriptError ("%s is not derived from %s so cannot access its states.",
|
|
type->TypeName.GetChars(), stype->TypeName.GetChars());
|
|
}
|
|
if (type != stype)
|
|
{
|
|
type = stype;
|
|
actor = GetDefaultByType (type);
|
|
}
|
|
}
|
|
}
|
|
label = name;
|
|
// Check for offset
|
|
offset = NULL;
|
|
if ((pt = strchr (name, '+')) != NULL)
|
|
{
|
|
*pt = '\0';
|
|
offset = pt + 1;
|
|
}
|
|
v = offset ? strtol (offset, NULL, 0) : 0;
|
|
|
|
// Get the state's address.
|
|
if (type==mytype) state = FindState (actor, type, label);
|
|
else state = FindStateInClass (actor, type, label);
|
|
|
|
if (state != NULL)
|
|
{
|
|
state += v;
|
|
}
|
|
else if (v != 0)
|
|
{
|
|
sc.ScriptError ("Attempt to get invalid state %s from actor %s.", label, type->TypeName.GetChars());
|
|
}
|
|
delete[] namestart; // free the allocated string buffer
|
|
return state;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FixStatePointers
|
|
//
|
|
// Fixes an actor's default state pointers.
|
|
//
|
|
//==========================================================================
|
|
|
|
static void FixStatePointers (FActorInfo *actor, TArray<FStateDefine> & list)
|
|
{
|
|
for(unsigned i=0;i<list.Size(); i++)
|
|
{
|
|
size_t v=(size_t)list[i].State;
|
|
if (v >= 1 && v < 0x10000)
|
|
{
|
|
list[i].State = actor->OwnedStates + v - 1;
|
|
}
|
|
if (list[i].Children.Size() > 0) FixStatePointers(actor, list[i].Children);
|
|
}
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// FixStatePointersAgain
|
|
//
|
|
// Resolves an actor's state pointers that were specified as jumps.
|
|
//
|
|
//==========================================================================
|
|
|
|
static void FixStatePointersAgain (FScanner &sc, FActorInfo *actor, AActor *defaults, TArray<FStateDefine> & list)
|
|
{
|
|
for(unsigned i=0;i<list.Size(); i++)
|
|
{
|
|
if (list[i].State != NULL && FState::StaticFindStateOwner (list[i].State, actor) == NULL)
|
|
{ // It's not a valid state, so it must be a label string. Resolve it.
|
|
list[i].State = ResolveGotoLabel (sc, defaults, actor->Class, (char *)list[i].State);
|
|
}
|
|
if (list[i].Children.Size() > 0) FixStatePointersAgain(sc, actor, defaults, list[i].Children);
|
|
}
|
|
}
|
|
|
|
|
|
//==========================================================================
|
|
//
|
|
// FinishStates
|
|
// copies a state block and fixes all state links
|
|
//
|
|
//==========================================================================
|
|
|
|
int FinishStates (FScanner &sc, FActorInfo *actor, AActor *defaults, Baggage &bag)
|
|
{
|
|
static int c=0;
|
|
int count = StateArray.Size();
|
|
|
|
if (count > 0)
|
|
{
|
|
FState *realstates = new FState[count];
|
|
int i;
|
|
int currange;
|
|
|
|
memcpy(realstates, &StateArray[0], count*sizeof(FState));
|
|
actor->OwnedStates = realstates;
|
|
actor->NumOwnedStates = count;
|
|
|
|
// adjust the state pointers
|
|
// In the case new states are added these must be adjusted, too!
|
|
FixStatePointers (actor, StateLabels);
|
|
|
|
for(i = currange = 0; i < count; i++)
|
|
{
|
|
// resolve labels and jumps
|
|
switch((ptrdiff_t)realstates[i].NextState)
|
|
{
|
|
case 0: // next
|
|
realstates[i].NextState = (i < count-1 ? &realstates[i+1] : &realstates[0]);
|
|
break;
|
|
|
|
case -1: // stop
|
|
realstates[i].NextState = NULL;
|
|
break;
|
|
|
|
case -2: // wait
|
|
realstates[i].NextState = &realstates[i];
|
|
break;
|
|
|
|
default: // loop
|
|
if ((size_t)realstates[i].NextState < 0x10000)
|
|
{
|
|
realstates[i].NextState = &realstates[(size_t)realstates[i].NextState-1];
|
|
}
|
|
else // goto
|
|
{
|
|
realstates[i].NextState = ResolveGotoLabel (sc, defaults, bag.Info->Class, (char *)realstates[i].NextState);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
StateArray.Clear ();
|
|
|
|
// Fix state pointers that are gotos
|
|
FixStatePointersAgain (sc, actor, defaults, StateLabels);
|
|
|
|
return count;
|
|
}
|
|
|
|
//==========================================================================
|
|
//
|
|
// For getting a state address from the parent
|
|
// No attempts have been made to add new functionality here
|
|
// This is strictly for keeping compatibility with old WADs!
|
|
//
|
|
//==========================================================================
|
|
FState *CheckState(FScanner &sc, PClass *type)
|
|
{
|
|
int v=0;
|
|
|
|
if (sc.GetString() && !sc.Crossed)
|
|
{
|
|
if (sc.Compare("0")) return NULL;
|
|
else if (sc.Compare("PARENT"))
|
|
{
|
|
FState * state = NULL;
|
|
sc.MustGetString();
|
|
|
|
FActorInfo * info = type->ParentClass->ActorInfo;
|
|
|
|
if (info != NULL)
|
|
{
|
|
state = info->FindState(FName(sc.String));
|
|
}
|
|
|
|
if (sc.GetString ())
|
|
{
|
|
if (sc.Compare ("+"))
|
|
{
|
|
sc.MustGetNumber ();
|
|
v = sc.Number;
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet ();
|
|
}
|
|
}
|
|
|
|
if (state == NULL && v==0) return NULL;
|
|
|
|
if (v!=0 && state==NULL)
|
|
{
|
|
sc.ScriptError("Attempt to get invalid state from actor %s\n", type->ParentClass->TypeName.GetChars());
|
|
return NULL;
|
|
}
|
|
state+=v;
|
|
return state;
|
|
}
|
|
else sc.ScriptError("Invalid state assignment");
|
|
}
|
|
return NULL;
|
|
}
|
|
|