swwmgz_m/zscript/items/swwm_powerups.zsc
Marisa the Magician 1802b24cf8 Nuggets give 2 health / 5 armor.
Mag ammo can now be sold.
Fix selling prices of bulk items.
Fix rare case where mag ammo can be picked up without a parent.
Various other adjustments.
2022-06-22 01:50:28 +02:00

3751 lines
100 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("gcsandwich",1,Owner.player);
SWWMHandler.DoFlash(Owner,Color(64,255,255,64),10);
Owner.A_QuakeEx(9,9,9,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
Owner.GiveBody(1000,1000);
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
Inventory found = null;
for ( Inventory i=other.Inv; i; i=i.Inv )
{
if ( !(i is 'SWWMHealth') && !(i is 'SWWMArmor') ) continue;
found = i;
}
if ( !found ) return;
// place ourselves right after it
Inventory saved = found.Inv;
found.Inv = self;
other.Inv = Inv;
Inv = saved;
}
override void OnDrop( Actor dropper )
{
lastdropper = dropper;
}
override void PostTeleport( Vector3 destpos, double destangle, int flags )
{
if ( !lastdropper ) return;
SWWMUtility.MarkAchievement("tele",lastdropper.player);
}
Default
{
//$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("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("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 InvinciballArmor : SWWMArmor
{
Default
{
+SWWMARMOR.NOHITSOUND;
+SWWMARMOR.NOHITFLASH;
+SWWMARMOR.NODRAIN;
SWWMArmor.ArmorPriority 9;
}
override int HandleDamage( int damage, Name damageType, int flags )
{
if ( damageType == 'EndLevel' ) return 0;
if ( master ) InvinciballPower(master).DoHitFX();
return damage;
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner.FindInventory('InvinciballPower') )
Destroy();
}
}
Class InvinciballPower : Powerup
{
Mixin SWWMShadedPowerup;
Actor l, snd;
Inventory am;
int lasteffect;
transient int lastpulse;
Default
{
Powerup.Duration -30;
Inventory.Icon "graphics/HUD/Icons/I_Invinciball.png";
Powerup.Color "FF 30 00", 0.1;
+INVENTORY.ADDITIVETIME;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
lasteffect = int.min;
l = Spawn("InvinciballLight",Owner.pos);
l.target = Owner;
l.master = self;
lastpulse = max(lastpulse,gametic+35);
SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.1;
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner ) return;
if ( !snd ) snd = Spawn("InvinciSnd",Owner.pos);
snd.target = Owner;
snd.master = self;
if ( am ) return;
am = Owner.FindInventory("InvinciballArmor");
if ( !am )
{
am = Inventory(Spawn("InvinciballArmor"));
am.AttachToOwner(Owner);
}
am.master = self;
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("powerup/invinciballend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.05;
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_INVINCIBALL"));
}
void DoHitFX()
{
if ( level.maptime > lasteffect+5 )
{
SWWMHandler.DoFlash(Owner,Color(64,255,64,0),15);
Owner.A_StartSound("powerup/invinciballhit",CHAN_POWERUP,CHANF_OVERLAP);
lasteffect = level.maptime;
lastpulse = max(lastpulse,gametic+20);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 1.05;
}
}
}
Class InvinciballX : 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("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 RagekitArmor : SWWMArmor
{
Default
{
+SWWMARMOR.NOHITSOUND;
+SWWMARMOR.NOHITFLASH;
+SWWMARMOR.NODRAIN;
SWWMArmor.ArmorPriority 8;
}
override int HandleDamage( int damage, Name damageType, int flags )
{
if ( damageType != 'EndLevel' ) return 0;
return int(damage*.75);
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner.FindInventory('RagekitPower') )
Destroy();
}
}
Class RagekitPower : Powerup
{
Mixin SWWMShadedPowerup;
Actor l, snd;
Inventory am;
int lasteffect;
transient int lastpulse, lastrage;
override double GetSpeedFactor()
{
return 2.;
}
Default
{
Powerup.Duration -30;
Inventory.Icon "graphics/HUD/Icons/I_Ragekit.png";
Powerup.Color "FF 00 00", 0.2;
+INVENTORY.ADDITIVETIME;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
if ( !(Owner is 'Demolitionist') )
{
// only usable by Demolitionist
Destroy();
return;
}
if ( Owner.player == players[consoleplayer] )
lastrage = SWWMHandler.AddOneliner("ragekit",2,20)+40;
Owner.A_AlertMonsters(swwm_uncapalert?0:5000);
SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30);
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
lasteffect = int.min;
lastpulse = max(lastpulse,gametic+35);
Demolitionist(Owner).lastbump *= .95;
l = Spawn("RagekitLight",Owner.pos);
l.target = Owner;
l.master = self;
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner ) return;
if ( !snd ) snd = Spawn("RageSnd",Owner.pos);
snd.target = Owner;
snd.master = self;
if ( !(level.maptime%30) )
{
SWWMHandler.DoFlash(Owner,Color(16,255,0,0),5);
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)+20;
Owner.A_QuakeEx(2,2,2,Random[Rage](1,2),0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.5);
lastpulse = max(lastpulse,gametic+10);
Demolitionist(Owner).lastbump *= .995;
}
if ( am ) return;
am = Owner.FindInventory("RagekitArmor");
if ( !am )
{
am = Inventory(Spawn("RagekitArmor"));
am.AttachToOwner(Owner);
}
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("powerup/ragekitend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
SWWMHandler.DoFlash(Owner,Color(128,255,0,0),30);
Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
Owner.A_AlertMonsters(2000);
Demolitionist(Owner).lastbump *= .9;
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_RAGEKIT"));
}
void DoHitFX()
{
if ( level.maptime <= lasteffect+5 ) return;
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|QF_3D,rollIntensity:1.);
if ( (Owner.player == players[consoleplayer]) && (gametic > lastrage) && (swwm_mutevoice < 2) )
lastrage = SWWMHandler.AddOneliner("ragekit",2,5)+40;
Owner.A_StartSound("powerup/ragekithit",CHAN_POWERUP,CHANF_OVERLAP);
lasteffect = level.maptime;
lastpulse = max(lastpulse,gametic+35);
Demolitionist(Owner).lastbump *= .9;
}
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( passive ) return;
if ( (damageType == 'Melee') || (damageType == 'Jump') || (damageType == 'Dash') || (damageType == 'Buttslam') || (damageType == 'GroundPound') )
{
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("rage",1,Owner.player);
if ( !(Owner is 'Demolitionist') )
{
SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30);
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
return true;
}
let r = RagekitPower(Owner.FindInventory("RagekitPower"));
if ( r )
{
r.EffectTics += r.default.EffectTics;
SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30);
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
r.lastpulse = max(r.lastpulse,gametic+35);
Demolitionist(Owner).lastbump *= .95;
}
else Owner.GiveInventory("RagekitPower",1);
return true;
}
override void PreTravelled()
{
if ( tracer ) tracer.Destroy();
}
override void Travelled()
{
if ( tracer ) return;
tracer = Spawn("RagekitX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
tracer = Spawn("RagekitX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
Default
{
//$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 TryPickup( in out Actor toucher )
{
if ( !level.allmap )
{
Actor rt = toucher;
if ( toucher.player ) rt = toucher.player.mo;
if ( rt.player == players[consoleplayer] )
{
rt.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP);
// automatically zoom out so the player can know how far this goes
CVar.FindCVar('swwm_mm_zoom').SetFloat(2.);
}
if ( rt is 'Demolitionist' )
Demolitionist(rt).lastbump *= 1.1;
level.allmap = true;
// activate all interest markers
let ti = ThinkerIterator.Create("SWWMInterestMarker",STAT_MAPMARKER);
Actor a;
while ( a = Actor(ti.Next()) ) a.bDORMANT = false;
// "spread" to all players
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo || (players[i] == rt.player) ) continue;
if ( i == consoleplayer )
{
Console.Printf(StringTable.Localize("$D_OMNISHARE"),rt.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;
}
}
GoAwayAndDie();
return true;
}
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.RestrictedTo "Demolitionist";
Inventory.PickupFlash "SWWMPurplePickupFlash";
+INVENTORY.ALWAYSPICKUP;
+COUNTITEM;
+INVENTORY.BIGPOWERUP;
+FLOATBOB;
+NOTDMATCH;
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("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 = abs(Stamina)/2;
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 )
{
if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRA_FEM":"$SWWM_SELLEXTRA"),GetTag(),sellprice);
else Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRAREM_FEM":"$SWWM_SELLEXTRAREM"),Owner.player.GetUserName(),GetTag(),sellprice);
}
}
else if ( Charge > 0 )
{
int AddCharge = Charge+SWWMLamp(item).Charge;
Charge = min(Default.Charge,AddCharge);
// if there's charge to spare, increase amount
if ( AddCharge > Charge )
{
if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max;
Amount = min(MaxAmount,Amount+item.Amount);
Charge = AddCharge-Charge;
}
}
else
{
if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max;
// new copy, increase and take its charge
Amount = min(MaxAmount,Amount+item.Amount);
Charge = SWWMLamp(item).Charge;
}
item.bPickupGood = true;
return true;
}
return Super.HandlePickup(item);
}
override bool Use( bool pickup )
{
if ( pickup && !deathmatch ) return false;
bActivated = true;
bActive = !bActive;
if ( !OnIcon ) OnIcon = TexMan.CheckForTexture("graphics/HUD/Icons/I_Lamp.png",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 BarrierArmor : SWWMArmor
{
Default
{
+SWWMARMOR.NOHITSOUND;
+SWWMARMOR.NOHITFLASH;
+SWWMARMOR.NODRAIN;
SWWMArmor.ArmorPriority 10;
}
override int HandleDamage( int damage, Name damageType, int flags )
{
if ( (damageType == 'Fire') || (damageType == 'Ice') || (damageType == 'Slime') || (damageType == 'Electric') || (damageType == 'Plasma') || (damageType == 'Radiation') || (damageType == 'Wind') || (damageType == 'Water') || (damageType == 'Corroded') || (damageType == 'Lava') )
return damage;
return 0;
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner.FindInventory('BarrierPower') )
Destroy();
}
}
Class BarrierPower : PowerIronFeet
{
Mixin SWWMShadedPowerup;
Actor snd, l;
Inventory am;
Default
{
Inventory.Icon "graphics/HUD/Icons/I_Barrier.png";
Powerup.Duration -60;
Powerup.Color "20 FF 00", 0.1;
Powerup.Mode "Full"; // no leaky damage
+INVENTORY.ADDITIVETIME;
}
override void InitEffect()
{
Super.InitEffect();
if ( !Owner ) return;
l = Spawn("BarrierLight",Owner.pos);
l.target = Owner;
l.master = self;
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 0.95;
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("powerup/barrierend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= 0.95;
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_BARRIER"));
}
override void DoEffect()
{
// don't reset air supply like PowerIronFeet, call parent instead
Powerup.DoEffect();
if ( !Owner ) return;
if ( !snd ) snd = Spawn("BarrierSnd",Owner.pos);
snd.target = Owner;
snd.master = self;
if ( !am )
{
am = Owner.FindInventory("BarrierArmor");
if ( !am )
{
am = Inventory(Spawn("BarrierArmor"));
am.AttachToOwner(Owner);
}
}
// break ourselves if we're in an endlevel sector
bool endlv = false;
for ( int i=0; i<Owner.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("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 = 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);
let p = SWWMPuff.Setup(t.hitlist[i].hitlocation,t.hitlist[i].x,self,target,t.hitlist[i].hitactor);
t.hitlist[i].hitactor.DamageMobj(p,target,dmg,'Plasma',DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF);
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|QF_3D,falloff:1000,rollintensity:2.);
A_StartSound("powerup/mykradvo",CHAN_BODY,CHANF_DEFAULT,1.,.25);
A_StartSound("powerup/mykradvo",CHAN_VOICE,CHANF_DEFAULT,1.,.25);
FlashPlayer(100,1500);
int numpt = Random[ExploS](20,30);
for ( int i=0; i<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|QF_3D);
// don't consume on failure
Amount++;
return true;
}
if ( (targets.Size() == 1) && targets[0] && !targets[0].bBOSS && !targets[0].FindInventory("BossMarker") )
SWWMUtility.MarkAchievement("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("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 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.);
WorldOffset = (0,0,16);
}
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;
A_SetScale(.16+.01*sin(GetAge()*4));
if ( !bsprite ) bsprite = GetSpriteIndex('XZW0');
bool bOldInvis = bInvisible;
bInvisible = target.bInvisible||(target.sprite!=bsprite);
if ( bInvisible != bOldInvis )
{
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 PostBeginPlay()
{
Super.PostBeginPlay();
WorldOffset = (0,0,16+special2);
}
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);
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');
bool bOldInvis = bInvisible;
bInvisible = target.bInvisible||(target.sprite!=bsprite);
if ( bInvisible != bOldInvis )
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("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|QF_3D,rollIntensity:1.);
lasteffect = int.min;
lastpulse = max(lastpulse,gametic+35);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= .95;
l = Spawn("AngeryLight",Owner.pos);
l.target = Owner;
l.master = self;
}
override void DoEffect()
{
Super.DoEffect();
if ( !Owner ) return;
if ( !snd ) snd = Spawn("AngerySnd",Owner.pos);
snd.target = Owner;
snd.master = self;
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("powerup/devastationend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
SWWMHandler.DoFlash(Owner,Color(128,224,0,255),30);
Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
Owner.A_AlertMonsters(2000);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= .9;
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DEVASTATION"));
}
void DoHitFX()
{
if ( level.maptime <= lasteffect+5 ) return;
Owner.A_AlertMonsters(swwm_uncapalert?0:5000);
SWWMHandler.DoFlash(Owner,Color(64,224,0,255),10);
Owner.A_QuakeEx(8,8,8,Random[Rage](3,8),0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
Owner.A_StartSound("powerup/devastationhit",CHAN_POWERUP,CHANF_OVERLAP);
lasteffect = level.maptime;
lastpulse = max(lastpulse,gametic+35);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= .9;
}
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( passive || (damage <= 0) ) return;
// (2^31-1)/25 : guarantee that it caps rather than overflowing
if ( damage > 85899345 ) newdamage = int.max;
else newdamage = damage*25;
// don't play hit fx for wall busting, as it'll be done manually if the bust goes through
if ( damageType != 'Wallbust' ) DoHitFX();
}
}
Class AngerySigil : Inventory
{
Mixin SWWMAutoUseFix;
Mixin SWWMOverlapPickupSound;
Mixin SWWMUseToPickup;
Mixin SWWMRespawn;
Mixin SWWMPickupGlow;
override bool Use( bool pickup )
{
if ( pickup && !deathmatch ) return false;
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
SWWMUtility.AchievementProgressInc("deva",1,Owner.player);
let r = AngeryPower(Owner.FindInventory("AngeryPower"));
if ( r )
{
r.EffectTics += r.default.EffectTics;
SWWMHandler.DoFlash(Owner,Color(64,224,0,255),30);
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:1.);
if ( Owner is 'Demolitionist' )
Demolitionist(Owner).lastbump *= .95;
}
else Owner.GiveInventory("AngeryPower",1);
return true;
}
override void PreTravelled()
{
if ( tracer ) tracer.Destroy();
}
override void Travelled()
{
Super.Travelled();
if ( tracer ) return;
tracer = Spawn("AngerySigilX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
tracer = Spawn("AngerySigilX",pos);
tracer.angle = angle;
tracer.target = self;
tracer.FloatBobPhase = FloatBobPhase;
}
Default
{
//$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.);
WorldOffset = (0,0,20);
}
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');
bool bOldInvis = bInvisible;
bInvisible = target.bInvisible||(target.sprite!=bsprite);
if ( bInvisible != bOldInvis )
{
SetState(SpawnState+bInvisible);
A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.);
}
}
States
{
Spawn:
BLPS C -1 Bright;
TNT1 A -1;
Stop;
}
}
Class DivineSpriteLight : PointLightAttenuated
{
Default
{
Args 255,255,255,100;
}
override void Tick()
{
Super.Tick();
if ( !target || !master )
{
Destroy();
return;
}
if ( target.player )
SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true);
else SetOrigin(target.Vec3Offset(0,0,target.height/2),true);
double vol = clamp((target.Health-1000)/6000.,0.,1.);
int lv = clamp(int(vol*255),0,255);
args[LIGHT_RED] = lv;
args[LIGHT_GREEN] = lv;
args[LIGHT_BLUE] = lv;
args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*10;
}
}
Class DivineSpriteSnd : 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("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.);
WorldOffset = (0,0,16);
}
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');
bool bOldInvis = bInvisible;
bInvisible = target.bInvisible||(target.sprite!=bsprite);
if ( bOldInvis != bInvisible )
{
SetState(SpawnState+bInvisible);
A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.);
}
}
States
{
Spawn:
DVSP A -1 Bright;
TNT1 A -1;
Stop;
}
}