swwmgz_m/zscript/weapons/swwm_deepdarkimpact.zsc
Marisa the Magician e5e6ce619c Fix severe performance issues in large maps.
So... Remember that one decision I made about avoiding BlockThingsIterator as
much as possible? Turns out that was a stupid idea. There ARE situations where
it's better to iterate sector thinglists, yes, especially for things that are
NOT part of the blockmap, but in other cases, the excess allocations of new
iterators are a reasonable price to pay for the lower perf impact in extreme
cases, such as maps that have a gazillion sectors with gazillions of things in
them (I'm looking at you, UDMF mappers).
As a compromise, at least, in situations where the thinglists are needed, I did
add a sort of micro-optimization by implementing code to check if a bounding
box is inside a sector (would be nice if this was part of GZDoom itself, tho).
2023-12-19 11:46:29 +01:00

495 lines
14 KiB
Text

// Dr. Locke's Mighty Wolf Breath Airgun aka "Deep Impact" Airblaster (from SWWM series)
// Slot 1, replaces Fist, Staff, Hexen starting weapons
Class DeepTracer : LineTracer
{
Array<Actor> hitlist;
Array<double> hitdist;
Array<double> hitposx, hitposy, hitposz;
override ETraceStatus TraceCallback()
{
if ( Results.HitType == TRACE_HitActor )
{
if ( Results.HitActor.bSHOOTABLE )
{
hitlist.Push(Results.HitActor);
hitdist.Push(Results.Distance);
hitposx.Push(Results.HitPos.x);
hitposy.Push(Results.HitPos.y);
hitposz.Push(Results.HitPos.z);
return TRACE_Skip;
}
return TRACE_Skip;
}
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
{
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
return TRACE_Stop;
return TRACE_Skip;
}
return TRACE_Stop;
}
}
Class THitList
{
Actor a;
int nhits;
Vector3 avgdir;
double avgdist;
Vector3 avgpos;
}
Class DeepImpact : SWWMWeapon
{
int clipcount;
double charge;
bool charging;
int wastecycle; // for easter egg
transient ui SmoothDynamicValueInterpolator ChargeInter;
transient int failtime;
Property ClipCount : clipcount;
override void HudTick()
{
Super.HudTick();
if ( !ChargeInter ) ChargeInter = SmoothDynamicValueInterpolator.Create(clipcount,.5);
ChargeInter.Update(clipcount);
}
override bool ReportHUDAmmo()
{
return (ClipCount>0);
}
action void A_BeginCharge()
{
invoker.charge = 0;
A_StartSound("deepimpact/charge",CHAN_WEAPONEXTRA);
A_QuakeEx(2.,2.,2.,35,0,1,"",QF_RELATIVE|QF_SCALEUP,rollIntensity:.2);
A_AlertMonsters(swwm_uncapalert?0:100);
}
action void A_ChargeUp()
{
invoker.charge += 1./GameTicRate;
A_WeaponOffset(FRandom[Impact](-1,1)*invoker.charge,32+FRandom[Impact](-1,1)*invoker.charge);
if ( invoker.charge >= (invoker.clipcount*.01) )
{
A_WeaponOffset(0,32);
player.SetPSprite(PSP_WEAPON,ResolveState("AltRelease"));
}
}
override Vector3 GetTraceOffset( int index )
{
return (10.,2.,-3.);
}
action void A_DryFire()
{
let weap = Weapon(invoker);
if ( !weap ) return;
A_BumpView(-.05);
A_QuakeEx(.1,.1,.1,2,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.05);
A_StartSound("deepimpact/dryfire",CHAN_WEAPON,CHANF_OVERLAP,.5);
A_AlertMonsters(swwm_uncapalert?0:70);
Vector3 x = SWWMUtility.GetPlayerViewDir(self);
Vector3 origin = SWWMUtility.GetFireOffset(self,10,2,-3);
int numpt = Random[Impact](5,7);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+SWWMUtility.Vec3FromAngles(FRandom[Impact](0,360),FRandom[Impact](-90,90))*.1).unit()*FRandom[Impact](.1,.4);
let s = Spawn("SWWMSmoke",origin);
s.vel = pvel;
s.scale *= .3;
s.alpha *= .05;
s.SetShade(Color(1,1,1)*Random[Impact](192,255));
}
}
action void A_Air()
{
let weap = Weapon(invoker);
if ( !weap ) return;
A_BumpFOV(.995);
A_BumpView(-.2);
A_QuakeEx(.8,.8,.8,2,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.05);
A_StartSound("deepimpact/fire",CHAN_WEAPON,CHANF_OVERLAP);
A_AlertMonsters(swwm_uncapalert?0:300);
A_PlayerFire();
invoker.clipcount = max(0,invoker.clipcount-3);
Vector3 dir;
let [x, y, z] = SWWMUtility.GetPlayerAxes(self);
SWWMUtility.DoKnockback(self,-x,2000.);
Vector3 origin = SWWMUtility.GetFireOffset(self,10,2,-3);
DeepTracer t = new("DeepTracer");
Array<THitList> list;
list.Clear();
int rings = 1;
int wallhits = 0;
Vector3 hitnormal;
Vector3 wnorm = (0,0,0);
for ( double i=0; i<.35; i+=.05 )
{
for ( int j=0; j<360; j+=(360/rings) )
{
dir = SWWMUtility.ConeSpread(x,y,z,j,i);
t.hitlist.Clear();
t.hitdist.Clear();
t.hitposx.Clear();
t.hitposy.Clear();
t.hitposz.Clear();
t.Trace(origin,level.PointInSector(origin.xy),dir,250-i*150,0,ignore:self);
SWWMBulletTrail.DoTrail(self,origin,dir,250-i*150,0);
for ( int k=0; k<t.hitlist.Size(); k++ )
{
int inl = -1;
for ( int l=0; l<list.Size(); l++ )
{
if ( list[l].a != t.hitlist[k] ) continue;
inl = l;
break;
}
if ( inl == -1 )
{
THitList l = new("THitList");
l.a = t.hitlist[k];
inl = list.Push(l);
}
list[inl].nhits++;
list[inl].avgdir += dir;
list[inl].avgdist += t.hitdist[k];
list[inl].avgpos += (t.hitposx[k],t.hitposy[k],t.hitposz[k]);
}
if ( t.Results.HitType == TRACE_HitWall )
{
t.Results.HitLine.RemoteActivate(self,t.Results.Side,SPAC_Impact,t.Results.HitPos);
wallhits++;
hitnormal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit();
if ( !t.Results.Side ) hitnormal *= -1;
wnorm += (hitnormal*.2-dir)/max(50.,t.Results.Distance);
}
else if ( t.Results.HitType == TRACE_HitFloor )
{
wallhits++;
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.top.Normal;
else hitnormal = t.Results.HitSector.floorplane.Normal;
wnorm += (hitnormal*.2-dir)/max(50.,t.Results.Distance);
let b = Spawn("InvisibleSplasher",t.Results.HitPos);
b.target = self;
}
else if ( t.Results.HitType == TRACE_HitCeiling )
{
wallhits++;
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.bottom.Normal;
else hitnormal = t.Results.HitSector.ceilingplane.Normal;
wnorm += (hitnormal*.2-dir)/max(50.,t.Results.Distance);
}
if ( swwm_omnibust ) BusterWall.Bust(t.Results,int(5*(1.-clamp(t.Results.Distance/250.,0.,1.))),self,t.Results.HitVector,t.Results.HitPos.z);
}
rings += 5;
}
if ( wallhits > 0 )
{
wnorm /= wallhits;
if ( (pos.z > floorz) && TestMobjZ() ) wnorm *= 2.4;
SWWMUtility.DoKnockback(self,wnorm.unit(),wnorm.length()*5000000.);
}
foreach ( l:list )
{
if ( !l.a ) continue;
Vector3 avgdir = l.avgdir/l.nhits;
double avgdist = l.avgdist/l.nhits;
Vector3 avgpos = l.avgpos/l.nhits;
double dmg = 5000.*(1.-clamp(avgdist/250.,0.,1.));
SWWMUtility.DoKnockback(l.a,avgdir,dmg*35.);
let p = SWWMPuff.Setup(avgpos,avgdir,invoker,self,l.a);
l.a.DamageMobj(p,self,int(dmg/250.),'Push',DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF);
}
let st = Demolitionist(self).mystats;
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
foreach ( s:level.Sectors )
{
// don't check sectors that aren't within bounds, saves some time
if ( !hnd.BoxInSectorBounds(s,origin.xy,250,level.PointInSector(origin.xy).PortalGroup) )
continue;
for ( Actor m=s.thinglist; m; m=m.snext )
{
if ( !SWWMUtility.ValidProjectile(m) ) continue;
Vector3 rdir = level.Vec3Diff(origin,m.pos);
double rdist = rdir.length();
if ( rdist <= 0. ) continue;
rdir /= rdist;
if ( LineTrace(atan2(rdir.y,rdir.x),rdist,asin(-rdir.z),TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y) || (rdist > 250) || (rdir dot x < .75) ) continue;
m.speed = m.vel.length();
m.vel = m.speed*1.5*(-m.vel.unit()*.3+rdir+x*.2).unit();
Vector3 ndir = m.vel.unit();
m.angle = atan2(ndir.y,ndir.x);
m.pitch = asin(-ndir.z);
if ( m.bSEEKERMISSILE && (m.target != self) ) m.tracer = m.target;
m.target = self;
if ( !m.FindInventory("ParriedBuff") )
{
let pb = Inventory(Spawn("ParriedBuff"));
pb.AttachToOwner(m);
pb.bAMBUSH = true;
}
if ( st ) st.parries++;
SWWMUtility.AchievementProgressInc("parry",1,player);
}
}
int numpt = Random[Impact](7,12);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+SWWMUtility.Vec3FromAngles(FRandom[Impact](0,360),FRandom[Impact](-90,90))*.2).unit()*FRandom[Impact](.5,4);
let s = Spawn("SWWMSmoke",origin);
s.vel = pvel;
s.special1 = 1;
s.scale *= .5;
s.alpha *= .15;
s.SetShade(Color(1,1,1)*Random[Impact](192,255));
}
}
action void A_DryAltFire()
{
invoker.failtime = gametic+32;
A_StartSound("deepimpact/dryaltfire",CHAN_WEAPON,CHANF_OVERLAP);
A_BumpView(-.05);
A_QuakeEx(.1,.1,.1,2,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.05);
}
action void A_AltBullet()
{
let weap = Weapon(invoker);
if ( !weap ) return;
A_StopSound(CHAN_WEAPONEXTRA);
A_BumpFOV(.85);
A_BumpView(-5);
A_QuakeEx(6.,6.,6.,10,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.7);
A_StartSound("deepimpact/altfire",CHAN_WEAPON,CHANF_OVERLAP,attenuation:.5);
A_AlertMonsters(swwm_uncapalert?0:8000);
A_PlayerFire();
invoker.clipcount = 0;
Vector3 x = SWWMUtility.GetPlayerViewDir(self);
SWWMUtility.DoKnockback(self,-x,42000.);
Vector3 origin = SWWMUtility.GetFireOffset(self,10,2,-3);
double a = FRandom[Spread](0,360), s = FRandom[Spread](0,.002);
let [x2, y2, z2] = SWWMUtility.GetPlayerAxesAutoAimed(self);
Vector3 dir = SWWMUtility.ConeSpread(x2,y2,z2,a,s);
let p = Spawn("AirBullet",origin);
p.target = self;
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.vel = dir*p.speed;
invoker.charge = 0;
int numpt = Random[Impact](14,18);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+SWWMUtility.Vec3FromAngles(FRandom[Impact](0,360),FRandom[Impact](-90,90))*.5).unit()*FRandom[Impact](1,6);
let s = Spawn("SWWMSmoke",origin);
s.vel = pvel;
s.special1 = 2;
s.scale *= .7;
s.alpha *= .2;
s.SetShade(Color(1,1,1)*Random[Impact](192,255));
}
}
action void A_Crank()
{
invoker.clipcount = min(invoker.default.clipcount,invoker.clipcount+25);
A_StartSound("deepimpact/reload",CHAN_WEAPON,CHANF_OVERLAP);
A_QuakeEx(.3,.3,.3,7,0,1,"",QF_RELATIVE|QF_SCALEUP,rollIntensity:.05);
invoker.wastecycle = 0;
}
action void A_NoCrank()
{
A_StartSound("deepimpact/noreload",CHAN_WEAPON,CHANF_OVERLAP);
A_QuakeEx(.2,.2,.2,2,0,1,"",QF_RELATIVE|QF_SCALEUP,rollIntensity:.05);
invoker.wastecycle++;
let s = Demolitionist(self).mystats;
if ( s.deepegg < 4 )
{
if ( invoker.wastecycle < (10+s.deepegg*10) ) return;
s.deepegg++;
}
else return;
invoker.wastecycle = 0;
if ( player == players[consoleplayer] )
EventHandler.SendInterfaceEvent(consoleplayer,"swwmsetdialogue.CRANK"..1+(s.deepegg-1));
}
override void MarkPrecacheSounds()
{
Super.MarkPrecacheSounds();
MarkSound("deepimpact/fire");
MarkSound("deepimpact/charge");
MarkSound("deepimpact/altfire");
MarkSound("deepimpact/dryfire");
MarkSound("deepimpact/select");
MarkSound("deepimpact/checkout");
MarkSound("deepimpact/deselect");
MarkSound("deepimpact/bullet");
MarkSound("deepimpact/bullethit1");
MarkSound("deepimpact/bullethit2");
MarkSound("deepimpact/reloadbeg");
MarkSound("deepimpact/reloadend");
MarkSound("deepimpact/reload");
MarkSound("deepimpact/noreload");
}
Default
{
Tag "$T_DEEPIMPACT";
Inventory.Icon "graphics/HUD/Icons/W_DeepImpact.png";
Inventory.PickupMessage "$I_DEEPIMPACT";
Obituary "$O_DEEPIMPACT_WEAK";
SWWMWeapon.Tooltip "$TT_DEEPIMPACT";
SWWMWeapon.GetLine "getdeepimpact";
Weapon.UpSound "deepimpact/select";
Weapon.SlotNumber 1;
Weapon.SelectionOrder 2000;
Stamina 6000;
DeepImpact.ClipCount 100;
+WEAPON.MELEEWEAPON;
+WEAPON.WIMPY_WEAPON;
+WEAPON.NOAUTOSWITCHTO;
+SWWMWEAPON.NOSWAPWEAPON;
}
States
{
Spawn:
XZW1 A -1;
Stop;
Select:
XZW2 I 2 A_FullRaise();
XZW2 J 2 A_BumpView(.2,tics:2);
XZW2 KL 2 A_BumpView(-.2,tics:2);
XZW2 MN 2 A_BumpView(-.1,tics:2);
XZW2 OP 2 A_BumpView(.1,tics:2);
Goto Ready;
Ready:
XZW2 A 1 A_WeaponReady(WRF_ALLOWRELOAD|WRF_ALLOWUSER1|WRF_ALLOWZOOM);
Wait;
DryFire:
XZW2 A 1 A_DryFire();
XZW2 QR 2;
XZW2 TU 3;
XZW2 A 2;
Goto Ready;
Fire:
XZW2 A 0 A_JumpIf(invoker.ClipCount<=0,"DryFire");
XZW2 A 1 A_Air();
XZW2 QR 2;
XZW2 STU 3;
Goto Ready;
AltFire:
XZW2 A 0 A_JumpIf(invoker.ClipCount<100,"DryAltFire");
XZW2 A 0 A_BeginCharge();
XZW2 A 1 A_ChargeUp();
XZW2 V 1 A_ChargeUp();
Wait;
DryAltFire:
// the useless goddess
XZW2 A 2 A_DryAltFire();
XZW2 Q 4;
XZW2 U 5;
XZW2 A 2;
Goto Ready;
AltRelease:
XZW2 V 1 A_AltBullet();
XZW2 W 1 A_BumpView(.15,tics:15);
XZW2 X 2;
XZW2 YZ 3;
XZW3 AB 5;
XZW3 CD 4;
XZW3 EFG 3;
Goto Ready;
Reload:
XZW2 A 0 A_JumpIf(invoker.clipcount>=invoker.default.clipcount,"NoReload");
XZW2 A 2 A_StartSound("deepimpact/reloadbeg",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 HIJ 2 A_BumpView(.1,tics:2);
ReloadHold:
XZW3 K 2
{
A_Crank();
A_BumpView(-.1,tics:2);
}
XZW3 LM 2 A_BumpView(-.2,tics:2);
XZW3 NOPQ 3;
XZW3 K 0 A_JumpIf((player.cmd.buttons&(BT_RELOAD|BT_ALTATTACK))&&(invoker.clipcount<invoker.default.clipcount),"ReloadHold");
XZW3 K 2 A_StartSound("deepimpact/reloadend",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 RSTUV 2 A_BumpView(.1,tics:2);
Goto Ready;
NoReload:
XZW2 A 2 A_StartSound("deepimpact/reloadbeg",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 HIJ 2 A_BumpView(.1,tics:2);
NoReloadHold:
XZW3 K 2
{
A_NoCrank();
A_BumpView(-.1,tics:2);
}
XZW3 L 2 A_BumpView(-.2,tics:2);
XZW3 M 5 A_BumpView(-.2,tics:2);
XZW3 L 3;
XZW3 K 2 A_StartSound("deepimpact/reloadend",CHAN_WEAPON,CHANF_OVERLAP);
XZW3 RSTUV 2 A_BumpView(.1,tics:2);
Goto Ready;
Zoom:
XZW2 A 3
{
A_StartSound("deepimpact/checkout",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerCheckGun();
A_BumpView(.2,tics:3);
}
XZW3 WXYZ 3 A_BumpView(-.2,tics:3);
XZW4 AB 4 A_BumpView(-.1,tics:4);
XZW4 CD 3 A_BumpView(.2,tics:3);
XZW4 EFG 2 A_BumpView(.1,tics:2);
Goto Ready;
User1:
XZW2 A 2
{
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZW4 H 2
{
A_BumpView(.5,tics:4);
A_StartSound("demolitionist/reloadbeg",CHAN_WEAPON,CHANF_OVERLAP);
}
XZW4 I 2;
XZW4 J 1
{
A_BumpFOV(.98);
A_QuakeEx(.5,.5,.5,8,0,15,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.2);
A_Parry(9);
}
XZW4 KLM 1 A_BumpView(-1);
XZW4 N 4 A_Melee(60,"demolitionist/whits");
XZW4 O 3 { invoker.PlayUpSound(self); }
XZW4 PQRSTUV 2;
Goto Ready;
Deselect:
XZW2 A 2
{
A_StartSound("deepimpact/deselect",CHAN_WEAPON,CHANF_OVERLAP);
A_BumpView(-.2,tics:2);
}
XZW2 BC 2 A_BumpView(-.1,tics:2);
XZW2 DE 2 A_BumpView(-.1,tics:2);
XZW2 FG 2 A_BumpView(.1,tics:2);
XZW2 H 2 A_BumpView(.2,tics:2);
XZW2 I -1 A_FullLower();
Stop;
}
}