// 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 { bool loaded[25]; bool fired[25]; int rotation[6]; int whichspin; transient bool cancelreload; override void DoEffect() { Super.DoEffect(); // push back selection order if weapon is unloaded if ( !bInitialized ) { SelectionOrder = default.SelectionOrder; return; } for ( int i=0; i<25; i++ ) { if ( !loaded[i] || fired[i] ) continue; SelectionOrder = default.SelectionOrder; return; } SelectionOrder = 1400; } override bool ReportHUDAmmo() { for ( int i=0; i<25; i++ ) if ( loaded[i] && !fired[i] ) return true; return Super.ReportHUDAmmo(); } override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount ) { if ( (firemode == PrimaryFire) || (firemode == AltFire) ) { for ( int i=0; i<25; i++ ) if ( loaded[i] && !fired[i] ) return true; return (Ammo1.Amount > 0); } return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount); } override void InitializeWeapon() { // first wallbuster has five barrels loaded for ( int i=0; i<25; i++ ) loaded[i] = (i<5); for ( int i=0; i<25; i++ ) fired[i] = false; } 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 impact = "SpreadImpact", int bc = 1, bool bust = false ) { // Wall busting if ( bust || swwm_omnibust ) { int bustdmg = dmg; BusterWall.Bust(t.Results,bustdmg,self,t.Results.HitVector,t.Results.HitPos.z); } foreach ( l:t.ShootThroughList ) { l.Activate(self,0,SPAC_PCross); l.Activate(self,0,SPAC_Impact); } foreach ( w:t.WaterHitList ) { let b = Spawn("SmolInvisibleSplasher",w.hitpos); b.target = self; b.A_CheckTerrain(); } for ( int i=5; i 0) ) { player.SetPSprite(PSP_WEAPON,ResolveState("Reload")); return; } // 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 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] = SWWMUtility.GetPlayerAxes(self); [x2, y2, z2] = SWWMUtility.GetPlayerAxesAutoAimed(self); int flashstr = 0; int alertness = 0; // second pass, play the fire effects for ( int i=0; i 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; A_StartSound("spreadgun/redfire",CHAN_WEAPON,CHANF_OVERLAP,1./(howmany**.65),.6-howmany*.004,1.-howmany*.015); flashstr = max(120,flashstr+10); A_CBTFlash("FlashRed",i); if ( alertness > 800 ) alertness += 200; else alertness += 800; } if ( howmany < 3 ) { player.SetPsprite(PSP_WEAPON,ResolveState("FireOne")); SWWMHandler.DoFlash(self,Color(40,255,192,64),5); } else if ( howmany < 15 ) { player.SetPsprite(PSP_WEAPON,ResolveState("FireFive")); SWWMHandler.DoFlash(self,Color(60,255,192,64),6); } else { player.SetPsprite(PSP_WEAPON,ResolveState("FireTwentyFive")); SWWMHandler.DoFlash(self,Color(80,255,192,64),7); } 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 ( flashstr > 0 ) { let l = Spawn("SWWMWeaponLight",pos); l.args[3] = flashstr; l.target = self; } // third pass, actually fire them Vector3 base = SWWMUtility.GetFireOffset(self,10,3.5,-6); SpreadgunTracer st; for ( int i=0; i 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; invoker.fired[idx] = true; double t1 = 90-int(i%5)*72; double t2 = 360-int(i/5)*72; Vector2 b = AngleToVector(t1,1.2); b.y += 3.; Vector2 n = RotateVector(b,t2); origin = level.Vec3Offset(base,n.x*y+n.y*z); // the shooting if ( !st ) st = new("SpreadgunTracer"); st.ignoreme = self; // attempt to uniformize expected damage while reducing traces (mainly for performance) int expecteddmg = 200; int numshot = max(21-howmany,5); int individualdmg = int(ceil(expecteddmg/double(numshot))); for ( int j=0; j1)); } 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.); } } 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_StartDetachOverlays() { 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] ) A_ChangeModel("",1,"","",4+i,"models","",CMDL_USESURFACESKIN,-1); else if ( invoker.fired[idx] ) A_ChangeModel("",1,"","",4+i,"models","ShellNormal_Used.png",CMDL_USESURFACESKIN,-1); else A_ChangeModel("",1,"","",4+i,"models","ShellNormal.png",CMDL_USESURFACESKIN,-1); } A_StartSound("wallbuster/meleestart",CHAN_WEAPON,CHANF_OVERLAP); } action void A_StartAttachOverlays() { 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] ) A_ChangeModel("",1,"","",4+i,"models","",CMDL_USESURFACESKIN,-1); else A_ChangeModel("",1,"","",4+i,"models","ShellNormal.png",CMDL_USESURFACESKIN,-1); } A_StartSound("wallbuster/meleeend",CHAN_WEAPON,CHANF_OVERLAP); } action void A_DropShells() { for ( int i=0; i<5; i++ ) { if ( invoker.loaded[invoker.rotation[5]*5+i] ) { if ( invoker.fired[invoker.rotation[5]*5+i] ) { Vector3 x, y, z; [x, y, z] = SWWMUtility.GetPlayerAxes(self); Vector3 origin = SWWMUtility.GetFireOffset(self,10,-3,-13); let c = Spawn("RedShellCasing",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 { if ( (invoker.Ammo1.Amount >= invoker.Ammo1.MaxAmount) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) invoker.BufferAmmo(invoker.AmmoType1,1); else invoker.Ammo1.Amount = min(invoker.Ammo1.Amount+1,invoker.Ammo1.MaxAmount); } } invoker.loaded[invoker.rotation[5]*5+i] = null; invoker.fired[invoker.rotation[5]*5+i] = false; A_ChangeModel("",1,"","",4+i,"models","",CMDL_USESURFACESKIN,-1); } invoker.rotation[invoker.rotation[5]] = 4; } action int GetShellsToLoad() { int cnt = 25; for ( int i=0; i<25; i++ ) cnt -= (invoker.loaded[i]&&!invoker.fired[i]); if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) { int realammo = invoker.CountBufferedAmmo(invoker.AmmoType1)+invoker.Ammo1.Amount; cnt = min(cnt,realammo); } return max(0,cnt); } action void A_LoadShell() { int toload = GetShellsToLoad(); if ( toload <= 0 ) return; invoker.loaded[invoker.rotation[5]*5+invoker.rotation[invoker.rotation[5]]] = true; if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) && !invoker.FetchBufferedAmmo(invoker.AmmoType1,1) ) { let am = FindInventory(invoker.AmmoType1); if ( am && (am.Amount > 0) ) am.Amount--; } if ( (toload > 1) && (invoker.rotation[invoker.rotation[5]] > 0) ) // keep spinning if there's more to load 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) && bInitialized ) { for ( int i=0; i<25; i++ ) { if ( !loaded[i] || fired[i] ) continue; let cur = Owner.FindInventory(AmmoType1); if ( !cur ) { cur = Inventory(Spawn(AmmoType1)); 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 { Tag "$T_WALLBUSTER"; Inventory.PickupMessage "$I_WALLBUSTER"; Obituary "$O_WALLBUSTER"; SWWMWeapon.Tooltip "$TT_WALLBUSTER"; SWWMWeapon.GetLine "getwallbuster"; SWWMWeapon.NumCrosshairs 25; Weapon.SlotNumber 4; Weapon.SelectionOrder 400; Weapon.UpSound "wallbuster/select"; Weapon.AmmoType1 "RedShell"; Weapon.AmmoGive1 5; SWWMWeapon.DropAmmoType "SWWMShellAmmoBig"; Stamina 35000; +SWWMWEAPON.NOFIRSTGIVE; } 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 ( GetShellsToLoad() ) 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; Goto Detach; Detach: XZW4 W 0 { int rnum = GetShellsToLoad(); invoker.cancelreload = false; A_Overlay(-9999,"CheckCancelReload"); // if it's a full rotation, don't hand-spin if ( 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 UVWXY 1; DetachSpinReturn: XZWE Z 1; XZWF A 2 { invoker.cancelreload = false; A_Overlay(-9999,"CheckCancelReload"); A_HandSpin(); } XZWF BCDEFG 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(); } 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 { int rnum = invoker.cancelreload?0:GetShellsToLoad(); player.SetPSprite(-9999,null); return A_JumpIf(rnum,"AttachSpin"); } Goto Attach; CheckCancelReload: TNT1 A 1 { if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK|BT_RELOAD|BT_ZOOM|BT_USER1) ) invoker.cancelreload = true; } Wait; 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 EFGHIL 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; EndReload: XZW4 W 1 { invoker.ClearBufferedAmmo(); for ( int i=0; i<5; i++ ) A_ChangeModel("",1,"","",4+i,"models","",CMDL_USESURFACESKIN,-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; } }