776 lines
20 KiB
Text
776 lines
20 KiB
Text
// Other items
|
|
|
|
Class Omnisight : Inventory
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMRotatingPickup;
|
|
Mixin SWWMUnrealStyleDrop;
|
|
|
|
override bool TryPickup( Actor &toucher )
|
|
{
|
|
if ( !level.allmap )
|
|
{
|
|
Actor rt = toucher;
|
|
if ( toucher.player ) rt = toucher.player.mo;
|
|
if ( rt.player == players[consoleplayer] )
|
|
rt.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
if ( rt is 'Demolitionist' )
|
|
Demolitionist(rt).lastbump *= 1.1;
|
|
level.allmap = true;
|
|
// activate all interest markers
|
|
let ti = ThinkerIterator.Create("SWWMInterestMarker",STAT_MAPMARKER);
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) ) a.bDORMANT = false;
|
|
// "spread" to all players
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( !playeringame[i] || !players[i].mo || (players[i] == rt.player) ) continue;
|
|
if ( i == consoleplayer )
|
|
{
|
|
Console.Printf(StringTable.Localize("$D_OMNISHARE"),rt.player.GetUserName());
|
|
players[i].mo.A_StartSound("powerup/omnisight",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
}
|
|
if ( players[i].mo is 'Demolitionist' )
|
|
Demolitionist(players[i].mo).lastbump *= 1.1;
|
|
}
|
|
}
|
|
GoAwayAndDie();
|
|
return true;
|
|
}
|
|
Default
|
|
{
|
|
Tag "$T_OMNISIGHT";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.PickupMessage "$I_OMNISIGHT";
|
|
Inventory.RestrictedTo "Demolitionist";
|
|
Inventory.PickupFlash "SWWMCyanPickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
+NOTDMATCH;
|
|
+DONTGIB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// 1.1 Safety Tether
|
|
|
|
Class SafetyTether : Inventory
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMRespawn;
|
|
Mixin SWWMRotatingPickup;
|
|
Mixin SWWMUnrealStyleDrop;
|
|
|
|
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') )
|
|
{
|
|
let p = Spawn(PickupFlash,pos);
|
|
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());
|
|
if ( !level.useplayerstartz ) safepos.z = ONFLOORZ;
|
|
Vector3 oldpos = Owner.pos;
|
|
if ( !Owner.Teleport(safepos,safeangle,0) )
|
|
{
|
|
if ( Owner is 'Demolitionist' )
|
|
Demolitionist(Owner).lastbump *= .95;
|
|
Owner.A_StartSound("powerup/tethererror",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
if ( Owner.player == players[consoleplayer] ) Console.Printf(StringTable.Localize("$D_TETHERFAIL"));
|
|
return;
|
|
}
|
|
let s = Spawn("DemolitionistShockwave",oldpos);
|
|
s.target = Owner;
|
|
s.special1 = 120;
|
|
s.A_AlertMonsters(0,AMF_EMITFROMTARGET);
|
|
s = Spawn("DemolitionistShockwave",Owner.pos);
|
|
s.target = Owner;
|
|
s.special1 = 120;
|
|
s.A_AlertMonsters(0,AMF_EMITFROMTARGET);
|
|
if ( Owner.player == players[consoleplayer] )
|
|
{
|
|
Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
|
|
Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
|
|
Owner.A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
|
|
}
|
|
SWWMHandler.DoFlash(Owner,Color(255,255,255,255),10);
|
|
SWWMHandler.DoFlash(Owner,Color(255,128,192,255),30);
|
|
Owner.GiveBody(100,100);
|
|
SWWMUtility.AchievementProgressInc("sneaky",1,Owner.player);
|
|
Amount--;
|
|
if ( Amount <= 0 ) DepleteOrDestroy();
|
|
}
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup || bPrimed ) return false;
|
|
if ( !OnIcon[0] ) OnIcon[0] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyOn.png");
|
|
if ( !OnIcon[1] ) OnIcon[1] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyNo.png");
|
|
if ( !OnIcon[2] ) OnIcon[2] = TexMan.CheckForTexture("graphics/HUD/Icons/I_SafetyYes.png");
|
|
bPrimed = true;
|
|
primetim = 0;
|
|
Vector3 safepos;
|
|
if ( deathmatch ) safepos = level.PickDeathmatchStart();
|
|
else safepos = level.PickPlayerStart(Owner.PlayerNumber());
|
|
if ( !level.useplayerstartz )
|
|
{
|
|
// find the floor for proper distance check
|
|
let s = level.PointInSector(safepos.xy);
|
|
safepos.z = s.floorplane.ZAtPoint(safepos.xy);
|
|
}
|
|
bFailed = (level.Vec3Diff(Owner.pos,safepos).length() < 400);
|
|
Owner.A_StartSound(bFailed?"powerup/tetherfail":"powerup/tetheruse",CHAN_ITEMEXTRA,CHANF_OVERLAP);
|
|
// don't consume on use, will happen later
|
|
Amount++;
|
|
return true;
|
|
}
|
|
|
|
Default
|
|
{
|
|
Tag "$T_SAFETY";
|
|
Stamina 240000;
|
|
Inventory.Icon "graphics/HUD/Icons/I_Safety.png";
|
|
Inventory.PickupSound "misc/p_pkup";
|
|
Inventory.UseSound "";
|
|
Inventory.PickupMessage "$T_SAFETY";
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMCyanPickupFlash";
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.INVBAR;
|
|
+COUNTITEM;
|
|
+INVENTORY.BIGPOWERUP;
|
|
+FLOATBOB;
|
|
+DONTGIB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 33;
|
|
XZW1 B 2;
|
|
Loop;
|
|
}
|
|
}
|
|
|
|
// TBD 1.4 Ballsy Bomb
|
|
|
|
Class BallImpact : SWWMNonInteractiveActor
|
|
{
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_QuakeEx(3.,3.,3.,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:100,rollIntensity:.3);
|
|
A_StartSound("leadball/hit",CHAN_VOICE);
|
|
A_SprayDecal("WallCrack",-20);
|
|
int numpt = Random[Spreadgun](5,10);
|
|
Vector3 x = SWWMUtility.Vec3FromAngles(angle,pitch);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+SWWMUtility.Vec3FromAngles(FRandom[Spreadgun](0,360),FRandom[Spreadgun](-90,90))*.8).unit()*FRandom[Spreadgun](.1,1.2);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[Spreadgun](128,192));
|
|
}
|
|
numpt = Random[Spreadgun](4,12);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Spreadgun](0,360),FRandom[Spreadgun](-90,90))*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[Spreadgun](4,8);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Spreadgun](0,360),FRandom[Spreadgun](-90,90))*FRandom[Spreadgun](2,8);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class TheBall : Actor
|
|
{
|
|
Mixin SWWMMissileFix;
|
|
|
|
double heat;
|
|
int deadtimer;
|
|
Vector3 oldvel;
|
|
Actor lasthit;
|
|
|
|
Default
|
|
{
|
|
Obituary "$O_LEADBALL";
|
|
PROJECTILE;
|
|
+BOUNCEONWALLS;
|
|
+BOUNCEONFLOORS;
|
|
+BOUNCEONCEILINGS;
|
|
+CANBOUNCEWATER;
|
|
+USEBOUNCESTATE;
|
|
+DONTBOUNCEONSKY;
|
|
+NODAMAGETHRUST;
|
|
+ALLOWBOUNCEONACTORS;
|
|
+DONTBOUNCEONSHOOTABLES;
|
|
+HITOWNER;
|
|
-NOGRAVITY;
|
|
Speed 80;
|
|
Gravity 0.1;
|
|
BounceFactor 1.0;
|
|
Radius 2;
|
|
Height 4;
|
|
}
|
|
override int SpecialMissileHit( Actor victim )
|
|
{
|
|
if ( (vel.length() <= 5) || ((victim == target) && !bHITOWNER) || (victim == lasthit) || (!victim.bSHOOTABLE && !victim.bSOLID) )
|
|
return MHIT_PASS;
|
|
// check if we should rip or bounce
|
|
// girthitude
|
|
double girth = (victim.radius+victim.height)/2.*max(50,victim.mass)*(victim.health/double(victim.GetSpawnHealth()));
|
|
// how hard this damn thing is going to slam
|
|
double slamforce = vel.length()*350.+heat*120;
|
|
int dmg = int(vel.length()*4.5+heat*45);
|
|
bool is_schutt = victim.bSHOOTABLE;
|
|
// critical hit!
|
|
bool crit = false;
|
|
if ( is_schutt && !Random[Spreadgun](0,9) )
|
|
{
|
|
Spawn("SWWMItemFog",pos);
|
|
int whichclonk = Random[Spreadgun](1,11);
|
|
String snd = String.Format("misc/clonk%d",whichclonk);
|
|
A_AlertMonsters(swwm_uncapalert?0:2500,AMF_EMITFROMTARGET);
|
|
A_StartSound(snd,CHAN_VOICE,CHANF_OVERLAP,1.,.2);
|
|
A_StartSound(snd,CHAN_VOICE,CHANF_OVERLAP,1.,.2);
|
|
victim.A_QuakeEx(8.,8.,8.,8,0,3000,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:300,rollIntensity:1.);
|
|
victim.A_StartSound(snd,CHAN_DAMAGE,CHANF_OVERLAP,1.,.2);
|
|
slamforce *= 4;
|
|
dmg *= 4;
|
|
vel *= 1.1;
|
|
let numpt = Random[Spreadgun](20,30);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Spreadgun](0,360),FRandom[Spreadgun](-90,90))*FRandom[Spreadgun](1,8);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.scale *= 3.;
|
|
s.alpha *= .2;
|
|
s.vel = pvel;
|
|
}
|
|
crit = true;
|
|
if ( target ) SWWMUtility.AchievementProgressInc("balls",1,target.player);
|
|
}
|
|
SWWMUtility.DoKnockback(victim,vel.unit(),slamforce);
|
|
bool bleeds = (victim && !victim.bINVULNERABLE && !victim.bNOBLOOD && !victim.bDORMANT && is_schutt);
|
|
int newdmg = victim.DamageMobj(self,target,dmg,crit?'CriticalConcussion':'Concussion',crit?(DMG_THRUSTLESS|DMG_FOILINVUL):DMG_THRUSTLESS); // crits ignore invulnerability
|
|
Vector3 dir = -vel.unit();
|
|
// slam jam
|
|
if ( bleeds )
|
|
{
|
|
A_StartSound("leadball/hitf",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
if ( (newdmg > 0) && victim )
|
|
{
|
|
victim.A_StartSound("leadball/hitf",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
victim.TraceBleed(newdmg,self);
|
|
victim.SpawnBlood(pos,atan2(dir.y,dir.x),dmg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
A_StartSound("leadball/hit",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
if ( victim ) victim.A_StartSound("leadball/hit",CHAN_DAMAGE,CHANF_OVERLAP,(vel.length()/30.)**.5);
|
|
if ( vel.length() > 15. )
|
|
{
|
|
let s = Spawn("BallImpact",pos);
|
|
s.angle = atan2(dir.y,dir.x);
|
|
s.pitch = asin(-dir.z);
|
|
}
|
|
}
|
|
// make it so the crit does not propagate to friendlies unless we bonked a friend (you monster!)
|
|
if ( crit )
|
|
SWWMUtility.DoExplosion(self,dmg/2,25000,150,80,((victim.isFriend(target))?0:DE_NOHURTFRIEND)|DE_NONEXPLOSIVE,crit?'CriticalConcussion':'Concussion',target,DMG_FOILINVUL);
|
|
if ( crit && victim && (victim.Health <= 0) && (victim.bBOSS || victim.FindInventory("BossMarker")) && target )
|
|
SWWMUtility.MarkAchievement("clonk",target.player);
|
|
// only rip shootables
|
|
if ( (slamforce > girth) && is_schutt )
|
|
{
|
|
vel *= .7;
|
|
return MHIT_PASS;
|
|
}
|
|
// force bounce
|
|
BlockingMobj = victim;
|
|
A_HandleBounce();
|
|
lasthit = victim;
|
|
// pretend to pass through
|
|
return MHIT_PASS;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("leadball/fly",CHAN_WEAPON,CHANF_LOOP,.6,3.,2.);
|
|
heat = 1.;
|
|
}
|
|
override void Tick()
|
|
{
|
|
oldvel = vel;
|
|
Super.Tick();
|
|
if ( isFrozen() || (freezetics > 0) ) return;
|
|
if ( InStateSequence(CurState,ResolveState("Death")) )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 )
|
|
{
|
|
let numpt = Random[Spreadgun](3,6);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[Spreadgun](0,360),FRandom[Spreadgun](-90,90))*FRandom[Spreadgun](1,8);
|
|
let s = Spawn("SWWMSmallSmoke",pos);
|
|
s.scale *= 3.;
|
|
s.alpha *= .2;
|
|
s.vel = pvel;
|
|
}
|
|
Destroy();
|
|
}
|
|
return;
|
|
}
|
|
heat -= 0.004+0.0004*vel.length();
|
|
A_SoundVolume(CHAN_WEAPON,vel.length()/75.);
|
|
if ( heat <= 0 ) return;
|
|
let s = Spawn("SWWMHalfSmoke",pos);
|
|
s.alpha *= heat;
|
|
}
|
|
void A_HandleBounce()
|
|
{
|
|
lasthit = null;
|
|
Vector3 HitNormal = SWWMUtility.GetActorHitNormal(self);
|
|
// send the needed data for a bust
|
|
if ( (special1 == 2) || swwm_omnibust )
|
|
{
|
|
int dmg = int(oldvel.length()*4.2+heat*80);
|
|
BusterWall.ProjectileBust(self,dmg,oldvel.unit());
|
|
}
|
|
// undo the bounce, we need to hook in our own
|
|
vel = oldvel;
|
|
// re-do the bounce with our formula
|
|
double bcefact = .9;
|
|
if ( BlockingMobj )
|
|
{
|
|
bcefact *= .7;
|
|
if ( !BlockingMobj.bINVULNERABLE && !BlockingMobj.bNOBLOOD && !BlockingMobj.bDORMANT )
|
|
bcefact *= .6;
|
|
}
|
|
vel = (vel dot HitNormal)*HitNormal*FRandom[Spreadgun](-1.8,-1.)+vel;
|
|
vel += SWWMUtility.Vec3FromAngles(FRandom[Spreadgun](0,360),FRandom[Spreadgun](-90,90))*.4;
|
|
vel *= bcefact;
|
|
// slam jam
|
|
if ( !BlockingMobj )
|
|
{
|
|
A_StartSound("leadball/hit",CHAN_VOICE,CHANF_OVERLAP,max(0.,(vel.length()/30.-.2))**.5);
|
|
if ( vel.length() > 15 )
|
|
{
|
|
let s = Spawn("BallImpact",pos);
|
|
s.angle = atan2(HitNormal.y,HitNormal.x);
|
|
s.pitch = asin(-HitNormal.z);
|
|
}
|
|
}
|
|
gravity = .35;
|
|
if ( (vel.length() < 5) && (pos.z <= floorz) )
|
|
{
|
|
ClearBounce();
|
|
ExplodeMissile();
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1;
|
|
Stop;
|
|
Bounce:
|
|
XZW1 A 0 A_HandleBounce();
|
|
Goto Spawn;
|
|
Death:
|
|
XZW1 A -1
|
|
{
|
|
bMOVEWITHSECTOR = true;
|
|
A_StopSound(CHAN_WEAPON);
|
|
}
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// TBD 1.4 Battle-Boi Sentry
|
|
|
|
Class SaltTracer : LineTracer
|
|
{
|
|
Array<Line> ShootThroughList;
|
|
Array<WaterHit> WaterHitList;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
// liquid splashes
|
|
if ( Results.CrossedWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&Line.ML_BlockHitscan) )
|
|
return TRACE_Stop;
|
|
ShootThroughList.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class SaltLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "SaltExpl,1";
|
|
ReactionTime 30;
|
|
Args 0,0,0,240;
|
|
}
|
|
}
|
|
Class SaltLight2 : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "SaltExpl";
|
|
ReactionTime 30;
|
|
Args 0,0,0,70;
|
|
}
|
|
}
|
|
|
|
Class SaltImpact : SWWMNonInteractiveActor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_SALTSHOT";
|
|
DamageType 'Salt';
|
|
RenderStyle "Add";
|
|
Scale 1.8;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FORCEXYBILLBOARD;
|
|
+FOILINVUL;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_AlertMonsters(swwm_uncapalert?0:6000,AMF_EMITFROMTARGET);
|
|
SWWMUtility.DoExplosion(self,30+special2*4,15000,100,40);
|
|
A_QuakeEx(3.,3.,3.,10,0,250,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,falloff:150,rollintensity:0.2);
|
|
A_StartSound("saltshot/hit",CHAN_VOICE,attenuation:.35);
|
|
A_SprayDecal("ShockMarkSmall",-172);
|
|
A_SprayDecal("SaltMark",-172);
|
|
Scale *= FRandom[ExploS](0.8,1.1);
|
|
int numpt = Random[ExploS](5,9)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](1,6);
|
|
let s = Spawn("SWWMSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,3,4)*Random[ExploS](48,63));
|
|
s.special1 = Random[ExploS](1,2);
|
|
s.scale *= 2.4;
|
|
s.A_SetRenderStyle(.4,STYLE_AddShaded);
|
|
}
|
|
numpt = Random[ExploS](3,9)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](2,6);
|
|
let s = Spawn("SWWMSpark",pos);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = Random[ExploS](3,6)-special1;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](2,12);
|
|
let s = Spawn("SWWMChip",pos);
|
|
s.vel = pvel;
|
|
}
|
|
Spawn("SaltLight2",pos);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 0 NoDelay A_Jump(256,"Expl1","Expl2","Expl3");
|
|
Expl1:
|
|
KSX1 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
Expl2:
|
|
KSX2 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
Expl3:
|
|
KSX3 ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SaltBeam : SWWMNonInteractiveActor
|
|
{
|
|
Default
|
|
{
|
|
Obituary "$O_SALTSHOT";
|
|
DamageType 'Salt';
|
|
RenderStyle "Add";
|
|
Stamina 9;
|
|
Speed 32;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+FOILINVUL;
|
|
}
|
|
|
|
void SpreadOut()
|
|
{
|
|
special1 = 1;
|
|
let [x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
|
|
let t = new("SaltTracer");
|
|
t.Trace(pos,cursector,x,speed,0,ignore:target);
|
|
foreach ( l:t.ShootThroughList )
|
|
{
|
|
l.Activate(target,0,SPAC_PCross);
|
|
l.Activate(target,0,SPAC_Impact);
|
|
}
|
|
foreach ( w:t.WaterHitList )
|
|
{
|
|
let b = Actor.Spawn("InvisibleSplasher",w.hitpos);
|
|
b.target = target;
|
|
b.A_CheckTerrain();
|
|
}
|
|
for ( int i=4; i<t.Results.Distance; i+=8 )
|
|
{
|
|
if ( Random[Spreadgun](0,Stamina) ) continue;
|
|
let b = Actor.Spawn("SWWMHalfSmoke",level.Vec3Offset(pos,x*i));
|
|
b.Scale *= FRandom[Spreadgun](.6,.8);
|
|
b.special1 = Random[Spreadgun](1,2);
|
|
b.A_SetRenderStyle(.3,STYLE_AddShaded);
|
|
b.SetShade(Color(1,3,4)*Random[Spreadgun](48,63));
|
|
b.vel += x*FRandom[Spreadgun](-.5,3);
|
|
}
|
|
if ( t.Results.HitType != TRACE_HitNone )
|
|
{
|
|
if ( (args[1] == 2) || swwm_omnibust ) BusterWall.Bust(t.Results,60+Accuracy*10,target,x,t.Results.HitPos.z);
|
|
if ( t.Results.HitType == TRACE_HitActor )
|
|
{
|
|
SWWMUtility.DoKnockback(t.Results.HitActor,x,25000);
|
|
let p = SWWMPuff.Setup(t.Results.HitPos,x,self,target,t.Results.HitActor);
|
|
t.Results.HitActor.DamageMobj(p,target,40+Accuracy*5,'Salt',DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF);
|
|
if ( t.Results.HitActor && t.Results.HitActor.bIsMonster && !Random[Spreadgun](0,3) )
|
|
t.Results.HitActor.Howl();
|
|
}
|
|
Vector3 norm = SWWMUtility.GetLineTracerHitNormal(t.Results);
|
|
if ( t.Results.HitType == TRACE_HitWall ) t.Results.HitLine.RemoteActivate(tracer,t.Results.Side,SPAC_Impact,t.Results.HitPos);
|
|
let i = Spawn("SaltImpact",level.Vec3Offset(t.Results.HitPos,norm*4));
|
|
i.angle = atan2(norm.y,norm.x);
|
|
i.pitch = asin(-norm.z);
|
|
i.target = target;
|
|
i.special1 = (Stamina-9)/4;
|
|
i.special2 = Accuracy;
|
|
i.args[0] = args[1];
|
|
speed = t.Results.Distance; // shortens in minimap
|
|
return;
|
|
}
|
|
else if ( (args[0] > 20) && !Random[Spreadgun](0,800/args[0]) )
|
|
{
|
|
let i = Spawn("SaltImpact",level.Vec3Offset(pos,x*speed));
|
|
i.angle = atan2(x.y,x.x);
|
|
i.pitch = asin(-x.z);
|
|
i.target = target;
|
|
i.special1 = (Stamina-9)/4;
|
|
i.special2 = Accuracy;
|
|
i.args[0] = args[1];
|
|
return;
|
|
}
|
|
// next beam
|
|
if ( !(special2%4) && !Random[Spreadgun](0,Stamina) )
|
|
Spawn("SaltLight",level.Vec3Offset(pos,x*speed/2));
|
|
let next = Spawn("SaltBeam",level.Vec3Offset(pos,x*speed));
|
|
double a = FRandom[Spreadgun](0,360), s = FRandom[Spreadgun](0,.06);
|
|
Vector3 dir = SWWMUtility.ConeSpread(x,y,z,a,s);
|
|
next.angle = atan2(dir.y,dir.x);
|
|
next.pitch = asin(-dir.z);
|
|
next.target = target;
|
|
next.special2 = (special2+1)%10;
|
|
next.args[0] = args[0]+1;
|
|
next.args[1] = args[1];
|
|
next.SetStateLabel("TrailSpawn");
|
|
}
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( !Random[Spreadgun](0,3) )
|
|
A_StartSound("saltshot/trail",CHAN_VOICE,CHANF_DEFAULT,.3,4.);
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( freezetics > 0 )
|
|
{
|
|
freezetics--;
|
|
return;
|
|
}
|
|
if ( isFrozen() ) return;
|
|
A_FadeOut(.04);
|
|
if ( Random[Spreadgun](-2,args[2]/10) == 0 )
|
|
SWWMUtility.DoExplosion(self,5+Accuracy,5000,speed,flags:DE_HOWL|DE_NONEXPLOSIVE,ignoreme:target);
|
|
if ( ((special2%4) || args[2]) && !special1 ) SpreadOut();
|
|
args[2]++;
|
|
if ( !CheckNoDelay() || (tics == -1) ) return;
|
|
if ( tics > 0 ) tics--;
|
|
while ( !tics )
|
|
{
|
|
if ( !SetState(CurState.NextState) )
|
|
return;
|
|
}
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A -1 Bright NoDelay
|
|
{
|
|
return FindState("StarterDev")+Random[Spreadgun](0,11)*2;
|
|
}
|
|
Stop;
|
|
TrailSpawn:
|
|
XZW2 A -1 Bright
|
|
{
|
|
return FindState("TrailerDev")+Random[Spreadgun](0,11)*2;
|
|
}
|
|
Stop;
|
|
StarterDev:
|
|
#### # 25 Bright;
|
|
XZW1 B -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 C -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 D -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 E -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 F -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 G -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 H -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 I -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 J -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 K -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 L -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW1 M -1 Bright;
|
|
Stop;
|
|
TrailerDev:
|
|
#### # 25 Bright;
|
|
XZW2 B -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 C -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 D -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 E -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 F -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 G -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 H -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 I -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 J -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 K -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 L -1 Bright;
|
|
Stop;
|
|
#### # 25 Bright;
|
|
XZW2 M -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|