Page 1 of 1

A touch trigger: issue with Call Special and teleport

Posted: Fri May 06, 2022 10:03 am
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

Re: A touch trigger: issue with Call Special

Posted: Fri May 06, 2022 3:28 pm
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.

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

Posted: Wed May 11, 2022 12:45 pm
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 GetActorZ and/or GetActorFloorZ for line triggers to achieve the desired effect.

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

Posted: Fri May 13, 2022 9:24 am
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);

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

Posted: Fri May 13, 2022 12:19 pm
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 GetActorZ and/or GetActorFloorZ 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;
   }

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

Posted: Fri May 13, 2022 5:58 pm
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.