qzdoom/src/p_states.cpp

1144 lines
30 KiB
C++
Raw Normal View History

2016-03-01 15:47:10 +00:00
/*
** p_states.cpp
** state management
**
**---------------------------------------------------------------------------
** Copyright 1998-2008 Randy Heit
** Copyright 2006-2008 Christoph Oelckers
** 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.
**---------------------------------------------------------------------------
**
**
*/
#include "actor.h"
#include "templates.h"
#include "cmdlib.h"
#include "i_system.h"
#include "c_dispatch.h"
#include "v_text.h"
#include "thingdef.h"
#include "r_state.h"
2016-03-01 15:47:10 +00:00
- fixed: State labels were resolved in the calling function's context instead of the called function one's. This could cause problems with functions that take states as parameters but use them to set them internally instead of passing them through the A_Jump interface back to the caller, like A_Chase or A_LookEx. This required some quite significant refactoring because the entire state resolution logic had been baked into the compiler which turned out to be a major maintenance problem. Fixed this by adding a new builtin type 'statelabel'. This is an opaque identifier representing a state, with the actual data either directly encoded into the number for single label state or an index into a state information table. The state resolution is now the task of the called function as it should always have remained. Note, that this required giving back the 'action' qualifier to most state jumping functions. - refactored most A_Jump checkers to a two stage setup with a pure checker that returns a boolean and a scripted A_Jump wrapper, for some simpler checks the checker function was entirely omitted and calculated inline in the A_Jump function. It is strongly recommended to use the boolean checkers unless using an inline function invocation in a state as they lead to vastly clearer code and offer more flexibility. - let Min() and Max() use the OP_MIN and OP_MAX opcodes. Although these were present, these function were implemented using some grossly inefficient branching tests. - the DECORATE 'state' cast kludge will now actually call ResolveState because a state label is not a state and needs conversion.
2016-11-14 13:12:27 +00:00
// stores indices for symbolic state labels for some old-style DECORATE functions.
FStateLabelStorage StateLabels;
2016-03-01 15:47:10 +00:00
// Each state is owned by an actor. Actors can own any number of
// states, but a single state cannot be owned by more than one
// actor. States are archived by recording the actor they belong
// to and the index into that actor's list of states.
//==========================================================================
//
// This wraps everything needed to get a current sprite from a state into
// one single script function.
//
//==========================================================================
DEFINE_ACTION_FUNCTION(FState, GetSpriteTexture)
{
PARAM_SELF_STRUCT_PROLOGUE(FState);
PARAM_INT(rotation);
PARAM_INT_DEF(skin);
PARAM_FLOAT_DEF(scalex);
PARAM_FLOAT_DEF(scaley);
spriteframe_t *sprframe;
if (skin == 0)
{
sprframe = &SpriteFrames[sprites[self->sprite].spriteframes + self->GetFrame()];
}
else
{
sprframe = &SpriteFrames[sprites[Skins[skin].sprite].spriteframes + self->GetFrame()];
scalex = Skins[skin].Scale.X;
scaley = Skins[skin].Scale.Y;
}
if (numret > 0) ret[0].SetInt(sprframe->Texture[rotation].GetIndex());
if (numret > 1) ret[1].SetInt(!!(sprframe->Flip & (1 << rotation)));
if (numret > 2) ret[2].SetVector2(DVector2(scalex, scaley));
return MIN(3, numret);
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// Find the actor that a state belongs to.
//
//==========================================================================
PClassActor *FState::StaticFindStateOwner (const FState *state)
{
for (unsigned int i = 0; i < PClassActor::AllActorClasses.Size(); ++i)
{
PClassActor *info = PClassActor::AllActorClasses[i];
if (info->OwnsState(state))
2016-03-01 15:47:10 +00:00
{
return info;
}
}
return NULL;
}
//==========================================================================
//
// Find the actor that a state belongs to, but restrict the search to
// the specified type and its ancestors.
//
//==========================================================================
PClassActor *FState::StaticFindStateOwner (const FState *state, PClassActor *info)
{
while (info != NULL)
{
if (info->OwnsState(state))
2016-03-01 15:47:10 +00:00
{
return info;
}
info = dyn_cast<PClassActor>(info->ParentClass);
}
return NULL;
}
//==========================================================================
//
//
//==========================================================================
FString FState::StaticGetStateName(const FState *state)
{
auto so = FState::StaticFindStateOwner(state);
return FStringf("%s.%d", so->TypeName.GetChars(), int(state - so->GetStates()));
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
//
//==========================================================================
FStateLabel *FStateLabels::FindLabel (FName label)
{
return const_cast<FStateLabel *>(BinarySearch<FStateLabel, FName>(Labels, NumLabels, &FStateLabel::Label, label));
}
void FStateLabels::Destroy ()
{
for(int i = 0; i < NumLabels; i++)
{
if (Labels[i].Children != NULL)
{
Labels[i].Children->Destroy();
free(Labels[i].Children); // These are malloc'd, not new'd!
Labels[i].Children = NULL;
}
}
}
//===========================================================================
//
// HasStates
//
// Checks whether the actor has special death states.
//
//===========================================================================
bool AActor::HasSpecialDeathStates () const
{
const PClassActor *info = static_cast<PClassActor *>(GetClass());
if (info->GetStateLabels() != NULL)
2016-03-01 15:47:10 +00:00
{
FStateLabel *slabel = info->GetStateLabels()->FindLabel (NAME_Death);
2016-03-01 15:47:10 +00:00
if (slabel != NULL && slabel->Children != NULL)
{
for(int i = 0; i < slabel->Children->NumLabels; i++)
{
if (slabel->Children->Labels[i].State != NULL)
{
return true;
}
}
}
}
return false;
}
//==========================================================================
//
// Creates a list of names from a string. Dots are used as separator
//
//==========================================================================
TArray<FName> &MakeStateNameList(const char * fname)
{
static TArray<FName> namelist(3);
FName firstpart, secondpart;
char *c;
// Handle the old names for the existing death states
char *name = copystring(fname);
firstpart = strtok(name, ".");
switch (firstpart)
{
case NAME_Burn:
firstpart = NAME_Death;
secondpart = NAME_Fire;
break;
case NAME_Ice:
firstpart = NAME_Death;
secondpart = NAME_Ice;
break;
case NAME_Disintegrate:
firstpart = NAME_Death;
secondpart = NAME_Disintegrate;
break;
case NAME_XDeath:
firstpart = NAME_Death;
secondpart = NAME_Extreme;
break;
}
namelist.Clear();
namelist.Push(firstpart);
if (secondpart != NAME_None)
{
namelist.Push(secondpart);
}
while ((c = strtok(NULL, ".")) != NULL)
{
FName cc = c;
namelist.Push(cc);
}
delete[] name;
return namelist;
}
//===========================================================================
//
// FindState (multiple names version)
//
// Finds a state that matches as many of the supplied names as possible.
// A state with more names than those provided does not match.
// A state with fewer names can match if there are no states with the exact
// same number of names.
//
// The search proceeds like this. For the current class, keeping matching
// names until there are no more. If both the argument list and the state
// are out of names, it's an exact match, so return it. If the state still
// has names, ignore it. If the argument list still has names, remember it.
//
//===========================================================================
FState *PClassActor::FindState(int numnames, FName *names, bool exact) const
{
FStateLabels *labels = GetStateLabels();
2016-03-01 15:47:10 +00:00
FState *best = NULL;
if (labels != NULL)
{
int count = 0;
FStateLabel *slabel = NULL;
FName label;
// Find the best-matching label for this class.
while (labels != NULL && count < numnames)
{
label = *names++;
slabel = labels->FindLabel(label);
if (slabel != NULL)
{
count++;
labels = slabel->Children;
best = slabel->State;
}
else
{
break;
}
}
if (count < numnames && exact)
{
return NULL;
}
}
return best;
}
//==========================================================================
//
// Finds the state associated with the given string
//
//==========================================================================
FState *PClassActor::FindStateByString(const char *name, bool exact)
{
TArray<FName> &namelist = MakeStateNameList(name);
return FindState(namelist.Size(), &namelist[0], exact);
}
- fixed: State labels were resolved in the calling function's context instead of the called function one's. This could cause problems with functions that take states as parameters but use them to set them internally instead of passing them through the A_Jump interface back to the caller, like A_Chase or A_LookEx. This required some quite significant refactoring because the entire state resolution logic had been baked into the compiler which turned out to be a major maintenance problem. Fixed this by adding a new builtin type 'statelabel'. This is an opaque identifier representing a state, with the actual data either directly encoded into the number for single label state or an index into a state information table. The state resolution is now the task of the called function as it should always have remained. Note, that this required giving back the 'action' qualifier to most state jumping functions. - refactored most A_Jump checkers to a two stage setup with a pure checker that returns a boolean and a scripted A_Jump wrapper, for some simpler checks the checker function was entirely omitted and calculated inline in the A_Jump function. It is strongly recommended to use the boolean checkers unless using an inline function invocation in a state as they lead to vastly clearer code and offer more flexibility. - let Min() and Max() use the OP_MIN and OP_MAX opcodes. Although these were present, these function were implemented using some grossly inefficient branching tests. - the DECORATE 'state' cast kludge will now actually call ResolveState because a state label is not a state and needs conversion.
2016-11-14 13:12:27 +00:00
//==========================================================================
//
// validate a runtime state index.
//
//==========================================================================
static bool VerifyJumpTarget(PClassActor *cls, FState *CallingState, int index)
{
while (cls != RUNTIME_CLASS(AActor))
{
// both calling and target state need to belong to the same class.
if (cls->OwnsState(CallingState))
{
return cls->OwnsState(CallingState + index);
}
// We can safely assume the ParentClass is of type PClassActor
// since we stop when we see the Actor base class.
cls = static_cast<PClassActor *>(cls->ParentClass);
}
return false;
}
//==========================================================================
//
// Get a statw pointer from a symbolic label
//
//==========================================================================
2016-03-01 15:47:10 +00:00
FState *FStateLabelStorage::GetState(int pos, PClassActor *cls, bool exact)
- fixed: State labels were resolved in the calling function's context instead of the called function one's. This could cause problems with functions that take states as parameters but use them to set them internally instead of passing them through the A_Jump interface back to the caller, like A_Chase or A_LookEx. This required some quite significant refactoring because the entire state resolution logic had been baked into the compiler which turned out to be a major maintenance problem. Fixed this by adding a new builtin type 'statelabel'. This is an opaque identifier representing a state, with the actual data either directly encoded into the number for single label state or an index into a state information table. The state resolution is now the task of the called function as it should always have remained. Note, that this required giving back the 'action' qualifier to most state jumping functions. - refactored most A_Jump checkers to a two stage setup with a pure checker that returns a boolean and a scripted A_Jump wrapper, for some simpler checks the checker function was entirely omitted and calculated inline in the A_Jump function. It is strongly recommended to use the boolean checkers unless using an inline function invocation in a state as they lead to vastly clearer code and offer more flexibility. - let Min() and Max() use the OP_MIN and OP_MAX opcodes. Although these were present, these function were implemented using some grossly inefficient branching tests. - the DECORATE 'state' cast kludge will now actually call ResolveState because a state label is not a state and needs conversion.
2016-11-14 13:12:27 +00:00
{
if (pos > 0x10000000)
{
return cls? cls->FindState(ENamedName(pos - 0x10000000)) : nullptr;
}
else if (pos < 0)
{
// decode the combined value produced by the script.
int index = (pos >> 16) & 32767;
pos = ((pos & 65535) - 1) * 4;
FState *state;
memcpy(&state, &Storage[pos + sizeof(int)], sizeof(state));
if (VerifyJumpTarget(cls, state, index))
return state + index;
else
return nullptr;
}
else if (pos > 0)
{
int val;
pos = (pos - 1) * 4;
memcpy(&val, &Storage[pos], sizeof(int));
if (val == 0)
{
FState *state;
memcpy(&state, &Storage[pos + sizeof(int)], sizeof(state));
return state;
}
else if (cls != nullptr)
{
FName *labels = (FName*)&Storage[pos + sizeof(int)];
return cls->FindState(val, labels, exact);
- fixed: State labels were resolved in the calling function's context instead of the called function one's. This could cause problems with functions that take states as parameters but use them to set them internally instead of passing them through the A_Jump interface back to the caller, like A_Chase or A_LookEx. This required some quite significant refactoring because the entire state resolution logic had been baked into the compiler which turned out to be a major maintenance problem. Fixed this by adding a new builtin type 'statelabel'. This is an opaque identifier representing a state, with the actual data either directly encoded into the number for single label state or an index into a state information table. The state resolution is now the task of the called function as it should always have remained. Note, that this required giving back the 'action' qualifier to most state jumping functions. - refactored most A_Jump checkers to a two stage setup with a pure checker that returns a boolean and a scripted A_Jump wrapper, for some simpler checks the checker function was entirely omitted and calculated inline in the A_Jump function. It is strongly recommended to use the boolean checkers unless using an inline function invocation in a state as they lead to vastly clearer code and offer more flexibility. - let Min() and Max() use the OP_MIN and OP_MAX opcodes. Although these were present, these function were implemented using some grossly inefficient branching tests. - the DECORATE 'state' cast kludge will now actually call ResolveState because a state label is not a state and needs conversion.
2016-11-14 13:12:27 +00:00
}
}
return nullptr;
}
//==========================================================================
//
// State label conversion function for scripts
//
//==========================================================================
DEFINE_ACTION_FUNCTION(AActor, FindState)
{
PARAM_SELF_PROLOGUE(AActor);
PARAM_INT(newstate);
PARAM_BOOL_DEF(exact)
ACTION_RETURN_STATE(StateLabels.GetState(newstate, self->GetClass(), exact));
- fixed: State labels were resolved in the calling function's context instead of the called function one's. This could cause problems with functions that take states as parameters but use them to set them internally instead of passing them through the A_Jump interface back to the caller, like A_Chase or A_LookEx. This required some quite significant refactoring because the entire state resolution logic had been baked into the compiler which turned out to be a major maintenance problem. Fixed this by adding a new builtin type 'statelabel'. This is an opaque identifier representing a state, with the actual data either directly encoded into the number for single label state or an index into a state information table. The state resolution is now the task of the called function as it should always have remained. Note, that this required giving back the 'action' qualifier to most state jumping functions. - refactored most A_Jump checkers to a two stage setup with a pure checker that returns a boolean and a scripted A_Jump wrapper, for some simpler checks the checker function was entirely omitted and calculated inline in the A_Jump function. It is strongly recommended to use the boolean checkers unless using an inline function invocation in a state as they lead to vastly clearer code and offer more flexibility. - let Min() and Max() use the OP_MIN and OP_MAX opcodes. Although these were present, these function were implemented using some grossly inefficient branching tests. - the DECORATE 'state' cast kludge will now actually call ResolveState because a state label is not a state and needs conversion.
2016-11-14 13:12:27 +00:00
}
// same as above but context aware.
DEFINE_ACTION_FUNCTION(AActor, ResolveState)
- fixed: State labels were resolved in the calling function's context instead of the called function one's. This could cause problems with functions that take states as parameters but use them to set them internally instead of passing them through the A_Jump interface back to the caller, like A_Chase or A_LookEx. This required some quite significant refactoring because the entire state resolution logic had been baked into the compiler which turned out to be a major maintenance problem. Fixed this by adding a new builtin type 'statelabel'. This is an opaque identifier representing a state, with the actual data either directly encoded into the number for single label state or an index into a state information table. The state resolution is now the task of the called function as it should always have remained. Note, that this required giving back the 'action' qualifier to most state jumping functions. - refactored most A_Jump checkers to a two stage setup with a pure checker that returns a boolean and a scripted A_Jump wrapper, for some simpler checks the checker function was entirely omitted and calculated inline in the A_Jump function. It is strongly recommended to use the boolean checkers unless using an inline function invocation in a state as they lead to vastly clearer code and offer more flexibility. - let Min() and Max() use the OP_MIN and OP_MAX opcodes. Although these were present, these function were implemented using some grossly inefficient branching tests. - the DECORATE 'state' cast kludge will now actually call ResolveState because a state label is not a state and needs conversion.
2016-11-14 13:12:27 +00:00
{
PARAM_ACTION_PROLOGUE(AActor);
PARAM_STATE_ACTION(newstate);
ACTION_RETURN_STATE(newstate);
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// Search one list of state definitions for the given name
//
//==========================================================================
FStateDefine *FStateDefinitions::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;
def.DefineFlags = SDF_NEXT;
return &list[list.Push(def)];
}
return NULL;
}
//==========================================================================
//
// Finds the address of a state label given by name.
// Adds the state label if it doesn't exist
//
//==========================================================================
FStateDefine *FStateDefinitions::FindStateAddress(const char *name)
{
FStateDefine *statedef = NULL;
TArray<FName> &namelist = MakeStateNameList(name);
TArray<FStateDefine> *statelist = &StateLabels;
for(unsigned i = 0; i < namelist.Size(); i++)
{
statedef = FindStateLabelInList(*statelist, namelist[i], true);
statelist = &statedef->Children;
}
return statedef;
}
//==========================================================================
//
// Adds a new state to the curremt list
//
//==========================================================================
void FStateDefinitions::SetStateLabel(const char *statename, FState *state, uint8_t defflags)
2016-03-01 15:47:10 +00:00
{
FStateDefine *std = FindStateAddress(statename);
std->State = state;
std->DefineFlags = defflags;
}
//==========================================================================
//
// Adds a new state to the current list
//
//==========================================================================
void FStateDefinitions::AddStateLabel(const char *statename)
{
intptr_t index = StateArray.Size();
FStateDefine *std = FindStateAddress(statename);
std->State = (FState *)(index+1);
std->DefineFlags = SDF_INDEX;
laststate = NULL;
lastlabel = index;
}
//==========================================================================
//
// Returns the index a state label points to. May only be called before
// installing states.
//
//==========================================================================
int FStateDefinitions::GetStateLabelIndex (FName statename)
{
FStateDefine *std = FindStateLabelInList(StateLabels, statename, false);
if (std == NULL)
{
return -1;
}
assert((size_t)std->State <= StateArray.Size() + 1);
return (int)((ptrdiff_t)std->State - 1);
}
//==========================================================================
//
// Finds the state associated with the given name
// returns NULL if none found
//
//==========================================================================
FState *FStateDefinitions::FindState(const char * name)
{
FStateDefine *statedef = NULL;
TArray<FName> &namelist = MakeStateNameList(name);
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;
}
//==========================================================================
//
// Creates the final list of states from the state definitions
//
//==========================================================================
static int labelcmp(const void *a, const void *b)
2016-03-01 15:47:10 +00:00
{
FStateLabel *A = (FStateLabel *)a;
FStateLabel *B = (FStateLabel *)b;
return ((int)A->Label - (int)B->Label);
}
FStateLabels *FStateDefinitions::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 FStateDefinitions::InstallStates(PClassActor *info, AActor *defaults)
{
if (defaults == nullptr)
{
I_Error("Called InstallStates without actor defaults in %s", info->TypeName.GetChars());
}
2016-03-01 15:47:10 +00:00
// First ensure we have a valid spawn state.
FState *state = FindState("Spawn");
if (state == NULL)
{
// A NULL spawn state will crash the engine so set it to something valid.
SetStateLabel("Spawn", GetDefault<AActor>()->SpawnState);
}
auto &sl = info->ActorInfo()->StateList;
if (sl != NULL)
2016-03-01 15:47:10 +00:00
{
sl->Destroy();
M_Free(sl);
2016-03-01 15:47:10 +00:00
}
sl = CreateStateLabelList(StateLabels);
2016-03-01 15:47:10 +00:00
// 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
//
//===========================================================================
void FStateDefinitions::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;
def.DefineFlags = SDF_STATE;
dest.Push(def);
if (list->Labels[i].Children != NULL)
{
MakeStateList(list->Labels[i].Children, dest[dest.Size()-1].Children);
}
}
}
void FStateDefinitions::MakeStateDefines(const PClassActor *cls)
{
StateArray.Clear();
laststate = NULL;
laststatebeforelabel = NULL;
lastlabel = -1;
if (cls != NULL && cls->GetStateLabels() != NULL)
2016-03-01 15:47:10 +00:00
{
MakeStateList(cls->GetStateLabels(), StateLabels);
2016-03-01 15:47:10 +00:00
}
else
{
StateLabels.Clear();
}
}
//===========================================================================
//
// AddStateDefines
//
// Adds a list of states to the current definitions
//
//===========================================================================
void FStateDefinitions::AddStateDefines(const FStateLabels *list)
{
if (list != NULL) for(int i = 0; i < list->NumLabels; i++)
{
if (list->Labels[i].Children == NULL)
{
if (!FindStateLabelInList(StateLabels, list->Labels[i].Label, false))
{
FStateDefine def;
def.Label = list->Labels[i].Label;
def.State = list->Labels[i].State;
def.DefineFlags = SDF_STATE;
StateLabels.Push(def);
}
}
}
}
//==========================================================================
//
// 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.
//
//==========================================================================
void FStateDefinitions::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].DefineFlags == SDF_INDEX)
{
if (target == NULL)
{
statelist[i].State = NULL;
statelist[i].DefineFlags = SDF_STOP;
}
else
{
statelist[i].State = (FState *)copystring (target);
statelist[i].DefineFlags = SDF_LABEL;
}
}
if (statelist[i].Children.Size() > 0)
{
RetargetStatePointers(count, target, statelist[i].Children);
}
}
}
void FStateDefinitions::RetargetStates (intptr_t count, const char *target)
{
RetargetStatePointers(count, target, StateLabels);
}
//==========================================================================
//
// ResolveGotoLabel
//
// Resolves any strings being stored in a state's NextState field
//
//==========================================================================
FState *FStateDefinitions::ResolveGotoLabel (AActor *actor, PClassActor *mytype, char *name)
{
PClassActor *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 = dyn_cast<PClassActor>(type->ParentClass);
actor = GetDefaultByType(type);
}
else
{
// first check whether a state of the desired name exists
PClass *stype = PClass::FindClass (classname);
if (stype == NULL)
{
I_Error ("%s is an unknown class.", classname);
}
if (!stype->IsDescendantOf (RUNTIME_CLASS(AActor)))
{
I_Error ("%s is not an actor class, so it has no states.", stype->TypeName.GetChars());
}
if (!stype->IsAncestorOf (type))
{
I_Error ("%s is not derived from %s so cannot access its states.",
type->TypeName.GetChars(), stype->TypeName.GetChars());
}
if (type != stype)
{
type = static_cast<PClassActor *>(stype);
actor = GetDefaultByType (type);
}
}
}
label = name;
// Check for offset
offset = NULL;
if ((pt = strchr (name, '+')) != NULL)
{
*pt = '\0';
offset = pt + 1;
}
v = offset ? (int)strtoll (offset, NULL, 0) : 0;
2016-03-01 15:47:10 +00:00
// Get the state's address.
if (type == mytype)
{
state = FindState (label);
}
else
{
state = type->FindStateByString(label, true);
}
if (state != NULL)
{
state += v;
}
else if (v != 0)
{
I_Error ("Attempt to get invalid state %s from actor %s.", label, type->TypeName.GetChars());
}
else
{
Printf (TEXTCOLOR_RED "Attempt to get invalid state %s from actor %s.\n", label, type->TypeName.GetChars());
}
delete[] namestart; // free the allocated string buffer
return state;
}
//==========================================================================
//
// FixStatePointers
//
// Fixes an actor's default state pointers.
//
//==========================================================================
void FStateDefinitions::FixStatePointers (PClassActor *actor, TArray<FStateDefine> & list)
{
for (unsigned i = 0; i < list.Size(); i++)
{
if (list[i].DefineFlags == SDF_INDEX)
{
size_t v = (size_t)list[i].State;
list[i].State = actor->GetStates() + v - 1;
2016-03-01 15:47:10 +00:00
list[i].DefineFlags = SDF_STATE;
}
if (list[i].Children.Size() > 0)
{
FixStatePointers(actor, list[i].Children);
}
}
}
//==========================================================================
//
// ResolveGotoLabels
//
// Resolves an actor's state pointers that were specified as jumps.
//
//==========================================================================
void FStateDefinitions::ResolveGotoLabels (PClassActor *actor, AActor *defaults, TArray<FStateDefine> & list)
{
for (unsigned i = 0; i < list.Size(); i++)
{
if (list[i].State != NULL && list[i].DefineFlags == SDF_LABEL)
{ // It's not a valid state, so it must be a label string. Resolve it.
list[i].State = ResolveGotoLabel (defaults, actor, (char *)list[i].State);
list[i].DefineFlags = SDF_STATE;
}
if (list[i].Children.Size() > 0) ResolveGotoLabels(actor, defaults, list[i].Children);
}
}
//==========================================================================
//
// SetGotoLabel
//
// sets a jump at the current state or retargets a label
//
//==========================================================================
bool FStateDefinitions::SetGotoLabel(const char *string)
{
// copy the text - this must be resolved later!
if (laststate != NULL)
{ // Following a state definition: Modify it.
laststate->NextState = (FState*)copystring(string);
laststate->DefineFlags = SDF_LABEL;
laststatebeforelabel = NULL;
return true;
}
else if (lastlabel >= 0)
{ // Following a label: Retarget it.
RetargetStates (lastlabel+1, string);
if (laststatebeforelabel != NULL)
{
laststatebeforelabel->NextState = (FState*)copystring(string);
laststatebeforelabel->DefineFlags = SDF_LABEL;
laststatebeforelabel = NULL;
}
return true;
}
return false;
}
//==========================================================================
//
// SetStop
//
// sets a stop operation
//
//==========================================================================
bool FStateDefinitions::SetStop()
{
if (laststate != NULL)
{
laststate->DefineFlags = SDF_STOP;
laststatebeforelabel = NULL;
return true;
}
else if (lastlabel >=0)
{
RetargetStates (lastlabel+1, NULL);
if (laststatebeforelabel != NULL)
{
laststatebeforelabel->DefineFlags = SDF_STOP;
laststatebeforelabel = NULL;
}
return true;
}
return false;
}
//==========================================================================
//
// SetWait
//
// sets a wait or fail operation
//
//==========================================================================
bool FStateDefinitions::SetWait()
{
if (laststate != NULL)
{
laststate->DefineFlags = SDF_WAIT;
laststatebeforelabel = NULL;
return true;
}
return false;
}
//==========================================================================
//
// SetLoop
//
// sets a loop operation
//
//==========================================================================
bool FStateDefinitions::SetLoop()
{
if (laststate != NULL)
{
laststate->DefineFlags = SDF_INDEX;
laststate->NextState = (FState*)(lastlabel+1);
laststatebeforelabel = NULL;
return true;
}
return false;
}
//==========================================================================
//
// AddStates
//
// Adds some state to the current definition set. Returns the number of
// states added. Positive = no errors, negative = errors.
//
//==========================================================================
int FStateDefinitions::AddStates(FState *state, const char *framechars, const FScriptPosition &sc)
2016-03-01 15:47:10 +00:00
{
bool error = false;
int frame = 0;
int count = 0;
while (*framechars)
{
bool noframe = false;
if (*framechars == '#')
noframe = true;
else if (*framechars == '^')
2016-03-01 15:47:10 +00:00
frame = '\\' - 'A';
else
2016-03-01 15:47:10 +00:00
frame = (*framechars & 223) - 'A';
framechars++;
if (frame < 0 || frame > 28)
{
frame = 0;
error = true;
}
state->Frame = frame;
if (noframe) state->StateFlags |= STF_SAMEFRAME;
2016-11-22 12:03:46 +00:00
else state->StateFlags &= ~STF_SAMEFRAME;
2016-03-01 15:47:10 +00:00
StateArray.Push(*state);
SourceLines.Push(sc);
2016-03-01 15:47:10 +00:00
++count;
// NODELAY flag is not carried past the first state
2016-11-22 12:03:46 +00:00
state->StateFlags &= ~STF_NODELAY;
2016-03-01 15:47:10 +00:00
}
laststate = &StateArray[StateArray.Size() - 1];
laststatebeforelabel = laststate;
return !error ? count : -count;
}
//==========================================================================
//
// FinishStates
// copies a state block and fixes all state links using the current list of labels
//
//==========================================================================
int FStateDefinitions::FinishStates(PClassActor *actor, AActor *defaults)
{
int count = StateArray.Size();
if (count > 0)
{
FState *realstates = (FState*)ClassDataAllocator.Alloc(count * sizeof(FState));
2016-03-01 15:47:10 +00:00
int i;
memcpy(realstates, &StateArray[0], count*sizeof(FState));
actor->ActorInfo()->OwnedStates = realstates;
actor->ActorInfo()->NumOwnedStates = count;
SaveStateSourceLines(realstates, SourceLines);
2016-03-01 15:47:10 +00:00
// adjust the state pointers
// In the case new states are added these must be adjusted, too!
FixStatePointers(actor, StateLabels);
// Fix state pointers that are gotos
ResolveGotoLabels(actor, defaults, StateLabels);
for (i = 0; i < count; i++)
{
// resolve labels and jumps
switch (realstates[i].DefineFlags)
{
case SDF_STOP: // stop
realstates[i].NextState = NULL;
break;
case SDF_WAIT: // wait
realstates[i].NextState = &realstates[i];
break;
case SDF_NEXT: // next
realstates[i].NextState = (i < count-1 ? &realstates[i+1] : &realstates[0]);
break;
case SDF_INDEX: // loop
realstates[i].NextState = &realstates[(size_t)realstates[i].NextState-1];
break;
case SDF_LABEL:
realstates[i].NextState = ResolveGotoLabel(defaults, actor, (char *)realstates[i].NextState);
break;
}
}
}
else
{
// Fix state pointers that are gotos
ResolveGotoLabels(actor, defaults, StateLabels);
}
return count;
}
2016-03-01 15:47:10 +00:00
//==========================================================================
//
// Prints all state label info to the logfile
//
//==========================================================================
void DumpStateHelper(FStateLabels *StateList, const FString &prefix)
{
for (int i = 0; i < StateList->NumLabels; i++)
{
if (StateList->Labels[i].State != NULL)
{
const PClassActor *owner = FState::StaticFindStateOwner(StateList->Labels[i].State);
if (owner == NULL)
{
Printf(PRINT_LOG, "%s%s: invalid\n", prefix.GetChars(), StateList->Labels[i].Label.GetChars());
}
else
{
Printf(PRINT_LOG, "%s%s: %s\n", prefix.GetChars(), StateList->Labels[i].Label.GetChars(), FState::StaticGetStateName(StateList->Labels[i].State));
2016-03-01 15:47:10 +00:00
}
}
if (StateList->Labels[i].Children != NULL)
{
DumpStateHelper(StateList->Labels[i].Children, prefix + '.' + StateList->Labels[i].Label.GetChars());
}
}
}
CCMD(dumpstates)
{
for (unsigned int i = 0; i < PClassActor::AllActorClasses.Size(); ++i)
{
PClassActor *info = PClassActor::AllActorClasses[i];
Printf(PRINT_LOG, "State labels for %s\n", info->TypeName.GetChars());
DumpStateHelper(info->GetStateLabels(), "");
2016-03-01 15:47:10 +00:00
Printf(PRINT_LOG, "----------------------------\n");
}
}
//==========================================================================
//
// sets up the script-side version of states
//
//==========================================================================
DEFINE_FIELD(FState, NextState)
DEFINE_FIELD(FState, sprite)
DEFINE_FIELD(FState, Tics)
DEFINE_FIELD(FState, TicRange)
DEFINE_FIELD(FState, Frame)
DEFINE_FIELD(FState, UseFlags)
DEFINE_FIELD(FState, Misc1)
DEFINE_FIELD(FState, Misc2)
DEFINE_FIELD_BIT(FState, StateFlags, bSlow, STF_SLOW)
DEFINE_FIELD_BIT(FState, StateFlags, bFast, STF_FAST)
DEFINE_FIELD_BIT(FState, StateFlags, bFullbright, STF_FULLBRIGHT)
DEFINE_FIELD_BIT(FState, StateFlags, bNoDelay, STF_NODELAY)
DEFINE_FIELD_BIT(FState, StateFlags, bSameFrame, STF_SAMEFRAME)
DEFINE_FIELD_BIT(FState, StateFlags, bCanRaise, STF_CANRAISE)
DEFINE_FIELD_BIT(FState, StateFlags, bDehacked, STF_DEHACKED)
DEFINE_ACTION_FUNCTION(FState, DistanceTo)
{
PARAM_SELF_STRUCT_PROLOGUE(FState);
PARAM_POINTER(other, FState);
int retv = INT_MIN;
if (other != nullptr)
{
// Safely calculate the distance between two states.
auto o1 = FState::StaticFindStateOwner(self);
if (o1->OwnsState(other)) retv = int(other - self);
}
ACTION_RETURN_INT(retv);
}
DEFINE_ACTION_FUNCTION(FState, ValidateSpriteFrame)
{
PARAM_SELF_STRUCT_PROLOGUE(FState);
ACTION_RETURN_BOOL(self->Frame < sprites[self->sprite].numframes);
}