// Powerups go here Class GrilledCheeseSafeguard : Powerup { Default { Inventory.Icon "graphics/HUD/Icons/I_Sandwich.png"; Powerup.Duration -3; } override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags ) { if ( passive ) newdamage = 0; } } Class GrilledCheeseSandwich : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; // for falling off cliffs and others // last 5 seconds of safe positions bool lastsafevalid; Vector3 lastsafepos[5]; double lastsafeangle[5]; int safetic; int dteleport; Actor lastdropper; override Inventory CreateCopy( Actor other ) { // additional lore SWWMLoreLibrary.Add(other.player,"GCSandwich"); return Super.CreateCopy(other); } void DoTheThing( bool extrasafe = false ) { SWWMUtility.AchievementProgressInc("gcsandwich",1,Owner.player); SWWMHandler.DoFlash(Owner,Color(64,255,255,64),10); Owner.A_QuakeEx(9,9,9,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); Owner.GiveBody(1000,1000); if ( Owner is 'Demolitionist' ) { let n = Owner.FindInventory("ArmorNugget"); if ( !n ) Owner.GiveInventory("ArmorNugget",GetDefaultByType("ArmorNugget").MaxAmount); else n.Amount = n.MaxAmount; let b = Owner.FindInventory("BlastSuit"); if ( !b ) Owner.GiveInventory("BlastSuit",GetDefaultByType("BlastSuit").MaxAmount); else b.Amount = b.MaxAmount; let w = Owner.FindInventory("WarArmor"); if ( !w ) Owner.GiveInventory("WarArmor",GetDefaultByType("WarArmor").MaxAmount); else w.Amount = w.MaxAmount; SWWMLoreLibrary.Add(Owner.player,"Nugget"); SWWMLoreLibrary.Add(Owner.player,"BlastSuit"); SWWMLoreLibrary.Add(Owner.player,"WarArmor"); Demolitionist(Owner).lastbump *= 1.2; } let f = Spawn("SWWMItemFog",Owner.Vec3Offset(0,0,Owner.Height/2)); f.bAMBUSH = true; if ( !extrasafe ) return; let s = Owner.FindInventory("GrilledCheeseSafeguard"); if ( !s ) Owner.GiveInventory("GrilledCheeseSafeguard",1); else Powerup(s).EffectTics = Powerup(s).default.EffectTics; } override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( Owner.Health > 500 ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); if ( (Owner.player == players[consoleplayer]) && (swwm_mutevoice < 2) ) { SWWMHandler.AddOneliner("eat",2); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).facegrin = true; } DoTheThing(); return true; } override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags ) { if ( Owner.FindInventory("GrilledCheeseSafeguard") ) return; // the safeguard absorbs all if ( Owner.ApplyDamageFactor(damageType,damage) <= 0 ) return; // this damage type is ignored by the player, so it does not affect us if ( damageType == 'EndLevel' ) return; // don't trigger on endlevel damage if ( (damageType == 'Telefrag') && source ) { // prevent overlap with voodoo doll if ( source.player == Owner.player ) dteleport = 1; // teleport has to be on next tic else source.DamageMobj(Owner,Owner,damage,damageType,flags); // kill what attempted the telefrag } if ( (Owner.Health-damage <= 0) && (Amount > 0) ) { if ( (swwm_strictuntouchable == 1) && Owner.player ) { let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); if ( hnd ) hnd.tookdamage[Owner.PlayerNumber()] = true; } if ( damageType == 'InstantDeath' ) SafeTeleport(); // get out of pits newdamage = 0; if ( (Owner.player == players[consoleplayer]) || bBigPowerup ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); DoTheThing(true); Amount--; } } void SafeTeleport( bool tostart = false ) { Spawn("SWWMItemFog",Owner.Vec3Offset(0,0,Owner.Height/2)); Vector3 safepos; double safeangle; if ( tostart || !lastsafevalid ) { if ( deathmatch ) [safepos, safeangle] = level.PickDeathmatchStart(); else [safepos, safeangle] = level.PickPlayerStart(Owner.PlayerNumber()); if ( !level.useplayerstartz ) safepos.z = ONFLOORZ; } else { safepos = lastsafepos[4]; safeangle = lastsafeangle[4]; } Owner.Teleport(safepos,safeangle,0); } override void DoEffect() { Super.DoEffect(); if ( dteleport > 0 ) { dteleport--; if ( dteleport <= 0 ) dteleport = -1; } else if ( dteleport == -1 ) { dteleport = 0; SafeTeleport(); } // check safe spot if ( Owner && !(Owner.CurSector.flags&Sector.SECF_ENDLEVEL) && (Owner.CurSector.DamageAmount <= 0) && (Owner.waterlevel < 2) && (Owner.GetFloorTerrain().DamageAmount <= 0) && (Owner.pos.z <= Owner.floorz) ) { if ( safetic == 0 ) { if ( lastsafevalid ) { for ( int i=4; i>0; i-- ) { lastsafepos[i] = lastsafepos[i-1]; lastsafeangle[i] = lastsafeangle[i-1]; } lastsafepos[0] = Owner.pos; lastsafeangle[0] = Owner.angle; } else { lastsafevalid = true; for ( int i=0; i<5; i++ ) { lastsafepos[i] = Owner.pos; lastsafeangle[i] = Owner.angle; } } } safetic = (safetic+1)%35; } else safetic = 1; if ( (Amount <= 0) && (dteleport == 0) ) DepleteOrDestroy(); } override void AttachToOwner( Actor other ) { Super.AttachToOwner(other); // find last armor/health item Inventory found = null; for ( Inventory i=other.Inv; i; i=i.Inv ) { if ( !(i is 'SWWMHealth') && !(i is 'SWWMArmor') ) continue; found = i; } if ( !found ) return; // place ourselves right after it Inventory saved = found.Inv; found.Inv = self; other.Inv = Inv; Inv = saved; } override void OnDrop( Actor dropper ) { lastdropper = dropper; } override void PostTeleport( Vector3 destpos, double destangle, int flags ) { if ( !lastdropper ) return; SWWMUtility.MarkAchievement("tele",lastdropper.player); } Default { Tag "$T_SANDWICH"; Stamina 800000; Inventory.Icon "graphics/HUD/Icons/I_Sandwich.png"; Inventory.PickupSound "misc/p_pkup"; Inventory.UseSound "powerup/sandwich"; Inventory.PickupMessage "$T_SANDWICH"; Inventory.MaxAmount 5; Inventory.InterHubAmount 5; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; } States { Spawn: XZW1 A -1; Stop; } } Mixin Class SWWMShadedPowerup { override Color GetBlend() { if ( swwm_shaders ) return 0; return Super.GetBlend(); } } Class GhostSnd : SWWMNonInteractiveActor { override void Tick() { if ( !target || !master ) { Destroy(); return; } SetOrigin(target.pos,true); if ( players[consoleplayer].Camera == target ) { A_SoundVolume(CHAN_VOICE,0.); A_SoundVolume(CHAN_7,.4); } else { A_SoundVolume(CHAN_VOICE,.1); A_SoundVolume(CHAN_7,0.); } } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/ghostact",CHAN_VOICE,CHANF_LOOP,.1,1.5); A_StartSound("powerup/ghostact",CHAN_7,CHANF_LOOP,.4,ATTN_NONE); } override void OnDestroy() { Super.OnDestroy(); A_StopSound(CHAN_VOICE); A_StopSound(CHAN_7); } } Class GhostTarget : Actor { bool diedie; Default { +SPECTRAL; +NOGRAVITY; +DONTSPLASH; +SHOOTABLE; +NONSHOOTABLE; +NOTELEPORT; +NODAMAGE; +NOBLOOD; +CANTSEEK; +SHADOW; // so they can barely aim Radius .1; Height 56; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( diedie ) A_FadeOut(.02); let bt = BlockThingsIterator.Create(self,300); while ( bt.Next() ) { let t = bt.Thing; if ( !t || !t.bIsMonster || t.player || !t.IsHostile(master) || (t.target != self) ) continue; if ( SWWMUtility.BoxIntersect(self,t,pad:16) || t.CheckMeleeRange() ) { // they found out, there's no one here diedie = true; break; } } // player made noise or is visible again if ( !master || (LastHeard == master) || !master.FindInventory("GhostPower") ) { let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); if ( hnd ) for ( int i=0; i Owner.floorz ) Owner.player.centering = true; } Owner.A_StartSound("powerup/gravityend",CHAN_ITEMEXTRA,CHANF_OVERLAP); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 1.02; if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_GRAVITYS")); } override void DoEffect() { Super.DoEffect(); if ( !Owner ) return; Owner.bFLY = true; Owner.bNOGRAVITY = true; if ( !snd ) snd = Spawn("GravSnd",Owner.pos); snd.target = Owner; snd.master = self; } } Class GravityX : SWWMItemOverlay { Default { RenderStyle "Normal"; } } Class GravitySuppressor : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); let g = GravityPower(Owner.FindInventory("GravityPower")); if ( g ) { g.EffectTics += g.default.EffectTics; if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 1.04; } else Owner.GiveInventory("GravityPower",1); SWWMUtility.AchievementProgressInc("gravity",1,Owner.player); return true; } override void PreTravelled() { if ( tracer ) tracer.Destroy(); } override void Travelled() { if ( tracer ) return; tracer = Spawn("GravityX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } override void PostBeginPlay() { Super.PostBeginPlay(); tracer = Spawn("GravityX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } Default { Tag "$T_GRAVITYS"; Stamina 150000; Inventory.Icon "graphics/HUD/Icons/I_Gravity.png"; Inventory.PickupSound "misc/p_pkup"; Inventory.UseSound "powerup/gravity"; Inventory.PickupMessage "$T_GRAVITYS"; Inventory.MaxAmount 5; Inventory.InterHubAmount 5; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; } States { Spawn: XZW1 A -1; Stop; } } Class InvinciballLight : PointLightAttenuated { Default { Args 192,64,0,80; } override void Tick() { Super.Tick(); if ( !target || !master ) { Destroy(); return; } if ( target.player ) SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true); else SetOrigin(target.Vec3Offset(0,0,target.height/2),true); args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*8; bDORMANT = Powerup(master).isBlinking(); } } Class InvinciSnd : SWWMNonInteractiveActor { override void Tick() { if ( !target || !master ) { Destroy(); return; } SetOrigin(target.pos,true); if ( players[consoleplayer].Camera == target ) { A_SoundVolume(CHAN_VOICE,0.); A_SoundVolume(CHAN_7,.8); } else { A_SoundVolume(CHAN_VOICE,.4); A_SoundVolume(CHAN_7,0.); } } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/invinciballact",CHAN_VOICE,CHANF_LOOP,.4,1.5); A_StartSound("powerup/invinciballact",CHAN_7,CHANF_LOOP,.8,ATTN_NONE); } override void OnDestroy() { Super.OnDestroy(); A_StopSound(CHAN_VOICE); A_StopSound(CHAN_7); } } Class InvinciballArmor : SWWMArmor { Default { +SWWMARMOR.NOHITSOUND; +SWWMARMOR.NOHITFLASH; +SWWMARMOR.NODRAIN; SWWMArmor.ArmorPriority 9; } override int HandleDamage( int damage, Name damageType, int flags ) { if ( damageType == 'EndLevel' ) return 0; if ( master ) InvinciballPower(master).DoHitFX(); return damage; } override void DoEffect() { Super.DoEffect(); if ( !Owner.FindInventory('InvinciballPower') ) Destroy(); } } Class InvinciballPower : Powerup { Mixin SWWMShadedPowerup; Actor l, snd; Inventory am; int lasteffect; transient int lastpulse; Default { Powerup.Duration -30; Inventory.Icon "graphics/HUD/Icons/I_Invinciball.png"; Powerup.Color "FF 30 00", 0.1; +INVENTORY.ADDITIVETIME; } override void InitEffect() { Super.InitEffect(); if ( !Owner ) return; lasteffect = int.min; l = Spawn("InvinciballLight",Owner.pos); l.target = Owner; l.master = self; lastpulse = max(lastpulse,gametic+35); SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 1.1; } override void DoEffect() { Super.DoEffect(); if ( !Owner ) return; if ( !snd ) snd = Spawn("InvinciSnd",Owner.pos); snd.target = Owner; snd.master = self; if ( am ) return; am = Owner.FindInventory("InvinciballArmor"); if ( !am ) { am = Inventory(Spawn("InvinciballArmor")); am.AttachToOwner(Owner); } am.master = self; } override void EndEffect() { Super.EndEffect(); if ( !Owner ) return; Owner.A_StartSound("powerup/invinciballend",CHAN_ITEMEXTRA,CHANF_OVERLAP); SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 1.05; if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_INVINCIBALL")); } void DoHitFX() { if ( level.maptime > lasteffect+5 ) { SWWMHandler.DoFlash(Owner,Color(64,255,64,0),15); Owner.A_StartSound("powerup/invinciballhit",CHAN_POWERUP,CHANF_OVERLAP); lasteffect = level.maptime; lastpulse = max(lastpulse,gametic+20); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 1.05; } } } Class InvinciballX : SWWMItemOverlay { Default { RenderStyle "Normal"; } } Class FuckingInvinciball : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; override Inventory CreateCopy( Actor other ) { // additional lore SWWMLoreLibrary.Add(other.player,"Invinciball"); return Super.CreateCopy(other); } override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); Owner.A_StartSound("misc/sundowner",CHAN_POWERUPEXTRA); let i = InvinciballPower(Owner.FindInventory("InvinciballPower")); if ( i ) { i.EffectTics += i.default.EffectTics; i.lastpulse = max(i.lastpulse,gametic+35); SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 1.1; } else Owner.GiveInventory("InvinciballPower",1); SWWMUtility.AchievementProgressInc("sunny",1,Owner.player); return true; } override void PreTravelled() { if ( tracer ) tracer.Destroy(); } override void Travelled() { if ( tracer ) return; tracer = Spawn("InvinciballX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } override void PostBeginPlay() { Super.PostBeginPlay(); tracer = Spawn("InvinciballX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } Default { Tag "$T_INVINCIBALL"; Stamina 640000; Inventory.Icon "graphics/HUD/Icons/I_Invinciball.png"; Inventory.PickupSound "misc/p_pkup"; Inventory.UseSound "powerup/invinciball"; Inventory.PickupMessage "$T_INVINCIBALL"; Inventory.MaxAmount 5; Inventory.InterHubAmount 5; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; } States { Spawn: XZW1 A -1; Stop; } } Class RagekitLight : PointLightAttenuated { Default { Args 255,0,0,80; } override void Tick() { Super.Tick(); if ( !target || !master ) { Destroy(); return; } if ( target.player ) SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true); else SetOrigin(target.Vec3Offset(0,0,target.height/2),true); args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*8; bDORMANT = Powerup(master).isBlinking(); } } Class RageSnd : SWWMNonInteractiveActor { override void Tick() { if ( !target || !master ) { Destroy(); return; } SetOrigin(target.pos,true); if ( players[consoleplayer].Camera == target ) { A_SoundVolume(CHAN_VOICE,0.); A_SoundVolume(CHAN_7,.5); } else { A_SoundVolume(CHAN_VOICE,.4); A_SoundVolume(CHAN_7,0.); } } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/ragekitact",CHAN_VOICE,CHANF_LOOP,.4,1.5); A_StartSound("powerup/ragekitact",CHAN_7,CHANF_LOOP,.5,ATTN_NONE); } override void OnDestroy() { Super.OnDestroy(); A_StopSound(CHAN_VOICE); A_StopSound(CHAN_7); } } Class RagekitArmor : SWWMArmor { Default { +SWWMARMOR.NOHITSOUND; +SWWMARMOR.NOHITFLASH; +SWWMARMOR.NODRAIN; SWWMArmor.ArmorPriority 8; } override int HandleDamage( int damage, Name damageType, int flags ) { if ( damageType != 'EndLevel' ) return 0; return int(damage*.75); } override void DoEffect() { Super.DoEffect(); if ( !Owner.FindInventory('RagekitPower') ) Destroy(); } } Class RagekitPower : Powerup { Mixin SWWMShadedPowerup; Actor l, snd; Inventory am; int lasteffect; transient int lastpulse, lastrage; override double GetSpeedFactor() { return 2.; } Default { Powerup.Duration -30; Inventory.Icon "graphics/HUD/Icons/I_Ragekit.png"; Powerup.Color "FF 00 00", 0.2; +INVENTORY.ADDITIVETIME; } override void InitEffect() { Super.InitEffect(); if ( !Owner ) return; if ( !(Owner is 'Demolitionist') ) { // only usable by Demolitionist Destroy(); return; } if ( Owner.player == players[consoleplayer] ) lastrage = SWWMHandler.AddOneliner("ragekit",2,20)+40; Owner.A_AlertMonsters(swwm_uncapalert?0:5000); SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30); Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); lasteffect = int.min; lastpulse = max(lastpulse,gametic+35); Demolitionist(Owner).lastbump *= .95; l = Spawn("RagekitLight",Owner.pos); l.target = Owner; l.master = self; } override void DoEffect() { Super.DoEffect(); if ( !Owner ) return; if ( !snd ) snd = Spawn("RageSnd",Owner.pos); snd.target = Owner; snd.master = self; if ( !(level.maptime%30) ) { SWWMHandler.DoFlash(Owner,Color(16,255,0,0),5); Owner.GiveBody(1,100); Owner.A_AlertMonsters(swwm_uncapalert?0:2000); if ( (Owner.player == players[consoleplayer]) && (gametic > lastrage) && (swwm_mutevoice < 2) ) lastrage = SWWMHandler.AddOneliner("ragekit",2,5)+20; Owner.A_QuakeEx(2,2,2,Random[Rage](1,2),0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.5); lastpulse = max(lastpulse,gametic+10); Demolitionist(Owner).lastbump *= .995; } if ( am ) return; am = Owner.FindInventory("RagekitArmor"); if ( !am ) { am = Inventory(Spawn("RagekitArmor")); am.AttachToOwner(Owner); } } override void EndEffect() { Super.EndEffect(); if ( !Owner ) return; Owner.A_StartSound("powerup/ragekitend",CHAN_ITEMEXTRA,CHANF_OVERLAP); SWWMHandler.DoFlash(Owner,Color(128,255,0,0),30); Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); Owner.A_AlertMonsters(2000); Demolitionist(Owner).lastbump *= .9; if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_RAGEKIT")); } void DoHitFX() { if ( level.maptime <= lasteffect+5 ) return; Owner.GiveBody(5,100); Owner.A_AlertMonsters(swwm_uncapalert?0:5000); SWWMHandler.DoFlash(Owner,Color(64,255,0,0),10); Owner.A_QuakeEx(8,8,8,Random[Rage](3,8),0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); if ( (Owner.player == players[consoleplayer]) && (gametic > lastrage) && (swwm_mutevoice < 2) ) lastrage = SWWMHandler.AddOneliner("ragekit",2,5)+40; Owner.A_StartSound("powerup/ragekithit",CHAN_POWERUP,CHANF_OVERLAP); lasteffect = level.maptime; lastpulse = max(lastpulse,gametic+35); Demolitionist(Owner).lastbump *= .9; } override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags ) { if ( passive ) return; if ( (damageType == 'Melee') || (damageType == 'Jump') || (damageType == 'Dash') || (damageType == 'Buttslam') || (damageType == 'GroundPound') || (damageType == 'HammerShockwave') ) { newdamage = damage*8; DoHitFX(); } } } Class RagekitX : SWWMItemOverlay { Default { RenderStyle "Normal"; } } Class Ragekit : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA); Owner.GiveBody(100,100); SWWMUtility.AchievementProgressInc("rage",1,Owner.player); if ( !(Owner is 'Demolitionist') ) { SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30); Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); return true; } let r = RagekitPower(Owner.FindInventory("RagekitPower")); if ( r ) { r.EffectTics += r.default.EffectTics; SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30); Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); r.lastpulse = max(r.lastpulse,gametic+35); Demolitionist(Owner).lastbump *= .95; } else Owner.GiveInventory("RagekitPower",1); return true; } override void PreTravelled() { if ( tracer ) tracer.Destroy(); } override void Travelled() { if ( tracer ) return; tracer = Spawn("RagekitX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } override void PostBeginPlay() { Super.PostBeginPlay(); tracer = Spawn("RagekitX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } Default { Tag "$T_RAGEKIT"; Stamina 500000; Inventory.Icon "graphics/HUD/Icons/I_Ragekit.png"; Inventory.PickupSound "misc/p_pkup"; Inventory.UseSound "powerup/ragekit"; Inventory.PickupMessage "$T_RAGEKIT"; Inventory.MaxAmount 5; Inventory.InterHubAmount 5; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; } States { Spawn: XZW1 A -1; Stop; } } Class Omnisight : Inventory { Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; override bool TryPickup( in out Actor toucher ) { if ( !level.allmap ) { Actor rt = toucher; if ( toucher.player ) rt = toucher.player.mo; if ( rt.player == players[consoleplayer] ) { rt.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP); // automatically zoom out so the player can know how far this goes CVar.FindCVar('swwm_mm_zoom').SetFloat(2.); } if ( rt is 'Demolitionist' ) Demolitionist(rt).lastbump *= 1.1; level.allmap = true; // activate all interest markers let ti = ThinkerIterator.Create("SWWMInterestMarker",STAT_MAPMARKER); Actor a; while ( a = Actor(ti.Next()) ) a.bDORMANT = false; // "spread" to all players for ( int i=0; i mindist) && !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; mindist = dist; lamp = a; master = a.target; if ( CompanionLamp(lamp).moff.Find(self) == CompanionLamp(lamp).moff.Size() ) CompanionLamp(lamp).moff.Push(self); if ( master && master.player ) SetFriendPlayer(master.player); else bFRIENDLY = false; } } if ( !lamp || (lamp.frame == 0) || (Distance3DSquared(lamp) > 62500) || !CheckSight(lamp,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false; if ( target && (target.Health > 0) && CheckSight(target) ) return false; return true; } void A_SmoothWander() { if ( level.Vec3Diff(pos,trail).length() < speed ) { double ang = FRandom[Moth](0,360); double pt = FRandom[Moth](-30,30); double dist = FRandom[Moth](20,40); ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; } Vector3 newpos = level.Vec3Offset(pos,ofs); if ( level.IsPointInLevel(newpos) ) trail = newpos; if ( vel.length() > 0 ) { Vector3 uvel = vel.unit(); angle += Clamp(deltaangle(angle,atan2(uvel.y,uvel.x)),-5.,5.); pitch += Clamp(deltaangle(pitch,asin(-uvel.z)),-5.,5.); } vel *= .8; Vector3 dir = level.Vec3Diff(pos,trail); if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.05,.4*speed,.5*speed); } void A_SmoothChase() { if ( !target || (target.Health <= 0) ) { A_ClearTarget(); SetStateLabel("Spawn"); return; } if ( CheckMeleeRange() ) { SetStateLabel("Melee"); return; } Vector3 dest = target.Vec3Offset(0,0,target.height*.75); Vector3 dir = level.Vec3Diff(pos,dest); if ( dir.length() > 0 ) { Vector3 dirunit = dir.unit(); FLineTraceData d; LineTrace(atan2(dirunit.y,dirunit.x),dir.length(),asin(-dirunit.z),data:d); if ( (d.HitType != TRACE_HitActor) && (d.HitActor != target) ) { A_Chase(); return; } } if ( vel.length() > 0 ) { Vector3 uvel = vel.unit(); angle = atan2(uvel.y,uvel.x); pitch = asin(-uvel.z); } vel *= .8; if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.3*speed,2.*speed); } void A_FollowLamp() { if ( !lamp ) { SetStateLabel("Spawn"); return; } double dst = level.Vec3Diff(pos,trail).length(); if ( (dst < speed) || (dst > 50) ) { double ang = FRandom[Moth](0,360); double pt = FRandom[Moth](-30,30); double dist = FRandom[Moth](20,30); ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; } Vector3 newpos = level.Vec3Offset(lamp.Vec3Offset(0,0,lamp.height/2),ofs); if ( level.IsPointInLevel(newpos) ) trail = newpos; if ( vel.length() > 0 ) { Vector3 uvel = vel.unit(); angle = atan2(uvel.y,uvel.x); pitch = asin(-uvel.z); } vel *= .8; Vector3 dir = level.Vec3Diff(pos,trail); if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.4*speed,2.*speed); Vector3 diff = level.Vec3Diff(pos,lamp.pos); if ( (diff.x > -8) && (diff.x < 8) && (diff.y > -8) && (diff.y < 8) && (diff.z > -4) && (diff.z < lamp.height+4) ) { if ( diff.x < 0 ) vel.x -= .2; else vel.x += .2; if ( diff.y < 0 ) vel.y -= .2; else vel.y += .2; if ( diff.z < 0 ) vel.z -= .2; else vel.z += .2; } } void A_SmoothMove() { if ( vel.length() > 0 ) { Vector3 uvel = vel.unit(); angle = atan2(uvel.y,uvel.x); pitch = asin(-uvel.z); } vel *= .8; } void A_Scrape() { if ( CheckMeleeRange() ) { A_FaceTarget(0,0); lifespan -= 5; Vector3 awaydir = level.Vec3Diff(target.Vec3Offset(0,0,target.height),pos).unit(); vel += awaydir*8.; int dmg = target.DamageMobj(self,master?master:Actor(self),GetMissileDamage(0,0),'Melee',Random[Moth](0,8)?DMG_NO_PAIN:0); if ( target && !target.bNOBLOOD && !target.bDORMANT && !target.bINVULNERABLE ) { target.TraceBleed(dmg,self); target.SpawnBlood(pos,atan2(awaydir.y,awaydir.x)+180,dmg); } A_StartSound("moth/scrape",CHAN_WEAPON,CHANF_OVERLAP,.2,2.5); DamageMobj(target,target,1,'Melee'); } } override void Tick() { Super.Tick(); if ( isFrozen() || (freezetics > 0) ) return; if ( isEntranced() ) { lifespan = 100; return; } if ( target && (target.Health > 0) ) lifespan = max(20,lifespan); lifespan--; if ( lifespan <= 0 ) { let s = Spawn("SWWMSmallSmoke",pos); s.alpha *= .3; Destroy(); } } States { Spawn: XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced"); XZW1 BC 1 { A_SmoothWander(); A_Look(); } Loop; See: // go for enemies XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced"); XZW1 BC 1 A_SmoothChase(); Loop; See.Entranced: // follow the lamp XZW1 B 0 A_JumpIf(!isEntranced(),"Spawn"); XZW1 BC 1 A_FollowLamp(); Loop; Melee: XZW1 B 0 A_Scrape(); XZW1 BCBC 1 A_SmoothMove(); Goto See; Death: TNT1 A 1 { A_StartSound("moth/die",CHAN_VOICE,CHANF_OVERLAP,.6,2.5); let s = Spawn("SWWMSmallSmoke",pos); s.alpha *= .3; } Stop; } } Class LampMoth2 : LampMoth { Default { Tag "$T_WMOTH"; DamageFunction 3; Speed 3; Scale 1.5; Health 200; } } Class LampMashiro : Actor abstract { // // ~nothing here yet, but she will make an appearance someday~ // // ⠀⠀⠀⠀⠤⠀⠄⠀⠀⠀⠳⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡣⣁⢌⡀⢄⠁⡁⠝⢿⣿⣿⣿⡻⣿⣿⣷⣦ // ⠀⠀⠠⠀⠠⠀⠀⡀⠘⣠⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣊⢇⠠⢐⢄⡙⢿⣿⣿⣾⠫⣻⣿ // ⠀⡄⠂⢴⠠⠌⠰⢇⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠣⡣⡱⣌⠻⣿⣿⣿⣿⣿ // ⠀⠁⠛⠂⠀⠉⡍⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠘⣮⣾⡮⢦⡹⣿⣿⣿⣿ // ⠄⠠⠉⢼⡇⠶⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⢿⣿⣿⡦⢣⡉⢝⢝⢽ // ⠀⠀⠚⠃⠀⠀⡀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⢸⣿⣿⣿⢀⢃⠀⡁⠑ // ⠰⠀⠂⠁⠀⠀⡆⠀⠀⠀⣴⣿⣿⣿⣿⣿⠋⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⣿⣿⣿⣇⠆⢃⠈⡇ // ⠀⠀⠀⢠⠄⣠⣇⠀⢀⣾⣿⣿⣿⣿⣿⡃⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⢔⢌⢆⢑ // ⠀⠀⠀⢀⣶⣿⣷⢀⣾⣿⣿⣿⣿⡯⡊⠀⢸⡏⣟⣻⣽⣭⣽⣛⡛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢕⢕⠄ // ⠀⢠⣶⣿⣿⢿⢃⣾⣿⣿⣿⣿⡫⡪⠀⠀⢸⡇⣿⣿⣿⣿⣿⣿⣿⣦⡘⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢔⢕⢅ // ⠀⣺⣿⡉⠕⠁⣼⣿⣿⣿⡿⡫⡪⠂⠀⢔⢸⢀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣶⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣪⠃⣿⣿⣿⣿⡟⢄⣡⣵ // ⡛⠟⢋⠀⠀⢸⣿⣿⣿⣿⢠⢫⡊⠀⡔⢵⡈⢠⢣⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠡⠚⠓⠪⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡫⡎⣸⣿⣿⣿⡟⢠⢃⣂⡿ // ⡃⡊⢃⡔⡠⣿⣿⣿⣿⡇⣜⡘⢀⢜⡀⡅⡆⠘⢐⠁⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢾⠿⢿⣿⣿⣿⣿⣷⣾⣽⡻⢿⣿⣿⣿⣿⣿⣿⣿⡿⣛⣿⣿⣟⡜⠠⣻⣿⣿⡟⠴⠃⠉⠁⠀ // ⡺⢠⡾⣸⡷⣿⣿⣿⣿⠂⣓⠂⡎⡺⠪⠒⠃⠀⠀⠀⠀⠀⠙⠿⣯⢻⣿⣿⣿⡟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣾⣿⡷⣲⣿⣿⣿⣿⣿⣿⣿⣷⣝⢿⣿⣿⣿⡿⡫⡺⣽⣿⣿⡗⠀⢸⣿⣿⠏⠀⠀⠀⠀⠀⠀ // ⣴⣿⢣⣿⢳⣿⣿⣿⡿⡨⡊⠠⡪⡢⠂⠀⠀⠀⠀⠀⢀⣀⠀⠀⠘⢧⡻⣿⣿⡇⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢳⣝⠿⡫⡪⣪⡮⣾⣿⡟⢠⠅⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀ // ⣿⡏⣾⢣⡾⣿⣿⣿⡇⢪⠂⡨⡠⠀⠀⠀⠀⢀⡴⠋⠉⠈⠉⢦⡀⠠⢅⠹⣿⢰⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢟⣫⣾⡿⣫⣦⠸⢊⡄⣼⣿⡟⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⡿⢸⣏⣾⣇⣿⣿⣿⡇⢵⢀⢓⠀⠀⠀⠀⠀⡮⠀⠀⠀⠀⠀⠈⣇⠀⠧⡗⡈⢿⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⣯⣾⣿⡿⠛⠊⣬⠴⢪⠋⣼⣿⠟⢔⢝⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⠃⡿⣸⣿⣿⢸⣿⣿⡇⡊⢰⢱⢂⡀⠀⠀⠀⢳⠀⠀⠀⠀⠀⢰⡇⠀⣸⢐⡈⢄⢿⠿⠿⢿⡿⠽⢫⣵⣿⡿⠿⠛⠛⠙⠉⠁⠀⠞⠙⠃⠍⡁⠖⡪⡫⡪⣻⢁⣾⡿⢃⢞⢕⡝⢰⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀ // ⢸⢣⣿⣿⡗⡈⢿⣿⡇⠀⢸⣿⣷⣵⣄⠀⠀⠈⠲⣠⣠⣠⡴⠋⠀⠀⢒⢔⢂⢀⢎⢇⣓⡕⠂⡚⡩⢡⢰⢂⣓⡣⣢⠒⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⡲⡑⢡⣾⠟⡠⡣⣂⢇⠃⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀ // ⡟⣾⣿⡿⣸⢸⡘⣿⣷⠀⢸⣿⣿⣿⣿⣿⣦⣄⣀⣀⣀⣀⣀⣠⣤⣜⢔⢕⢕⢔⢕⢕⣗⣇⣳⡪⣺⢐⢱⢑⢒⠖⠁⠀⠀⢀⠄⠔⠖⢦⣀⠀⠀⠰⠌⣴⠟⡡⡪⡪⡪⡜⡜⣸⣿⣿⣿⣿⡿⡢⠀⠀⠀⠀⠀⠀ // ⢱⣿⣿⡣⢑⠔⡕⠘⣿⡀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣷⣵⣕⣥⣣⣣⡣⡪⣐⢱⢠⡃⠀⠀⠀⣴⠁⠀⠀⠀⠀⠸⣆⠀⠀⠘⠀⡪⡪⡪⡪⡪⣸⢡⣿⣿⣿⣿⣿⡯⣺⡀⢤⡀⠀⠀⠀ // ⣿⣿⣟⣝⣝⣺⠁⠀⠘⣧⢘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣇⣣⠣⡘⡀⠀⠀⠀⡟⠀⠀⠀⠀⠀⠨⡗⠀⠀⠀⠈⢮⠺⡪⢊⢫⠃⣾⣿⣿⣿⣿⣿⡪⡏⣦⡀⢹⣷⣤⣀ // ⣿⣿⢑⢇⢆⠆⠀⠀⣸⣎⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⣧⣀⠀⠀⢻⡀⠀⠀⠀⢀⡼⠃⠀⠀⠀⠀⠸⢘⢜⡐⡕⣸⣿⣿⣿⣿⣿⣃⣟⣱⣿⣿⣧⢻⣿⣿ // ⣿⣟⢕⢕⢼⠀⠀⠀⣿⣿⣧⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠉⠓⠒⠓⠋⠀⠀⠀⠀⠀⠠⠀⠱⡱⡨⢣⣿⣿⣿⣿⣿⢗⡯⣱⣿⣿⣿⢇⣄⠃⠙ // ⣿⡒⡢⡢⡃⠀⠀⠀⣿⣿⣿⣷⣝⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣄⣀⣀⣀⣀⢀⣀⡀⡠⣀⡢⡪⠂⣽⣿⣿⣿⣿⣟⡕⣺⣿⣿⣿⣏⣿⣷⢔⡄ // ⡿⡸⢰⢔⠂⠀⠀⠀⢿⣿⣿⣿⣿⣯⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣯⢮⡊⡠⣰⣿⣿⣿⣿⡿⣡⣿⣿⣿⣿⡟⣾⣿⣿⢵⠅ // ⣟⢕⢝⣗⠀⠀⠀⡹⡘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⡾⢱⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⡿⣹⣿⣿⡟⡹⢰ // ⢕⢕⢕⢕⠠⡢⣪⢂⢇⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⣾⣿⢣⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⡿⣱⣿⣿⣿⡫⡇⣼ // ⢕⢕⢕⠅⣘⡪⡚⡔⠍⠂⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣫⣾⣿⢟⢁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢣⣿⣿⣿⣏⡼⢠⣿ // ⢕⢕⢕⠀⣖⡪⡪⣂⠀⠐⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢟⣫⣵⡿⢟⣫⡴⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⣸⣿⣿⣿⢒⠇⣼⣿ // ⢕⢅⢕⠨⣃⢪⢨⢂⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣳⢰⢬⡙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣍⣛⠻⠿⠿⠿⢛⣯⡭⠶⣞⣛⣭⣵⣾⣿⠿⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⣿⣿⣿⡯⡝⢰⣿⣿ // ⢝⢕⠇⢸⠱⡑⡁⠀⣀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣬⣃⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⡱⢃⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⣼⣿⣿⣿⣸⢡⣿⣿⣿ // ⢕⢕⠅⢜⠕⡠⣢⣾⣿⣷⣤⣄⢀⣬⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢛⠴⢡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⢠⣿⣿⣿⢝⢡⣿⣿⣿⣿ // ⢕⢕⠅⣕⢥⡶⢀⠻⣿⣿⣿⣟⢿⣿⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢫⡐⣕⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⢀⣿⣿⣿⠏⢁⣿⣿⣿⣿⣿ // ⢕⢕⢠⢗⣛⣓⣁⢱⣾⣿⣿⣿⣷⣷⣽⣿⡻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢛⢔⠕⠓⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⢀⣾⣿⡿⡣⢎⣿⣿⣿⣿⣿⣿ // ⡕⡕⠠⡅⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡝⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠐⣁⡴⡴⢣⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⢠⣾⠿⣫⠞⣱⣿⣿⣿⣿⣿⠟⡡ // ⢅⢇⢘⢕⢍⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠛⠉⠁⠀⢀⢜⢕⢅⠕⣡⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠐⡫⡠⠜⣡⣾⣿⣿⡿⠟⠫⠦⠪⠊ // ⢕⢕⢸⣵⣷⣾⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣦⣤⡩⣉⣉⡩⣉⣉⠉⠉⠈⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⣤⢔⢗⢕⠕⣰⣿⣿⣿⣿⣿⣿⣿⣿⡿⢋⢔⢕⠕⣓⠏⣊⠰⠻⠟⠛⣉⠅⢄⠂⡪⡪⡪⠨ // // ~it actually won't be her, but one can dream~ // } Class CompanionLamp : Actor { Vector3 Trail; Array moff; Actor parent; bool justteleport; Default { Tag "$T_LAMP"; +NOGRAVITY; +NOTELEPORT; +DONTSPLASH; +INTERPOLATEANGLES; +LOOKALLAROUND; +FRIENDLY; +NOBLOCKMONST; Radius 4; Height 16; } // random chance to spawn moths void A_Moth() { // count up special1++; for ( int i=0; i= 30) ) return; // spawn a moth at a random offset double ang = FRandom[Moth](0,360); double pt = FRandom[Moth](-30,30); double dist = FRandom[Moth](10,30); Vector3 ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist; Vector3 spawnpos = level.Vec3Offset(Vec3Offset(0,0,height/2),ofs); if ( !level.IsPointInLevel(spawnpos) ) return; // higher chance of white moths if carrying the Mashiro plush //int mchance = parent.FindInventory("MothPlushy")?3:9; let m = LampMoth(Spawn(Random[Moth](0,9)?"LampMoth":"LampMoth2",spawnpos)); if ( !m.TestMobjLocation() ) { m.Destroy(); return; } let s = Spawn("SWWMSmallSmoke",m.pos); s.alpha *= .3; m.master = parent; m.lamp = self; m.trail = m.pos; moff.Push(m); SWWMUtility.AchievementProgressInc("moth",1,parent.player); } override void PostBeginPlay() { Super.PostBeginPlay(); if ( !parent || !SWWMLamp(master) ) { Destroy(); return; } Spawn("SWWMItemFog",pos); Trail = pos; } override void Tick() { Super.Tick(); if ( !parent || !SWWMLamp(master) ) { Destroy(); return; } if ( isFrozen() || (freezetics > 0) ) return; // update trailing position bool foundspot = false; for ( int i=0; i<180; i+=5 ) { for ( int j=1; j>=-1; j-=2 ) { double ang = (parent.angle-180)+i*j; Vector3 testpos = level.Vec3Offset(parent.pos,SWWMUtility.RotateVector3((32,0,parent.height-8+1.5*sin(level.maptime*3.)),ang)); if ( !level.IsPointInLevel(testpos) ) continue; Vector3 oldpos = pos; Vector3 oldprev = prev; Actor oldblockingmobj = blockingmobj; Line oldblockingline = blockingline; Sector oldblockingfloor = blockingfloor, oldblockingceiling = blockingceiling; SetOrigin(testpos,false); if ( !TestMobjLocation() || SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) || BlockingFloor || BlockingCeiling ) { SetOrigin(oldpos,false); prev = oldprev; blockingmobj = oldblockingmobj; blockingline = oldblockingline; blockingfloor = oldblockingfloor; blockingceiling = oldblockingceiling; continue; } SetOrigin(oldpos,false); prev = oldprev; blockingmobj = oldblockingmobj; blockingline = oldblockingline; blockingfloor = oldblockingfloor; blockingceiling = oldblockingceiling; Trail = testpos; foundspot = true; } // check at most for a 45 degree offset if ( foundspot && (i > 45) ) break; } Vector3 diff = level.Vec3Diff(pos,parent.pos); if ( (diff.length() > 400) || justteleport ) { Vector3 rel = level.Vec3Diff(pos,trail); justteleport = false; Actor f = Spawn("SWWMItemFog",pos); f.A_StartSound("lamp/disappear",CHAN_VOICE); // carry over the moths for ( int i=0; i -16) && (diff.x < 16) && (diff.y > -16) && (diff.y < 16) && (diff.z > -16) && (diff.z < parent.height+8) ) { if ( diff.x < 0 ) vel.x -= .2; else vel.x += .2; if ( diff.y < 0 ) vel.y -= .2; else vel.y += .2; if ( diff.z < 0 ) vel.z -= .2; else vel.z += .2; blocked = true; } if ( blocked ) return; Vector3 dir = level.Vec3Diff(pos,trail); if ( dir.length() > 0 ) vel += dir.unit()*min(dir.length()*.05,20.); } States { Spawn: XZW1 A 1 { if ( SWWMLamp(master) && SWWMLamp(master).bActive ) { A_StartSound("lamp/on",CHAN_ITEMEXTRA,CHANF_OVERLAP); return ResolveState("Active"); } return ResolveState(null); } Wait; Active: XZW1 B 1 { A_Moth(); if ( !SWWMLamp(master) || !SWWMLamp(master).bActive ) { A_StartSound("lamp/off",CHAN_ITEMEXTRA,CHANF_OVERLAP); return ResolveState("Spawn"); } return ResolveState(null); } Wait; } } Class SWWMLamp : Inventory { Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; bool bActive, bActivated; TextureID OnIcon; Actor thelamp; int charge; Property Charge : charge; override Inventory CreateCopy( Actor other ) { // additional lore SWWMLoreLibrary.Add(other.player,"MothLamp"); return Super.CreateCopy(other); } override bool HandlePickup( Inventory item ) { // add charge if ( item.GetClass() == GetClass() ) { if ( (Charge >= Default.Charge) && (Amount+item.Amount > MaxAmount) ) { // sell excess int sellprice = abs(Stamina)/2; SWWMScoreObj.Spawn(sellprice,level.Vec3Offset(Owner.pos,SWWMUtility.Vec3FromAngles(FRandom[ScoreBits](0,360),FRandom[ScoreBits](-90,90))*8.+(0,0,Owner.Height/2))); SWWMCredits.Give(Owner.player,sellprice); if ( Owner.player ) { if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRA_FEM":"$SWWM_SELLEXTRA"),GetTag(),sellprice); else Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRAREM_FEM":"$SWWM_SELLEXTRAREM"),Owner.player.GetUserName(),GetTag(),sellprice); } } else if ( Charge > 0 ) { int AddCharge = Charge+SWWMLamp(item).Charge; Charge = min(Default.Charge,AddCharge); // if there's charge to spare, increase amount if ( AddCharge > Charge ) { if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max; Amount = min(MaxAmount,Amount+item.Amount); Charge = AddCharge-Charge; } } else { if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max; // new copy, increase and take its charge Amount = min(MaxAmount,Amount+item.Amount); Charge = SWWMLamp(item).Charge; } item.bPickupGood = true; return true; } return Super.HandlePickup(item); } override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; bActivated = true; bActive = !bActive; if ( !OnIcon ) OnIcon = TexMan.CheckForTexture("graphics/HUD/Icons/I_Lamp.png"); Icon = bActive?OnIcon:default.Icon; // don't consume on use Amount++; return true; } override bool ShouldSpawn() { if ( deathmatch ) return false; return Super.ShouldSpawn(); } override void PreTravelled() { // remove the lamp if ( thelamp ) thelamp.Destroy(); } override void DoEffect() { Super.DoEffect(); if ( !thelamp && bActivated ) { thelamp = Spawn("CompanionLamp",level.Vec3Offset(Owner.pos,SWWMUtility.RotateVector3((20,0,24),Owner.angle))); CompanionLamp(thelamp).parent = Owner; thelamp.master = self; let f = Spawn("SWWMItemFog",thelamp.pos); f.A_StartSound("lamp/appear",CHAN_VOICE); } if ( bActive && !(level.maptime%35) && !isFrozen() ) Charge--; if ( Charge <= 0 ) { Amount--; if ( Amount <= 0 ) DepleteOrDestroy(); else Charge = default.Charge; } } override void DetachFromOwner() { Super.DetachFromOwner(); if ( thelamp ) { let f = Spawn("SWWMItemFog",thelamp.pos); f.A_StartSound("lamp/disappear",CHAN_VOICE); thelamp.Destroy(); } Icon = default.Icon; bActive = false; bActivated = false; } clearscope bool isBlinking() const { return ( (Charge < 10) && (level.maptime&8) ); } Default { Tag "$T_LAMP"; Inventory.Icon "graphics/HUD/Icons/I_LampOff.png"; Inventory.PickupSound "misc/p_pkup"; Inventory.PickupMessage "$I_LAMP"; Inventory.Amount 1; Inventory.MaxAmount 5; Inventory.InterHubAmount 5; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; SWWMLamp.Charge 100; Stamina 70000; } States { Spawn: XZW1 A -1; Stop; } } Class BarrierLight : PointLightAttenuated { Default { Args 32,72,0,80; } override void Tick() { Super.Tick(); if ( !target || !master ) { Destroy(); return; } if ( target.player ) SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true); else SetOrigin(target.Vec3Offset(0,0,target.height/2),true); bDORMANT = Powerup(master).isBlinking(); } } Class BarrierSnd : SWWMNonInteractiveActor { override void Tick() { if ( !target || !master ) { Destroy(); return; } SetOrigin(target.pos,true); if ( players[consoleplayer].Camera == target ) { A_SoundVolume(CHAN_VOICE,0.); A_SoundVolume(CHAN_7,.1); } else { A_SoundVolume(CHAN_VOICE,.06); A_SoundVolume(CHAN_7,0.); } } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/barrieract",CHAN_VOICE,CHANF_LOOP,.06,1.5); A_StartSound("powerup/barrieract",CHAN_7,CHANF_LOOP,.1,ATTN_NONE); } override void OnDestroy() { Super.OnDestroy(); A_StopSound(CHAN_VOICE); A_StopSound(CHAN_7); } } Class BarrierArmor : SWWMArmor { Default { +SWWMARMOR.NOHITSOUND; +SWWMARMOR.NOHITFLASH; +SWWMARMOR.NODRAIN; SWWMArmor.ArmorPriority 10; } override int HandleDamage( int damage, Name damageType, int flags ) { if ( (damageType == 'Fire') || (damageType == 'Ice') || (damageType == 'Slime') || (damageType == 'Electric') || (damageType == 'Plasma') || (damageType == 'Radiation') || (damageType == 'Wind') || (damageType == 'Water') || (damageType == 'Corroded') || (damageType == 'Lava') ) return damage; return 0; } override void DoEffect() { Super.DoEffect(); if ( !Owner.FindInventory('BarrierPower') ) Destroy(); } } Class BarrierPower : PowerIronFeet { Mixin SWWMShadedPowerup; Actor snd, l; Inventory am; Default { Inventory.Icon "graphics/HUD/Icons/I_Barrier.png"; Powerup.Duration -60; Powerup.Color "20 FF 00", 0.1; Powerup.Mode "Full"; // no leaky damage +INVENTORY.ADDITIVETIME; } override void InitEffect() { Super.InitEffect(); if ( !Owner ) return; l = Spawn("BarrierLight",Owner.pos); l.target = Owner; l.master = self; if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 0.95; } override void EndEffect() { Super.EndEffect(); if ( !Owner ) return; Owner.A_StartSound("powerup/barrierend",CHAN_ITEMEXTRA,CHANF_OVERLAP); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 0.95; if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_BARRIER")); } override void DoEffect() { // don't reset air supply like PowerIronFeet, call parent instead Powerup.DoEffect(); if ( !Owner ) return; if ( !snd ) snd = Spawn("BarrierSnd",Owner.pos); snd.target = Owner; snd.master = self; if ( !am ) { am = Owner.FindInventory("BarrierArmor"); if ( !am ) { am = Inventory(Spawn("BarrierArmor")); am.AttachToOwner(Owner); } } // break ourselves if we're in an endlevel sector bool endlv = false; for ( int i=0; i= (Owner.pos.z+Owner.Height) ) continue; if ( !(ff.model.flags&Sector.SECF_ENDLEVEL) ) continue; endlv = true; break; } if ( !endlv && (Owner.pos.z <= Owner.floorz) ) { bool damageterrain = false; if ( (Owner.CurSector.damageamount > 0) && (Owner.CurSector.damageinterval > 0) ) damageterrain = true; else { let t = Owner.CurSector.GetFloorTerrain(0); if ( t && (t.DamageAmount > 0) && (t.DamageTimeMask > 0) ) damageterrain = true; } if ( damageterrain && (Owner.CurSector.flags&Sector.SECF_ENDLEVEL) ) endlv = true; } if ( !endlv ) return; EffectTics = min(0,EffectTics); } } Class EBarrier : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; int terrainwait; override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); let b = BarrierPower(Owner.FindInventory("BarrierPower")); if ( b ) { b.EffectTics += b.default.EffectTics; if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= 0.95; } else Owner.GiveInventory("BarrierPower",1); SWWMUtility.AchievementProgressInc("barrier",1,Owner.player); return true; } override void PreTravelled() { if ( tracer ) tracer.Destroy(); } override void Travelled() { Super.Travelled(); if ( tracer ) return; tracer = Spawn("EBarrierX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } override void PostBeginPlay() { Super.PostBeginPlay(); tracer = Spawn("EBarrierX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } override void DoEffect() { Super.DoEffect(); if ( !Owner || (Owner.Health <= 0) ) return; // check terrain for auto-use let b = Powerup(Owner.FindInventory("BarrierPower")); if ( b && (b.EffectTics > 5) ) { terrainwait = 20; return; } bool damageterrain = false; bool endlevelterrain = false; // check any 3d floors first for ( int i=0; i= (Owner.pos.z+Owner.Height) ) continue; if ( ff.model.flags&Sector.SECF_ENDLEVEL ) endlevelterrain = true; damageterrain = true; break; } if ( !damageterrain && ((Owner.pos.z == Owner.CurSector.floorplane.ZAtPoint(Owner.pos.xy)) || Owner.waterlevel) ) { if ( (Owner.CurSector.damageamount > 0) && (Owner.CurSector.damageinterval > 0) ) damageterrain = true; else { let t = Owner.CurSector.GetFloorTerrain(0); if ( t && (t.DamageAmount > 0) && (t.DamageTimeMask > 0) ) damageterrain = true; } if ( damageterrain && (Owner.CurSector.flags&Sector.SECF_ENDLEVEL) ) endlevelterrain = true; } // do not auto-use for these if ( endlevelterrain ) return; if ( !damageterrain ) { terrainwait = max(0,terrainwait-1); return; } else terrainwait++; if ( terrainwait <= 20 ) return; terrainwait = 0; Owner.UseInventory(self); } Default { Tag "$T_BARRIER"; Stamina 80000; Inventory.Icon "graphics/HUD/Icons/I_Barrier.png"; Inventory.PickupSound "misc/p_pkup"; Inventory.UseSound "powerup/barrier"; Inventory.PickupMessage "$T_BARRIER"; Inventory.MaxAmount 5; Inventory.InterHubAmount 5; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; // must copy height of vanilla radsuit Height 46; } States { Spawn: XZW1 A -1; Stop; } } Class EBarrierX : SWWMItemOverlay {} Class TendrilTracer : LineTracer { Actor ignore; Array ShootThroughList; Array HitList; override ETraceStatus TraceCallback() { if ( Results.HitType == TRACE_HitActor ) { if ( Results.HitActor == ignore ) return TRACE_Skip; if ( Results.HitActor.bSHOOTABLE ) { let ent = new("HitListEntry"); ent.hitactor = Results.HitActor; ent.hitlocation = Results.HitPos; ent.x = Results.HitVector; hitlist.Push(ent); } return TRACE_Skip; } else if ( Results.HitType == TRACE_HitWall ) { ShootThroughList.Push(Results.HitLine); return TRACE_Skip; } return TRACE_Skip; } } // main heatseeker Class MykradvoTendril : SWWMNonInteractiveActor { Vector3 nextpos, nextdir; action void A_Trace() { tics = bMISSILEMORE?2:1; Vector3 x, y, z; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); if ( !bSTANDSTILL ) { let t = new("TendrilTracer"); t.ignore = target; t.hitlist.Clear(); t.ShootThroughList.Clear(); t.Trace(pos,CurSector,x,speed,0); for ( int i=0; i 1 ) { dirto /= dist; dir = (dir+dirto*(clamp(1.-(dist/4000.),.25,1.)**1.5)).unit(); } // early split if ( dist < speed ) bMISSILEEVENMORE = true; } invoker.nextdir = dir; } action void A_Spread() { if ( bMISSILEMORE && bMISSILEEVENMORE ) { // spread into sub-tendrils Vector3 x, y, z; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); int ntendies = tracer?clamp(tracer.GetSpawnHealth()/400,2,10):2; for ( int i=0; i ReactionTime)) ) { if ( !bSTANDSTILL ) { int numpt = bMISSILEMORE?9:3; Vector3 x, y, z; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_FadeOut(bMISSILEMORE?.05:bSTANDSTILL?.2:.1); if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } Default { Obituary "$O_MYKRADVO"; RenderStyle "Add"; DamageFunction 100; ReactionTime 8; Speed 64; +INTERPOLATEANGLES; +FOILINVUL; +MISSILEMORE; } States { Spawn: TNT1 A 0 Bright; XZW1 A 1 Bright A_Trace(); XZW1 A 1 Bright { A_Spread(); return FindState("Fade")+Random[Mykradvo](0,11)*2; } Stop; Fade: #### # 20 Bright; XZW1 B -1 Bright; Stop; #### # 20 Bright; XZW1 C -1 Bright; Stop; #### # 20 Bright; XZW1 D -1 Bright; Stop; #### # 20 Bright; XZW1 E -1 Bright; Stop; #### # 20 Bright; XZW1 F -1 Bright; Stop; #### # 20 Bright; XZW1 G -1 Bright; Stop; #### # 20 Bright; XZW1 H -1 Bright; Stop; #### # 20 Bright; XZW1 I -1 Bright; Stop; #### # 20 Bright; XZW1 J -1 Bright; Stop; #### # 20 Bright; XZW1 K -1 Bright; Stop; #### # 20 Bright; XZW1 L -1 Bright; Stop; #### # 20 Bright; XZW1 M -1 Bright; Stop; } } // sub seekers Class MykradvoSmallTendril : MykradvoTendril { Default { Speed 16; DamageFunction 10; ReactionTime 20; -MISSILEMORE; } States { Fade: #### # 10 Bright; XZW1 B -1 Bright; Stop; #### # 10 Bright; XZW1 C -1 Bright; Stop; #### # 10 Bright; XZW1 D -1 Bright; Stop; #### # 10 Bright; XZW1 E -1 Bright; Stop; #### # 10 Bright; XZW1 F -1 Bright; Stop; #### # 10 Bright; XZW1 G -1 Bright; Stop; #### # 10 Bright; XZW1 H -1 Bright; Stop; #### # 10 Bright; XZW1 I -1 Bright; Stop; #### # 10 Bright; XZW1 J -1 Bright; Stop; #### # 10 Bright; XZW1 K -1 Bright; Stop; #### # 10 Bright; XZW1 L -1 Bright; Stop; #### # 10 Bright; XZW1 M -1 Bright; Stop; } } // non-hurting non-seekers Class MykradvoSmallNullTendril : MykradvoSmallTendril { Default { Speed 8; ReactionTime 6; +STANDSTILL; } States { Fade: #### # 5 Bright; XZW1 B -1 Bright; Stop; #### # 5 Bright; XZW1 C -1 Bright; Stop; #### # 5 Bright; XZW1 D -1 Bright; Stop; #### # 5 Bright; XZW1 E -1 Bright; Stop; #### # 5 Bright; XZW1 F -1 Bright; Stop; #### # 5 Bright; XZW1 G -1 Bright; Stop; #### # 5 Bright; XZW1 H -1 Bright; Stop; #### # 5 Bright; XZW1 I -1 Bright; Stop; #### # 5 Bright; XZW1 J -1 Bright; Stop; #### # 5 Bright; XZW1 K -1 Bright; Stop; #### # 5 Bright; XZW1 L -1 Bright; Stop; #### # 5 Bright; XZW1 M -1 Bright; Stop; } } Class MykradvoBurstLight : PaletteLight { Default { Tag "Purple"; ReactionTime 60; Args 0,0,0,400; } } // 'splode Class MykradvoBurst : SWWMNonInteractiveActor { Array targets; int nstep; Default { RenderStyle "Add"; +FORCEXYBILLBOARD; Scale 1.4; } void FlashPlayer( int str, double rad ) { if ( !SWWMUtility.InPlayerFOV(players[consoleplayer],self,rad) ) return; let mo = players[consoleplayer].Camera; double dist = Distance3D(mo); str = int(str*(1.-(dist/rad))); SWWMHandler.DoFlash(mo,Color(str,250,240,255),5); SWWMHandler.DoFlash(mo,Color(str,128,0,255),15); } override void PostBeginPlay() { nstep = clamp(targets.Size()/10,1,5); A_AlertMonsters(swwm_uncapalert?0:8000); A_QuakeEx(9,9,9,80,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:1000,rollintensity:2.); A_StartSound("powerup/mykradvo",CHAN_BODY,CHANF_DEFAULT,1.,.25); A_StartSound("powerup/mykradvo",CHAN_VOICE,CHANF_DEFAULT,1.,.25); FlashPlayer(100,1500); int numpt = Random[ExploS](20,30); for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( (special1++)%3 ) { if ( targets.Size() > 0 ) { int numpt = Random[ExploS](2,4); for ( int j=0; j 0) ) { let t = Spawn("MykradvoTendril",pos); t.angle = t.AngleTo(targ); t.pitch = SWWMUtility.PitchTo(t,targ,.5); t.target = target; t.tracer = targ; } else i--; targets.Delete(0); } } if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 2 Bright; TNT1 A 1 A_JumpIf(!IsActorPlayingSound(CHAN_VOICE)&&!(invoker.targets.Size()),1); Wait; TNT1 A 1; Stop; } } Class Mykradvo : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; Actor ringa[2]; Array targets; // quicksort (targets) private bool CmpDist( Actor ref, Vector3 a, Vector3 b ) { double dista = level.Vec3Diff(ref.pos,a).length(); double distb = level.Vec3Diff(ref.pos,b).length(); return (dista < distb); } private int partition_targets( Array a, int l, int h, Actor ref ) { Actor pv = a[h]; int i = (l-1); for ( int j=l; j<=(h-1); j++ ) { if ( CmpDist(ref,a[j].pos,pv.pos) ) { i++; Actor tmp = a[j]; a[j] = a[i]; a[i] = tmp; } } Actor tmp = a[h]; a[h] = a[i+1]; a[i+1] = tmp; return i+1; } private void qsort_targets( Array a, int l, int h, Actor ref ) { if ( l >= h ) return; int p = partition_targets(a,l,h,ref); qsort_targets(a,l,p-1,ref); qsort_targets(a,p+1,h,ref); } bool FindTargets( Actor t ) { targets.Clear(); // search all actively hostile enemies within 50m let ti = ThinkerIterator.Create("Actor"); Actor a; while ( a=Actor(ti.Next()) ) { // must be an active, shootable live monster if ( !a.bISMONSTER || !a.bSHOOTABLE || a.bDORMANT || (a.Health <= 0) ) continue; // skip non-hostiles if ( a.IsFriend(t) ) continue; // is targetting us and is within 10m // or // is visible and is within 100m if ( ((a.target == t) && SWWMUtility.SphereIntersect(a,t.pos,320)) || (t.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) && SWWMUtility.SphereIntersect(a,t.pos,3200)) ) targets.Push(a); } // sorted, so the closest take priority qsort_targets(targets,0,targets.Size()-1,t); return (targets.Size() > 0); } override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); Vector3 spawnpos = Owner.Vec3Angle(15,Owner.angle,Owner.Height*.7); if ( !FindTargets(Owner) ) { int numpt = Random[ExploS](8,12); let f = Spawn("SWWMPurplePickupFlash",spawnpos-(0,0,16)); f.Scale *= .5; for ( int i=0; i=2)?default.Icon:(primetim>=6)?(bFailed?OnIcon[1]:OnIcon[2]):OnIcon[0]; if ( !bFailed && (primetim == 20) ) Owner.A_StartSound("hahaha/hahaha",CHAN_POWERUP,CHANF_OVERLAP); primetim++; if ( (primetim <= 20) || (!bFailed && (primetim <= 50)) ) return; primetim = 0; bPrimed = false; Icon = default.Icon; if ( bFailed ) return; Vector3 safepos; double safeangle; if ( deathmatch ) [safepos, safeangle] = level.PickDeathmatchStart(); else [safepos, safeangle] = level.PickPlayerStart(Owner.PlayerNumber()); if ( !level.useplayerstartz ) safepos.z = ONFLOORZ; Vector3 oldpos = Owner.pos; if ( !Owner.Teleport(safepos,safeangle,0) ) { if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= .95; Owner.A_StartSound("powerup/tethererror",CHAN_ITEMEXTRA,CHANF_OVERLAP); if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$D_TETHERFAIL")); return; } let s = Spawn("DemolitionistShockwave",oldpos); s.target = Owner; s.special1 = 120; s = Spawn("DemolitionistShockwave",Owner.pos); s.target = Owner; s.special1 = 120; if ( Owner.player == players[consoleplayer] ) { Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP); Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7); Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4); } SWWMHandler.DoFlash(Owner,Color(255,255,255,255),10); SWWMHandler.DoFlash(Owner,Color(255,128,192,255),30); Owner.GiveBody(100,100); SWWMUtility.AchievementProgressInc("sneaky",1,Owner.player); Amount--; if ( Amount <= 0 ) DepleteOrDestroy(); } override bool Use( bool pickup ) { if ( pickup || bPrimed ) return false; if ( !OnIcon[0] ) OnIcon[0] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyOn.png"); if ( !OnIcon[1] ) OnIcon[1] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyNo.png"); if ( !OnIcon[2] ) OnIcon[2] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyYes.png"); bPrimed = true; primetim = 0; Vector3 safepos; if ( deathmatch ) safepos = level.PickDeathmatchStart(); else safepos = level.PickPlayerStart(Owner.PlayerNumber()); if ( !level.useplayerstartz ) { // find the floor for proper distance check let s = level.PointInSector(safepos.xy); safepos.z = s.floorplane.ZAtPoint(safepos.xy); } bFailed = (level.Vec3Diff(Owner.pos,safepos).length() < 400); if ( (Owner.player == players[consoleplayer]) || bBigPowerup ) Owner.A_StartSound(bFailed?"powerup/tetherfail":"powerup/tetheruse",CHAN_ITEMEXTRA,CHANF_OVERLAP); // don't consume on use, will happen later Amount++; return true; } Default { Tag "$T_SAFETY"; Stamina 240000; Inventory.Icon "graphics/HUD/Icons/I_Safety.png"; Inventory.PickupSound "misc/p_pkup"; Inventory.UseSound ""; Inventory.PickupMessage "$T_SAFETY"; Inventory.MaxAmount 5; Inventory.InterHubAmount 5; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; } States { Spawn: XZW1 A 33; XZW1 B 2; Loop; } } Class AngeryLight : PointLightAttenuated { Default { Args 224,0,255,80; } override void Tick() { Super.Tick(); if ( !target || !master ) { Destroy(); return; } if ( target.player ) SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true); else SetOrigin(target.Vec3Offset(0,0,target.height/2),true); args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*8; bDORMANT = Powerup(master).isBlinking(); } } Class AngerySnd : SWWMNonInteractiveActor { override void Tick() { if ( !target || !master ) { Destroy(); return; } SetOrigin(target.pos,true); if ( players[consoleplayer].Camera == target ) { A_SoundVolume(CHAN_VOICE,0.); A_SoundVolume(CHAN_7,.5); } else { A_SoundVolume(CHAN_VOICE,.4); A_SoundVolume(CHAN_7,0.); } } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/devastationact",CHAN_VOICE,CHANF_LOOP,.4,1.5); A_StartSound("powerup/devastationact",CHAN_7,CHANF_LOOP,.5,ATTN_NONE); } override void OnDestroy() { Super.OnDestroy(); A_StopSound(CHAN_VOICE); A_StopSound(CHAN_7); } } Class AngeryPower : Powerup { Mixin SWWMShadedPowerup; Actor l, snd; int lasteffect; transient int lastpulse; Default { Powerup.Duration -50; Inventory.Icon "graphics/HUD/Icons/I_Devastation.png"; Powerup.Color "C0 00 FF", 0.2; +INVENTORY.ADDITIVETIME; } override void InitEffect() { Super.InitEffect(); if ( !Owner ) return; Owner.A_AlertMonsters(swwm_uncapalert?0:5000); SWWMHandler.DoFlash(Owner,Color(64,224,0,255),30); Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); lasteffect = int.min; lastpulse = max(lastpulse,gametic+35); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= .95; l = Spawn("AngeryLight",Owner.pos); l.target = Owner; l.master = self; } override void DoEffect() { Super.DoEffect(); if ( !Owner ) return; if ( !snd ) snd = Spawn("AngerySnd",Owner.pos); snd.target = Owner; snd.master = self; } override void EndEffect() { Super.EndEffect(); if ( !Owner ) return; Owner.A_StartSound("powerup/devastationend",CHAN_ITEMEXTRA,CHANF_OVERLAP); SWWMHandler.DoFlash(Owner,Color(128,224,0,255),30); Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); Owner.A_AlertMonsters(2000); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= .9; if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DEVASTATION")); } void DoHitFX() { if ( level.maptime <= lasteffect+5 ) return; Owner.A_AlertMonsters(swwm_uncapalert?0:5000); SWWMHandler.DoFlash(Owner,Color(64,224,0,255),10); Owner.A_QuakeEx(8,8,8,Random[Rage](3,8),0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); Owner.A_StartSound("powerup/devastationhit",CHAN_POWERUP,CHANF_OVERLAP); lasteffect = level.maptime; lastpulse = max(lastpulse,gametic+35); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= .9; } override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags ) { if ( passive || (damage <= 0) ) return; if ( damageType == 'Mortal' ) // can only be in 4s { // max cap is the closest combination of 4s smaller than (2^31-1) if ( damage > 44444444 ) newdamage = 444444444; else newdamage = damage*10+4; } else { // (2^31-1)/25 : guarantee that it caps rather than overflowing if ( damage > 85899345 ) newdamage = int.max; else newdamage = damage*25; } // don't play hit fx for wall busting, as it'll be done manually if the bust goes through if ( damageType != 'Wallbust' ) DoHitFX(); } } Class AngerySigil : Inventory { Mixin SWWMAutoUseFix; Mixin SWWMOverlapPickupSound; Mixin SWWMUseToPickup; Mixin SWWMRespawn; Mixin SWWMPickupGlow; override bool Use( bool pickup ) { if ( pickup && !deathmatch ) return false; if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP); SWWMUtility.AchievementProgressInc("deva",1,Owner.player); let r = AngeryPower(Owner.FindInventory("AngeryPower")); if ( r ) { r.EffectTics += r.default.EffectTics; SWWMHandler.DoFlash(Owner,Color(64,224,0,255),30); Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.); if ( Owner is 'Demolitionist' ) Demolitionist(Owner).lastbump *= .95; } else Owner.GiveInventory("AngeryPower",1); return true; } override void PreTravelled() { if ( tracer ) tracer.Destroy(); } override void Travelled() { Super.Travelled(); if ( tracer ) return; tracer = Spawn("AngerySigilX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } override void PostBeginPlay() { Super.PostBeginPlay(); tracer = Spawn("AngerySigilX",pos); tracer.angle = angle; tracer.target = self; tracer.FloatBobPhase = FloatBobPhase; } Default { Tag "$T_DEVASTATION"; Stamina -1500000; Inventory.Icon "graphics/HUD/Icons/I_Devastation.png"; Inventory.PickupSound "misc/p_pkup_vip"; Inventory.UseSound "powerup/devastation"; Inventory.PickupMessage "$T_DEVASTATION"; Inventory.MaxAmount 3; Inventory.InterHubAmount 3; Inventory.PickupFlash "SWWMPurplePickupFlash"; +INVENTORY.ALWAYSPICKUP; +INVENTORY.AUTOACTIVATE; +INVENTORY.INVBAR; +COUNTITEM; +INVENTORY.BIGPOWERUP; +FLOATBOB; +DONTGIB; FloatBobStrength 0.25; } States { Spawn: XZW1 A -1; Stop; } } Class AngerySigilX : SWWMItemOverlay { Default { Scale .5; Alpha .35; RenderStyle "Subtract"; +FORCEXYBILLBOARD; } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/devastationamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.); WorldOffset = (0,0,20); } override void Tick() { if ( !target ) { Destroy(); return; } prev = target.prev; if ( (target.pos != pos) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+vel,true); if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE); FloatBobPhase = target.FloatBobPhase; bool bOldInvis = bInvisible; bInvisible = target.bInvisible||Inventory(target).Owner; if ( bInvisible != bOldInvis ) { SetState(SpawnState+bInvisible); A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.); } } States { Spawn: BLPS C -1 Bright; TNT1 A -1; Stop; } } Class DivineSpriteLight : PointLightAttenuated { Default { Args 255,255,255,100; } override void Tick() { Super.Tick(); if ( !target || !master ) { Destroy(); return; } if ( target.player ) SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true); else SetOrigin(target.Vec3Offset(0,0,target.height/2),true); double vol = clamp((target.Health-1000)/6000.,0.,1.); int lv = clamp(int(vol*255),0,255); args[LIGHT_RED] = lv; args[LIGHT_GREEN] = lv; args[LIGHT_BLUE] = lv; args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*10; } } Class DivineSpriteSnd : SWWMNonInteractiveActor { override void Tick() { if ( !target || !master ) { Destroy(); return; } SetOrigin(target.pos,true); double vol = clamp((DivineSpriteEffect(master).AlphInter.GetValue()-1000.)/9000.,0.,1.); if ( players[consoleplayer].Camera == target ) { A_SoundVolume(CHAN_VOICE,0.); A_SoundVolume(CHAN_7,.8*vol); } else { A_SoundVolume(CHAN_VOICE,.4*vol); A_SoundVolume(CHAN_7,0.); } } override void PostBeginPlay() { Super.PostBeginPlay(); A_StartSound("powerup/divineact",CHAN_VOICE,CHANF_LOOP,.4,1.5); A_StartSound("powerup/divineact",CHAN_7,CHANF_LOOP,.8,ATTN_NONE); } override void OnDestroy() { Super.OnDestroy(); A_StopSound(CHAN_VOICE); A_StopSound(CHAN_7); } } Class DivineSpriteEffect : Inventory { int healcnt; int healtim; bool bHealDone; Actor l, snd; DynamicValueInterpolator AlphInter; TextureID flaretex; Property HealTimer : healtim; default { Inventory.Icon "graphics/HUD/Icons/I_Divine.png"; DivineSpriteEffect.HealTimer 1750; +INVENTORY.UNDROPPABLE; +INVENTORY.UNTOSSABLE; } clearscope bool isBlinking() const { return ( (healtim <= BLINKTHRESHOLD) && (healtim&8) ); } override Color GetBlend() { if ( swwm_shaders ) return 0; if ( !AlphInter ) AlphInter = DynamicValueInterpolator.Create(Owner.Health,.1,1,100); double alph = clamp((AlphInter.GetValue()-1000.)/6000.,0.,1.); return Color(int(64*alph),255,255,255); } override void Travelled() { Super.Travelled(); bHealDone = true; } override void DoEffect() { Super.DoEffect(); if ( !l ) l = Spawn("DivineSpriteLight",Owner.pos); l.target = Owner; l.master = self; if ( !snd ) snd = Spawn("DivineSpriteSnd",Owner.pos); snd.target = Owner; snd.master = self; int numpt = Random[ExploS](5,10); if ( !AlphInter ) AlphInter = DynamicValueInterpolator.Create(Owner.Health,.1,1,100); AlphInter.Update(Owner.Health); double alph = clamp((AlphInter.GetValue()-1000.)/6000.,0.,1.); double scl = clamp((AlphInter.GetValue()-1000.)/6000.,2.,4.); if ( !flaretex ) flaretex = TexMan.CheckForTexture("graphics/Particles/xflare.png"); for ( int i=0; i0)?newdmg:dmg,self); victim.SpawnBlood(pos,atan2(dir.y,dir.x),dmg); } } else { A_StartSound("leadball/hit",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5); if ( victim ) victim.A_StartSound("leadball/hit",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); } } // make it so the crit does not propagate to friendlies unless we bonked a friend (you monster!) if ( crit ) SWWMUtility.DoExplosion(self,dmg/2,25000,150,80,((victim.isFriend(target))?0:DE_NOHURTFRIEND)|DE_NONEXPLOSIVE,crit?'CriticalConcussion':'Concussion',target,DMG_FOILINVUL); if ( crit && victim && (victim.Health <= 0) && (victim.bBOSS || victim.FindInventory("BossMarker")) && target ) SWWMUtility.MarkAchievement("clonk",target.player); // 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("leadball/fly",CHAN_WEAPON,CHANF_LOOP,.6,3.,2.); heat = 1.; } override void Tick() { oldvel = vel; Super.Tick(); if ( isFrozen() || (freezetics > 0) ) return; if ( InStateSequence(CurState,ResolveState("Death")) ) { deadtimer++; if ( deadtimer > 300 ) { let numpt = Random[Spreadgun](3,6); for ( int i=0; i 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; } } // TBD 1.4 Battle-Boi Sentry Class SaltTracer : LineTracer { Actor ignore; 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 == 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 : SWWMNonInteractiveActor { Default { Obituary "$O_SALTSHOT"; DamageType "Salt"; RenderStyle "Add"; Scale 1.8; +NODAMAGETHRUST; +FORCERADIUSDMG; +FORCEXYBILLBOARD; +FOILINVUL; } override void PostBeginPlay() { Super.PostBeginPlay(); A_AlertMonsters(swwm_uncapalert?0:6000); SWWMUtility.DoExplosion(self,30+special2*4,15000,100,40); A_QuakeEx(3,3,3,10,0,250,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:150,rollintensity:0.2); A_StartSound("saltshot/hit",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 20) && !Random[Spreadgun](0,800/args[0]) ) { let i = Spawn("SaltImpact",level.Vec3Offset(pos,x*speed)); 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) ) Spawn("SaltLight",level.Vec3Offset(pos,x*speed/2)); let next = Spawn("SaltBeam",level.Vec3Offset(pos,x*speed)); double a = FRandom[Spreadgun](0,360), s = FRandom[Spreadgun](0,.06); Vector3 dir = SWWMUtility.ConeSpread(x,y,z,a,s); 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("saltshot/trail",CHAN_VOICE,CHANF_DEFAULT,.3,4.); } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; A_FadeOut(.04); if ( Random[Spreadgun](-2,args[2]/10) == 0 ) SWWMUtility.DoExplosion(self,5+Accuracy,5000,speed,flags:DE_HOWL|DE_NONEXPLOSIVE,ignoreme:target); if ( ((special2%4) || 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; } }