qzdoom/wadsrc/static/zscript/actor_attacks.txt
Christoph Oelckers b4c272ddff - 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.
2018-11-24 17:01:12 +01:00

535 lines
13 KiB
Text

extend class Actor
{
//---------------------------------------------------------------------------
//
// Used by A_CustomBulletAttack and A_FireBullets
//
//---------------------------------------------------------------------------
static void AimBulletMissile(Actor proj, Actor puff, int flags, bool temp, bool cba)
{
if (proj && puff)
{
// FAF_BOTTOM = 1
// Aim for the base of the puff as that's where blood puffs will spawn... roughly.
proj.A_Face(puff, 0., 0., 0., 0., 1);
proj.Vel3DFromAngle(proj.Speed, proj.Angle, proj.Pitch);
if (!temp)
{
if (cba)
{
if (flags & CBAF_PUFFTARGET) proj.target = puff;
if (flags & CBAF_PUFFMASTER) proj.master = puff;
if (flags & CBAF_PUFFTRACER) proj.tracer = puff;
}
else
{
if (flags & FBF_PUFFTARGET) proj.target = puff;
if (flags & FBF_PUFFMASTER) proj.master = puff;
if (flags & FBF_PUFFTRACER) proj.tracer = puff;
}
}
}
if (puff && temp)
{
puff.Destroy();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
void A_CustomBulletAttack(double spread_xy, double spread_z, int numbullets, int damageperbullet, class<Actor> pufftype = "BulletPuff", double range = 0, int flags = 0, int ptr = AAPTR_TARGET, class<Actor> missile = null, double Spawnheight = 32, double Spawnofs_xy = 0)
{
let ref = GetPointer(ptr);
if (range == 0)
range = MISSILERANGE;
int i;
double bangle;
double bslope = 0.;
int laflags = (flags & CBAF_NORANDOMPUFFZ)? LAF_NORANDOMPUFFZ : 0;
if (ref != NULL || (flags & CBAF_AIMFACING))
{
if (!(flags & CBAF_AIMFACING))
{
A_Face(ref);
}
bangle = self.Angle;
if (!(flags & CBAF_NOPITCH)) bslope = AimLineAttack (bangle, MISSILERANGE);
if (pufftype == null) pufftype = 'BulletPuff';
A_PlaySound(AttackSound, CHAN_WEAPON);
for (i = 0; i < numbullets; i++)
{
double pangle = bangle;
double slope = bslope;
if (flags & CBAF_EXPLICITANGLE)
{
pangle += spread_xy;
slope += spread_z;
}
else
{
pangle += spread_xy * Random2[cwbullet]() / 255.;
slope += spread_z * Random2[cwbullet]() / 255.;
}
int damage = damageperbullet;
if (!(flags & CBAF_NORANDOM))
damage *= random[cwbullet](1, 3);
let puff = LineAttack(pangle, range, slope, damage, 'Hitscan', pufftype, laflags);
if (missile != null && pufftype != null)
{
double x = Spawnofs_xy * cos(pangle);
double y = Spawnofs_xy * sin(pangle);
SetXYZ(Vec3Offset(x, y, 0.));
let proj = SpawnMissileAngleZSpeed(Pos.Z + GetBobOffset() + Spawnheight, missile, self.Angle, 0, GetDefaultByType(missile).Speed, self, false);
SetXYZ(pos);
if (proj)
{
bool temp = (puff == null);
if (!puff)
{
puff = LineAttack(pangle, range, slope, 0, 'Hitscan', pufftype, laflags | LAF_NOINTERACT);
}
if (puff)
{
AimBulletMissile(proj, puff, flags, temp, true);
}
}
}
}
}
}
//============================================================================
//
// P_DaggerAlert
//
//============================================================================
void DaggerAlert(Actor target)
{
Actor looker;
if (LastHeard != NULL)
return;
if (health <= 0)
return;
if (!bIsMonster)
return;
if (bInCombat)
return;
bInCombat = true;
self.target = target;
let painstate = FindState('Pain', 'Dagger');
if (painstate != NULL)
{
SetState(painstate);
}
for (looker = cursector.thinglist; looker != NULL; looker = looker.snext)
{
if (looker == self || looker == target)
continue;
if (looker.health <= 0)
continue;
if (!looker.bSeesDaggers)
continue;
if (!looker.bInCombat)
{
if (!looker.CheckSight(target) && !looker.CheckSight(self))
continue;
looker.target = target;
if (looker.SeeSound)
{
looker.A_PlaySound(looker.SeeSound, CHAN_VOICE);
}
looker.SetState(looker.SeeState);
looker.bInCombat = true;
}
}
}
//===========================================================================
//
// 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;
}
}
}