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).
495 lines
14 KiB
Text
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;
|
|
}
|
|
}
|