mirror of
https://github.com/ZDoom/Raze.git
synced 2024-12-14 14:41:21 +00:00
427 lines
11 KiB
Text
427 lines
11 KiB
Text
|
|
extend class DukeActor
|
|
{
|
|
static void tracers(Vector3 start, Vector3 dest, int n)
|
|
{
|
|
sectortype sect = nullptr;
|
|
|
|
let direction = dest - start;
|
|
|
|
if (direction.XY.Sum() < 192.75)
|
|
return;
|
|
|
|
let pos = start;
|
|
let add = direction / (n + 1);
|
|
for (int i = n; i > 0; i--)
|
|
{
|
|
pos += add;
|
|
sect = Raze.updatesector(pos.XY, sect);
|
|
if (sect)
|
|
{
|
|
if (sect.lotag == ST_2_UNDERWATER)
|
|
{
|
|
Vector2 scale = (0.0625 + random(0, 3) * REPEAT_SCALE, 0.0625 + random(0, 3) * REPEAT_SCALE);
|
|
dlevel.SpawnActor(sect, pos, "DukeWaterBubble", -32, scale, frandom(0, 360), 0., 0., Duke.GetLocalPlayer().actor, STAT_MISC);
|
|
}
|
|
else
|
|
dlevel.SpawnActor(sect, pos, "DukeSmallSmoke", -32, (0.21875, 0.21875), 0, 0., 0., Duke.GetLocalPlayer().actor, STAT_MISC);
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool HitscanAttack(DukeActor actor, DukePlayer p, Vector3 pos, double ang, double hspread, double vspread, double enemyspread, bool forcespread, double aimangle = -1, bool waterhalfhitchance = false, class<DukeActor> sparktype = "DukeShotSpark") const
|
|
{
|
|
let sectp = actor.sector;
|
|
double vel = 1024, zvel = 0;
|
|
HitInfo hit;
|
|
|
|
if (actor.extra >= 0) actor.shade = -96;
|
|
|
|
if (p != null)
|
|
{
|
|
let [aimed, direct] = actor.aim(self, aimangle);
|
|
if (aimed)
|
|
{
|
|
// if we already aim directly at the target, use the shooter's actual pitch for the hitscan instead of re-aiming.
|
|
if (direct) [vel, zvel] = Raze.setFreeAimVelocity(vel, zvel, p.getPitchWithView(), 16.);
|
|
else
|
|
{
|
|
double dal = ((aimed.scale.X * aimed.spriteHeight()) * 0.5) + aimed.sparkoffset;
|
|
double dist = (p.actor.pos.XY - aimed.pos.XY).Length();
|
|
zvel = ((aimed.pos.Z - pos.Z - dal) * 16) / dist;
|
|
}
|
|
ang = (aimed.pos - pos).Angle();
|
|
}
|
|
|
|
if (aimed == nullptr || forcespread)
|
|
{
|
|
ang += hspread / 2 - frandom(0, hspread);
|
|
if (aimed == nullptr) [vel, zvel] = Raze.setFreeAimVelocity(vel, zvel, p.getPitchWithView(), 16);
|
|
zvel += vspread / 8 - frandom(0, vspread / 4);
|
|
}
|
|
pos.Z -= 2;
|
|
}
|
|
else
|
|
{
|
|
let j = actor.findplayer();
|
|
pos.Z -= 4;
|
|
double dist = (j.actor.pos.XY - actor.pos.XY).Length();
|
|
zvel = ((j.actor.pos.Z + j.actor.viewzoffset - pos.Z) * 16) / dist;
|
|
zvel += frandom(-0.5, 0.5);
|
|
if (!actor.bALTHITSCANDIRECTION)
|
|
{
|
|
ang += enemyspread / 2 - frandom(0, enemyspread);
|
|
}
|
|
else
|
|
{
|
|
// one of those lousy hacks in Duke.
|
|
ang = (j.actor.pos.XY - pos.XY).Angle() + enemyspread - frandom(0, enemyspread * 2);
|
|
}
|
|
}
|
|
|
|
actor.cstat &= ~CSTAT_SPRITE_BLOCK_ALL;
|
|
Raze.hitscan(pos, sectp, (ang.ToVector() * vel, zvel * 64), hit, CLIPMASK1);
|
|
|
|
if ((ud.mapflags & MFLAG_ALLSECTORTYPES) && hit.hitSector != nullptr &&
|
|
((hit.hitSector.lotag == ST_160_FLOOR_TELEPORT && zvel > 0) || (hit.hitSector.lotag == ST_161_CEILING_TELEPORT && zvel < 0))
|
|
&& hit.hitActor == nullptr && hit.hitWall == nullptr)
|
|
{
|
|
DukeStatIterator its;
|
|
for (let effector = its.First(STAT_EFFECTOR); effector; effector = its.Next())
|
|
{
|
|
if (effector.sector == hit.hitSector && effector.GetClassName() == 'DukeSectorEffector' && effector.ownerActor && effector.lotag == SE_7_TELEPORT)
|
|
{
|
|
let owner = effector.ownerActor;
|
|
Vector3 npos;
|
|
npos.XY = hit.hitpos.XY + (owner.pos.XY - effector.pos.XY);
|
|
if (hit.hitSector.lotag == ST_161_CEILING_TELEPORT)
|
|
{
|
|
npos.Z = owner.sector.floorz;
|
|
}
|
|
else
|
|
{
|
|
npos.Z = owner.sector.ceilingz;
|
|
}
|
|
Raze.hitscan(npos, owner.sector, (ang.ToVector() * 1024, zvel * 0.25), hit, CLIPMASK1);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
actor.cstat |= CSTAT_SPRITE_BLOCK_ALL;
|
|
|
|
if (hit.hitSector == nullptr) return true;
|
|
|
|
// RR shotgun only
|
|
if (waterhalfhitchance && hit.hitSector.lotag == ST_1_ABOVE_WATER && random(0, 1))
|
|
return true;
|
|
|
|
if (random(0, 15) == 0 && hit.hitSector.lotag == ST_2_UNDERWATER)
|
|
tracers(hit.hitpos, pos, 8 - (ud.multimode >> 1));
|
|
|
|
DukeActor spark = null;
|
|
if (p != null)
|
|
{
|
|
spark = dlevel.SpawnActor(hit.hitSector, hit.hitpos, sparktype, -15, (0.15625, 0.15625), ang, 0., 0., actor, STAT_PROJECTILE);
|
|
if (!spark) return true;
|
|
|
|
spark.extra = self.strength + random(0, 5);
|
|
|
|
if (hit.hitWall == nullptr && hit.hitActor == nullptr)
|
|
{
|
|
if (zvel < 0)
|
|
{
|
|
if (hit.hitSector.ceilingstat & CSTAT_SECTOR_SKY)
|
|
{
|
|
spark.scale = (0, 0);
|
|
return true;
|
|
}
|
|
else
|
|
dlevel.checkhitceiling(hit.hitSector, spark);
|
|
}
|
|
if (zvel < 0 || hit.hitSector.lotag != ST_1_ABOVE_WATER)
|
|
spark.spawn("DukeSmallSmoke");
|
|
}
|
|
|
|
let hitActor = DukeActor(hit.hitActor);
|
|
if (hitActor)
|
|
{
|
|
if (hitActor.bNOHITSCANHIT)
|
|
return true;
|
|
hitActor.OnHit(spark);
|
|
if (hitActor.isPlayer() && (ud.coop != 1 || ud.ffire == 1))
|
|
{
|
|
let jib = spark.spawn("DukeJibs6");
|
|
spark.scale = (0, 0);
|
|
if (jib)
|
|
{
|
|
jib.pos.Z += 4;
|
|
jib.vel.X = 1;
|
|
jib.scale = (0.375, 0.375);
|
|
jib.Angle += frandom(-11.25, 11.25);
|
|
}
|
|
}
|
|
else spark.spawn("DukeSmallSmoke");
|
|
|
|
if (p != null && Duke.isshootableswitch(hitActor.spritetexture()))
|
|
{
|
|
p.checkhitswitch(nullptr, hitActor);
|
|
return true;
|
|
}
|
|
}
|
|
else if (hit.hitWall)
|
|
{
|
|
spark.spawn("DukeSmallSmoke");
|
|
|
|
if (!(Raze.tileflags(hit.hitWall.walltexture) & (Duke.TFLAG_DOORWALL | Duke.TFLAG_BLOCKDOOR)))
|
|
{
|
|
if (p != null && Duke.isshootableswitch(hit.hitWall.walltexture))
|
|
{
|
|
p.checkhitswitch(hit.hitWall, nullptr);
|
|
return true;
|
|
}
|
|
|
|
if (!(hit.hitWall.hitag != 0 || (hit.hitWall.twoSided() && hit.hitWall.nextWallp().hitag != 0)))
|
|
{
|
|
|
|
if (hit.hitSector && hit.hitSector.lotag == 0 && !(Raze.tileflags(hit.hitWall.overtexture) & Duke.TFLAG_FORCEFIELD))
|
|
{
|
|
if ((hit.hitWall.twoSided() && hit.hitWall.nextSectorp().lotag == 0) || (!hit.hitWall.twoSided() && hit.hitSector.lotag == 0))
|
|
{
|
|
if ((hit.hitWall.cstat & CSTAT_WALL_MASKED) == 0)
|
|
{
|
|
bool ok = true;
|
|
if (hit.hitWall.twoSided())
|
|
{
|
|
DukeSectIterator it;
|
|
for (let l = it.First(hit.hitWall.nextSectorp()); l; l = it.Next())
|
|
{
|
|
if (l.statnum == STAT_EFFECTOR && l.lotag == SE_13_EXPLOSIVE)
|
|
{
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
{
|
|
DukeStatIterator it;
|
|
for (let l = it.First(STAT_MISC); l; l = it.Next())
|
|
{
|
|
if (l is 'DukeBulletHole' && (l.pos - spark.pos).Length() < frandom(0.75, 1.25))
|
|
{
|
|
ok = false;
|
|
break;
|
|
}
|
|
}
|
|
if (ok)
|
|
{
|
|
let hole = spark.spawn("DukeBulletHole");
|
|
if (hole)
|
|
{
|
|
hole.vel.X = -1 / 16.;
|
|
hole.Angle = hit.hitWall.delta().Angle() - 90;
|
|
hole.DoMove(CLIPMASK0);
|
|
hole.cstat2 |= CSTAT2_SPRITE_DECAL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hit.hitWall.cstat & CSTAT_WALL_BOTTOM_SWAP)
|
|
if (hit.hitWall.twoSided())
|
|
if (hit.hitpos.Z >= hit.hitWall.nextSectorp().floorz)
|
|
hit.hitWall = hit.hitWall.nextWallp();
|
|
|
|
dlevel.checkhitwall(hit.hitWall, spark, hit.hitpos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
spark = dlevel.SpawnActor(hit.hitSector, hit.hitpos, sparktype, -15, (0.375, 0.375), ang, 0., 0., actor, STAT_PROJECTILE);
|
|
if (spark)
|
|
{
|
|
spark.extra = self.strength;
|
|
let hitActor = DukeActor(hit.hitActor);
|
|
|
|
if (hitActor)
|
|
{
|
|
hitActor.OnHit(spark);
|
|
if (!hitActor.isPlayer())
|
|
spark.spawn("DukeSmallSmoke");
|
|
else spark.scale = (0, 0);
|
|
}
|
|
else if (hit.hitWall)
|
|
dlevel.checkhitwall(hit.hitWall, spark, hit.hitpos);
|
|
}
|
|
}
|
|
|
|
if (spark && random(0, 255) < 4)
|
|
{
|
|
spark.PlayActorSound("PISTOL_RICOCHET");
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
class DukeShotSpark : DukeActor
|
|
{
|
|
default
|
|
{
|
|
pic "SHOTSPARK1";
|
|
+FORCERUNCON;
|
|
+LIGHTDAMAGE;
|
|
statnum STAT_MISC;
|
|
Strength PISTOL_WEAPON_STRENGTH;
|
|
}
|
|
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
return HitscanAttack(actor, p, pos, ang, 5.625, 4, 11.25, false);
|
|
}
|
|
|
|
}
|
|
|
|
class DukeShotgunShot : DukeActor
|
|
{
|
|
default
|
|
{
|
|
pic "SHOTGUN";
|
|
Strength SHOTGUN_WEAPON_STRENGTH;
|
|
}
|
|
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
return HitscanAttack(actor, p, pos, ang, 5.625, 4, 11.25, true);
|
|
}
|
|
}
|
|
|
|
class DukeChaingunShot : DukeActor
|
|
{
|
|
default
|
|
{
|
|
pic "CHAINGUN";
|
|
Strength CHAINGUN_WEAPON_STRENGTH;
|
|
}
|
|
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
return HitscanAttack(actor, p, pos, ang, 5.625, 4, 11.25, true);
|
|
}
|
|
}
|
|
|
|
// RR' damage properties are a bit different.
|
|
class RedneckShotSpark : DukeShotSpark
|
|
{
|
|
default
|
|
{
|
|
Strength CASUL_WEAPON_STRENGTH;
|
|
}
|
|
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
return HitscanAttack(actor, p, pos, ang, 5.625, 4, 11.25, false);
|
|
}
|
|
}
|
|
|
|
|
|
class RedneckShotgunShot : DukeShotgunShot
|
|
{
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
return HitscanAttack(actor, p, pos, ang, 22.5, 4, 11.25, true, -1, true);
|
|
}
|
|
}
|
|
|
|
|
|
class RedneckChaingunShot : DukeChaingunShot
|
|
{
|
|
default
|
|
{
|
|
Strength RIFLE_WEAPON_STRENGTH;
|
|
}
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
return HitscanAttack(actor, p, pos, ang, 5.625, 4, 11.25, true);
|
|
}
|
|
}
|
|
|
|
extend class DukeActor
|
|
{
|
|
// Todo: once we have real weapons, this should be cleaned up.
|
|
double WW2GIAimAngle(DukePlayer p)
|
|
{
|
|
if (p == null) return -1;
|
|
int lAmount = p.GetGameVar("PLR_MORALE", -1);
|
|
if (lAmount >= 40) return -1;
|
|
return 7.91015625;
|
|
}
|
|
|
|
double, double WW2GIGetShotRange(DukePlayer p)
|
|
{
|
|
// the logic here looks very broken...
|
|
if (p == null) return 5.625, 4;
|
|
int lAmount = p.GetGameVar("PLR_MORALE", -1);
|
|
|
|
if (lAmount < 25) return 11.25, 4;
|
|
if (lAmount > 70) switch(p.curr_weapon)
|
|
{
|
|
case DukeWpn.PISTOL_WEAPON:
|
|
return 5.626, 4;
|
|
case DukeWpn.CHAINGUN_WEAPON:
|
|
return 2.8175, 2;
|
|
case DukeWpn.SHRINKER_WEAPON:
|
|
return 11.25, 8;
|
|
default:
|
|
return 2.8175, 4;
|
|
}
|
|
return 5.625, 4;
|
|
}
|
|
}
|
|
|
|
|
|
// WW2GI has different settings.
|
|
class WW2GIShotSpark : DukeShotSpark
|
|
{
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
let [hspread, vspread] = self.WW2GIGetShotRange(p);
|
|
return HitscanAttack(actor, p, pos, ang, hspread, vspread, 11.25, true, self.WW2GIAimAngle(p));
|
|
}
|
|
}
|
|
|
|
class WW2GIShotgunShot : DukeShotgunShot
|
|
{
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
let [hspread, vspread] = self.WW2GIGetShotRange(p);
|
|
return HitscanAttack(actor, p, pos, ang, hspread, vspread, 11.25, true, self.WW2GIAimAngle(p));
|
|
}
|
|
}
|
|
|
|
|
|
class WW2GIChaingunShot : DukeChaingunShot
|
|
{
|
|
override bool ShootThis(DukeActor actor, DukePlayer p, Vector3 pos, double ang)
|
|
{
|
|
let [hspread, vspread] = self.WW2GIGetShotRange(p);
|
|
return HitscanAttack(actor, p, pos, ang, hspread, vspread, 11.25, true, self.WW2GIAimAngle(p));
|
|
}
|
|
}
|