// Blackmann "Rhino Stopper" Spreadgun (from Instant Action 3, also planned for Zanaveth Ultra Suite 2) // Slot 3, replaces Shotgun, Ethereal Crossbow, Serpent Staff Class SpreadgunTracer : LineTracer { Actor ignoreme; Array hitlist; Array shootthroughlist; Array waterhitlist; override ETraceStatus TraceCallback() { // liquid splashes if ( Results.CrossedWater ) { let hl = new("WaterHit"); hl.sect = Results.CrossedWater; hl.hitpos = Results.CrossedWaterPos; WaterHitList.Push(hl); } else if ( Results.Crossed3DWater ) { let hl = new("WaterHit"); hl.sect = Results.Crossed3DWater; hl.hitpos = Results.Crossed3DWaterPos; WaterHitList.Push(hl); } if ( Results.HitType == TRACE_HitActor ) { if ( Results.HitActor == ignoreme ) return TRACE_Skip; if ( Results.HitActor.bSHOOTABLE ) { int amt = SWWMDamageAccumulator.GetAmount(Results.HitActor); // getgibhealth isn't clearscope and that's a problem int gibh = (Results.HitActor.GibHealth!=int.min)?-abs(Results.HitActor.GibHealth):-int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor); // if gibbed, go through without dealing more damage if ( Results.HitActor.health-amt <= gibh ) return TRACE_Skip; let ent = new("HitListEntry"); ent.hitactor = Results.HitActor; ent.hitlocation = Results.HitPos; ent.x = Results.HitVector; hitlist.Push(ent); // go right on through if dead if ( Results.HitActor.health-amt <= 0 ) return TRACE_Skip; // stap return TRACE_Stop; } 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; ShootThroughList.Push(Results.HitLine); return TRACE_Skip; } return TRACE_Stop; } } Class Spreadgun : SWWMWeapon { bool fired; // shell was used bool wasfired; // for hammer priming bool chambered; // a shell is actually loaded bool goldload; // a golden shell is loaded (buckshot otherwise) bool wasgold; // for what ammo/shell to drop bool loadgold; // hint for switching to gold shells on next load override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) { if ( goldload ) return StringTable.Localize("$O_SPREADGUN_GOLD"); return Super.GetObituary(victim,inflictor,mod,playerattack); } override void DoEffect() { Super.DoEffect(); // push back selection order if weapon is unloaded (or has a golden shell loaded) if ( !bInitialized ) SelectionOrder = default.SelectionOrder; else SelectionOrder = (chambered&&!fired&&!goldload)?default.SelectionOrder:1500; } override bool ReportHUDAmmo() { if ( !fired && chambered ) return true; return Super.ReportHUDAmmo(); } override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount ) { if ( (firemode == PrimaryFire) || (firemode == EitherFire) ) { if ( !fired && chambered ) return true; if ( (Ammo1.Amount > 0) || (Ammo2.Amount > 0) ) return true; if ( autoswitch ) PlayerPawn(Owner).PickNewWeapon(null); return false; } return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount); } action void A_SelectUnloadState() { if ( invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState("UnloadFired")); else player.SetPSprite(PSP_WEAPON,invoker.FindState("Unload")); if ( invoker.chambered ) { A_ChangeModel("",1,"","",4,"models",String.Format("Shell_%s%s.png",invoker.wasgold?"Gold":"Normal",invoker.fired?"_Used":""),CMDL_USESURFACESKIN,-1); A_Overlay(-9999,"UnloadDummy"); } else { A_ChangeModel("",1,"","",4,"models","",CMDL_USESURFACESKIN,-1); A_Overlay(-9999,"UnloadDummyEmpty"); } A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP); } action void A_SelectLoadState() { invoker.wasfired = false; A_ChangeModel("",1,"","",4,"models","",CMDL_USESURFACESKIN,-1); A_ChangeModel("",1,"","",5,"models",String.Format("Shell_%s.png",invoker.loadgold?"Gold":"Normal"),CMDL_USESURFACESKIN,-1); if ( invoker.fired ) { invoker.wasfired = true; player.SetPSprite(PSP_WEAPON,invoker.FindState("LoadFired")); } else player.SetPSprite(PSP_WEAPON,invoker.FindState("Load")); A_Overlay(-9999,"LoadDummy"); } action void A_DropShell() { if ( !invoker.fired ) { let amo = invoker.wasgold?invoker.Ammo2:invoker.Ammo1; let amotype = invoker.wasgold?invoker.AmmoType2:invoker.AmmoType1; if ( (amo.Amount >= amo.MaxAmount) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) invoker.BufferAmmo(amotype,1); else amo.Amount = min(amo.Amount+1,amo.MaxAmount); return; } Vector3 x, y, z; [x, y, z] = SWWMUtility.GetPlayerAxes(self); Vector3 origin = SWWMUtility.GetFireOffset(self,10,0,-10); let c = Spawn(invoker.wasgold?"GoldShellCasing":"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; } action void ProcessTraceHit( SpreadgunTracer t, Vector3 origin, Vector3 dir, int dmg, double mm, Class impact = "SpreadImpact", int bc = 1 ) { if ( swwm_omnibust ) { // Wall busting 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 ) amo.Amount--; } invoker.chambered = true; invoker.fired = false; invoker.ClearBufferedAmmo(); } action void A_Prime() { if ( invoker.fired || invoker.wasfired ) A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP); A_ChangeModel("",1,"","",5,"models","",CMDL_USESURFACESKIN,-1); } action State A_SpreadgunReady() { int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1|WRF_NOSECONDARY; if ( ((invoker.Ammo1.Amount > 0) || sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) && (invoker.fired || !invoker.chambered || invoker.goldload) ) flg |= WRF_ALLOWRELOAD; if ( ((invoker.Ammo2.Amount > 0) || sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) && (invoker.fired || !invoker.chambered || !invoker.goldload) ) flg &= ~WRF_NOSECONDARY; if ( ((invoker.Ammo1.Amount <= 0) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true)) && (invoker.fired || !invoker.chambered) ) flg |= WRF_NOPRIMARY; A_WeaponReady(flg); if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK) ) invoker.CheckAmmo(EitherFire,true); return ResolveState(null); } override bool PickupForAmmoSWWM( SWWMWeapon ownedWeapon ) { bool good = Super.PickupForAmmoSWWM(ownedWeapon); let Owner = ownedWeapon.Owner; if ( (AmmoGive1 == 0) && !fired && chambered ) { let loadammo = goldload?AmmoType2:AmmoType1; let cur = Owner.FindInventory(loadammo); if ( !cur ) { cur = Inventory(Spawn(loadammo)); 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 InitializeWeapon() { fired = false; chambered = true; goldload = false; } override void ModifyDropAmount( int dropamount ) { Super.ModifyDropAmount(dropamount); // toss some ammo while we're at it if ( Random[Spreadgun](0,1) ) A_DropItem("RedShell",Random[Spreadgun](1,2)); } action void A_UpdatePickup() { frame = invoker.fired; } override void MarkPrecacheSounds() { Super.MarkPrecacheSounds(); MarkSound("spreadgun/open"); MarkSound("spreadgun/hammer"); MarkSound("spreadgun/close"); MarkSound("spreadgun/shellin"); MarkSound("spreadgun/select"); MarkSound("spreadgun/deselect"); MarkSound("spreadgun/redfire1"); MarkSound("spreadgun/redfire2"); MarkSound("spreadgun/goldfire1"); MarkSound("spreadgun/goldfire2"); MarkSound("spreadgun/checkgun"); MarkSound("spreadgun/casing1"); MarkSound("spreadgun/casing2"); MarkSound("spreadgun/casing3"); MarkSound("spreadgun/casing4"); MarkSound("spreadgun/casing5"); MarkSound("spreadgun/casing6"); MarkSound("spreadgun/gcasing1"); MarkSound("spreadgun/gcasing2"); MarkSound("spreadgun/gcasing3"); MarkSound("spreadgun/pellet1"); MarkSound("spreadgun/pellet2"); MarkSound("spreadgun/pellet3"); MarkSound("spreadgun/pellet4"); MarkSound("spreadgun/pellet5"); MarkSound("spreadgun/pellet6"); MarkSound("spreadgun/pellet7"); MarkSound("spreadgun/pellet8"); MarkSound("spreadgun/pelletf1"); MarkSound("spreadgun/pelletf2"); MarkSound("spreadgun/pelletf3"); MarkSound("spreadgun/pelletf4"); MarkSound("spreadgun/pelletf5"); MarkSound("spreadgun/pelletf6"); MarkSound("spreadgun/goldexpl1"); MarkSound("spreadgun/goldexpl2"); } Default { Tag "$T_SPREADGUN"; Inventory.PickupMessage "$I_SPREADGUN"; Obituary "$O_SPREADGUN"; SWWMWeapon.Tooltip "$TT_SPREADGUN"; SWWMWeapon.GetLine "getspreadgun"; Weapon.UpSound "spreadgun/select"; Weapon.SlotNumber 3; Weapon.SelectionOrder 500; Weapon.AmmoType1 "RedShell"; Weapon.AmmoGive1 1; Weapon.AmmoType2 "GoldShell"; Weapon.AmmoGive2 0; SWWMWeapon.DropAmmoType "SWWMShellAmmoSmall"; Stamina 15000; +SWWMWEAPON.NOFIRSTGIVE; } States { Spawn: XZW1 A -1 NoDelay A_UpdatePickup(); Stop; Deselect: XZW2 A 1 { A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP); return A_JumpIf(invoker.fired,"DeselectFired"); } XZW2 BCDEFGHI 1; XZW2 I -1 A_FullLower(); Stop; DeselectFired: XZW2 Z 1; XZW3 ABCDEFGH 1; XZW3 H -1 A_FullLower(); Stop; Select: XZW2 I 1 { A_FullRaise(); return A_JumpIf(invoker.fired,"SelectFired"); } XZW2 JKLMNOPQ 1; Goto Ready; SelectFired: XZW3 HIJKLMNOP 1; Goto ReadyFired; Ready: XZW2 A 1 A_SpreadgunReady(); Wait; ReadyFired: XZW2 Z 1 A_SpreadgunReady(); Wait; Fire: XZW2 A 1 { if ( invoker.fired || !invoker.chambered ) return ResolveState("Reload"); A_FireShell(); return ResolveState(null); } XZW2 RSTU 1; XZW2 VWXY 2; Goto ReadyFired; AltFire: #### # -1 { A_PlayerReload(); invoker.wasgold = invoker.goldload; invoker.loadgold = true; A_SelectUnloadState(); } Stop; Reload: #### # -1 { A_PlayerReload(); invoker.wasgold = invoker.goldload; invoker.loadgold = false; A_SelectUnloadState(); } Stop; UnloadDummy: // overlay with shared functions for all unload anims TNT1 A 11; TNT1 A 14 { invoker.chambered = false; A_StartSound("spreadgun/open",CHAN_WEAPON,CHANF_OVERLAP); } TNT1 A 1 A_DropShell(); Stop; UnloadDummyEmpty: TNT1 A 11; TNT1 A 14 A_StartSound("spreadgun/open",CHAN_WEAPON,CHANF_OVERLAP); Stop; UnloadFired: XZW2 Z 2; XZW3 QRST 2; XZW3 UVWXYZ 1; XZW4 ABCDEFGH 1; XZW4 I 1; Goto Reload2; Unload: XZW2 A 2; XZW9 PQRS 2; XZW9 TUVWXYZ 1; XZWA ABCDEFG 1; XZWA H 1; Goto Reload2; Reload2: #### # -1 A_SelectLoadState(); Stop; LoadDummy: TNT1 A 9; TNT1 A 12 A_LoadShell(); TNT1 A 2 A_StartSound("spreadgun/close",CHAN_WEAPON,CHANF_OVERLAP); TNT1 A 2 A_Prime(); TNT1 A 1 { invoker.PlayUpSound(self); } Stop; LoadDummyEmpty: TNT1 A 9; TNT1 A 2 A_StartSound("spreadgun/close",CHAN_WEAPON,CHANF_OVERLAP); TNT1 A 2 A_Prime(); TNT1 A 1 { invoker.PlayUpSound(self); } Stop; LoadFired: XZW4 IJKLMNOPQRSTUVWXYZ 1; XZW5 ABCDEFGHIJKLMNO 1; Goto Ready; Load: XZWA HIJKLMNOPQRSTUVWXYZ 1; XZWB ABCDEFGHIJKLMN 1; Goto Ready; Zoom: XZW2 A 1 { A_StartSound("spreadgun/checkgun",CHAN_WEAPON,CHANF_OVERLAP); A_PlayerCheckGun(); return A_JumpIf(invoker.fired,"ZoomFired"); } XZW5 PQRSTUVWXYZ 1; XZW6 ABCDEFGHI 2; XZW6 JKLMNO 1; Goto Ready; ZoomFired: XZW2 Z 1; XZW8 CDEFGHIJKLM 1; XZW8 NOPQRSTUV 2; XZW8 WXYZ 1; XZW9 AB 1; Goto ReadyFired; DummyMelee: TNT1 A 3 { A_Parry(9); A_PlayerMelee(true); } TNT1 A 1 A_Melee(); Stop; User1: XZW2 A 2 { A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP); return A_JumpIf(invoker.fired,"User1Fired"); } XZW7 PQ 2; User1Hold: XZW7 R 1 { A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP); A_Overlay(-9999,"DummyMelee"); } XZW7 STUV 2; XZW7 WX 3; XZW7 Y 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1Hold"); XZW7 Y 0 { invoker.PlayUpSound(self); } XZW7 Y 2; XZW7 Z 2; XZW8 AB 2; Goto Ready; User1Fired: XZW2 Z 2; XZW9 CD 2; User1FiredHold: XZW9 E 1 { A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP); A_Overlay(-9999,"DummyMelee"); } XZW9 FGHI 2; XZW9 JK 3; XZW9 L 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1FiredHold"); XZW9 L 0 { invoker.PlayUpSound(self); } XZW9 LMNO 2; Goto ReadyFired; FlashRed: XZW0 A 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[3] = 120; l.target = self; } Stop; FlashGold: XZW0 B 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[3] = 300; l.target = self; } Stop; } }