- Fuck it, Quadravol will be lever action. - Tiny cleanup. - Rewrite the weapon replacement system (less of a mess now maybe?). - Some menu fixes. - Minimap zoom increments like in the Common Library. - Add missing sound definition for Safety Tether. (This mostly went unnoticed because it's VERY rare to have it play) - Shift Sparkster x3 (DLC2) to slot 7. This way you can have both it and the Quadravol simultaneously. It would be unfair to not let you hold both "iconic" UnSX weapons at once. - Small lore tweak on Quadravol stance swap. - Fix off-by-one bug in looping palette lights. - Re-do logo shader. Use separate layer textures. - Fix Elemental Coating breaking "End Level" damage sectors. (This will be the last batch of changes before I continue working on menus)
3755 lines
102 KiB
Text
3755 lines
102 KiB
Text
// Powerups go here
|
|
Class GrilledCheeseSafeguard : Powerup
|
|
{
|
|
Default
|
|
{
|
|
Inventory.Icon "graphics/HUD/Icons/I_Sandwich.png";
|
|
Powerup.Duration -3;
|
|
}
|
|
|
|
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( passive ) newdamage = 0;
|
|
}
|
|
}
|
|
|
|
Class GrilledCheeseSandwich : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
// for falling off cliffs and others
|
|
// last 5 seconds of safe positions
|
|
bool lastsafevalid;
|
|
Vector3 lastsafepos[5];
|
|
double lastsafeangle[5];
|
|
int safetic;
|
|
int dteleport;
|
|
Actor lastdropper;
|
|
|
|
override Inventory CreateCopy( Actor other )
|
|
{
|
|
// additional lore
|
|
SWWMLoreLibrary.Add(other.player,"GCSandwich");
|
|
return Super.CreateCopy(other);
|
|
}
|
|
void DoTheThing( bool extrasafe = false )
|
|
{
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_gcsandwich',1,Owner.player);
|
|
SWWMHandler.DoFlash(Owner,Color(64,255,255,64),10);
|
|
Owner.A_QuakeEx(9,9,9,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
Owner.GiveBody(1000,1000);
|
|
SWWMScoreObj.Spawn(1000,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
|
|
if ( Owner is 'Demolitionist' )
|
|
{
|
|
if ( swwm_accdamage )
|
|
SWWMScoreObj.Spawn(600,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
|
|
else
|
|
{
|
|
SWWMScoreObj.Spawn(200,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
|
|
SWWMScoreObj.Spawn(150,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
|
|
SWWMScoreObj.Spawn(250,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
|
|
}
|
|
let n = Owner.FindInventory("ArmorNugget");
|
|
if ( !n ) Owner.GiveInventory("ArmorNugget",GetDefaultByType("ArmorNugget").MaxAmount);
|
|
else n.Amount = n.MaxAmount;
|
|
let b = Owner.FindInventory("BlastSuit");
|
|
if ( !b ) Owner.GiveInventory("BlastSuit",GetDefaultByType("BlastSuit").MaxAmount);
|
|
else b.Amount = b.MaxAmount;
|
|
let w = Owner.FindInventory("WarArmor");
|
|
if ( !w ) Owner.GiveInventory("WarArmor",GetDefaultByType("WarArmor").MaxAmount);
|
|
else w.Amount = w.MaxAmount;
|
|
SWWMLoreLibrary.Add(Owner.player,"Nugget");
|
|
SWWMLoreLibrary.Add(Owner.player,"BlastSuit");
|
|
SWWMLoreLibrary.Add(Owner.player,"WarArmor");
|
|
Demolitionist(Owner).lastbump *= 1.2;
|
|
}
|
|
else
|
|
{
|
|
SWWMScoreObj.Spawn(200,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Armor);
|
|
let n = Owner.FindInventory("ArmorNugget");
|
|
if ( !n ) Owner.GiveInventory("ArmorNugget",GetDefaultByType("ArmorNugget").MaxAmount);
|
|
else n.Amount = n.MaxAmount;
|
|
SWWMLoreLibrary.Add(Owner.player,"Nugget");
|
|
}
|
|
let f = Spawn("SWWMItemFog",Owner.Vec3Offset(0,0,Owner.Height/2));
|
|
f.bAMBUSH = true;
|
|
if ( !extrasafe ) return;
|
|
let s = Owner.FindInventory("GrilledCheeseSafeguard");
|
|
if ( !s ) Owner.GiveInventory("GrilledCheeseSafeguard",1);
|
|
else Powerup(s).EffectTics = Powerup(s).default.EffectTics;
|
|
}
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( Owner.Health > 500 ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
DoTheThing();
|
|
return true;
|
|
}
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( Owner.FindInventory("GrilledCheeseSafeguard") )
|
|
return; // the safeguard absorbs all
|
|
if ( Owner.ApplyDamageFactor(damageType,damage) <= 0 )
|
|
return; // this damage type is ignored by the player, so it does not affect us
|
|
if ( damageType == 'EndLevel' )
|
|
return; // don't trigger on endlevel damage
|
|
if ( (damageType == 'Telefrag') && source )
|
|
{
|
|
// prevent overlap with voodoo doll
|
|
if ( source.player == Owner.player )
|
|
dteleport = 1; // teleport has to be on next tic
|
|
else source.DamageMobj(Owner,Owner,damage,damageType,flags); // kill what attempted the telefrag
|
|
}
|
|
if ( (Owner.Health-damage <= 0) && (Amount > 0) )
|
|
{
|
|
if ( (swwm_strictuntouchable == 1) && Owner.player )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) hnd.tookdamage[Owner.PlayerNumber()] = true;
|
|
}
|
|
if ( damageType == 'InstantDeath' )
|
|
SafeTeleport(); // get out of pits
|
|
newdamage = 0;
|
|
if ( (Owner.player == players[consoleplayer]) || bBigPowerup ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
DoTheThing(true);
|
|
Amount--;
|
|
}
|
|
}
|
|
void SafeTeleport( bool tostart = false )
|
|
{
|
|
Spawn("SWWMItemFog",Owner.Vec3Offset(0,0,Owner.Height/2));
|
|
Vector3 safepos;
|
|
double safeangle;
|
|
if ( tostart || !lastsafevalid )
|
|
{
|
|
if ( deathmatch ) [safepos, safeangle] = level.PickDeathmatchStart();
|
|
else [safepos, safeangle] = level.PickPlayerStart(Owner.PlayerNumber());
|
|
}
|
|
else
|
|
{
|
|
safepos = lastsafepos[4];
|
|
safeangle = lastsafeangle[4];
|
|
}
|
|
Owner.Teleport(safepos,safeangle,0);
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( dteleport > 0 )
|
|
{
|
|
dteleport--;
|
|
if ( dteleport <= 0 ) dteleport = -1;
|
|
}
|
|
else if ( dteleport == -1 )
|
|
{
|
|
dteleport = 0;
|
|
SafeTeleport();
|
|
}
|
|
// check safe spot
|
|
if ( Owner && !(Owner.CurSector.flags&Sector.SECF_ENDLEVEL) && (Owner.CurSector.DamageAmount <= 0) && (Owner.waterlevel < 2) && (Owner.GetFloorTerrain().DamageAmount <= 0) && (Owner.pos.z <= Owner.floorz) )
|
|
{
|
|
if ( safetic == 0 )
|
|
{
|
|
if ( lastsafevalid )
|
|
{
|
|
for ( int i=4; i>0; i-- )
|
|
{
|
|
lastsafepos[i] = lastsafepos[i-1];
|
|
lastsafeangle[i] = lastsafeangle[i-1];
|
|
}
|
|
lastsafepos[0] = Owner.pos;
|
|
lastsafeangle[0] = Owner.angle;
|
|
}
|
|
else
|
|
{
|
|
lastsafevalid = true;
|
|
for ( int i=0; i<5; i++ )
|
|
{
|
|
lastsafepos[i] = Owner.pos;
|
|
lastsafeangle[i] = Owner.angle;
|
|
}
|
|
}
|
|
}
|
|
safetic = (safetic+1)%35;
|
|
}
|
|
else safetic = 1;
|
|
if ( (Amount <= 0) && (dteleport == 0) ) DepleteOrDestroy();
|
|
}
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// find last armor/health item or invuln/ragekit/barrier or the collar
|
|
Inventory found = null;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'SWWMHealth') && !(i is 'SWWMArmor') && !(i is 'InvinciballPower') && !(i is 'RagekitPower') && !(i is 'BarrierPower') && !(i is 'AlmasteelPlating') && !(i is 'SayaCollar') ) continue;
|
|
found = i;
|
|
}
|
|
if ( !found ) return;
|
|
// place ourselves right after it
|
|
Inventory saved = found.Inv;
|
|
found.Inv = self;
|
|
other.Inv = Inv;
|
|
Inv = saved;
|
|
}
|
|
override void OnDrop( Actor dropper )
|
|
{
|
|
lastdropper = dropper;
|
|
}
|
|
override void PostTeleport( Vector3 destpos, double destangle, int flags )
|
|
{
|
|
if ( !lastdropper ) return;
|
|
SWWMUtility.MarkAchievement('swwm_achievement_tele',lastdropper.player);
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Grilled Cheese Sandwich
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Sandwich.png
|
|
//$Icon powerup
|
|
Tag "$T_SANDWICH";
|
|
Stamina 800000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Sandwich.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "powerup/sandwich";
|
|
Inventory.PickupMessage "$T_SANDWICH";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 12;
|
|
Height 24;
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Mixin Class SWWMShadedPowerup
|
|
{
|
|
override Color GetBlend()
|
|
{
|
|
if ( swwm_shaders ) return 0;
|
|
return Super.GetBlend();
|
|
}
|
|
}
|
|
|
|
Class GhostSnd : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
if ( players[consoleplayer].Camera == target )
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,0.);
|
|
A_SoundVolume(CHAN_7,.4);
|
|
}
|
|
else
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,.1);
|
|
A_SoundVolume(CHAN_7,0.);
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/ghostact",CHAN_VOICE,CHANF_LOOP,.1,1.5);
|
|
A_StartSound("powerup/ghostact",CHAN_7,CHANF_LOOP,.4,ATTN_NONE);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
A_StopSound(CHAN_VOICE);
|
|
A_StopSound(CHAN_7);
|
|
}
|
|
}
|
|
|
|
Class GhostTarget : Actor
|
|
{
|
|
bool diedie;
|
|
|
|
Default
|
|
{
|
|
+SPECTRAL;
|
|
+NOGRAVITY;
|
|
+DONTSPLASH;
|
|
+SHOOTABLE;
|
|
+NONSHOOTABLE;
|
|
+NOTELEPORT;
|
|
+NODAMAGE;
|
|
+NOBLOOD;
|
|
+CANTSEEK;
|
|
+SHADOW; // so they can barely aim
|
|
Radius .1;
|
|
Height 56;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
if ( diedie ) A_FadeOut(.02);
|
|
let bt = BlockThingsIterator.Create(self,300);
|
|
while ( bt.Next() )
|
|
{
|
|
let t = bt.Thing;
|
|
if ( !t || !t.bIsMonster || t.player || !t.IsHostile(master) || (t.target != self) ) continue;
|
|
if ( SWWMUtility.BoxIntersect(self,t,pad:16) || t.CheckMeleeRange() )
|
|
{
|
|
// they found out, there's no one here
|
|
diedie = true;
|
|
break;
|
|
}
|
|
}
|
|
// player made noise or is visible again
|
|
if ( !master || (LastHeard == master) || !master.FindInventory("GhostPower") )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) for ( int i=0; i<hnd.suckableactors.Size(); i++ )
|
|
{
|
|
let a = hnd.suckableactors[i];
|
|
if ( !a || !a.bISMONSTER || a.player || !a.IsHostile(master) || (a.Health <= 0) ) continue;
|
|
if ( a.target == self ) a.target = master;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
Class GhostPower : PowerInvisibility
|
|
{
|
|
Mixin SWWMShadedPowerup;
|
|
|
|
Actor snd;
|
|
|
|
Default
|
|
{
|
|
Inventory.Icon "graphics/HUD/Icons/I_Ghost.png";
|
|
Powerup.Duration -60;
|
|
Powerup.Strength 100;
|
|
Powerup.Mode "Translucent";
|
|
Powerup.Color "F0 E0 FF", 0.1;
|
|
+INVENTORY.ADDITIVETIME;
|
|
+CANTSEEK;
|
|
}
|
|
|
|
override void InitEffect()
|
|
{
|
|
Super.InitEffect();
|
|
if ( !Owner ) return;
|
|
SWWMHandler.DoFlash(Owner,Color(96,224,192,255),20);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.04;
|
|
DoEffect();
|
|
}
|
|
override void EndEffect()
|
|
{
|
|
Super.EndEffect();
|
|
if ( !Owner ) return;
|
|
Owner.A_StartSound("powerup/ghostend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
SWWMHandler.DoFlash(Owner,Color(96,224,192,255),20);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.02;
|
|
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_GHOSTARTI"));
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner ) return;
|
|
// are any enemies targetting us? if so, make them focus on a fake target located where we currently are standing
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
Actor gt = null;
|
|
if ( hnd ) for ( int i=0; i<hnd.suckableactors.Size(); i++ )
|
|
{
|
|
let a = hnd.suckableactors[i];
|
|
if ( !a || !a.bISMONSTER || a.player || !a.IsHostile(Owner) || (a.Health <= 0) ) continue;
|
|
// make them forget the ghost if we make noise
|
|
if ( (a.LastHeard == Owner) && (a.target is 'GhostTarget') && (a.target.master == Owner) )
|
|
{
|
|
a.target = Owner;
|
|
continue;
|
|
}
|
|
if ( a.target != Owner ) continue;
|
|
if ( !gt )
|
|
{
|
|
gt = Spawn("GhostTarget",Owner.pos);
|
|
if ( Owner.bFRIENDLY || Owner.player ) gt.bFRIENDLY = true;
|
|
}
|
|
a.target = gt;
|
|
a.LastHeard = gt;
|
|
gt.master = Owner;
|
|
}
|
|
if ( !snd ) snd = Spawn("GhostSnd",Owner.pos);
|
|
snd.target = Owner;
|
|
snd.master = self;
|
|
}
|
|
|
|
override void AlterWeaponSprite( VisStyle vis, in out int changed )
|
|
{
|
|
// leave weapons alone
|
|
vis.RenderStyle = STYLE_Normal;
|
|
vis.Alpha = 1.f;
|
|
changed = 1;
|
|
}
|
|
}
|
|
|
|
Class GhostArtifactX : Actor
|
|
{
|
|
SpriteID bsprite;
|
|
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOINTERACTION;
|
|
Radius .1;
|
|
Height 0;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
prev = target.prev;
|
|
vel = target.vel;
|
|
if ( (target.pos != pos) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+vel,true);
|
|
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
|
|
FloatBobPhase = target.FloatBobPhase;
|
|
if ( !bsprite ) bsprite = GetSpriteIndex('XZW1');
|
|
bInvisible = target.bInvisible||(target.sprite!=bsprite);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class GhostArtifact : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
Default
|
|
{
|
|
//$Title Ghost Artifact
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Ghost.png
|
|
//$Icon powerup
|
|
Tag "$T_GHOSTARTI";
|
|
Stamina 120000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Ghost.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "powerup/ghost";
|
|
Inventory.PickupMessage "$T_GHOSTARTI";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 12;
|
|
Height 24;
|
|
}
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
let g = GhostPower(Owner.FindInventory("GhostPower"));
|
|
if ( g )
|
|
{
|
|
g.EffectTics += g.default.EffectTics;
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.04;
|
|
}
|
|
else Owner.GiveInventory("GhostPower",1);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_ghost',1,Owner.player);
|
|
return true;
|
|
}
|
|
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
if ( tracer ) return;
|
|
tracer = Spawn("GhostArtifactX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("GhostArtifactX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class GravSnd : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
if ( players[consoleplayer].Camera == target )
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,0.);
|
|
A_SoundVolume(CHAN_7,.7);
|
|
}
|
|
else
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,.2);
|
|
A_SoundVolume(CHAN_7,0.);
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/gravityact",CHAN_VOICE,CHANF_LOOP,.2,1.5);
|
|
A_StartSound("powerup/gravityact",CHAN_7,CHANF_LOOP,.7,ATTN_NONE);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
A_StopSound(CHAN_VOICE);
|
|
A_StopSound(CHAN_7);
|
|
}
|
|
}
|
|
|
|
Class GravityPower : Powerup
|
|
{
|
|
Mixin SWWMShadedPowerup;
|
|
|
|
Actor snd;
|
|
|
|
Default
|
|
{
|
|
Inventory.Icon "graphics/HUD/Icons/I_Gravity.png";
|
|
Powerup.Duration -60;
|
|
+INVENTORY.ADDITIVETIME;
|
|
}
|
|
|
|
override void InitEffect()
|
|
{
|
|
Super.InitEffect();
|
|
if ( !Owner ) return;
|
|
DoEffect();
|
|
if ( Owner.pos.z <= Owner.floorz )
|
|
Owner.vel.z = 1;
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.04;
|
|
}
|
|
override void EndEffect()
|
|
{
|
|
Super.EndEffect();
|
|
if ( !Owner ) return;
|
|
if ( !Owner.bFLYCHEAT )
|
|
{
|
|
Owner.bFLY = false;
|
|
Owner.bNOGRAVITY = false;
|
|
if ( Owner.pos.z > Owner.floorz ) Owner.player.centering = true;
|
|
}
|
|
Owner.A_StartSound("powerup/gravityend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.02;
|
|
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_GRAVITYS"));
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner ) return;
|
|
Owner.bFLY = true;
|
|
Owner.bNOGRAVITY = true;
|
|
if ( !snd ) snd = Spawn("GravSnd",Owner.pos);
|
|
snd.target = Owner;
|
|
snd.master = self;
|
|
}
|
|
|
|
}
|
|
|
|
Class GravityX : GhostArtifactX
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Normal";
|
|
}
|
|
}
|
|
|
|
Class GravitySuppressor : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
let g = GravityPower(Owner.FindInventory("GravityPower"));
|
|
if ( g )
|
|
{
|
|
g.EffectTics += g.default.EffectTics;
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.04;
|
|
}
|
|
else Owner.GiveInventory("GravityPower",1);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_gravity',1,Owner.player);
|
|
return true;
|
|
}
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
if ( tracer ) return;
|
|
tracer = Spawn("GravityX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("GravityX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Gravity Suppressor
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Gravity.png
|
|
//$Icon powerup
|
|
Tag "$T_GRAVITYS";
|
|
Stamina 150000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Gravity.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "powerup/gravity";
|
|
Inventory.PickupMessage "$T_GRAVITYS";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 12;
|
|
Height 28;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class InvinciballLight : PointLightAttenuated
|
|
{
|
|
Default
|
|
{
|
|
Args 192,64,0,80;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( target.player )
|
|
SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true);
|
|
else SetOrigin(target.Vec3Offset(0,0,target.height/2),true);
|
|
args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*8;
|
|
bDORMANT = Powerup(master).isBlinking();
|
|
}
|
|
}
|
|
Class InvinciSnd : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
if ( players[consoleplayer].Camera == target )
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,0.);
|
|
A_SoundVolume(CHAN_7,.8);
|
|
}
|
|
else
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,.4);
|
|
A_SoundVolume(CHAN_7,0.);
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/invinciballact",CHAN_VOICE,CHANF_LOOP,.4,1.5);
|
|
A_StartSound("powerup/invinciballact",CHAN_7,CHANF_LOOP,.8,ATTN_NONE);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
A_StopSound(CHAN_VOICE);
|
|
A_StopSound(CHAN_7);
|
|
}
|
|
}
|
|
|
|
Class InvinciballPower : Powerup
|
|
{
|
|
Mixin SWWMShadedPowerup;
|
|
|
|
Actor l, snd;
|
|
int lasteffect;
|
|
transient int lastpulse;
|
|
|
|
Default
|
|
{
|
|
Powerup.Duration -30;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Invinciball.png";
|
|
Powerup.Color "FF 30 00", 0.1;
|
|
+INVENTORY.ADDITIVETIME;
|
|
}
|
|
|
|
override void InitEffect()
|
|
{
|
|
Super.InitEffect();
|
|
if ( !Owner ) return;
|
|
lasteffect = int.min;
|
|
l = Spawn("InvinciballLight",Owner.pos);
|
|
l.target = Owner;
|
|
l.master = self;
|
|
lastpulse = max(lastpulse,gametic+35);
|
|
SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.1;
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner ) return;
|
|
if ( !snd ) snd = Spawn("InvinciSnd",Owner.pos);
|
|
snd.target = Owner;
|
|
snd.master = self;
|
|
}
|
|
|
|
override void EndEffect()
|
|
{
|
|
Super.EndEffect();
|
|
if ( !Owner ) return;
|
|
Owner.A_StartSound("powerup/invinciballend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.05;
|
|
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_INVINCIBALL"));
|
|
}
|
|
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// find first with health/armor, plating/collar, sandwich after it
|
|
Inventory found = null;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i.Inv is 'SWWMHealth') && !(i.Inv is 'SWWMArmor') && !(i.Inv is 'GrilledCheeseSandwich') && !(i.Inv is 'AlmasteelPlating') && !(i.Inv is 'SayaCollar') ) continue;
|
|
found = i;
|
|
break;
|
|
}
|
|
if ( !found )
|
|
{
|
|
// we're good
|
|
return;
|
|
}
|
|
// place ourselves right after it
|
|
Inventory saved = found.Inv;
|
|
found.Inv = self;
|
|
other.Inv = Inv;
|
|
Inv = saved;
|
|
}
|
|
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( Owner.ApplyDamageFactor(damageType,damage) <= 0 )
|
|
return; // this damage type is ignored, so don't flash
|
|
if ( damageType == 'EndLevel' )
|
|
return; // don't trigger on endlevel damage
|
|
if ( damage > 0 )
|
|
{
|
|
newdamage = 0;
|
|
if ( level.maptime > lasteffect+5 )
|
|
{
|
|
SWWMHandler.DoFlash(Owner,Color(64,255,64,0),15);
|
|
Owner.A_StartSound("powerup/invinciballhit",CHAN_POWERUP,CHANF_OVERLAP);
|
|
lasteffect = level.maptime;
|
|
lastpulse = max(lastpulse,gametic+20);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.05;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Class InvinciballX : GhostArtifactX
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Normal";
|
|
}
|
|
}
|
|
|
|
Class FuckingInvinciball : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
override Inventory CreateCopy( Actor other )
|
|
{
|
|
// additional lore
|
|
SWWMLoreLibrary.Add(other.player,"Invinciball");
|
|
return Super.CreateCopy(other);
|
|
}
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
Owner.A_StartSound("misc/sundowner",CHAN_POWERUPEXTRA);
|
|
let i = InvinciballPower(Owner.FindInventory("InvinciballPower"));
|
|
if ( i )
|
|
{
|
|
i.EffectTics += i.default.EffectTics;
|
|
i.lastpulse = max(i.lastpulse,gametic+35);
|
|
SWWMHandler.DoFlash(Owner,Color(96,255,64,0),20);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.1;
|
|
}
|
|
else Owner.GiveInventory("InvinciballPower",1);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_sunny',1,Owner.player);
|
|
return true;
|
|
}
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
if ( tracer ) return;
|
|
tracer = Spawn("InvinciballX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("InvinciballX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Invinciball
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Invinciball.png
|
|
//$Icon powerup
|
|
Tag "$T_INVINCIBALL";
|
|
Stamina 640000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Invinciball.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "powerup/invinciball";
|
|
Inventory.PickupMessage "$T_INVINCIBALL";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 10;
|
|
Height 26;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class RagekitLight : PointLightAttenuated
|
|
{
|
|
Default
|
|
{
|
|
Args 255,0,0,80;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( target.player )
|
|
SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true);
|
|
else SetOrigin(target.Vec3Offset(0,0,target.height/2),true);
|
|
args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*8;
|
|
bDORMANT = Powerup(master).isBlinking();
|
|
}
|
|
}
|
|
|
|
Class RageSnd : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
if ( players[consoleplayer].Camera == target )
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,0.);
|
|
A_SoundVolume(CHAN_7,.5);
|
|
}
|
|
else
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,.4);
|
|
A_SoundVolume(CHAN_7,0.);
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/ragekitact",CHAN_VOICE,CHANF_LOOP,.4,1.5);
|
|
A_StartSound("powerup/ragekitact",CHAN_7,CHANF_LOOP,.5,ATTN_NONE);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
A_StopSound(CHAN_VOICE);
|
|
A_StopSound(CHAN_7);
|
|
}
|
|
}
|
|
|
|
Class RagekitPower : Powerup
|
|
{
|
|
Mixin SWWMShadedPowerup;
|
|
|
|
Actor l, snd;
|
|
int lasteffect;
|
|
transient int lastpulse, lastrage;
|
|
|
|
override double GetSpeedFactor()
|
|
{
|
|
return 2.;
|
|
}
|
|
|
|
Default
|
|
{
|
|
Powerup.Duration -30;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Ragekit.png";
|
|
Powerup.Color "FF 00 00", 0.2;
|
|
+INVENTORY.ADDITIVETIME;
|
|
}
|
|
|
|
override void InitEffect()
|
|
{
|
|
Super.InitEffect();
|
|
if ( !Owner ) return;
|
|
if ( !(Owner is 'Demolitionist') )
|
|
{
|
|
// only usable by Demolitionist
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( Owner.player == players[consoleplayer] )
|
|
lastrage = SWWMHandler.AddOneliner("ragekit",2,20)+40;
|
|
Owner.A_AlertMonsters(swwm_uncapalert?0:5000);
|
|
SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30);
|
|
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
lasteffect = int.min;
|
|
lastpulse = max(lastpulse,gametic+35);
|
|
Demolitionist(Owner).lastbump *= .95;
|
|
l = Spawn("RagekitLight",Owner.pos);
|
|
l.target = Owner;
|
|
l.master = self;
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner ) return;
|
|
if ( !snd ) snd = Spawn("RageSnd",Owner.pos);
|
|
snd.target = Owner;
|
|
snd.master = self;
|
|
if ( !(level.maptime%30) )
|
|
{
|
|
SWWMHandler.DoFlash(Owner,Color(16,255,0,0),5);
|
|
if ( Owner.GiveBody(1,100) )
|
|
SWWMScoreObj.Spawn(1,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
|
|
Owner.A_AlertMonsters(swwm_uncapalert?0:2000);
|
|
if ( (Owner.player == players[consoleplayer]) && (gametic > lastrage) && (swwm_mutevoice < 2) )
|
|
lastrage = SWWMHandler.AddOneliner("ragekit",2,5)+40;
|
|
Owner.A_QuakeEx(2,2,2,Random[Rage](1,2),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.5);
|
|
lastpulse = max(lastpulse,gametic+10);
|
|
Demolitionist(Owner).lastbump *= .995;
|
|
}
|
|
}
|
|
|
|
override void EndEffect()
|
|
{
|
|
Super.EndEffect();
|
|
if ( !Owner ) return;
|
|
Owner.A_StartSound("powerup/ragekitend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
SWWMHandler.DoFlash(Owner,Color(128,255,0,0),30);
|
|
Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
Owner.A_AlertMonsters(2000);
|
|
Demolitionist(Owner).lastbump *= .9;
|
|
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_RAGEKIT"));
|
|
}
|
|
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// find first with health/armor, plating/collar, sandwich or barrier after it
|
|
Inventory found = null;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i.Inv is 'SWWMHealth') && !(i.Inv is 'SWWMArmor') && !(i.Inv is 'GrilledCheeseSandwich') && !(i.Inv is 'AlmasteelPlating') && !(i.Inv is 'SayaCollar') && !(i.Inv is 'BarrierPower') ) continue;
|
|
found = i;
|
|
break;
|
|
}
|
|
if ( !found )
|
|
{
|
|
// we're good
|
|
return;
|
|
}
|
|
// place ourselves right after it
|
|
Inventory saved = found.Inv;
|
|
found.Inv = self;
|
|
other.Inv = Inv;
|
|
Inv = saved;
|
|
}
|
|
|
|
void DoHitFX()
|
|
{
|
|
if ( level.maptime <= lasteffect+5 ) return;
|
|
if ( Owner.GiveBody(5,100) )
|
|
SWWMScoreObj.Spawn(5,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
|
|
Owner.A_AlertMonsters(swwm_uncapalert?0:5000);
|
|
SWWMHandler.DoFlash(Owner,Color(64,255,0,0),10);
|
|
Owner.A_QuakeEx(8,8,8,Random[Rage](3,8),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
if ( (Owner.player == players[consoleplayer]) && (gametic > lastrage) && (swwm_mutevoice < 2) )
|
|
lastrage = SWWMHandler.AddOneliner("ragekit",2,5)+40;
|
|
Owner.A_StartSound("powerup/ragekithit",CHAN_POWERUP,CHANF_OVERLAP);
|
|
lasteffect = level.maptime;
|
|
lastpulse = max(lastpulse,gametic+35);
|
|
Demolitionist(Owner).lastbump *= .9;
|
|
}
|
|
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( damageType == 'EndLevel' )
|
|
return; // don't trigger on endlevel damage
|
|
if ( damage > 0 ) newdamage = damage/4;
|
|
}
|
|
|
|
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( passive ) return;
|
|
if ( (damageType == 'Melee') || (damageType == 'Jump') || (damageType == 'Dash') || (damageType == 'Buttslam') || (damageType == 'GroundPound') )
|
|
{
|
|
newdamage = damage*8;
|
|
DoHitFX();
|
|
}
|
|
}
|
|
}
|
|
|
|
Class RagekitX : GhostArtifactX
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Normal";
|
|
}
|
|
}
|
|
|
|
Class Ragekit : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
|
|
if ( Owner.GiveBody(100,100) )
|
|
SWWMScoreObj.Spawn(100,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_rage',1,Owner.player);
|
|
if ( !(Owner is 'Demolitionist') )
|
|
{
|
|
SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30);
|
|
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
return true;
|
|
}
|
|
let r = RagekitPower(Owner.FindInventory("RagekitPower"));
|
|
if ( r )
|
|
{
|
|
r.EffectTics += r.default.EffectTics;
|
|
SWWMHandler.DoFlash(Owner,Color(64,255,0,0),30);
|
|
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
r.lastpulse = max(r.lastpulse,gametic+35);
|
|
Demolitionist(Owner).lastbump *= .95;
|
|
}
|
|
else Owner.GiveInventory("RagekitPower",1);
|
|
return true;
|
|
}
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
if ( tracer ) return;
|
|
tracer = Spawn("RagekitX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("RagekitX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Ragekit
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Ragekit.png
|
|
//$Icon powerup
|
|
Tag "$T_RAGEKIT";
|
|
Stamina 500000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Ragekit.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "powerup/ragekit";
|
|
Inventory.PickupMessage "$T_RAGEKIT";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 12;
|
|
Height 24;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class Omnisight : Inventory
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( !level.allmap )
|
|
{
|
|
if ( Owner.player == players[consoleplayer] )
|
|
{
|
|
Owner.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
// automatically zoom out so the player can know how far this goes
|
|
CVar.FindCVar('swwm_mm_zoom').SetFloat(2.);
|
|
}
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.1;
|
|
level.allmap = true;
|
|
}
|
|
// activate all interest markers
|
|
let ti = ThinkerIterator.Create("SWWMInterestMarker",STAT_MAPMARKER);
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) ) a.bDORMANT = false;
|
|
// spread to all players
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( !playeringame[i] || !players[i].mo || !(players[i].mo is 'Demolitionist') || players[i].mo.FindInventory(GetClass()) ) continue;
|
|
let o = Inventory(Spawn(GetClass()));
|
|
o.ClearCounters();
|
|
o.AttachToOwner(players[i].mo);
|
|
if ( i == consoleplayer )
|
|
{
|
|
Console.Printf(StringTable.Localize("$D_OMNISHARE"),Owner.player.GetUserName());
|
|
players[i].mo.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
// automatically zoom out so the player can know how far this goes
|
|
CVar.FindCVar('swwm_mm_zoom').SetFloat(2.);
|
|
}
|
|
if ( players[i].mo is 'Demolitionist' )
|
|
Demolitionist(players[i].mo).lastbump *= 1.1;
|
|
}
|
|
// not used up, must be kept for the targetting features to work
|
|
return false;
|
|
}
|
|
override void DetachFromOwner()
|
|
{
|
|
// reset zoom on removal
|
|
if ( (Owner.player == players[consoleplayer]) && (swwm_mm_zoom > 1.) )
|
|
CVar.FindCVar('swwm_mm_zoom').SetFloat(1.);
|
|
Super.DetachFromOwner();
|
|
}
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
if ( item.GetClass() == GetClass() )
|
|
{
|
|
item.bPickupGood = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
override bool ShouldSpawn()
|
|
{
|
|
if ( deathmatch ) return false;
|
|
return Super.ShouldSpawn();
|
|
}
|
|
Default
|
|
{
|
|
//$Title Omnisight
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Omnisight.png
|
|
//$Icon powerup
|
|
Tag "$T_OMNISIGHT";
|
|
Inventory.Icon "graphics/HUD/Icons/I_Omnisight.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.PickupMessage "$I_OMNISIGHT";
|
|
Inventory.MaxAmount 1;
|
|
Inventory.InterHubAmount 0;
|
|
Inventory.RestrictedTo "Demolitionist";
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+INVENTORY.UNDROPPABLE;
|
|
+INVENTORY.UNTOSSABLE;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 6;
|
|
Height 26;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class LampMoth : Actor
|
|
{
|
|
Actor lamp;
|
|
Vector3 trail, ofs;
|
|
int lifespan;
|
|
|
|
Default
|
|
{
|
|
Tag "$T_MOTH";
|
|
Radius 2;
|
|
Height 4;
|
|
Speed 2;
|
|
DamageFunction 1;
|
|
MeleeRange 16;
|
|
Mass 10;
|
|
Health 50;
|
|
DeathSound "moth/die";
|
|
BloodColor "20 10 10";
|
|
MONSTER;
|
|
-COUNTKILL;
|
|
+THRUACTORS;
|
|
+NOGRAVITY;
|
|
+NOTELEPORT;
|
|
+FLOAT;
|
|
+NOPAIN;
|
|
+FRIENDLY;
|
|
+LOOKALLAROUND;
|
|
+QUICKTORETALIATE;
|
|
+INTERPOLATEANGLES;
|
|
+NOBLOCKMONST;
|
|
}
|
|
override string GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( inflictor && (inflictor != self) )
|
|
{
|
|
if ( inflictor == master ) return StringTable.Localize("$O_MOTHSELF"); // not likely to happen
|
|
else return StringTable.Localize("$O_MOTH");
|
|
}
|
|
return StringTable.Localize("$O_MOTH2");
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("moth/fly",CHAN_BODY,CHANF_LOOP,.02,4.,FRandom[Moth](.8,1.2));
|
|
if ( master && master.player ) SetFriendPlayer(master.player);
|
|
else bFRIENDLY = false;
|
|
}
|
|
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
|
|
{
|
|
// no hurt moff
|
|
if ( source && IsFriend(source) ) damage = 0;
|
|
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
|
|
}
|
|
bool isEntranced()
|
|
{
|
|
if ( !lamp )
|
|
{
|
|
// look for nearby lamps
|
|
let bi = BlockThingsIterator.Create(self,250);
|
|
double mindist = 250.;
|
|
while ( bi.Next() )
|
|
{
|
|
if ( !bi.Thing || !(bi.Thing is 'CompanionLamp') ) continue;
|
|
Actor a = bi.Thing;
|
|
double dist = Distance3D(a);
|
|
if ( (a.frame == 0) || (dist > mindist) && !CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue;
|
|
mindist = dist;
|
|
lamp = a;
|
|
master = a.target;
|
|
if ( CompanionLamp(lamp).moff.Find(self) == CompanionLamp(lamp).moff.Size() )
|
|
CompanionLamp(lamp).moff.Push(self);
|
|
if ( master && master.player ) SetFriendPlayer(master.player);
|
|
else bFRIENDLY = false;
|
|
}
|
|
}
|
|
if ( !lamp || (lamp.frame == 0) || (Distance3D(lamp) > 250) || !CheckSight(lamp,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false;
|
|
if ( target && (target.Health > 0) && CheckSight(target) ) return false;
|
|
return true;
|
|
}
|
|
void A_SmoothWander()
|
|
{
|
|
if ( level.Vec3Diff(pos,trail).length() < speed )
|
|
{
|
|
double ang = FRandom[Moth](0,360);
|
|
double pt = FRandom[Moth](-30,30);
|
|
double dist = FRandom[Moth](20,40);
|
|
ofs = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*dist;
|
|
}
|
|
Vector3 newpos = level.Vec3Offset(pos,ofs);
|
|
if ( level.IsPointInLevel(newpos) ) trail = newpos;
|
|
if ( vel.length() > 0 )
|
|
{
|
|
Vector3 uvel = vel.unit();
|
|
angle += Clamp(deltaangle(angle,atan2(uvel.y,uvel.x)),-5.,5.);
|
|
pitch += Clamp(deltaangle(pitch,asin(-uvel.z)),-5.,5.);
|
|
}
|
|
vel *= .8;
|
|
Vector3 dir = level.Vec3Diff(pos,trail);
|
|
if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.05,.4*speed,.5*speed);
|
|
}
|
|
void A_SmoothChase()
|
|
{
|
|
if ( !target || (target.Health <= 0) )
|
|
{
|
|
A_ClearTarget();
|
|
SetStateLabel("Spawn");
|
|
return;
|
|
}
|
|
if ( CheckMeleeRange() )
|
|
{
|
|
SetStateLabel("Melee");
|
|
return;
|
|
}
|
|
Vector3 dest = target.Vec3Offset(0,0,target.height*.75);
|
|
Vector3 dir = level.Vec3Diff(pos,dest);
|
|
if ( dir.length() > 0 )
|
|
{
|
|
Vector3 dirunit = dir.unit();
|
|
FLineTraceData d;
|
|
LineTrace(atan2(dirunit.y,dirunit.x),dir.length(),asin(-dirunit.z),data:d);
|
|
if ( (d.HitType != TRACE_HitActor) && (d.HitActor != target) )
|
|
{
|
|
A_Chase();
|
|
return;
|
|
}
|
|
}
|
|
if ( vel.length() > 0 )
|
|
{
|
|
Vector3 uvel = vel.unit();
|
|
angle = atan2(uvel.y,uvel.x);
|
|
pitch = asin(-uvel.z);
|
|
}
|
|
vel *= .8;
|
|
if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.3*speed,2.*speed);
|
|
}
|
|
void A_FollowLamp()
|
|
{
|
|
if ( !lamp )
|
|
{
|
|
SetStateLabel("Spawn");
|
|
return;
|
|
}
|
|
double dst = level.Vec3Diff(pos,trail).length();
|
|
if ( (dst < speed) || (dst > 50) )
|
|
{
|
|
double ang = FRandom[Moth](0,360);
|
|
double pt = FRandom[Moth](-30,30);
|
|
double dist = FRandom[Moth](20,30);
|
|
ofs = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*dist;
|
|
}
|
|
Vector3 newpos = level.Vec3Offset(lamp.Vec3Offset(0,0,lamp.height/2),ofs);
|
|
if ( level.IsPointInLevel(newpos) ) trail = newpos;
|
|
if ( vel.length() > 0 )
|
|
{
|
|
Vector3 uvel = vel.unit();
|
|
angle = atan2(uvel.y,uvel.x);
|
|
pitch = asin(-uvel.z);
|
|
}
|
|
vel *= .8;
|
|
Vector3 dir = level.Vec3Diff(pos,trail);
|
|
if ( dir.length() > 0 ) vel += dir.unit()*clamp(dir.length()*.02,.4*speed,2.*speed);
|
|
Vector3 diff = level.Vec3Diff(pos,lamp.pos);
|
|
if ( (diff.x > -8) && (diff.x < 8) && (diff.y > -8) && (diff.y < 8) && (diff.z > -4) && (diff.z < lamp.height+4) )
|
|
{
|
|
if ( diff.x < 0 ) vel.x -= .2;
|
|
else vel.x += .2;
|
|
if ( diff.y < 0 ) vel.y -= .2;
|
|
else vel.y += .2;
|
|
if ( diff.z < 0 ) vel.z -= .2;
|
|
else vel.z += .2;
|
|
}
|
|
}
|
|
void A_SmoothMove()
|
|
{
|
|
if ( vel.length() > 0 )
|
|
{
|
|
Vector3 uvel = vel.unit();
|
|
angle = atan2(uvel.y,uvel.x);
|
|
pitch = asin(-uvel.z);
|
|
}
|
|
vel *= .8;
|
|
}
|
|
void A_Scrape()
|
|
{
|
|
if ( CheckMeleeRange() )
|
|
{
|
|
A_FaceTarget(0,0);
|
|
lifespan -= 5;
|
|
Vector3 awaydir = level.Vec3Diff(target.Vec3Offset(0,0,target.height),pos).unit();
|
|
vel += awaydir*8.;
|
|
int dmg = target.DamageMobj(self,master?master:Actor(self),GetMissileDamage(0,0),'Melee',Random[Moth](0,8)?DMG_NO_PAIN:0);
|
|
if ( target && !target.bNOBLOOD && !target.bDORMANT && !target.bINVULNERABLE )
|
|
{
|
|
target.TraceBleed(dmg,self);
|
|
target.SpawnBlood(pos,atan2(awaydir.y,awaydir.x)+180,dmg);
|
|
}
|
|
A_StartSound("moth/scrape",CHAN_WEAPON,CHANF_OVERLAP,.2,2.5);
|
|
DamageMobj(target,target,1,'Melee');
|
|
}
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( isEntranced() )
|
|
{
|
|
lifespan = 100;
|
|
return;
|
|
}
|
|
if ( target && (target.Health > 0) ) lifespan = max(20,lifespan);
|
|
lifespan--;
|
|
if ( lifespan <= 0 )
|
|
{
|
|
let s = Spawn("SWWMSmallSmoke",pos);
|
|
s.alpha *= .3;
|
|
Destroy();
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced");
|
|
XZW1 BC 1
|
|
{
|
|
A_SmoothWander();
|
|
A_Look();
|
|
}
|
|
Loop;
|
|
See: // go for enemies
|
|
XZW1 B 0 A_JumpIf(isEntranced(),"See.Entranced");
|
|
XZW1 BC 1 A_SmoothChase();
|
|
Loop;
|
|
See.Entranced: // follow the lamp
|
|
XZW1 B 0 A_JumpIf(!isEntranced(),"Spawn");
|
|
XZW1 BC 1 A_FollowLamp();
|
|
Loop;
|
|
Melee:
|
|
XZW1 B 0 A_Scrape();
|
|
XZW1 BCBC 1 A_SmoothMove();
|
|
Goto See;
|
|
Death:
|
|
TNT1 A 1
|
|
{
|
|
A_StartSound("moth/die",CHAN_VOICE,CHANF_OVERLAP,.6,2.5);
|
|
let s = Spawn("SWWMSmallSmoke",pos);
|
|
s.alpha *= .3;
|
|
}
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class LampMoth2 : LampMoth
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_WMOTH";
|
|
DamageFunction 3;
|
|
Speed 3;
|
|
Scale 1.5;
|
|
Health 200;
|
|
}
|
|
}
|
|
|
|
Class LampMashiro : Actor abstract
|
|
{
|
|
//
|
|
// ~nothing here yet, but she will make an appearance someday~
|
|
//
|
|
// ⠀⠀⠀⠀⠤⠀⠄⠀⠀⠀⠳⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡣⣁⢌⡀⢄⠁⡁⠝⢿⣿⣿⣿⡻⣿⣿⣷⣦
|
|
// ⠀⠀⠠⠀⠠⠀⠀⡀⠘⣠⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣊⢇⠠⢐⢄⡙⢿⣿⣿⣾⠫⣻⣿
|
|
// ⠀⡄⠂⢴⠠⠌⠰⢇⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠣⡣⡱⣌⠻⣿⣿⣿⣿⣿
|
|
// ⠀⠁⠛⠂⠀⠉⡍⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠘⣮⣾⡮⢦⡹⣿⣿⣿⣿
|
|
// ⠄⠠⠉⢼⡇⠶⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⢿⣿⣿⡦⢣⡉⢝⢝⢽
|
|
// ⠀⠀⠚⠃⠀⠀⡀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⢸⣿⣿⣿⢀⢃⠀⡁⠑
|
|
// ⠰⠀⠂⠁⠀⠀⡆⠀⠀⠀⣴⣿⣿⣿⣿⣿⠋⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⣿⣿⣿⣇⠆⢃⠈⡇
|
|
// ⠀⠀⠀⢠⠄⣠⣇⠀⢀⣾⣿⣿⣿⣿⣿⡃⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣿⣿⢔⢌⢆⢑
|
|
// ⠀⠀⠀⢀⣶⣿⣷⢀⣾⣿⣿⣿⣿⡯⡊⠀⢸⡏⣟⣻⣽⣭⣽⣛⡛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢕⢕⠄
|
|
// ⠀⢠⣶⣿⣿⢿⢃⣾⣿⣿⣿⣿⡫⡪⠀⠀⢸⡇⣿⣿⣿⣿⣿⣿⣿⣦⡘⠄⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡯⡇⣿⣿⣿⣿⣗⢔⢕⢅
|
|
// ⠀⣺⣿⡉⠕⠁⣼⣿⣿⣿⡿⡫⡪⠂⠀⢔⢸⢀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣶⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣪⠃⣿⣿⣿⣿⡟⢄⣡⣵
|
|
// ⡛⠟⢋⠀⠀⢸⣿⣿⣿⣿⢠⢫⡊⠀⡔⢵⡈⢠⢣⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠡⠚⠓⠪⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡫⡎⣸⣿⣿⣿⡟⢠⢃⣂⡿
|
|
// ⡃⡊⢃⡔⡠⣿⣿⣿⣿⡇⣜⡘⢀⢜⡀⡅⡆⠘⢐⠁⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢾⠿⢿⣿⣿⣿⣿⣷⣾⣽⡻⢿⣿⣿⣿⣿⣿⣿⣿⡿⣛⣿⣿⣟⡜⠠⣻⣿⣿⡟⠴⠃⠉⠁⠀
|
|
// ⡺⢠⡾⣸⡷⣿⣿⣿⣿⠂⣓⠂⡎⡺⠪⠒⠃⠀⠀⠀⠀⠀⠙⠿⣯⢻⣿⣿⣿⡟⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣾⣿⡷⣲⣿⣿⣿⣿⣿⣿⣿⣷⣝⢿⣿⣿⣿⡿⡫⡺⣽⣿⣿⡗⠀⢸⣿⣿⠏⠀⠀⠀⠀⠀⠀
|
|
// ⣴⣿⢣⣿⢳⣿⣿⣿⡿⡨⡊⠠⡪⡢⠂⠀⠀⠀⠀⠀⢀⣀⠀⠀⠘⢧⡻⣿⣿⡇⢿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢳⣝⠿⡫⡪⣪⡮⣾⣿⡟⢠⠅⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀
|
|
// ⣿⡏⣾⢣⡾⣿⣿⣿⡇⢪⠂⡨⡠⠀⠀⠀⠀⢀⡴⠋⠉⠈⠉⢦⡀⠠⢅⠹⣿⢰⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢟⣫⣾⡿⣫⣦⠸⢊⡄⣼⣿⡟⢠⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
// ⡿⢸⣏⣾⣇⣿⣿⣿⡇⢵⢀⢓⠀⠀⠀⠀⠀⡮⠀⠀⠀⠀⠀⠈⣇⠀⠧⡗⡈⢿⣺⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⣯⣾⣿⡿⠛⠊⣬⠴⢪⠋⣼⣿⠟⢔⢝⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
// ⠃⡿⣸⣿⣿⢸⣿⣿⡇⡊⢰⢱⢂⡀⠀⠀⠀⢳⠀⠀⠀⠀⠀⢰⡇⠀⣸⢐⡈⢄⢿⠿⠿⢿⡿⠽⢫⣵⣿⡿⠿⠛⠛⠙⠉⠁⠀⠞⠙⠃⠍⡁⠖⡪⡫⡪⣻⢁⣾⡿⢃⢞⢕⡝⢰⣆⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
|
// ⢸⢣⣿⣿⡗⡈⢿⣿⡇⠀⢸⣿⣷⣵⣄⠀⠀⠈⠲⣠⣠⣠⡴⠋⠀⠀⢒⢔⢂⢀⢎⢇⣓⡕⠂⡚⡩⢡⢰⢂⣓⡣⣢⠒⠀⠀⠀⠀⠀⠀⠀⠈⠢⡈⡲⡑⢡⣾⠟⡠⡣⣂⢇⠃⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀
|
|
// ⡟⣾⣿⡿⣸⢸⡘⣿⣷⠀⢸⣿⣿⣿⣿⣿⣦⣄⣀⣀⣀⣀⣀⣠⣤⣜⢔⢕⢕⢔⢕⢕⣗⣇⣳⡪⣺⢐⢱⢑⢒⠖⠁⠀⠀⢀⠄⠔⠖⢦⣀⠀⠀⠰⠌⣴⠟⡡⡪⡪⡪⡜⡜⣸⣿⣿⣿⣿⡿⡢⠀⠀⠀⠀⠀⠀
|
|
// ⢱⣿⣿⡣⢑⠔⡕⠘⣿⡀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣷⣵⣕⣥⣣⣣⡣⡪⣐⢱⢠⡃⠀⠀⠀⣴⠁⠀⠀⠀⠀⠸⣆⠀⠀⠘⠀⡪⡪⡪⡪⡪⣸⢡⣿⣿⣿⣿⣿⡯⣺⡀⢤⡀⠀⠀⠀
|
|
// ⣿⣿⣟⣝⣝⣺⠁⠀⠘⣧⢘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣇⣣⠣⡘⡀⠀⠀⠀⡟⠀⠀⠀⠀⠀⠨⡗⠀⠀⠀⠈⢮⠺⡪⢊⢫⠃⣾⣿⣿⣿⣿⣿⡪⡏⣦⡀⢹⣷⣤⣀
|
|
// ⣿⣿⢑⢇⢆⠆⠀⠀⣸⣎⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⣧⣀⠀⠀⢻⡀⠀⠀⠀⢀⡼⠃⠀⠀⠀⠀⠸⢘⢜⡐⡕⣸⣿⣿⣿⣿⣿⣃⣟⣱⣿⣿⣧⢻⣿⣿
|
|
// ⣿⣟⢕⢕⢼⠀⠀⠀⣿⣿⣧⡹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠉⠓⠒⠓⠋⠀⠀⠀⠀⠀⠠⠀⠱⡱⡨⢣⣿⣿⣿⣿⣿⢗⡯⣱⣿⣿⣿⢇⣄⠃⠙
|
|
// ⣿⡒⡢⡢⡃⠀⠀⠀⣿⣿⣿⣷⣝⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣄⣀⣀⣀⣀⢀⣀⡀⡠⣀⡢⡪⠂⣽⣿⣿⣿⣿⣟⡕⣺⣿⣿⣿⣏⣿⣷⢔⡄
|
|
// ⡿⡸⢰⢔⠂⠀⠀⠀⢿⣿⣿⣿⣿⣯⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣯⢮⡊⡠⣰⣿⣿⣿⣿⡿⣡⣿⣿⣿⣿⡟⣾⣿⣿⢵⠅
|
|
// ⣟⢕⢝⣗⠀⠀⠀⡹⡘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⡾⢱⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⡿⣹⣿⣿⡟⡹⢰
|
|
// ⢕⢕⢕⢕⠠⡢⣪⢂⢇⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⣫⣾⣿⢣⣿⣿⣿⣿⣿⣻⣿⣿⣿⣿⡿⣱⣿⣿⣿⡫⡇⣼
|
|
// ⢕⢕⢕⠅⣘⡪⡚⡔⠍⠂⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⣫⣾⣿⢟⢁⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢣⣿⣿⣿⣏⡼⢠⣿
|
|
// ⢕⢕⢕⠀⣖⡪⡪⣂⠀⠐⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢟⣫⣵⡿⢟⣫⡴⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⣸⣿⣿⣿⢒⠇⣼⣿
|
|
// ⢕⢅⢕⠨⣃⢪⢨⢂⠀⠀⠀⠈⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣄⣳⢰⢬⡙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣍⣛⠻⠿⠿⠿⢛⣯⡭⠶⣞⣛⣭⣵⣾⣿⠿⢁⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⢀⣿⣿⣿⡯⡝⢰⣿⣿
|
|
// ⢝⢕⠇⢸⠱⡑⡁⠀⣀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣬⣃⣹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⣿⣿⣿⡿⡱⢃⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⣼⣿⣿⣿⣸⢡⣿⣿⣿
|
|
// ⢕⢕⠅⢜⠕⡠⣢⣾⣿⣷⣤⣄⢀⣬⢻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢛⠴⢡⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⢠⣿⣿⣿⢝⢡⣿⣿⣿⣿
|
|
// ⢕⢕⠅⣕⢥⡶⢀⠻⣿⣿⣿⣟⢿⣿⣿⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢫⡐⣕⢡⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⢀⣿⣿⣿⠏⢁⣿⣿⣿⣿⣿
|
|
// ⢕⢕⢠⢗⣛⣓⣁⢱⣾⣿⣿⣿⣷⣷⣽⣿⡻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢛⢔⠕⠓⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠀⠀⢀⣾⣿⡿⡣⢎⣿⣿⣿⣿⣿⣿
|
|
// ⡕⡕⠠⡅⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡝⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠋⠐⣁⡴⡴⢣⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠃⠀⠀⢠⣾⠿⣫⠞⣱⣿⣿⣿⣿⣿⠟⡡
|
|
// ⢅⢇⢘⢕⢍⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣎⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠛⠉⠁⠀⢀⢜⢕⢅⠕⣡⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠁⠀⠀⠐⡫⡠⠜⣡⣾⣿⣿⡿⠟⠫⠦⠪⠊
|
|
// ⢕⢕⢸⣵⣷⣾⣿⣿⣿⣿⣟⢿⣿⣿⣿⣿⣿⣿⣿⣦⣤⡩⣉⣉⡩⣉⣉⠉⠉⠈⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⠀⣤⢔⢗⢕⠕⣰⣿⣿⣿⣿⣿⣿⣿⣿⡿⢋⢔⢕⠕⣓⠏⣊⠰⠻⠟⠛⣉⠅⢄⠂⡪⡪⡪⠨
|
|
//
|
|
// ~it actually won't be her, but one can dream~
|
|
//
|
|
}
|
|
|
|
Class CompanionLamp : Actor
|
|
{
|
|
Vector3 Trail;
|
|
Array<LampMoth> moff;
|
|
Actor parent;
|
|
bool justteleport;
|
|
|
|
Default
|
|
{
|
|
Tag "$T_LAMP";
|
|
+NOGRAVITY;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+LOOKALLAROUND;
|
|
+FRIENDLY;
|
|
+NOBLOCKMONST;
|
|
Radius 4;
|
|
Height 16;
|
|
}
|
|
// random chance to spawn moths
|
|
void A_Moth()
|
|
{
|
|
// count up
|
|
special1++;
|
|
for ( int i=0; i<moff.Size(); i++ )
|
|
{
|
|
if ( moff[i] && (moff[i].lamp == self) && moff[i].isEntranced() ) continue;
|
|
moff.Delete(i);
|
|
i--;
|
|
}
|
|
if ( (special1%35) || Random[Moth](0,9) || (moff.Size() >= 30) ) return;
|
|
// spawn a moth at a random offset
|
|
double ang = FRandom[Moth](0,360);
|
|
double pt = FRandom[Moth](-30,30);
|
|
double dist = FRandom[Moth](10,30);
|
|
Vector3 ofs = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*dist;
|
|
Vector3 spawnpos = level.Vec3Offset(Vec3Offset(0,0,height/2),ofs);
|
|
if ( !level.IsPointInLevel(spawnpos) ) return;
|
|
// higher chance of white moths if carrying the Mashiro plush
|
|
//int mchance = parent.FindInventory("MothPlushy")?3:9;
|
|
let m = LampMoth(Spawn(Random[Moth](0,9)?"LampMoth":"LampMoth2",spawnpos));
|
|
if ( !m.TestMobjLocation() )
|
|
{
|
|
m.Destroy();
|
|
return;
|
|
}
|
|
let s = Spawn("SWWMSmallSmoke",m.pos);
|
|
s.alpha *= .3;
|
|
m.master = parent;
|
|
m.lamp = self;
|
|
m.trail = m.pos;
|
|
moff.Push(m);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_moth',1,parent.player);
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( !parent || !SWWMLamp(master) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Spawn("SWWMItemFog",pos);
|
|
Trail = pos;
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !parent || !SWWMLamp(master) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( isFrozen() ) return;
|
|
// update trailing position
|
|
bool foundspot = false;
|
|
for ( int i=0; i<180; i+=5 )
|
|
{
|
|
for ( int j=1; j>=-1; j-=2 )
|
|
{
|
|
double ang = (parent.angle-180)+i*j;
|
|
Vector3 testpos = level.Vec3Offset(parent.pos,(cos(ang)*32,sin(ang)*32,parent.height-8+1.5*sin(level.maptime*3.)));
|
|
if ( !level.IsPointInLevel(testpos) ) continue;
|
|
Vector3 oldpos = pos;
|
|
Vector3 oldprev = prev;
|
|
Actor oldblockingmobj = blockingmobj;
|
|
Line oldblockingline = blockingline;
|
|
Sector oldblockingfloor = blockingfloor, oldblockingceiling = blockingceiling;
|
|
SetOrigin(testpos,false);
|
|
if ( !TestMobjLocation() || SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) || BlockingFloor || BlockingCeiling )
|
|
{
|
|
SetOrigin(oldpos,false);
|
|
prev = oldprev;
|
|
blockingmobj = oldblockingmobj;
|
|
blockingline = oldblockingline;
|
|
blockingfloor = oldblockingfloor;
|
|
blockingceiling = oldblockingceiling;
|
|
continue;
|
|
}
|
|
SetOrigin(oldpos,false);
|
|
prev = oldprev;
|
|
blockingmobj = oldblockingmobj;
|
|
blockingline = oldblockingline;
|
|
blockingfloor = oldblockingfloor;
|
|
blockingceiling = oldblockingceiling;
|
|
Trail = testpos;
|
|
foundspot = true;
|
|
}
|
|
// check at most for a 45 degree offset
|
|
if ( foundspot && (i > 45) ) break;
|
|
}
|
|
Vector3 diff = level.Vec3Diff(pos,parent.pos);
|
|
if ( (diff.length() > 400) || justteleport )
|
|
{
|
|
Vector3 rel = level.Vec3Diff(pos,trail);
|
|
justteleport = false;
|
|
Actor f = Spawn("SWWMItemFog",pos);
|
|
f.A_StartSound("lamp/disappear",CHAN_VOICE);
|
|
// carry over the moths
|
|
for ( int i=0; i<moff.Size(); i++ )
|
|
{
|
|
if ( !moff[i] ) continue;
|
|
Vector3 whereto = level.Vec3Offset(moff[i].pos,rel);
|
|
if ( !level.IsPointInLevel(whereto) )
|
|
continue;
|
|
Vector3 oldp = moff[i].pos;
|
|
moff[i].SetOrigin(whereto,false);
|
|
if ( !moff[i].TestMobjLocation() )
|
|
moff[i].SetOrigin(oldp,false);
|
|
}
|
|
SetOrigin(trail,false);
|
|
angle = AngleTo(parent);
|
|
vel *= 0.;
|
|
f = Spawn("SWWMItemFog",pos);
|
|
f.A_StartSound("lamp/appear",CHAN_VOICE);
|
|
return;
|
|
}
|
|
angle += Clamp(deltaangle(angle,AngleTo(parent)),-5.,5.);
|
|
vel *= .8;
|
|
bool blocked = false;
|
|
if ( SWWMUtility.BlockingLineIsBlocking(self,Line.ML_BLOCKING|Line.ML_BLOCKEVERYTHING) )
|
|
{
|
|
// push away from wall
|
|
Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
|
|
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) ) normal *= -1;
|
|
vel += 4.*normal;
|
|
blocked = true;
|
|
}
|
|
if ( BlockingFloor )
|
|
{
|
|
// push away from floor
|
|
Vector3 normal = BlockingFloor.floorplane.Normal;
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
normal = -BlockingFloor.Get3DFLoor(i).top.Normal;
|
|
break;
|
|
}
|
|
vel += 4.*normal;
|
|
blocked = true;
|
|
}
|
|
if ( BlockingCeiling )
|
|
{
|
|
// push away from ceiling
|
|
Vector3 normal = BlockingCeiling.ceilingplane.Normal;
|
|
// find closest 3d floor for its normal
|
|
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
|
|
normal = -BlockingCeiling.Get3DFloor(i).bottom.Normal;
|
|
break;
|
|
}
|
|
vel += 4.*normal;
|
|
blocked = true;
|
|
}
|
|
if ( (diff.x > -16) && (diff.x < 16) && (diff.y > -16) && (diff.y < 16) && (diff.z > -16) && (diff.z < parent.height+8) )
|
|
{
|
|
if ( diff.x < 0 ) vel.x -= .2;
|
|
else vel.x += .2;
|
|
if ( diff.y < 0 ) vel.y -= .2;
|
|
else vel.y += .2;
|
|
if ( diff.z < 0 ) vel.z -= .2;
|
|
else vel.z += .2;
|
|
blocked = true;
|
|
}
|
|
if ( blocked ) return;
|
|
Vector3 dir = level.Vec3Diff(pos,trail);
|
|
if ( dir.length() > 0 )
|
|
vel += dir.unit()*min(dir.length()*.05,20.);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1
|
|
{
|
|
if ( SWWMLamp(master) && SWWMLamp(master).bActive )
|
|
{
|
|
A_StartSound("lamp/on",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
return ResolveState("Active");
|
|
}
|
|
return ResolveState(null);
|
|
}
|
|
Wait;
|
|
Active:
|
|
XZW1 B 1
|
|
{
|
|
A_Moth();
|
|
if ( !SWWMLamp(master) || !SWWMLamp(master).bActive )
|
|
{
|
|
A_StartSound("lamp/off",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
return ResolveState("Spawn");
|
|
}
|
|
return ResolveState(null);
|
|
}
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class SWWMLamp : Inventory
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
bool bActive, bActivated;
|
|
TextureID OnIcon;
|
|
Actor thelamp;
|
|
int charge;
|
|
|
|
Property Charge : charge;
|
|
|
|
override Inventory CreateCopy( Actor other )
|
|
{
|
|
// additional lore
|
|
SWWMLoreLibrary.Add(other.player,"MothLamp");
|
|
return Super.CreateCopy(other);
|
|
}
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
// add charge
|
|
if ( item.GetClass() == GetClass() )
|
|
{
|
|
if ( (Charge >= Default.Charge) && (Amount+item.Amount > MaxAmount) )
|
|
{
|
|
// sell excess
|
|
int sellprice = SWWMUtility.Round100(Stamina*.7);
|
|
SWWMScoreObj.Spawn(sellprice,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2));
|
|
SWWMCredits.Give(Owner.player,sellprice);
|
|
if ( Owner.player )
|
|
Console.Printf(StringTable.Localize(SWWMUtility.SellFemaleItem(item)?"$SWWM_SELLEXTRA_FEM":"$SWWM_SELLEXTRA"),Owner.player.GetUserName(),GetTag(),sellprice);
|
|
}
|
|
else if ( Charge > 0 )
|
|
{
|
|
int AddCharge = Charge+SWWMLamp(item).Charge;
|
|
Charge = min(Default.Charge,AddCharge);
|
|
// if there's charge to spare, increase amount
|
|
if ( AddCharge > Charge )
|
|
{
|
|
if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max;
|
|
Amount = min(MaxAmount,Amount+item.Amount);
|
|
Charge = AddCharge-Charge;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( (Amount > 0) && (Amount+item.Amount < 0) ) Amount = int.max;
|
|
// new copy, increase and take its charge
|
|
Amount = min(MaxAmount,Amount+item.Amount);
|
|
Charge = SWWMLamp(item).Charge;
|
|
}
|
|
item.bPickupGood = true;
|
|
return true;
|
|
}
|
|
return Super.HandlePickup(item);
|
|
}
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
bActivated = true;
|
|
bActive = !bActive;
|
|
if ( !OnIcon ) OnIcon = TexMan.CheckForTexture("graphics/HUD/Icons/I_Lamp.png",TexMan.Type_MiscPatch);
|
|
Icon = bActive?OnIcon:default.Icon;
|
|
// don't consume on use
|
|
Amount++;
|
|
return true;
|
|
}
|
|
override bool ShouldSpawn()
|
|
{
|
|
if ( deathmatch ) return false;
|
|
return Super.ShouldSpawn();
|
|
}
|
|
override void PreTravelled()
|
|
{
|
|
// remove the lamp
|
|
if ( thelamp ) thelamp.Destroy();
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !thelamp && bActivated )
|
|
{
|
|
thelamp = Spawn("CompanionLamp",Owner.Vec3Offset(cos(Owner.angle)*20,sin(Owner.angle)*20,24));
|
|
CompanionLamp(thelamp).parent = Owner;
|
|
thelamp.master = self;
|
|
let f = Spawn("SWWMItemFog",thelamp.pos);
|
|
f.A_StartSound("lamp/appear",CHAN_VOICE);
|
|
}
|
|
if ( bActive && !(level.maptime%35) && !isFrozen() ) Charge--;
|
|
if ( Charge <= 0 )
|
|
{
|
|
Amount--;
|
|
if ( Amount <= 0 ) DepleteOrDestroy();
|
|
else Charge = default.Charge;
|
|
}
|
|
}
|
|
override void DetachFromOwner()
|
|
{
|
|
Super.DetachFromOwner();
|
|
if ( thelamp )
|
|
{
|
|
let f = Spawn("SWWMItemFog",thelamp.pos);
|
|
f.A_StartSound("lamp/disappear",CHAN_VOICE);
|
|
thelamp.Destroy();
|
|
}
|
|
Icon = default.Icon;
|
|
bActive = false;
|
|
bActivated = false;
|
|
}
|
|
clearscope bool isBlinking() const
|
|
{
|
|
return ( (Charge < 10) && (level.maptime&8) );
|
|
}
|
|
Default
|
|
{
|
|
//$Title Lamp
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_LampOff.png
|
|
//$Icon powerup
|
|
Tag "$T_LAMP";
|
|
Inventory.Icon "graphics/HUD/Icons/I_LampOff.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.PickupMessage "$I_LAMP";
|
|
Inventory.Amount 1;
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
SWWMLamp.Charge 100;
|
|
Stamina 70000;
|
|
Radius 8;
|
|
Height 28;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class BarrierLight : PointLightAttenuated
|
|
{
|
|
Default
|
|
{
|
|
Args 32,72,0,80;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( target.player )
|
|
SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true);
|
|
else SetOrigin(target.Vec3Offset(0,0,target.height/2),true);
|
|
bDORMANT = Powerup(master).isBlinking();
|
|
}
|
|
}
|
|
Class BarrierSnd : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
if ( players[consoleplayer].Camera == target )
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,0.);
|
|
A_SoundVolume(CHAN_7,.1);
|
|
}
|
|
else
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,.06);
|
|
A_SoundVolume(CHAN_7,0.);
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/barrieract",CHAN_VOICE,CHANF_LOOP,.06,1.5);
|
|
A_StartSound("powerup/barrieract",CHAN_7,CHANF_LOOP,.1,ATTN_NONE);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
A_StopSound(CHAN_VOICE);
|
|
A_StopSound(CHAN_7);
|
|
}
|
|
}
|
|
|
|
Class BarrierPower : PowerIronFeet
|
|
{
|
|
Mixin SWWMShadedPowerup;
|
|
|
|
Actor snd, l;
|
|
|
|
Default
|
|
{
|
|
Inventory.Icon "graphics/HUD/Icons/I_Barrier.png";
|
|
Powerup.Duration -60;
|
|
Powerup.Color "20 FF 00", 0.1;
|
|
Powerup.Mode "Full"; // no leaky damage
|
|
+INVENTORY.ADDITIVETIME;
|
|
}
|
|
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// find first item with armor/health, plating/collar, sandwich, invincibility after it
|
|
Inventory found = null;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i.Inv is 'SWWMHealth') && !(i.Inv is 'SWWMArmor') && !(i.Inv is 'GrilledCheeseSandwich') && !(i.Inv is 'AlmasteelPlating') && !(i.Inv is 'SayaCollar') && !(i.Inv is 'InvinciballPower') ) continue;
|
|
found = i;
|
|
break;
|
|
}
|
|
if ( !found )
|
|
{
|
|
// we're good
|
|
return;
|
|
}
|
|
// place ourselves right after it
|
|
Inventory saved = found.Inv;
|
|
found.Inv = self;
|
|
other.Inv = Inv;
|
|
Inv = saved;
|
|
}
|
|
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage )
|
|
{
|
|
// negate elemental damage
|
|
if ( (damageType == 'Fire') || (damageType == 'Ice') || (damageType == 'Slime') || (damageType == 'Electric') || (damageType == 'Plasma') || (damageType == 'Radiation') || (damageType == 'Wind') || (damageType == 'Water') || (damageType == 'Corroded') || (damageType == 'Lava') )
|
|
newdamage = 0;
|
|
}
|
|
|
|
override void InitEffect()
|
|
{
|
|
Super.InitEffect();
|
|
if ( !Owner ) return;
|
|
l = Spawn("BarrierLight",Owner.pos);
|
|
l.target = Owner;
|
|
l.master = self;
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 0.95;
|
|
}
|
|
override void EndEffect()
|
|
{
|
|
Super.EndEffect();
|
|
if ( !Owner ) return;
|
|
Owner.A_StartSound("powerup/barrierend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 0.95;
|
|
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_BARRIER"));
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
// don't reset air supply like PowerIronFeet, call parent instead
|
|
Powerup.DoEffect();
|
|
if ( !Owner ) return;
|
|
if ( !snd ) snd = Spawn("BarrierSnd",Owner.pos);
|
|
snd.target = Owner;
|
|
snd.master = self;
|
|
// break ourselves if we're in an endlevel sector
|
|
bool endlv = false;
|
|
for ( int i=0; i<Owner.CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
F3DFloor ff = Owner.CurSector.Get3DFloor(i);
|
|
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SWIMMABLE)) ) continue;
|
|
if ( (ff.model.DamageAmount <= 0) || (ff.model.damageinterval <= 0) ) continue;
|
|
if ( ff.top.ZAtPoint(Owner.pos.xy) <= Owner.pos.z ) continue;
|
|
if ( ff.bottom.ZAtPoint(Owner.pos.xy) >= (Owner.pos.z+Owner.Height) ) continue;
|
|
if ( !(ff.model.flags&Sector.SECF_ENDLEVEL) ) continue;
|
|
endlv = true;
|
|
break;
|
|
}
|
|
if ( !endlv && (Owner.pos.z <= Owner.floorz) )
|
|
{
|
|
bool damageterrain = false;
|
|
if ( (Owner.floorsector.damageamount > 0) && (Owner.floorsector.damageinterval > 0) ) damageterrain = true;
|
|
else
|
|
{
|
|
let t = Owner.GetFloorTerrain();
|
|
if ( t && (t.DamageAmount > 0) && (t.DamageTimeMask > 0) )
|
|
damageterrain = true;
|
|
}
|
|
if ( damageterrain && (Owner.floorsector.flags&Sector.SECF_ENDLEVEL) ) endlv = true;
|
|
}
|
|
if ( !endlv ) return;
|
|
EffectTics = min(0,EffectTics);
|
|
}
|
|
}
|
|
|
|
Class EBarrier : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
int terrainwait;
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
let b = BarrierPower(Owner.FindInventory("BarrierPower"));
|
|
if ( b )
|
|
{
|
|
b.EffectTics += b.default.EffectTics;
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 0.95;
|
|
}
|
|
else Owner.GiveInventory("BarrierPower",1);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_barrier',1,Owner.player);
|
|
return true;
|
|
}
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
Super.Travelled();
|
|
if ( tracer ) return;
|
|
tracer = Spawn("EBarrierX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("EBarrierX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner || (Owner.Health <= 0) ) return;
|
|
// check terrain for auto-use
|
|
let b = Powerup(Owner.FindInventory("BarrierPower"));
|
|
if ( b && (b.EffectTics > 5) )
|
|
{
|
|
terrainwait = 20;
|
|
return;
|
|
}
|
|
bool damageterrain = false;
|
|
bool endlevelterrain = false;
|
|
// check any 3d floors first
|
|
for ( int i=0; i<Owner.CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
F3DFloor ff = Owner.CurSector.Get3DFloor(i);
|
|
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SWIMMABLE)) ) continue;
|
|
if ( (ff.model.DamageAmount <= 0) || (ff.model.damageinterval <= 0) ) continue;
|
|
if ( ff.top.ZAtPoint(Owner.pos.xy) <= Owner.pos.z ) continue;
|
|
if ( ff.bottom.ZAtPoint(Owner.pos.xy) >= (Owner.pos.z+Owner.Height) ) continue;
|
|
if ( ff.model.flags&Sector.SECF_ENDLEVEL ) endlevelterrain = true;
|
|
damageterrain = true;
|
|
break;
|
|
}
|
|
if ( !damageterrain && (Owner.pos.z <= Owner.floorz) )
|
|
{
|
|
if ( (Owner.floorsector.damageamount > 0) && (Owner.floorsector.damageinterval > 0) ) damageterrain = true;
|
|
else
|
|
{
|
|
let t = Owner.GetFloorTerrain();
|
|
if ( t && (t.DamageAmount > 0) && (t.DamageTimeMask > 0) )
|
|
damageterrain = true;
|
|
}
|
|
if ( damageterrain && (Owner.floorsector.flags&Sector.SECF_ENDLEVEL) ) endlevelterrain = true;
|
|
}
|
|
// do not auto-use for these
|
|
if ( endlevelterrain ) return;
|
|
if ( !damageterrain )
|
|
{
|
|
terrainwait = max(0,terrainwait-1);
|
|
return;
|
|
}
|
|
else terrainwait++;
|
|
if ( terrainwait <= 20 ) return;
|
|
terrainwait = 0;
|
|
bool shouldautouse = false;
|
|
if ( swwm_enforceautousebarrier == 1 ) shouldautouse = true;
|
|
else if ( swwm_enforceautousebarrier == -1 ) shouldautouse = false;
|
|
else shouldautouse = CVar.GetCVar('swwm_autousebarrier',Owner.player).GetBool();
|
|
if ( shouldautouse ) Owner.UseInventory(self);
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Barrier
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Barrier.png
|
|
//$Icon powerup
|
|
Tag "$T_BARRIER";
|
|
Stamina 80000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Barrier.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "powerup/barrier";
|
|
Inventory.PickupMessage "$T_BARRIER";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 6;
|
|
Height 28;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class EBarrierX : GhostArtifactX
|
|
{
|
|
}
|
|
|
|
Class TendrilTracer : LineTracer
|
|
{
|
|
Actor ignore;
|
|
Array<Line> ShootThroughList;
|
|
Array<HitListEntry> HitList;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignore ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE )
|
|
{
|
|
let ent = new("HitListEntry");
|
|
ent.hitactor = Results.HitActor;
|
|
ent.hitlocation = Results.HitPos;
|
|
ent.x = Results.HitVector;
|
|
hitlist.Push(ent);
|
|
}
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( Results.HitType == TRACE_HitWall )
|
|
{
|
|
ShootThroughList.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Skip;
|
|
}
|
|
}
|
|
|
|
// main heatseeker
|
|
Class MykradvoTendril : Actor
|
|
{
|
|
Vector3 nextpos, nextdir;
|
|
|
|
action void A_Trace()
|
|
{
|
|
tics = bMISSILEMORE?2:1;
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
if ( !bSTANDSTILL )
|
|
{
|
|
let t = new("TendrilTracer");
|
|
t.ignore = target;
|
|
t.hitlist.Clear();
|
|
t.ShootThroughList.Clear();
|
|
t.Trace(pos,CurSector,x,speed,0);
|
|
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
|
|
{
|
|
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
|
|
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
|
|
}
|
|
for ( int i=0; i<t.hitlist.Size(); i++ )
|
|
{
|
|
if ( t.hitlist[i].hitactor.IsFriend(target) ) continue;
|
|
if ( (t.hitlist[i].hitactor == tracer) && bMISSILEMORE ) bMISSILEEVENMORE = true; // we split
|
|
int dmg = (t.hitlist[i].hitactor.bBOSS||t.hitlist[i].hitactor.FindInventory("BossMarker"))?(GetMissileDamage(0,0)*4):max(t.hitlist[i].hitactor.Health,GetMissileDamage(0,0));
|
|
SWWMUtility.DoKnockback(t.hitlist[i].hitactor,-t.hitlist[i].x+(0,0,.5),((t.hitlist[i].hitactor.Health-dmg)<=0)?60000:8000);
|
|
t.hitlist[i].hitactor.DamageMobj(self,target,dmg,'Plasma',DMG_THRUSTLESS);
|
|
if ( t.hitlist[i].hitactor && t.hitlist[i].hitactor.bISMONSTER && !Random[Mykradvo](0,3) )
|
|
t.hitlist[i].hitactor.Howl();
|
|
}
|
|
}
|
|
invoker.nextpos = level.Vec3Offset(pos,x*speed);
|
|
if ( !bSTANDSTILL && (!tracer || !tracer.bSHOOTABLE || (tracer.Health <= 0) || ((tracer.bBOSS || tracer.FindInventory("BossMarker")) && !bMISSILEMORE)) )
|
|
{
|
|
ReactionTime--;
|
|
if ( ReactionTime <= 0 )
|
|
{
|
|
bAMBUSH = true;
|
|
return;
|
|
}
|
|
}
|
|
double a = FRandom[Mykradvo](0,360), s = FRandom[Mykradvo](0.,bSTANDSTILL?3.:bMISSILEMORE?.75:1.5);
|
|
Vector3 dir = (x+cos(a)*y*s+sin(a)*z*s).unit();
|
|
if ( tracer )
|
|
{
|
|
Vector3 destofs = bMISSILEMORE?tracer.Vec3Offset(0,0,tracer.Height/2.):tracer.Vec3Offset(FRandom[Mykradvo](-1.2,1.2)*tracer.Radius,FRandom[Mykradvo](-1.2,1.2)*tracer.Radius,FRandom[Mykradvo](-.1,1.1)*tracer.height);
|
|
Vector3 dirto = level.Vec3Diff(invoker.nextpos,destofs);
|
|
double dist = dirto.length();
|
|
if ( dist > 1 )
|
|
{
|
|
dirto /= dist;
|
|
dir = (dir+dirto*(clamp(1.-(dist/4000.),.25,1.)**1.5)).unit();
|
|
}
|
|
// early split
|
|
if ( dist < speed ) bMISSILEEVENMORE = true;
|
|
}
|
|
invoker.nextdir = dir;
|
|
}
|
|
action void A_Spread()
|
|
{
|
|
if ( bMISSILEMORE && bMISSILEEVENMORE )
|
|
{
|
|
// spread into sub-tendrils
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
int ntendies = tracer?clamp(tracer.GetSpawnHealth()/400,2,10):2;
|
|
for ( int i=0; i<ntendies; i++ )
|
|
{
|
|
let r = Spawn("MykradvoSmallTendril",invoker.nextpos);
|
|
double a = FRandom[Mykradvo](0,360), s = FRandom[Mykradvo](0.,1.);
|
|
Vector3 sdir = (x+y*cos(a)*s+z*sin(a)*s).unit();
|
|
r.angle = atan2(sdir.y,sdir.x);
|
|
r.pitch = asin(-sdir.z);
|
|
r.target = target;
|
|
r.tracer = tracer;
|
|
if ( tracer && (tracer.bBOSS || tracer.FindInventory("BossMarker")) ) r.ReactionTime -= Random[ExploS](5,15);
|
|
else r.ReactionTime += Random[ExploS](0,20);
|
|
}
|
|
return;
|
|
}
|
|
if ( bAMBUSH || (bSTANDSTILL && (special1 > ReactionTime)) )
|
|
{
|
|
if ( !bSTANDSTILL )
|
|
{
|
|
int numpt = bMISSILEMORE?9:3;
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
double a = FRandom[ExploS](0,360), s = FRandom[ExploS](0,1.);
|
|
Vector3 sdir = (x+cos(a)*y*s+sin(a)*z*s).unit();
|
|
let r = Spawn("MykradvoSmallNullTendril",invoker.nextpos);
|
|
r.angle = atan2(sdir.y,sdir.x);
|
|
r.pitch = asin(-sdir.z);
|
|
r.target = target;
|
|
if ( bMISSILEMORE ) r.ReactionTime += Random[ExploS](0,4);
|
|
else r.ReactionTime -= Random[ExploS](0,4);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
let b = Spawn(GetClass(),invoker.nextpos);
|
|
b.angle = atan2(invoker.nextdir.y,invoker.nextdir.x);
|
|
b.pitch = asin(-invoker.nextdir.z);
|
|
b.target = target;
|
|
b.tracer = tracer;
|
|
b.special1 = special1+1;
|
|
b.special2 = special2;
|
|
b.ReactionTime = ReactionTime;
|
|
if ( !bSTANDSTILL && !((special1+special2)%4) && !Random[Mykradvo](0,4) )
|
|
A_StartSound(bMISSILEMORE?"mykradvo/arc":"mykradvo/smallarc",CHAN_WEAPON,attenuation:(bMISSILEMORE?.5:1.5),pitch:FRandom[Mykradvo](.75,1.25));
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( !special1 ) special2 = Random[Mykradvo](0,8);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
A_FadeOut(bMISSILEMORE?.05:bSTANDSTILL?.2:.1);
|
|
if ( !CheckNoDelay() || (tics == -1) ) return;
|
|
if ( tics > 0 ) tics--;
|
|
while ( !tics )
|
|
{
|
|
if ( !SetState(CurState.NextState) )
|
|
return;
|
|
}
|
|
}
|
|
Default
|
|
{
|
|
Obituary "$O_MYKRADVO";
|
|
RenderStyle "Add";
|
|
DamageFunction 100;
|
|
ReactionTime 8;
|
|
Speed 64;
|
|
Radius .1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+NOTELEPORT;
|
|
+FOILINVUL;
|
|
+NOINTERACTION;
|
|
+MISSILEMORE;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 0 Bright;
|
|
XZW1 A 1 Bright A_Trace();
|
|
XZW1 A 1 Bright
|
|
{
|
|
A_Spread();
|
|
return FindState("Fade")+Random[Mykradvo](0,11)*2;
|
|
}
|
|
Stop;
|
|
Fade:
|
|
#### # 20 Bright;
|
|
XZW1 B -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 C -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 D -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 E -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 F -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 G -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 H -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 I -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 J -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 K -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 L -1 Bright;
|
|
Stop;
|
|
#### # 20 Bright;
|
|
XZW1 M -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// sub seekers
|
|
Class MykradvoSmallTendril : MykradvoTendril
|
|
{
|
|
Default
|
|
{
|
|
Speed 16;
|
|
DamageFunction 10;
|
|
ReactionTime 20;
|
|
-MISSILEMORE;
|
|
}
|
|
States
|
|
{
|
|
Fade:
|
|
#### # 10 Bright;
|
|
XZW1 B -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 C -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 D -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 E -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 F -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 G -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 H -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 I -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 J -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 K -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 L -1 Bright;
|
|
Stop;
|
|
#### # 10 Bright;
|
|
XZW1 M -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// non-hurting non-seekers
|
|
Class MykradvoSmallNullTendril : MykradvoSmallTendril
|
|
{
|
|
Default
|
|
{
|
|
Speed 8;
|
|
ReactionTime 6;
|
|
+STANDSTILL;
|
|
}
|
|
States
|
|
{
|
|
Fade:
|
|
#### # 5 Bright;
|
|
XZW1 B -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 C -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 D -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 E -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 F -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 G -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 H -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 I -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 J -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 K -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 L -1 Bright;
|
|
Stop;
|
|
#### # 5 Bright;
|
|
XZW1 M -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class MykradvoBurstLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "Purple";
|
|
ReactionTime 60;
|
|
Args 0,0,0,400;
|
|
}
|
|
}
|
|
|
|
// 'splode
|
|
Class MykradvoBurst : Actor
|
|
{
|
|
Array<Actor> targets;
|
|
int nstep;
|
|
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
Scale 1.4;
|
|
}
|
|
void FlashPlayer( int str, double rad )
|
|
{
|
|
if ( !SWWMUtility.InPlayerFOV(players[consoleplayer],self,rad) ) return;
|
|
let mo = players[consoleplayer].Camera;
|
|
double dist = Distance3D(mo);
|
|
str = int(str*(1.-(dist/rad)));
|
|
SWWMHandler.DoFlash(mo,Color(str,250,240,255),5);
|
|
SWWMHandler.DoFlash(mo,Color(str,128,0,255),15);
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
nstep = clamp(targets.Size()/10,1,5);
|
|
A_AlertMonsters(swwm_uncapalert?0:8000);
|
|
A_QuakeEx(9,9,9,80,0,3000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:1000,rollintensity:2.);
|
|
A_StartSound("powerup/mykradvo",CHAN_BODY,CHANF_DEFAULT,1.,.25);
|
|
A_StartSound("powerup/mykradvo",CHAN_VOICE,CHANF_DEFAULT,1.,.25);
|
|
FlashPlayer(100,1500);
|
|
int numpt = Random[ExploS](20,30);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.25,8);
|
|
let s = Spawn("SWWMHalfSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31));
|
|
s.A_SetRenderStyle(.4,STYLE_AddShaded);
|
|
s.special1 = Random[ExploS](1,5);
|
|
s.scale *= 2.;
|
|
}
|
|
numpt = Random[ExploS](8,12);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,90);
|
|
let s = Spawn("MykradvoSmallNullTendril",pos);
|
|
s.angle = ang;
|
|
s.pitch = pt;
|
|
s.target = target;
|
|
s.ReactionTime += Random[ExploS](0,8);
|
|
}
|
|
Spawn("MykradvoBurstLight",pos);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
if ( (special1++)%3 )
|
|
{
|
|
if ( targets.Size() > 0 )
|
|
{
|
|
int numpt = Random[ExploS](2,4);
|
|
for ( int j=0; j<numpt; j++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.25,8);
|
|
let s = Spawn("SWWMHalfSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[ExploS](128,160)+Color(28,0,31));
|
|
s.A_SetRenderStyle(.4,STYLE_AddShaded);
|
|
s.special1 = Random[ExploS](1,5);
|
|
s.scale *= 2.;
|
|
}
|
|
numpt = Random[ExploS](1,2);
|
|
for ( int j=0; j<numpt; j++ )
|
|
{
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,90);
|
|
let s = Spawn("MykradvoSmallNullTendril",pos);
|
|
s.angle = ang;
|
|
s.pitch = pt;
|
|
s.target = target;
|
|
s.ReactionTime += Random[ExploS](0,8);
|
|
}
|
|
}
|
|
for ( int i=0; i<nstep; i++ )
|
|
{
|
|
if ( targets.Size() <= 0 ) break;
|
|
let targ = targets[0];
|
|
if ( targ && targ.bSHOOTABLE && (targ.Health > 0) )
|
|
{
|
|
let t = Spawn("MykradvoTendril",pos);
|
|
t.angle = t.AngleTo(targ);
|
|
t.pitch = SWWMUtility.PitchTo(t,targ,.5);
|
|
t.target = target;
|
|
t.tracer = targ;
|
|
}
|
|
else i--;
|
|
targets.Delete(0);
|
|
}
|
|
}
|
|
if ( !CheckNoDelay() || (tics == -1) ) return;
|
|
if ( tics > 0 ) tics--;
|
|
while ( !tics )
|
|
{
|
|
if ( !SetState(CurState.NextState) )
|
|
return;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XEX4 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\ 2 Bright;
|
|
TNT1 A 1 A_JumpIf(!IsActorPlayingSound(CHAN_VOICE)&&!(invoker.targets.Size()),1);
|
|
Wait;
|
|
TNT1 A 1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class Mykradvo : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
Actor ringa[2];
|
|
|
|
Array<Actor> targets;
|
|
|
|
// quicksort (targets)
|
|
private bool CmpDist( Actor ref, Vector3 a, Vector3 b )
|
|
{
|
|
double dista = level.Vec3Diff(ref.pos,a).length();
|
|
double distb = level.Vec3Diff(ref.pos,b).length();
|
|
return (dista < distb);
|
|
}
|
|
private int partition_targets( Array<Actor> a, int l, int h, Actor ref )
|
|
{
|
|
Actor pv = a[h];
|
|
int i = (l-1);
|
|
for ( int j=l; j<=(h-1); j++ )
|
|
{
|
|
if ( CmpDist(ref,a[j].pos,pv.pos) )
|
|
{
|
|
i++;
|
|
Actor tmp = a[j];
|
|
a[j] = a[i];
|
|
a[i] = tmp;
|
|
}
|
|
}
|
|
Actor tmp = a[h];
|
|
a[h] = a[i+1];
|
|
a[i+1] = tmp;
|
|
return i+1;
|
|
}
|
|
private void qsort_targets( Array<Actor> a, int l, int h, Actor ref )
|
|
{
|
|
if ( l >= h ) return;
|
|
int p = partition_targets(a,l,h,ref);
|
|
qsort_targets(a,l,p-1,ref);
|
|
qsort_targets(a,p+1,h,ref);
|
|
}
|
|
|
|
bool FindTargets( Actor t )
|
|
{
|
|
targets.Clear();
|
|
// search all actively hostile enemies within 50m
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor a;
|
|
while ( a=Actor(ti.Next()) )
|
|
{
|
|
// must be an active, shootable live monster
|
|
if ( !a.bISMONSTER || !a.bSHOOTABLE || a.bDORMANT || (a.Health <= 0) ) continue;
|
|
// skip non-hostiles
|
|
if ( a.IsFriend(t) ) continue;
|
|
// is targetting us and is within 10m
|
|
// or
|
|
// is visible and is within 100m
|
|
if ( ((a.target == t) && SWWMUtility.SphereIntersect(a,t.pos,320))
|
|
|| (t.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) && SWWMUtility.SphereIntersect(a,t.pos,3200)) )
|
|
targets.Push(a);
|
|
}
|
|
// sorted, so the closest take priority
|
|
qsort_targets(targets,0,targets.Size()-1,t);
|
|
return (targets.Size() > 0);
|
|
}
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
Vector3 spawnpos = Owner.Vec3Angle(15,Owner.angle,Owner.Height*.7);
|
|
if ( !FindTargets(Owner) )
|
|
{
|
|
int numpt = Random[ExploS](8,12);
|
|
let f = Spawn("SWWMPurplePickupFlash",spawnpos-(0,0,16));
|
|
f.Scale *= .5;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,90);
|
|
let s = Spawn("MykradvoSmallNullTendril",spawnpos+(cos(ang)*cos(pt),sin(ang)+cos(pt),-sin(pt))*FRandom[Mykradvo](2,4));
|
|
s.angle = ang;
|
|
s.pitch = pt;
|
|
s.ReactionTime += Random[ExploS](0,8);
|
|
}
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 0.98;
|
|
Owner.A_QuakeEx(1,1,1,4,0,8,"",QF_RELATIVE|QF_SCALEDOWN);
|
|
// don't consume on failure
|
|
Amount++;
|
|
return true;
|
|
}
|
|
if ( (targets.Size() == 1) && targets[0] && !targets[0].bBOSS && !targets[0].FindInventory("BossMarker") )
|
|
SWWMUtility.MarkAchievement('swwm_achievement_anone',Owner.player);
|
|
let p = Spawn("MykradvoBurst",spawnpos);
|
|
p.target = Owner;
|
|
MykradvoBurst(p).targets.Move(targets);
|
|
targets.Clear();
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 1.2;
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_anom',1,Owner.player);
|
|
return true;
|
|
}
|
|
|
|
action void A_Zap()
|
|
{
|
|
int numpt = Random[ExploS](2,4);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,90);
|
|
let s = Spawn("MykradvoSmallNullTendril",pos+(0,0,16+GetBobOffset())+(cos(ang)*cos(pt),sin(ang)+cos(pt),-sin(pt))*FRandom[Mykradvo](4,8));
|
|
s.angle = ang;
|
|
s.pitch = pt;
|
|
s.ReactionTime += Random[ExploS](-2,2);
|
|
}
|
|
A_StartSound("mykradvo/smallarc",CHAN_WEAPON,CHANF_OVERLAP,attenuation:3.);
|
|
A_SetTics(Random[Mykradvo](10,50));
|
|
}
|
|
|
|
override bool ShouldSpawn()
|
|
{
|
|
return !deathmatch;
|
|
}
|
|
|
|
override bool CanPickup( Actor toucher )
|
|
{
|
|
// in DM, we can't be picked up unless we can be used
|
|
if ( deathmatch && !FindTargets(toucher) )
|
|
return false;
|
|
return Super.CanPickup(toucher);
|
|
}
|
|
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
for ( int i=0; i<2; i++ )
|
|
{
|
|
if ( !ringa[i] ) continue;
|
|
ringa[i].Destroy();
|
|
}
|
|
}
|
|
override void Travelled()
|
|
{
|
|
Super.Travelled();
|
|
if ( !tracer )
|
|
{
|
|
tracer = Spawn("MykradvoX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
for ( int i=0; i<2; i++ )
|
|
{
|
|
if ( ringa[i] ) continue;
|
|
ringa[i] = Spawn("MykradvoX2",pos);
|
|
ringa[i].angle = angle;
|
|
ringa[i].target = self;
|
|
ringa[i].FloatBobPhase = FloatBobPhase;
|
|
ringa[i].special1 = -1+2*i;
|
|
ringa[i].special2 = -2+4*i;
|
|
ringa[i].frame = i;
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("MykradvoX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
for ( int i=0; i<2; i++ )
|
|
{
|
|
ringa[i] = Spawn("MykradvoX2",pos);
|
|
ringa[i].angle = angle;
|
|
ringa[i].target = self;
|
|
ringa[i].FloatBobPhase = FloatBobPhase;
|
|
ringa[i].special1 = -1+2*i;
|
|
ringa[i].special2 = -2+4*i;
|
|
ringa[i].frame = i;
|
|
}
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Mykradvo
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Mykradvo.png
|
|
//$Icon powerup
|
|
Tag "$T_MYKRADVO";
|
|
Stamina -1200000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Mykradvo.png";
|
|
Inventory.PickupSound "misc/p_pkup_vip";
|
|
Inventory.UseSound "mykradvo/arc";
|
|
Inventory.PickupMessage "$T_MYKRADVO";
|
|
Inventory.MaxAmount 3;
|
|
Inventory.InterHubAmount 3;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 10;
|
|
Height 24;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW0 A 10 A_Zap();
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class MykradvoX : GhostArtifactX
|
|
{
|
|
Default
|
|
{
|
|
Scale .16;
|
|
+FORCEXYBILLBOARD;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/mykradvoamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
prev = target.prev+(0,0,16);
|
|
vel = target.vel;
|
|
if ( (target.pos != pos+(0,0,16)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,16)+vel,true);
|
|
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
|
|
FloatBobPhase = target.FloatBobPhase;
|
|
A_SetScale(.16+.01*sin(GetAge()*4));
|
|
if ( !bsprite ) bsprite = GetSpriteIndex('XZW0');
|
|
bInvisible = target.bInvisible||(target.sprite!=bsprite);
|
|
SetState(SpawnState+bInvisible);
|
|
A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
MKRV A -1 Bright;
|
|
TNT1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class MykradvoX2 : GhostArtifactX
|
|
{
|
|
Default
|
|
{
|
|
+ROLLSPRITE;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
prev = target.prev+(0,0,16+special2);
|
|
vel = target.vel;
|
|
if ( (target.pos != pos+(0,0,16+special2)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,16+special2)+vel,true);
|
|
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
|
|
A_SetPitch(sin(GetAge()*special1*8)*5,SPF_INTERPOLATE);
|
|
A_SetRoll(cos(GetAge()*special1*8)*5,SPF_INTERPOLATE);
|
|
FloatBobPhase = target.FloatBobPhase;
|
|
A_SetScale(1.+.05*cos(GetAge()*4)*special1);
|
|
if ( !bsprite ) bsprite = GetSpriteIndex('XZW0');
|
|
bInvisible = target.bInvisible||(target.sprite!=bsprite);
|
|
SetState(SpawnState+bInvisible);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 # -1 Bright;
|
|
TNT1 # -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// 1.1 extra items
|
|
|
|
Class SafetyTether : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
|
|
bool bPrimed, bFailed;
|
|
int primetim;
|
|
TextureID OnIcon[3];
|
|
|
|
override void Travelled()
|
|
{
|
|
Super.Travelled();
|
|
bPrimed = false;
|
|
}
|
|
override Inventory CreateTossable( int amt )
|
|
{
|
|
if ( bPrimed ) return null; // can't drop while primed
|
|
let ret = Super.CreateTossable(amt);
|
|
// reattach our glow if we became a pickup
|
|
if ( (ret == self) && (PickupFlash is 'SWWMPickupFlash') && swwm_itemglows )
|
|
{
|
|
let p = Spawn(PickupFlash,Vec3Offset(0,0,16));
|
|
p.target = self;
|
|
p.SetStateLabel("Pickup");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !bPrimed ) return;
|
|
Icon = ((primetim%4)>=2)?default.Icon:(primetim>=6)?(bFailed?OnIcon[1]:OnIcon[2]):OnIcon[0];
|
|
if ( !bFailed && (primetim == 20) ) Owner.A_StartSound("hahaha/hahaha",CHAN_POWERUP,CHANF_OVERLAP);
|
|
primetim++;
|
|
if ( (primetim <= 20) || (!bFailed && (primetim <= 50)) ) return;
|
|
primetim = 0;
|
|
bPrimed = false;
|
|
Icon = default.Icon;
|
|
if ( bFailed ) return;
|
|
Vector3 safepos;
|
|
double safeangle;
|
|
if ( deathmatch ) [safepos, safeangle] = level.PickDeathmatchStart();
|
|
else [safepos, safeangle] = level.PickPlayerStart(Owner.PlayerNumber());
|
|
Vector3 oldpos = Owner.pos;
|
|
if ( !Owner.Teleport(safepos,safeangle,0) )
|
|
{
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= .95;
|
|
Owner.A_StartSound("powerup/tethererror",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$D_TETHERFAIL"));
|
|
return;
|
|
}
|
|
let s = Spawn("DemolitionistShockwave",oldpos);
|
|
s.target = Owner;
|
|
s.special1 = 120;
|
|
s = Spawn("DemolitionistShockwave",Owner.pos);
|
|
s.target = Owner;
|
|
s.special1 = 120;
|
|
if ( Owner.player == players[consoleplayer] )
|
|
{
|
|
Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
|
Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
|
|
Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
|
|
}
|
|
SWWMHandler.DoFlash(Owner,Color(255,255,255,255),10);
|
|
SWWMHandler.DoFlash(Owner,Color(255,128,192,255),30);
|
|
if ( Owner.GiveBody(100,100) )
|
|
SWWMScoreObj.Spawn(100,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_sneaky',1,Owner.player);
|
|
Amount--;
|
|
if ( Amount <= 0 ) DepleteOrDestroy();
|
|
}
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup || bPrimed ) return false;
|
|
if ( !OnIcon[0] ) OnIcon[0] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyOn.png");
|
|
if ( !OnIcon[1] ) OnIcon[1] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyNo.png");
|
|
if ( !OnIcon[2] ) OnIcon[2] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyYes.png");
|
|
bPrimed = true;
|
|
primetim = 0;
|
|
Vector3 safepos;
|
|
if ( deathmatch ) safepos = level.PickDeathmatchStart();
|
|
else safepos = level.PickPlayerStart(Owner.PlayerNumber());
|
|
bFailed = (level.Vec3Diff(Owner.pos,safepos).length() < 400);
|
|
if ( (Owner.player == players[consoleplayer]) || bBigPowerup ) Owner.A_StartSound(bFailed?"powerup/tetherfail":"powerup/tetheruse",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
// don't consume on use, will happen later
|
|
Amount++;
|
|
return true;
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Safety Tether
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Safety.png
|
|
//$Icon powerup
|
|
Tag "$T_SAFETY";
|
|
Stamina 240000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Safety.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "";
|
|
Inventory.PickupMessage "$T_SAFETY";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 5;
|
|
Height 23;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 33;
|
|
XZW1 B 2;
|
|
Loop;
|
|
}
|
|
}
|
|
|
|
Class AngeryLight : PointLightAttenuated
|
|
{
|
|
Default
|
|
{
|
|
Args 224,0,255,80;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( target.player )
|
|
SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true);
|
|
else SetOrigin(target.Vec3Offset(0,0,target.height/2),true);
|
|
args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*8;
|
|
bDORMANT = Powerup(master).isBlinking();
|
|
}
|
|
}
|
|
Class AngerySnd : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
if ( players[consoleplayer].Camera == target )
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,0.);
|
|
A_SoundVolume(CHAN_7,.5);
|
|
}
|
|
else
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,.4);
|
|
A_SoundVolume(CHAN_7,0.);
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/devastationact",CHAN_VOICE,CHANF_LOOP,.4,1.5);
|
|
A_StartSound("powerup/devastationact",CHAN_7,CHANF_LOOP,.5,ATTN_NONE);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
A_StopSound(CHAN_VOICE);
|
|
A_StopSound(CHAN_7);
|
|
}
|
|
}
|
|
Class AngeryPower : Powerup
|
|
{
|
|
Mixin SWWMShadedPowerup;
|
|
|
|
Actor l, snd;
|
|
int lasteffect;
|
|
transient int lastpulse;
|
|
|
|
Default
|
|
{
|
|
Powerup.Duration -50;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Devastation.png";
|
|
Powerup.Color "C0 00 FF", 0.2;
|
|
+INVENTORY.ADDITIVETIME;
|
|
}
|
|
|
|
override void InitEffect()
|
|
{
|
|
Super.InitEffect();
|
|
if ( !Owner ) return;
|
|
Owner.A_AlertMonsters(swwm_uncapalert?0:5000);
|
|
SWWMHandler.DoFlash(Owner,Color(64,224,0,255),30);
|
|
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
lasteffect = int.min;
|
|
lastpulse = max(lastpulse,gametic+35);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= .95;
|
|
l = Spawn("AngeryLight",Owner.pos);
|
|
l.target = Owner;
|
|
l.master = self;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner ) return;
|
|
if ( !snd ) snd = Spawn("AngerySnd",Owner.pos);
|
|
snd.target = Owner;
|
|
snd.master = self;
|
|
}
|
|
|
|
override void EndEffect()
|
|
{
|
|
Super.EndEffect();
|
|
if ( !Owner ) return;
|
|
Owner.A_StartSound("powerup/devastationend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
SWWMHandler.DoFlash(Owner,Color(128,224,0,255),30);
|
|
Owner.A_QuakeEx(4,4,4,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
Owner.A_AlertMonsters(2000);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= .9;
|
|
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DEVASTATION"));
|
|
}
|
|
|
|
void DoHitFX()
|
|
{
|
|
if ( level.maptime <= lasteffect+5 ) return;
|
|
Owner.A_AlertMonsters(swwm_uncapalert?0:5000);
|
|
SWWMHandler.DoFlash(Owner,Color(64,224,0,255),10);
|
|
Owner.A_QuakeEx(8,8,8,Random[Rage](3,8),0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
Owner.A_StartSound("powerup/devastationhit",CHAN_POWERUP,CHANF_OVERLAP);
|
|
lasteffect = level.maptime;
|
|
lastpulse = max(lastpulse,gametic+35);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= .9;
|
|
}
|
|
|
|
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( passive || (damage <= 0) ) return;
|
|
// (2^31-1)/25 : guarantee that it caps rather than overflowing
|
|
if ( damage > 85899345 ) newdamage = int.max;
|
|
else newdamage = damage*25;
|
|
// don't play hit fx for wall busting, as it'll be done manually if the bust goes through
|
|
if ( damageType != 'Wallbust' ) DoHitFX();
|
|
}
|
|
}
|
|
Class AngerySigil : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup && !deathmatch ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_deva',1,Owner.player);
|
|
let r = AngeryPower(Owner.FindInventory("AngeryPower"));
|
|
if ( r )
|
|
{
|
|
r.EffectTics += r.default.EffectTics;
|
|
SWWMHandler.DoFlash(Owner,Color(64,224,0,255),30);
|
|
Owner.A_QuakeEx(8,8,8,20,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= .95;
|
|
}
|
|
else Owner.GiveInventory("AngeryPower",1);
|
|
return true;
|
|
}
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
Super.Travelled();
|
|
if ( tracer ) return;
|
|
tracer = Spawn("AngerySigilX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("AngerySigilX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
Default
|
|
{
|
|
//$Title Devastation Sigil
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Devastation.png
|
|
//$Icon powerup
|
|
Tag "$T_DEVASTATION";
|
|
Stamina -1500000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Devastation.png";
|
|
Inventory.PickupSound "misc/p_pkup_vip";
|
|
Inventory.UseSound "powerup/devastation";
|
|
Inventory.PickupMessage "$T_DEVASTATION";
|
|
Inventory.MaxAmount 3;
|
|
Inventory.InterHubAmount 3;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 8;
|
|
Height 28;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
Class AngerySigilX : GhostArtifactX
|
|
{
|
|
Default
|
|
{
|
|
Scale .5;
|
|
Alpha .35;
|
|
RenderStyle "Subtract";
|
|
+FORCEXYBILLBOARD;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/devastationamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
prev = target.prev+(0,0,20);
|
|
vel = target.vel;
|
|
if ( (target.pos != pos+(0,0,20)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,20)+vel,true);
|
|
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
|
|
FloatBobPhase = target.FloatBobPhase;
|
|
if ( !bsprite ) bsprite = GetSpriteIndex('XZW1');
|
|
bInvisible = target.bInvisible||(target.sprite!=bsprite);
|
|
SetState(SpawnState+bInvisible);
|
|
A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
BLPS C -1 Bright;
|
|
TNT1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class DivineSpriteLight : PointLightAttenuated
|
|
{
|
|
Default
|
|
{
|
|
Args 255,255,255,100;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( target.player )
|
|
SetOrigin(target.Vec2OffsetZ(0,0,target.player.viewz),true);
|
|
else SetOrigin(target.Vec3Offset(0,0,target.height/2),true);
|
|
double vol = clamp((target.Health-1000)/6000.,0.,1.);
|
|
int lv = clamp(int(vol*255),0,255);
|
|
args[LIGHT_RED] = lv;
|
|
args[LIGHT_GREEN] = lv;
|
|
args[LIGHT_BLUE] = lv;
|
|
args[LIGHT_INTENSITY] = Random[Invinciball](10,12)*10;
|
|
}
|
|
}
|
|
Class DivineSpriteSnd : Actor
|
|
{
|
|
Default
|
|
{
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(target.pos,true);
|
|
double vol = clamp((DivineSpriteEffect(master).AlphInter.GetValue()-1000.)/9000.,0.,1.);
|
|
if ( players[consoleplayer].Camera == target )
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,0.);
|
|
A_SoundVolume(CHAN_7,.8*vol);
|
|
}
|
|
else
|
|
{
|
|
A_SoundVolume(CHAN_VOICE,.4*vol);
|
|
A_SoundVolume(CHAN_7,0.);
|
|
}
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/divineact",CHAN_VOICE,CHANF_LOOP,.4,1.5);
|
|
A_StartSound("powerup/divineact",CHAN_7,CHANF_LOOP,.8,ATTN_NONE);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
Super.OnDestroy();
|
|
A_StopSound(CHAN_VOICE);
|
|
A_StopSound(CHAN_7);
|
|
}
|
|
}
|
|
Class DivineSpriteEffect : Inventory
|
|
{
|
|
int healcnt;
|
|
int healtim;
|
|
bool bHealDone;
|
|
Actor l, snd;
|
|
DynamicValueInterpolator AlphInter;
|
|
|
|
Property HealTimer : healtim;
|
|
|
|
default
|
|
{
|
|
Inventory.Icon "graphics/HUD/Icons/I_Divine.png";
|
|
DivineSpriteEffect.HealTimer 1750;
|
|
+INVENTORY.UNDROPPABLE;
|
|
+INVENTORY.UNTOSSABLE;
|
|
}
|
|
|
|
clearscope bool isBlinking() const
|
|
{
|
|
return ( (healtim <= BLINKTHRESHOLD) && (healtim&8) );
|
|
}
|
|
|
|
override Color GetBlend()
|
|
{
|
|
if ( swwm_shaders ) return 0;
|
|
if ( !AlphInter ) AlphInter = DynamicValueInterpolator.Create(Owner.Health,.1,1,100);
|
|
double alph = clamp((AlphInter.GetValue()-1000.)/6000.,0.,1.);
|
|
return Color(int(64*alph),255,255,255);
|
|
}
|
|
|
|
override void Travelled()
|
|
{
|
|
Super.Travelled();
|
|
bHealDone = true;
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !l ) l = Spawn("DivineSpriteLight",Owner.pos);
|
|
l.target = Owner;
|
|
l.master = self;
|
|
if ( !snd ) snd = Spawn("DivineSpriteSnd",Owner.pos);
|
|
snd.target = Owner;
|
|
snd.master = self;
|
|
int numpt = Random[ExploS](5,10);
|
|
if ( !AlphInter ) AlphInter = DynamicValueInterpolator.Create(Owner.Health,.1,1,100);
|
|
AlphInter.Update(Owner.Health);
|
|
double alph = clamp((AlphInter.GetValue()-1000.)/6000.,0.,1.);
|
|
double scl = clamp((AlphInter.GetValue()-1000.)/6000.,2.,4.);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,90);
|
|
double dst = FRandom[ExploS](.25,.75)*Owner.height;
|
|
Vector3 dir = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt));
|
|
Vector3 ppos = Owner.Vec3offset(0,0,Owner.height/2)+dir*dst;
|
|
A_SpawnParticle("White",SPF_FULLBRIGHT,30,scl,0,ppos.x,ppos.y,ppos.z,dir.x*.2,dir.y*.2,dir.z*.2,0,0,.05,alph,-1,-scl/30.);
|
|
}
|
|
if ( bHealDone || (Owner.Health <= 0) )
|
|
{
|
|
if ( AlphInter.GetValue() <= 1000 ) DepleteOrDestroy();
|
|
return;
|
|
}
|
|
healcnt++;
|
|
healtim--;
|
|
if ( (healtim <= 0) || (Owner.Health <= 0) )
|
|
{
|
|
Owner.A_StartSound("powerup/divineend",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
SWWMHandler.DoFlash(Owner,Color(80,255,255,255),40);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 0.95;
|
|
bHealDone = true;
|
|
if ( (healtim <= 0) && Owner && Owner.CheckLocalView() )
|
|
Console.Printf(StringTable.Localize("$D_SPRITE"));
|
|
}
|
|
else if ( Owner.Health < 1000 )
|
|
{
|
|
healcnt = 0;
|
|
Owner.GiveBody(1000,1000);
|
|
SWWMScoreObj.Spawn(1000,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
|
|
SWWMHandler.DoFlash(Owner,Color(40,255,255,255),20);
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= 0.97;
|
|
Owner.A_StartSound("powerup/divinehit",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
}
|
|
else if ( !(healcnt%5) )
|
|
{
|
|
Owner.GiveBody(500,10000);
|
|
SWWMScoreObj.Spawn(100,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),ST_Health);
|
|
SWWMHandler.DoFlash(Owner,Color(10,255,255,255),10);
|
|
Owner.A_StartSound("powerup/divinehit",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
}
|
|
}
|
|
}
|
|
Class DivineSprite : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMPickupGlow;
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
// never get auto-used on pickup unless we're in deathmatch
|
|
if ( pickup && !deathmatch ) return false;
|
|
let p = Owner.FindInventory("DivineSpriteEffect");
|
|
if ( p ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
Owner.GiveInventory("DivineSpriteEffect",1);
|
|
SWWMUtility.AchievementProgressInc('swwm_progress_divine',1,Owner.player);
|
|
return true;
|
|
}
|
|
override void PreTravelled()
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
}
|
|
override void Travelled()
|
|
{
|
|
if ( tracer ) return;
|
|
tracer = Spawn("DivineSpriteX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
tracer = Spawn("DivineSpriteX",pos);
|
|
tracer.angle = angle;
|
|
tracer.target = self;
|
|
tracer.FloatBobPhase = FloatBobPhase;
|
|
}
|
|
|
|
Default
|
|
{
|
|
//$Title Divine Sprite
|
|
//$Group Powerups
|
|
//$Sprite graphics/HUD/Icons/I_Divine.png
|
|
//$Icon powerup
|
|
Tag "$T_DIVINE";
|
|
Stamina -3600000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Divine.png";
|
|
Inventory.PickupSound "misc/p_pkup_vip";
|
|
Inventory.UseSound "powerup/divineuse";
|
|
Inventory.PickupMessage "$T_DIVINE";
|
|
Inventory.MaxAmount 3;
|
|
Inventory.InterHubAmount 3;
|
|
Inventory.PickupFlash "SWWMPurplePickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
Radius 8;
|
|
Height 24;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
Class DivineSpriteX : GhostArtifactX
|
|
{
|
|
Default
|
|
{
|
|
Scale .5;
|
|
+FORCEXYBILLBOARD;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("powerup/divineamb",CHAN_VOICE,CHANF_LOOP,attenuation:2.);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !target )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
prev = target.prev+(0,0,16);
|
|
vel = target.vel;
|
|
if ( (target.pos != pos+(0,0,16)) || (target.vel != (0,0,0)) ) SetOrigin(target.pos+(0,0,16)+vel,true);
|
|
if ( angle != target.angle ) A_SetAngle(target.angle,SPF_INTERPOLATE);
|
|
FloatBobPhase = target.FloatBobPhase;
|
|
if ( !bsprite ) bsprite = GetSpriteIndex('XZW1');
|
|
bInvisible = target.bInvisible||(target.sprite!=bsprite);
|
|
SetState(SpawnState+bInvisible);
|
|
A_SoundVolume(CHAN_VOICE,bInvisible?0.:1.);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
DVSP A -1 Bright;
|
|
TNT1 A -1;
|
|
Stop;
|
|
}
|
|
}
|