swwmgz_m/zscript/swwm_powerup.zsc
Marisa Kirisame 42db2d3603 Added various screen shaders, plus a bunch of other changes.
Spreadgun rebalancing.
Underwater sounds.
Embiggener use sound.
Other small things here and there.
2020-03-02 14:41:28 +01:00

1462 lines
41 KiB
Text

// Powerups go here
Class GrilledCheeseSandwich : Inventory
{
Mixin SWWMAutoUseFix;
override Inventory CreateCopy( Actor other )
{
// additional lore
SWWMLoreLibrary.Add(other.player,"GCSandwich");
return Super.CreateCopy(other);
}
private void DoTheThing()
{
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.A_StartSound(UseSound,CHAN_ITEMEXTRA);
Owner.GiveBody(1000,1000);
SWWMScoreObj.Spawn(1000,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GREEN);
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");
}
override bool Use( bool pickup )
{
if ( pickup && !deathmatch ) return false;
if ( Owner.Health >= 1000 ) return false;
DoTheThing();
return true;
}
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( passive && (Owner.Health-damage <= 0) && (Amount > 0) )
{
newdamage = 0;
DoTheThing();
Amount--;
}
}
override void DoEffect()
{
Super.DoEffect();
if ( Amount <= 0 ) DepleteOrDestroy();
}
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.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
FloatBobStrength 0.25;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class GhostSnd : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
}
override void Tick()
{
Super.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_LOOPING,.1,1.5);
A_StartSound("powerup/ghostact",CHAN_7,CHANF_LOOPING,.4,ATTN_NONE);
}
override void OnDestroy()
{
Super.OnDestroy();
A_StopSound(CHAN_VOICE);
A_StopSound(CHAN_7);
}
}
Class GhostPower : PowerInvisibility
{
Actor snd;
Default
{
Inventory.Icon "graphics/HUD/Icons/I_Ghost.png";
Powerup.Duration -60;
Powerup.Strength 100;
Powerup.Mode "Translucent";
Powerup.Color "F0E0FF", 0.1;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
SWWMHandler.DoFlash(Owner,Color(96,224,192,255),20);
DoEffect();
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.bNOTARGET = false;
Owner.A_StartSound("powerup/ghostend",CHAN_ITEMEXTRA);
SWWMHandler.DoFlash(Owner,Color(96,224,192,255),20);
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_GHOSTARTI"));
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner ) return;
Owner.bNOTARGET = true;
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
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
Radius 0.1;
Height 0;
+FLOATBOB;
FloatBobStrength 0.25;
}
override void Tick()
{
Super.Tick();
if ( !target )
{
Destroy();
return;
}
Warp(target,flags:WARPF_COPYINTERPOLATION|WARPF_NOCHECKPOSITION);
bInvisible = target.bInvisible||!target.InStateSequence(target.CurState,target.FindState("Spawn"));
}
States
{
Spawn:
XZW1 A -1 Bright;
Stop;
}
}
Class GhostArtifact : Inventory
{
Mixin SWWMAutoUseFix;
Default
{
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.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
FloatBobStrength 0.25;
}
override bool Use( bool pickup )
{
if ( pickup && !deathmatch ) return false;
Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
let g = GhostPower(Owner.FindInventory("GhostPower"));
if ( g ) g.EffectTics = g.default.EffectTics;
else Owner.GiveInventory("GhostPower",1);
return true;
}
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;
}
override void Tick()
{
Super.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_LOOPING,.2,1.5);
A_StartSound("powerup/gravityact",CHAN_7,CHANF_LOOPING,.7,ATTN_NONE);
}
override void OnDestroy()
{
Super.OnDestroy();
A_StopSound(CHAN_VOICE);
A_StopSound(CHAN_7);
}
}
Class GravityPower : Powerup
{
Actor snd;
Default
{
Inventory.Icon "graphics/HUD/Icons/I_Gravity.png";
Powerup.Duration -60;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
DoEffect();
if ( Owner.pos.z <= Owner.floorz )
Owner.vel.z = 1;
}
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);
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;
override bool Use( bool pickup )
{
if ( pickup && !deathmatch ) return false;
Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
let g = GravityPower(Owner.FindInventory("GravityPower"));
if ( g ) g.EffectTics = g.default.EffectTics;
else Owner.GiveInventory("GravityPower",1);
return true;
}
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.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
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 : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
}
override void Tick()
{
Super.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_LOOPING,.4,1.5);
A_StartSound("powerup/invinciballact",CHAN_7,CHANF_LOOPING,.8,ATTN_NONE);
}
override void OnDestroy()
{
Super.OnDestroy();
A_StopSound(CHAN_VOICE);
A_StopSound(CHAN_7);
}
}
Class InvinciballPower : Powerup
{
Actor l, snd;
int lasteffect;
transient int lastpulse;
Default
{
Powerup.Duration -30;
Inventory.Icon "graphics/HUD/Icons/I_Invinciball.png";
Powerup.Color "FF3000", 0.1;
}
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);
}
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);
SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20);
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_INVINCIBALL"));
}
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( (damage > 0) && passive )
{
if ( damageType == 'Ynykron' )
{
newdamage = damage;
return;
}
newdamage = 0;
if ( level.maptime > lasteffect+5 )
{
SWWMHandler.DoFlash(Owner,Color(64,255,64,0),15);
Owner.A_StartSound("powerup/invinciballhit",CHAN_POWERUP);
lasteffect = level.maptime;
lastpulse = max(lastpulse,gametic+20);
}
}
}
}
Class InvinciballX : GhostArtifactX
{
Default
{
RenderStyle "Normal";
}
}
Class FuckingInvinciball : Inventory
{
Mixin SWWMAutoUseFix;
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;
Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
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);
}
else Owner.GiveInventory("InvinciballPower",1);
return true;
}
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.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
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 : Actor
{
Default
{
+NOBLOCKMAP;
+NOGRAVITY;
}
override void Tick()
{
Super.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_LOOPING,.4,1.5);
A_StartSound("powerup/ragekitact",CHAN_7,CHANF_LOOPING,.5,ATTN_NONE);
}
override void OnDestroy()
{
Super.OnDestroy();
A_StopSound(CHAN_VOICE);
A_StopSound(CHAN_7);
}
}
Class RagekitPower : Powerup
{
Actor l, snd;
int lasteffect, lastrage;
transient int lastpulse;
override double GetSpeedFactor()
{
return 2.;
}
Default
{
Powerup.Duration -30;
Inventory.Icon "graphics/HUD/Icons/I_Ragekit.png";
Powerup.Color "FF0000", 0.2;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
if ( Owner.player == players[consoleplayer] )
lastrage = SWWMHandler.AddOneliner("ragekit",2,20);
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);
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.player == players[consoleplayer]) && (gametic > lastrage) && (CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt() < 2) )
lastrage = SWWMHandler.AddOneliner("ragekit",2,5);
Owner.A_QuakeEx(2,2,2,Random[Rage](1,2),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.5);
lastpulse = max(lastpulse,gametic+10);
}
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("powerup/ragekitend",CHAN_ITEMEXTRA);
SWWMHandler.DoFlash(Owner,Color(128,255,0,0),30);
Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_RAGEKIT"));
}
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( !passive && ((damageType == 'Melee') || (damageType == 'Dash') || (damageType == 'GroundPound')) )
{
newdamage = damage*8;
if ( level.maptime > lasteffect+5 )
{
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) )
lastrage = SWWMHandler.AddOneliner("ragekit",2,5);
Owner.A_StartSound("powerup/ragekithit",CHAN_POWERUP);
lasteffect = level.maptime;
lastpulse = max(lastpulse,gametic+35);
}
}
else if ( passive )
newdamage = damage/4;
}
}
Class RagekitX : GhostArtifactX
{
Default
{
RenderStyle "Normal";
}
}
Class Ragekit : Inventory
{
Mixin SWWMAutoUseFix;
override bool Use( bool pickup )
{
if ( pickup && !deathmatch ) return false;
Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
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);
}
else Owner.GiveInventory("RagekitPower",1);
return true;
}
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.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
FloatBobStrength 0.25;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class Omnisight : Inventory
{
override bool Use( bool pickup )
{
if ( !level.allmap )
{
Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
level.allmap = true;
}
// not used up, must be kept for the targetting features to work
return false;
}
Default
{
Tag "$T_OMNISIGHT";
Inventory.UseSound "powerup/omnisight";
Inventory.PickupSound "misc/p_pkup";
Inventory.PickupMessage "$I_OMNISIGHT";
Inventory.MaxAmount 1;
Inventory.InterHubAmount 0;
+INVENTORY.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
+FLOATBOB;
FloatBobStrength 0.25;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
Class LampMoth : Actor
{
Actor lamp;
Vector3 trail, ofs;
int lifespan;
Default
{
Tag "$T_MOTH";
Radius 1;
Height 2;
Speed 2;
DamageFunction 1;
MeleeRange 16;
Mass 10;
Health 10;
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 ( master && master.player ) return String.Format(StringTable.Localize("$O_MOTH"),master.player.GetUserName());
return StringTable.Localize("$O_MOTH2");
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("moth/fly",CHAN_BODY,CHANF_LOOPING,.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) ) continue;
mindist = dist;
lamp = a;
master = a.target;
if ( CompanionLamp(lamp).moff.Find(self) == -1 )
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) ) 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);
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,self,GetMissileDamage(0,0),'Melee',Random[Moth](0,8)?DMG_NO_PAIN:0);
if ( !target.bNOBLOOD )
{
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() || 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 100;
}
}
Class LampMashiro : Actor abstract
{
//
// ~nothing here yet, but she will make an appearance someday~
//
// ⠀⠀⠀⠀⠤⠀⠄⠀⠀⠀⠳⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡣⣁⢌⡀⢄⠁⡁⠝⢿⣿⣿⣿⡻⣿⣿⣷⣦
// ⠀⠀⠠⠀⠠⠀⠀⡀⠘⣠⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣊⢇⠠⢐⢄⡙⢿⣿⣿⣾⠫⣻⣿
// ⠀⡄⠂⢴⠠⠌⠰⢇⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠣⡣⡱⣌⠻⣿⣿⣿⣿⣿
// ⠀⠁⠛⠂⠀⠉⡍⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠘⣮⣾⡮⢦⡹⣿⣿⣿⣿
// ⠄⠠⠉⢼⡇⠶⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⢿⣿⣿⡦⢣⡉⢝⢝⢽
// ⠀⠀⠚⠃⠀⠀⡀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⢸⣿⣿⣿⢀⢃⠀⡁⠑
// ⠰⠀⠂⠁⠀⠀⡆⠀⠀⠀⣴⣿⣿⣿⣿⣿⠋⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⣿⣿⣿⣇⠆⢃⠈⡇
// ⠀⠀⠀⢠⠄⣠⣇⠀⢀⣾⣿⣿⣿⣿⣿⡃⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⢔⢌⢆⢑
// ⠀⠀⠀⢀⣶⣿⣷⢀⣾⣿⣿⣿⣿⡯⡊⠀⢸⡏⣟⣻⣽⣭⣽⣛⡛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢕⢕⠄
// ⠀⢠⣶⣿⣿⢿⢃⣾⣿⣿⣿⣿⡫⡪⠀⠀⢸⡇⣿⣿⣿⣿⣿⣿⣿⣦⡘⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢔⢕⢅
// ⠀⣺⣿⡉⠕⠁⣼⣿⣿⣿⡿⡫⡪⠂⠀⢔⢸⢀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣶⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣪⠃⣿⣿⣿⣿⡟⢄⣡⣵
// ⡛⠟⢋⠀⠀⢸⣿⣿⣿⣿⢠⢫⡊⠀⡔⢵⡈⢠⢣⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠡⠚⠓⠪⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡫⡎⣸⣿⣿⣿⡟⢠⢃⣂⡿
// ⡃⡊⢃⡔⡠⣿⣿⣿⣿⡇⣜⡘⢀⢜⡀⡅⡆⠘⢐⠁⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢾⠿⢿⣿⣿⣿⣿⣷⣾⣽⡻⢿⣿⣿⣿⣿⣿⣿⣿⡿⣛⣿⣿⣟⡜⠠⣻⣿⣿⡟⠴⠃⠉⠁⠀
// ⡺⢠⡾⣸⡷⣿⣿⣿⣿⠂⣓⠂⡎⡺⠪⠒⠃⠀⠀⠀⠀⠀⠙⠿⣯⢻⣿⣿⣿⡟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣾⣿⡷⣲⣿⣿⣿⣿⣿⣿⣿⣷⣝⢿⣿⣿⣿⡿⡫⡺⣽⣿⣿⡗⠀⢸⣿⣿⠏⠀⠀⠀⠀⠀⠀
// ⣴⣿⢣⣿⢳⣿⣿⣿⡿⡨⡊⠠⡪⡢⠂⠀⠀⠀⠀⠀⢀⣀⠀⠀⠘⢧⡻⣿⣿⡇⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢳⣝⠿⡫⡪⣪⡮⣾⣿⡟⢠⠅⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀
// ⣿⡏⣾⢣⡾⣿⣿⣿⡇⢪⠂⡨⡠⠀⠀⠀⠀⢀⡴⠋⠉⠈⠉⢦⡀⠠⢅⠹⣿⢰⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢟⣫⣾⡿⣫⣦⠸⢊⡄⣼⣿⡟⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⡿⢸⣏⣾⣇⣿⣿⣿⡇⢵⢀⢓⠀⠀⠀⠀⠀⡮⠀⠀⠀⠀⠀⠈⣇⠀⠧⡗⡈⢿⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⣯⣾⣿⡿⠛⠊⣬⠴⢪⠋⣼⣿⠟⢔⢝⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⠃⡿⣸⣿⣿⢸⣿⣿⡇⡊⢰⢱⢂⡀⠀⠀⠀⢳⠀⠀⠀⠀⠀⢰⡇⠀⣸⢐⡈⢄⢿⠿⠿⢿⡿⠽⢫⣵⣿⡿⠿⠛⠛⠙⠉⠁⠀⠞⠙⠃⠍⡁⠖⡪⡫⡪⣻⢁⣾⡿⢃⢞⢕⡝⢰⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
// ⢸⢣⣿⣿⡗⡈⢿⣿⡇⠀⢸⣿⣷⣵⣄⠀⠀⠈⠲⣠⣠⣠⡴⠋⠀⠀⢒⢔⢂⢀⢎⢇⣓⡕⠂⡚⡩⢡⢰⢂⣓⡣⣢⠒⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⡲⡑⢡⣾⠟⡠⡣⣂⢇⠃⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀
// ⡟⣾⣿⡿⣸⢸⡘⣿⣷⠀⢸⣿⣿⣿⣿⣿⣦⣄⣀⣀⣀⣀⣀⣠⣤⣜⢔⢕⢕⢔⢕⢕⣗⣇⣳⡪⣺⢐⢱⢑⢒⠖⠁⠀⠀⢀⠄⠔⠖⢦⣀⠀⠀⠰⠌⣴⠟⡡⡪⡪⡪⡜⡜⣸⣿⣿⣿⣿⡿⡢⠀⠀⠀⠀⠀⠀
// ⢱⣿⣿⡣⢑⠔⡕⠘⣿⡀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣷⣵⣕⣥⣣⣣⡣⡪⣐⢱⢠⡃⠀⠀⠀⣴⠁⠀⠀⠀⠀⠸⣆⠀⠀⠘⠀⡪⡪⡪⡪⡪⣸⢡⣿⣿⣿⣿⣿⡯⣺⡀⢤⡀⠀⠀⠀
// ⣿⣿⣟⣝⣝⣺⠁⠀⠘⣧⢘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣇⣣⠣⡘⡀⠀⠀⠀⡟⠀⠀⠀⠀⠀⠨⡗⠀⠀⠀⠈⢮⠺⡪⢊⢫⠃⣾⣿⣿⣿⣿⣿⡪⡏⣦⡀⢹⣷⣤⣀
// ⣿⣿⢑⢇⢆⠆⠀⠀⣸⣎⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⣧⣀⠀⠀⢻⡀⠀⠀⠀⢀⡼⠃⠀⠀⠀⠀⠸⢘⢜⡐⡕⣸⣿⣿⣿⣿⣿⣃⣟⣱⣿⣿⣧⢻⣿⣿
// ⣿⣟⢕⢕⢼⠀⠀⠀⣿⣿⣧⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠉⠓⠒⠓⠋⠀⠀⠀⠀⠀⠠⠀⠱⡱⡨⢣⣿⣿⣿⣿⣿⢗⡯⣱⣿⣿⣿⢇⣄⠃⠙
// ⣿⡒⡢⡢⡃⠀⠀⠀⣿⣿⣿⣷⣝⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣄⣀⣀⣀⣀⢀⣀⡀⡠⣀⡢⡪⠂⣽⣿⣿⣿⣿⣟⡕⣺⣿⣿⣿⣏⣿⣷⢔⡄
// ⡿⡸⢰⢔⠂⠀⠀⠀⢿⣿⣿⣿⣿⣯⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣯⢮⡊⡠⣰⣿⣿⣿⣿⡿⣡⣿⣿⣿⣿⡟⣾⣿⣿⢵⠅
// ⣟⢕⢝⣗⠀⠀⠀⡹⡘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⡾⢱⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⡿⣹⣿⣿⡟⡹⢰
// ⢕⢕⢕⢕⠠⡢⣪⢂⢇⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⣾⣿⢣⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⡿⣱⣿⣿⣿⡫⡇⣼
// ⢕⢕⢕⠅⣘⡪⡚⡔⠍⠂⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣫⣾⣿⢟⢁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢣⣿⣿⣿⣏⡼⢠⣿
// ⢕⢕⢕⠀⣖⡪⡪⣂⠀⠐⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢟⣫⣵⡿⢟⣫⡴⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⣸⣿⣿⣿⢒⠇⣼⣿
// ⢕⢅⢕⠨⣃⢪⢨⢂⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣳⢰⢬⡙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣍⣛⠻⠿⠿⠿⢛⣯⡭⠶⣞⣛⣭⣵⣾⣿⠿⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⣿⣿⣿⡯⡝⢰⣿⣿
// ⢝⢕⠇⢸⠱⡑⡁⠀⣀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣬⣃⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⡱⢃⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⣼⣿⣿⣿⣸⢡⣿⣿⣿
// ⢕⢕⠅⢜⠕⡠⣢⣾⣿⣷⣤⣄⢀⣬⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢛⠴⢡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⢠⣿⣿⣿⢝⢡⣿⣿⣿⣿
// ⢕⢕⠅⣕⢥⡶⢀⠻⣿⣿⣿⣟⢿⣿⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢫⡐⣕⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⢀⣿⣿⣿⠏⢁⣿⣿⣿⣿⣿
// ⢕⢕⢠⢗⣛⣓⣁⢱⣾⣿⣿⣿⣷⣷⣽⣿⡻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢛⢔⠕⠓⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⢀⣾⣿⡿⡣⢎⣿⣿⣿⣿⣿⣿
// ⡕⡕⠠⡅⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡝⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠐⣁⡴⡴⢣⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⢠⣾⠿⣫⠞⣱⣿⣿⣿⣿⣿⠟⡡
// ⢅⢇⢘⢕⢍⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠛⠉⠁⠀⢀⢜⢕⢅⠕⣡⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠐⡫⡠⠜⣡⣾⣿⣿⡿⠟⠫⠦⠪⠊
// ⢕⢕⢸⣵⣷⣾⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣦⣤⡩⣉⣉⡩⣉⣉⠉⠉⠈⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⣤⢔⢗⢕⠕⣰⣿⣿⣿⣿⣿⣿⣿⣿⡿⢋⢔⢕⠕⣓⠏⣊⠰⠻⠟⠛⣉⠅⢄⠂⡪⡪⡪⠨
//
}
Class CompanionLamp : Actor
{
Vector3 Trail;
Array<LampMoth> moff;
Actor parent;
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
for ( int i=0; i<moff.Size(); i++ )
{
if ( moff[i] && (moff[i].lamp == self) && moff[i].isEntranced() ) continue;
moff.Delete(i);
i--;
}
if ( (GetAge()%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;
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);
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( !parent || !SWWMLamp(master) )
{
Destroy();
return;
}
Spawn("SWWMItemFog",pos);
Trail = pos;
}
// the stupidest thing ever, it's called BlockingLine but it's not always blocking us
private bool BlockingLineIsBlocking()
{
if ( !BlockingLine ) return false;
// one-sided
if ( !BlockingLine.sidedef[1] ) return true;
// blocks us
if ( BlockingLine.flags&(Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) ) return true;
return false;
}
private int WhichBlockingLineSide()
{
if ( !BlockingLine ) return 0;
return ((pos.y-BlockingLine.v1.p.y)*BlockingLine.delta.x+(BlockingLine.v1.p.x-pos.x)*BlockingLine.delta.y > double.epsilon);
}
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() || BlockingLineIsBlocking() || 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 )
{
Actor f = Spawn("SWWMItemFog",pos);
f.A_StartSound("lamp/disappear",CHAN_VOICE);
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 ( BlockingLine && BlockingLineIsBlocking() )
{
// push away from wall
Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
if ( !WhichBlockingLineSide() ) 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).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).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);
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);
return ResolveState("Spawn");
}
return ResolveState(null);
}
Wait;
}
}
Class SWWMLamp : Inventory
{
bool bActive;
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 > 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;
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 void DoEffect()
{
Super.DoEffect();
if ( !thelamp )
{
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;
}
clearscope bool isBlinking()
{
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.ALWAYSPICKUP;
+INVENTORY.AUTOACTIVATE;
+INVENTORY.INVBAR;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
FloatBobStrength 0.25;
SWWMLamp.Charge 100;
Stamina 70000;
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}