diff --git a/src/g_inventory/a_weapons.cpp b/src/g_inventory/a_weapons.cpp index 50e77f44b..37611c8b0 100644 --- a/src/g_inventory/a_weapons.cpp +++ b/src/g_inventory/a_weapons.cpp @@ -227,30 +227,6 @@ bool AWeapon::CheckAmmo(int fireMode, bool autoSwitch, bool requireAmmo, int amm return false; } -//=========================================================================== -// -// AWeapon :: DepleteAmmo -// -// Use up some of the weapon's ammo. Returns true if the ammo was successfully -// depleted. If checkEnough is false, then the ammo will always be depleted, -// even if it drops below zero. -// -//=========================================================================== - -bool AWeapon::DepleteAmmo(bool altFire, bool checkEnough, int ammouse) -{ - IFVIRTUAL(AWeapon, DepleteAmmo) - { - VMValue params[] = { (DObject*)this, altFire, checkEnough, ammouse }; - VMReturn ret; - int retval; - ret.IntAt(&retval); - VMCall(func, params, 4, &ret, 1); - return !!retval; - } - return false; -} - //=========================================================================== // // AWeapon :: GetUpState diff --git a/src/g_inventory/a_weapons.h b/src/g_inventory/a_weapons.h index 73a9d15ec..f1ab58fd4 100644 --- a/src/g_inventory/a_weapons.h +++ b/src/g_inventory/a_weapons.h @@ -140,7 +140,6 @@ public: EitherFire }; bool CheckAmmo (int fireMode, bool autoSwitch, bool requireAmmo=false, int ammocount = -1); - bool DepleteAmmo (bool altFire, bool checkEnough=true, int ammouse = -1); enum { diff --git a/src/p_actionfunctions.cpp b/src/p_actionfunctions.cpp index db72ad812..50ff36e23 100644 --- a/src/p_actionfunctions.cpp +++ b/src/p_actionfunctions.cpp @@ -1960,452 +1960,6 @@ DEFINE_ACTION_FUNCTION(AActor, A_TakeFromSiblings) ACTION_RETURN_INT(count); } -//=========================================================================== -// -// Common code for A_SpawnItem and A_SpawnItemEx -// -//=========================================================================== - -enum SIX_Flags -{ - SIXF_TRANSFERTRANSLATION = 0x00000001, - SIXF_ABSOLUTEPOSITION = 0x00000002, - SIXF_ABSOLUTEANGLE = 0x00000004, - SIXF_ABSOLUTEVELOCITY = 0x00000008, - SIXF_SETMASTER = 0x00000010, - SIXF_NOCHECKPOSITION = 0x00000020, - SIXF_TELEFRAG = 0x00000040, - SIXF_CLIENTSIDE = 0x00000080, // only used by Skulldronum - SIXF_TRANSFERAMBUSHFLAG = 0x00000100, - SIXF_TRANSFERPITCH = 0x00000200, - SIXF_TRANSFERPOINTERS = 0x00000400, - SIXF_USEBLOODCOLOR = 0x00000800, - SIXF_CLEARCALLERTID = 0x00001000, - SIXF_MULTIPLYSPEED = 0x00002000, - SIXF_TRANSFERSCALE = 0x00004000, - SIXF_TRANSFERSPECIAL = 0x00008000, - SIXF_CLEARCALLERSPECIAL = 0x00010000, - SIXF_TRANSFERSTENCILCOL = 0x00020000, - SIXF_TRANSFERALPHA = 0x00040000, - SIXF_TRANSFERRENDERSTYLE = 0x00080000, - SIXF_SETTARGET = 0x00100000, - SIXF_SETTRACER = 0x00200000, - SIXF_NOPOINTERS = 0x00400000, - SIXF_ORIGINATOR = 0x00800000, - SIXF_TRANSFERSPRITEFRAME = 0x01000000, - SIXF_TRANSFERROLL = 0x02000000, - SIXF_ISTARGET = 0x04000000, - SIXF_ISMASTER = 0x08000000, - SIXF_ISTRACER = 0x10000000, -}; - -static bool InitSpawnedItem(AActor *self, AActor *mo, int flags) -{ - if (mo == NULL) - { - return false; - } - AActor *originator = self; - - if (!(mo->flags2 & MF2_DONTTRANSLATE)) - { - if (flags & SIXF_TRANSFERTRANSLATION) - { - mo->Translation = self->Translation; - } - else if (flags & SIXF_USEBLOODCOLOR) - { - // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. - mo->Translation = self->BloodTranslation; - } - } - if (flags & SIXF_TRANSFERPOINTERS) - { - mo->target = self->target; - mo->master = self->master; // This will be overridden later if SIXF_SETMASTER is set - mo->tracer = self->tracer; - } - - mo->Angles.Yaw = self->Angles.Yaw; - if (flags & SIXF_TRANSFERPITCH) - { - mo->Angles.Pitch = self->Angles.Pitch; - } - if (!(flags & SIXF_ORIGINATOR)) - { - while (originator && originator->isMissile()) - { - originator = originator->target; - } - } - if (flags & SIXF_TELEFRAG) - { - P_TeleportMove(mo, mo->Pos(), true); - // This is needed to ensure consistent behavior. - // Otherwise it will only spawn if nothing gets telefragged - flags |= SIXF_NOCHECKPOSITION; - } - if (mo->flags3 & MF3_ISMONSTER) - { - if (!(flags & SIXF_NOCHECKPOSITION) && !P_TestMobjLocation(mo)) - { - // The monster is blocked so don't spawn it at all! - mo->ClearCounters(); - mo->Destroy(); - return false; - } - else if (originator && !(flags & SIXF_NOPOINTERS)) - { - if (originator->flags3 & MF3_ISMONSTER) - { - // If this is a monster transfer all friendliness information - mo->CopyFriendliness(originator, true); - } - else if (originator->player) - { - // A player always spawns a monster friendly to him - mo->flags |= MF_FRIENDLY; - mo->SetFriendPlayer(originator->player); - - AActor * attacker=originator->player->attacker; - if (attacker) - { - if (!(attacker->flags&MF_FRIENDLY) || - (deathmatch && attacker->FriendPlayer!=0 && attacker->FriendPlayer!=mo->FriendPlayer)) - { - // Target the monster which last attacked the player - mo->LastHeard = mo->target = attacker; - } - } - } - } - } - else if (!(flags & SIXF_TRANSFERPOINTERS)) - { - // If this is a missile or something else set the target to the originator - mo->target = originator ? originator : self; - } - if (flags & SIXF_NOPOINTERS) - { - //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. - mo->LastHeard = NULL; //Sanity check. - mo->target = NULL; - mo->master = NULL; - mo->tracer = NULL; - } - if (flags & SIXF_SETMASTER) - { // don't let it attack you (optional)! - mo->master = originator; - } - if (flags & SIXF_SETTARGET) - { - mo->target = originator; - } - if (flags & SIXF_SETTRACER) - { - mo->tracer = originator; - } - if (flags & SIXF_TRANSFERSCALE) - { - mo->Scale = self->Scale; - } - if (flags & SIXF_TRANSFERAMBUSHFLAG) - { - mo->flags = (mo->flags & ~MF_AMBUSH) | (self->flags & MF_AMBUSH); - } - if (flags & SIXF_CLEARCALLERTID) - { - self->RemoveFromHash(); - self->tid = 0; - } - if (flags & SIXF_TRANSFERSPECIAL) - { - mo->special = self->special; - memcpy(mo->args, self->args, sizeof(self->args)); - } - if (flags & SIXF_CLEARCALLERSPECIAL) - { - self->special = 0; - memset(self->args, 0, sizeof(self->args)); - } - if (flags & SIXF_TRANSFERSTENCILCOL) - { - mo->fillcolor = self->fillcolor; - } - if (flags & SIXF_TRANSFERALPHA) - { - mo->Alpha = self->Alpha; - } - if (flags & SIXF_TRANSFERRENDERSTYLE) - { - mo->RenderStyle = self->RenderStyle; - } - - if (flags & SIXF_TRANSFERSPRITEFRAME) - { - mo->sprite = self->sprite; - mo->frame = self->frame; - } - - if (flags & SIXF_TRANSFERROLL) - { - mo->Angles.Roll = self->Angles.Roll; - } - - if (flags & SIXF_ISTARGET) - { - self->target = mo; - } - if (flags & SIXF_ISMASTER) - { - self->master = mo; - } - if (flags & SIXF_ISTRACER) - { - self->tracer = mo; - } - return true; -} - -//=========================================================================== -// -// A_SpawnItem -// -// Spawns an item in front of the caller like Heretic's time bomb -// -//=========================================================================== - -DEFINE_ACTION_FUNCTION(AActor, A_SpawnItem) -{ - PARAM_ACTION_PROLOGUE(AActor); - PARAM_CLASS (missile, AActor) - PARAM_FLOAT (distance) - PARAM_FLOAT (zheight) - PARAM_BOOL (useammo) - PARAM_BOOL (transfer_translation); - - if (numret > 1) ret[1].SetObject(nullptr); - - if (missile == NULL) - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } - - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - - if (ACTION_CALL_FROM_PSPRITE()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (weapon == NULL) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - } - - AActor *mo = Spawn( missile, self->Vec3Angle(distance, self->Angles.Yaw, -self->Floorclip + self->GetBobOffset() + zheight), ALLOW_REPLACE); - - int flags = (transfer_translation ? SIXF_TRANSFERTRANSLATION : 0) + (useammo ? SIXF_SETMASTER : 0); - bool res = InitSpawnedItem(self, mo, flags); // for an inventory item's use state - if (numret > 0) ret[0].SetInt(res); - if (numret > 1) ret[1].SetObject(mo); - return MIN(numret, 2); - -} - -//=========================================================================== -// -// A_SpawnItemEx -// -// Enhanced spawning function -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_SpawnItemEx) -{ - PARAM_SELF_PROLOGUE(AActor); - PARAM_CLASS (missile, AActor); - PARAM_FLOAT (xofs) - PARAM_FLOAT (yofs) - PARAM_FLOAT (zofs) - PARAM_FLOAT (xvel) - PARAM_FLOAT (yvel) - PARAM_FLOAT (zvel) - PARAM_ANGLE (angle) - PARAM_INT (flags) - PARAM_INT (chance) - PARAM_INT (tid) - - if (numret > 1) ret[1].SetObject(nullptr); - - if (missile == NULL) - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } - if (chance > 0 && pr_spawnitemex() < chance) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - // Don't spawn monsters if this actor has been massacred - if (self->DamageType == NAME_Massacre && (GetDefaultByType(missile)->flags3 & MF3_ISMONSTER)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - - DVector2 pos; - - if (!(flags & SIXF_ABSOLUTEANGLE)) - { - angle += self->Angles.Yaw; - } - double s = angle.Sin(); - double c = angle.Cos(); - - if (flags & SIXF_ABSOLUTEPOSITION) - { - pos = self->Vec2Offset(xofs, yofs); - } - else - { - // in relative mode negative y values mean 'left' and positive ones mean 'right' - // This is the inverse orientation of the absolute mode! - pos = self->Vec2Offset(xofs * c + yofs * s, xofs * s - yofs*c); - } - - if (!(flags & SIXF_ABSOLUTEVELOCITY)) - { - // Same orientation issue here! - double newxvel = xvel * c + yvel * s; - yvel = xvel * s - yvel * c; - xvel = newxvel; - } - - AActor *mo = Spawn(missile, DVector3(pos, self->Z() - self->Floorclip + self->GetBobOffset() + zofs), ALLOW_REPLACE); - bool res = InitSpawnedItem(self, mo, flags); - if (res) - { - if (tid != 0) - { - assert(mo->tid == 0); - mo->tid = tid; - mo->AddToHash(); - } - mo->Vel = {xvel, yvel, zvel}; - if (flags & SIXF_MULTIPLYSPEED) - { - mo->Vel *= mo->Speed; - } - mo->Angles.Yaw = angle; - } - if (numret > 0) ret[0].SetInt(res); - if (numret > 1) ret[1].SetObject(mo); - return MIN(numret, 2); -} - -//=========================================================================== -// -// A_ThrowGrenade -// -// Throws a grenade (like Hexen's fighter flechette) -// -//=========================================================================== -DEFINE_ACTION_FUNCTION(AActor, A_ThrowGrenade) -{ - PARAM_ACTION_PROLOGUE(AActor); - PARAM_CLASS (missile, AActor); - PARAM_FLOAT (zheight) - PARAM_FLOAT (xyvel) - PARAM_FLOAT (zvel) - PARAM_BOOL (useammo) - - if (numret > 1) ret[1].SetObject(nullptr); - - if (missile == NULL) - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } - if (ACTION_CALL_FROM_PSPRITE()) - { - // Used from a weapon, so use some ammo - AWeapon *weapon = self->player->ReadyWeapon; - - if (weapon == NULL) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - if (useammo && !weapon->DepleteAmmo(weapon->bAltFire)) - { - if (numret > 0) ret[0].SetInt(true); - return MIN(numret, 2); - } - } - - AActor *bo; - - bo = Spawn(missile, - self->PosPlusZ(-self->Floorclip + self->GetBobOffset() + zheight + 35 + (self->player? self->player->crouchoffset : 0.)), - ALLOW_REPLACE); - if (bo) - { - P_PlaySpawnSound(bo, self); - if (xyvel != 0) - bo->Speed = xyvel; - bo->Angles.Yaw = self->Angles.Yaw + (((pr_grenade()&7) - 4) * (360./256.)); - - DAngle pitch = -self->Angles.Pitch; - DAngle angle = bo->Angles.Yaw; - - // There are two vectors we are concerned about here: xy and z. We rotate - // them separately according to the shooter's pitch and then sum them to - // get the final velocity vector to shoot with. - - double xy_xyscale = bo->Speed * pitch.Cos(); - double xy_velz = bo->Speed * pitch.Sin(); - double xy_velx = xy_xyscale * angle.Cos(); - double xy_vely = xy_xyscale * angle.Sin(); - - pitch = self->Angles.Pitch; - double z_xyscale = zvel * pitch.Sin(); - double z_velz = zvel * pitch.Cos(); - double z_velx = z_xyscale * angle.Cos(); - double z_vely = z_xyscale * angle.Sin(); - - bo->Vel.X = xy_velx + z_velx + self->Vel.X / 2; - bo->Vel.Y = xy_vely + z_vely + self->Vel.Y / 2; - bo->Vel.Z = xy_velz + z_velz; - - bo->target = self; - if (!P_CheckMissileSpawn(bo, self->radius)) bo = nullptr; - - if (numret > 0) ret[0].SetInt(true); - if (numret > 1) ret[1].SetObject(bo); - return MIN(numret, 2); - } - else - { - if (numret > 0) ret[0].SetInt(false); - return MIN(numret, 2); - } -} - - //=========================================================================== // // A_Recoil diff --git a/wadsrc/static/zscript/actor.txt b/wadsrc/static/zscript/actor.txt index e4eff57aa..1021977e9 100644 --- a/wadsrc/static/zscript/actor.txt +++ b/wadsrc/static/zscript/actor.txt @@ -1071,8 +1071,6 @@ class Actor : Thinker native native bool A_SetInventory(class itemtype, int amount, int ptr = AAPTR_DEFAULT, bool beyondMax = false); native bool A_GiveInventory(class itemtype, int amount = 0, int giveto = AAPTR_DEFAULT); native bool A_TakeInventory(class itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT); - action native bool, Actor A_SpawnItem(class itemtype = "Unknown", double distance = 0, double zheight = 0, bool useammo = true, bool transfer_translation = false); - native bool, Actor A_SpawnItemEx(class itemtype, double xofs = 0, double yofs = 0, double zofs = 0, double xvel = 0, double yvel = 0, double zvel = 0, double angle = 0, int flags = 0, int failchance = 0, int tid=0); native void A_Print(string whattoprint, double time = 0, name fontname = "none"); native void A_PrintBold(string whattoprint, double time = 0, name fontname = "none"); native void A_Log(string whattoprint, bool local = false); @@ -1097,7 +1095,6 @@ class Actor : Thinker native native bool RaiseActor(Actor other, int flags = 0); native bool CanRaise(); native void Revive(); - action native bool, Actor A_ThrowGrenade(class itemtype, double zheight = 0, double xyvel = 0, double zvel = 0, bool useammo = true); native void A_Weave(int xspeed, int yspeed, double xdist, double ydist); action native state, bool A_Teleport(statelabel teleportstate = null, class targettype = "BossSpot", class fogtype = "TeleportFog", int flags = 0, double mindist = 128, double maxdist = 0, int ptr = AAPTR_DEFAULT); diff --git a/wadsrc/static/zscript/actor_attacks.txt b/wadsrc/static/zscript/actor_attacks.txt index 3e4c08f80..44c32d1f9 100644 --- a/wadsrc/static/zscript/actor_attacks.txt +++ b/wadsrc/static/zscript/actor_attacks.txt @@ -170,5 +170,366 @@ extend class Actor } } + //=========================================================================== + // + // Common code for A_SpawnItem and A_SpawnItemEx + // + //=========================================================================== + + bool InitSpawnedItem(Actor mo, int flags) + { + if (mo == NULL) + { + return false; + } + Actor originator = self; + + if (!(mo.bDontTranslate)) + { + if (flags & SXF_TRANSFERTRANSLATION) + { + mo.Translation = Translation; + } + else if (flags & SXF_USEBLOODCOLOR) + { + // [XA] Use the spawning actor's BloodColor to translate the newly-spawned object. + mo.Translation = BloodTranslation; + } + } + if (flags & SXF_TRANSFERPOINTERS) + { + mo.target = self.target; + mo.master = self.master; // This will be overridden later if SXF_SETMASTER is set + mo.tracer = self.tracer; + } + + mo.Angle = self.Angle; + if (flags & SXF_TRANSFERPITCH) + { + mo.Pitch = self.Pitch; + } + if (!(flags & SXF_ORIGINATOR)) + { + while (originator && (originator.bMissile || originator.default.bMissile)) + { + originator = originator.target; + } + } + if (flags & SXF_TELEFRAG) + { + mo.TeleportMove(mo.Pos, true); + // This is needed to ensure consistent behavior. + // Otherwise it will only spawn if nothing gets telefragged + flags |= SXF_NOCHECKPOSITION; + } + if (mo.bIsMonster) + { + if (!(flags & SXF_NOCHECKPOSITION) && !mo.TestMobjLocation()) + { + // The monster is blocked so don't spawn it at all! + mo.ClearCounters(); + mo.Destroy(); + return false; + } + else if (originator && !(flags & SXF_NOPOINTERS)) + { + if (originator.bIsMonster) + { + // If this is a monster transfer all friendliness information + mo.CopyFriendliness(originator, true); + } + else if (originator.player) + { + // A player always spawns a monster friendly to him + mo.bFriendly = true; + mo.SetFriendPlayer(originator.player); + + Actor attacker=originator.player.attacker; + if (attacker) + { + if (!(attacker.bFriendly) || + (deathmatch && attacker.FriendPlayer != 0 && attacker.FriendPlayer != mo.FriendPlayer)) + { + // Target the monster which last attacked the player + mo.LastHeard = mo.target = attacker; + } + } + } + } + } + else if (!(flags & SXF_TRANSFERPOINTERS)) + { + // If this is a missile or something else set the target to the originator + mo.target = originator ? originator : self; + } + if (flags & SXF_NOPOINTERS) + { + //[MC]Intentionally eliminate pointers. Overrides TRANSFERPOINTERS, but is overridden by SETMASTER/TARGET/TRACER. + mo.LastHeard = NULL; //Sanity check. + mo.target = NULL; + mo.master = NULL; + mo.tracer = NULL; + } + if (flags & SXF_SETMASTER) + { // don't let it attack you (optional)! + mo.master = originator; + } + if (flags & SXF_SETTARGET) + { + mo.target = originator; + } + if (flags & SXF_SETTRACER) + { + mo.tracer = originator; + } + if (flags & SXF_TRANSFERSCALE) + { + mo.Scale = self.Scale; + } + if (flags & SXF_TRANSFERAMBUSHFLAG) + { + mo.bAmbush = bAmbush; + } + if (flags & SXF_CLEARCALLERTID) + { + self.ChangeTid(0); + } + if (flags & SXF_TRANSFERSPECIAL) + { + mo.special = self.special; + mo.args[0] = self.args[0]; + mo.args[1] = self.args[1]; + mo.args[2] = self.args[2]; + mo.args[3] = self.args[3]; + mo.args[4] = self.args[4]; + } + if (flags & SXF_CLEARCALLERSPECIAL) + { + self.special = 0; + mo.args[0] = 0; + mo.args[1] = 0; + mo.args[2] = 0; + mo.args[3] = 0; + mo.args[4] = 0; + } + if (flags & SXF_TRANSFERSTENCILCOL) + { + mo.SetShade(self.fillcolor); + } + if (flags & SXF_TRANSFERALPHA) + { + mo.Alpha = self.Alpha; + } + if (flags & SXF_TRANSFERRENDERSTYLE) + { + mo.RenderStyle = self.RenderStyle; + } + + if (flags & SXF_TRANSFERSPRITEFRAME) + { + mo.sprite = self.sprite; + mo.frame = self.frame; + } + + if (flags & SXF_TRANSFERROLL) + { + mo.Roll = self.Roll; + } + + if (flags & SXF_ISTARGET) + { + self.target = mo; + } + if (flags & SXF_ISMASTER) + { + self.master = mo; + } + if (flags & SXF_ISTRACER) + { + self.tracer = mo; + } + return true; + } + + + //=========================================================================== + // + // A_SpawnItem + // + // Spawns an item in front of the caller like Heretic's time bomb + // + //=========================================================================== + + action bool, Actor A_SpawnItem(class missile = "Unknown", double distance = 0, double zheight = 0, bool useammo = true, bool transfer_translation = false) + { + if (missile == NULL) + { + return false, null; + } + + // Don't spawn monsters if this actor has been massacred + if (DamageType == 'Massacre' && GetDefaultByType(missile).bIsMonster) + { + return true, null; + } + + if (stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + let player = self.player; + if (player == null) return false, null; + let weapon = player.ReadyWeapon; + // Used from a weapon, so use some ammo + + if (weapon == NULL || (useammo && !weapon.DepleteAmmo(weapon.bAltFire))) + { + return true, null; + } + } + + let mo = Spawn(missile, Vec3Angle(distance, Angle, -Floorclip + GetBobOffset() + zheight), ALLOW_REPLACE); + + int flags = (transfer_translation ? SXF_TRANSFERTRANSLATION : 0) + (useammo ? SXF_SETMASTER : 0); + bool res = InitSpawnedItem(mo, flags); // for an inventory item's use state + return res, mo; + } + + //=========================================================================== + // + // A_SpawnItemEx + // + // Enhanced spawning function + // + //=========================================================================== + bool, Actor A_SpawnItemEx(class missile, double xofs = 0, double yofs = 0, double zofs = 0, double xvel = 0, double yvel = 0, double zvel = 0, double angle = 0, int flags = 0, int chance = 0, int tid=0) + { + if (missile == NULL) + { + return false, null; + } + if (chance > 0 && random[spawnitemex]() < chance) + { + return true, null; + } + // Don't spawn monsters if this actor has been massacred + if (DamageType == 'Massacre' && GetDefaultByType(missile).bIsMonster) + { + return true, null; + } + + Vector2 pos; + + if (!(flags & SXF_ABSOLUTEANGLE)) + { + angle += self.Angle; + } + double s = sin(angle); + double c = cos(angle); + + if (flags & SXF_ABSOLUTEPOSITION) + { + pos = Vec2Offset(xofs, yofs); + } + else + { + // in relative mode negative y values mean 'left' and positive ones mean 'right' + // This is the inverse orientation of the absolute mode! + pos = Vec2Offset(xofs * c + yofs * s, xofs * s - yofs*c); + } + + if (!(flags & SXF_ABSOLUTEVELOCITY)) + { + // Same orientation issue here! + double newxvel = xvel * c + yvel * s; + yvel = xvel * s - yvel * c; + xvel = newxvel; + } + + let mo = Spawn(missile, (pos, self.pos.Z - Floorclip + GetBobOffset() + zofs), ALLOW_REPLACE); + bool res = InitSpawnedItem(mo, flags); + if (res) + { + if (tid != 0) + { + mo.ChangeTid(tid); + } + mo.Vel = (xvel, yvel, zvel); + if (flags & SXF_MULTIPLYSPEED) + { + mo.Vel *= mo.Speed; + } + mo.Angle = angle; + } + return res, mo; + } + + + //=========================================================================== + // + // A_ThrowGrenade + // + // Throws a grenade (like Hexen's fighter flechette) + // + //=========================================================================== + action bool, Actor A_ThrowGrenade(class missile, double zheight = 0, double xyvel = 0, double zvel = 0, bool useammo = true) + { + if (missile == NULL) + { + return false, null; + } + if (stateinfo != null && stateinfo.mStateType == STATE_Psprite) + { + let player = self.player; + if (player == null) return false, null; + let weapon = player.ReadyWeapon; + // Used from a weapon, so use some ammo + + if (weapon == NULL || (useammo && !weapon.DepleteAmmo(weapon.bAltFire))) + { + return true, null; + } + } + + let bo = Spawn(missile, pos + (0, 0, (-Floorclip + GetBobOffset() + zheight + 35 + (player? player.crouchoffset : 0.))), ALLOW_REPLACE); + if (bo) + { + bo.PlaySpawnSound(self); + if (xyvel != 0) + bo.Speed = xyvel; + bo.Angle = Angle + (((random[grenade]()&7) - 4) * (360./256.)); + + let pitch = -self.Pitch; + let angle = bo.Angle; + + // There are two vectors we are concerned about here: xy and z. We rotate + // them separately according to the shooter's pitch and then sum them to + // get the final velocity vector to shoot with. + + double xy_xyscale = bo.Speed * cos(pitch); + double xy_velz = bo.Speed * sin(pitch); + double xy_velx = xy_xyscale * cos(angle); + double xy_vely = xy_xyscale * sin(angle); + + pitch = self.Pitch; + double z_xyscale = zvel * sin(pitch); + double z_velz = zvel * cos(pitch); + double z_velx = z_xyscale * cos(angle); + double z_vely = z_xyscale * sin(angle); + + bo.Vel.X = xy_velx + z_velx + Vel.X / 2; + bo.Vel.Y = xy_vely + z_vely + Vel.Y / 2; + bo.Vel.Z = xy_velz + z_velz; + + bo.target = self; + if (!bo.CheckMissileSpawn(radius)) bo = null; + return true, bo; + } + else + { + return false, null; + } + } + + + }