0
0
Fork 0
mirror of https://github.com/ZDoom/gzdoom.git synced 2025-04-11 04:22:40 +00:00

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.
This commit is contained in:
Boondorl 2025-01-23 10:01:11 -05:00 committed by Ricardo Luís Vaz Silva
parent 7d180069e3
commit bd60fd7546
6 changed files with 308 additions and 1 deletions

View file

@ -452,6 +452,7 @@ xx(Readthismenu)
xx(Playermenu)
// more stuff
xx(Behavior)
xx(ColorSet)
xx(NeverSwitchOnPickup)
xx(MoveBob)

View file

@ -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<FName, DObject*> 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

View file

@ -197,6 +197,22 @@ void AActor::EnableNetworking(const bool enable)
Super::EnableNetworking(true);
}
//==========================================================================
//
// AActor :: PropagateMark
//
//==========================================================================
size_t AActor::PropagateMark()
{
TMap<FName, DObject*>::Iterator it = { Behaviors };
TMap<FName, DObject*>::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<DObject>();
if (b == nullptr)
return nullptr;
auto& owner = b->PointerVar<AActor>(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<FName> toRemove = {};
TMap<FName, DObject*>::Iterator it = { Behaviors };
TMap<FName, DObject*>::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<AActor>(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<FName> toRemove = {};
// Clean up any empty behaviors that remained as well while
// changing the owner.
TMap<FName, DObject*>::Iterator it = { Behaviors };
TMap<FName, DObject*>::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<AActor>(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<FName> toRemove = {};
TMap<FName, DObject*>::Iterator it = { Behaviors };
TMap<FName, DObject*>::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)
{

View file

@ -226,6 +226,33 @@ FSerializer& FDoomSerializer::StatePointer(const char* key, void* ptraddr, bool
}
FSerializer& Serialize(FSerializer& arc, const char* key, TMap<FName, DObject*>& value, TMap<FName, DObject*>* def)
{
if (!arc.BeginObject(key))
return arc;
if (arc.isWriting())
{
TMap<FName, DObject*>::Iterator it = { value };
TMap<FName, DObject*>::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)
{

View file

@ -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<FName, DObject*>& value, TMap<FName, DObject*>* 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);

View file

@ -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<Behavior> type) const;
native bool RemoveBehavior(class<Behavior> type);
native Behavior AddBehavior(class<Behavior> 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);