- scriptified A_SpawnItem(Ex) and A_ThrowGrenade.

These were the last native functions referencing AWeapon::DepleteAmmo, so that function is now exclusively on the scripting side.
This commit is contained in:
Christoph Oelckers 2018-11-24 17:01:12 +01:00 committed by drfrag
parent 1b225588d1
commit 8c2c888d82
5 changed files with 361 additions and 474 deletions

View file

@ -234,30 +234,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

View file

@ -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
{

View file

@ -1975,452 +1975,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

View file

@ -1070,8 +1070,6 @@ class Actor : Thinker native
native bool A_SetInventory(class<Inventory> itemtype, int amount, int ptr = AAPTR_DEFAULT, bool beyondMax = false);
native bool A_GiveInventory(class<Inventory> itemtype, int amount = 0, int giveto = AAPTR_DEFAULT);
native bool A_TakeInventory(class<Inventory> itemtype, int amount = 0, int flags = 0, int giveto = AAPTR_DEFAULT);
action native bool, Actor A_SpawnItem(class<Actor> itemtype = "Unknown", double distance = 0, double zheight = 0, bool useammo = true, bool transfer_translation = false);
native bool, Actor A_SpawnItemEx(class<Actor> 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);
@ -1096,7 +1094,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<Actor> 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<SpecialSpot> targettype = "BossSpot", class<Actor> fogtype = "TeleportFog", int flags = 0, double mindist = 128, double maxdist = 0, int ptr = AAPTR_DEFAULT);

View file

@ -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<Actor> 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<Actor> 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<Actor> 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;
}
}
}