diff --git a/src/actor.h b/src/actor.h index 83a264f00..18d96a0dd 100644 --- a/src/actor.h +++ b/src/actor.h @@ -683,7 +683,7 @@ public: void ConversationAnimation (int animnum); // Make this actor hate the same things as another actor - void CopyFriendliness (AActor *other, bool changeTarget); + void CopyFriendliness (AActor *other, bool changeTarget, bool resetHealth=true); // Moves the other actor's inventory to this one void ObtainInventory (AActor *other); diff --git a/src/p_mobj.cpp b/src/p_mobj.cpp index a034e9d47..1cb5c9571 100644 --- a/src/p_mobj.cpp +++ b/src/p_mobj.cpp @@ -770,7 +770,7 @@ bool AActor::GiveAmmo (const PClass *type, int amount) // //============================================================================ -void AActor::CopyFriendliness (AActor *other, bool changeTarget) +void AActor::CopyFriendliness (AActor *other, bool changeTarget, bool resetHealth) { level.total_monsters -= CountsAsKill(); TIDtoHate = other->TIDtoHate; @@ -786,7 +786,7 @@ void AActor::CopyFriendliness (AActor *other, bool changeTarget) // LastHeard must be set as well so that A_Look can react to the new target if called LastHeard = target = other->target; } - health = SpawnHealth(); + if (resetHealth) health = SpawnHealth(); level.total_monsters += CountsAsKill(); } diff --git a/src/thingdef/thingdef_codeptr.cpp b/src/thingdef/thingdef_codeptr.cpp index 2c6fddfc8..a29d56723 100644 --- a/src/thingdef/thingdef_codeptr.cpp +++ b/src/thingdef/thingdef_codeptr.cpp @@ -84,7 +84,6 @@ static FRandom pr_burst ("Burst"); static FRandom pr_monsterrefire ("MonsterRefire"); static FRandom pr_teleport("A_Teleport"); - //========================================================================== // // ACustomInventory :: CallStateChain @@ -137,6 +136,291 @@ bool ACustomInventory::CallStateChain (AActor *actor, FState * State) return result; } + + +//========================================================================== +// +// Checks whether this actor is a missile +// Unfortunately this was buggy in older versions of the code and many +// released DECORATE monsters rely on this bug so it can only be fixed +// with an optional flag +// +//========================================================================== +inline static bool isMissile(AActor * self, bool precise=true) +{ + return self->flags&MF_MISSILE || (precise && self->GetDefault()->flags&MF_MISSILE); +} + + +//========================================================================== +// +// Pointer-based operations +// +//========================================================================== + + +enum AAPTR +{ + AAPTR_DEFAULT = 0, + AAPTR_NULL = 0x1, + AAPTR_TARGET = 0x2, + AAPTR_MASTER = 0x4, + AAPTR_TRACER = 0x8 +}; + +// [FDARI] Exported logic for guarding against loops in Target (for missiles) and Master (for all) chains. +// It is called from multiple locations. +// The code may be in need of optimisation. + +void VerifyTargetChain(AActor *self, bool preciseMissileCheck=true) +{ + if (!(self && isMissile(self, preciseMissileCheck))) return; + AActor *origin = self; + AActor *next = origin->target; + + // origin: the most recent actor that has been verified as appearing only once + // next: the next actor to be verified; will be "origin" in the next iteration + + while (next && isMissile(next, preciseMissileCheck)) // we only care when there are missiles involved + { + AActor *compare = self; + // every new actor must prove not to be the first actor in the chain, or any subsequent actor + // any actor up to and including "origin" has only appeared once + do + { + if (compare == next) + { + // if any of the actors from self to (inclusive) origin match the next actor, + // self has reached/created a loop + self->target = NULL; + return; + } + if (compare == origin) break; // when "compare" = origin, we know that the next actor is, and should be "next" + compare = compare->target; + } while (true); // we're never leaving the loop here + + origin = next; + next = next->target; + } +} + +void VerifyMasterChain(AActor *self) +{ + // See VerifyTargetChain for detailed comments. + + if (!self) return; + AActor *origin = self; + AActor *next = origin->master; + while (next) // We always care (See "VerifyTargetChain") + { + AActor *compare = self; + do + { + if (compare == next) + { + self->master = NULL; + return; + } + if (compare == origin) break; + compare = compare->master; + } while (true); // we're never leaving the loop here + + origin = next; + next = next->master; + } +} + +enum PTROP +{ + PTROP_UNSAFETARGET = 1, + PTROP_UNSAFEMASTER = 2, + PTROP_NOSAFEGUARDS = PTROP_UNSAFETARGET|PTROP_UNSAFEMASTER +}; + + +//========================================================================== +// +// A_RearrangePointers +// +// Allow an actor to change its relationship to other actors by +// copying pointers freely between TARGET MASTER and TRACER. +// Can also assign null value, but does not duplicate A_ClearTarget. +// +//========================================================================== + + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_RearrangePointers) +{ + ACTION_PARAM_START(4); + ACTION_PARAM_INT(ptr_target, 0); + ACTION_PARAM_INT(ptr_master, 1); + ACTION_PARAM_INT(ptr_tracer, 2); + ACTION_PARAM_INT(flags, 3); + + // Rearrange pointers internally + + // Fetch all values before modification, so that all fields can get original values + AActor + *gettarget = self->target, + *getmaster = self->master, + *gettracer = self->tracer; + + switch (ptr_target) // pick the new target + { + case AAPTR_MASTER: + self->target = getmaster; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_TRACER: + self->target = gettracer; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(self); + break; + case AAPTR_NULL: + self->target = NULL; + // THIS IS NOT "A_ClearTarget", so no other targeting info is removed + break; + } + + // presently permitting non-monsters to set master + switch (ptr_master) // pick the new master + { + case AAPTR_TARGET: + self->master = gettarget; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_TRACER: + self->master = gettracer; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(self); + break; + case AAPTR_NULL: + self->master = NULL; + break; + } + + switch (ptr_tracer) // pick the new tracer + { + case AAPTR_TARGET: + self->tracer = gettarget; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_MASTER: + self->tracer = getmaster; + break; // no verification deemed necessary; the engine never follows a tracer chain(?) + case AAPTR_NULL: + self->tracer = NULL; + break; + } +} + +//========================================================================== +// +// A_TransferPointer +// +// Copy one pointer (MASTER, TARGET or TRACER) from this actor (SELF), +// or from an this actor's MASTER, TARGET or TRACER. +// +// You can copy any one of that actor's pointers +// +// Assign the copied pointer to any one pointer in SELF, +// MASTER, TARGET or TRACER. +// +// Any attempt to make an actor point to itself will replace the pointer +// with a null value. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TransferPointer) +{ + ACTION_PARAM_START(5); + ACTION_PARAM_INT(ptr_source, 0); + ACTION_PARAM_INT(ptr_recepient, 1); + ACTION_PARAM_INT(ptr_sourcefield, 2); + ACTION_PARAM_INT(ptr_recepientfield, 3); + ACTION_PARAM_INT(flags, 4); + + AActor *source, *recepient; + + // Exchange pointers with actors to whom you have pointers (or with yourself, if you must) + + switch (ptr_source) // pick an actor to provide a pointer + { + case AAPTR_DEFAULT: source = self; break; + case AAPTR_TARGET: source = self->target; break; + case AAPTR_MASTER: source = self->master; break; + case AAPTR_TRACER: source = self->tracer; break; + default: return; + } + + if (!source) return; // you must pick somebody. MAYBE we should make a null assignment instead of just returning. + + switch (ptr_recepient) // pick an actor to store the provided pointer value + { + case AAPTR_DEFAULT: recepient = self; break; + case AAPTR_TARGET: recepient = self->target; break; + case AAPTR_MASTER: recepient = self->master; break; + case AAPTR_TRACER: recepient = self->tracer; break; + default: return; + } + + if (!recepient) return; // you must pick somebody. No way we can even make a null assignment here. + + switch (ptr_sourcefield) // convert source from dataprovider to data + { + case AAPTR_TARGET: source = source->target; break; // now we don't care where the data comes from anymore + case AAPTR_MASTER: source = source->master; break; // so we reassign source; it now holds the data itself + case AAPTR_TRACER: source = source->tracer; break; + default: source = NULL; + } + + if (source == recepient) source = NULL; // The recepient should not acquire a pointer to itself; will write NULL + + if (ptr_recepientfield == AAPTR_DEFAULT) ptr_recepientfield = ptr_sourcefield; // If default: Write to same field as data was read from + + switch (ptr_recepientfield) // assignment and safeguards (optional) + { + case AAPTR_TARGET: + recepient->target = source; + if (!(PTROP_UNSAFETARGET & flags)) VerifyTargetChain(recepient); + break; + case AAPTR_MASTER: + recepient->master = source; + if (!(PTROP_UNSAFEMASTER & flags)) VerifyMasterChain(recepient); + break; + case AAPTR_TRACER: + recepient->tracer = source; + break; + } +} + +//========================================================================== +// +// A_CopyFriendliness +// +// Join forces with one of the actors you are pointing to (MASTER by default) +// +// Normal CopyFriendliness reassigns health. This function will not. +// +//========================================================================== + +DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CopyFriendliness) +{ + ACTION_PARAM_START(1); + ACTION_PARAM_INT(ptr_source, 0); + + if (self->player) return; + + AActor *source; + + switch (ptr_source) + { + case AAPTR_TARGET: source = self->target; break; + case AAPTR_MASTER: source = self->master; break; + case AAPTR_TRACER: source = self->tracer; break; + default: return; + } + + if (source) self->CopyFriendliness(source, false, false); // Overriding default behaviour: No modification of health +} + //========================================================================== // // Simple flag changers @@ -676,19 +960,6 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CallSpecial) ACTION_SET_RESULT(res); } -//========================================================================== -// -// Checks whether this actor is a missile -// Unfortunately this was buggy in older versions of the code and many -// released DECORATE monsters rely on this bug so it can only be fixed -// with an optional flag -// -//========================================================================== -inline static bool isMissile(AActor * self, bool precise=true) -{ - return self->flags&MF_MISSILE || (precise && self->GetDefault()->flags&MF_MISSILE); -} - //========================================================================== // // The ultimate code pointer: Fully customizable missiles! @@ -1382,10 +1653,28 @@ DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_CustomRailgun) static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) { - ACTION_PARAM_START(2); + ACTION_PARAM_START(3); // param count up ACTION_PARAM_CLASS(mi, 0); ACTION_PARAM_INT(amount, 1); + // [FDARI] Modified code: Allow any pointer to be used for receiver + ACTION_PARAM_INT(setreceiver, 2); + + switch (setreceiver) + { + case AAPTR_TARGET: + if (receiver->target) { receiver = receiver->target; break; } + return; + case AAPTR_MASTER: + if (receiver->master) { receiver = receiver->master; break; } + return; + case AAPTR_TRACER: + if (receiver->tracer) { receiver = receiver->tracer; break; } + return; + } + + // FDARI: End of modified code + bool res=true; if (receiver == NULL) return; @@ -1413,7 +1702,7 @@ static void DoGiveInventory(AActor * receiver, DECLARE_PARAMINFO) else res = false; ACTION_SET_RESULT(res); -} +} DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_GiveInventory) { @@ -1438,10 +1727,28 @@ enum void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) { - ACTION_PARAM_START(3); + ACTION_PARAM_START(4); // param count up ACTION_PARAM_CLASS(item, 0); ACTION_PARAM_INT(amount, 1); ACTION_PARAM_INT(flags, 2); + + // [FDARI] Modified code: Allow any pointer to be used for receiver + ACTION_PARAM_INT(setreceiver, 3); + + switch (setreceiver) + { + case AAPTR_TARGET: + if (receiver->target) { receiver = receiver->target; break; } + return; + case AAPTR_MASTER: + if (receiver->master) { receiver = receiver->master; break; } + return; + case AAPTR_TRACER: + if (receiver->tracer) { receiver = receiver->tracer; break; } + return; + } + + // FDARI: End of modified code if (item == NULL || receiver == NULL) return; @@ -1471,7 +1778,7 @@ void DoTakeInventory(AActor * receiver, DECLARE_PARAMINFO) else inv->Amount-=amount; } ACTION_SET_RESULT(res); -} +} DEFINE_ACTION_FUNCTION_PARAMS(AActor, A_TakeInventory) { diff --git a/wadsrc/static/actors/actor.txt b/wadsrc/static/actors/actor.txt index 065ee6281..4d5fb4ac7 100644 --- a/wadsrc/static/actors/actor.txt +++ b/wadsrc/static/actors/actor.txt @@ -198,8 +198,8 @@ ACTOR Actor native //: Thinker action native A_JumpIfMasterCloser(float distance, state label); action native A_JumpIfInventory(class itemtype, int itemamount, state label); action native A_JumpIfArmorType(string Type, state label, int amount = 1); - action native A_GiveInventory(class itemtype, int amount = 0); - action native A_TakeInventory(class itemtype, int amount = 0, int flags = 0); + action native A_GiveInventory(class itemtype, int amount = 0, int giveto = AAPTR_DEFAULT); + action native A_TakeInventory(class itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT); action native A_SpawnItem(class itemtype = "Unknown", float distance = 0, float zheight = 0, bool useammo = true, bool transfer_translation = false); action native A_SpawnItemEx(class itemtype, float xofs = 0, float yofs = 0, float zofs = 0, float xvel = 0, float yvel = 0, float zvel = 0, float angle = 0, int flags = 0, int failchance = 0); action native A_Print(string whattoprint, float time = 0, string fontname = ""); @@ -284,6 +284,10 @@ ACTOR Actor native //: Thinker action native A_CheckSightOrRange(float distance, state label); + action native A_RearrangePointers(int newtarget, int newmaster = AAPTR_DEFAULT, int newtracer = AAPTR_DEFAULT, int flags=0); + action native A_TransferPointer(int ptr_source, int ptr_recepient, int sourcefield, int recepientfield=AAPTR_DEFAULT, int flags=0); + action native A_CopyFriendliness(int ptr_source = AAPTR_MASTER); + States { Spawn: diff --git a/wadsrc/static/actors/constants.txt b/wadsrc/static/actors/constants.txt index 0b432db91..b55737405 100644 --- a/wadsrc/static/actors/constants.txt +++ b/wadsrc/static/actors/constants.txt @@ -197,6 +197,20 @@ Const Int BLOCKF_EVERYTHING = 32; Const Int BLOCKF_RAILING = 64; Const Int BLOCKF_USE = 128; +// Pointer constants, bitfield-enabled + +Const Int AAPTR_DEFAULT = 0; +Const Int AAPTR_NULL = 1; +Const Int AAPTR_TARGET = 2; +Const Int AAPTR_MASTER = 4; +Const Int AAPTR_TRACER = 8; + +// Pointer operation flags + +Const Int PTROP_UNSAFETARGET = 1; +Const Int PTROP_UNSAFEMASTER = 2; +Const Int PTROP_NOSAFEGUARDS = PTROP_UNSAFETARGET|PTROP_UNSAFEMASTER; + // This is only here to provide one global variable for testing.