From bd60fd754633890cb7e6ee404d26b59d14280028 Mon Sep 17 00:00:00 2001 From: Boondorl Date: Thu, 23 Jan 2025 10:01:11 -0500 Subject: [PATCH] Added initial Behavior API Allows for arbitrary behaviors to be attached to Actors in a way that doesn't require the use of Inventory tokens. This list can't be freely modified nor can it have duplicates meaning it has far better deterministic behavior than using Inventory items for this purpose. --- src/namedef_custom.h | 1 + src/playsim/actor.h | 13 ++ src/playsim/p_mobj.cpp | 252 +++++++++++++++++++++++++- src/serializer_doom.cpp | 27 +++ src/serializer_doom.h | 1 + wadsrc/static/zscript/actors/actor.zs | 15 ++ 6 files changed, 308 insertions(+), 1 deletion(-) diff --git a/src/namedef_custom.h b/src/namedef_custom.h index dbf94dbafc..af61b6499c 100644 --- a/src/namedef_custom.h +++ b/src/namedef_custom.h @@ -452,6 +452,7 @@ xx(Readthismenu) xx(Playermenu) // more stuff +xx(Behavior) xx(ColorSet) xx(NeverSwitchOnPickup) xx(MoveBob) diff --git a/src/playsim/actor.h b/src/playsim/actor.h index 2f461e1089..f5bfefb5a9 100644 --- a/src/playsim/actor.h +++ b/src/playsim/actor.h @@ -793,6 +793,7 @@ public: virtual void OnDestroy() override; virtual void Serialize(FSerializer &arc) override; + virtual size_t PropagateMark() override; virtual void PostSerialize() override; virtual void PostBeginPlay() override; // Called immediately before the actor's first tick virtual void Tick() override; @@ -1373,6 +1374,7 @@ public: // landing speed from a jump with normal gravity (squats the player's view) // (note: this is put into AActor instead of the PlayerPawn because non-players also use the value) double LandingSpeed; + TMap Behaviors; // ThingIDs @@ -1434,6 +1436,17 @@ public: return GetClass()->FindState(numnames, names, exact); } + DObject* FindBehavior(const PClass& type) const + { + auto b = Behaviors.CheckKey(type.TypeName); + return b != nullptr && *b != nullptr && !((*b)->ObjectFlags & OF_EuthanizeMe) ? *b : nullptr; + } + DObject* AddBehavior(const PClass& type); + bool RemoveBehavior(const PClass& type); + void TickBehaviors(); + void MoveBehaviors(AActor& from); + void ClearBehaviors(); + bool HasSpecialDeathStates () const; double X() const diff --git a/src/playsim/p_mobj.cpp b/src/playsim/p_mobj.cpp index 9700118504..743843852f 100644 --- a/src/playsim/p_mobj.cpp +++ b/src/playsim/p_mobj.cpp @@ -197,6 +197,22 @@ void AActor::EnableNetworking(const bool enable) Super::EnableNetworking(true); } +//========================================================================== +// +// AActor :: PropagateMark +// +//========================================================================== + +size_t AActor::PropagateMark() +{ + TMap::Iterator it = { Behaviors }; + TMap::Pair* pair = nullptr; + while (it.NextPair(pair)) + GC::Mark(pair->Value); + + return Super::PropagateMark(); +} + //========================================================================== // // AActor :: Serialize @@ -402,7 +418,8 @@ void AActor::Serialize(FSerializer &arc) ("morphflags", MorphFlags) ("premorphproperties", PremorphProperties) ("morphexitflash", MorphExitFlash) - ("damagesource", damagesource); + ("damagesource", damagesource) + ("behaviors", Behaviors); SerializeTerrain(arc, "floorterrain", floorterrain, &def->floorterrain); @@ -444,6 +461,229 @@ void AActor::PostSerialize() UpdateWaterLevel(false); } +//========================================================================== +// +// Behaviors allow for actions to be defined on Actors not coupled to +// specific inventory tokens. Only one can be attached at a time. +// +//========================================================================== + +bool AActor::RemoveBehavior(const PClass& type) +{ + if (Behaviors.CheckKey(type.TypeName)) + { + auto b = Behaviors[type.TypeName]; + + Behaviors.Remove(type.TypeName); + if (b != nullptr && !(b->ObjectFlags & OF_EuthanizeMe)) + { + b->Destroy(); + return true; + } + } + + return false; +} + +static int RemoveBehavior(AActor* self, PClass* type) +{ + return self->RemoveBehavior(*type); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, RemoveBehavior, RemoveBehavior) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_CLASS_NOT_NULL(type, DObject); + ACTION_RETURN_BOOL(self->RemoveBehavior(*type)); +} + +DObject* AActor::AddBehavior(const PClass& type) +{ + if (type.bAbstract || !type.IsDescendantOf(NAME_Behavior)) + return nullptr; + + auto b = FindBehavior(type); + if (b == nullptr) + { + b = Create(); + if (b == nullptr) + return nullptr; + + auto& owner = b->PointerVar(NAME_Owner); + owner = this; + + Behaviors[type.TypeName] = b; + IFOVERRIDENVIRTUALPTRNAME(b, NAME_Behavior, Initialize) + { + VMValue params[] = { b }; + VMCall(func, params, 1, nullptr, 0); + + if (b->ObjectFlags & OF_EuthanizeMe) + { + RemoveBehavior(type); + return nullptr; + } + } + } + else + { + IFOVERRIDENVIRTUALPTRNAME(b, NAME_Behavior, Readded) + { + VMValue params[] = { b }; + VMCall(func, params, 1, nullptr, 0); + + if (b->ObjectFlags & OF_EuthanizeMe) + { + RemoveBehavior(type); + return nullptr; + } + } + } + + return b; +} + +static DObject* AddBehavior(AActor* self, PClass* type) +{ + return self->AddBehavior(*type); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, AddBehavior, AddBehavior) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_CLASS_NOT_NULL(type, DObject); + ACTION_RETURN_OBJECT(self->AddBehavior(*type)); +} + +void AActor::TickBehaviors() +{ + TArray toRemove = {}; + + TMap::Iterator it = { Behaviors }; + TMap::Pair* pair = nullptr; + while (it.NextPair(pair)) + { + auto b = pair->Value; + if (b == nullptr || (b->ObjectFlags & OF_EuthanizeMe)) + { + toRemove.Push(pair->Key); + continue; + } + + auto& owner = b->PointerVar(NAME_Owner); + if (owner != this) + { + toRemove.Push(pair->Key); + continue; + } + + IFOVERRIDENVIRTUALPTRNAME(b, NAME_Behavior, Tick) + { + VMValue params[] = { b }; + VMCall(func, params, 1, nullptr, 0); + + if (b->ObjectFlags & OF_EuthanizeMe) + toRemove.Push(pair->Key); + } + } + + for (auto& type : toRemove) + RemoveBehavior(*PClass::FindClass(type)); +} + +static void TickBehaviors(AActor* self) +{ + self->TickBehaviors(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, TickBehaviors, TickBehaviors) +{ + PARAM_SELF_PROLOGUE(AActor); + self->TickBehaviors(); + return 0; +} + +static DObject* FindBehavior(AActor* self, PClass* type) +{ + return self->FindBehavior(*type); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, FindBehavior, FindBehavior) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_CLASS_NOT_NULL(type, DObject); + ACTION_RETURN_OBJECT(self->FindBehavior(*type)); +} + +void AActor::MoveBehaviors(AActor& from) +{ + // Clean these up properly before transferring. + ClearBehaviors(); + + Behaviors.TransferFrom(from.Behaviors); + + TArray toRemove = {}; + + // Clean up any empty behaviors that remained as well while + // changing the owner. + TMap::Iterator it = { Behaviors }; + TMap::Pair* pair = nullptr; + while (it.NextPair(pair)) + { + auto b = pair->Value; + if (b == nullptr || (b->ObjectFlags & OF_EuthanizeMe)) + { + toRemove.Push(pair->Key); + continue; + } + + auto owner = b->PointerVar(NAME_Owner); + owner = this; + } + + for (auto& type : toRemove) + RemoveBehavior(*PClass::FindClass(type)); +} + +static void MoveBehaviors(AActor* self, AActor* from) +{ + self->MoveBehaviors(*from); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, MoveBehaviors, MoveBehaviors) +{ + PARAM_SELF_PROLOGUE(AActor); + PARAM_OBJECT_NOT_NULL(from, AActor); + self->MoveBehaviors(*from); + return 0; +} + +void AActor::ClearBehaviors() +{ + TArray toRemove = {}; + + TMap::Iterator it = { Behaviors }; + TMap::Pair* pair = nullptr; + while (it.NextPair(pair)) + toRemove.Push(pair->Key); + + for (auto& type : toRemove) + RemoveBehavior(*PClass::FindClass(type)); + + Behaviors.Clear(); +} + +static void ClearBehaviors(AActor* self) +{ + self->ClearBehaviors(); +} + +DEFINE_ACTION_FUNCTION_NATIVE(AActor, ClearBehaviors, ClearBehaviors) +{ + PARAM_SELF_PROLOGUE(AActor); + self->ClearBehaviors(); + return 0; +} //========================================================================== // @@ -3931,6 +4171,11 @@ void AActor::Tick () return; } + // These should always tick regardless of prediction or not (let the behavior itself + // handle this). + if (!isFrozen()) + TickBehaviors(); + if (flags5 & MF5_NOINTERACTION) { // only do the minimally necessary things here to save time: @@ -5251,6 +5496,9 @@ void AActor::OnDestroy () Level->localEventManager->WorldThingDestroyed(this); } + + ClearBehaviors(); + DeleteAttachedLights(); ClearRenderSectorList(); ClearRenderLineList(); @@ -5497,6 +5745,8 @@ int MorphPointerSubstitution(AActor* from, AActor* to) VMCall(func, params, 2, nullptr, 0); } + to->MoveBehaviors(*from); + // Go through player infos. for (int i = 0; i < MAXPLAYERS; ++i) { diff --git a/src/serializer_doom.cpp b/src/serializer_doom.cpp index 2ec1c8b861..a46d8c3f3b 100644 --- a/src/serializer_doom.cpp +++ b/src/serializer_doom.cpp @@ -226,6 +226,33 @@ FSerializer& FDoomSerializer::StatePointer(const char* key, void* ptraddr, bool } +FSerializer& Serialize(FSerializer& arc, const char* key, TMap& value, TMap* def) +{ + if (!arc.BeginObject(key)) + return arc; + + if (arc.isWriting()) + { + TMap::Iterator it = { value }; + TMap::Pair* pair = nullptr; + while (it.NextPair(pair)) + arc(pair->Key.GetChars(), pair->Value); + } + else + { + const char* k = nullptr; + while ((k = arc.GetKey()) != nullptr) + { + DObject* obj = nullptr; + arc(k, obj); + value[k] = obj; + } + } + + arc.EndObject(); + return arc; +} + template<> FSerializer &Serialize(FSerializer &ar, const char *key, FPolyObj *&value, FPolyObj **defval) { diff --git a/src/serializer_doom.h b/src/serializer_doom.h index 40b6710a17..ec1a54fb89 100644 --- a/src/serializer_doom.h +++ b/src/serializer_doom.h @@ -43,6 +43,7 @@ FSerializer &Serialize(FSerializer &arc, const char *key, ticcmd_t &sid, ticcmd_ FSerializer &Serialize(FSerializer &arc, const char *key, usercmd_t &cmd, usercmd_t *def); FSerializer &Serialize(FSerializer &arc, const char *key, FInterpolator &rs, FInterpolator *def); FSerializer& Serialize(FSerializer& arc, const char* key, struct FStandaloneAnimation& value, struct FStandaloneAnimation* defval); +FSerializer& Serialize(FSerializer& arc, const char* key, TMap& value, TMap* def); template<> FSerializer &Serialize(FSerializer &arc, const char *key, FPolyObj *&value, FPolyObj **defval); template<> FSerializer &Serialize(FSerializer &arc, const char *key, sector_t *&value, sector_t **defval); diff --git a/wadsrc/static/zscript/actors/actor.zs b/wadsrc/static/zscript/actors/actor.zs index a809e674d9..7861258fe1 100644 --- a/wadsrc/static/zscript/actors/actor.zs +++ b/wadsrc/static/zscript/actors/actor.zs @@ -73,6 +73,14 @@ class ViewPosition native native readonly int Flags; } +class Behavior play abstract +{ + readonly Actor Owner; + + virtual void Initialize() {} + virtual void Readded() {} + virtual void Tick() {} +} class Actor : Thinker native { @@ -500,6 +508,13 @@ class Actor : Thinker native return sin(fb * (180./32)) * 8; } + native clearscope Behavior FindBehavior(class type) const; + native bool RemoveBehavior(class type); + native Behavior AddBehavior(class type); + native void TickBehaviors(); + native void ClearBehaviors(); + native void MoveBehaviors(Actor from); + native clearscope bool isFrozen() const; virtual native void BeginPlay(); virtual native void Activate(Actor activator);