swwmgz_m/zscript/swwm_common_fx.zsc
Marisa the Magician 8d0a087e6c Reduce perf hit of bullet trails by only spawning underwater bubbles actually underwater, instead of always spawning them and then relying on them despawning themselves after checking their own water level.
Further optimization could be possible if I found a way to divide the trace results into segments based on intersections with water volumes. This would save on countless calls to Vec3Offset and PointInSector. Something to keep in mind, I suppose.
While I was at it, I did make the underwater check into a new utility function. It is more or less based on how updating water level is done internally, but saves time by just checking a single point rather than an actor's full height.
Not sure if I'll need to use it elsewhere, but if that turns out to not be the case I'll change it to a private function of the SWWMBulletTrail class afterwards.
2025-03-14 16:23:20 +01:00

1995 lines
48 KiB
Text

// basic effects
// imitates UE1 light type LT_TexturePaletteOnce/LT_TexturePaletteLoop
Class PaletteLight : PointLight
{
Color pal[256];
bool IsLooping;
int InitialReactionTime;
Default
{
Tag "Explosion";
Args 0,0,0,80;
ReactionTime 15;
}
private void UpdateLight()
{
int index = clamp(255-((255*ReactionTime)/InitialReactionTime),0,255);
args[LIGHT_RED] = pal[index].r;
args[LIGHT_GREEN] = pal[index].g;
args[LIGHT_BLUE] = pal[index].b;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
String palname = GetTag();
int sep = palname.IndexOf(",");
int palnum = 0;
if ( sep != -1 )
{
String palnumstr = palname.Mid(sep+1);
palnum = palnumstr.ToInt()*768;
palname.Truncate(sep);
}
int lump = Wads.CheckNumForFullname(String.Format("palettes/%s.pal",palname));
String paldat = Wads.ReadLump(lump);
for ( int i=0; i<256; i++ )
{
pal[i].r = paldat.ByteAt(palnum++);
pal[i].g = paldat.ByteAt(palnum++);
pal[i].b = paldat.ByteAt(palnum++);
}
if ( ReactionTime < 0 )
{
ReactionTime = abs(ReactionTime)-1;
IsLooping = true;
}
InitialReactionTime = ReactionTime;
UpdateLight();
}
override void Tick()
{
Super.Tick();
if ( isFrozen() || (freezetics > 0) ) return;
ReactionTime--;
if ( ReactionTime < 0 )
{
if ( !IsLooping )
{
Destroy();
return;
}
else ReactionTime = abs(InitialReactionTime);
}
if ( target ) SetOrigin(target.pos,true);
UpdateLight();
}
}
// base visual thinker subclasses
Class SWWMStaticSprite : VisualThinker abstract
{
bool bCheckWater; // checks water level, calls EnteredWater and LeftWater virtuals (perf intensive)
bool lastwater; // internal, water state from previous tic
bool firstwater; // internal, water state has been checked at least once
bool bWallStop; // stops moving if the next tic takes us out of bounds (perf intensive)
bool bWallKill; // destroy self if out of bounds (perf intensive)
double speed; // multiply any initial thrust by this (used by subclasses, mainly)
protected bool CheckWater()
{
if ( !cursector ) return false;
if ( cursector.moreflags&Sector.SECMF_UNDERWATER )
{
// directly underwater
return true;
}
let hsec = cursector.GetHeightSec();
if ( hsec )
{
// check for height transfer
double fh = hsec.floorplane.ZAtPoint(pos.xy);
if ( hsec.moreflags&Sector.SECMF_UNDERWATERMASK )
{
if ( fh-pos.z > 0 ) return true;
double ch = hsec.ceilingplane.ZAtPoint(pos.xy);
if ( !(hsec.moreflags&Sector.SECMF_FAKEFLOORONLY)
&& (pos.z > ch) )
return true;
}
}
else
{
// check for swimmable 3d floors
for ( int i=0; i<cursector.Get3DFloorCount(); i++ )
{
let ff = cursector.Get3DFloor(i);
if ( !(ff.flags&F3DFloor.FF_EXISTS)
|| (ff.flags&F3DFloor.FF_SOLID)
|| !(ff.flags&F3DFloor.FF_SWIMMABLE) )
continue;
double fh = ff.bottom.ZAtPoint(pos.xy);
double ch = ff.top.ZAtPoint(pos.xy);
if ( (pos.z >= fh) && (pos.z <= ch) )
return true;
}
}
return false;
}
virtual void EnteredWater() {}
virtual void LeftWater() {}
virtual void OnTick() {}
override void Tick()
{
Super.Tick();
if ( IsFrozen() )
return;
if ( bCheckWater )
{
bool curwater = CheckWater();
if ( curwater && (!lastwater || !firstwater) )
EnteredWater();
else if ( !curwater && (lastwater || !firstwater) )
LeftWater();
if ( bDestroyed ) // might destroy self in either virtual function
return;
lastwater = curwater;
firstwater = true;
}
if ( bWallStop )
{
let newpos = level.Vec3Offset(pos,vel);
if ( !level.IsPointInLevel(newpos) ) vel *= 0.;
}
if ( bWallKill && !level.IsPointInLevel(pos) )
{
Destroy();
return;
}
OnTick();
}
virtual void SetupSprite()
{
// safe defaults
texture = TexMan.CheckForTexture("BLPFA0");
bCheckWater = false;
bWallStop = false;
bWallKill = false;
}
static SWWMStaticSprite SpawnAt( Class<SWWMStaticSprite> type, Vector3 spawnpos )
{
let t = SWWMStaticSprite(level.SpawnVisualThinker(type));
t.pos = spawnpos;
t.cursector = level.PointInSector(t.pos.xy);
t.SetupSprite();
t.speed = 1.; // default
return t;
}
}
Class SWWMAnimSprite : SWWMStaticSprite
{
string sprname; // base name of sprite
uint8 sprframe; // current sprite frame (0-28)
uint8 numframes; // total frame count of animation (max 29)
uint8 frameskip; // how many frames to skip each step
uint8 framestep; // how many tics to wait for each frame step
uint8 framecnt; // internal, counter for frame stepping
bool bLooping; // animation loops, otherwise die upon reaching last frame
private void TickAnim( bool bFirstTick = false )
{
if ( !bFirstTick )
{
framecnt++;
if ( framecnt < framestep )
return;
framecnt = 0;
sprframe += 1+frameskip;
if ( sprframe >= numframes )
{
if ( !bLooping )
{
Destroy();
return;
}
else sprframe -= numframes;
}
}
string tname = String.Format("%.4s%c0",sprname,0x41+sprframe);
texture = TexMan.CheckForTexture(tname);
}
override void Tick()
{
Super.Tick();
if ( !IsFrozen() && !bDestroyed )
TickAnim();
}
override void SetupSprite()
{
// safe defaults
sprname = "XEX1";
sprframe = 0;
numframes = 28;
frameskip = 0;
framestep = 1;
framecnt = 0;
bLooping = false;
bCheckWater = false;
bWallStop = false;
bWallKill = false;
}
static SWWMAnimSprite SpawnAt( Class<SWWMAnimSprite> type, Vector3 spawnpos )
{
let t = SWWMAnimSprite(level.SpawnVisualThinker(type));
t.pos = spawnpos;
t.cursector = level.PointInSector(t.pos.xy);
t.SetupSprite();
t.speed = 1.; // default
t.TickAnim(true);
return t;
}
}
// Generic smoke, lightweight tick
Class SWWMSmoke : SWWMNonInteractiveActor
{
Default
{
RenderStyle 'Shaded';
StencilColor "FF FF FF";
Speed 1;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
Scale .3;
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](.5,1.5);
alpha = min(1.,alpha*FRandom[Puff](.5,1.5));
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[Puff](.2,.8)*speed;
roll = Frandom[Puff](0,360);
scale.x *= RandomPick[Puff](-1,1);
scale.y *= RandomPick[Puff](-1,1);
}
override void Tick()
{
prev = pos; // for interpolation
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
vel *= .96;
vel.z += .01;
// linetrace-based movement (hopefully more reliable than traditional methods)
Vector3 dir = vel;
double spd = vel.length();
dir /= spd;
double dist = spd;
FLineTraceData d;
Vector3 newpos = pos;
newpos.z = clamp(newpos.z,floorz,ceilingz);
int nstep = 0;
while ( dist > 0 )
{
// safeguard, too many bounces
if ( nstep > MAXBOUNCEPERTIC )
{
Destroy();
return;
}
double ang = atan2(dir.y,dir.x);
double pt = asin(-dir.z);
LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d);
if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = SWWMUtility.GetLineTraceHitNormal(d);
dist -= d.Distance;
// should only happen if we bounced
dir = d.HitDir-(FRandom[Puff](1.,1.2)*hitnormal*(d.HitDir dot hitnormal));
vel = dir*spd;
newpos = d.HitLocation+dir;
}
else
{
dist = 0.;
newpos = level.Vec3Offset(newpos,dir*spd);
}
nstep++;
}
newpos.z = clamp(newpos.z,floorz,ceilingz);
SetOrigin(newpos,true);
UpdateWaterLevel();
if ( (waterlevel > 0) && !bAMBUSH )
{
let b = Spawn('SWWMBubble',pos);
b.scale *= abs(scale.x);
b.vel = vel;
Destroy();
return;
}
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XSMK ABCDEFGHIJKLMNOPQRST 1 A_SetTics(1+special1);
Stop;
}
}
// Visual thinker smoke, less overhead, used for heavier effects
Class SWWMHalfSmoke : SWWMAnimSprite
{
override void SetupSprite()
{
Super.SetupSprite();
sprname = "XSMK";
numframes = 20;
bCheckWater = true;
SetRenderStyle(STYLE_Shaded);
scolor = 0xFFFFFFFF;
Scale = (.3,.3);
Flags |= SPF_ROLL;
}
override void OnTick()
{
vel *= .96;
vel.z += .01;
}
override void EnteredWater()
{
let b = SWWMAnimSprite.SpawnAt('SWWMHalfBubble',pos);
b.scale *= abs(scale.x);
b.vel = vel;
Destroy();
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](.5,1.5);
alpha *= FRandom[Puff](.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[Puff](.2,.8)*speed;
roll = FRandom[Puff](0,360);
bXFlip = Random[Puff](0,1);
bYFlip = Random[Puff](0,1);
}
}
// lesser version
Class SWWMSmallSmoke : SWWMHalfSmoke
{
override void SetupSprite()
{
Super.SetupSprite();
sprname = "QSM6";
numframes = 18;
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](.5,1.5);
alpha *= FRandom[Puff](.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[Puff](.04,.16)*speed;
roll = Frandom[Puff](0,360);
bXFlip = Random[Puff](0,1);
bYFlip = Random[Puff](0,1);
}
}
Class SWWMBubble : SWWMNonInteractiveActor
{
Default
{
RenderStyle 'Add';
+FORCEXYBILLBOARD;
Scale 0.5;
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[Puff](0.2,0.8);
if ( (waterlevel <= 0) || (waterdepth < (scale.y*9.)) ) Destroy();
SetState(ResolveState('Spawn')+Random[Puff](0,19));
}
override void Tick()
{
prev = pos;
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
vel *= 0.96;
vel.z += 0.05;
// linetrace-based movement (hopefully more reliable than traditional methods)
Vector3 dir = vel;
double spd = vel.length();
dir /= spd;
double dist = spd;
FLineTraceData d;
Vector3 newpos = pos;
newpos.z = clamp(newpos.z,floorz,ceilingz);
int nstep = 0;
while ( dist > 0 )
{
// safeguard, too many bounces
if ( nstep > MAXBOUNCEPERTIC )
{
Destroy();
return;
}
double ang = atan2(dir.y,dir.x);
double pt = asin(-dir.z);
LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d);
if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = SWWMUtility.GetLineTraceHitNormal(d);
dist -= d.Distance;
// should only happen if we bounced
dir = d.HitDir-(FRandom[Puff](1.,1.2)*hitnormal*(d.HitDir dot hitnormal));
vel = dir*spd;
newpos = d.HitLocation+dir;
}
else
{
dist = 0.;
newpos = level.Vec3Offset(newpos,dir*spd);
}
nstep++;
}
newpos.z = clamp(newpos.z,floorz,ceilingz);
SetOrigin(newpos,true);
UpdateWaterLevel();
if ( (waterlevel <= 0) || (waterdepth < (scale.y*9.)) || !Random[Puff](0,100) ) Destroy();
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XBUB ABCDEFGHIJKLMNOPQRST 1;
Loop;
}
}
// Visual thinker version of the above, much simpler checks
Class SWWMHalfBubble : SWWMAnimSprite
{
override void SetupSprite()
{
Super.SetupSprite();
sprname = "XBUB";
sprframe = Random[Puff](0,19);
numframes = 20;
bCheckWater = true;
bLooping = true;
bWallKill = true;
SetRenderStyle(STYLE_Add);
Scale = (.5,.5);
}
override void OnTick()
{
vel *= .96;
vel.z += .05;
if ( !Random[Puff](0,100) )
Destroy();
}
override void LeftWater()
{
Destroy();
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[Puff](.2,.8)*speed;
}
}
Class SWWMSparkTrail : SWWMNonInteractiveActor
{
Default
{
RenderStyle 'Add';
+FORCEXYBILLBOARD;
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
A_SetScale(scale.x*.9,scale.y);
A_FadeOut(.06);
}
States
{
Spawn:
XZW1 A -1 Bright;
Stop;
}
}
Class SWWMSpark : SWWMNonInteractiveActor
{
bool dead;
Sector tracksector;
int trackplane;
Default
{
RenderStyle 'Add';
+FORCEXYBILLBOARD;
Gravity .2;
Scale .05;
}
override void Tick()
{
prev = pos;
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
if ( dead )
{
// do nothing but follow floor movement
if ( tracksector )
{
double trackz;
if ( trackplane ) trackz = tracksector.ceilingplane.ZAtPoint(pos.xy);
else trackz = tracksector.floorplane.ZAtPoint(pos.xy);
if ( trackz != pos.z ) SetZ(trackz);
}
}
else
{
vel.z -= GetGravity();
// linetrace-based movement (hopefully more reliable than traditional methods)
Vector3 dir = vel;
double spd = vel.length();
dir /= spd;
double dist = spd;
FLineTraceData d;
Vector3 newpos = pos;
newpos.z = clamp(newpos.z,floorz,ceilingz);
int nstep = 0;
while ( dist > 0 )
{
Vector3 oldpos = newpos;
// safeguard, too many bounces
if ( nstep > MAXBOUNCEPERTIC )
{
Destroy();
return;
}
double ang = atan2(dir.y,dir.x);
double pt = asin(-dir.z);
LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d);
if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = SWWMUtility.GetLineTraceHitNormal(d);
dist -= d.Distance;
// should only happen if we bounced
dir = d.HitDir-(2*hitnormal*(d.HitDir dot hitnormal));
spd *= .4;
dist *= .4;
vel = dir*spd;
newpos = d.HitLocation+dir;
}
else
{
dist = 0.;
newpos = level.Vec3Offset(newpos,dir*spd);
}
if ( ((spd < 1.) || (dir.z <= 0.)) && ((d.HitType == TRACE_HitFloor) || (newpos.z <= floorz)) )
{
// lose speed and die
if ( d.Hit3DFloor )
{
newpos.z = d.Hit3DFloor.top.ZAtPoint(newpos.xy);
tracksector = d.Hit3DFloor.model;
trackplane = 1;
}
else
{
// hacky workaround
if ( !d.HitSector ) d.HitSector = floorsector;
newpos.z = d.HitSector.floorplane.ZAtPoint(newpos.xy);
tracksector = d.HitSector;
trackplane = 0;
}
vel = (0,0,0);
pitch = 0;
roll = 0;
dead = true;
SetStateLabel('Death');
break;
}
nstep++;
}
newpos.z = clamp(newpos.z,floorz,ceilingz);
Vector3 taildir = level.Vec3Diff(newpos,pos);
double taillen = taildir.length();
if ( (taillen > 0.) && (alpha > .3) && (waterlevel <= 0) )
{
taildir /= taillen;
let t = Spawn('SWWMSparkTrail',newpos);
t.alpha = alpha*.3;
t.speed = taillen;
[t.angle, t.pitch, t.scale.y] = SWWMUtility.CalcYBeam(taildir,taillen);
}
SetOrigin(newpos,true);
if ( (pos.z <= floorz) && GetFloorTerrain().IsLiquid )
{
Destroy();
return;
}
}
UpdateWaterLevel();
if ( waterlevel > 0 )
{
let b = Spawn('SWWMBubble',pos);
b.vel = vel;
b.scale *= 0.3;
Destroy();
return;
}
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
BLPF A 1 Bright A_FadeOut(0.01);
Wait;
Death:
BLPF A 1 Bright A_FadeOut(0.05);
Wait;
}
}
Class SWWMChip : SWWMNonInteractiveActor
{
SWWMChip prevchip, nextchip;
bool killme;
double anglevel, pitchvel, rollvel;
bool dead;
Sector tracksector;
int trackplane;
Default
{
+INTERPOLATEANGLES;
+ROLLSPRITE;
+ROLLCENTER;
+FORCEXYBILLBOARD;
Gravity .35;
Scale .2;
}
override void PostBeginPlay()
{
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
frame = Random[Junk](0,7);
scale *= Frandom[Junk](0.8,1.2);
SWWMHandler.QueueChip(self);
}
override void OnDestroy()
{
SWWMHandler.DeQueueChip(self);
Super.OnDestroy();
}
override void Tick()
{
prev = pos; // for interpolation
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
if ( dead )
{
// do nothing but follow floor movement
if ( tracksector )
{
double trackz;
if ( trackplane ) trackz = tracksector.ceilingplane.ZAtPoint(pos.xy);
else trackz = tracksector.floorplane.ZAtPoint(pos.xy);
if ( trackz != pos.z )
{
SetZ(trackz);
UpdateWaterLevel(false);
}
}
}
else
{
if ( waterlevel <= 0 ) vel.z -= GetGravity();
// linetrace-based movement (hopefully more reliable than traditional methods)
Vector3 dir = vel;
double spd = vel.length();
dir /= spd;
double dist = spd;
FLineTraceData d;
Vector3 newpos = pos;
newpos.z = clamp(newpos.z,floorz,ceilingz);
int nstep = 0;
while ( dist > 0 )
{
// safeguard, too many bounces
if ( nstep > MAXBOUNCEPERTIC )
{
Destroy();
return;
}
double ang = atan2(dir.y,dir.x);
double pt = asin(-dir.z);
LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d);
if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = SWWMUtility.GetLineTraceHitNormal(d);
dist -= d.Distance;
// should only happen if we bounced
dir = d.HitDir-(2*hitnormal*(d.HitDir dot hitnormal));
spd *= .3;
dist *= .3;
vel = dir*spd;
newpos = d.HitLocation+dir;
SetStateLabel('Bounce');
}
else
{
dist = 0.;
newpos = level.Vec3Offset(newpos,dir*spd);
}
newpos.z = clamp(newpos.z,floorz,ceilingz);
if ( ((spd < 1.) || (dir.z <= 0.)) && ((d.HitType == TRACE_HitFloor) || (newpos.z <= floorz)) )
{
// lose speed and die
if ( d.Hit3DFloor )
{
newpos.z = d.Hit3DFloor.top.ZAtPoint(newpos.xy);
tracksector = d.Hit3DFloor.model;
trackplane = 1;
}
else
{
// hacky workaround
if ( !d.HitSector ) d.HitSector = floorsector;
newpos.z = d.HitSector.floorplane.ZAtPoint(newpos.xy);
tracksector = d.HitSector;
trackplane = 0;
}
vel = (0,0,0);
dead = true;
SetStateLabel('Death');
break;
}
nstep++;
}
SetOrigin(newpos,true);
UpdateWaterLevel();
if ( (pos.z <= floorz) && GetFloorTerrain().IsLiquid )
{
Destroy();
return;
}
}
if ( killme ) A_FadeOut(.01);
if ( waterlevel > 0 )
{
vel *= .99;
if ( pos.z > floorz ) vel.z -= gravity*.01;
anglevel *= .98;
pitchvel *= .98;
rollvel *= .98;
}
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XZW1 # 1
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
}
Loop;
Bounce:
XZW1 # 0
{
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
}
Goto Spawn;
Death:
XZW1 # -1;
Stop;
}
}
Class PoofLight : PaletteLight
{
Default
{
Tag "Yellow";
ReactionTime 5;
Args 0,0,0,60;
}
}
Class PoofLight2 : PaletteLight
{
Default
{
Tag "Yellow";
ReactionTime 20;
Args 0,0,0,90;
}
}
Class SWWMItemFog : SWWMNonInteractiveActor
{
Default
{
RenderStyle 'Add';
+ROLLSPRITE;
+ROLLCENTER;
+FORCEXYBILLBOARD;
}
States
{
Spawn:
BLPF A 2 Bright NoDelay
{
// offset up
SetOrigin(Vec3Offset(0,0,16),false);
roll = FRandom[ExploS](0,360);
scale *= FRandom[ExploS](0.9,1.1);
scale.x *= RandomPick[ExploS](-1,1);
scale.y *= RandomPick[ExploS](-1,1);
int numpt = Random[ExploS](8,12);
if ( bAMBUSH )
{
numpt *= 2;
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](.3,8);
let s = SWWMAnimSprite.SpawnAt('SWWMHalfSmoke',pos);
s.vel = pvel;
s.scolor = Color(3,2,1)*Random[ExploS](64,85);
s.SetRenderStyle(STYLE_AddShaded);
s.scale *= 3.;
s.alpha *= .4;
}
}
else for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](.3,8);
let s = SWWMAnimSprite.SpawnAt('SWWMSmallSmoke',pos);
s.vel = pvel;
s.scolor = Color(3,2,1)*Random[ExploS](64,85);
s.SetRenderStyle(STYLE_AddShaded);
s.scale *= 3.;
s.alpha *= .2;
}
Spawn(bAMBUSH?'PoofLight2':'PoofLight',pos);
}
BLPF A 1 Bright A_FadeOut(.3);
Wait;
}
}
Class TeleLight : PaletteLight
{
Default
{
Tag "ImpactWav";
ReactionTime 10;
Args 0,0,0,150;
}
}
Class SWWMTeleportSparkle : SWWMStaticSprite
{
double scalestep, alphastep;
override void SetupSprite()
{
texture = TexMan.CheckForTexture("BLPFC0");
bCheckWater = false;
bWallStop = false;
bWallKill = false;
Scale = (.3,.3);
Flags |= SPF_FULLBRIGHT;
SetRenderStyle(STYLE_Add);
}
override void OnTick()
{
vel *= .98;
scale *= scalestep;
alpha = max(0.,alpha-alphastep);
if ( alpha <= 0. ) Destroy();
}
}
Class SWWMTeleportDest : SWWMNonInteractiveActor
{
FSpawnParticleParams flare;
override void PostBeginPlay()
{
special1 = Random[ExploS](0,10);
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
if ( (level.maptime+special1)%10 ) return;
if ( !flare.texture )
{
flare.color1 = 0xFF88AAFF;
flare.texture = TexMan.CheckForTexture("graphics/Particles/xflare.png");
flare.style = STYLE_AddShaded;
flare.flags = SPF_FULLBRIGHT;
flare.fadestep = -1;
}
double dist = Distance3DSquared(players[consoleplayer].Camera);
if ( dist >= 250000. ) return;
double alph = clamp((250000.-dist)/250000.,0.,1.);
flare.pos = pos+(0,0,28);
int numpt = Random[ExploS](0,2);
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
Vector3 pvel = SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[ExploS](.2,.8);
flare.lifetime = Random[ExploS](120,240);
flare.size = FRandom[ExploS](2.,4.);
flare.sizestep = FRandom[ExploS](-.02,-.01);
flare.vel = pvel;
flare.startalpha = FRandom[ExploS](.15,.3)*alph;
level.SpawnParticle(flare);
}
}
}
Class SWWMTeleportLine : SWWMNonInteractiveActor
{
Line tline;
FSpawnParticleParams flare;
override void PostBeginPlay()
{
special1 = Random[ExploS](0,5);
}
override void Tick()
{
if ( !tline || !tline.special )
{
Destroy();
return;
}
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
if ( (level.maptime+special1)%5 ) return;
Vector3 apos, bpos;
apos.xy = tline.v1.p;
bpos.xy = tline.v2.p;
apos.z = max(tline.frontsector.floorplane.ZAtPoint(apos.xy),tline.backsector.floorplane.ZAtPoint(apos.xy));
bpos.z = max(tline.frontsector.floorplane.ZAtPoint(bpos.xy),tline.backsector.floorplane.ZAtPoint(bpos.xy));
int numpt = Random[ExploS](0,2);
numpt *= int(clamp((apos-bpos).length()/32,1,8));
if ( !flare.texture )
{
flare.color1 = 0xFF88AAFF;
flare.texture = TexMan.CheckForTexture("graphics/Particles/xflare.png");
flare.style = STYLE_AddShaded;
flare.flags = SPF_FULLBRIGHT;
flare.fadestep = -1;
}
for ( int i=0; i<numpt; i++ )
{
double d = FRandom[ExploS](0.,1.);
Vector3 ppos = bpos*d+apos*(1.-d)+(0,0,FRandom[ExploS](1,4));
Vector3 rpos = level.Vec3Diff(ppos,players[consoleplayer].Camera.pos);
double dist = rpos dot rpos;
if ( dist >= 250000. ) continue;
double alph = clamp((250000.-dist)/250000.,0.,1.);
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
Vector3 pvel = SWWMUtility.Vec3FromAngles(ang,pt)*FRandom[ExploS](.0,.3);
flare.lifetime = Random[ExploS](120,240);
flare.size = FRandom[ExploS](2.,4.);
flare.sizestep = FRandom[ExploS](-.02,-.01);
flare.pos = ppos;
flare.vel = pvel;
flare.accel = (0,0,FRandom[ExploS](.05,.1));
flare.startalpha = FRandom[ExploS](.15,.3)*alph;
level.SpawnParticle(flare);
}
}
}
Class SWWMTeleportFog : SWWMNonInteractiveActor
{
Default
{
RenderStyle 'Add';
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("misc/teleport",CHAN_VOICE);
Spawn('TeleLight',pos);
}
States
{
Spawn:
TNT1 A 1
{
int numpt = int(Random[ExploS](6,12)*alpha);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](.3,8)*alpha;
let s = SWWMAnimSprite.SpawnAt('SWWMSmallSmoke',pos);
s.vel = pvel;
s.scolor = Color(1,2,3)*int(Random[ExploS](64,85)*alpha);
s.SetRenderStyle(STYLE_AddShaded);
s.scale *= 3.*alpha;
s.alpha *= alpha;
}
numpt = int(Random[ExploS](4,8));
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
double dist = (FRandom[ExploS](5,10)+60*(1.-alpha));
if ( LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN) ) continue;
Vector3 ofs = SWWMUtility.Vec3FromAngles(ang,pt)*dist;
Vector3 spos = level.Vec3Offset(pos,ofs);
let s = SWWMTeleportSparkle(SWWMStaticSprite.SpawnAt('SWWMTeleportSparkle',spos));
s.scale *= FRandom[ExploS](.8,1.2);
s.scalestep = FRandom[ExploS](.93,.97);
s.alphastep = FRandom[ExploS](.02,.04);
s.roll = FRandom[ExploS](0,360);
}
A_FadeOut(.07);
}
Wait;
}
}
Class SWWMPickupFlash : SWWMNonInteractiveActor
{
Vector3 lastitempos;
FSpawnParticleParams flare;
Default
{
RenderStyle 'Add';
Args 0,3,2,1;
+ROLLSPRITE;
+ROLLCENTER;
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
frame = Args[0];
if ( CurState != ResolveState('Pickup') ) return;
WorldOffset = (0,0,16);
}
void A_Sparkle()
{
// offset up
SetOrigin(Vec3Offset(0,0,16),false);
roll = FRandom[ExploS](0,360);
scale *= FRandom[ExploS](.9,1.1);
scale.x *= RandomPick[ExploS](-1,1);
scale.y *= RandomPick[ExploS](-1,1);
int numpt = Random[ExploS](8,10);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](.3,8);
let s = SWWMAnimSprite.SpawnAt('SWWMSmallSmoke',pos);
s.vel = pvel;
s.scolor = Color(Args[1],Args[2],Args[3])*Random[ExploS](64,85);
s.SetRenderStyle(STYLE_AddShaded);
s.scale *= 3.;
s.alpha *= .5;
}
}
// needed since cube roots scale very badly when there's thousands of these on the map
private static double AlphaCubeRoot( double alpha )
{
static const double lut[] =
{
0.000000000, 0.250244738, 0.297592823, 0.329340597, 0.353899503, 0.374203165, 0.391654181, 0.407042226,
0.420859807, 0.433436601, 0.445005066, 0.455735780, 0.465757939, 0.475171947, 0.484057512, 0.492479061,
0.500489477, 0.508132748, 0.515445890, 0.522460372, 0.529203190, 0.535697696, 0.541964232, 0.548020638,
0.553882655, 0.559564246, 0.565077860, 0.570434647, 0.575644637, 0.580716886, 0.585659603, 0.590480253,
0.595185647, 0.599782016, 0.604275079, 0.608670097, 0.612971920, 0.617185033, 0.621313591, 0.625361451,
0.629332199, 0.633229179, 0.637055512, 0.640814114, 0.644507720, 0.648138893, 0.651710042, 0.655223431,
0.658681194, 0.662085345, 0.665437783, 0.668740305, 0.671994612, 0.675202314, 0.678364941, 0.681483943,
0.684560698, 0.687596518, 0.690592652, 0.693550290, 0.696470567, 0.699354564, 0.702203318, 0.705017817,
0.707799006, 0.710547792, 0.713265041, 0.715951586, 0.718608224, 0.721235720, 0.723834810, 0.726406200,
0.728950569, 0.731468570, 0.733960833, 0.736427963, 0.738870544, 0.741289137, 0.743684287, 0.746056516,
0.748406329, 0.750734215, 0.753040646, 0.755326076, 0.757590947, 0.759835686, 0.762060704, 0.764266402,
0.766453167, 0.768621373, 0.770771384, 0.772903552, 0.775018219, 0.777115716, 0.779196366, 0.781260480,
0.783308363, 0.785340308, 0.787356603, 0.789357525, 0.791343346, 0.793314328, 0.795270729, 0.797212796,
0.799140774, 0.801054897, 0.802955396, 0.804842496, 0.806716415, 0.808577364, 0.810425553, 0.812261184,
0.814084452, 0.815895552, 0.817694672, 0.819481993, 0.821257696, 0.823021954, 0.824774940, 0.826516818,
0.828247753, 0.829967903, 0.831677424, 0.833376467, 0.835065182, 0.836743713, 0.838412204, 0.840070792,
0.841719614, 0.843358803, 0.844988489, 0.846608801, 0.848219862, 0.849821795, 0.851414720, 0.852998754,
0.854574013, 0.856140608, 0.857698650, 0.859248247, 0.860789506, 0.862322530, 0.863847421, 0.865364279,
0.866873203, 0.868374287, 0.869867628, 0.871353317, 0.872831445, 0.874302101, 0.875765373, 0.877221347,
0.878670107, 0.880111737, 0.881546317, 0.882973927, 0.884394645, 0.885808550, 0.887215717, 0.888616220,
0.890010132, 0.891397525, 0.892778470, 0.894153037, 0.895521294, 0.896883307, 0.898239144, 0.899588868,
0.900932545, 0.902270236, 0.903602004, 0.904927909, 0.906248011, 0.907562370, 0.908871043, 0.910174088,
0.911471559, 0.912763514, 0.914050005, 0.915331088, 0.916606813, 0.917877235, 0.919142403, 0.920402368,
0.921657180, 0.922906887, 0.924151539, 0.925391181, 0.926625862, 0.927855627, 0.929080522, 0.930300591,
0.931515878, 0.932726428, 0.933932282, 0.935133484, 0.936330074, 0.937522094, 0.938709585, 0.939892586,
0.941071137, 0.942245277, 0.943415044, 0.944580475, 0.945741609, 0.946898482, 0.948051130, 0.949199588,
0.950343894, 0.951484081, 0.952620183, 0.953752235, 0.954880270, 0.956004322, 0.957124423, 0.958240605,
0.959352900, 0.960461340, 0.961565955, 0.962666776, 0.963763834, 0.964857158, 0.965946779, 0.967032724,
0.968115023, 0.969193704, 0.970268796, 0.971340326, 0.972408321, 0.973472809, 0.974533817, 0.975591370,
0.976645495, 0.977696218, 0.978743564, 0.979787559, 0.980828227, 0.981865593, 0.982899681, 0.983930516,
0.984958121, 0.985982520, 0.987003736, 0.988021791, 0.989036710, 0.990048513, 0.991057224, 0.992062865,
0.993065456, 0.994065020, 0.995061577, 0.996055150, 0.997045758, 0.998033422, 0.999018163, 1.000000000
};
alpha *= 255;
int low = int(floor(alpha));
int high = int(ceil(alpha));
double theta = alpha-low;
return lut[low]*(1.-theta)+lut[high]*theta;
}
void A_Shimmer()
{
if ( !target || Inventory(target).Owner )
{
Destroy();
return;
}
if ( target.bINVISIBLE )
{
bINVISIBLE = true;
return;
}
else if ( bINVISIBLE ) bINVISIBLE = false;
// try to reduce calls to SetOrigin as much as possible, for performance
if ( target.pos != lastitempos )
{
prev = pos;
SetOrigin(target.pos,true);
}
lastitempos = target.pos;
if ( target.bFLOATBOB && !bFLOATBOB )
{
bFLOATBOB = true;
FloatBobStrength = target.FloatBobStrength;
FloatBobPhase = target.FloatBobPhase;
}
else if ( !target.bFLOATBOB && bFLOATBOB ) bFLOATBOB = false;
A_SetScale(FRandom[ClientSparkles](1.1,1.3));
double camdist = Distance3DSquared(players[consoleplayer].Camera);
if ( camdist <= 40000. ) alpha = 0.;
else
{
if ( (camdist-40000.) >= 160000000. ) alpha = 1.;
else alpha = AlphaCubeRoot(clamp(max(0,camdist-40000.)/160000000.,0.,1.));
alpha *= FRandom[ClientSparkles](.9,1.);
}
// nearby sparkles
if ( camdist >= 250000. ) return;
double alph = clamp((250000.-camdist)/250000.,0.,1.);
if ( !flare.texture )
{
flare.color1 = Color(Args[1]*85,Args[2]*85,Args[3]*85);
flare.texture = TexMan.CheckForTexture("graphics/Particles/xflare.png");
flare.style = STYLE_AddShaded;
flare.flags = SPF_FULLBRIGHT;
flare.lifetime = 30;
flare.accel = (0,0,.05);
flare.fadestep = -1;
}
flare.startalpha = alph;
int numpt = Random[ClientSparkles](1,3);
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ClientSparkles](0,360);
double pt = FRandom[ClientSparkles](0,360);
double dst = FRandom[ClientSparkles](4,16);
Vector3 dir = SWWMUtility.Vec3FromAngles(ang,pt);
flare.size = FRandom[ExploS](2.,3.);
flare.pos = pos+(0,0,16+GetBobOffset())+dir*dst;
flare.vel = dir*.2;
flare.sizestep = FRandom[ExploS](-.04,-.02);
level.SpawnParticle(flare);
}
}
States
{
Spawn:
BLPF # 0 Bright;
BLPF # 1 Bright A_Sparkle();
BLPF # 1 Bright A_FadeOut(.2);
Wait;
Pickup:
BLPS # 1 Bright A_Shimmer();
Wait;
}
}
Class SWWMPinkPickupFlash : SWWMPickupFlash
{
Default
{
Args 1,3,1,2;
}
}
Class SWWMCyanPickupFlash : SWWMPickupFlash
{
Default
{
Args 2,1,2,3;
}
}
Class SWWMGreenPickupFlash : SWWMPickupFlash
{
Default
{
Args 3,1,3,1;
}
}
Class SWWMBluePickupFlash : SWWMPickupFlash
{
Default
{
Args 4,1,1,3;
}
}
Class SWWMPurplePickupFlash : SWWMPickupFlash
{
Default
{
Args 5,2,1,3;
}
}
Class SWWMRedPickupFlash : SWWMPickupFlash
{
Default
{
Args 6,3,1,1;
}
}
Class SWWMWhitePickupFlash : SWWMPickupFlash
{
Default
{
Args 7,3,3,3;
}
}
// Bullet trails from DT
Class WaterHit
{
Vector3 hitpos;
}
Class InvisibleSplasher : Actor
{
Default
{
Mass 100;
VSpeed -2;
Radius 2;
Height 4;
+NOBLOCKMAP; // needed to prevent infinite loops with some 3D floor water (yes, you read that right)
}
States
{
Spawn:
TNT1 A 2;
Stop;
}
}
Class SmolInvisibleSplasher : InvisibleSplasher
{
Default
{
Mass 5;
}
}
Class SWWMBulletTrail : LineTracer
{
Array<WaterHit> WaterHitList;
Array<Line> ShootThroughList;
static play void DoTrail( Actor target, Vector3 pos, Vector3 dir, double dist, int bubblesparse, bool smoky = false )
{
let t = new('SWWMBulletTrail');
t.WaterHitList.Clear();
t.ShootThroughList.Clear();
t.Trace(pos,level.PointInSector(pos.xy),dir,dist,0,ignore:target);
foreach ( l:t.ShootThroughList )
{
// have to do both separately, for whatever reason
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();
}
Vector3 ppos;
SWWMAnimSprite b;
bool bInWater;
for ( int i=5; i<t.Results.Distance; i+=10 )
{
if ( Random[Boolet](0,bubblesparse) ) continue;
if ( !Random[Boolet](0,i/100) ) continue; // fall off w/ distance
ppos = level.Vec3Offset(pos,dir*i);
bInWater = SWWMUtility.PointInWater(ppos);
if ( smoky && !bInWater ) b = SWWMAnimSprite.SpawnAt('SWWMHalfSmoke',ppos);
else if ( !smoky && bInWater ) b = SWWMAnimSprite.SpawnAt('SWWMHalfBubble',ppos);
else continue;
b.Scale *= FRandom[Boolet](.4,.6);
}
t.Destroy();
}
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|Line.ML_BlockEverything)) )
return TRACE_Stop;
ShootThroughList.Push(Results.HitLine);
return TRACE_Skip;
}
return TRACE_Stop;
}
}
// Dummy "puff" actor used for hitscan compatibility
// (don't forget to pass DMG_INFLICTOR_IS_PUFF to DamageMobj calls for this to work)
Class SWWMPuff : SWWMNonInteractiveActor
{
static Actor Setup( Vector3 pos, Vector3 dir, Actor inflictor = null, Actor source = null, Actor victim = null )
{
let p = Spawn('SWWMPuff',pos);
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.master = inflictor; // so certain scripts can fetch the weapon that spawned this puff
p.target = source; // as expected with PUFFGETSOWNER
p.tracer = victim; // as expected with HITTRACER
return p;
}
// make sure obituaries work as intended
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( master ) return master.GetObituary(victim,master,mod,playerattack);
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
default
{
+ALWAYSPUFF;
+PUFFONACTORS;
+PUFFGETSOWNER;
+HITTRACER;
}
States
{
Spawn:
TNT1 A 5; // should be enough for damage accumulators, just to be safe
Stop;
}
}
// Blob shadows for player and (eventually) monsters
Class SWWMShadow : SWWMNonInteractiveActor
{
Sector oldfloor;
static SWWMShadow Track( Actor other )
{
// this is necessary due to multiplayer respawn nonsense
let ti = ThinkerIterator.Create('SWWMShadow');
SWWMShadow s;
while ( s = SWWMShadow(ti.Next()) )
{
if ( s.target != other ) continue;
s.Update(true);
return s;
}
s = SWWMShadow(Spawn('SWWMShadow',other.pos));
s.target = other;
s.Update(true);
return s;
}
private void UpdateVisual()
{
// update scale / alpha
if ( target.bINVISIBLE || (target.sprite == 0) || (target.CurSector.GetTexture(0) == skyflatnum) )
alpha = 0.;
else
{
double relz = target.pos.z-pos.z;
if ( target.bFLOATBOB ) relz += target.GetBobOffset();
double bscale = (target.radius/16.)*(1.-min(1.,.003*relz));
alpha = 1.-min(1.,.006*abs(target.pos.z-pos.z));
alpha *= target.alpha;
A_SetScale(bscale);
}
}
private void Update( bool nointerpolate = false )
{
// update position
double curz = target.CurSector.NextLowestFloorAt(target.pos.x,target.pos.y,target.pos.z);
if ( (target.pos.xy == pos.xy) && (pos.z == curz) )
{
UpdateVisual();
return;
}
prev = pos;
SetOrigin((target.pos.x,target.pos.y,curz),true);
if ( nointerpolate )
prev = pos;
else if ( oldfloor != target.CurSector )
prev.z = pos.z; // prevent interpolation of steep height changes
// update slope alignment
if ( !oldfloor || (oldfloor != target.CurSector) )
SWWMUtility.SetToSlope(self,0);
oldfloor = target.CurSector;
UpdateVisual();
}
override void Tick()
{
if ( !target )
{
Destroy();
return;
}
if ( freezetics > 0 )
{
freezetics--;
return;
}
Update();
}
default
{
RenderStyle 'Shaded';
StencilColor "00 00 00";
}
States
{
Spawn:
XZW1 A -1;
Stop;
}
}
// Terrain FX (cheap)
Class SWWMBaseSplash : SWWMNonInteractiveActor abstract
{
TextureID pufftex[8];
FSpawnParticleParams puff;
abstract void DoSplash();
void Splash()
{
puff.style = STYLE_Shaded;
puff.fadestep = -1;
puff.pos = pos;
for ( int i=0; i<8; i++ )
pufftex[i] = TexMan.CheckForTexture("graphics/Particles/xpuff"..i..".png");
DoSplash();
}
States
{
Spawn:
TNT1 A 1 NoDelay Splash();
Stop;
}
}
Class SWWMWaterSplash : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 60;
puff.startalpha = .5;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<60; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-60);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](2.,12.);
dir *= str*.25;
dir.z += 1.;
puff.color1 = SWWMUtility.LerpColor(0xFF4060FF,0xFFA0C0FF,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:1200,AMF_EMITFROMTARGET);
}
}
Class SWWMWaterSplash2 : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 50;
puff.startalpha = .5;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<15; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](1.,6.);
dir *= str*.25;
dir.z += .35;
puff.color1 = SWWMUtility.LerpColor(0xFF4060FF,0xFFA0C0FF,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:300,AMF_EMITFROMTARGET);
}
}
Class SWWMBloodSplash : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 60;
puff.startalpha = .5;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<60; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-60);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](2.,12.);
dir *= str*.25;
dir.z += 1.;
puff.color1 = SWWMUtility.LerpColor(0xFF800000,0xFF600000,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+.5;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:1200,AMF_EMITFROMTARGET);
}
}
Class SWWMBloodSplash2 : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 50;
puff.startalpha = .5;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<15; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](1.,6.);
dir *= str*.25;
dir.z += .35;
puff.color1 = SWWMUtility.LerpColor(0xFF800000,0xFF600000,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+.5;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:300,AMF_EMITFROMTARGET);
}
}
Class SWWMSludgeSplash : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 40;
puff.startalpha = .8;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<60; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-60);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](2.,8.);
dir *= str*.25;
dir.z += .4;
puff.color1 = SWWMUtility.LerpColor(0xFF405040,0xFF303030,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+2.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:1200,AMF_EMITFROMTARGET);
}
}
Class SWWMSludgeSplash2 : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 30;
puff.startalpha = .8;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<15; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](1.,4.);
dir *= str*.25;
dir.z += .15;
puff.color1 = SWWMUtility.LerpColor(0xFF405040,0xFF303030,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+2.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:300,AMF_EMITFROMTARGET);
}
}
Class SWWMMudSplash : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 40;
puff.startalpha = .8;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<60; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-60);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](2.,8.);
dir *= str*.25;
dir.z += .4;
puff.color1 = SWWMUtility.LerpColor(0xFF504020,0xFF302010,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+2.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:1200,AMF_EMITFROMTARGET);
}
}
Class SWWMMudSplash2 : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 30;
puff.startalpha = .8;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<15; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](1.,4.);
dir *= str*.25;
dir.z += .15;
puff.color1 = SWWMUtility.LerpColor(0xFF504020,0xFF302010,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+2.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:300,AMF_EMITFROMTARGET);
}
}
Class SWWMSlimeSplash : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 40;
puff.startalpha = .8;
puff.style = STYLE_AddShaded;
puff.flags = SPF_FULLBRIGHT;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<60; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-60);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](2.,8.);
dir *= str*.25;
dir.z += .4;
puff.color1 = SWWMUtility.LerpColor(0xFF00FF00,0xFF008000,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+2.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:1200,AMF_EMITFROMTARGET);
}
}
Class SWWMSlimeSplash2 : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 30;
puff.startalpha = .8;
puff.style = STYLE_AddShaded;
puff.flags = SPF_FULLBRIGHT;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<15; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](1.,4.);
dir *= str*.25;
dir.z += .15;
puff.color1 = SWWMUtility.LerpColor(0xFF00FF00,0xFF008000,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+2.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
A_AlertMonsters(swwm_uncapalert?0:300,AMF_EMITFROMTARGET);
}
}
Class SWWMLavaSplash : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 40;
puff.startalpha = .8;
puff.style = STYLE_AddShaded;
puff.flags = SPF_FULLBRIGHT;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<60; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-60);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](2.,12.);
dir *= str*.35;
dir.z += .6;
puff.color1 = SWWMUtility.LerpColor(0xFFFFC040,0xFFFF4020,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+1.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
let s = Spawn('SWWMSizzleSmoke',pos);
s.target = target;
}
}
Class SWWMLavaSplash2 : SWWMBaseSplash
{
override void DoSplash()
{
puff.lifetime = 30;
puff.startalpha = .8;
puff.style = STYLE_AddShaded;
puff.flags = SPF_FULLBRIGHT;
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<15; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](1.,6.);
dir *= str*.35;
dir.z += .2;
puff.color1 = SWWMUtility.LerpColor(0xFFFFC040,0xFFFF4020,FRandom[ExploS](0.,1.));
puff.texture = pufftex[Random[ExploS](0,7)];
puff.size = str+1.;
puff.sizestep = -.02*str;
puff.vel = dir;
puff.accel = (0,0,-.03*str);
level.SpawnParticle(puff);
}
let s = Spawn('SWWMSizzleSmoke2',pos);
s.target = target;
}
}
Class SWWMSizzleSmoke : SWWMBaseSplash
{
override void DoSplash()
{
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<4; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](.5,2.);
let s = SWWMAnimSprite.SpawnAt('SWWMSmallSmoke',pos);
s.vel = dir*str+(0,0,.4);
s.scolor = Color(1,1,1)*Random[ExploS](192,224);
s.scale *= 8.;
s.alpha *= .4;
s.SetRenderStyle(STYLE_AddShaded);
s.framestep = Random[ExploS](1,3);
}
A_AlertMonsters(swwm_uncapalert?0:1200,AMF_EMITFROMTARGET);
}
}
Class SWWMSizzleSmoke2 : SWWMBaseSplash
{
override void DoSplash()
{
double ang, pt, str;
Vector3 dir;
for ( int i=0; i<2; i++ )
{
ang = FRandom[ExploS](0,360);
pt = FRandom[ExploS](-90,-30);
dir = SWWMUtility.Vec3FromAngles(ang,pt);
str = FRandom[ExploS](.25,1.);
let s = SWWMAnimSprite.SpawnAt('SWWMSmallSmoke',pos);
s.vel = dir*str+(0,0,.15);
s.scolor = Color(1,1,1)*Random[ExploS](192,224);
s.scale *= 4.;
s.alpha *= .3;
s.SetRenderStyle(STYLE_AddShaded);
s.framestep = Random[ExploS](1,2);
}
A_AlertMonsters(swwm_uncapalert?0:300,AMF_EMITFROMTARGET);
}
}
// Hexen thing
Class SWWMCrushedSpike : Actor
{
Default
{
Radius 20;
Height 16;
+SOLID;
+FLOORCLIP;
+NOTELEPORT;
}
States
{
Spawn:
TSPK X -1;
Stop;
}
}