swwmgz_m/zscript/items/swwm_powerups.zsc
Marisa Kirisame 4d7019ae86 Several changes from devel once more:
- Fuck it, Quadravol will be lever action.
 - Tiny cleanup.
 - Rewrite the weapon replacement system (less of a mess now maybe?).
 - Some menu fixes.
 - Minimap zoom increments like in the Common Library.
 - Add missing sound definition for Safety Tether.
   (This mostly went unnoticed because it's VERY rare to have it play)
 - Shift Sparkster x3 (DLC2) to slot 7.
   This way you can have both it and the Quadravol simultaneously.
   It would be unfair to not let you hold both "iconic" UnSX weapons at once.
 - Small lore tweak on Quadravol stance swap.
 - Fix off-by-one bug in looping palette lights.
 - Re-do logo shader. Use separate layer textures.
 - Fix Elemental Coating breaking "End Level" damage sectors.
(This will be the last batch of changes before I continue working on menus)
2021-12-08 18:17:41 +01:00

3755 lines
102 KiB
Text

// 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('swwm_progress_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,rollIntensity:1.);
Owner.GiveBody(1000,1000);
SWWMScoreObj.Spawn(1000,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
if ( Owner is 'Demolitionist' )
{
if ( swwm_accdamage )
SWWMScoreObj.Spawn(600,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
else
{
SWWMScoreObj.Spawn(200,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
SWWMScoreObj.Spawn(150,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
SWWMScoreObj.Spawn(250,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
}
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;
}
else
{
SWWMScoreObj.Spawn(200,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
let n = Owner.FindInventory("ArmorNugget");
if ( !n ) Owner.GiveInventory("ArmorNugget",GetDefaultByType("ArmorNugget").MaxAmount);
else n.Amount = n.MaxAmount;
SWWMLoreLibrary.Add(Owner.player,"Nugget");
}
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);
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());
}
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 or invuln/ragekit/barrier or the collar
Inventory found = null;
for ( Inventory i=other.Inv; i; i=i.Inv )
{
if ( !(i is 'SWWMHealth') && !(i is 'SWWMArmor') && !(i is 'InvinciballPower') && !(i is 'RagekitPower') && !(i is 'BarrierPower') && !(i is 'AlmasteelPlating') && !(i is 'SayaCollar') ) 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('swwm_achievement_tele',lastdropper.player);
}
Default
{
//$Title Grilled Cheese Sandwich
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Sandwich.png
//$Icon powerup
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;
FloatBobStrength 0.25;
Radius 12;
Height 24;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Mixin Class SWWMShadedPowerup
{
override Color GetBlend()
{
if ( swwm_shaders ) return 0;
return Super.GetBlend();
}
}
Class GhostSnd : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOINTERACTION;
}
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 ( 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<hnd.suckableactors.Size(); i++ )
{
let a = hnd.suckableactors[i];
if ( !a || !a.bISMONSTER || a.player || !a.IsHostile(master) || (a.Health <= 0) ) continue;
if ( a.target == self ) a.target = master;
}
Destroy();
}
}
}
Class GhostPower : PowerInvisibility
{
Mixin SWWMShadedPowerup;
Actor snd;
Default
{
Inventory.Icon "graphics/HUD/Icons/I_Ghost.png";
Powerup.Duration -60;
Powerup.Strength 100;
Powerup.Mode "Translucent";
Powerup.Color "F0 E0 FF", 0.1;
+INVENTORY.ADDITIVETIME;
+CANTSEEK;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
SWWMHandler.DoFlash(Owner,Color(96,224,192,255),20);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.04;
DoEffect();
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("powerup/ghostend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
SWWMHandler.DoFlash(Owner,Color(96,224,192,255),20);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.02;
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_GHOSTARTI"));
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner ) return;
// are any enemies targetting us? if so, make them focus on a fake target located where we currently are standing
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
Actor gt = null;
if ( hnd ) for ( int i=0; i<hnd.suckableactors.Size(); i++ )
{
let a = hnd.suckableactors[i];
if ( !a || !a.bISMONSTER || a.player || !a.IsHostile(Owner) || (a.Health <= 0) ) continue;
// make them forget the ghost if we make noise
if ( (a.LastHeard == Owner) && (a.target is 'GhostTarget') && (a.target.master == Owner) )
{
a.target = Owner;
continue;
}
if ( a.target != Owner ) continue;
if ( !gt )
{
gt = Spawn("GhostTarget",Owner.pos);
if ( Owner.bFRIENDLY || Owner.player ) gt.bFRIENDLY = true;
}
a.target = gt;
a.LastHeard = gt;
gt.master = Owner;
}
if ( !snd ) snd = Spawn("GhostSnd",Owner.pos);
snd.target = Owner;
snd.master = self;
}
override void AlterWeaponSprite( VisStyle vis, in out int changed )
{
// leave weapons alone
vis.RenderStyle = STYLE_Normal;
vis.Alpha = 1.f;
changed = 1;
}
}
Class GhostArtifactX : Actor
{
SpriteID bsprite;
Default
{
RenderStyle "Add";
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOINTERACTION;
Radius .1;
Height 0;
+FLOATBOB;
FloatBobStrength 0.25;
}
override void Tick()
{
if ( !target )
{
Destroy();
return;
}
prev = target.prev;
vel = target.vel;
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;
if ( !bsprite ) bsprite = GetSpriteIndex('XZW1');
bInvisible = target.bInvisible||(target.sprite!=bsprite);
}
States
{
Spawn:
XZW1 A -1 Bright;
Stop;
}
}
Class GhostArtifact : Inventory
{
Mixin SWWMAutoUseFix;
Mixin SWWMOverlapPickupSound;
Mixin SWWMUseToPickup;
Mixin SWWMRespawn;
Mixin SWWMPickupGlow;
Default
{
//$Title Ghost Artifact
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Ghost.png
//$Icon powerup
Tag "$T_GHOSTARTI";
Stamina 120000;
Inventory.Icon "graphics/HUD/Icons/I_Ghost.png";
Inventory.PickupSound "misc/p_pkup";
Inventory.UseSound "powerup/ghost";
Inventory.PickupMessage "$T_GHOSTARTI";
Inventory.MaxAmount 5;
Inventory.InterHubAmount 5;
Inventory.PickupFlash "SWWMPurplePickupFlash";
+INVENTORY.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
FloatBobStrength 0.25;
Radius 12;
Height 24;
}
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 = GhostPower(Owner.FindInventory("GhostPower"));
if ( g )
{
g.EffectTics += g.default.EffectTics;
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.04;
}
else Owner.GiveInventory("GhostPower",1);
SWWMUtility.AchievementProgressInc('swwm_progress_ghost',1,Owner.player);
return true;
}
override void PreTravelled()
{
if ( tracer ) tracer.Destroy();
}
override void Travelled()
{
if ( tracer ) return;
tracer = Spawn("GhostArtifactX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
tracer = Spawn("GhostArtifactX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class GravSnd : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOINTERACTION;
}
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,.7);
}
else
{
A_SoundVolume(CHAN_VOICE,.2);
A_SoundVolume(CHAN_7,0.);
}
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("powerup/gravityact",CHAN_VOICE,CHANF_LOOP,.2,1.5);
A_StartSound("powerup/gravityact",CHAN_7,CHANF_LOOP,.7,ATTN_NONE);
}
override void OnDestroy()
{
Super.OnDestroy();
A_StopSound(CHAN_VOICE);
A_StopSound(CHAN_7);
}
}
Class GravityPower : Powerup
{
Mixin SWWMShadedPowerup;
Actor snd;
Default
{
Inventory.Icon "graphics/HUD/Icons/I_Gravity.png";
Powerup.Duration -60;
+INVENTORY.ADDITIVETIME;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
DoEffect();
if ( Owner.pos.z <= Owner.floorz )
Owner.vel.z = 1;
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.04;
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
if ( !Owner.bFLYCHEAT )
{
Owner.bFLY = false;
Owner.bNOGRAVITY = false;
if ( Owner.pos.z > 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 : GhostArtifactX
{
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('swwm_progress_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
{
//$Title Gravity Suppressor
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Gravity.png
//$Icon powerup
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;
FloatBobStrength 0.25;
Radius 12;
Height 28;
}
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 : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOINTERACTION;
}
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 InvinciballPower : Powerup
{
Mixin SWWMShadedPowerup;
Actor l, snd;
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;
}
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"));
}
override void AttachToOwner( Actor other )
{
Super.AttachToOwner(other);
// find first with health/armor, plating/collar, sandwich after it
Inventory found = null;
for ( Inventory i=other.Inv; i; i=i.Inv )
{
if ( !(i.Inv is 'SWWMHealth') && !(i.Inv is 'SWWMArmor') && !(i.Inv is 'GrilledCheeseSandwich') && !(i.Inv is 'AlmasteelPlating') && !(i.Inv is 'SayaCollar') ) continue;
found = i;
break;
}
if ( !found )
{
// we're good
return;
}
// place ourselves right after it
Inventory saved = found.Inv;
found.Inv = self;
other.Inv = Inv;
Inv = saved;
}
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
{
if ( Owner.ApplyDamageFactor(damageType,damage) <= 0 )
return; // this damage type is ignored, so don't flash
if ( damageType == 'EndLevel' )
return; // don't trigger on endlevel damage
if ( damage > 0 )
{
newdamage = 0;
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 : GhostArtifactX
{
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('swwm_progress_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
{
//$Title Invinciball
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Invinciball.png
//$Icon powerup
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;
FloatBobStrength 0.25;
Radius 10;
Height 26;
}
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 : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOINTERACTION;
}
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 RagekitPower : Powerup
{
Mixin SWWMShadedPowerup;
Actor l, snd;
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,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);
if ( Owner.GiveBody(1,100) )
SWWMScoreObj.Spawn(1,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
Owner.A_AlertMonsters(swwm_uncapalert?0:2000);
if ( (Owner.player == players[consoleplayer]) && (gametic > lastrage) && (swwm_mutevoice < 2) )
lastrage = SWWMHandler.AddOneliner("ragekit",2,5)+40;
Owner.A_QuakeEx(2,2,2,Random[Rage](1,2),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.5);
lastpulse = max(lastpulse,gametic+10);
Demolitionist(Owner).lastbump *= .995;
}
}
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,rollIntensity:1.);
Owner.A_AlertMonsters(2000);
Demolitionist(Owner).lastbump *= .9;
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_RAGEKIT"));
}
override void AttachToOwner( Actor other )
{
Super.AttachToOwner(other);
// find first with health/armor, plating/collar, sandwich or barrier after it
Inventory found = null;
for ( Inventory i=other.Inv; i; i=i.Inv )
{
if ( !(i.Inv is 'SWWMHealth') && !(i.Inv is 'SWWMArmor') && !(i.Inv is 'GrilledCheeseSandwich') && !(i.Inv is 'AlmasteelPlating') && !(i.Inv is 'SayaCollar') && !(i.Inv is 'BarrierPower') ) continue;
found = i;
break;
}
if ( !found )
{
// we're good
return;
}
// place ourselves right after it
Inventory saved = found.Inv;
found.Inv = self;
other.Inv = Inv;
Inv = saved;
}
void DoHitFX()
{
if ( level.maptime <= lasteffect+5 ) return;
if ( Owner.GiveBody(5,100) )
SWWMScoreObj.Spawn(5,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
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,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 AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
{
if ( damageType == 'EndLevel' )
return; // don't trigger on endlevel damage
if ( damage > 0 ) newdamage = damage/4;
}
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') )
{
newdamage = damage*8;
DoHitFX();
}
}
}
Class RagekitX : GhostArtifactX
{
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);
if ( Owner.GiveBody(100,100) )
SWWMScoreObj.Spawn(100,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
SWWMUtility.AchievementProgressInc('swwm_progress_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,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,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
{
//$Title Ragekit
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Ragekit.png
//$Icon powerup
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;
FloatBobStrength 0.25;
Radius 12;
Height 24;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class Omnisight : Inventory
{
Mixin SWWMOverlapPickupSound;
Mixin SWWMUseToPickup;
Mixin SWWMRespawn;
override bool Use( bool pickup )
{
if ( !level.allmap )
{
if ( Owner.player == players[consoleplayer] )
{
Owner.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 ( Owner is 'Demolitionist' )
Demolitionist(Owner).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<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo || !(players[i].mo is 'Demolitionist') || players[i].mo.FindInventory(GetClass()) ) continue;
let o = Inventory(Spawn(GetClass()));
o.ClearCounters();
o.AttachToOwner(players[i].mo);
if ( i == consoleplayer )
{
Console.Printf(StringTable.Localize("$D_OMNISHARE"),Owner.player.GetUserName());
players[i].mo.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 ( players[i].mo is 'Demolitionist' )
Demolitionist(players[i].mo).lastbump *= 1.1;
}
// not used up, must be kept for the targetting features to work
return false;
}
override void DetachFromOwner()
{
// reset zoom on removal
if ( (Owner.player == players[consoleplayer]) && (swwm_mm_zoom > 1.) )
CVar.FindCVar('swwm_mm_zoom').SetFloat(1.);
Super.DetachFromOwner();
}
override bool HandlePickup( Inventory item )
{
if ( item.GetClass() == GetClass() )
{
item.bPickupGood = true;
return true;
}
return false;
}
override bool ShouldSpawn()
{
if ( deathmatch ) return false;
return Super.ShouldSpawn();
}
Default
{
//$Title Omnisight
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Omnisight.png
//$Icon powerup
Tag "$T_OMNISIGHT";
Inventory.Icon "graphics/HUD/Icons/I_Omnisight.png";
Inventory.PickupSound "misc/p_pkup";
Inventory.PickupMessage "$I_OMNISIGHT";
Inventory.MaxAmount 1;
Inventory.InterHubAmount 0;
Inventory.RestrictedTo "Demolitionist";
Inventory.PickupFlash "SWWMPurplePickupFlash";
+INVENTORY.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
+FLOATBOB;
FloatBobStrength 0.25;
Radius 6;
Height 26;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class LampMoth : Actor
{
Actor lamp;
Vector3 trail, ofs;
int lifespan;
Default
{
Tag "$T_MOTH";
Radius 2;
Height 4;
Speed 2;
DamageFunction 1;
MeleeRange 16;
Mass 10;
Health 50;
DeathSound "moth/die";
BloodColor "20 10 10";
MONSTER;
-COUNTKILL;
+THRUACTORS;
+NOGRAVITY;
+NOTELEPORT;
+FLOAT;
+NOPAIN;
+FRIENDLY;
+LOOKALLAROUND;
+QUICKTORETALIATE;
+INTERPOLATEANGLES;
+NOBLOCKMONST;
}
override string GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( inflictor && (inflictor != self) )
{
if ( inflictor == master ) return StringTable.Localize("$O_MOTHSELF"); // not likely to happen
else return StringTable.Localize("$O_MOTH");
}
return StringTable.Localize("$O_MOTH2");
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("moth/fly",CHAN_BODY,CHANF_LOOP,.02,4.,FRandom[Moth](.8,1.2));
if ( master && master.player ) SetFriendPlayer(master.player);
else bFRIENDLY = false;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
// no hurt moff
if ( source && IsFriend(source) ) damage = 0;
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
}
bool isEntranced()
{
if ( !lamp )
{
// look for nearby lamps
let bi = BlockThingsIterator.Create(self,250);
double mindist = 250.;
while ( bi.Next() )
{
if ( !bi.Thing || !(bi.Thing is 'CompanionLamp') ) continue;
Actor a = bi.Thing;
double dist = Distance3D(a);
if ( (a.frame == 0) || (dist > 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) || (Distance3D(lamp) > 250) || !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 = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(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 = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(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() ) 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<LampMoth> 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<moff.Size(); i++ )
{
if ( moff[i] && (moff[i].lamp == self) && moff[i].isEntranced() ) continue;
moff.Delete(i);
i--;
}
if ( (special1%35) || Random[Moth](0,9) || (moff.Size() >= 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 = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(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('swwm_progress_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() ) 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,(cos(ang)*32,sin(ang)*32,parent.height-8+1.5*sin(level.maptime*3.)));
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<moff.Size(); i++ )
{
if ( !moff[i] ) continue;
Vector3 whereto = level.Vec3Offset(moff[i].pos,rel);
if ( !level.IsPointInLevel(whereto) )
continue;
Vector3 oldp = moff[i].pos;
moff[i].SetOrigin(whereto,false);
if ( !moff[i].TestMobjLocation() )
moff[i].SetOrigin(oldp,false);
}
SetOrigin(trail,false);
angle = AngleTo(parent);
vel *= 0.;
f = Spawn("SWWMItemFog",pos);
f.A_StartSound("lamp/appear",CHAN_VOICE);
return;
}
angle += Clamp(deltaangle(angle,AngleTo(parent)),-5.,5.);
vel *= .8;
bool blocked = false;
if ( SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) )
{
// push away from wall
Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) ) normal *= -1;
vel += 4.*normal;
blocked = true;
}
if ( BlockingFloor )
{
// push away from floor
Vector3 normal = BlockingFloor.floorplane.Normal;
// find closest 3d floor for its normal
for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
{
if ( !(BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
normal = -BlockingFloor.Get3DFLoor(i).top.Normal;
break;
}
vel += 4.*normal;
blocked = true;
}
if ( BlockingCeiling )
{
// push away from ceiling
Vector3 normal = BlockingCeiling.ceilingplane.Normal;
// find closest 3d floor for its normal
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
{
if ( !(BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
normal = -BlockingCeiling.Get3DFloor(i).bottom.Normal;
break;
}
vel += 4.*normal;
blocked = true;
}
if ( (diff.x > -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 = SWWMUtility.Round100(Stamina*.7);
SWWMScoreObj.Spawn(sellprice,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2));
SWWMCredits.Give(Owner.player,sellprice);
if ( Owner.player )
Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRA_FEM":"$SWWM_SELLEXTRA"),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",TexMan.Type_MiscPatch);
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",Owner.Vec3Offset(cos(Owner.angle)*20,sin(Owner.angle)*20,24));
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
{
//$Title Lamp
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_LampOff.png
//$Icon powerup
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;
FloatBobStrength 0.25;
SWWMLamp.Charge 100;
Stamina 70000;
Radius 8;
Height 28;
}
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 : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOINTERACTION;
}
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 BarrierPower : PowerIronFeet
{
Mixin SWWMShadedPowerup;
Actor snd, l;
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 AttachToOwner( Actor other )
{
Super.AttachToOwner(other);
// find first item with armor/health, plating/collar, sandwich, invincibility after it
Inventory found = null;
for ( Inventory i=other.Inv; i; i=i.Inv )
{
if ( !(i.Inv is 'SWWMHealth') && !(i.Inv is 'SWWMArmor') && !(i.Inv is 'GrilledCheeseSandwich') && !(i.Inv is 'AlmasteelPlating') && !(i.Inv is 'SayaCollar') && !(i.Inv is 'InvinciballPower') ) continue;
found = i;
break;
}
if ( !found )
{
// we're good
return;
}
// place ourselves right after it
Inventory saved = found.Inv;
found.Inv = self;
other.Inv = Inv;
Inv = saved;
}
override void AbsorbDamage( int damage, Name damageType, out int newdamage )
{
// negate elemental damage
if ( (damageType == 'Fire') || (damageType == 'Ice') || (damageType == 'Slime') || (damageType == 'Electric') || (damageType == 'Plasma') || (damageType == 'Radiation') || (damageType == 'Wind') || (damageType == 'Water') || (damageType == 'Corroded') || (damageType == 'Lava') )
newdamage = 0;
}
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;
// break ourselves if we're in an endlevel sector
bool endlv = false;
for ( int i=0; i<Owner.CurSector.Get3DFloorCount(); i++ )
{
F3DFloor ff = Owner.CurSector.Get3DFloor(i);
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SWIMMABLE)) ) continue;
if ( (ff.model.DamageAmount <= 0) || (ff.model.damageinterval <= 0) ) continue;
if ( ff.top.ZAtPoint(Owner.pos.xy) <= Owner.pos.z ) continue;
if ( ff.bottom.ZAtPoint(Owner.pos.xy) >= (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.floorsector.damageamount > 0) && (Owner.floorsector.damageinterval > 0) ) damageterrain = true;
else
{
let t = Owner.GetFloorTerrain();
if ( t && (t.DamageAmount > 0) && (t.DamageTimeMask > 0) )
damageterrain = true;
}
if ( damageterrain && (Owner.floorsector.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('swwm_progress_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.CurSector.Get3DFloorCount(); i++ )
{
F3DFloor ff = Owner.CurSector.Get3DFloor(i);
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SWIMMABLE)) ) continue;
if ( (ff.model.DamageAmount <= 0) || (ff.model.damageinterval <= 0) ) continue;
if ( ff.top.ZAtPoint(Owner.pos.xy) <= Owner.pos.z ) continue;
if ( ff.bottom.ZAtPoint(Owner.pos.xy) >= (Owner.pos.z+Owner.Height) ) continue;
if ( ff.model.flags&Sector.SECF_ENDLEVEL ) endlevelterrain = true;
damageterrain = true;
break;
}
if ( !damageterrain && (Owner.pos.z <= Owner.floorz) )
{
if ( (Owner.floorsector.damageamount > 0) && (Owner.floorsector.damageinterval > 0) ) damageterrain = true;
else
{
let t = Owner.GetFloorTerrain();
if ( t && (t.DamageAmount > 0) && (t.DamageTimeMask > 0) )
damageterrain = true;
}
if ( damageterrain && (Owner.floorsector.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;
bool shouldautouse = false;
if ( swwm_enforceautousebarrier == 1 ) shouldautouse = true;
else if ( swwm_enforceautousebarrier == -1 ) shouldautouse = false;
else shouldautouse = CVar.GetCVar('swwm_autousebarrier',Owner.player).GetBool();
if ( shouldautouse ) Owner.UseInventory(self);
}
Default
{
//$Title Barrier
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Barrier.png
//$Icon powerup
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;
FloatBobStrength 0.25;
Radius 6;
Height 28;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class EBarrierX : GhostArtifactX
{
}
Class TendrilTracer : LineTracer
{
Actor ignore;
Array<Line> ShootThroughList;
Array<HitListEntry> 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 : Actor
{
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<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.hitlist.Size(); i++ )
{
if ( t.hitlist[i].hitactor.IsFriend(target) ) continue;
if ( (t.hitlist[i].hitactor == tracer) && bMISSILEMORE ) bMISSILEEVENMORE = true; // we split
int dmg = (t.hitlist[i].hitactor.bBOSS||t.hitlist[i].hitactor.FindInventory("BossMarker"))?(GetMissileDamage(0,0)*4):max(t.hitlist[i].hitactor.Health,GetMissileDamage(0,0));
SWWMUtility.DoKnockback(t.hitlist[i].hitactor,-t.hitlist[i].x+(0,0,.5),((t.hitlist[i].hitactor.Health-dmg)<=0)?60000:8000);
t.hitlist[i].hitactor.DamageMobj(self,target,dmg,'Plasma',DMG_THRUSTLESS);
if ( t.hitlist[i].hitactor && t.hitlist[i].hitactor.bISMONSTER && !Random[Mykradvo](0,3) )
t.hitlist[i].hitactor.Howl();
}
}
invoker.nextpos = level.Vec3Offset(pos,x*speed);
if ( !bSTANDSTILL && (!tracer || !tracer.bSHOOTABLE || (tracer.Health <= 0) || ((tracer.bBOSS || tracer.FindInventory("BossMarker")) && !bMISSILEMORE)) )
{
ReactionTime--;
if ( ReactionTime <= 0 )
{
bAMBUSH = true;
return;
}
}
double a = FRandom[Mykradvo](0,360), s = FRandom[Mykradvo](0.,bSTANDSTILL?3.:bMISSILEMORE?.75:1.5);
Vector3 dir = (x+cos(a)*y*s+sin(a)*z*s).unit();
if ( tracer )
{
Vector3 destofs = bMISSILEMORE?tracer.Vec3Offset(0,0,tracer.Height/2.):tracer.Vec3Offset(FRandom[Mykradvo](-1.2,1.2)*tracer.Radius,FRandom[Mykradvo](-1.2,1.2)*tracer.Radius,FRandom[Mykradvo](-.1,1.1)*tracer.height);
Vector3 dirto = level.Vec3Diff(invoker.nextpos,destofs);
double dist = dirto.length();
if ( dist > 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<ntendies; i++ )
{
let r = Spawn("MykradvoSmallTendril",invoker.nextpos);
double a = FRandom[Mykradvo](0,360), s = FRandom[Mykradvo](0.,1.);
Vector3 sdir = (x+y*cos(a)*s+z*sin(a)*s).unit();
r.angle = atan2(sdir.y,sdir.x);
r.pitch = asin(-sdir.z);
r.target = target;
r.tracer = tracer;
if ( tracer && (tracer.bBOSS || tracer.FindInventory("BossMarker")) ) r.ReactionTime -= Random[ExploS](5,15);
else r.ReactionTime += Random[ExploS](0,20);
}
return;
}
if ( bAMBUSH || (bSTANDSTILL && (special1 > 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<numpt; i++ )
{
double a = FRandom[ExploS](0,360), s = FRandom[ExploS](0,1.);
Vector3 sdir = (x+cos(a)*y*s+sin(a)*z*s).unit();
let r = Spawn("MykradvoSmallNullTendril",invoker.nextpos);
r.angle = atan2(sdir.y,sdir.x);
r.pitch = asin(-sdir.z);
r.target = target;
if ( bMISSILEMORE ) r.ReactionTime += Random[ExploS](0,4);
else r.ReactionTime -= Random[ExploS](0,4);
}
}
return;
}
let b = Spawn(GetClass(),invoker.nextpos);
b.angle = atan2(invoker.nextdir.y,invoker.nextdir.x);
b.pitch = asin(-invoker.nextdir.z);
b.target = target;
b.tracer = tracer;
b.special1 = special1+1;
b.special2 = special2;
b.ReactionTime = ReactionTime;
if ( !bSTANDSTILL && !((special1+special2)%4) && !Random[Mykradvo](0,4) )
A_StartSound(bMISSILEMORE?"mykradvo/arc":"mykradvo/smallarc",CHAN_WEAPON,attenuation:(bMISSILEMORE?.5:1.5),pitch:FRandom[Mykradvo](.75,1.25));
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( !special1 ) special2 = Random[Mykradvo](0,8);
}
override void Tick()
{
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;
Radius .1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+INTERPOLATEANGLES;
+NOTELEPORT;
+FOILINVUL;
+NOINTERACTION;
+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 : Actor
{
Array<Actor> targets;
int nstep;
Default
{
RenderStyle "Add";
+NOBLOCKMAP;
+NOGRAVITY;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+NOINTERACTION;
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,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<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.25,8);
let s = Spawn("SWWMHalfSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31));
s.A_SetRenderStyle(.4,STYLE_AddShaded);
s.special1 = Random[ExploS](1,5);
s.scale *= 2.;
}
numpt = Random[ExploS](8,12);
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
let s = Spawn("MykradvoSmallNullTendril",pos);
s.angle = ang;
s.pitch = pt;
s.target = target;
s.ReactionTime += Random[ExploS](0,8);
}
Spawn("MykradvoBurstLight",pos);
}
override void Tick()
{
if ( isFrozen() ) return;
if ( (special1++)%3 )
{
if ( targets.Size() > 0 )
{
int numpt = Random[ExploS](2,4);
for ( int j=0; j<numpt; j++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.25,8);
let s = Spawn("SWWMHalfSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31));
s.A_SetRenderStyle(.4,STYLE_AddShaded);
s.special1 = Random[ExploS](1,5);
s.scale *= 2.;
}
numpt = Random[ExploS](1,2);
for ( int j=0; j<numpt; j++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
let s = Spawn("MykradvoSmallNullTendril",pos);
s.angle = ang;
s.pitch = pt;
s.target = target;
s.ReactionTime += Random[ExploS](0,8);
}
}
for ( int i=0; i<nstep; i++ )
{
if ( targets.Size() <= 0 ) break;
let targ = targets[0];
if ( targ && targ.bSHOOTABLE && (targ.Health > 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<Actor> 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<Actor> 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<Actor> 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<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
let s = Spawn("MykradvoSmallNullTendril",spawnpos+(cos(ang)*cos(pt),sin(ang)+cos(pt),-sin(pt))*FRandom[Mykradvo](2,4));
s.angle = ang;
s.pitch = pt;
s.ReactionTime += Random[ExploS](0,8);
}
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 0.98;
Owner.A_QuakeEx(1,1,1,4,0,8,"",QF_RELATIVE|QF_SCALEDOWN);
// don't consume on failure
Amount++;
return true;
}
if ( (targets.Size() == 1) && targets[0] && !targets[0].bBOSS && !targets[0].FindInventory("BossMarker") )
SWWMUtility.MarkAchievement('swwm_achievement_anone',Owner.player);
let p = Spawn("MykradvoBurst",spawnpos);
p.target = Owner;
MykradvoBurst(p).targets.Move(targets);
targets.Clear();
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.2;
SWWMUtility.AchievementProgressInc('swwm_progress_anom',1,Owner.player);
return true;
}
action void A_Zap()
{
int numpt = Random[ExploS](2,4);
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
let s = Spawn("MykradvoSmallNullTendril",pos+(0,0,16+GetBobOffset())+(cos(ang)*cos(pt),sin(ang)+cos(pt),-sin(pt))*FRandom[Mykradvo](4,8));
s.angle = ang;
s.pitch = pt;
s.ReactionTime += Random[ExploS](-2,2);
}
A_StartSound("mykradvo/smallarc",CHAN_WEAPON,CHANF_OVERLAP,attenuation:3.);
A_SetTics(Random[Mykradvo](10,50));
}
override bool ShouldSpawn()
{
return !deathmatch;
}
override bool CanPickup( Actor toucher )
{
// in DM, we can't be picked up unless we can be used
if ( deathmatch && !FindTargets(toucher) )
return false;
return Super.CanPickup(toucher);
}
override void PreTravelled()
{
if ( tracer ) tracer.Destroy();
for ( int i=0; i<2; i++ )
{
if ( !ringa[i] ) continue;
ringa[i].Destroy();
}
}
override void Travelled()
{
Super.Travelled();
if ( !tracer )
{
tracer = Spawn("MykradvoX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
for ( int i=0; i<2; i++ )
{
if ( ringa[i] ) continue;
ringa[i] = Spawn("MykradvoX2",pos);
ringa[i].angle = angle;
ringa[i].target = self;
ringa[i].FloatBobPhase = FloatBobPhase;
ringa[i].special1 = -1+2*i;
ringa[i].special2 = -2+4*i;
ringa[i].frame = i;
}
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
tracer = Spawn("MykradvoX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
for ( int i=0; i<2; i++ )
{
ringa[i] = Spawn("MykradvoX2",pos);
ringa[i].angle = angle;
ringa[i].target = self;
ringa[i].FloatBobPhase = FloatBobPhase;
ringa[i].special1 = -1+2*i;
ringa[i].special2 = -2+4*i;
ringa[i].frame = i;
}
}
Default
{
//$Title Mykradvo
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Mykradvo.png
//$Icon powerup
Tag "$T_MYKRADVO";
Stamina -1200000;
Inventory.Icon "graphics/HUD/Icons/I_Mykradvo.png";
Inventory.PickupSound "misc/p_pkup_vip";
Inventory.UseSound "mykradvo/arc";
Inventory.PickupMessage "$T_MYKRADVO";
Inventory.MaxAmount 3;
Inventory.InterHubAmount 3;
Inventory.PickupFlash "SWWMPurplePickupFlash";
+INVENTORY.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
FloatBobStrength 0.25;
Radius 10;
Height 24;
}
States
{
Spawn:
XZW0 A 10 A_Zap();
Wait;
}
}
Class MykradvoX : GhostArtifactX
{
Default
{
Scale .16;
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("powerup/mykradvoamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.);
}
override void Tick()
{
if ( !target )
{
Destroy();
return;
}
prev = target.prev+(0,0,16);
vel = target.vel;
if ( (target.pos != pos+(0,0,16)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,16)+vel,true);
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
FloatBobPhase = target.FloatBobPhase;
A_SetScale(.16+.01*sin(GetAge()*4));
if ( !bsprite ) bsprite = GetSpriteIndex('XZW0');
bInvisible = target.bInvisible||(target.sprite!=bsprite);
SetState(SpawnState+bInvisible);
A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.);
}
States
{
Spawn:
MKRV A -1 Bright;
TNT1 A -1;
Stop;
}
}
Class MykradvoX2 : GhostArtifactX
{
Default
{
+ROLLSPRITE;
}
override void Tick()
{
if ( !target )
{
Destroy();
return;
}
prev = target.prev+(0,0,16+special2);
vel = target.vel;
if ( (target.pos != pos+(0,0,16+special2)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,16+special2)+vel,true);
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
A_SetPitch(sin(GetAge()*special1*8)*5,SPF_INTERPOLATE);
A_SetRoll(cos(GetAge()*special1*8)*5,SPF_INTERPOLATE);
FloatBobPhase = target.FloatBobPhase;
A_SetScale(1.+.05*cos(GetAge()*4)*special1);
if ( !bsprite ) bsprite = GetSpriteIndex('XZW0');
bInvisible = target.bInvisible||(target.sprite!=bsprite);
SetState(SpawnState+bInvisible);
}
States
{
Spawn:
XZW1 # -1 Bright;
TNT1 # -1;
Stop;
}
}
// 1.1 extra items
Class SafetyTether : Inventory
{
Mixin SWWMAutoUseFix;
Mixin SWWMOverlapPickupSound;
Mixin SWWMUseToPickup;
Mixin SWWMRespawn;
bool bPrimed, bFailed;
int primetim;
TextureID OnIcon[3];
override void Travelled()
{
Super.Travelled();
bPrimed = false;
}
override Inventory CreateTossable( int amt )
{
if ( bPrimed ) return null; // can't drop while primed
let ret = Super.CreateTossable(amt);
// reattach our glow if we became a pickup
if ( (ret == self) && (PickupFlash is 'SWWMPickupFlash') && swwm_itemglows )
{
let p = Spawn(PickupFlash,Vec3Offset(0,0,16));
p.target = self;
p.SetStateLabel("Pickup");
}
return ret;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bPrimed ) return;
Icon = ((primetim%4)>=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());
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);
if ( Owner.GiveBody(100,100) )
SWWMScoreObj.Spawn(100,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
SWWMUtility.AchievementProgressInc('swwm_progress_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());
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
{
//$Title Safety Tether
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Safety.png
//$Icon powerup
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;
FloatBobStrength 0.25;
Radius 5;
Height 23;
}
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 : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOINTERACTION;
}
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,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,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,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;
// (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('swwm_progress_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,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
{
//$Title Devastation Sigil
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Devastation.png
//$Icon powerup
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;
FloatBobStrength 0.25;
Radius 8;
Height 28;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class AngerySigilX : GhostArtifactX
{
Default
{
Scale .5;
Alpha .35;
RenderStyle "Subtract";
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("powerup/devastationamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.);
}
override void Tick()
{
if ( !target )
{
Destroy();
return;
}
prev = target.prev+(0,0,20);
vel = target.vel;
if ( (target.pos != pos+(0,0,20)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,20)+vel,true);
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
FloatBobPhase = target.FloatBobPhase;
if ( !bsprite ) bsprite = GetSpriteIndex('XZW1');
bInvisible = target.bInvisible||(target.sprite!=bsprite);
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 : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
+NOINTERACTION;
}
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;
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.);
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
double dst = FRandom[ExploS](.25,.75)*Owner.height;
Vector3 dir = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt));
Vector3 ppos = Owner.Vec3offset(0,0,Owner.height/2)+dir*dst;
A_SpawnParticle("White",SPF_FULLBRIGHT,30,scl,0,ppos.x,ppos.y,ppos.z,dir.x*.2,dir.y*.2,dir.z*.2,0,0,.05,alph,-1,-scl/30.);
}
if ( bHealDone || (Owner.Health <= 0) )
{
if ( AlphInter.GetValue() <= 1000 ) DepleteOrDestroy();
return;
}
healcnt++;
healtim--;
if ( (healtim <= 0) || (Owner.Health <= 0) )
{
Owner.A_StartSound("powerup/divineend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
SWWMHandler.DoFlash(Owner,Color(80,255,255,255),40);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 0.95;
bHealDone = true;
if ( (healtim <= 0) && Owner && Owner.CheckLocalView() )
Console.Printf(StringTable.Localize("$D_SPRITE"));
}
else if ( Owner.Health < 1000 )
{
healcnt = 0;
Owner.GiveBody(1000,1000);
SWWMScoreObj.Spawn(1000,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
SWWMHandler.DoFlash(Owner,Color(40,255,255,255),20);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 0.97;
Owner.A_StartSound("powerup/divinehit",CHAN_ITEMEXTRA,CHANF_OVERLAP);
}
else if ( !(healcnt%5) )
{
Owner.GiveBody(500,10000);
SWWMScoreObj.Spawn(100,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
SWWMHandler.DoFlash(Owner,Color(10,255,255,255),10);
Owner.A_StartSound("powerup/divinehit",CHAN_ITEMEXTRA,CHANF_OVERLAP);
}
}
}
Class DivineSprite : Inventory
{
Mixin SWWMAutoUseFix;
Mixin SWWMOverlapPickupSound;
Mixin SWWMUseToPickup;
Mixin SWWMRespawn;
Mixin SWWMPickupGlow;
override bool Use( bool pickup )
{
// never get auto-used on pickup unless we're in deathmatch
if ( pickup && !deathmatch ) return false;
let p = Owner.FindInventory("DivineSpriteEffect");
if ( p ) return false;
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
Owner.GiveInventory("DivineSpriteEffect",1);
SWWMUtility.AchievementProgressInc('swwm_progress_divine',1,Owner.player);
return true;
}
override void PreTravelled()
{
if ( tracer ) tracer.Destroy();
}
override void Travelled()
{
if ( tracer ) return;
tracer = Spawn("DivineSpriteX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
tracer = Spawn("DivineSpriteX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
Default
{
//$Title Divine Sprite
//$Group Powerups
//$Sprite graphics/HUD/Icons/I_Divine.png
//$Icon powerup
Tag "$T_DIVINE";
Stamina -3600000;
Inventory.Icon "graphics/HUD/Icons/I_Divine.png";
Inventory.PickupSound "misc/p_pkup_vip";
Inventory.UseSound "powerup/divineuse";
Inventory.PickupMessage "$T_DIVINE";
Inventory.MaxAmount 3;
Inventory.InterHubAmount 3;
Inventory.PickupFlash "SWWMPurplePickupFlash";
+INVENTORY.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
FloatBobStrength 0.25;
Radius 8;
Height 24;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class DivineSpriteX : GhostArtifactX
{
Default
{
Scale .5;
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("powerup/divineamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.);
}
override void Tick()
{
if ( !target )
{
Destroy();
return;
}
prev = target.prev+(0,0,16);
vel = target.vel;
if ( (target.pos != pos+(0,0,16)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,16)+vel,true);
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
FloatBobPhase = target.FloatBobPhase;
if ( !bsprite ) bsprite = GetSpriteIndex('XZW1');
bInvisible = target.bInvisible||(target.sprite!=bsprite);
SetState(SpawnState+bInvisible);
A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.);
}
States
{
Spawn:
DVSP A -1 Bright;
TNT1 A -1;
Stop;
}
}