Page 1 of 1

Virtual/Action A_FireProjectile doesn't exist on owner

PostPosted: Sat Jul 31, 2021 4:33 am
by peewee_RotA
I have a weapon base that uses some virtual methods to select what to fire based on some states of the player. This weapon calls an Action from one of its weapon states, which then calls one of a half dozen Virtual methods based on some logic. Those virtual methods have Overrides in the inheriting class. So essentially I have an Action calling Overrides in a child class from weapon states. The problem is that in that virtual method, the owner doesn't have a A_FireProjectile method but it does have SpawnPlayerMissile which can do the same thing.

There are a few known quirks here, so I'll explain them as I understand them:

A) Action methods cannot be Virtual and Virtual methods cannot be Actions. I think this is because of pointer scope as described in...

B) When an action is called from a weapon frame, the self pointer becomes the player, and the methods are technically run from the player.

C) From within an Action, In order to call a method that is NOT an action and not a method on the Player, you can use "invoker.MyMethodName()" to call it from the weapon.
Note: This is the typical solution to calling a virtual method from within an action.

D) When invoker.MyMethodName() is used from an action, "owner" gets you back to the player and is needed to call any player method that you would call normally without using an object pointer.

All that being said, you can call "A_FireProjectile" from an Action method in a weapon, which assumes that the self is the player. However, when getting into a virtual method using "invoker.MyMethodName()" the owner.player does not have a method named "A_FireProjectile". Attempting to call it will cause an error.

Since there are some weird pointer situations I also tried owner.A_FireProjectile and owner.player.A_FireProjectile and even owner.player.mo.A_FireProjectile. Calling it without a pointer doesn't throw an error but also does nothing. Since owner.SpawnPlayerMissile() works.

Expand to see code samples:
Spoiler:


Since this works I'm going to continue the pattern, but I was curious if there was an explanation to why the method is available in Actions but not if I call it from the actual player object.

Thanks in advance!

Re: Virtual/Action A_FireProjectile doesn't exist on owner

PostPosted: Sat Jul 31, 2021 4:40 am
by Graf Zahl
It's because the calling convention for 'action' functions from weapon frames is different. They are not part of Actor, but of Weapon. And in order to call them properly the invoking function needs to be marked 'action' as well so that it can forward the needed state. A virtual cannot do that.

This is all a relic of some very old decision to make actor code pointers work for weapons in Dehacked that was made almost 20 years ago and messed up two calling interfaces that should not have been mixed in the first place.

Re: Virtual/Action A_FireProjectile doesn't exist on owner

PostPosted: Sat Jul 31, 2021 4:47 am
by peewee_RotA
Thanks for the explanation!

So I think what I missed is that A_FireProjectile is an action and SpawnPlayerMissile is just a method on the Player object.

Re: Virtual/Action A_FireProjectile doesn't exist on owner

PostPosted: Sat Jul 31, 2021 5:06 am
by Graf Zahl
Yes. SpawnPlayerMissile is the low level worker that's getting called by A_FireProjectile and lots of other functions. It got less features as a result but since it does not depend on the interface it is easier to call.

Re: Virtual/Action A_FireProjectile doesn't exist on owner

PostPosted: Mon Aug 09, 2021 8:41 am
by peewee_RotA
As a followup, the majority for what I needed to do worked when using invoker to call a virtual method. Once you get to the virtual method, owner is your player and most actions can be taken there.

The problem is that out of about literally 36 methods, I had 1 that needed to set a state that called another action. Once the virtual method is called from the invoker, it looks like the self pointer stack is broken. For example if you then set the state and run another action, player.mo becomes null.

So in order to support new state labels with additional actions on the stack, I've decided to try "overriding" in a round about way.

The base weapon now contains an action that then sets state. Those states then can contain actions. What this means is that the behavior can be overridden on all child class weapons simply by defining that state label.
Code: Select allExpand view
class BaseWeapon : Weapon
{
   action void A_SetWeapState(StateLabel stateName)
   {
      player.SetPsprite(PSP_WEAPON, player.ReadyWeapon.FindState(stateName)); //Creating a simple method for setting state by player sprite method. Setting via "setstatelabel" seems to have issues deep in the action callstack.
   }

   action void A_FireSpell()
   {
      if (player == null)
         return;

           let magePlayer = XRpgMagePlayer(player.mo);
           if (magePlayer && magePlayer.ActiveSpell)
           {
         switch (magePlayer.ActiveSpell.SpellType)
         {
         case SPELLTYPE_FIRE:
            A_SetWeapState("FlameSpell");
            break;
         case SPELLTYPE_ICE:
            A_SetWeapState("IceSpell");
            break;
         case SPELLTYPE_POISON:
            A_SetWeapState("PoisonSpell");
            break;
         }
           }
   }
}


The downside... all child class weapons HAVE to define those states. But the easy solution is to just add all of the states above
Code: Select allExpand view
AltFire:
   CONE B 3;
   CONE C 4;
   CONE D 3;
   CONE E 5;
   CONE F 1 A_FireSpell();
IceSpell:
PoisonSpell: //These have to be defined. Doing this will just fall through to the end of the AltFireFinish frames
AltFireFinish:
   CONE G 3;
   CONE A 9;
   CONE A 10 A_ReFire;
   Goto Ready;
FlameSpell:
   CONE F 2 Bright A_FireFlameSpell; //Call some custom action to shoot a flame missile.
   Goto AltFireFinish;