raze/wadsrc/static/zscript/games/duke/actors/projectiles.zs
2023-04-02 13:10:34 +02:00

1059 lines
No EOL
21 KiB
Text

// Note: Duke's handling is dumb enough to make it impossible for other actors than the predefined projectile type to be used as projectile -
// even if it is given the right statnum the projectile code won't get called for it.
// So even in the future any projectile needs to inherit from this to gain the needed feature support.
extend class DukeActor
{
// placed in DukeActor so it remains reusable.
void bounce()
{
Vector3 vect = (self.angle.ToVector() * self.vel.X, self.vel.Z);
let sectp = self.sector;
double daang = sectp.walls[0].delta().Angle();
double k;
if (self.pos.Z < (self.floorz + self.ceilingz) * 0.5)
k = sectp.ceilingheinum;
else
k = sectp.floorheinum;
Vector3 davec = (sin(daang) * k, -cos(daang) * k, 4096);
double dotp = vect dot davec;
double l = davec.LengthSquared();
vect -= davec * (2 * dotp / l);
self.vel.Z = vect.Z;
self.vel.X = vect.XY.Length();
self.angle = vect.Angle();
}
}
class DukeProjectile : DukeActor
{
default
{
statnum STAT_PROJECTILE;
}
Vector3 oldpos; // holds the position before the current move
double velx, vely; // holds the actual velocity for the current move. This can differ from the actor's internal values.
Sound SpawnSound;
// this large batch of subsequently called virtuals is owed to the spaghetti-like implementation of the orignal moveprojectiles function.
virtual bool premoveeffect()
{
return false;
}
virtual bool postmoveeffect(CollisionData coll)
{
if (coll.type != kHitSprite)
{
if (self.pos.Z < self.ceilingz)
{
coll.setSector(self.sector);
self.vel.Z -= 1/256.;
}
else if ((self.pos.Z > self.floorz && self.sector.lotag != ST_1_ABOVE_WATER) ||
(self.pos.Z > self.floorz + 16 && self.sector.lotag == ST_1_ABOVE_WATER))
{
coll.setSector(self.sector);
if (self.sector.lotag != ST_1_ABOVE_WATER)
self.vel.Z += 1/256.;
}
}
return false;
}
virtual bool weaponhitsprite_pre(DukeActor targ)
{
targ.checkhitsprite(self);
return false;
}
virtual bool weaponhitplayer(DukeActor targ)
{
targ.PlayActorSound("PISTOL_BODYHIT");
return false;
}
protected bool weaponhitsprite(DukeActor targ)
{
if (self.weaponhitsprite_pre(targ)) return true;
if (!targ.isPlayer()) return false;
return self.weaponhitplayer(targ);
}
virtual bool weaponhitwall(walltype wal)
{
if (self.actorflag2(SFLAG2_MIRRORREFLECT) && dlevel.isMirror(wal))
{
let k = wal.delta().Angle();
self.angle = k * 2 - self.angle;
self.ownerActor = self;
self.spawn("DukeTransporterStar");
return true;
}
else
{
self.SetPosition(oldpos);
dlevel.checkhitwall(wal, self, self.pos);
if (self.actorflag2(SFLAG2_REFLECTIVE))
{
if (!dlevel.isMirror(wal))
{
self.extra >>= 1;
self.yint--;
}
let k = wal.delta().Angle();
self.angle = k * 2 - self.angle;
return true;
}
}
return false;
}
virtual bool weaponhitsector()
{
self.SetPosition(oldpos);
if (self.vel.Z < 0)
{
if ((self.sector.ceilingstat & CSTAT_SECTOR_SKY) && (self.sector.ceilingpal == 0))
{
self.Destroy();
return true;
}
dlevel.checkhitceiling(self.sector, self);
}
return false;
}
virtual void posthiteffect(CollisionData coll)
{
self.Destroy();
}
override void Tick()
{
double vel = self.vel.X;
double velz = self.vel.Z;
oldpos = self.pos;
int p = -1;
if (self.actorflag2(SFLAG2_UNDERWATERSLOWDOWN) && self.sector.lotag == ST_2_UNDERWATER)
{
vel *= 0.5;
velz *= 0.5;
}
self.getglobalz();
if (self.premoveeffect()) return;
CollisionData coll;
self.movesprite_ex((self.angle.ToVector() * vel, velz), CLIPMASK1, coll);
if (!self.sector)
{
self.Destroy();
return;
}
if (self.postmoveeffect(coll)) return;
if (coll.type != 0)
{
if (coll.type == kHitSprite)
{
if (self.weaponhitsprite(DukeActor(coll.hitactor()))) return;
}
else if (coll.type == kHitWall)
{
if (weaponhitwall(coll.hitWall())) return;
}
else if (coll.type == kHitSector)
{
if (weaponhitsector()) return;
}
posthiteffect(coll);
}
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DukeFirelaser : DukeProjectile // Liztrooper shot
{
default
{
spriteset "FIRELASER", "FIRELASER2", "FIRELASER3", "FIRELASER4", "FIRELASER5", "FIRELASER6";
}
override bool postmoveeffect(CollisionData coll)
{
if (Super.postmoveeffect(coll)) return true;
for (int k = -3; k < 2; k++)
{
double zAdd = k * self.vel.Z / 24;
let spawned = dlevel.SpawnActor(self.sector, self.pos.plusZ(zAdd) + self.angle.ToVector() * k * 2., 'DukeFireLaserTrail', -40 + (k << 2), self.scale, 0, 0., 0., self.ownerActor, STAT_MISC);
if (spawned)
{
spawned.opos = self.opos - self.pos + spawned.pos;
spawned.cstat = CSTAT_SPRITE_YCENTER;
spawned.pal = self.pal;
}
}
return false;
}
override bool animate(tspritetype tspr)
{
if (Raze.isRR()) tspr.setSpritePic(self, ((PlayClock >> 2) % 6));
tspr.shade = -127;
return true;
}
}
class DukeFirelaserTrail : DukeActor
{
default
{
spriteset "FIRELASER", "FIRELASER2", "FIRELASER3", "FIRELASER4", "FIRELASER5", "FIRELASER6";
}
override void Tick()
{
if (self.extra == 999)
{
self.Destroy();
}
}
override bool animate(tspritetype tspr)
{
self.extra = 999;
if (Raze.isRR()) tspr.setSpritePic(self, ((PlayClock >> 2) % 6));
tspr.shade = -127;
return true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DukeShrinkSpark : DukeProjectile
{
default
{
spriteset "SHRINKSPARK", "SHRINKSPARK1", "SHRINKSPARK2", "SHRINKSPARK3";
}
override void posthiteffect(CollisionData coll)
{
self.spawn('DukeShrinkerExplosion');
self.PlayActorSound("SHRINKER_HIT");
self.hitradius(gs.shrinkerblastradius, 0, 0, 0, 0);
self.Destroy();
}
override bool animate(tspritetype tspr)
{
tspr.setSpritePic(self, (PlayClock >> 4) & 3);
tspr.shade = -127;
return true;
}
}
class DukeShrinkerExplosion : DukeActor
{
default
{
spriteset "SHRINKEREXPLOSION";
}
override bool animate(tspritetype tspr)
{
tspr.shade = -127;
return false;
}
override void Initialize()
{
let owner = self.ownerActor;
if (owner != self)
{
self.Angle = owner.Angle;
self.cstat = CSTAT_SPRITE_YCENTER | randomXFlip();
double c,f;
[c, f] = self.sector.getSlopes(self.pos.XY);
self.pos.Z = min(self.pos.Z, f - 12);
}
self.shade = -64;
self.scale = (0.5, 0.5);
self.ChangeStat(STAT_MISC);
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DukeRPG : DukeProjectile
{
default
{
pic "RPG";
}
override void Initialize()
{
SpawnSound = "RPG_SHOOT";
}
override bool premoveeffect()
{
if ((!self.ownerActor || !self.ownerActor.actorflag2(SFLAG2_NONSMOKYROCKET)) && self.scale.X >= 0.15625 && self.sector.lotag != ST_2_UNDERWATER)
{
let spawned = self.spawn("DukeSmallSmoke");
if (spawned) spawned.pos.Z += 1;
}
return super.premoveeffect();
}
override bool postmoveeffect(CollisionData coll)
{
Super.postmoveeffect(coll);
if (self.temp_actor != nullptr && (self.pos.XY - self.temp_actor.pos.XY).LengthSquared() < 16 * 16)
coll.setActor(self.temp_actor);
return false;
}
override void posthiteffect(CollisionData coll)
{
self.rpgexplode(coll.type, oldpos, true, -1, "RPG_EXPLODE");
self.Destroy();
}
void rpgexplode(int hit, Vector3 pos, bool exbottom, int newextra, Sound playsound)
{
let explosion = self.spawn("DukeExplosion2");
if (!explosion) return;
explosion.pos = pos;
if (self.scale.X < 0.15625)
{
explosion.scale = (0.09375, 0.09375);
}
else if (hit == kHitSector)
{
if (self.vel.Z > 0 && exbottom)
self.spawn("DukeExplosion2Bot");
else
{
explosion.cstat |= CSTAT_SPRITE_YFLIP;
explosion.pos.Z += 48;
}
}
if (newextra > 0) self.extra = newextra;
self.PlayActorSound(playsound);
if (self.scale.X >= 0.15625)
{
int x = self.extra;
self.hitradius(gs.rpgblastradius, x >> 2, x >> 1, x - (x >> 2), x);
}
else
{
int x = self.extra + (Duke.global_random() & 3);
self.hitradius((gs.rpgblastradius >> 1), x >> 2, x >> 1, x - (x >> 2), x);
}
}
override void Tick()
{
super.Tick();
if (self.sector && self.sector.lotag == ST_2_UNDERWATER && self.scale.X >= 0.15625 && Duke.rnd(140))
self.spawn('DukeWaterBubble');
}
override bool animate(tspritetype tspr)
{
tspr.shade = -127;
return true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DukeFreezeBlast : DukeProjectile
{
default
{
pic "FREEZEBLAST";
}
override bool postmoveeffect(CollisionData coll)
{
return false;
}
override bool weaponhitsprite_pre(DukeActor targ)
{
if (targ.pal == 1) // is target already frozen?
{
if (targ.badguy() || targ.isPlayer())
{
let spawned = targ.spawn('DukeTransporterStar');
if (spawned)
{
spawned.pal = 1;
spawned.scale = (0.5, 0.5);
}
self.Destroy();
return true;
}
}
return super.weaponhitsprite_pre(targ);
}
override void Tick()
{
if (self.yint < 1 || self.extra < 2 || (self.vel.X == 0 && self.vel.Z == 0))
{
let star = self.spawn("DukeTransporterStar");
if (star)
{
star.pal = 1;
star.scale = (0.5, 0.5);
}
self.Destroy();
}
else
Super.Tick();
}
override bool weaponhitsector()
{
self.bounce();
self.doMove(CLIPMASK1);
self.extra >>= 1;
if (self.scale.X > 0.125 )
self.scale.X -= 0.03125;
if (self.scale.Y > 0.125 )
self.scale.Y -= 0.03125;
self.yint--;
return true;
}
override bool animate(tspritetype tspr)
{
tspr.shade = -127;
return true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DukeSpit : DukeProjectile
{
default
{
pic "SPIT";
}
override bool postmoveeffect(CollisionData coll)
{
Super.postmoveeffect(coll);
if (self.vel.Z < 24)
self.vel.Z += gs.gravity - 112 / 256.;
return false;
}
override bool weaponhitplayer(DukeActor targ)
{
if (Super.weaponhitplayer(targ)) return true;
let p = targ.GetPlayer();
p.addPitch(-14.04);
p.centerview();
if (p.loogcnt == 0)
{
if (!p.actor.CheckSoundPlaying("PLAYER_LONGTERM_PAIN"))
p.actor.PlayActorSound("PLAYER_LONGTERM_PAIN");
int j = random(3, 7);
p.numloogs = j;
p.loogcnt = 24 * 4;
for (int x = 0; x < j; x++)
{
p.loogie[x].X = random(0, 319);
p.loogie[x].Y = random(0, 199);
}
}
return false;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DukeCoolExplosion1 : DukeProjectile // octabrain shot.
{
default
{
spriteset "COOLEXPLOSION1", "COOLEXPLOSION2", "COOLEXPLOSION3", "COOLEXPLOSION4", "COOLEXPLOSION5",
"COOLEXPLOSION6", "COOLEXPLOSION7", "COOLEXPLOSION8", "COOLEXPLOSION9", "COOLEXPLOSION10",
"COOLEXPLOSION11", "COOLEXPLOSION12", "COOLEXPLOSION13", "COOLEXPLOSION14", "COOLEXPLOSION15",
"COOLEXPLOSION16", "COOLEXPLOSION17", "COOLEXPLOSION18", "COOLEXPLOSION19", "COOLEXPLOSION20";
}
override void Initialize()
{
self.angle = self.ownerActor.angle;
self.shade = -64;
self.cstat = CSTAT_SPRITE_YCENTER | self.randomXFlip();
double c, f;
[c, f] = self.sector.getSlopes(self.pos.XY);
if (self.pos.Z > f - 12)
self.pos.Z = f - 12;
}
override bool premoveeffect()
{
if (!self.CheckSoundPlaying("WIERDSHOT_FLY"))
self.PlayActorSound("WIERDSHOT_FLY");
return false;
}
override bool weaponhitsprite_pre(DukeActor targ)
{
if (!targ.isPlayer())
{
return true;
}
self.vel.X = self.vel.Z = 0;
return super.weaponhitsprite_pre(targ);
}
override bool weaponhitwall(walltype wal)
{
self.vel.X = self.vel.Z = 0;
return super.weaponhitwall(wal);
}
override bool weaponhitsector()
{
self.vel.X = self.vel.Z = 0;
return super.weaponhitsector();
}
override void posthiteffect(CollisionData coll)
{
// don't destroy.
}
override void Tick()
{
Super.Tick();
if (++self.shade >= 40)
{
self.Destroy();
}
}
override bool animate(tspritetype tspr)
{
tspr.setSpritePic(self, clamp((self.shade >> 1), 0, 19));
tspr.shade = -127;
return true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class DukeFireball : DukeProjectile // WorldTour only
{
default
{
pic "FIREBALL";
}
override bool premoveeffect()
{
let Owner = self.ownerActor;
if (self.sector.lotag == 2)
{
self.Destroy();
return true;
}
if (self.detail != 1)
{
if (self.temp_data[0] >= 1 && self.temp_data[0] < 6)
{
double siz = 1.0 - (self.temp_data[0] * 0.2);
DukeActor trail = self.temp_actor;
let ball = self.spawn('DukeFireball');
if (ball)
{
self.temp_actor = ball;
ball.vel.X = self.vel.X;
ball.vel.Z = self.vel.Z;
ball.angle = self.angle;
if (self.temp_data[0] > 1)
{
if (trail)
{
ball.pos = trail.temp_pos;
ball.vel = trail.temp_pos2;
}
}
double scale = self.scale.X * siz;
ball.scale = (scale, scale);
ball.cstat = self.cstat;
ball.extra = 0;
ball.temp_pos = ball.pos;
ball.temp_pos2 = ball.vel;
ball.detail = 1;
ball.ChangeStat(STAT_PROJECTILE);
}
}
self.temp_data[0]++;
}
if (self.vel.Z < 15000. / 256.)
self.vel.Z += 200 / 256.;
return false;
}
override bool weaponhitsprite_pre(DukeActor targ)
{
if (self.detail != 1)
return super.weaponhitsprite_pre(targ);
return false;
}
override bool weaponhitplayer(DukeActor targ)
{
let p = targ.GetPlayer();
let Owner = self.ownerActor;
if (p && ud.multimode >= 2 && Owner && Owner.isPlayer())
{
p.numloogs = -1 - self.yint;
}
return Super.weaponhitplayer(targ);
}
override bool weaponhitsector()
{
if (super.weaponhitsector()) return true;
if (self.detail != 1)
{
let spawned = self.spawn('DukeLavapool');
if (spawned)
{
spawned.ownerActor = self;
spawned.hitOwnerActor = self;
spawned.yint = self.yint;
}
self.Destroy();
return true;
}
return false;
}
override bool animate(tspritetype tspr)
{
tspr.shade = -127;
return true;
}
}
//---------------------------------------------------------------------------
//
// These 3 just use the base projectile code...
//
//---------------------------------------------------------------------------
class RedneckUWhip : DukeProjectile
{
default
{
pic "UWHIP";
}
override bool animate(tspritetype tspr)
{
tspr.shade = -127;
return true;
}
}
class RedneckOWhip : RedneckUWhip
{
default
{
pic "OWHIP";
}
}
class RedneckVixenShot : RedneckUWhip // COOLEXPLOSION1
{
default
{
pic "VIXENSHOT";
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class RedneckDynamiteArrow : DukeRPG
{
default
{
pic "RPG";
}
override bool weaponhitsprite_pre(DukeActor targ)
{
if (targ.actorflag2(SFLAG2_TRANFERPALTOJIBS) && targ.pal == 19)
{
self.PlayActorSound("RPG_EXPLODE");
let spawned = self.spawn("DukeExplosion2");
if (spawned)
spawned.pos = oldpos;
return true;
}
return Super.weaponhitsprite_pre(targ);
}
override void posthiteffect(CollisionData coll)
{
self.rpgexplode(coll.type, oldpos, false, -1, "RPG_EXPLODE");
self.Destroy();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class RedneckChickenArrow : RedneckDynamiteArrow
{
default
{
pic "RPG2";
}
override void Initialize()
{
self.hitag = 0;
SpawnSound = "CHICKENBOW_FIRE";
self.lotsofstuff("RedneckFeather", random(1, 4));
}
override bool premoveeffect()
{
// seeker handling
self.hitag++;
if (self.scale.X >= 0.15625 && self.sector.lotag != ST_2_UNDERWATER)
{
let spawned = self.spawn("DukeSmallSmoke");
if (spawned) spawned.pos.Z += 1;
if (random(0, 15) == 2)
{
self.spawn("RedneckFeather");
}
}
DukeActor ts = self.seek_actor;
if (!ts) return false;
if (ts.extra <= 0)
self.seek_actor = null;
if (self.seek_actor && self.hitag > 5)
{
let ang = (ts.pos - self.pos).Angle();
let ang2 = deltaangle(ang, self.angle);
// this was quite broken in the original code. Fixed so that it seeks properly
if (abs(ang2) < 17.5)
{
self.angle = ang;
}
else if (ang2 > 0)
{
self.angle -= 9;
}
else
self.angle += 9;
if (self.hitag > 180)
if (self.vel.Z <= 0)
self.vel.Z += 200 / 256;
}
return false;
}
override void posthiteffect(CollisionData coll)
{
self.rpgexplode(coll.type, oldpos, false, 150, "CHKBOWEX");
self.Destroy();
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class RedneckBoatGrenade : RedneckDynamiteArrow // RRRA only
{
default
{
pic "BOATGRENADE";
}
override void Initialize()
{
SpawnSound = "MORTAR";
self.extra = 10;
self.vel.Z = -10;
self.vel.X *= 2;
super.Initialize();
}
override bool premoveeffect()
{
if (self.extra)
{
self.vel.Z = -(self.extra * 250/256.); // 250 looks like a typo...
self.extra--;
}
else
self.makeitfall();
return Super.premoveeffect();
}
override void posthiteffect(CollisionData coll)
{
self.rpgexplode(coll.type, oldpos, false, 160, "RPG_EXPLODE");
self.Destroy();
}
}
//---------------------------------------------------------------------------
//
// this class is called shitball - but it's not just about throwing shit in the game,
// the entire logic with 4 different looks depending on the shooter is also shit...
//
//---------------------------------------------------------------------------
class RedneckShitBall : DukeSpit
{
default
{
spriteset "SHITBALL", "SHITBALL2", "SHITBALL3", "SHITBALL4",
"FROGBALL1", "FROGBALL2", "FROGBALL3", "FROGBALL4", "FROGBALL5", "FROGBALL6",
"SHITBURN", "SHITBURN2", "SHITBURN3", "SHITBURN4",
"RABBITBALL";
}
private void rabbitguts()
{
self.spawnguts('RedneckRabbitJibA', 2);
self.spawnguts('RedneckRabbitJibB', 2);
self.spawnguts('RedneckRabbitJibC', 2);
}
override bool weaponhitplayer(DukeActor targ)
{
if (ownerActor && ownerActor.actorflag2(SFLAG2_SPAWNRABBITGUTS))
rabbitguts();
return Super.weaponhitplayer(targ);
}
override bool weaponhitwall(walltype wal)
{
self.SetPosition(oldpos);
if (ownerActor && ownerActor.actorflag2(SFLAG2_SPAWNRABBITGUTS))
rabbitguts();
return super.weaponhitwall(wal);
}
override bool weaponhitsector()
{
self.setPosition(oldpos);
if (ownerActor && ownerActor.actorflag2(SFLAG2_SPAWNRABBITGUTS))
rabbitguts();
return super.weaponhitsector();
}
override bool animate(tspritetype tspr)
{
int sprite = ((PlayClock >> 4) & 3);
if (self.ownerActor)
{
let OwnerAc = self.ownerActor;
if (OwnerAc.actorflag2(SFLAG2_TRANFERPALTOJIBS))
{
if (OwnerAc.pal == 8)
{
sprite = 4 + ((PlayClock >> 4) % 6);
}
else if (OwnerAc.pal == 19)
{
sprite = 10 + ((PlayClock >> 4) & 3);
tspr.shade = -127;
}
}
else if (OwnerAc.actorflag2(SFLAG2_SPAWNRABBITGUTS))
{
tspr.clipdist |= TSPR_ROTATE8FRAMES;
sprite = 14;
}
}
return true;
}
}
//---------------------------------------------------------------------------
//
//
//
//---------------------------------------------------------------------------
class RedneckSawBlade : DukeProjectile
{
default
{
spriteset "SAWBLADE", "SAWBLADE2", "SAWBLADE3", "SAWBLADE4", "SAWBLADE5", "SAWBLADE6", "SAWBLADE7", "SAWBLADE8",
"CHEERBLADE", "CHEERBLADE2", "CHEERBLADE3", "CHEERBLADE4";
}
override bool weaponhitwall(walltype wal)
{
if (dlevel.wallflags(wal, 0) & Duke.TFLAG_NOCIRCLEREFLECT)
{
self.Destroy();
return true;
}
if (self.extra <= 0)
{
self.pos += self.angle.ToVector() * 8;
let Owner = self.ownerActor;
if (!Owner || !(Owner.actorflag2(SFLAG2_ALTPROJECTILESPRITE))) // depends on the shooter. Urgh...
{
let j = self.spawn("RedneckCircleStuck");
if (j)
{
j.scale = (0.125, 0.125);
j.cstat = CSTAT_SPRITE_ALIGNMENT_WALL;
j.angle += 90;
j.clipdist = self.scale.X * self.spriteWidth() * 0.125;
}
}
self.Destroy();
return true;
}
if (!dlevel.isMirror(wal))
{
self.extra -= 20;
self.yint--;
}
let k = wal.delta().Angle();
self.angle = k * 2 - self.angle;
return true;
}
override bool animate(tspritetype tspr)
{
int frame;
if (!OwnerActor || !(OwnerActor.actorflag2(SFLAG2_ALTPROJECTILESPRITE))) frame = ((PlayClock >> 4) & 7);
else frame = 8 + ((PlayClock >> 4) & 3);
tspr.SetSpritePic(self, frame);
return true;
}
}
class RedneckCircleStuck : DukeActor
{
default
{
pic "CIRCLESTUCK";
}
}