[ZScript] Problems Extending A_SpawnItemEx

Fri Jan 14, 2022 11:41 am

This is a somewhat difficult problem to explain because I don't know exactly what is wrong so I'll start by explaining what I'm trying to accomplish. I'm trying to extend a A_SpawnItemEx by copying it to a new function and adding the additional bits I want to it. I'm trying to add the ability to set a spawned actor's pitch and roll through parameters rather than being limited to inheriting the calling actor's pitch and roll. I'm also trying to add a flag that will automatically take the spawned actor's pitch into account when setting the actor's velocity, this will be useful projectiles for instance.

I've been working with and learning ZScript for about two or three years now and have built a fairly reasonable foundation with it, so I know how to go about adding these additional features to A_SpawnItemEx in theory. The problem is my math skills. I've managed to add the ability to set the spawned actor's pitch and roll but I lack the necessary math skills to figure out how to set an actor's velocity if pitch should be taken into account for said velocity. I've attempted to figure it out myself and sometimes it seems to work right and other times the spawned actor's velocity is completely out of whack. The last feature I want is for the spawned actor's positional offsets to take the actor's pitch into account if a flag is passed. For example, if the actor is spawned pitching up at 45 degrees, the X offset should raise the actor as well as move it forward by the appropriate amount. I have absolutely no idea how to implement this so I haven't even tried.

Here is what I've got so far, if anyone can help me figure out what I've done wrong with the velocity calculations, I would greatly appreciate it. Extra kudos if someone can show me how to have the spawned offsets take into account the actor's pitch.
Spoiler:

Re: [ZScript] Problems Extending A_SpawnItemEx

Mon Jan 24, 2022 4:03 am

I've been banging my head against this and I think I've mostly figured it out myself. I've had a look at the GZDoom source to see how certain functions are done and have looked at some other smarter people's projects, ZWeapon library was quite useful. After quite a bit of testing, it looks like my function works properly, I can now spawn objects with offsets that are affected by pitch as well as velocity that is affected by pitch. That being said, if someone better at maths would have a look at my function to see if there are any mistakes, I would very much appreciate it. I don't actually understand much of the math in my function, I just frakensteined it together from other functions that did similar things to what I wanted and kept messing with it until it worked so there a bound to be some mistakes or oversights.

Code:
Bool, Actor A_SpawnItemExtended(Class<Actor> Actr, Double OffsetX = 0.0, Double OffsetY = 0.0, Double OffsetZ = 0.0, Double VelX = 0.0, Double VelY = 0.0, Double VelZ = 0.0, Double Angle = 0.0, Double Pitch = 0.0, Double Roll = 0.0, Int Flags = 0, Int Flags2 = 0, Int FailChance = 0, Int TID = 0)
   {
      Bool Result;
      Actor MapObject;
      Double CallerAngle;
      Double CallerPitch;
      Double CallerRoll;
      Double CallerPosZ;
      Double CallerVelZ;
      Vector3 Position;
      Double TSin;
      Double TCos;
      Double NewVelX;

      If (!Actr)
      {
         Return False, Null;
      }

      If (FailChance > 0 && Random[SpawnItemEx]() < FailChance)
      {
         Return True, Null;
      }

      If (DamageType == 'Massacre' && GetDefaultByType(Actr).bISMONSTER)
      {
         Return True, Null;
      }

      CallerAngle = Self.Angle;
      CallerPitch = Self.Pitch;
      CallerRoll = Self.Roll;
      CallerPosZ = Self.Pos.Z;
      CallerVelZ = Self.Vel.Z;

      If (Flags2 & SXF_OFFSETSFROMPITCH)
      {
         Vector3 BaseDirection;
         Double BaseAngle;
         Double BasePitch;
         Vector3 Right;
         Vector3 Up;
         Vector2 OffsetXZ;
         Vector3 Offset;

         BaseDirection = (Cos(CallerPitch) * Cos(CallerAngle),Cos(CallerPitch) * Sin(CallerAngle),-Sin(CallerPitch));
         Right = (Cos(CallerAngle - 90.0),Sin(CallerAngle - 90.0),0.0);
         Up = (Cos(CallerPitch - 90.0) * Cos(CallerAngle),Cos(CallerPitch - 90.0) * Sin(CallerAngle),-Sin(CallerPitch - 90.0));

         BaseDirection += Sin(Angle) * Right;
         BaseDirection += Sin(-Pitch) * Up;

         BaseAngle = VectorAngle(BaseDirection.X,BaseDirection.Y);
         BaseDirection.XY = RotateVector(BaseDirection.XY,-BaseAngle);
         BasePitch = -VectorAngle(BaseDirection.X,BaseDirection.Z);

         OffsetY *= -1;
         OffsetXZ = RotateVector((OffsetX,OffsetZ),-BasePitch);
         OffsetX = OffsetXZ.X;
         OffsetZ = OffsetXZ.Y;
         Offset = (OffsetX,OffsetY,OffsetZ);
         Offset.XY = RotateVector(Offset.XY,BaseAngle);

         If (Flags & SXF_ABSOLUTEPOSITION)
         {
            Position.XY = Vec2Offset(Offset.X,Offset.Y,False);
         }
         Else
         {
            Position.XY = (Self.Pos.X + Offset.X,Self.Pos.Y + Offset.Y);
         }

         Position.Z = CallerPosZ - FloorClip + GetBobOffset(0.0) + Offset.Z;
      }
      Else
      {
         If (!(Flags & SXF_ABSOLUTEANGLE))
         {
            Angle += CallerAngle;
         }

         TSin = Sin(Angle);
         TCos = Cos(Angle);

         If (Flags & SXF_ABSOLUTEPOSITION)
         {
            Position.XY = Vec2Offset(OffsetX,OffsetY,False);
         }
         Else
         {
            Position.XY = Vec2Offset(OffsetX * TCos + OffsetY * TSin,OffsetX * TSin - OffsetY * TCos,False);
         }

         Position.Z = CallerPosZ - FloorClip + GetBobOffset(0.0) + OffsetZ;
      }

      MapObject = Spawn(Actr,Position,ALLOW_REPLACE);
      Result = InitSpawnedItem(MapObject,Flags);

      If (Result)
      {
         If (Flags2 & SXF_OFFSETSFROMPITCH)
         {
            If (!(Flags & SXF_ABSOLUTEANGLE))
            {
               Angle += CallerAngle;
            }

            TSin = Sin(Angle);
            TCos = Cos(Angle);
         }

         If (!(Flags2 & SXF_ABSOLUTEPITCH))
         {
            Pitch = Clamp(Pitch + CallerPitch,-90.0,90.0);
         }

         If (!(Flags2 & SXF_ABSOLUTEROLL))
         {
            Roll += CallerRoll;
         }

         If (!(Flags & SXF_ABSOLUTEVELOCITY))
         {
            NewVelX = VelX * TCos + VelY * TSin;
            VelY = VelX * TSin - VelY * TCos;
            VelX = NewVelX;

            If (Flags2 & SXF_ADDZVELOCITY)
            {
                  VelZ += CallerVelZ;
            }
         }

         MapObject.Angle = Angle;
         MapObject.Pitch = Pitch;
         MapObject.Roll = Roll;
         MapObject.Vel = (VelX,VelY,VelZ);

         If (Flags2 & SXF_VELOCITYFROMPITCH)
         {
            If (Flags2 & SXF_ADDZVELOCITY)
            {
               MapObject.Vel3DFromAngle(MapObject.Vel.XY.Length(),VectorAngle(VelX,VelY),MapObject.Pitch);
               MapObject.Vel.Z += VelZ;
            }
            Else
            {
                  MapObject.Vel3DFromAngle(MapObject.Vel.Length(),VectorAngle(VelX,VelY),VectorAngle(MapObject.Vel.XY.Length(),VelZ) + Pitch);
            }
         }

         If (Flags & SXF_MULTIPLYSPEED)
         {
            MapObject.Vel *= MapObject.Speed;
         }

         If (Flags2 & SXF_PITCHFROMMOMENTUM)
         {
            MapObject.A_FaceMovementDirection(0.0,0.0,270.0,FMDF_NOANGLE,AAPTR_DEFAULT);
         }

         If (TID != 0)
         {
            MapObject.ChangeTID(TID);
         }
      }

      Return Result, MapObject;
   }