// Blackmann "Rhino Stopper" Spreadgun (from Instant Action 3, also planned for Zanaveth Ultra Suite 2) // Slot 3, replaces Shotgun, Ethereal Crossbow, Serpent Staff Class RedShellCasing : SWWMCasing { Default { BounceSound "spreadgun/casing"; } override void PostBeginPlay() { Super.PostBeginPlay(); heat = 0; } } Class GreenShellCasing : RedShellCasing {} Class WhiteShellCasing : RedShellCasing {} Class BlueShellCasing : RedShellCasing {} Class BlackShellCasing : RedShellCasing {} Class PurpleShellCasing : RedShellCasing {} Class GoldShellCasing : RedShellCasing { Default { BounceSound "spreadgun/gcasing"; } } 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, fuck int gibhealth = -int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor); if ( Results.HitActor.GibHealth != int.min ) gibhealth = -abs(Results.HitActor.GibHealth); // if gibbed, go through without dealing more damage if ( Results.HitActor.health-amt <= gibhealth ) 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 SpreadSlugTracer : SpreadgunTracer { double penetration; // please don't laugh 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 ) { let ent = new("HitListEntry"); ent.hitactor = Results.HitActor; ent.hitlocation = Results.HitPos; ent.x = Results.HitVector; if ( (Results.HitActor.Health >= int(penetration)) || Results.HitActor.bNODAMAGE ) { ent.hitdamage = int(penetration); penetration = 0; } else { ent.hitdamage = min(Results.HitActor.health+int(Results.HitActor.GetSpawnHealth()*gameinfo.gibfactor),int(penetration)); penetration = max(0,penetration-ent.hitdamage); } hitlist.Push(ent); if ( penetration <= 0 ) return TRACE_Stop; 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; ShootThroughList.Push(Results.HitLine); return TRACE_Skip; } return TRACE_Stop; } } Class SpreadImpact : Actor { Default { Radius 0.1; Height 0; +NOGRAVITY; +NOCLIP; +DONTSPLASH; +NOTELEPORT; +NOINTERACTION; } override void PostBeginPlay() { Super.PostBeginPlay(); if ( swwm_extraalert ) A_AlertMonsters(swwm_uncapalert?0:200); A_StartSound("spreadgun/pellet",CHAN_VOICE,CHANF_DEFAULT,.4,4.); A_SprayDecal("TinyPock",-20); int numpt = Random[Spreadgun](2,4)-special1; Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); for ( int i=0; i0)?.1:.02); scale += initsc*.2; } States { Spawn: XFLM ABCDEFGHIJKLMNOPQRST -1 Bright; Stop; } } Class DragonBreathArm : Actor { Vector3 oldvel; Default { Obituary "$O_SPREADGUN_WHITE"; DamageType 'Fire'; PROJECTILE; +THRUACTORS; +BOUNCEONWALLS; +BOUNCEONFLOORS; +BOUNCEONCEILINGS; +USEBOUNCESTATE; +NODAMAGETHRUST; +FORCERADIUSDMG; -NOGRAVITY; Gravity 0.15; BounceFactor 1.0; Radius 4; Height 4; } override void PostBeginPlay() { Super.PostBeginPlay(); reactiontime = Random[ExploS](18,24); vel = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch))*FRandom[ExploS](16.,24.); } override void Tick() { oldvel = vel; Super.Tick(); } void A_HandleBounce() { Vector3 HitNormal = -vel.unit(); F3DFloor ff; if ( BlockingFloor ) { // find closest 3d floor for its normal for ( int i=0; i= (BlockingMobj.pos.x+BlockingMobj.radius) ) HitNormal = (1,0,0); else if ( (pos.y+radius) <= (BlockingMobj.pos.y-BlockingMobj.radius) ) HitNormal = (0,-1,0); else if ( (pos.y-radius) >= (BlockingMobj.pos.y+BlockingMobj.radius) ) HitNormal = (0,1,0); else if ( pos.z >= (BlockingMobj.pos.z+BlockingMobj.height) ) HitNormal = (0,0,1); else if ( (pos.z+height) <= BlockingMobj.pos.z ) HitNormal = (0,0,-1); } // undo the bounce, we need to hook in our own vel = oldvel; // re-do the bounce with our formula vel = .8*((vel dot HitNormal)*HitNormal*(-1.8+FRandom[Spreadgun](.0,.8))+vel); bHITOWNER = true; } States { Spawn: TNT1 A 1 { if ( waterlevel > 0 ) ReactionTime -= 2; A_CountDown(); let p = Spawn("DragonBreathPuff",pos); p.alpha *= .6+.4*(ReactionTime/20.); p.scale *= 3.5-2.5*(ReactionTime/20.); if ( !(ReactionTime%5) ) { let l = Spawn("PaletteLight",pos); l.Args[3] = int(90+50*(ReactionTime/20.)); l.ReactionTime = int(2+8*(ReactionTime/20.)); l.target = p; } if ( !(ReactionTime%2) ) SWWMUtility.DoExplosion(self,1+(reactiontime*1.5),1000+200*reactiontime,150-6*reactiontime,flags:DE_HOWL,ignoreme:bHITOWNER?null:target); double spd = vel.length(); vel = (vel*.4+(FRandom[ExploS](-.2,.2),FRandom[ExploS](-.2,.2),FRandom[ExploS](-.2,.2))).unit()*spd; Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,5); if ( !(ReactionTime%2) ) { let s = Spawn("SWWMHalfSmoke",pos); s.vel = pvel+vel*.2; s.SetShade(Color(1,1,1)*Random[ExploS](96,192)); s.special1 = Random[ExploS](2,4); s.scale *= 2.4; s.alpha *= .1+.2*(ReactionTime/20.); int numpt = Random[Spreadgun](-2,4); for ( int i=0; i 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 == ignore ) return TRACE_Skip; if ( Results.HitActor.bSHOOTABLE ) 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) ) return TRACE_Stop; ShootThroughList.Push(Results.HitLine); return TRACE_Skip; } return TRACE_Stop; } } Class SaltLight : PaletteLight { Default { Tag "SaltExpl,1"; ReactionTime 30; Args 0,0,0,240; } } Class SaltLight2 : PaletteLight { Default { Tag "SaltExpl"; ReactionTime 30; Args 0,0,0,70; } } Class SaltImpact : Actor { Default { Obituary "$O_SPREADGUN_BLUE"; DamageType "Electric"; RenderStyle "Add"; Radius 0.1; Height 0; Scale 1.8; +NOGRAVITY; +NOBLOCKMAP; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +NOTELEPORT; +FOILINVUL; +NOINTERACTION; } override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) { if ( args[0] >= 1 ) return StringTable.Localize("$O_WALLBUSTER_BLUE"); return Super.GetObituary(victim,inflictor,mod,playerattack); } override void PostBeginPlay() { Super.PostBeginPlay(); A_AlertMonsters(swwm_uncapalert?0:6000); SWWMUtility.DoExplosion(self,25+special2*5,15000,100,40); A_QuakeEx(3,3,3,10,0,250,"",QF_RELATIVE|QF_SCALEDOWN,falloff:150,rollintensity:0.2); A_StartSound("spreadgun/salt",CHAN_VOICE,attenuation:.35); A_SprayDecal("ShockMarkSmall",-172); A_SprayDecal("SaltMark",-172); Scale *= FRandom[ExploS](0.8,1.1); int numpt = Random[ExploS](5,9)-special1; for ( int i=0; i 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: TNT1 A 0 NoDelay A_Jump(256,"Expl1","Expl2","Expl3"); Expl1: KSX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright; Stop; Expl2: KSX2 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright; Stop; Expl3: KSX3 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright; Stop; } } Class SaltBeam : Actor { Default { Obituary "$O_SPREADGUN_BLUE"; DamageType "Electric"; RenderStyle "Add"; Radius 0.1; Height 0; Stamina 9; +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +NOTELEPORT; +ROLLSPRITE; +ROLLCENTER; +NODAMAGETHRUST; +FORCERADIUSDMG; +FOILINVUL; +NOINTERACTION; } override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) { if ( args[1] >= 1 ) return StringTable.Localize("$O_WALLBUSTER_BLUE"); return Super.GetObituary(victim,inflictor,mod,playerattack); } void SpreadOut() { special1 = 1; Vector3 x, y, z; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); let t = new("SaltTracer"); t.ignore = target; t.Trace(pos,cursector,x,32,TRACE_HitSky); for ( int i=0; i 20) && !Random[Spreadgun](0,800/args[0]) ) { let i = Spawn("SaltImpact",level.Vec3Offset(pos,x*32)); i.angle = atan2(x.y,x.x); i.pitch = asin(-x.z); i.target = target; i.special1 = (Stamina-9)/4; i.special2 = Accuracy; i.args[0] = args[1]; return; } // next beam if ( !(special2%4) && !Random[Spreadgun](0,Stamina/2) ) Spawn("SaltLight",level.Vec3Offset(pos,x*16)); let next = Spawn("SaltBeam",level.Vec3Offset(pos,x*32)); double a = FRandom[Spreadgun](0,360), s = FRandom[Spreadgun](0,.06); Vector3 dir = (x+y*cos(a)*s+z*sin(a)*s).unit(); next.angle = atan2(dir.y,dir.x); next.pitch = asin(-dir.z); next.target = target; next.special2 = (special2+1)%10; next.args[0] = args[0]+1; next.args[1] = args[1]; next.SetStateLabel("TrailSpawn"); } override void PostBeginPlay() { Super.PostBeginPlay(); if ( !Random[Spreadgun](0,3) ) A_StartSound("spreadgun/salttrail",CHAN_VOICE,CHANF_DEFAULT,.3,4.); } override void Tick() { if ( isFrozen() ) return; A_FadeOut(.04); if ( Random[Spreadgun](-2,args[2]/10) == 0 ) SWWMUtility.DoExplosion(self,5+Accuracy,5000,32,flags:DE_HOWL,ignoreme:target); if ( ((special2%5) || args[2]) && !special1 ) SpreadOut(); args[2]++; if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XZW1 A -1 Bright NoDelay { return FindState("StarterDev")+Random[Spreadgun](0,11)*2; } Stop; TrailSpawn: XZW2 A -1 Bright { return FindState("TrailerDev")+Random[Spreadgun](0,11)*2; } Stop; StarterDev: #### # 25 Bright; XZW1 B -1 Bright; Stop; #### # 25 Bright; XZW1 C -1 Bright; Stop; #### # 25 Bright; XZW1 D -1 Bright; Stop; #### # 25 Bright; XZW1 E -1 Bright; Stop; #### # 25 Bright; XZW1 F -1 Bright; Stop; #### # 25 Bright; XZW1 G -1 Bright; Stop; #### # 25 Bright; XZW1 H -1 Bright; Stop; #### # 25 Bright; XZW1 I -1 Bright; Stop; #### # 25 Bright; XZW1 J -1 Bright; Stop; #### # 25 Bright; XZW1 K -1 Bright; Stop; #### # 25 Bright; XZW1 L -1 Bright; Stop; #### # 25 Bright; XZW1 M -1 Bright; Stop; TrailerDev: #### # 25 Bright; XZW2 B -1 Bright; Stop; #### # 25 Bright; XZW2 C -1 Bright; Stop; #### # 25 Bright; XZW2 D -1 Bright; Stop; #### # 25 Bright; XZW2 E -1 Bright; Stop; #### # 25 Bright; XZW2 F -1 Bright; Stop; #### # 25 Bright; XZW2 G -1 Bright; Stop; #### # 25 Bright; XZW2 H -1 Bright; Stop; #### # 25 Bright; XZW2 I -1 Bright; Stop; #### # 25 Bright; XZW2 J -1 Bright; Stop; #### # 25 Bright; XZW2 K -1 Bright; Stop; #### # 25 Bright; XZW2 L -1 Bright; Stop; #### # 25 Bright; XZW2 M -1 Bright; Stop; } } Class OnFireLight : DynamicLight { OnFire of; override void Tick() { Super.Tick(); if ( !of || !of.victim ) { Destroy(); return; } Args[0] = clamp(of.Amount*4,0,255); Args[1] = clamp(of.Amount*2,0,160); Args[2] = clamp(of.Amount/2,0,24); Args[3] = int(max(of.victim.default.radius,of.victim.default.height)*(of.victim.scale.x+of.victim.scale.y)*1.2+40+clamp(of.amount/5,0,120)); SetOrigin(of.Victim.Vec3Offset(0,0,of.Victim.Height/2),true); } } Class OnFire : Actor { OnFire prevfire, nextfire; Actor victim, instigator, lite; int amount, cnt, delay; double oangle; override void OnDestroy() { let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); if ( hnd ) { hnd.fires_cnt--; if ( !prevfire ) { hnd.fires = nextfire; if ( nextfire ) nextfire.prevfire = null; } else { prevfire.nextfire = nextfire; if ( nextfire ) nextfire.prevfire = prevfire; } } Super.OnDestroy(); } override void Tick() { if ( isFrozen() ) return; if ( !victim ) { A_StopSound(CHAN_5); Destroy(); return; } SetOrigin(victim.pos,false); if ( victim.waterlevel > 0 ) { if ( lite ) lite.Destroy(); amount -= int(victim.waterlevel**2); } if ( (victim.Health <= 0) || ((victim is 'FlamingChunk') && !victim.bMISSILE) ) amount = min(amount,100); if ( !(level.maptime%6) ) amount--; if ( victim.player ) amount -= int(abs(actor.deltaangle(victim.angle,oangle))/30); oangle = victim.angle; if ( amount < -30 ) { A_StopSound(CHAN_5); Destroy(); return; } if ( cnt > 0 ) cnt--; else { cnt = min(10,30-int(29*(min(1.,amount/500.)**3.))); if ( victim.bSHOOTABLE && (victim.Health > 0) && (amount > 0) ) { int flg = DMG_THRUSTLESS; if ( victim is 'Centaur' ) flg |= DMG_FOILINVUL; // you're on fire, that shield is worthless victim.DamageMobj(self,instigator,clamp(int(amount*.1),1,30),'Fire',flg); // need to use this actor as inflictor to have a proper obituary if ( victim.bISMONSTER && !Random[FlameT](0,3) ) victim.Howl(); } if ( !victim ) { A_StopSound(CHAN_5); Destroy(); return; } } double mult = max(victim.radius,victim.height)/30.; if ( victim is 'FlamingChunk' ) mult *= 20.-victim.special1*3.; if ( delay > 0 ) delay--; if ( (level.maptime+special1)%6 ) return; A_SoundVolume(CHAN_5,min(1.,mult*amount/80.)); int numpt = clamp(int(Random[FlameT](2,4)*amount*.01),1,4); numpt = int(clamp(numpt*mult**.5,1,3)); for ( int i=0; i 0 ) { let c = victim.Spawn("OnFireTrail",pos); c.special1 = Random[FlameT](-2,2); c.scale *= max(.3,mult*0.5); c.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.5,2.)*c.scale.x; } if ( !(i%2) ) { let s = victim.Spawn("SWWMHalfSmoke",pos); s.scale *= max(1.,1.6*mult); s.alpha *= min(amount+30,100)*.01; s.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.2,.6)*s.scale.x; } } if ( amount <= 0 ) return; // spread to nearby actors let bt = BlockThingsIterator.Create(victim); while ( bt.Next() ) { let t = bt.Thing; if ( !t || !t.bSHOOTABLE || (t.Health <= 0) || (t == victim) || ((t == instigator) && (delay > 0)) || (victim.Distance3D(t) > victim.radius+t.radius+40) || !victim.CheckSight(t,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; int amt = max(1,amount/10); OnFire of = IsOnFire(t); if ( of ) { amt = min(5,amt); if ( instigator ) of.instigator = instigator; of.amount = min(500,of.amount+amt); of.cnt = min(of.cnt,5); } else Apply(t,instigator,amt); } } static OnFire Apply( Actor victim, Actor instigator, int amount, int delay = 0 ) { if ( amount <= 0 ) return null; let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); if ( !hnd ) return null; OnFire t; for ( t=hnd.fires; t; t=t.nextfire ) { if ( t.victim != victim ) continue; if ( instigator ) t.instigator = instigator; t.amount = min(500,t.amount+amount); t.cnt = min(t.cnt,5); return t; } t = OnFire(Spawn("OnFire",victim.pos)); t.victim = victim; t.instigator = instigator; t.amount = min(500,amount); t.cnt = 1; t.special1 = Random[FlameT](0,10); t.A_StartSound("spreadgun/flame",CHAN_5,CHANF_LOOP); double mult = max(victim.radius,victim.height)/30.; if ( victim is 'FlamingChunk' ) mult *= 20.-victim.special1*3.; t.A_SoundVolume(CHAN_5,min(1.,mult*amount/80.)); // for chunks t.delay = delay; t.lite = Actor.Spawn("OnFireLight",victim.pos); OnFireLight(t.lite).of = t; t.oangle = victim.angle; // append t.nextfire = hnd.fires; if ( hnd.fires ) hnd.fires.prevfire = t; hnd.fires = t; hnd.fires_cnt++; return t; } static OnFire IsOnFire( Actor victim ) { let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); if ( !hnd ) return null; OnFire t; for ( t=hnd.fires; t; t=t.nextfire ) { if ( t.victim != victim ) continue; if ( t.amount <= 0 ) return null; return t; } return null; } Default { +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +NOEXTREMEDEATH; +NOINTERACTION; Obituary "$O_SPREADGUN_BLACK"; } } Class OnFireTrailLight : PaletteLight { Default { Tag "HellExpl"; Args 0,0,0,40; ReactionTime 40; } override void Tick() { Super.Tick(); Args[0] /= 10; Args[1] /= 10; Args[2] /= 10; Args[3] += 3; if ( !target || (target.waterlevel > 0) ) { Destroy(); return; } SetOrigin(target.pos,true); } } Class OnFireTrail : Actor { override void PostBeginPlay() { Super.PostBeginPlay(); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); roll = FRandom[ExploS](0,360); } action void A_Flame() { special1++; if ( waterlevel > 0 ) vel *= .9; else { vel *= .98; vel.z += .1+.2*abs(scale.x); } if ( waterlevel > 0 ) { let s = Spawn("SWWMSmoke",pos); s.vel = (FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2)); s.vel += vel*.3; s.alpha *= alpha*2; s.scale *= .5+abs(scale.x)*(.5+special1/6.); Destroy(); return; } if ( !Random[FlameT](0,int(40*(default.alpha-alpha))) ) { let s = Spawn("SWWMHalfSmoke",pos); s.vel = (FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2),FRandom[FlameT](-.2,.2)); s.vel += vel*.3; s.alpha *= alpha*1.5; s.scale *= .5+abs(scale.x)*(.5+special1/6.); } } override void Tick() { if ( isFrozen() ) return; SetOrigin(level.Vec3Offset(pos,vel),true); UpdateWaterLevel(); if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } Default { RenderStyle "Add"; Speed 2; Radius 4; Height 4; Alpha .6; Scale .8; +NOBLOCKMAP; +NOGRAVITY; +NOFRICTION; +SLIDESONWALLS; +NOTELEPORT; +FORCEXYBILLBOARD; +ROLLSPRITE; +ROLLCENTER; +DROPOFF; +NOBLOCKMONST; +DONTSPLASH; +NOINTERACTION; } States { Spawn: XFLM ABCDEFGHIJKLMNOPQRST 1 Bright { A_Flame(); A_SetScale(scale.x*0.98); A_FadeOut(0.02); } Wait; } } Class FlamingChunk : Actor { double rollvel; OnFire myfire; Vector3 oldvel; int deadtimer; Actor lasthit; override void PostBeginPlay() { Super.PostBeginPlay(); rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1); Scale *= FRandom[FlameT](.8,1.2); if ( waterlevel <= 0 ) myfire = OnFire.Apply(self,target,int(200*scale.x),special1?0:6); frame = Random[FlameT](0,5); } override int DoSpecialDamage( Actor target, int damage, Name damagetype ) { if ( target != lasthit ) { OnFire.Apply(target,self.target,myfire?myfire.Amount:1); lasthit = target; } return damage; } override void Tick() { oldvel = vel; Super.Tick(); if ( isFrozen() ) return; if ( InStateSequence(CurState,ResolveState("Death")) ) { deadtimer++; if ( deadtimer > 300 ) A_FadeOut(0.05); return; } } void A_HandleBounce() { bHITOWNER = true; lasthit = null; Vector3 HitNormal = -vel.unit(); F3DFloor ff; if ( BlockingFloor ) { // find closest 3d floor for its normal for ( int i=0; i 5) && (scale.x > .1) && !Random[FlameT](0,2+special1*2) ) { int npt = Random[FlameT](2,3); for ( int i=0; i= 1 ) return StringTable.Localize("$O_WALLBUSTER_PURPLE"); return Super.GetObituary(victim,inflictor,mod,playerattack); } override int SpecialMissileHit( Actor victim ) { if ( (vel.length() <= 5) || ((victim == target) && !bHITOWNER) || (victim == lasthit) || (!victim.bSHOOTABLE && !victim.bSOLID) ) return 1; // check if we should rip or bounce // girthitude double girth = (victim.radius+victim.height)/2.*max(50,victim.mass)*(victim.health/double(victim.GetSpawnHealth())); // how hard this damn thing is going to slam double slamforce = vel.length()*350.+heat*120; int dmg = int(vel.length()*4.2+heat*80); bool is_schutt = victim.bSHOOTABLE; // critical hit! bool crit = false; if ( is_schutt && !Random[Spreadgun](0,9) ) { Spawn("SWWMItemFog",pos); int whichclonk = Random[Spreadgun](1,11); String snd = String.Format("misc/clonk%d",whichclonk); A_AlertMonsters(swwm_uncapalert?0:2500); A_StartSound(snd,CHAN_VOICE,CHANF_OVERLAP,1.,.2); A_StartSound(snd,CHAN_VOICE,CHANF_OVERLAP,1.,.2); victim.A_QuakeEx(8,8,8,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:1.); victim.A_StartSound(snd,CHAN_DAMAGE,CHANF_OVERLAP,1.,.2); slamforce *= 4; dmg *= 4; vel *= 1.1; let numpt = Random[Spreadgun](20,30); for ( int i=0; i0)?newdmg:dmg,self); victim.SpawnBlood(pos,atan2(dir.y,dir.x),dmg); } } else { A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5); if ( victim ) victim.A_StartSound("spreadgun/ball",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5); if ( vel.length() > 15. ) { let s = Spawn("BallImpact",pos); s.angle = atan2(dir.y,dir.x); s.pitch = asin(-dir.z); } } if ( crit ) SWWMUtility.DoExplosion(self,dmg/2,25000,150,80,ignoreme:target); // only rip shootables if ( (slamforce > girth) && is_schutt ) { vel *= .7; return 1; } // force bounce BlockingMobj = victim; A_HandleBounce(); lasthit = victim; // pretend to pass through return 1; } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("pusher/fly",CHAN_WEAPON,CHANF_LOOP,.6,3.,2.); heat = 1.; } override void Tick() { oldvel = vel; Super.Tick(); if ( isFrozen() ) return; if ( InStateSequence(CurState,ResolveState("Death")) ) { deadtimer++; if ( deadtimer > 300 ) { let numpt = Random[Spreadgun](3,6); for ( int i=0; i 15) && swwm_balluse ) { int locknum = SWWMUtility.GetLineLock(BlockingLine); // remotely activate unlocked lines (that aren't exits) if ( (!locknum || (target && target.CheckKeys(locknum,false,true))) && !SWWMUtility.IsExitLine(BlockingLine) ) BlockingLine.RemoteActivate(target,wside,SPAC_Use,pos); } } else if ( BlockingMobj ) { Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos); HitNormal = diff.unit(); } // send the needed data for a bust if ( (special1 == 2) || swwm_omnibust ) { int dmg = int(oldvel.length()*4.2+heat*80); BusterWall.ProjectileBust(self,dmg,oldvel.unit()); } // undo the bounce, we need to hook in our own vel = oldvel; // re-do the bounce with our formula double bcefact = .9; if ( BlockingMobj ) { bcefact *= .7; if ( !BlockingMobj.bINVULNERABLE && !BlockingMobj.bNOBLOOD && !BlockingMobj.bDORMANT ) bcefact *= .6; } vel = (vel dot HitNormal)*HitNormal*FRandom[Spreadgun](-1.8,-1.)+vel; vel += (FRandom[Spreadgun](-.4,.4),FRandom[Spreadgun](-.4,.4),FRandom[Spreadgun](-.4,.4)); vel *= bcefact; // slam jam if ( !BlockingMobj ) { A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,max(0.,(vel.length()/30.-.2))**.5); if ( vel.length() > 15 ) { let s = Spawn("BallImpact",pos); s.angle = atan2(HitNormal.y,HitNormal.x); s.pitch = asin(-HitNormal.z); } } gravity = .35; if ( (vel.length() < 5) && (pos.z <= floorz) ) { ClearBounce(); ExplodeMissile(); } } States { Spawn: XZW1 A -1; Stop; Bounce: XZW1 A 0 A_HandleBounce(); Goto Spawn; Death: XZW1 A -1 { bMOVEWITHSECTOR = true; A_StopSound(CHAN_WEAPON); } Stop; } } Class GExploLight : PaletteLight { Default { ReactionTime 80; Args 0,0,0,220; } } Class GExploLight2 : PaletteLight { Default { ReactionTime 60; Args 0,0,0,120; } } Class GoldenImpact : Actor { Default { DamageType "Explodium"; RenderStyle "Add"; Radius 0.1; Height 0; Scale 8.; +NOGRAVITY; +NOBLOCKMAP; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +NOTELEPORT; +FOILINVUL; +NOINTERACTION; } override void PostBeginPlay() { Super.PostBeginPlay(); A_AlertMonsters(swwm_uncapalert?0:40000); SWWMUtility.DoExplosion(self,7777,40000,600,500,DE_EXTRAZTHRUST); A_QuakeEx(9,9,9,40,0,5000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:500,rollintensity:1.5); A_StartSound("spreadgun/goldexpl",CHAN_VOICE,attenuation:.3); A_StartSound("spreadgun/goldexpl",CHAN_WEAPON,attenuation:.15); A_SprayDecal("WumboRocketBlast",-172); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); int numpt = Random[ExploS](10,20); for ( int i=0; i 20) ) return; FLineTraceData d; Vector3 HitNormal; Vector3 origin; double ang, pt; for ( int i=0; i<6; i++ ) { double totaldist = 30+(special1**2.)*1.2; ang = FRandom[ExploS](0,360); pt = FRandom[ExploS](-90,90); origin = pos; while ( totaldist > 0 ) { LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else hitnormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) hitnormal *= -1; } totaldist -= d.Distance; if ( totaldist > 0 ) { Vector3 bounced = d.HitDir-(1.2*hitnormal*(d.HitDir dot HitNormal)); ang = atan2(bounced.y,bounced.x); pt = asin(-bounced.z); origin = d.HitLocation+hitnormal; } } let p = Spawn("GoldenSubImpact",d.HitLocation+hitnormal*4); p.angle = atan2(hitnormal.y,hitnormal.x); p.pitch = asin(-hitnormal.z); p.target = target; } } override void Tick() { if ( isFrozen() ) return; if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XEX1 AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTTUUVVWWXXYYZZ[[\\]] 1 Bright A_GoldSpread(); TNT1 A 1 { A_GoldSpread(); if ( special1 > 20 ) Destroy(); } Wait; } } Class GoldenSubImpact : Actor { Default { DamageType "Explodium"; RenderStyle "Add"; Scale 6.; Alpha .8; Radius 0.1; Height 0; +NOGRAVITY; +NOBLOCKMAP; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +NOTELEPORT; +FOILINVUL; +NOINTERACTION; } override void PostBeginPlay() { Super.PostBeginPlay(); SWWMUtility.DoExplosion(self,777,30000,400,300,DE_EXTRAZTHRUST); A_QuakeEx(7,7,7,20,0,2000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollintensity:.8); A_SprayDecal("BigRocketBlast",-172); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); int numpt = Random[ExploS](3,8); for ( int i=0; i 10) ) return; FLineTraceData d; Vector3 HitNormal; Vector3 origin; double ang, pt; for ( int i=0; i<3; i++ ) { double totaldist = 20+(special1**2.)*.8; ang = FRandom[ExploS](0,360); pt = FRandom[ExploS](-90,90); origin = pos; while ( totaldist > 0 ) { LineTrace(ang,totaldist,pt,TRF_THRUACTORS|TRF_ABSPOSITION,origin.z,origin.x,origin.y,d); hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else hitnormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) hitnormal *= -1; } totaldist -= d.Distance; if ( totaldist > 0 ) { Vector3 bounced = d.HitDir-(1.2*hitnormal*(d.HitDir dot HitNormal)); ang = atan2(bounced.y,bounced.x); pt = asin(-bounced.z); origin = d.HitLocation+hitnormal; } } let p = Spawn("GoldenSubSubImpact",d.HitLocation+hitnormal*4); p.angle = atan2(hitnormal.y,hitnormal.x); p.pitch = asin(-hitnormal.z); p.target = target; } } override void Tick() { if ( isFrozen() ) return; if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XEX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright A_GoldSubSpread(); TNT1 A 1 { A_GoldSubSpread(); if ( special1 > 10 ) Destroy(); } Stop; } } Class GoldenSubSubImpact : Actor { Default { DamageType "Explodium"; RenderStyle "Add"; Scale 3.; Alpha .6; Radius 0.1; Height 0; +NOGRAVITY; +NOBLOCKMAP; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +NOTELEPORT; +FOILINVUL; +NOINTERACTION; } override void PostBeginPlay() { Super.PostBeginPlay(); SWWMUtility.DoExplosion(self,77,20000,200,100,DE_EXTRAZTHRUST); A_QuakeEx(4,4,4,15,0,1000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollintensity:.4); A_SprayDecal("RocketBlast",-172); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); int numpt = Random[ExploS](1,2); for ( int i=0; i 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XEX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright; Stop; } } Class Spreadgun : SWWMWeapon { bool fired; // shell was used Class loadammo, nextammo; // currently loaded shell, next shell to load transient ui TextureID WeaponBox, AmmoIcon[7], LoadedIcon[7]; transient ui Font TewiFont; override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack ) { if ( loadammo is 'RedShell' ) return StringTable.Localize("$O_SPREADGUN_RED"); if ( loadammo is 'GreenShell' ) return StringTable.Localize("$O_SPREADGUN_GREEN"); if ( loadammo is 'WhiteShell' ) return StringTable.Localize("$O_SPREADGUN_WHITE"); if ( loadammo is 'BlueShell' ) return StringTable.Localize("$O_SPREADGUN_BLUE"); if ( loadammo is 'BlackShell' ) return StringTable.Localize("$O_SPREADGUN_BLACK"); if ( loadammo is 'PurpleShell' ) return StringTable.Localize("$O_SPREADGUN_PURPLE"); if ( loadammo is 'GoldShell' ) return StringTable.Localize("$O_SPREADGUN_GOLD"); return Super.GetObituary(victim,inflictor,mod,playerattack); } override void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss ) { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; if ( !WeaponBox ) { WeaponBox = TexMan.CheckForTexture("graphics/HUD/SpreadgunDisplay.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/WhiteShell.png",TexMan.Type_Any); AmmoIcon[3] = TexMan.CheckForTexture("graphics/HUD/BlueShell.png",TexMan.Type_Any); AmmoIcon[4] = TexMan.CheckForTexture("graphics/HUD/BlackShell.png",TexMan.Type_Any); AmmoIcon[5] = TexMan.CheckForTexture("graphics/HUD/PurpleShell.png",TexMan.Type_Any); AmmoIcon[6] = TexMan.CheckForTexture("graphics/HUD/GoldShell.png",TexMan.Type_Any); LoadedIcon[0] = TexMan.CheckForTexture("graphics/HUD/LoadedRedShell.png",TexMan.Type_Any); LoadedIcon[1] = TexMan.CheckForTexture("graphics/HUD/LoadedGreenShell.png",TexMan.Type_Any); LoadedIcon[2] = TexMan.CheckForTexture("graphics/HUD/LoadedWhiteShell.png",TexMan.Type_Any); LoadedIcon[3] = TexMan.CheckForTexture("graphics/HUD/LoadedBlueShell.png",TexMan.Type_Any); LoadedIcon[4] = TexMan.CheckForTexture("graphics/HUD/LoadedBlackShell.png",TexMan.Type_Any); LoadedIcon[5] = TexMan.CheckForTexture("graphics/HUD/LoadedPurpleShell.png",TexMan.Type_Any); LoadedIcon[6] = TexMan.CheckForTexture("graphics/HUD/LoadedGoldShell.png",TexMan.Type_Any); } if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded'); Screen.DrawTexture(WeaponBox,false,bx-54,by-43,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); int ox = 6; int oy = 11; for ( int i=0; i<7; i++ ) { Screen.DrawTexture(AmmoIcon[i],false,bx-ox,by-oy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,(types[i]==nextammo)?Color(0,0,0,0):Color(128,0,0,0)); String astr = String.Format("%3d",Owner.CountInv(types[i])); Screen.DrawText(TewiFont,Font.CR_FIRE,bx-ox-(TewiFont.StringWidth(astr)+1),by-oy-2,astr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,(types[i]==nextammo)?Color(0,0,0,0):Color(128,0,0,0)); oy += 10; if ( i == 3 ) { oy = 21; ox = 33; } } for ( int i=0; i<7; i++ ) { if ( loadammo != types[i] ) continue; Screen.DrawTexture(LoadedIcon[i],false,bx-48,by-8,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fired?Color(128,0,0,0):Color(0,0,0,0)); break; } } override bool ReportHUDAmmo() { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; for ( int i=0; i<7; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true; return !fired; } override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount ) { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; if ( (firemode == PrimaryFire) || (firemode == AltFire) ) { if ( !fired ) return true; for ( int i=0; i<7; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true; return false; } return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount); } override bool UsesAmmo( Class kind ) { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; for ( int i=0; i<7; i++ ) if ( kind is types[i] ) return true; return false; } action void A_SelectUnloadState() { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; static const statelabel primedstates[] = {"UnloadRed", "UnloadGreen", "UnloadWhite", "UnloadBlue", "UnloadBlack", "UnloadPurple", "UnloadGold"}; static const statelabel firedstates[] = {"UnloadRedFired", "UnloadGreenFired", "UnloadWhiteFired", "UnloadBlueFired", "UnloadBlackFired", "UnloadPurpleFired", "UnloadGoldFired"}; int amidx = 0; for ( int i=0; i<7; i++ ) { if ( invoker.loadammo != types[i] ) continue; amidx = i; break; } if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx])); else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx])); A_Overlay(-9999,"UnloadDummy"); A_StartSound("spreadgun/deselect",CHAN_WEAPON,CHANF_OVERLAP); } action void A_SelectLoadState() { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; static const statelabel primedstates[] = {"LoadRed", "LoadGreen", "LoadWhite", "LoadBlue", "LoadBlack", "LoadPurple", "LoadGold"}; static const statelabel firedstates[] = {"LoadRedFired", "LoadGreenFired", "LoadWhiteFired", "LoadBlueFired", "LoadBlackFired", "LoadPurpleFired", "LoadGoldFired"}; int amidx = 0; for ( int i=0; i<7; i++ ) { if ( invoker.nextammo != types[i] ) continue; amidx = i; break; } if ( !invoker.fired ) player.SetPSprite(PSP_WEAPON,invoker.FindState(primedstates[amidx])); else player.SetPSprite(PSP_WEAPON,invoker.FindState(firedstates[amidx])); if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) { let amo = FindInventory(invoker.nextammo); if ( amo && (amo.Amount > 0) ) amo.Amount--; } A_Overlay(-9999,"LoadDummy"); } action void A_DropShell() { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; static const Class casetypes[] = {"RedShellCasing","GreenShellCasing","WhiteShellCasing","BlueShellCasing","BlackShellCasing","PurpleShellCasing","GoldShellCasing"}; if ( !invoker.fired ) { for ( int i=0; i<7; i++ ) { if ( invoker.loadammo != types[i] ) continue; let amo = FindInventory(types[i]); if ( !amo ) { amo = Inventory(Spawn(types[i])); amo.AttachToOwner(self); amo.Amount = 0; } if ( amo.Amount < amo.MaxAmount ) amo.Amount++; else if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) Spawn(types[i],Vec3Angle(5,angle,height/2)); break; } } else { for ( int i=0; i<7; i++ ) { if ( invoker.loadammo != types[i] ) continue; Vector3 x, y, z; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-10*z); let c = Spawn(casetypes[i],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; break; } } } action void ProcessTraceHit( SpreadgunTracer t, Vector3 origin, Vector3 dir, int dmg, double mm, Class impact = "SpreadImpact", int bc = 1, bool large = false ) { if ( swwm_omnibust ) { // Wall busting 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 types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; static const statelabel flashes[] = {"FlashRed","FlashGreen","FlashWhite","FlashBlue","FlashBlack","FlashPurple","FlashGold"}; static const String sounds[] = {"spreadgun/redfire","spreadgun/greenfire","spreadgun/whitefire","spreadgun/bluefire","spreadgun/blackfire","spreadgun/purplefire","spreadgun/goldfire"}; static const int louds[] = {800,1000,1100,1200,1400,600,2500}; static const int quakes[] = {3,4,2,4,3,1,6}; static const Color cols[] = {Color(40,255,192,64),Color(36,255,192,80),Color(64,255,160,32),Color(48,32,176,255),Color(72,255,128,16),Color(24,255,224,96),Color(96,255,224,16)}; for ( int i=0; i<7; i++ ) { if ( invoker.loadammo != types[i] ) continue; A_SWWMFlash(flashes[i]); A_StartSound(sounds[i],CHAN_WEAPON,CHANF_OVERLAP,attenuation:.6); A_AlertMonsters(swwm_uncapalert?0:louds[i]); A_QuakeEx(quakes[i],quakes[i],quakes[i],9,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.2*quakes[i]); A_ZoomFactor(1.-quakes[i]*.04,ZOOM_INSTANT); A_ZoomFactor(1.); A_PlayerFire(); SWWMHandler.DoFlash(self,cols[i],5); 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-2*z); Vector3 x2, y2, z2; [x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll); double a, s; Vector3 dir; SpreadgunTracer st; SpreadSlugTracer sst; switch ( i ) { case 1: sst = new("SpreadSlugTracer"); sst.ignoreme = self; sst.penetration = 250.; a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.01); 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,true); for ( int i=0; i<3; i++ ) { let s = Spawn("SWWMViewSmoke",origin); SWWMViewSmoke(s).ofs = (15,3,-3); s.target = self; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.alpha *= 0.5; } for ( int i=0; i<6; i++ ) { let s = Spawn("SWWMSmoke",origin); s.scale *= .8; s.alpha *= .3; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.); } for ( int i=0; i<10; i++ ) { let s = Spawn("SWWMSpark",origin); s.scale *= .2; s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } SWWMUtility.DoKnockback(self,-x,25000.); break; case 2: for ( int j=0; j<5; j++ ) { a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.2); dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit(); let p = Spawn("DragonBreathArm",origin); p.target = self; p.angle = atan2(dir.y,dir.x); p.pitch = asin(-dir.z); } for ( int i=0; i<7; i++ ) { let s = Spawn("SWWMViewSmoke",origin); SWWMViewSmoke(s).ofs = (15,3,-3); s.target = self; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.alpha *= .2; } for ( int i=0; i<20; i++ ) { let s = Spawn("SWWMSmoke",origin); s.special1 = 1; s.scale *= .9; s.alpha *= .3; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } for ( int i=0; i<15; i++ ) { let s = Spawn("SWWMSpark",origin); s.scale *= .3; s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } SWWMUtility.DoKnockback(self,-x,13000.); break; case 3: for ( int j=0; j<8; j++ ) { a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.8); 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); } for ( int i=0; i<9; i++ ) { let s = Spawn("SWWMViewSmoke",origin); SWWMViewSmoke(s).ofs = (15,3,-3); s.target = self; s.SetShade(Color(1,3,4)*Random[Spreadgun](32,63)); s.A_SetRenderStyle(.2,STYLE_AddShaded); } for ( int i=0; i<16; i++ ) { let s = Spawn("SWWMSmoke",origin); s.special1 = 1; s.scale *= .9; s.SetShade(Color(1,3,4)*Random[Spreadgun](32,63)); s.A_SetRenderStyle(.3,STYLE_AddShaded); s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } for ( int i=0; i<20; i++ ) { let s = Spawn("SWWMSpark",origin); s.scale *= .3; s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } SWWMUtility.DoKnockback(self,-x,23000.); break; case 4: for ( int j=0; j<10; j++ ) { a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.2); dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit(); let p = Spawn("FlamingChunk",origin); p.target = self; p.angle = atan2(dir.y,dir.x); p.pitch = asin(-dir.z); p.vel = dir*p.speed*FRandom[Spreadgun](.9,1.3); } for ( int i=0; i<12; i++ ) { let s = Spawn("SWWMViewSmoke",origin); SWWMViewSmoke(s).ofs = (15,3,-3); s.target = self; s.SetShade(Color(1,1,1)*Random[Spreadgun](48,128)); s.alpha *= .2; } for ( int i=0; i<30; i++ ) { let s = Spawn("SWWMSmoke",origin); s.special1 = 1; s.scale *= 1.3; s.alpha *= .3; s.SetShade(Color(1,1,1)*Random[Spreadgun](48,128)); s.vel += vel*.5+x*FRandom[Spreadgun](3.,12.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } for ( int i=0; i<20; i++ ) { let s = Spawn("SWWMSpark",origin); s.scale *= .3; s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,12.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } SWWMUtility.DoKnockback(self,-x,14000.); break; case 5: a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.03); dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit(); let b = Spawn("TheBall",origin); b.target = self; b.angle = atan2(dir.y,dir.x); b.pitch = asin(-dir.z); b.vel = dir*b.speed; for ( int i=0; i<4; i++ ) { let s = Spawn("SWWMViewSmoke",origin); SWWMViewSmoke(s).ofs = (15,3,-3); s.target = self; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.alpha *= 0.4; } for ( int i=0; i<8; i++ ) { let s = Spawn("SWWMSmoke",origin); s.scale *= .6; s.alpha *= .25; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.); } for ( int i=0; i<8; i++ ) { let s = Spawn("SWWMSpark",origin); s.scale *= .2; s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } SWWMUtility.DoKnockback(self,-x,9500.); break; case 6: a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.01); dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit(); FLineTraceData d; LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION|TRF_NOSKY,origin.z,origin.x,origin.y,d); SWWMBulletTrail.DoTrail(self,origin,dir,10000,2,true); if ( d.HitType != TRACE_HitNone ) { Vector3 hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else hitnormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) hitnormal *= -1; } let p = Spawn("SlugImpact",d.HitLocation+hitnormal); p.angle = atan2(hitnormal.y,hitnormal.x); p.pitch = asin(-hitnormal.z); if ( d.HitLine ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation); let b = Spawn("GoldenImpact",d.HitLocation+hitnormal*4.); b.angle = atan2(hitnormal.y,hitnormal.x); b.pitch = asin(-hitnormal.z); b.target = self; } for ( int i=0; i<3; i++ ) { let s = Spawn("SWWMViewSmoke",origin); SWWMViewSmoke(s).ofs = (15,3,-3); s.target = self; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.alpha *= 0.5; } for ( int i=0; i<6; i++ ) { let s = Spawn("SWWMSmoke",origin); s.scale *= .8; s.alpha *= .3; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.); } for ( int i=0; i<10; i++ ) { let s = Spawn("SWWMSpark",origin); s.scale *= .2; s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } for ( int i=0; i<50; i++ ) { let s = Spawn("FancyConfetti",origin); s.bAMBUSH = true; s.vel += vel*.5+x*FRandom[Spreadgun](1.,20.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } SWWMUtility.DoKnockback(self,-x,30000.); break; default: st = new("SpreadgunTracer"); st.ignoreme = self; for ( int j=0; j<30; j++ ) { a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.22); 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,6,7000,bc:5); } for ( int i=0; i<9; i++ ) { let s = Spawn("SWWMViewSmoke",origin); SWWMViewSmoke(s).ofs = (15,3,-3); s.target = self; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.alpha *= .2; } for ( int i=0; i<16; i++ ) { let s = Spawn("SWWMSmoke",origin); s.special1 = 1; s.scale *= .9; s.alpha *= .3; s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192)); s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } for ( int i=0; i<20; i++ ) { let s = Spawn("SWWMSpark",origin); s.scale *= .3; s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } SWWMUtility.DoKnockback(self,-x,20000.); break; } break; } A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP); invoker.fired = true; } action void A_LoadShell() { A_StartSound("spreadgun/shellin",CHAN_WEAPON,CHANF_OVERLAP); invoker.loadammo = invoker.nextammo; } action void A_Prime() { if ( invoker.fired ) { A_StartSound("spreadgun/hammer",CHAN_WEAPON,CHANF_OVERLAP); invoker.fired = false; } } override void AttachToOwner( Actor other ) { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; Super.AttachToOwner(other); if ( !loadammo ) loadammo = "RedShell"; for ( int i=0; i<7; i++ ) { Ammo a = Ammo(other.FindInventory(types[i])); if ( !a ) continue; nextammo = types[i]; return; } nextammo = AmmoType1; } action void A_SwitchAmmoType( bool rev = false ) { static const Class types[] = {"RedShell","GreenShell","WhiteShell","BlueShell","BlackShell","PurpleShell","GoldShell"}; int cur = 0, next = 0; for ( int i=0; i<7; i++ ) { if ( invoker.nextammo != types[i] ) continue; cur = i; break; } int ridx = -1; if ( rev ) { // check backwards from what we currently had for ( int i=cur; i>=0; i-- ) { if ( CountInv(types[i]) <= 0 ) continue; ridx = i; break; } if ( ridx == -1 ) { // check forwards instead, but avoid golden shells for ( int i=0; i<6; i++ ) { if ( CountInv(types[i]) <= 0 ) continue; ridx = i; break; } } if ( ridx != -1 ) next = ridx; } else { for ( int i=0; i<7; i++ ) { ridx = (i+cur+1)%7; if ( CountInv(types[ridx]) <= 0 ) continue; next = ridx; break; } } if ( invoker.nextammo != types[next] ) A_StartSound("misc/invchange",CHAN_WEAPONEXTRA,CHANF_UI|CHANF_LOCAL); invoker.nextammo = types[next]; A_WeaponReady(WRF_NOFIRE); } action void A_AltHold() { A_WeaponReady(WRF_NOFIRE); if ( player.cmd.buttons&BT_ALTATTACK ) return; if ( !invoker.fired ) player.SetPSPrite(PSP_WEAPON,invoker.FindState("Ready")); else player.SetPSPrite(PSP_WEAPON,invoker.FindState("ReadyFired")); } override void ModifyDropAmount( int dropamount ) { Super.ModifyDropAmount(dropamount); // toss some ammo while we're at it if ( Random[Spreadgun](0,1) ) A_DropItem(Random[Spreadgun](0,2)?"RedShell":"GreenShell",Random[Spreadgun](1,2)); } Default { Tag "$T_SPREADGUN"; Inventory.PickupMessage "$I_SPREADGUN"; Obituary "$O_SPREADGUN"; Inventory.Icon "graphics/HUD/Icons/W_Spreadgun.png"; Weapon.UpSound "spreadgun/select"; Weapon.SlotNumber 3; Weapon.SelectionOrder 500; Weapon.AmmoType1 "RedShell"; Weapon.AmmoGive1 1; //SWWMWeapon.SwapWeapon "PuntzerBeta"; SWWMWeapon.DropAmmoType "Clip"; Stamina 15000; +SWWMWEAPON.NOFIRSTGIVE; Radius 10; Height 24; } States { Spawn: XZW1 A -1; 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 { if ( CountInv(invoker.nextammo) <= 0 ) A_SwitchAmmoType(true); int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1; if ( invoker.nextammo && (CountInv(invoker.nextammo) > 0) && (invoker.loadammo != invoker.nextammo) ) flg |= WRF_ALLOWRELOAD; A_WeaponReady(flg); } Wait; ReadyFired: XZW2 Z 1 { if ( CountInv(invoker.nextammo) <= 0 ) A_SwitchAmmoType(true); int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1; if ( invoker.nextammo && (CountInv(invoker.nextammo) > 0) ) flg |= WRF_ALLOWRELOAD; else flg |= WRF_NOPRIMARY; A_WeaponReady(flg); if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK) ) invoker.CheckAmmo(EitherFire,true); } Wait; Fire: #### # 1 { if ( invoker.fired ) return ResolveState("Reload"); A_FireShell(); return ResolveState(null); } XZW2 RSTU 1; XZW2 VWXY 2; Goto ReadyFired; AltFire: #### # 1 A_SwitchAmmoType(); #### # 1 A_AltHold(); Wait; Reload: #### # 1 { A_PlayerReload(); A_SelectUnloadState(); } Stop; UnloadDummy: // overlay with shared functions for all unload anims TNT1 A 11; TNT1 A 14 A_StartSound("spreadgun/open",CHAN_WEAPON,CHANF_OVERLAP); TNT1 A 1 A_DropShell(); Stop; UnloadRedFired: XZW2 Z 2; XZW3 QRST 2; XZW3 UVWXYZ 1; XZW4 ABCDEFGH 1; XZW8 M 1; Goto Reload2; UnloadGreenFired: XZW2 Z 2; XZW4 IJKL 2; XZW4 MNOPQRSTUVWXYZ 1; XZW9 T 1; Goto Reload2; UnloadWhiteFired: XZW2 Z 2; XZW5 ABCD 2; XZW5 EFGHIJKLMNOPQR 1; XZWB A 1; Goto Reload2; UnloadBlueFired: XZW2 Z 2; XZW5 STUV 2; XZW5 WXYZ 1; XZW6 ABCDEFGHIJ 1; XZWC H 1; Goto Reload2; UnloadBlackFired: XZW2 Z 2; XZW6 KLMN 2; XZW6 OPQRSTUVWXYZ 1; XZW7 AB 1; XZWD O 1; Goto Reload2; UnloadPurpleFired: XZW2 Z 2; XZW7 CDEF 2; XZW7 GHIJKLMNOPQRST 1; XZWE V 1; Goto Reload2; UnloadGoldFired: XZW2 Z 2; XZW7 UVWX 2; XZW7 YZ 1; XZW8 ABCDEFGHIJKL 1; XZWG C 1; Goto Reload2; UnloadRed: XZW2 A 2; XZWK JKLM 2; XZWK NOPQRSTUVWXYZ 1; XZWL A 1; XZWP F 1; Goto Reload2; UnloadGreen: XZW2 A 2; XZWL BCDE 2; XZWL FGHIJKLMNOPQRS 1; XZWQ M 1; Goto Reload2; UnloadWhite: XZW2 A 2; XZWL TUVW 2; XZWL XYZ 1; XZWM ABCDEFGHIJK 1; XZWR T 1; Goto Reload2; UnloadBlue: XZW2 A 2; XZWM LMNO 2; XZWM PQRSTUVWXYZ 1; XZWN ABC 1; XZWT A 1; Goto Reload2; UnloadBlack: XZW2 A 2; XZWN DEFG 2; XZWN HIJKLMNOPQRSTU 1; XZWU H 1; Goto Reload2; UnloadPurple: XZW2 A 2; XZWN VWXY 2; XZWN Z 1; XZWO ABCDEFGHIJKLM 1; XZWV O 1; Goto Reload2; UnloadGold: XZW2 A 2; XZWO NOPQ 2; XZWO RSTUVWXYZ 1; XZWP ABCDE 1; XZWW V 1; Goto Reload2; Reload2: #### # 1 A_SelectLoadState(); Stop; LoadDummy: // overlay with shared functions for all load anims 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; LoadRedFired: XZW8 MNOPQRSTUVWXYZ 1; XZW9 ABCDEFGHIJKLMNOPQRS 1; Goto Ready; LoadGreenFired: XZW9 TUVWXYZ 1; XZWA ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; Goto Ready; LoadWhiteFired: XZWB ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; XZWC ABCDEFG 1; Goto Ready; LoadBlueFired: XZWC HIJKLMNOPQRSTUVWXYZ 1; XZWD ABCDEFGHIJKLMN 1; Goto Ready; LoadBlackFired: XZWD OPQRSTUVWXYZ 1; XZWE ABCDEFGHIJKLMNOPQRSTU 1; Goto Ready; LoadPurpleFired: XZWE VWXYZ 1; XZWF ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; XZWG AB 1; Goto Ready; LoadGoldFired: XZWG CDEFGHIJKLMNOPQRSTUVWXYZ 1; XZWH ABCDEFGHI 1; Goto Ready; LoadRed: XZWP FGHIJKLMNOPQRSTUVWXYZ 1; XZWQ ABCDEFGHIJKL 1; Goto Ready; LoadGreen: XZWQ MNOPQRSTUVWXYZ 1; XZWR ABCDEFGHIJKLMNOPQRS 1; Goto Ready; LoadWhite: XZWR TUVWXYZ 1; XZWS ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; Goto Ready; LoadBlue: XZWT ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; XZWU ABCDEFG 1; Goto Ready; LoadBlack: XZWU HIJKLMNOPQRSTUVWXYZ 1; XZWV ABCDEFGHIJKLMN 1; Goto Ready; LoadPurple: XZWV OPQRSTUVWXYZ 1; XZWW ABCDEFGHIJKLMNOPQRSTU 1; Goto Ready; LoadGold: XZWW VWXYZ 1; XZWX ABCDEFGHIJKLMNOPQRSTUVWXYZ 1; XZWY AB 1; Goto Ready; Zoom: XZW2 A 1 { A_StartSound("spreadgun/checkgun",CHAN_WEAPON,CHANF_OVERLAP); A_PlayerCheckGun(); return A_JumpIf(invoker.fired,"ZoomFired"); } XZWH JKLMNOPQRST 1; XZWH UVWXYZ 2; XZWI ABC 2; XZWI DEFGHI 1; Goto Ready; ZoomFired: XZW2 Z 1; XZWI WXYZ 1; XZWJ ABCDEFG 1; XZWJ HIJKLMNOP 2; XZWJ QRSTUV 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"); } XZWI JK 2; User1Hold: XZWI L 1 { A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP); A_Overlay(-9999,"DummyMelee"); } XZWI MNOP 2; XZWI QR 3; XZWI S 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1Hold"); XZWI S 0 { invoker.PlayUpSound(self); } XZWI STUV 2; Goto Ready; User1Fired: XZW2 Z 2; XZWJ WX 2; User1FiredHold: XZWJ Y 1 { A_StartSound("demolitionist/swing",CHAN_WEAPON,CHANF_OVERLAP); A_Overlay(-9999,"DummyMelee"); } XZWJ Z 2; XZWK ABC 2; XZWK DE 3; XZWK F 0 A_JumpIf(player.cmd.buttons&BT_USER1,"User1FiredHold"); XZWK F 0 { invoker.PlayUpSound(self); } XZWK FGHI 2; Goto ReadyFired; FlashRed: XZWZ A 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[3] = 120; l.target = self; } Stop; FlashGreen: XZWZ B 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[3] = 90; l.target = self; } Stop; FlashWhite: XZWZ C 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[1] = 176; l.args[2] = 32; l.args[3] = 160; l.target = self; } Stop; FlashBlue: XZWZ D 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[0] = 96; l.args[1] = 224; l.args[2] = 255; l.args[3] = 160; l.target = self; } Stop; FlashBlack: XZWZ E 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[1] = 144; l.args[2] = 16; l.args[3] = 280; l.target = self; } Stop; FlashPurple: XZWZ F 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[3] = 60; l.target = self; } Stop; FlashGold: XZWZ G 2 Bright { let l = Spawn("SWWMWeaponLight",pos); l.args[3] = 300; l.target = self; } Stop; } }