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);