Page 1 of 1

Raised Actor's Resurrector/Inflictor pointer?

Posted: Wed May 18, 2022 9:15 am
by Guest
Greetings,

I'm trying to write a WorldThingRevived event handler for a raised monster to copy certain properties from the monster that raised them, but unlike Spawning actors, raised monsters don't have a pointer to a Master actor, and I can't seem to find anything in the documentation that would let a raised monster track their Resurrector, unlike WorldThingDied which has also an Inflictor entity besides the Thing entity.

Is there any way to get a pointer to the monster that has resurrected an actor?

Re: Raised Actor's Resurrector/Inflictor pointer?

Posted: Wed May 18, 2022 9:23 am
by DragonFlayer
Whoops, forgot to login when posting this, replying so I can get notifications

Re: Raised Actor's Resurrector/Inflictor pointer?

Posted: Sun May 22, 2022 8:56 pm
by 22alpha22
From what I can tell, combing the wiki, gzdoom.pk3, and the source, none of the resurrection functions assign a pointer to the resurrectee that would reference the actor performing the resurrection. It looks like if you want a pointer to the monster performing the resurrection, you will have to write your own resurrection function. I would give an example of the Archvile's function to use as a base but it seems that it has not been scriptified and remains a native hardcoded function.

Here is a native version of the resurrection function, perhaps you might be able to zscriptify it and add a pointer to the monster doing the resurrecting.

Code: Select all

bool P_CheckForResurrection(AActor* self, bool usevilestates, FState* state = nullptr, FSoundID sound = 0)
{
	const AActor *info;
	AActor *temp;
		
	if (self->movedir != DI_NODIR)
	{
		const double absSpeed = fabs (self->Speed);
		DVector2 viletry = self->Vec2Offset(
			absSpeed * xspeed[self->movedir],
			absSpeed * yspeed[self->movedir], true);

		FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);

		FMultiBlockThingsIterator it(check, self->Level, viletry.X, viletry.Y, self->Z() - 64, self->Top() + 64, 32., false, NULL);
		FMultiBlockThingsIterator::CheckResult cres;
		while (it.Next(&cres))
		{
			AActor *corpsehit = cres.thing;
			FState *raisestate = corpsehit->GetRaiseState();
			if (raisestate != NULL)
			{
				// use the current actor's radius instead of the Arch Vile's default.
				double maxdist = corpsehit->GetDefault()->radius + self->radius;

				if (fabs(corpsehit->X() - cres.Position.X) > maxdist ||
					fabs(corpsehit->Y() - cres.Position.Y) > maxdist)
					continue;			// not actually touching
				// Let's check if there are floors in between the archvile and its target

				if (corpsehit->Sector->PortalGroup != self->Sector->PortalGroup)
				{
					// if in a different section of the map, only consider possible if a line of sight exists.
					if (!P_CheckSight(self, corpsehit))
						continue;
				}
				else
				{
					sector_t *vilesec = self->Sector;
					sector_t *corpsec = corpsehit->Sector;
					// We only need to test if at least one of the sectors has a 3D floor.
					sector_t *testsec = vilesec->e->XFloor.ffloors.Size() ? vilesec :
						(vilesec != corpsec && corpsec->e->XFloor.ffloors.Size()) ? corpsec : NULL;
					if (testsec)
					{
						double zdist1, zdist2;
						if (P_Find3DFloor(testsec, corpsehit->Pos(), false, true, zdist1)
							!= P_Find3DFloor(testsec, self->Pos(), false, true, zdist2))
						{
							// Not on same floor
							if (vilesec == corpsec || fabs(zdist1 - self->Z()) > self->Height)
								continue;
						}
					}
				}

				corpsehit->Vel.X = corpsehit->Vel.Y = 0;
				// [RH] Check against real height and radius

				double oldheight = corpsehit->Height;
				double oldradius = corpsehit->radius;
				ActorFlags oldflags = corpsehit->flags;

				corpsehit->flags |= MF_SOLID;
				corpsehit->Height = corpsehit->GetDefault()->Height;
				bool check = P_CheckPosition(corpsehit, corpsehit->Pos());
				corpsehit->flags = oldflags;
				corpsehit->radius = oldradius;
				corpsehit->Height = oldheight;
				if (!check || !P_CanResurrect(self, corpsehit)) continue;

				// got one!
				temp = self->target;
				self->target = corpsehit;
				A_FaceTarget(self);
				if (self->flags & MF_FRIENDLY)
				{
					// If this is a friendly Arch-Vile (which is turning the resurrected monster into its friend)
					// and the Arch-Vile is currently targetting the resurrected monster the target must be cleared.
					if (self->lastenemy == temp) self->lastenemy = nullptr;
					if (self->lastenemy == corpsehit) self->lastenemy = nullptr;
					if (temp == self->target) temp = NULL;
				}
				self->target = temp;

				// Make the state the monster enters customizable.
				if (state == nullptr) state = self->FindState(NAME_Heal);
				if (state != nullptr)
				{
					self->SetState(state);
				}
				else if (usevilestates)
				{
					// For Dehacked compatibility this has to use the Arch Vile's
					// heal state as a default if the actor doesn't define one itself.
					PClassActor *archvile = PClass::FindActor(NAME_Archvile);
					if (archvile != NULL)
					{
						self->SetState(archvile->FindState(NAME_Heal));
					}
				}
				if (sound == 0) sound = "vile/raise";
				S_Sound(corpsehit, CHAN_BODY, 0, sound, 1, ATTN_IDLE);
				info = corpsehit->GetDefault();

				if (GetTranslationType(corpsehit->Translation) == TRANSLATION_Blood)
				{
					corpsehit->Translation = info->Translation; // Clean up bloodcolor translation from crushed corpses
				}
				if (self->Level->i_compatflags & COMPATF_VILEGHOSTS)
				{
					corpsehit->Height *= 4;
					// [GZ] This was a commented-out feature, so let's make use of it,
					// but only for ghost monsters so that they are visibly different.
					if (corpsehit->Height == 0)
					{
						// Make raised corpses look ghostly
						if (corpsehit->Alpha > 0.5)
						{
							corpsehit->Alpha /= 2;
						}
						// This will only work if the render style is changed as well.
						if (corpsehit->RenderStyle == LegacyRenderStyles[STYLE_Normal])
						{
							corpsehit->RenderStyle = STYLE_Translucent;
						}
					}
				}
				else
				{
					corpsehit->Height = info->Height;	// [RH] Use real mobj height
					corpsehit->radius = info->radius;	// [RH] Use real radius
				}

				corpsehit->Revive();

				// You are the Archvile's minion now, so hate what it hates
				corpsehit->CopyFriendliness(self, false);
				corpsehit->SetState(raisestate);

				return true;
			}
		}
	}
	return false;
}

Re: Raised Actor's Resurrector/Inflictor pointer?

Posted: Mon May 23, 2022 6:14 am
by DragonFlayer
22alpha22 wrote:From what I can tell, combing the wiki, gzdoom.pk3, and the source, none of the resurrection functions assign a pointer to the resurrectee that would reference the actor performing the resurrection. It looks like if you want a pointer to the monster performing the resurrection, you will have to write your own resurrection function. I would give an example of the Archvile's function to use as a base but it seems that it has not been scriptified and remains a native hardcoded function.

Here is a native version of the resurrection function, perhaps you might be able to zscriptify it and add a pointer to the monster doing the resurrecting.

Code: Select all

bool P_CheckForResurrection(AActor* self, bool usevilestates, FState* state = nullptr, FSoundID sound = 0)
{
	const AActor *info;
	AActor *temp;
		
	if (self->movedir != DI_NODIR)
	{
		const double absSpeed = fabs (self->Speed);
		DVector2 viletry = self->Vec2Offset(
			absSpeed * xspeed[self->movedir],
			absSpeed * yspeed[self->movedir], true);

		FPortalGroupArray check(FPortalGroupArray::PGA_Full3d);

		FMultiBlockThingsIterator it(check, self->Level, viletry.X, viletry.Y, self->Z() - 64, self->Top() + 64, 32., false, NULL);
		FMultiBlockThingsIterator::CheckResult cres;
		while (it.Next(&cres))
		{
			AActor *corpsehit = cres.thing;
			FState *raisestate = corpsehit->GetRaiseState();
			if (raisestate != NULL)
			{
				// use the current actor's radius instead of the Arch Vile's default.
				double maxdist = corpsehit->GetDefault()->radius + self->radius;

				if (fabs(corpsehit->X() - cres.Position.X) > maxdist ||
					fabs(corpsehit->Y() - cres.Position.Y) > maxdist)
					continue;			// not actually touching
				// Let's check if there are floors in between the archvile and its target

				if (corpsehit->Sector->PortalGroup != self->Sector->PortalGroup)
				{
					// if in a different section of the map, only consider possible if a line of sight exists.
					if (!P_CheckSight(self, corpsehit))
						continue;
				}
				else
				{
					sector_t *vilesec = self->Sector;
					sector_t *corpsec = corpsehit->Sector;
					// We only need to test if at least one of the sectors has a 3D floor.
					sector_t *testsec = vilesec->e->XFloor.ffloors.Size() ? vilesec :
						(vilesec != corpsec && corpsec->e->XFloor.ffloors.Size()) ? corpsec : NULL;
					if (testsec)
					{
						double zdist1, zdist2;
						if (P_Find3DFloor(testsec, corpsehit->Pos(), false, true, zdist1)
							!= P_Find3DFloor(testsec, self->Pos(), false, true, zdist2))
						{
							// Not on same floor
							if (vilesec == corpsec || fabs(zdist1 - self->Z()) > self->Height)
								continue;
						}
					}
				}

				corpsehit->Vel.X = corpsehit->Vel.Y = 0;
				// [RH] Check against real height and radius

				double oldheight = corpsehit->Height;
				double oldradius = corpsehit->radius;
				ActorFlags oldflags = corpsehit->flags;

				corpsehit->flags |= MF_SOLID;
				corpsehit->Height = corpsehit->GetDefault()->Height;
				bool check = P_CheckPosition(corpsehit, corpsehit->Pos());
				corpsehit->flags = oldflags;
				corpsehit->radius = oldradius;
				corpsehit->Height = oldheight;
				if (!check || !P_CanResurrect(self, corpsehit)) continue;

				// got one!
				temp = self->target;
				self->target = corpsehit;
				A_FaceTarget(self);
				if (self->flags & MF_FRIENDLY)
				{
					// If this is a friendly Arch-Vile (which is turning the resurrected monster into its friend)
					// and the Arch-Vile is currently targetting the resurrected monster the target must be cleared.
					if (self->lastenemy == temp) self->lastenemy = nullptr;
					if (self->lastenemy == corpsehit) self->lastenemy = nullptr;
					if (temp == self->target) temp = NULL;
				}
				self->target = temp;

				// Make the state the monster enters customizable.
				if (state == nullptr) state = self->FindState(NAME_Heal);
				if (state != nullptr)
				{
					self->SetState(state);
				}
				else if (usevilestates)
				{
					// For Dehacked compatibility this has to use the Arch Vile's
					// heal state as a default if the actor doesn't define one itself.
					PClassActor *archvile = PClass::FindActor(NAME_Archvile);
					if (archvile != NULL)
					{
						self->SetState(archvile->FindState(NAME_Heal));
					}
				}
				if (sound == 0) sound = "vile/raise";
				S_Sound(corpsehit, CHAN_BODY, 0, sound, 1, ATTN_IDLE);
				info = corpsehit->GetDefault();

				if (GetTranslationType(corpsehit->Translation) == TRANSLATION_Blood)
				{
					corpsehit->Translation = info->Translation; // Clean up bloodcolor translation from crushed corpses
				}
				if (self->Level->i_compatflags & COMPATF_VILEGHOSTS)
				{
					corpsehit->Height *= 4;
					// [GZ] This was a commented-out feature, so let's make use of it,
					// but only for ghost monsters so that they are visibly different.
					if (corpsehit->Height == 0)
					{
						// Make raised corpses look ghostly
						if (corpsehit->Alpha > 0.5)
						{
							corpsehit->Alpha /= 2;
						}
						// This will only work if the render style is changed as well.
						if (corpsehit->RenderStyle == LegacyRenderStyles[STYLE_Normal])
						{
							corpsehit->RenderStyle = STYLE_Translucent;
						}
					}
				}
				else
				{
					corpsehit->Height = info->Height;	// [RH] Use real mobj height
					corpsehit->radius = info->radius;	// [RH] Use real radius
				}

				corpsehit->Revive();

				// You are the Archvile's minion now, so hate what it hates
				corpsehit->CopyFriendliness(self, false);
				corpsehit->SetState(raisestate);

				return true;
			}
		}
	}
	return false;
}

Yeah, this is what I was worried of, I guess I'll have to figure something out from it, thank you for pointing me to that function!

I was trying to modify CutmanMike's TwitchyDoom to make both spawned and resurrected monsters inherit the 'friendly' properties of their friendly monster master/resurrector, I was trying to do with WorldThingRevived the same thing I did with WorldThingSpawned, then realized raised enemies aren't even flagged as friendly? somehow they don't attack the player if the Archvile is friendly, while not having the friendly flag themselves.

This is what that looks like and why I'm trying to make raised monsters be aware of their resurrector:

Code: Select all

Class Friendliness_Handler : EventHandler
{
	override void WorldThingSpawned(WorldEvent e)
	{
		if(!e.Thing.bISMONSTER)
		{
			//Console.Printf("Spawned Actor (" .. e.Thing.GetClassName() ..") is not a Monster");
			//if(e.Thing.Master)
			//{
			//	Console.Printf("Spawned non-Monster Actor (" .. e.Thing.GetClassName() ..") has a Master (" .. e.Thing.Master.GetClassName() ..")");
			//}
			return;
		}
		//Console.Printf("Spawned Actor (" .. e.Thing.GetClassName() .. ") is a Monster");
		Actor mst = e.Thing.Master;
		if(mst)
		{
			//Console.Printf("Spawned Actor (" .. e.Thing.GetClassName() ..") has a Master (" .. mst.GetClassName() ..")");
			if(mst.bFRIENDLY)
			{
				//Console.Printf("Spawned Actor's (" .. e.Thing.GetClassName() ..") Master (" .. mst.GetClassName() ..") is Friendly");
				e.Thing.bFRIENDLY = mst.bFRIENDLY;
				e.Thing.bSHOOTABLE = mst.bSHOOTABLE;
				e.Thing.bNOBLOCKMONST = mst.bNOBLOCKMONST;
				e.Thing.bSOLID = mst.bSOLID;
				e.Thing.target = mst.target;
				e.Thing.lastheard = mst.lastheard;
				if(e.Thing.bCOUNTKILL)
				{
					e.Thing.A_ChangeCountFlags(0);
					e.Thing.bCOUNTKILL = false;
				}
				if(mst.FindInventory("Twitchy_FriendlySpawnProtection"))
				{
					e.Thing.GiveInventory("Twitchy_FriendlySpawnProtection", 1);
				}
				if(mst.FindInventory("Twitchy_FriendlyFireProtection"))
				{
					e.Thing.GiveInventory("Twitchy_FriendlyFireProtection", 1);
				}
				if(mst.FindInventory("Twitchy_FriendlyFollower"))
				{
					e.Thing.GiveInventory("Twitchy_FriendlyFollower", 1);
				}
				if(mst.FindInventory("Twitchy_Username"))
				{
					String username = mst.FindInventory("Twitchy_Username").GetTag();
					Twitchy_Username.Show(e.Thing, username);
				}
				Twitchy_Actor_MapMarker.Show(e.Thing);
			}
		}
	}