swwmgz_m/zscript/weapons/swwm_cbt.zsc

1497 lines
42 KiB
Text

// Blackmann Arms "Wallbuster" Heavy Armor Perforator Shotgun (planned for unreleased Total Destruction UT mod as the "Armor Perforator")
// Slot 3, replaces Super Shotgun, Ethereal Crossbow, Frost Shards
Class Wallbuster : SWWMWeapon
{
Class<Ammo> loaded[25];
bool fired[25];
int rotation[6];
bool initialized;
Array<Class<Ammo> > reloadqueue;
transient bool waitreload;
int whichspin;
int rnum;
transient bool cancelreload;
transient bool clearout;
transient ui TextureID WeaponBox, AmmoIcon[4], LoadIcon[4], UsedIcon[4], EmptyIcon;
Class<Ammo> curobt;
ui Vector3 cpos25[25];
ui Color ccol25[25];
ui Vector2 lagvpos25[25];
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( curobt is 'RedShell' ) return StringTable.Localize("$O_WALLBUSTER_RED");
if ( curobt is 'GreenShell' ) return StringTable.Localize("$O_WALLBUSTER_GREEN");
if ( curobt is 'BlueShell' ) return StringTable.Localize("$O_WALLBUSTER_BLUE");
if ( curobt is 'PurpleShell' ) return StringTable.Localize("$O_WALLBUSTER_PURPLE");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
override void DrawWeapon( double TicFrac, double bx, double by, double hs, Vector2 ss )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
/*
(Layout of indices)
0
4 1
3 2
L 9
K M 8 5
O N 7 6
H D
G I C E
F J B A
*/
static const int barrelposx[] =
{
29,24,26,32,34,
10,12,18,20,15,
17,23,25,20,15,
41,43,38,33,35,
48,43,38,40,46
};
static const int barrelposy[] =
{
46,42,36,36,42,
31,25,25,31,35,
8, 8,14,18,14,
8,14,18,14, 8,
31,35,31,25,25
};
if ( !WeaponBox )
{
WeaponBox = TexMan.CheckForTexture("graphics/HUD/WallbusterDisplay.png",TexMan.Type_Any);
AmmoIcon[0] = TexMan.CheckForTexture("graphics/HUD/RedShell.png",TexMan.Type_Any);
AmmoIcon[1] = TexMan.CheckForTexture("graphics/HUD/GreenShell.png",TexMan.Type_Any);
AmmoIcon[2] = TexMan.CheckForTexture("graphics/HUD/BlueShell.png",TexMan.Type_Any);
AmmoIcon[3] = TexMan.CheckForTexture("graphics/HUD/PurpleShell.png",TexMan.Type_Any);
LoadIcon[0] = TexMan.CheckForTexture("graphics/HUD/WallbusterRed.png",TexMan.Type_Any);
LoadIcon[1] = TexMan.CheckForTexture("graphics/HUD/WallbusterGreen.png",TexMan.Type_Any);
LoadIcon[2] = TexMan.CheckForTexture("graphics/HUD/WallbusterBlue.png",TexMan.Type_Any);
LoadIcon[3] = TexMan.CheckForTexture("graphics/HUD/WallbusterPurple.png",TexMan.Type_Any);
UsedIcon[0] = TexMan.CheckForTexture("graphics/HUD/WallbusterRedUsed.png",TexMan.Type_Any);
UsedIcon[1] = TexMan.CheckForTexture("graphics/HUD/WallbusterGreenUsed.png",TexMan.Type_Any);
UsedIcon[2] = TexMan.CheckForTexture("graphics/HUD/WallbusterBlueUsed.png",TexMan.Type_Any);
UsedIcon[3] = TexMan.CheckForTexture("graphics/HUD/WallbusterPurpleUsed.png",TexMan.Type_Any);
EmptyIcon = TexMan.CheckForTexture("graphics/HUD/WallbusterEmpty.png",TexMan.Type_Any);
}
Screen.DrawTexture(WeaponBox,false,bx-54,by-72,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
int ox = 6;
int oy = 60;
for ( int i=0; i<4; i++ )
{
Screen.DrawTexture(AmmoIcon[i],false,bx-ox,by-oy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
String astr = String.Format("%3d",Owner.CountInv(types[i]));
Screen.DrawText(SWWMStatusBar(StatusBar).mSmallFont,Font.CR_FIRE,bx-ox-(SWWMStatusBar(StatusBar).mSmallFont.StringWidth(astr)+1),by-oy-1,astr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
oy += 10;
if ( i == 1 )
{
oy = 60;
ox = 33;
}
}
// and here's the big clusterdick
for ( int i=0; i<25; i++ )
{
int idx = i;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx-rotation[group]);
while ( gidx < 0 ) gidx += 5;
idx = gidx+group*5;
// shift based on full rotation
idx = (idx-rotation[5]*5);
while ( idx < 0 ) idx += 25;
if ( !loaded[i] )
{
Screen.DrawTexture(EmptyIcon,false,bx-barrelposx[idx],by-barrelposy[idx],DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
continue;
}
int which = 0;
for ( int j=0; j<4; j++ )
{
if ( !(loaded[i] is types[j]) ) continue;
which = j;
break;
}
Screen.DrawTexture(fired[i]?UsedIcon[which]:LoadIcon[which],false,bx-barrelposx[idx],by-barrelposy[idx],DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
}
}
override void HudTick()
{
Super.HudTick();
if ( !Owner ) return;
// 25-trace
for ( int i=0; i<25; i++ )
[cpos25[i], ccol25[i]] = TraceForCrosshair25(i);
}
override void RenderUnderlay( RenderEvent e )
{
// draw custom crosshair
if ( automapactive || !(players[consoleplayer].Camera is 'PlayerPawn') ) return;
if ( !swwm_precisecrosshair ) return;
if ( crosshairforce ) return;
if ( !crosshairon && (swwm_precisecrosshair <= 1) ) return;
let sb = SWWMStatusBar(StatusBar);
if ( !sb ) return;
SWWMUtility.PrepareProjData(sb.projdata,e.ViewPos,e.ViewAngle,e.ViewPitch,e.ViewRoll,players[consoleplayer].fov);
int cnum = abs(CVar.FindCVar('crosshair').GetInt());
if ( !cnum ) return;
String tn = String.Format("XHAIR%s%d",(Screen.GetWidth()<640)?"S":"B",cnum);
TextureID ctex = TexMan.CheckForTexture(tn,TexMan.Type_MiscPatch);
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture(String.Format("XHAIR%s1",(Screen.GetWidth()<640)?"S":"B"),TexMan.Type_MiscPatch);
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture("XHAIRS1",TexMan.Type_MiscPatch);
Vector2 ts = TexMan.GetScaledSize(ctex);
double cs = crosshairscale;
double sz = 1.;
if ( cs > 0. ) sz = Screen.GetHeight()*cs/200.;
if ( crosshairgrow ) sz *= sb.CrosshairSize;
Vector3 tdir = level.Vec3Diff(e.ViewPos,cpos);
// project
Vector3 ndc = SWWMUtility.ProjectPoint(sb.projdata,e.ViewPos+tdir);
if ( ndc.z >= 1. ) return;
Vector2 vpos = SWWMUtility.NDCToViewport(sb.projdata,ndc);
double frametime = (MSTimeF()-prevframe)/1000.;
double theta = clamp(15.*frametime,0.,1.); // naive, but whatever
if ( !prevframe || (lagvpos == (0,0)) ) lagvpos = vpos;
else lagvpos = SWWMUtility.LerpVector2(lagvpos,vpos,theta);
Vector2 oldvpos;
for ( int i=0; i<25; i++ )
{
tdir = level.Vec3Diff(e.ViewPos,cpos25[i]);
// project
ndc = SWWMUtility.ProjectPoint(sb.projdata,e.ViewPos+tdir);
if ( ndc.z >= 1. ) return;
vpos = SWWMUtility.NDCToViewport(sb.projdata,ndc);
oldvpos = lagvpos25[i];
if ( !prevframe || (lagvpos25[i] == (0,0)) ) oldvpos = lagvpos25[i] = vpos;
else lagvpos25[i] = SWWMUtility.LerpVector2(lagvpos25[i],vpos,theta);
int streak = int(max(abs(oldvpos.x-lagvpos25[i].x),abs(oldvpos.y-lagvpos25[i].y)));
for ( int j=0; j<streak; j++ ) Screen.DrawTexture(ctex,false,int(SWWMUtility.lerp(oldvpos.x,lagvpos25[i].x,j/double(streak))),int(SWWMUtility.lerp(oldvpos.y,lagvpos25[i].y,j/double(streak))),DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol,DTA_Alpha,(j*.5)/streak);
Screen.DrawTexture(ctex,false,int(lagvpos25[i].x),int(lagvpos25[i].y),DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol25[i]);
}
prevframe = MSTimeF();
}
ui Vector3, Color TraceForCrosshair25( int i )
{
if ( !ctr ) ctr = new("SWWMCrosshairTracer");
ctr.ignoreme = Owner;
Vector3 x, y, z, ofs;
double s;
[x, y, z] = swwm_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
ofs = GetTraceOffset25(i);
Vector3 origin = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),ofs.x*x+ofs.y*y+ofs.z*z);
ctr.Trace(origin,level.PointInSector(origin.xy),x,10000.,0);
Color col = crosshaircolor;
int chp = crosshairhealth;
if ( chp >= 2 )
{
int hp = Clamp(Owner.Health,0,200);
double sat = (hp<150)?1.:(1.-(hp-150)/100.);
Vector3 rgb = SWWMUtility.HSVtoRGB((hp/300.,sat,1.));
col = Color(int(rgb.x*255),int(rgb.y*255),int(rgb.z*255));
}
else if ( chp == 1 )
{
double hp = Clamp(Owner.Health,0,100)/100.;
if ( hp <= 0 ) col = Color(255,0,0);
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
else col = Color(0,255,0);
}
else if ( (ctr.Results.HitType == TRACE_HitActor) && ctr.Results.HitActor.bSHOOTABLE )
{
// show target health, rather than our own
double hp = ctr.Results.HitActor.Health/double(ctr.Results.HitActor.GetSpawnHealth());
if ( hp <= 0 ) col = Color(255,0,0);
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
else col = Color(0,255,0);
}
if ( ctr.Results.HitType == TRACE_HitNone ) return level.Vec3Offset(origin,x*10000.), col;
else return ctr.Results.HitPos, col;
}
clearscope Vector3 GetTraceOffset25( int i ) const
{
double t1 = 90-int(i%5)*72;
double t2 = 360-int(i/5)*72;
Vector2 b = (cos(t1),sin(t1))*1.2;
b.y += 3.;
Vector2 n = (b.x*cos(t2)-b.y*sin(t2),b.x*sin(t2)+b.y*cos(t2));
return (10.,3.5+n.x,-6.+n.y);
}
override bool UsesAmmo( Class<Ammo> kind )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
for ( int i=0; i<4; i++ ) if ( kind is types[i] ) return true;
return false;
}
override bool ReportHUDAmmo()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
for ( int i=0; i<4; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
for ( int i=0; i<25; i++ ) if ( loaded[i] && !fired[i] ) return true;
return false;
}
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
if ( (firemode == PrimaryFire) || (firemode == AltFire) )
{
for ( int i=0; i<4; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
for ( int i=0; i<25; i++ ) if ( loaded[i] && !fired[i] ) return true;
return false;
}
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
}
bool CanReload()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
for ( int i=0; i<4; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
return false;
}
override void AttachToOwner( Actor other )
{
if ( !initialized )
{
// first wallbuster has five barrels loaded
initialized = true;
for ( int i=0; i<25; i++ )
loaded[i] = (i<5)?(Class<Ammo>)("RedShell"):null;
for ( int i=0; i<25; i++ )
fired[i] = false;
}
Super.AttachToOwner(other);
}
override void DetachFromOwner()
{
A_StopSound(CHAN_AMBEXTRA+1);
A_StopSound(CHAN_AMBEXTRA+2);
Super.DetachFromOwner();
}
action void A_CBTFlash( StateLabel flashlabel = null, int index = 0 )
{
if ( !player || !player.ReadyWeapon )
return;
Weapon weap = player.ReadyWeapon;
State flashstate = null;
if ( !flashlabel )
{
if ( weap.bAltFire )
flashstate = weap.FindState('AltFlash');
if ( !flashstate )
flashstate = weap.FindState('Flash');
}
else flashstate = weap.FindState(flashlabel);
player.SetPSprite(PSP_FLASH-index,flashstate+index);
A_OverlayFlags(PSP_FLASH-index,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true);
A_OverlayRenderStyle(PSP_FLASH-index,STYLE_Add);
}
action void ProcessTraceHit( SpreadgunTracer t, Vector3 origin, Vector3 dir, int dmg, double mm, Class<Actor> impact = "SpreadImpact", int bc = 1, bool large = false, bool bust = false )
{
// Wall busting
if ( bust )
{
int bustdmg = dmg;
if ( t is 'SpreadSlugTracer' ) bustdmg = int(SpreadSlugTracer(t).penetration);
BusterWall.Bust(t.Results,bustdmg,self,t.Results.HitVector,t.Results.HitPos.z);
}
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(self,0,SPAC_PCross);
t.ShootThroughList[i].Activate(self,0,SPAC_Impact);
}
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Spawn(large?"InvisibleSplasher":"SmolInvisibleSplasher",t.WaterHitList[i].hitpos);
b.A_CheckTerrain();
}
for ( int i=5; i<t.Results.Distance; i+=10 )
{
if ( !Random[Boolet](0,bc) ) continue;
let b = Actor.Spawn("SWWMBubble",level.Vec3Offset(origin,dir*i));
b.Scale *= FRandom[Boolet](.1,.3);
}
for ( int i=0; i<t.HitList.Size(); i++ )
{
int realdmg = dmg?dmg:t.HitList[i].HitDamage;
let p = SWWMPuff.Setup(t.HitList[i].HitLocation,t.HitList[i].x,invoker,self,t.HitList[i].HitActor);
SWWMDamageAccumulator.Accumulate(t.HitList[i].HitActor,realdmg,p,self,'shot',!large&&!swwm_shotgib,DMG_INFLICTOR_IS_PUFF);
SWWMUtility.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),mm*FRandom[Wallbuster](0.4,1.2));
if ( t.HitList[i].HitActor.bNOBLOOD || t.HitList[i].HitActor.bINVULNERABLE || t.HitList[i].HitActor.bDORMANT )
{
let p = Spawn(impact,t.HitList[i].HitLocation);
p.angle = atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180;
p.pitch = asin(t.HitList[i].x.z);
p.special1 = max(0,(bc-5)/4);
}
else
{
t.HitList[i].HitActor.TraceBleed(realdmg,self);
t.HitList[i].HitActor.SpawnBlood(t.HitList[i].HitLocation,atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180,realdmg);
if ( large ) t.HitList[i].HitActor.A_StartSound("spreadgun/slugf",CHAN_DAMAGE,CHANF_OVERLAP,1.,2.);
else t.HitList[i].HitActor.A_StartSound("spreadgun/pelletf",CHAN_DAMAGE,CHANF_OVERLAP,.4,4.);
}
}
if ( (t.Results.HitType != TRACE_HitNone) && (t.Results.HitType != TRACE_HasHitSky) && (t.Results.HitType != TRACE_HitActor) )
{
Vector3 hitnormal = -t.Results.HitVector;
if ( t.Results.HitType == TRACE_HitFloor )
{
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.top.Normal;
else hitnormal = t.Results.HitSector.floorplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitCeiling )
{
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.bottom.Normal;
else hitnormal = t.Results.HitSector.ceilingplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitWall )
{
hitnormal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit();
if ( !t.Results.Side ) hitnormal *= -1;
}
if ( t.Results.HitLine ) t.Results.HitLine.RemoteActivate(self,t.Results.Side,SPAC_Impact,t.Results.HitPos);
let p = Spawn(impact,t.Results.HitPos+hitnormal*4);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
p.special1 = max(0,(bc-5)/4);
if ( t.Results.HitType == TRACE_HitFloor ) p.CheckSplash(40);
}
}
override Vector3 GetTraceOffset()
{
return (10.,3.5,-6.);
}
action void A_FireShells( int num = 1 )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
static const statelabel flashes[] = {"FlashRed","FlashGreen","FlashBlue","FlashPurple"};
static const String sounds[] = {"spreadgun/redfire","spreadgun/greenfire","spreadgun/bluefire","spreadgun/purplefire"};
static const Color cols[] = {Color(40,255,192,64),Color(36,255,192,80),Color(48,32,176,255),Color(24,255,224,96)};
static const int louds[] = {800,1000,1100,1200,1400,600,2500};
int redflashstr = 0;
int blueflashstr = 0;
// speen
if ( num == 1 ) invoker.whichspin = 1;
else if ( num == 5 ) invoker.whichspin = 2;
else invoker.whichspin = 0;
// first pass, count fired rounds
int howmany = 0;
for ( int i=0; i<num; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
howmany++;
}
if ( howmany <= 0 )
{
A_StartSound("wallbuster/dryfire",CHAN_WEAPON,CHANF_OVERLAP);
player.SetPsprite(PSP_WEAPON,ResolveState("DryFire"));
return;
}
Vector3 x, y, z, origin, x2, y2, z2, dir;
double a, s;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
int alertness = 0;
// second pass, play the fire effects
int salts = 0;
for ( int i=0; i<num; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
int which = 0;
for ( int j=0; j<4; j++ )
{
if ( !(invoker.loaded[idx] is types[j]) ) continue;
which = j;
break;
}
double rfact = swwm_earbuster?.85:.6;
A_StartSound(sounds[which],CHAN_WEAPON,CHANF_OVERLAP,1./(howmany**rfact),.6-howmany*.004,1.-howmany*.015);
if ( which == 0 ) redflashstr = max(120,redflashstr+10);
else if ( which == 1 ) redflashstr = max(90,redflashstr+8);
else if ( which == 2 )
{
blueflashstr = max(160,blueflashstr+10);
salts++;
}
else if ( which == 3 ) redflashstr = max(60,redflashstr+6);
A_CBTFlash(flashes[which],i);
if ( alertness > louds[which] ) alertness += louds[which]/4;
else alertness += louds[which];
}
if ( salts >= 25 ) SWWMUtility.MarkAchievement("salt",player);
if ( howmany < 3 ) player.SetPsprite(PSP_WEAPON,ResolveState("FireOne"));
else if ( howmany < 15 ) player.SetPsprite(PSP_WEAPON,ResolveState("FireFive"));
else player.SetPsprite(PSP_WEAPON,ResolveState("FireTwentyFive"));
int qk = min(9,1+howmany/5);
int ql = min(25,6+howmany/2);
A_QuakeEx(qk,qk,qk,ql,0,8,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:qk*.15);
A_BumpFOV(1.-qk*.04);
A_AlertMonsters(swwm_uncapalert?0:alertness);
A_PlayerFire();
if ( redflashstr > 0 )
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = redflashstr;
l.target = self;
}
if ( blueflashstr > 0 )
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[0] = 96;
l.args[1] = 224;
l.args[2] = 255;
l.args[3] = blueflashstr;
l.target = self;
}
// third pass, actually fire them
Vector3 base = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+3.5*y-6*z);
SpreadgunTracer st;
SpreadSlugTracer sst;
for ( int i=0; i<num; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
int which = 0;
for ( int j=0; j<4; j++ )
{
if ( !(invoker.loaded[idx] is types[j]) ) continue;
which = j;
break;
}
invoker.fired[idx] = true;
double t1 = 90-int(i%5)*72;
double t2 = 360-int(i/5)*72;
Vector2 b = (cos(t1),sin(t1))*1.2;
b.y += 3.;
Vector2 n = (b.x*cos(t2)-b.y*sin(t2),b.x*sin(t2)+b.y*cos(t2));
origin = level.Vec3Offset(base,n.x*y+n.y*z);
invoker.curobt = types[which];
switch ( which )
{
case 1:
// slug
if ( !sst ) sst = new("SpreadSlugTracer");
sst.ignoreme = self;
sst.penetration = 240.;
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.002);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
sst.hitlist.Clear();
sst.shootthroughlist.Clear();
sst.waterhitlist.Clear();
sst.Trace(origin,level.PointInSector(origin.xy),dir,8000.,TRACE_HitSky);
ProcessTraceHit(sst,origin,dir,0,12000,"SlugImpact",1+howmany/8,true,(howmany>1));
for ( int i=0; i<(6-howmany/4); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .8;
s.alpha *= .3;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.);
}
for ( int i=0; i<(10-howmany/3); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .2;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
SWWMUtility.DoKnockback(self,-x,18000.);
break;
case 2:
// saltshot
for ( int j=0; j<(8-howmany/5); j++ )
{
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.15);
let b = Spawn("SaltBeam",level.Vec3Offset(origin,y*cos(a)*s+z*sin(a)*s));
b.target = self;
b.angle = atan2(x2.y,x2.x);
b.pitch = asin(-x2.z);
b.Stamina += howmany*16;
b.Accuracy += 2+howmany/5;
b.Args[1] = (howmany>1)?2:1; // came from wallbuster
}
for ( int i=0; i<(16-howmany/2); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.special1 = 1;
s.scale *= .9;
s.SetShade(Color(1,3,4)*Random[Wallbuster](32,63));
s.A_SetRenderStyle(.3,STYLE_AddShaded);
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
for ( int i=0; i<(20-howmany/2); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .3;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-2,2)+z*FRandom[Wallbuster](-2,2);
}
SWWMUtility.DoKnockback(self,-x,17000.);
break;
case 3:
// lead ball
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.005);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
let p = Spawn("TheBall",origin);
p.target = self;
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.vel = dir*p.speed*1.25;
p.special1 = (howmany>1)?2:1; // came from wallbuster
for ( int i=0; i<(8-howmany/4); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .6;
s.alpha *= .25;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.);
}
for ( int i=0; i<(8-howmany/4); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .2;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
SWWMUtility.DoKnockback(self,-x,7200.);
break;
default:
// buckshot
if ( !st ) st = new("SpreadgunTracer");
st.ignoreme = self;
// attempt to uniformize expected damage while reducing traces (mainly for performance)
int expecteddmg = 180;
int numshot = max(21-howmany,5);
int individualdmg = int(ceil(expecteddmg/double(numshot)));
for ( int j=0; j<numshot; j++ )
{
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.06);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
st.hitlist.Clear();
st.shootthroughlist.Clear();
st.waterhitlist.Clear();
st.Trace(origin,level.PointInSector(origin.xy),dir,8000.,TRACE_HitSky);
ProcessTraceHit(st,origin,dir,individualdmg,7000,bc:5+howmany/6,false,(howmany>1));
}
for ( int i=0; i<(16-howmany/2); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.special1 = 1;
s.scale *= .9;
s.alpha *= .3;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
for ( int i=0; i<(20-howmany/2); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .3;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-2,2)+z*FRandom[Wallbuster](-2,2);
}
SWWMUtility.DoKnockback(self,-x,12000.);
break;
}
}
}
action void A_SpinOne()
{
A_StartSound("wallbuster/spin",CHAN_WEAPON,CHANF_OVERLAP);
invoker.rotation[invoker.rotation[5]]++;
while ( invoker.rotation[invoker.rotation[5]] > 4 ) invoker.rotation[invoker.rotation[5]] -= 5;
}
action void A_SpinBig()
{
A_StartSound("wallbuster/spinbig",CHAN_WEAPON,CHANF_OVERLAP);
invoker.rotation[5]++;
while ( invoker.rotation[5] > 4 ) invoker.rotation[5] -= 5;
}
action void A_HandSpin()
{
A_StartSound("wallbuster/handspin",CHAN_WEAPON,CHANF_OVERLAP);
invoker.rotation[5]--;
while ( invoker.rotation[5] < 0 ) invoker.rotation[5] += 5;
}
action void A_OpenMenu()
{
invoker.reloadqueue.Clear();
invoker.waitreload = true;
if ( player == players[consoleplayer] )
Menu.SetMenu('WallbusterReloadMenu');
}
action void A_WaitMenu()
{
if ( invoker.waitreload ) return;
if ( invoker.clearout )
{
bool hasshells = false;
for ( int i=0; i<25; i++ )
{
if ( !invoker.loaded[i] || invoker.fired[i] ) continue;
hasshells = true;
break;
}
if ( hasshells ) player.SetPsprite(PSP_WEAPON,ResolveState("Detach"));
else player.SetPsprite(PSP_WEAPON,ResolveState("EndReload"));
}
else if ( (invoker.reloadqueue.Size() <= 0) ) player.SetPsprite(PSP_WEAPON,ResolveState("EndReload"));
else player.SetPsprite(PSP_WEAPON,ResolveState("Detach"));
}
action void A_StartDetachOverlays()
{
static const statelabel shells[] = {"DetachShell0","DetachShell1","DetachShell2","DetachShell3","DetachShell4"};
static const statelabel shellsfired[] = {"DetachShellF0","DetachShellF1","DetachShellF2","DetachShellF3","DetachShellF4"};
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] ) continue;
if ( invoker.fired[idx] )
A_Overlay(PSP_WEAPON+1+i,shellsfired[i]);
else A_Overlay(PSP_WEAPON+1+i,shells[i]);
}
A_StartSound("wallbuster/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_StartAttachOverlays()
{
static const statelabel shells[] = {"AttachShell0","AttachShell1","AttachShell2","AttachShell3","AttachShell4"};
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] ) continue;
A_Overlay(PSP_WEAPON+1+i,shells[i]);
}
A_StartSound("wallbuster/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_DropShells()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
static const Class<Actor> casetypes[] = {"RedShellCasing","GreenShellCasing","BlueShellCasing","PurpleShellCasing"};
for ( int i=0; i<5; i++ )
{
Class<Ammo> loaded = invoker.loaded[invoker.rotation[5]*5+i];
int which = -1;
for ( int j=0; j<4; j++ )
{
if ( loaded != types[j] ) continue;
which = j;
break;
}
if ( which != -1 )
{
if ( invoker.fired[invoker.rotation[5]*5+i] )
{
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-3*y-13*z);
let c = Spawn(casetypes[which],origin);
c.angle = angle;
c.pitch = pitch;
c.vel = x*FRandom[Junk](-.2,.2)+y*FRandom[Junk](-.2,.2)-(0,0,FRandom[Junk](2,3));
c.vel += vel*.5;
}
else
{
let amo = FindInventory(types[which]);
if ( !amo )
{
amo = Inventory(Spawn(types[which]));
amo.AttachToOwner(self);
amo.Amount = 0;
}
if ( (amo.Amount >= amo.MaxAmount) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
amo.CreateTossable(1);
amo.Amount++;
}
}
invoker.loaded[invoker.rotation[5]*5+i] = null;
invoker.fired[invoker.rotation[5]*5+i] = false;
}
invoker.rotation[invoker.rotation[5]] = 4;
}
action void A_LoadShell()
{
if ( invoker.reloadqueue.Size() <= 0 ) return;
Class<Ammo> toload = invoker.reloadqueue[invoker.reloadqueue.Size()-1];
invoker.reloadqueue.Pop();
invoker.loaded[invoker.rotation[5]*5+invoker.rotation[invoker.rotation[5]]] = toload;
if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
{
let am = FindInventory(toload);
if ( am && (am.Amount > 0) ) am.Amount--;
}
if ( (invoker.reloadqueue.Size() > 0) && (invoker.rotation[invoker.rotation[5]] > 0) )
invoker.rotation[invoker.rotation[5]]--;
A_StartSound("wallbuster/load",CHAN_WEAPON,CHANF_OVERLAP);
}
override bool PickupForAmmoSWWM( SWWMWeapon ownedWeapon )
{
bool good = Super.PickupForAmmoSWWM(ownedWeapon);
let Owner = ownedWeapon.Owner;
if ( (AmmoGive1 == 0) && initialized )
{
for ( int i=0; i<25; i++ )
{
if ( !loaded[i] || fired[i] ) continue;
let cur = Owner.FindInventory(loaded[i]);
if ( !cur )
{
cur = Inventory(Spawn(loaded[i]));
cur.Amount = 0;
cur.AttachToOwner(Owner);
}
// give the loaded shell (or drop)
if ( cur.Amount >= cur.MaxAmount ) cur.CreateTossable(1);
cur.Amount++;
good = true;
}
}
return good;
}
override void MarkPrecacheSounds()
{
Super.MarkPrecacheSounds();
MarkSound("wallbuster/select");
MarkSound("wallbuster/deselect");
MarkSound("wallbuster/dryfire");
MarkSound("wallbuster/spin");
MarkSound("wallbuster/spinbig");
MarkSound("wallbuster/unlock");
MarkSound("wallbuster/lock");
MarkSound("wallbuster/meleestart");
MarkSound("wallbuster/meleeend");
MarkSound("wallbuster/detach");
MarkSound("wallbuster/attach");
MarkSound("wallbuster/handspin");
MarkSound("wallbuster/load1");
MarkSound("wallbuster/load2");
MarkSound("wallbuster/load3");
MarkSound("wallbuster/load4");
MarkSound("wallbuster/smallbust1");
MarkSound("wallbuster/smallbust2");
MarkSound("wallbuster/smallbust3");
MarkSound("wallbuster/bigbust1");
MarkSound("wallbuster/bigbust2");
MarkSound("wallbuster/bigbust3");
}
Default
{
//$Title Wallbuster
//$Group Weapons
//$Sprite graphics/HUD/Icons/W_Wallbuster.png
//$Icon weapon
Tag "$T_WALLBUSTER";
Inventory.PickupMessage "$I_WALLBUSTER";
Obituary "$O_WALLBUSTER_RED";
SWWMWeapon.Tooltip "$TT_WALLBUSTER";
SWWMWeapon.GetLine "getwallbuster";
Inventory.Icon "graphics/HUD/Icons/W_Wallbuster.png";
Weapon.SlotNumber 4;
Weapon.SelectionOrder 400;
Weapon.UpSound "wallbuster/select";
Weapon.AmmoType1 "RedShell";
Weapon.AmmoGive1 5;
SWWMWeapon.DropAmmoType "Shell";
Stamina 35000;
+SWWMWEAPON.NOFIRSTGIVE;
Radius 30;
Height 36;
}
States
{
Spawn:
XZW1 A -1;
Stop;
Select:
XZW2 G 1 A_FullRaise();
XZW2 HIJKLMNOP 2;
Goto Ready;
Ready:
XZW2 A 1
{
int flg = WRF_ALLOWUSER1;
if ( invoker.CheckAmmo(PrimaryFire,false) ) flg |= WRF_ALLOWZOOM;
if ( invoker.CanReload() ) flg |= WRF_ALLOWRELOAD;
A_WeaponReady(flg);
if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK|BT_ZOOM) )
invoker.CheckAmmo(EitherFire,true);
}
Wait;
Fire:
XZW2 A 0 A_FireShells(1);
Goto FireOne;
AltFire:
XZW2 A 0 A_FireShells(5);
Goto FireFive;
Zoom:
XZW2 A 0 A_FireShells(25);
Goto FireTwentyFive;
FireOne:
XZW2 AQRSTUVWX 1;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
FireFive:
XZW2 A 2;
XZW3 CDEFGHIJK 2;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
FireTwentyFive:
XZW2 A 2;
XZW3 STUVWXYZ 2;
XZW4 ABC 2;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
DryFire:
XZW2 A 2;
XZW2 XA 4;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
OneSpin:
XZW2 A 1 A_SpinOne();
XZW2 YZ 2;
XZW3 A 2;
XZW3 B 0;
XZW2 A 0 A_JumpIf(invoker.rotation[invoker.rotation[5]]==0,"BigSpin");
Goto Ready;
BigSpin:
XZW2 A 1 A_SpinBig();
XZW3 LMNOPQ 2;
XZW3 R 0;
XZW2 A 0;
Goto Ready;
Reload:
XZW2 A 2 A_StartSound("wallbuster/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 DEFG 2;
XZW4 HIJK 1;
XZW4 L 1 A_StartSound("wallbuster/unlock",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 MNOPQ 1;
XZW4 RSTUV 1;
XZW4 W 1 A_OpenMenu();
XZW4 W 1 A_WaitMenu();
Wait;
Detach:
XZW4 W 0
{
invoker.rnum = invoker.reloadqueue.Size();
invoker.cancelreload = false;
A_Overlay(-9999,"CheckCancelReload");
// clearing out?
if ( invoker.clearout )
{
// jump to spin if current group is empty
bool hasshells = false;
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
hasshells = true;
break;
}
if ( !hasshells ) return ResolveState("DetachSpin");
return ResolveState(null);
}
// if it's a full rotation, don't hand-spin
if ( invoker.rnum > 20 ) return ResolveState(null);
// jump to spin if current group is full
int nset = 0, nsetp = 0;
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
nset++;
}
// but previous isn't
for ( int i=20; i<25; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
nsetp++;
}
return A_JumpIf((nset>=5)&&(nsetp<5),"DetachSpin");
}
XZW4 WXYZ 1;
XZW5 ABCDEF 1;
XZW5 G 1 A_StartSound("wallbuster/detach",CHAN_WEAPON,CHANF_OVERLAP);
Goto DetachHold;
DetachSpin:
XZWE U 0;
XZWE VWXY 1;
DetachSpinReturn:
XZWE Z 1;
XZWF A 2
{
invoker.cancelreload = false;
A_Overlay(-9999,"CheckCancelReload");
A_HandSpin();
}
XZWF BCDEF 2;
XZWF G 0
{
if ( invoker.clearout )
{
// jump to spin again if current group is empty
bool hasshells = false;
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
hasshells = true;
break;
}
if ( !hasshells ) return ResolveState("DetachSpinAgain");
}
return ResolveState(null);
}
XZWF G 2;
XZWF J 1 A_StartSound("wallbuster/detach",CHAN_WEAPON,CHANF_OVERLAP);
XZWF K 0;
Goto DetachHold;
DetachSpinAgain:
XZWF G 2;
XZWF H 0;
XZWH JKL 1;
Goto DetachSpinReturn;
DetachHold:
XZW5 H 1
{
A_StartDetachOverlays();
A_PlayerReload();
return A_JumpIf(invoker.clearout,"DetachHoldClear");
}
XZW5 IJKLMNOPQ 1;
XZW5 R 2 A_DropShells();
XZW5 ST 2;
XZW5 V 2 A_LoadShell();
XZW5 X 2 A_LoadShell();
XZW5 Y 2 A_LoadShell();
XZW6 A 2 A_LoadShell();
XZW6 C 2 A_LoadShell();
XZW6 E 0
{
if ( invoker.cancelreload ) invoker.reloadqueue.Clear();
player.SetPSprite(-9999,null);
return A_JumpIf((invoker.reloadqueue.Size()>0),"AttachSpin");
}
Goto Attach;
DetachHoldClear:
XZW5 HIJKLMNOPQ 1;
XZW5 R 3 A_DropShells();
XZW5 TX 3;
XZW6 A 3;
XZW6 E 0
{
bool keepgoing = false;
// still have more shells?
for ( int i=0; i<25; i++ )
{
if ( !invoker.loaded[i] || invoker.fired[i] ) continue;
keepgoing = true;
break;
}
if ( invoker.cancelreload ) keepgoing = false;
player.SetPSprite(-9999,null);
return A_JumpIf(keepgoing,"AttachSpin");
}
Goto Attach;
CheckCancelReload:
TNT1 A 1
{
if ( player.cmd.buttons&BT_RELOAD )
invoker.cancelreload = true;
}
Wait;
DetachShell0:
XZWA EFGHIJKLM 1;
XZWA N 0;
Stop;
DetachShell1:
XZWB CDEFGHIJK 1;
XZWB L 0;
Stop;
DetachShell2:
XZWC ABCDEFGHI 1;
XZWC J 0;
Stop;
DetachShell3:
XZWC YZ 1;
XZWD ABCDEFG 1;
XZWD H 0;
Stop;
DetachShell4:
XZWD WXYZ 1;
XZWE ABCDE 1;
XZWE F 0;
Stop;
DetachShellF0:
XZWF LMNOPQRST 1;
XZWF U 0;
Stop;
DetachShellF1:
XZWF VWXYZ 1;
XZWG ABCD 1;
XZWG E 0;
Stop;
DetachShellF2:
XZWG FGHIJKLMN 1;
XZWG O 0;
Stop;
DetachShellF3:
XZWG PQRSTUVWX 1;
XZWG Y 0;
Stop;
DetachShellF4:
XZWG Z 1;
XZWH ABCDEFGH 1;
XZWH I 0;
Stop;
Attach:
XZW6 EF 2;
XZW6 G 1 A_StartAttachOverlays();
XZW6 HIJKL 1;
XZW6 M 1 A_StartSound("wallbuster/attach",CHAN_WEAPON,CHANF_OVERLAP);
XZW6 NOPQ 1;
XZW6 RS 2;
XZW6 TUVWXYZ 1;
XZW7 ABC 1;
Goto EndReload;
AttachSpin:
XZW7 JK 2;
XZW7 L 1 A_StartAttachOverlays();
XZW7 MNOPQR 1;
XZW7 S 1 A_StartSound("wallbuster/attach",CHAN_WEAPON,CHANF_OVERLAP);
XZW7 TUVW 1;
XZW7 XYZ 1;
XZW8 ABC 1;
XZW8 D 2
{
invoker.cancelreload = false;
A_Overlay(-9999,"CheckCancelReload");
A_HandSpin();
}
XZW8 EFGH 2;
XZW8 I 0
{
if ( invoker.clearout )
{
// jump to spin again if current group is empty
bool hasshells = false;
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
hasshells = true;
break;
}
if ( !hasshells ) return ResolveState("AttachSpinAgain");
}
return ResolveState(null);
}
XZW8 IL 2;
XZW8 M 1 A_StartSound("wallbuster/detach",CHAN_WEAPON,CHANF_OVERLAP);
XZW8 N 0;
Goto DetachHold;
AttachSpinAgain:
XZW8 IJ 2;
XZW8 K 1;
XZW8 K 0;
XZWH MNO 1;
Goto DetachSpinReturn;
AttachShell0:
XZWA OPQRSTUVWXYZ 1;
XZWB A 1;
XZWB B 0;
Stop;
AttachShell1:
XZWB MNOPQRSTUVWXY 1;
XZWB Z 0;
Stop;
AttachShell2:
XZWC KLMNOPQRSTUVW 1;
XZWC X 0;
Stop;
AttachShell3:
XZWD IJKLMNOPQRSTU 1;
XZWD V 0;
Stop;
AttachShell4:
XZWE GHIJKLMNOPQRS 1;
XZWE T 0;
Stop;
EndReload:
XZW4 W 1;
XZW8 OPQRS 1;
XZW8 T 1 A_StartSound("wallbuster/lock",CHAN_WEAPON,CHANF_OVERLAP);
XZW8 UVWXY 1;
XZW8 Z 1 A_StartSound("wallbuster/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW9 ABC 1;
XZW9 DEF 2;
Goto Ready;
User1:
XZW2 A 2
{
A_StartSound("wallbuster/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZW9 GHI 2;
XZW9 J 2 A_Parry(9);
XZW9 KLM 1;
XZW9 N 1 A_Melee(70,"demolitionist/whitl",1.2,1.4,1.3);
XZW9 OPQRS 2;
XZW9 T 2 A_StartSound("wallbuster/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW9 UVW 2;
XZW9 XYZ 1;
XZWA ABCD 2;
Goto Ready;
Deselect:
XZW2 A 2 A_StartSound("wallbuster/deselect",CHAN_WEAPON,CHANF_OVERLAP);
XZW2 BCDEFG 2;
XZW2 G -1 A_FullLower();
Stop;
FlashRed:
XZWW A 2 Bright;
Stop;
XZWW B 2 Bright;
Stop;
XZWW C 2 Bright;
Stop;
XZWW D 2 Bright;
Stop;
XZWW E 2 Bright;
Stop;
XZWW F 2 Bright;
Stop;
XZWW G 2 Bright;
Stop;
XZWW H 2 Bright;
Stop;
XZWW I 2 Bright;
Stop;
XZWW J 2 Bright;
Stop;
XZWW K 2 Bright;
Stop;
XZWW L 2 Bright;
Stop;
XZWW M 2 Bright;
Stop;
XZWW N 2 Bright;
Stop;
XZWW O 2 Bright;
Stop;
XZWW P 2 Bright;
Stop;
XZWW Q 2 Bright;
Stop;
XZWW R 2 Bright;
Stop;
XZWW S 2 Bright;
Stop;
XZWW T 2 Bright;
Stop;
XZWW U 2 Bright;
Stop;
XZWW V 2 Bright;
Stop;
XZWW W 2 Bright;
Stop;
XZWW X 2 Bright;
Stop;
XZWW Y 2 Bright;
Stop;
FlashGreen:
XZWX A 2 Bright;
Stop;
XZWX B 2 Bright;
Stop;
XZWX C 2 Bright;
Stop;
XZWX D 2 Bright;
Stop;
XZWX E 2 Bright;
Stop;
XZWX F 2 Bright;
Stop;
XZWX G 2 Bright;
Stop;
XZWX H 2 Bright;
Stop;
XZWX I 2 Bright;
Stop;
XZWX J 2 Bright;
Stop;
XZWX K 2 Bright;
Stop;
XZWX L 2 Bright;
Stop;
XZWX M 2 Bright;
Stop;
XZWX N 2 Bright;
Stop;
XZWX O 2 Bright;
Stop;
XZWX P 2 Bright;
Stop;
XZWX Q 2 Bright;
Stop;
XZWX R 2 Bright;
Stop;
XZWX S 2 Bright;
Stop;
XZWX T 2 Bright;
Stop;
XZWX U 2 Bright;
Stop;
XZWX V 2 Bright;
Stop;
XZWX W 2 Bright;
Stop;
XZWX X 2 Bright;
Stop;
XZWX Y 2 Bright;
Stop;
FlashBlue:
XZWY A 2 Bright;
Stop;
XZWY B 2 Bright;
Stop;
XZWY C 2 Bright;
Stop;
XZWY D 2 Bright;
Stop;
XZWY E 2 Bright;
Stop;
XZWY F 2 Bright;
Stop;
XZWY G 2 Bright;
Stop;
XZWY H 2 Bright;
Stop;
XZWY I 2 Bright;
Stop;
XZWY J 2 Bright;
Stop;
XZWY K 2 Bright;
Stop;
XZWY L 2 Bright;
Stop;
XZWY M 2 Bright;
Stop;
XZWY N 2 Bright;
Stop;
XZWY O 2 Bright;
Stop;
XZWY P 2 Bright;
Stop;
XZWY Q 2 Bright;
Stop;
XZWY R 2 Bright;
Stop;
XZWY S 2 Bright;
Stop;
XZWY T 2 Bright;
Stop;
XZWY U 2 Bright;
Stop;
XZWY V 2 Bright;
Stop;
XZWY W 2 Bright;
Stop;
XZWY X 2 Bright;
Stop;
XZWY Y 2 Bright;
Stop;
FlashPurple:
XZWZ A 2 Bright;
Stop;
XZWZ B 2 Bright;
Stop;
XZWZ C 2 Bright;
Stop;
XZWZ D 2 Bright;
Stop;
XZWZ E 2 Bright;
Stop;
XZWZ F 2 Bright;
Stop;
XZWZ G 2 Bright;
Stop;
XZWZ H 2 Bright;
Stop;
XZWZ I 2 Bright;
Stop;
XZWZ J 2 Bright;
Stop;
XZWZ K 2 Bright;
Stop;
XZWZ L 2 Bright;
Stop;
XZWZ M 2 Bright;
Stop;
XZWZ N 2 Bright;
Stop;
XZWZ O 2 Bright;
Stop;
XZWZ P 2 Bright;
Stop;
XZWZ Q 2 Bright;
Stop;
XZWZ R 2 Bright;
Stop;
XZWZ S 2 Bright;
Stop;
XZWZ T 2 Bright;
Stop;
XZWZ U 2 Bright;
Stop;
XZWZ V 2 Bright;
Stop;
XZWZ W 2 Bright;
Stop;
XZWZ X 2 Bright;
Stop;
XZWZ Y 2 Bright;
Stop;
}
}