// common code goes here enum ESWWMGZChannels { CHAN_YOUDONEFUCKEDUP = 63200, // exception handler CHAN_DEMOVOICE = 63201, // demolitionist voices CHAN_FOOTSTEP = 63202, // footstep sounds and others CHAN_WEAPONEXTRA = 63203, // additional weapon sounds (usually loops) CHAN_POWERUP = 63204, // powerup sounds CHAN_POWERUPEXTRA = 63205, // additional powerup sounds CHAN_JETPACK = 63206, // jetpack sound CHAN_ITEMEXTRA = 63207, // additional item sounds CHAN_WEAPONEXTRA2 = 63208, // additional weapon sound slot CHAN_WEAPONEXTRA3 = 63209, // additional weapon sound slot (again) CHAN_DAMAGE = 63210, // used for impact/hit sounds CHAN_AMBEXTRA = 63211 // player ambience when submerged }; // Future planning, will be filled out with AI stuff and whatnot someday Class SWWMMonster : Actor abstract { // integrated fun tags virtual clearscope String GetFunTag( String defstr = "" ) { return GetTag(defstr); } // the function that should be overriden in subclasses virtual int HandleLocationalDamage( Actor inflictor, Actor source, int damage, Name mod, Vector3 HitLocation, int flags = 0, double angle = 0 ) { return damage; } // locational damage support, akin to UE1, but hitlocation will be treated as a relative offset, to make things easier // this one should be called directly by everything in this mod, when possible int LocationalDamageMobj( Actor inflictor, Actor source, int damage, Name mod, Vector3 HitLocation, int flags = 0, double angle = 0 ) { damage = HandleLocationalDamage(inflictor,source,damage,mod,HitLocation,flags,angle); return Super.DamageMobj(inflictor,source,damage,mod,flags,angle); } // "estimated" locational damage for the vanilla DamageMobj override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle ) { Vector3 guesspos = (0,0,Height/2.); // use inflictor if available, as it may be a projectile or hitscan puff // if damage comes from an item, use owner // all of this could be done better (or implemented as an engine feature), but whatever Actor whomst = inflictor?inflictor:source; if ( whomst is 'Inventory' ) whomst = Inventory(whomst).Owner; if ( whomst ) { if ( whomst.bMISSILE || (flags&DMG_INFLICTOR_IS_PUFF) ) guesspos = level.Vec3Diff(pos,whomst.pos); else guesspos = level.Vec3Diff(pos,whomst.Vec3Offset(0,0,whomst.Height/2)); guesspos.x = clamp(guesspos.x,-radius,radius); guesspos.y = clamp(guesspos.y,-radius,radius); guesspos.z = clamp(guesspos.z,0,height); } return LocationalDamageMobj(inflictor,source,damage,mod,guesspos,flags,angle); } } // Less mean-spirited Keen Class SWWMHangingKeen : Actor { action void A_DropKeen() { Spawn("SWWMDroppedKeen",Vec3Offset(0,0,8)); } override bool Used( Actor user ) { // test vertical range Vector3 diff = level.Vec3Diff(user.Vec3Offset(0,0,user.Height/2),Vec3Offset(0,0,Height/2)); double rang = user.player?PlayerPawn(user.player.mo).UseRange:(user.Height/2); if ( abs(diff.z) > rang ) return false; if ( Health > 0 ) { DamageMobj(user,user,Health,'Untie',DMG_FORCED|DMG_THRUSTLESS); return true; } return false; } Default { Tag "$FN_KEEN"; Health 100; Radius 10; Height 54; Mass int.max; PainChance 256; +SOLID; +SPAWNCEILING; +NOGRAVITY; +SHOOTABLE; +NOICEDEATH; +DONTFALL; +NOBLOOD; +DONTTHRUST; PainSound "keen/pain"; DeathSound "keen/death"; } States { Spawn: KEE2 A -1; Stop; Death: KEE2 A 6; KEE2 B 6 A_DropKeen(); KEE2 C 6 A_Scream(); KEE2 DE 6; KEE2 F 30; KEE2 F -1 A_KeenDie(); Stop; Pain: KEE2 G 4; KEE2 G 8 A_Pain(); Goto Spawn; } } Class SWWMDroppedKeen : Actor { Default { Radius 10; Height 32; Gravity .5; +NOBLOCKMAP; } States { Spawn: KEE3 A 1 A_JumpIf(pos.z<=floorz,1); Wait; KEE3 B 1 { vel.z = 4; } KEE3 B 1 A_JumpIf(pos.z<=floorz,1); Wait; KEE3 B 1 { vel.z = 2; } KEE3 B 1 A_JumpIf(pos.z<=floorz,1); Wait; KEE3 B 12; TNT1 A 1 { Spawn("TeleportFog",pos,ALLOW_REPLACE); } Stop; } } Class SWWMBossBrainExpl : Actor { void A_Ignite() { A_QuakeEx(3,3,3,20,0,400,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollintensity:2.); A_StartSound("explodium/hit",CHAN_VOICE,CHANF_DEFAULT,.4,.5); Scale *= FRandom[ExploS](0.8,1.1); Scale.x *= RandomPick[ExploS](-1,1); Scale.y *= RandomPick[ExploS](-1,1); int numpt = Random[ExploS](8,16); for ( int i=0; i 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; } dist -= d.Distance; if ( d.HitType != TRACE_HitNone ) { // 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+hitnormal; } else newpos = level.Vec3Offset(newpos,dir*spd); nstep++; } 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 0.1; Height 0; +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); 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; } } // visual smoke, very strictly non-interacting, updates even when game is frozen Class SWWMViewSmoke : SWWMHalfSmoke { Vector3 ofs, vvel; 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); vvel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16); } override void Tick() { prev = pos; // for interpolation if ( !target || !target.player ) { Destroy(); return; } Vector3 x, y, z; [x, y, z] = swwm_CoordUtil.GetAxes(target.pitch,target.angle,target.roll); Vector3 origin = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),x*ofs.x+y*ofs.y+z*ofs.z); SetOrigin(origin,true); UpdateWaterLevel(); bInvisible = (players[consoleplayer].camera != target); ofs += vvel; vvel *= 0.96; vvel.z += 0.01; if ( (waterlevel > 0) && !bAMBUSH ) Destroy(); if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } } Class SWWMBubble : Actor { Default { RenderStyle "Add"; Radius 2; Height 2; +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; 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; } dist -= d.Distance; if ( d.HitType != TRACE_HitNone ) { // 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+hitnormal; } else newpos = level.Vec3Offset(newpos,dir*spd); nstep++; } 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 SWWMSpark : Actor { bool dead; Sector tracksector; int trackplane; Default { RenderStyle "Add"; Radius 2; Height 2; +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 { if ( pos.z > floorz ) 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; 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; } dist -= d.Distance; if ( d.HitType != TRACE_HitNone ) { // 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+hitnormal; } else newpos = level.Vec3Offset(newpos,dir*spd); if ( (spd < 1.) && ((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 ( 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 2; Height 2; +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) && (pos.z > floorz) ) 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; 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; } dist -= d.Distance; if ( d.HitType != TRACE_HitNone ) { // 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+hitnormal; SetStateLabel("Bounce"); } else newpos = level.Vec3Offset(newpos,dir*spd); if ( (spd < 1.) && ((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 ( 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 SWWMNothing : Actor { States { Spawn: TNT1 A 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"; +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, int 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 0 ) alpha *= clamp(4.*((sd-Distance3D(players[consoleplayer].Camera))/sd),0.,1.); double relz = target.pos.z-pos.z; if ( target.bFLOATBOB ) relz += BobSin(target.FloatBobPhase)*target.FloatBobStrength; double bscale = (target.radius/16.)*(1.-min(1.,.003*relz)); // hax if ( target is 'CompanionLamp' ) bscale *= 2.; A_SetScale(bscale); } // 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) ) return; 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; } override void Tick() { if ( !target ) { Destroy(); return; } Update(); } default { RenderStyle "Shaded"; StencilColor "000000"; DistanceCheck 'swwm_shadowdist'; Radius .1; Height 0.; +NOBLOCKMAP; +NOINTERACTION; +DONTSPLASH; +NOTELEPORT; FloatBobPhase 0; } States { Spawn: XZW1 A -1; Stop; } }