A touch trigger: issue with Call Special and teleport

Ask about ACS, DECORATE, ZScript, or any other scripting questions here!

Moderator: GZDoom Developers

Forum rules
Before asking on how to use a ZDoom feature, read the ZDoom wiki first. If you still don't understand how to use a feature, then ask here.

Please bear in mind that the people helping you do not automatically know how much you know. You may be asked to upload your project file to look at. Don't be afraid to ask questions about what things mean, but also please be patient with the people trying to help you. (And helpers, please be patient with the person you're trying to help!)
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

A touch trigger: issue with Call Special and teleport

Post by Sir Robin »

I'm creating a touch trigger actor. That is, an actor that will execute it's special when it is touched. I thought I could do this easily with +BumpSpecial but that only seems to fire if the actor is solid. I want an actor that can be move through and trigger a special, like a trip wire or motion sensor. Why not just use a linedef trigger? Well we all know that line will fire no matter what Z, I wanted a trip that only occurs at a set Z, and could be jumped over or crouched under or whatever.

Here's the code:

Code: Select all

class MWR_TouchTrigger : actor
{
	Default
	{
		height 64;
		radius 16;
		+NOGRAVITY;
		+Special;
		MWR_TouchTrigger.TouchTimeout 35;
	}
	States
	{
		Spawn:
			UNKN A -1;//DEBUG
			Stop;
	}
	
	int TouchTimeout;
	int TouchCountdown;
	
	property TouchTimeout:TouchTimeout;
	
	override void Touch(Actor toucher)
	{
		if (TouchCountdown>0) {return;}
		console.printf(GetCharacterName().."@"..level.time..": Touched by "..toucher.GetCharacterName());//DEBUG
		toucher.A_CallSpecial(special, args[0], args[1], args[2], args[3], args[4]);
		TouchCountdown=TouchTimeout;
	}
	
	override void tick()
	{
		if (TouchCountdown>0){TouchCountdown--;}
		super.tick();
	}
}
If I set the special to open a door, it works.
If I set the special to teleport to a landing, it makes the source and destination fog but teleports the player to the trigger, not the teleport landing.

What am I doing wrong?

download
Last edited by Sir Robin on Fri May 06, 2022 3:29 pm, edited 1 time in total.
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: A touch trigger: issue with Call Special

Post by Sir Robin »

I try using Level.ExecuteSpecial instead of A_CallSpecial, but get the same result. I looked in the actor code and A_SetSpecial calls Level.ExecuteSpecial, so no functional difference.
User avatar
Player701
 
 
Posts: 1649
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: A touch trigger: issue with Call Special and teleport

Post by Player701 »

This has something to do with the way the movement code is written. I've tried running it in a debugger, and it seems that the player's movement for the current tick is processed after the teleport special is executed, overriding the player's coordinates as a result. I'm not yet sure if these is a way to work around this. See further below for a workaround solution.
Sir Robin wrote:Well we all know that line will fire no matter what Z, I wanted a trip that only occurs at a set Z, and could be jumped over or crouched under or whatever.
Consider using an ACS script with [wiki]GetActorZ[/wiki] and/or [wiki]GetActorFloorZ[/wiki] for line triggers to achieve the desired effect.
Last edited by Player701 on Fri May 13, 2022 9:25 am, edited 1 time in total.
User avatar
Player701
 
 
Posts: 1649
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: A touch trigger: issue with Call Special and teleport

Post by Player701 »

Workaround for the original setup: use an inventory item that will call the special as soon as it appears in the owner's inventory, and then destroy itself. This delays the call until the player's movement for the current tick has been fully processed.

Code: Select all

class SpecialInv : Inventory
{
    override void DoEffect()
    {
        Owner.A_CallSpecial(special, args[0], args[1], args[2], args[3], args[4]);
        Destroy();
    }
}
Add the following code to MWR_TouchTrigger:

Code: Select all

private void GiveSpecialInv(Actor who)
{
    let si = Inventory(Spawn('SpecialInv'));

    if (si != null)
    {
        si.special = special;
        
        for (int i = 0; i < 5; i++)
        {
            si.args[i] = args[i];
        }

        if (!si.CallTryPickup(who))
        {
            si.Destroy();
        }
    }
}
and replace toucher.A_CallSpecial(...) with

Code: Select all

GiveSpecialInv(toucher);
User avatar
Sir Robin
Posts: 537
Joined: Wed Dec 22, 2021 7:02 pm
Graphics Processor: Intel (Modern GZDoom)
Location: Medellin, Colombia

Re: A touch trigger: issue with Call Special and teleport

Post by Sir Robin »

Player701 wrote:This has something to do with the way the movement code is written. I've tried running it in a debugger, and it seems that the player's movement for the current tick is processed after the teleport special is executed, overriding the player's coordinates as a result. I'm not yet sure if these is a way to work around this. See further below for a workaround solution.
Thanks for looking into this. Is it worth reporting as a bug / feature request? I would think that a teleport function call should reset any movement logic in the actor(s) it is affecting.
Player701 wrote:Consider using an ACS script with [wiki]GetActorZ[/wiki] and/or [wiki]GetActorFloorZ[/wiki] for line triggers to achieve the desired effect.
I understand there are different ways to do this, I was experimenting to see pros/cons of different approaches. What I like about using an actor is the simplicity. An editor like UDB will see the teleport special, find it's target, and indicate the relationship with a line or whatever is configured to do, so it is easy to graphically understand the relationship. I could have a line special call a script and that script call a teleport and that teleport point to a destination, but now that's 3 things I have to look at manually to understand what is happening.
Player701 wrote:Workaround for the original setup: use an inventory item that will call the special as soon as it appears in the owner's inventory, and then destroy itself. This delays the call until the player's movement for the current tick has been fully processed.
Excellent! Thanks for taking the time to work this out!

I saw your first post about the timing and tried moving that special call. I didn't think to move it to an inventory item, but I did try moving it from the touch to the tick function of the trigger object and that works for the teleport issue:

Code: Select all

	bool DelayedSpecial;
	actor SpecialToucher;
	
	override void Touch(Actor toucher)
	{
		if (TouchCountdown>0) {return;}
		console.printf(GetCharacterName().."@"..level.time..": Touched by "..toucher.GetCharacterName());//DEBUG
		DelayedSpecial=true;
		SpecialToucher=toucher;
		TouchCountdown=TouchTimeout;
	}
	
	override void tick()
	{
		if (TouchCountdown>0){TouchCountdown--;}
		super.tick();
		
		if (DelayedSpecial && SpecialToucher)
		{
			SpecialToucher.A_CallSpecial(special, args[0], args[1], args[2], args[3], args[4]);
		}
		DelayedSpecial=false;
		SpecialToucher=null;
	}
User avatar
Player701
 
 
Posts: 1649
Joined: Wed May 13, 2009 3:15 am
Graphics Processor: nVidia with Vulkan support

Re: A touch trigger: issue with Call Special and teleport

Post by Player701 »

Sir Robin wrote:Thanks for looking into this. Is it worth reporting as a bug / feature request? I would think that a teleport function call should reset any movement logic in the actor(s) it is affecting.
You could ask, but I think Graf has said multiple times that he won't touch the movement code again...
Sir Robin wrote:I saw your first post about the timing and tried moving that special call. I didn't think to move it to an inventory item, but I did try moving it from the touch to the tick function of the trigger object and that works for the teleport issue:

Code: Select all

	bool DelayedSpecial;
	actor SpecialToucher;
	
	override void Touch(Actor toucher)
	{
		if (TouchCountdown>0) {return;}
		console.printf(GetCharacterName().."@"..level.time..": Touched by "..toucher.GetCharacterName());//DEBUG
		DelayedSpecial=true;
		SpecialToucher=toucher;
		TouchCountdown=TouchTimeout;
	}
	
	override void tick()
	{
		if (TouchCountdown>0){TouchCountdown--;}
		super.tick();
		
		if (DelayedSpecial && SpecialToucher)
		{
			SpecialToucher.A_CallSpecial(special, args[0], args[1], args[2], args[3], args[4]);
		}
		DelayedSpecial=false;
		SpecialToucher=null;
	}
Probably also an option, the difference is that DoEffect runs after the owner's tick code has been processed, making it more deterministic in regard to the order of execution. But I'm not sure if there is any real difference.

Return to “Scripting”