/* ** 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" #include "vm.h" // stores indices for symbolic state labels for some old-style DECORATE functions. FStateLabelStorage StateLabels; // 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); } //========================================================================== // // 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)) { 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)) { return info; } info = ValidateActor(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())); } //========================================================================== // // //========================================================================== FStateLabel *FStateLabels::FindLabel (FName label) { return const_cast(BinarySearch(Labels, NumLabels, &FStateLabel::Label, label)); } void FStateLabels::Destroy () { for(int i = 0; i < NumLabels; i++) { if (Labels[i].Children != NULL) { Labels[i].Children->Destroy(); M_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(GetClass()); if (info->GetStateLabels() != NULL) { FStateLabel *slabel = info->GetStateLabels()->FindLabel (NAME_Death); 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 &MakeStateNameList(const char * fname) { static TArray 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(); 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 &namelist = MakeStateNameList(name); return FindState(namelist.Size(), &namelist[0], exact); } //========================================================================== // // 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(cls->ParentClass); } return false; } //========================================================================== // // Get a statw pointer from a symbolic label // //========================================================================== FState *FStateLabelStorage::GetState(int pos, PClassActor *cls, bool exact) { 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); } } 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)); } // same as above but context aware. DEFINE_ACTION_FUNCTION(AActor, ResolveState) { PARAM_ACTION_PROLOGUE(AActor); PARAM_STATE_ACTION(newstate); ACTION_RETURN_STATE(newstate); } //========================================================================== // // Search one list of state definitions for the given name // //========================================================================== FStateDefine *FStateDefinitions::FindStateLabelInList(TArray & list, FName name, bool create) { for(unsigned i = 0; i &namelist = MakeStateNameList(name); TArray *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) { 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 &namelist = MakeStateNameList(name); TArray *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) { FStateLabel *A = (FStateLabel *)a; FStateLabel *B = (FStateLabel *)b; return ((int)A->Label - (int)B->Label); } FStateLabels *FStateDefinitions::CreateStateLabelList(TArray & 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;iLabels[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()); } // 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()->SpawnState); } auto &sl = info->ActorInfo()->StateList; if (sl != NULL) { sl->Destroy(); M_Free(sl); } sl = 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 // //=========================================================================== void FStateDefinitions::MakeStateList(const FStateLabels *list, TArray &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) { MakeStateList(cls->GetStateLabels(), StateLabels); } 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 & statelist) { for(unsigned i = 0;i 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 (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 = ValidateActor(type->ParentClass); } 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(stype); } } } 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; // 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 & 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; 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, TArray & 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 (actor, (char *)list[i].State); list[i].DefineFlags = SDF_STATE; } if (list[i].Children.Size() > 0) ResolveGotoLabels(actor, 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) { bool error = false; int frame = 0; int count = 0; while (*framechars) { bool noframe = false; if (*framechars == '#') noframe = true; else if (*framechars == '^') frame = '\\' - 'A'; else frame = (*framechars & 223) - 'A'; framechars++; if (frame < 0 || frame > 28) { frame = 0; error = true; } state->Frame = frame; if (noframe) state->StateFlags |= STF_SAMEFRAME; else state->StateFlags &= ~STF_SAMEFRAME; StateArray.Push(*state); SourceLines.Push(sc); ++count; // NODELAY flag is not carried past the first state state->StateFlags &= ~STF_NODELAY; } 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) { int count = StateArray.Size(); if (count > 0) { FState *realstates = (FState*)ClassDataAllocator.Alloc(count * sizeof(FState)); int i; memcpy(realstates, &StateArray[0], count*sizeof(FState)); actor->ActorInfo()->OwnedStates = realstates; actor->ActorInfo()->NumOwnedStates = count; SaveStateSourceLines(realstates, SourceLines); // 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, 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(actor, (char *)realstates[i].NextState); break; } } } else { // Fix state pointers that are gotos ResolveGotoLabels(actor, StateLabels); } return count; } //========================================================================== // // 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).GetChars()); } } 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(), ""); 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); }