// 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() ) return; ReactionTime--; if ( ReactionTime < 0 ) { if ( !IsLooping ) { Destroy(); return; } else ReactionTime = abs(InitialReactionTime); } if ( target ) SetOrigin(target.pos,true); UpdateLight(); } } // Generic smoke, lightweight tick Class SWWMSmoke : Actor { Default { RenderStyle "Shaded"; StencilColor "FFFFFF"; Radius .1; Height 0; Speed 1; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +FORCEXYBILLBOARD; +ROLLSPRITE; +ROLLCENTER; +THRUACTORS; +NOTELEPORT; +NOINTERACTION; Scale .3; FloatBobPhase 0; } 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 += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(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 ( 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); Vector3 hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else hitnormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) hitnormal *= -1; } if ( d.HitType != TRACE_HitNone ) { 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 ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XSMK ABCDEFGHIJKLMNOPQRST 1 A_SetTics(1+special1); Stop; } } // strictly non-interacting smoke, much lighter tick, used for heavier effects Class SWWMHalfSmoke : Actor { Default { RenderStyle "Shaded"; StencilColor "FFFFFF"; Radius .1; Height 0; Speed 1; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +FORCEXYBILLBOARD; +ROLLSPRITE; +ROLLCENTER; +NOTELEPORT; +NOINTERACTION; Scale 0.3; FloatBobPhase 0; } override void PostBeginPlay() { double ang, pt; scale *= FRandom[Puff](0.5,1.5); alpha *= FRandom[Puff](0.5,1.5); ang = FRandom[Puff](0,360); pt = FRandom[Puff](-90,90); vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.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 ( isFrozen() ) return; vel *= 0.96; vel.z += 0.01; SetOrigin(level.Vec3Offset(pos,vel),true); UpdateWaterLevel(); if ( (waterlevel > 0) && !bAMBUSH ) { let b = Spawn("SWWMBubble",pos); b.scale *= abs(scale.x); b.vel = vel; Destroy(); return; } if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XSMK ABCDEFGHIJKLMNOPQRST 1 A_SetTics(1+special1); Stop; } } Class SWWMSmallSmoke : SWWMHalfSmoke { override void PostBeginPlay() { double ang, pt; scale *= FRandom[Puff](0.1,0.3); alpha *= FRandom[Puff](0.5,1.5); ang = FRandom[Puff](0,360); pt = FRandom[Puff](-90,90); vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16); roll = Frandom[Puff](0,360); scale.x *= RandomPick[Puff](-1,1); scale.y *= RandomPick[Puff](-1,1); } States { Spawn: QSM6 ABCDEFGHIJKLMNOPQR 1 A_SetTics(1+special1); Stop; } } Class SWWMBubble : Actor { Default { RenderStyle "Add"; Radius .1; Height 0; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +FORCEXYBILLBOARD; +NOTELEPORT; +THRUACTORS; +NOINTERACTION; Scale 0.5; FloatBobPhase 0; } override void PostBeginPlay() { double ang, pt; scale *= FRandom[Puff](0.5,1.5); ang = FRandom[Puff](0,360); pt = FRandom[Puff](-90,90); vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8); if ( waterlevel <= 0 ) Destroy(); SetState(ResolveState("Spawn")+Random[Puff](0,19)); } override void Tick() { prev = pos; 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); Vector3 hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else hitnormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) hitnormal *= -1; } if ( d.HitType != TRACE_HitNone ) { 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) || !Random[Puff](0,100) ) Destroy(); if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: XBUB ABCDEFGHIJKLMNOPQRST 1; Loop; } } Class SWWMSparkTrail : Actor { Default { RenderStyle "Add"; Radius .1; Height 0.; +FORCEXYBILLBOARD; +NOGRAVITY; +NOBLOCKMAP; +NOINTERACTION; +DONTSPLASH; +NOTELEPORT; } override void Tick() { if ( isFrozen() ) return; A_SetScale(scale.x*.9,scale.y); A_FadeOut(.06); } States { Spawn: XZW1 A -1 Bright; Stop; } } Class SWWMSpark : Actor { bool dead; Sector tracksector; int trackplane; Default { RenderStyle "Add"; Radius .1; Height 0; +NOBLOCKMAP; +FORCEXYBILLBOARD; +THRUACTORS; +NOTELEPORT; +DONTSPLASH; +NOINTERACTION; Gravity 0.2; Scale 0.05; FloatBobPhase 0; } override void Tick() { prev = pos; 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); Vector3 hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else hitnormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) hitnormal *= -1; } if ( d.HitType != TRACE_HitNone ) { 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.scale.y = taillen; t.angle = atan2(taildir.y,taildir.x); t.pitch = asin(-taildir.z)+90; } 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 ( 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 : Actor { SWWMChip prevchip, nextchip; bool killme; double anglevel, pitchvel, rollvel; bool dead; Sector tracksector; int trackplane; Default { Radius .1; Height 0; +NOBLOCKMAP; +THRUACTORS; +NOTELEPORT; +DONTSPLASH; +INTERPOLATEANGLES; +ROLLSPRITE; +ROLLCENTER; +FORCEXYBILLBOARD; +NOINTERACTION; Gravity 0.35; Scale 0.2; FloatBobPhase 0; } 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 ( 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); Vector3 hitnormal = -d.HitDir; if ( d.HitType == TRACE_HitFloor ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.top.Normal; else hitnormal = d.HitSector.floorplane.Normal; } else if ( d.HitType == TRACE_HitCeiling ) { if ( d.Hit3DFloor ) hitnormal = -d.Hit3DFloor.bottom.Normal; else hitnormal = d.HitSector.ceilingplane.Normal; } else if ( d.HitType == TRACE_HitWall ) { hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit(); if ( !d.LineSide ) hitnormal *= -1; } if ( d.HitType != TRACE_HitNone ) { 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); pitch = 0; roll = 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 *= .98; 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: XZW2 # -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 : Actor { Default { RenderStyle "Add"; Radius .1; Height 0; +NOGRAVITY; +NOBLOCKMAP; +DONTSPLASH; +ROLLSPRITE; +ROLLCENTER; +NOINTERACTION; +FORCEXYBILLBOARD; FloatBobPhase 0; } 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 WaterHitList; Array ShootThroughList; Actor ignoreme; static play void DoTrail( Actor target, Vector3 pos, Vector3 dir, double dist, int bubblechance, bool smoky = false ) { let t = new("SWWMBulletTrail"); t.ignoreme = target; t.WaterHitList.Clear(); t.ShootThroughList.Clear(); t.Trace(pos,level.PointInSector(pos.xy),dir,dist,0); for ( int i=0; i