460 lines
13 KiB
Text
460 lines
13 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
|
|
{
|
|
Actor ignoreme;
|
|
Array<Actor> hitlist;
|
|
Array<double> hitdist;
|
|
Array<double> hitposx, hitposy, hitposz;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
|
|
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;
|
|
|
|
transient ui TextureID WeaponBox, AmmoBar;
|
|
transient ui DynamicValueInterpolator ChargeInter;
|
|
transient int failtime;
|
|
|
|
Property ClipCount : clipcount;
|
|
|
|
override void DrawWeapon( double TicFrac, double bx, double by, double hs, Vector2 ss )
|
|
{
|
|
if ( !WeaponBox ) WeaponBox = TexMan.CheckForTexture("graphics/HUD/DeepImpactDisplay.png",TexMan.Type_Any);
|
|
if ( !AmmoBar ) AmmoBar = TexMan.CheckForTexture("graphics/HUD/DeepImpactBar.png",TexMan.Type_Any);
|
|
Screen.DrawTexture(WeaponBox,false,bx-36,by-54,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
|
|
int chg = clamp(ChargeInter?ChargeInter.GetValue():clipcount,0,100);
|
|
int ct = int(((by-2)-(chg*50./100.))*hs);
|
|
bool blinking = (failtime>gametic)&&((failtime-gametic)%8>=4);
|
|
Screen.DrawTexture(AmmoBar,false,bx-7,by-52,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ClipTop,ct,DTA_ColorOverlay,blinking?Color(128,0,0,0):Color(0,0,0,0));
|
|
Screen.DrawText(smallfont,Font.CR_FIRE,bx-35,by-12,String.Format("%3d%%",chg),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,blinking?Color(128,0,0,0):Color(0,0,0,0));
|
|
}
|
|
|
|
override void HudTick()
|
|
{
|
|
Super.HudTick();
|
|
if ( !ChargeInter ) ChargeInter = DynamicValueInterpolator.Create(clipcount,.5,1,25);
|
|
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()
|
|
{
|
|
return (10.,2.,-3.);
|
|
}
|
|
|
|
action void A_DryFire()
|
|
{
|
|
let weap = Weapon(invoker);
|
|
if ( !weap ) return;
|
|
A_StartSound("deepimpact/dryfire",CHAN_WEAPON,CHANF_OVERLAP,.5);
|
|
A_AlertMonsters(swwm_uncapalert?0:70);
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-3*z);
|
|
int numpt = Random[Impact](5,7);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Impact](-.1,.1),FRandom[Impact](-.1,.1),FRandom[Impact](-.1,.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_QuakeEx(1,1,1,2,0,1,"",QF_RELATIVE|QF_SCALEDOWN,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 x, y, z, dir;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
SWWMUtility.DoKnockback(self,-x,2000.);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-3*z);
|
|
DeepTracer t = new("DeepTracer");
|
|
t.ignoreme = self;
|
|
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 = (x+y*cos(j)*i+z*sin(j)*i).unit();
|
|
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);
|
|
SWWMBulletTrail.DoTrail(self,origin,dir,250-i*150,0);
|
|
for ( int i=0; i<t.hitlist.Size(); i++ )
|
|
{
|
|
int inl = -1;
|
|
for ( int k=0; k<list.Size(); k++ )
|
|
{
|
|
if ( list[k].a != t.hitlist[i] ) continue;
|
|
inl = k;
|
|
break;
|
|
}
|
|
if ( inl == -1 )
|
|
{
|
|
THitList l = new("THitList");
|
|
l.a = t.hitlist[i];
|
|
inl = list.Push(l);
|
|
}
|
|
list[inl].nhits++;
|
|
list[inl].avgdir += dir;
|
|
list[inl].avgdist += t.hitdist[i];
|
|
list[inl].avgpos += (t.hitposx[i],t.hitposy[i],t.hitposz[i]);
|
|
}
|
|
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);
|
|
Spawn("InvisibleSplasher",t.Results.HitPos);
|
|
}
|
|
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.);
|
|
}
|
|
for ( int i=0; i<list.Size(); i++ )
|
|
{
|
|
Vector3 avgdir = list[i].avgdir/list[i].nhits;
|
|
double avgdist = list[i].avgdist/list[i].nhits;
|
|
Vector3 avgpos = list[i].avgpos/list[i].nhits;
|
|
double dmg = 5000.*(1.-clamp(avgdist/250.,0.,1.));
|
|
SWWMUtility.DoKnockback(list[i].a,avgdir,dmg*35.);
|
|
let p = SWWMPuff.Setup(avgpos,avgdir,invoker,self,list[i].a);
|
|
list[i].a.DamageMobj(p,self,int(dmg/250.),'Push',DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF);
|
|
}
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor m;
|
|
let s = Demolitionist(self).mystats;
|
|
while ( m = Actor(ti.Next()) )
|
|
{
|
|
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 ( s ) s.parries++;
|
|
SWWMUtility.AchievementProgressInc("parry",1,player);
|
|
}
|
|
int numpt = Random[Impact](7,12);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Impact](-.2,.2),FRandom[Impact](-.2,.2),FRandom[Impact](-.2,.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);
|
|
}
|
|
|
|
action void A_AltBullet()
|
|
{
|
|
let weap = Weapon(invoker);
|
|
if ( !weap ) return;
|
|
A_StopSound(CHAN_WEAPONEXTRA);
|
|
A_QuakeEx(6,6,6,10,0,1,"",QF_RELATIVE|QF_SCALEDOWN,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, y, z, x2, y2, z2;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
SWWMUtility.DoKnockback(self,-x,42000.);
|
|
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-3*z);
|
|
double a = FRandom[Spread](0,360), s = FRandom[Spread](0,.002);
|
|
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
|
|
Vector3 dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
|
|
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+(FRandom[Impact](-.5,.5),FRandom[Impact](-.5,.5),FRandom[Impact](-.5,.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(1,1,1,7,0,1,"",QF_RELATIVE|QF_SCALEUP,rollIntensity:.05);
|
|
}
|
|
|
|
action void A_NoCrank()
|
|
{
|
|
A_StartSound("deepimpact/noreload",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_QuakeEx(1,1,1,2,0,1,"",QF_RELATIVE|QF_SCALEUP,rollIntensity:.05);
|
|
}
|
|
|
|
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
|
|
{
|
|
//$Title Deep Impact
|
|
//$Group Weapons
|
|
//$Sprite graphics/HUD/Icons/W_DeepImpact.png
|
|
//$Icon weapon
|
|
Tag "$T_DEEPIMPACT";
|
|
Inventory.PickupMessage "$I_DEEPIMPACT";
|
|
Obituary "$O_DEEPIMPACT_WEAK";
|
|
SWWMWeapon.Tooltip "$TT_DEEPIMPACT";
|
|
SWWMWeapon.GetLine "getdeepimpact";
|
|
Inventory.Icon "graphics/HUD/Icons/W_DeepImpact.png";
|
|
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 JKLMNOP 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;
|
|
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;
|
|
ReloadHold:
|
|
XZW3 K 2 A_Crank();
|
|
XZW3 LM 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;
|
|
Goto Ready;
|
|
NoReload:
|
|
XZW2 A 2 A_StartSound("deepimpact/reloadbeg",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW3 HIJ 2;
|
|
NoReloadHold:
|
|
XZW3 K 2 A_NoCrank();
|
|
XZW3 L 2;
|
|
XZW3 M 5;
|
|
XZW3 L 3;
|
|
XZW3 K 2 A_StartSound("deepimpact/reloadend",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW3 RSTUV 2;
|
|
Goto Ready;
|
|
Zoom:
|
|
XZW2 A 3
|
|
{
|
|
A_StartSound("deepimpact/checkout",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_PlayerCheckGun();
|
|
}
|
|
XZW3 WXYZ 3;
|
|
XZW4 AB 4;
|
|
XZW4 CD 3;
|
|
XZW4 EFG 2;
|
|
Goto Ready;
|
|
User1:
|
|
XZW2 A 2
|
|
{
|
|
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_PlayerMelee();
|
|
}
|
|
XZW4 H 2 A_StartSound("demolitionist/reloadbeg",CHAN_WEAPON,CHANF_OVERLAP);
|
|
XZW4 I 2;
|
|
XZW4 J 1 A_Parry(9);
|
|
XZW4 KLM 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);
|
|
XZW2 BCDEFGHI 2;
|
|
XZW2 I -1 A_FullLower();
|
|
Stop;
|
|
}
|
|
}
|