// Gore FX ported over from Soundless Mound, with some edits // Base blood actor Class mkBlood : SWWMNonInteractiveActor { Default { +PUFFGETSOWNER; } void A_Bleed( int str = 1 ) { if ( !target ) return; let b = Spawn('mkBloodSpray',pos); Vector2 dirto = target.Vec2To(self).unit(); b.angle = atan2(dirto.y,dirto.x); b.pitch = FRandom[Blood](-60,30); b.translation = translation; b.target = target; b.args[0] = str; if ( target.bloodcolor ) b.SetShade(Color(target.bloodcolor.r/2,target.bloodcolor.g/2,target.bloodcolor.b/2)); else b.SetShade(Color(80,0,0)); b.CopyBloodColor(target); int numpt = Random[Blood](2,4)+str*2; double sstr = (2.5+str)/3.; Vector3 vdir = SWWMUtility.Vec3FromAngles(b.angle,b.pitch); TextureID pufftex[8]; for ( int i=0; i<8; i++ ) pufftex[i] = TexMan.CheckForTexture("graphics/Particles/xpuff"..i..".png"); FSpawnParticleParams puff; puff.color1 = b.fillcolor; puff.style = STYLE_Shaded; puff.accel = (0,0,-.25); puff.startalpha = .75; puff.fadestep = -1; puff.pos = pos; for ( int i=0; i=.5; j-=.125 ) { puff.texture = pufftex[Random[Blood](0,7)]; puff.lifetime = int(12*sstr); puff.size = 2.*str*FRandom[Blood](.6,1.4)*j; puff.vel = ndir*j; puff.sizestep = -.02*sstr*j; level.SpawnParticle(puff); } } let s = mkBloodSmoke(SWWMStaticSprite.SpawnAt('mkBloodSmoke',pos)); s.scolor = b.fillcolor; s.scale *= .4*str; s.thickness = str-1; } States { Spawn: TNT1 A 1 NoDelay A_Bleed(3); Stop; TNT1 A 1 A_Bleed(2); Stop; TNT1 A 1 A_Bleed(1); Stop; } } // a burst of blood attached to a bleeding actor Class mkBloodSpray : SWWMNonInteractiveActor { double str; int cnt; Vector3 attachofs; double baseang; color shadecol; override void PostBeginPlay() { if ( !target ) { Destroy(); return; } str = FRandom[Blood](2.,4.)*args[0]; cnt = Random[Blood](2,3)*args[0]; attachofs.xy = RotateVector(target.Vec2To(self),-target.angle); attachofs.z = pos.z-target.pos.z; baseang = angle-target.angle; } private bool IsTargetFlying() { if ( !target ) return false; if ( !target.bNOGRAVITY && (target.pos.z > target.floorz) && target.TestMobjZ() ) return true; if ( ((!target.bNOGRAVITY && target.bBlasted) || (target.health <= 0)) && (target.vel.length() > 10.) ) return true; return false; } override void Tick() { if ( !target ) { Destroy(); return; } if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; Vector3 setofs = SWWMUtility.RotateVector3(attachofs,target.angle); SetOrigin(level.Vec3Offset(target.pos,setofs),false); int sz = max(1,args[0]/2); double ang, pt; int cnt = sz-Random[Blood](0,4); for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( killme ) A_FadeOut(.01); if ( dead ) { // do nothing but follow floor movement and eventually fade out if ( tracksector ) { double trackz; if ( trackplane ) trackz = tracksector.ceilingplane.ZAtPoint(pos.xy)-.1; else trackz = tracksector.floorplane.ZAtPoint(pos.xy); if ( trackz != pos.z ) { SetZ(trackz); UpdateWaterLevel(false); } } if ( (waterlevel > 0) || GetFloorTerrain().isliquid ) { scale *= 1.005; A_FadeOut(.01); } if ( onceiling && (special2 < 200) && (scale.x > .2) ) { if ( special1-- ) return; special2 += 10; special1 = Random[Blood](20,30)+special2; let d = Spawn('mkBloodDrop',Vec3Offset(0,0,-2)); d.master = self; d.SetShade(fillcolor); d.CopyBloodColor(self); d.scale = Scale*FRandom[Blood](.4,.6); } return; // we don't need to update states when we're dead } else { if ( !pufftex[0] ) { for ( int i=0; i<8; i++ ) pufftex[i] = TexMan.CheckForTexture("graphics/Particles/xpuff"..i..".png"); puff.color1 = fillcolor; puff.style = STYLE_Shaded; puff.fadestep = -1; } // gravitational pull if ( waterlevel <= 0 ) vel.z -= GetGravity(); // linetrace-based movement (hopefully more reliable than traditional methods) Vector3 dir = vel; double spd = vel.length(); dir /= spd; FLineTraceData d; Vector3 newpos = pos; newpos.z = clamp(newpos.z,floorz,ceilingz); double ang = atan2(dir.y,dir.x); double pt = asin(-dir.z); LineTrace(ang,spd,pt,TRF_THRUACTORS|TRF_THRUHITSCAN|TRF_ABSPOSITION,newpos.z,newpos.x,newpos.y,d); if ( d.HitType != TRACE_HitNone ) { Vector3 hitnormal = SWWMUtility.GetLineTraceHitNormal(d); newpos = d.HitLocation+hitnormal; } else newpos = level.Vec3Offset(newpos,vel); newpos.z = clamp(newpos.z,floorz,ceilingz); Vector3 ndiff = level.Vec3Diff(newpos,pos); double ndist = ndiff.length(); ndiff /= ndist; puff.lifetime = 10; puff.startalpha = .5*alpha; puff.sizestep = -1.*scale.x; puff.accel = (0,0,-.5); for ( int i=0; i=.5; j-=.125 ) { puff.texture = pufftex[Random[Blood](0,7)]; puff.size = 10.*scale.x*FRandom[Blood](.6,1.4)*j; puff.pos = pos; puff.vel = ndir*j; puff.sizestep = -1.*scale.x*j; level.SpawnParticle(puff); } } Destroy(); return; } master.tracer = self; } bMISSILE = false; dead = true; Vector3 floordir; if ( d.Hit3DFloor ) { tracksector = d.Hit3DFloor.model; trackplane = 1; floordir = -d.Hit3DFloor.model.ceilingplane.Normal; } else { tracksector = d.HitSector; trackplane = 0; floordir = d.HitSector.floorplane.Normal; } SWWMUtility.SetToSlope(self,FRandom[Blood](0,360)); A_SetRenderStyle(1.,STYLE_Shaded); frame = Random[Blood](5,12); int numpt = Random[Blood](4,8); puff.lifetime = 20; puff.startalpha = .5*alpha; puff.pos = pos; puff.accel = (0,0,-.25); for ( int i=0; i=.5; j-=.125 ) { puff.texture = pufftex[Random[Blood](0,7)]; puff.size = 10.*scale.x*FRandom[Blood](.6,1.4)*j; puff.vel = ndir*j; puff.sizestep = -1.*scale.x*j; level.SpawnParticle(puff); } } vel *= 0; return; } if ( (d.HitType == TRACE_HitCeiling) || (pos.z >= ceilingz) ) { // hacky workaround if ( !d.HitSector ) { d.HitSector = ceilingsector; d.HitTexture = ceilingsector.GetTexture(1); } if ( (d.HitTexture == skyflatnum) || master ) { Destroy(); return; } // hit the ceiling SetOrigin(d.HitLocation-(0,0,1),true); A_StartSound("misc/blooddrop",volume:.1); bMISSILE = false; dead = true; onceiling = true; Vector3 floordir; if ( d.Hit3DFloor ) { tracksector = d.Hit3DFloor.model; trackplane = 0; floordir = -d.Hit3DFloor.model.floorplane.Normal; } else { tracksector = d.HitSector; trackplane = 1; floordir = d.HitSector.ceilingplane.Normal; } SWWMUtility.SetToSlope(self,FRandom[Blood](0,360),true); A_SetRenderStyle(1.,STYLE_Shaded); frame = Random[Blood](13,20); int numpt = Random[Blood](4,8); puff.lifetime = 20; puff.startalpha = .5*alpha; puff.pos = pos; puff.accel = (0,0,-.25); for ( int i=0; i=.5; j-=.125 ) { puff.texture = pufftex[Random[Blood](0,7)]; puff.size = 10.*scale.x*FRandom[Blood](.6,1.4)*j; puff.vel = ndir*j; puff.sizestep = -1.*scale.x*j; level.SpawnParticle(puff); } } vel *= 0; return; } if ( d.HitType == TRACE_HitWall ) { if ( d.HitLine.backsector && (d.HitLine.backsector.GetTexture(1) == skyflatnum) && (d.HitLocation.z >= d.HitLine.backsector.ceilingplane.ZAtPoint(d.HitLocation.xy)) ) { Destroy(); return; } // hit wall Vector2 walldir = (-d.HitLine.delta.y,d.HitLine.delta.x).unit(); if ( d.LineSide ) walldir *= -1; SetOrigin(d.HitLocation-walldir*8,true); TraceBleedAngle(20,atan2(walldir.y,walldir.x),0); A_StartSound("misc/blooddrop",volume:.1); int numpt = Random[Blood](4,8); puff.lifetime = 20; puff.startalpha = .5*alpha; puff.pos = pos; puff.accel = (0,0,-.25); for ( int i=0; i=.5; j-=.125 ) { puff.texture = pufftex[Random[Blood](0,7)]; puff.size = 10.*scale.x*FRandom[Blood](.6,1.4)*j; puff.vel = ndir*j; puff.sizestep = -1.*scale.x*j; level.SpawnParticle(puff); } } Destroy(); return; } UpdateWaterLevel(); if ( waterlevel > 0 ) A_FadeOut(); scale *= .99; if ( scale.x <= 0. ) { Destroy(); return; } } if ( !CheckNoDelay() || (tics == -1) ) return; if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } override void PostBeginPlay() { Super.PostBeginPlay(); SWWMHandler.QueueBlod(self); int jumps = Random[Blood](0,3); state dest = ResolveState('Spawn'); SetState(dest+jumps); } override void OnDestroy() { SWWMHandler.DeQueueBlod(self); Super.OnDestroy(); } States { Spawn: SBLD ABCD 2; Loop; } } // chunky salsa in the air Class mkBloodSmoke : SWWMStaticSprite { int thickness; override void SetupSprite() { texture = TexMan.CheckForTexture(String.Format("MSMK%c0",0x41+Random[Blood](0,7))); Scale = (.5,.5); Alpha = .35; SetRenderStyle(STYLE_Shaded); scolor = gameinfo.defaultbloodcolor; Flags |= SPF_ROLL; bCheckWater = true; bWallStop = true; } override void OnTick() { if ( lastwater ) alpha = max(0.,alpha-.1); alpha = max(0.,alpha-(.04/max(1.,thickness))); scale *= 1.+(.04/max(1.,thickness)); vel *= 1.-(.04/max(1.,thickness)); if ( alpha <= 0. ) Destroy(); } } // flying gibs Class mkFlyingGib : Actor { Mixin SWWMMissileFix; bool killme; int lastbleed; color shadecol; bool bleeding; double rollvel, pitchvel; mkFlyingGib prevmeat, nextmeat; Vector3 oldpos; TextureID pufftex[8]; FSpawnParticleParams puff; override void PostBeginPlay() { Super.PostBeginPlay(); for ( int i=0; i<8; i++ ) pufftex[i] = TexMan.CheckForTexture("graphics/Particles/xpuff"..i..".png"); puff.style = STYLE_Shaded; puff.lifetime = 40; puff.accel = (0,0,-.25); puff.fadestep = -1; frame = Random[Blood](0,7); double ang = FRandom[Gibs](0,360); double pt = FRandom[Gibs](-60,20); Vector3 dir = SWWMUtility.Vec3FromAngles(ang,pt); vel += dir*FRandom[Gibs](4.,8.); if ( master ) { vel += master.vel; CopyBloodColor(master); } rollvel = FRandom[Gibs](10,50)*RandomPick[Gibs](-1,1)*clamp(vel.length()/10.,.25,4.); pitchvel = FRandom[Gibs](10,50)*RandomPick[Gibs](-1,1)*clamp(vel.length()/10.,.25,4.); scale *= FRandom[Gibs](.5,1.5); if ( master && master.bloodcolor ) shadecol = Color(master.bloodcolor.r/2,master.bloodcolor.g/2,master.bloodcolor.b/2); else shadecol = Color(80,0,0); puff.color1 = shadecol; bleeding = true; if ( Random[Blood](0,1) ) bXFlip = true; SWWMHandler.QueueMeat(self); } override void OnDestroy() { SWWMHandler.DeQueueMeat(self); Super.OnDestroy(); } override void Tick() { oldpos = pos; Super.Tick(); if ( isFrozen() || (freezetics > 0) ) return; if ( killme ) A_FadeOut(.01); if ( CurState == ResolveState('Death2') ) { if ( vel.length() < .1 ) bleeding = false; return; } roll += rollvel; pitch += pitchvel; if ( waterlevel > 0 ) { rollvel *= .99; pitchvel *= .99; return; } if ( !bleeding ) return; Vector3 ndiff = level.Vec3Diff(pos,oldpos); double ndist = ndiff.length(); ndiff /= ndist; for ( int i=0; i level.maptime ) return; lastbleed = level.maptime+5; TraceBleedAngle(int(vel.length()),angle+180,-pitch); } Default { Radius 4; Height 4; Mass 10; Scale .75; Gravity .5; BounceType 'Doom'; BounceFactor .2; +MISSILE; +DROPOFF; +NOBLOCKMAP; +USEBOUNCESTATE; +CANBOUNCEWATER; -BOUNCEAUTOOFF; +MOVEWITHSECTOR; +THRUACTORS; +NOTELEPORT; +ROLLSPRITE; +ROLLCENTER; +INTERPOLATEANGLES; } States { Spawn: XZW1 # 1; Wait; Bounce: XZW1 # 0 { A_Bleed(); A_StartSound("misc/gibhit",CHAN_BODY,CHANF_OVERLAP); // gib stuck! if ( (floorz >= ceilingz) || !level.IsPointInLevel(pos) ) Destroy(); } Goto Spawn; Death: XZW1 # 1 A_JumpIf(pos.z<=floorz,'Death2'); Wait; Death2: XZW1 # -1 { A_StartSound("misc/gibhit",CHAN_BODY,CHANF_OVERLAP); pitch = Random[Gibs](-5,5); if ( abs(roll) < abs(180-roll) ) roll = Random[Gibs](-5,5); else roll = Random[Gibs](175,185); A_Stop(); // floor drop let b = Spawn('mkBloodDrop',pos); b.scale *= 2.0; b.SetShade(shadecol); } Stop; } } // Manually added gibbing Class mkGibber : SWWMNonInteractiveActor { Actor Gibbed; int gibcount, gibsize; int delay; color shadecol; meta Class gibtype; // allow custom gib types (will be used for monster pack) bool psnd; bool mksplat; Property GibType: gibtype; virtual void BurstGibs() { double ang, pt; Vector3 dir; int bloodthrottle = 0, gibthrottle = 0; let hnd = SWWMHandler(EventHandler.Find('SWWMHandler')); if ( hnd ) { if ( hnd.oldmaxblood != 0 ) bloodthrottle = max((hnd.blods_realcnt-hnd.oldmaxblood)/100,0); if ( hnd.oldmaxgibs != 0 ) gibthrottle = max((hnd.meats_realcnt-hnd.oldmaxgibs)/100,0); } for ( int i=0; i 2. ) a.vel = a.vel.unit()*2.; a.vel += dir*FRandom[Gibs](.2,.8); a.scolor = shadecol; a.thickness = Random[Gibs](1,3); } for ( int i=0; i 0 ) { freezetics--; return; } if ( isFrozen() ) return; if ( !gibbed ) { SetOrigin(level.Vec3Offset(pos,vel),false); return; } SetOrigin(gibbed.pos,false); scale = gibbed.scale; vel = gibbed.vel; if ( delay > 0 ) { delay--; return; } if ( !psnd ) { A_StartSound("misc/gibber",CHAN_VOICE,CHANF_OVERLAP); psnd = true; } if ( mksplat ) { let s = Spawn('mkBloodBlast',pos); s.SetShade(shadecol); s.master = gibbed; mksplat = false; } BurstGibs(); if ( reactiontime <= 0 ) Destroy(); } Default { Radius 32; Height 16; mkGibber.GibType 'mkFlyingGib'; } } // for exploding Cyberdemon/Spider Class mkBloodBlast : SWWMNonInteractiveActor { override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; double fz = CurSector.floorplane.ZAtPoint(pos.xy); if ( fz != pos.z ) SetOrigin((pos.x,pos.y,fz),true); if ( (waterlevel > 0) || GetFloorTerrain().isliquid ) A_FadeOut(); if ( !master ) A_FadeOut(.01); } override void PostBeginPlay() { double fz = CurSector.floorplane.ZAtPoint(pos.xy); SetZ(fz); prev.z = fz; A_QueueCorpse(); SWWMUtility.SetToSlope(self,FRandom[Blood](0,360)); } default { RenderStyle 'Shaded'; StencilColor "FF 00 00"; } States { Spawn: XZW1 A -1; Stop; } } // bare actors used for copying blood color to vanilla monsters Class GreenBloodReference : Actor { Default { BloodColor "00 FF 00"; } } Class BlueBloodReference : Actor { Default { BloodColor "00 00 FF"; } } Class PurpleBloodReference : Actor { Default { BloodColor "80 00 FF"; } } // corpse thump handler Class CorpseFallTracker : Thinker { Actor mybody; double lastvelz; bool wasflying; static void TrackBody( Actor b ) { if ( !b ) return; let cft = new('CorpseFallTracker'); cft.ChangeStatNum(STAT_USER); cft.mybody = b; cft.lastvelz = b.vel.z; cft.wasflying = ((b.pos.z>b.floorz)&&b.TestMobjZ()); } override void Tick() { if ( !mybody ) { Destroy(); return; } // play fall thumps bool isflying = ((mybody.pos.z>mybody.floorz)&&mybody.TestMobjZ()); if ( wasflying && !isflying && (lastvelz < -10) ) mybody.A_StartSound("misc/bodythump",CHAN_DAMAGE,CHANF_OVERLAP); wasflying = isflying; lastvelz = mybody.vel.z; // wait until body is dead on floor and at the last state of animation if ( (mybody.Health > 0) || isflying || (mybody.tics != -1) || (mybody.vel.length() > 0) ) return; let b = mybody.Spawn('mkBloodPool',mybody.pos); Color shadecol; if ( mybody.bloodcolor ) shadecol = Color(mybody.bloodcolor.r/2,mybody.bloodcolor.g/2,mybody.bloodcolor.b/2); else shadecol = Color(80,0,0); mkBloodPool(b).stepcol = shadecol; b.master = mybody; b.A_SetScale(mybody.default.radius/16.); // futureproofing hack (heh) let mtype = SWWMUtility.GetParentClassBefore(mybody.GetClass(),'Actor'); if ( mtype.GetClassName() == 'SWWMMonster' ) { b.A_SetRenderStyle(1.,STYLE_Shaded); b.SetShade(shadecol); } else b.translation = mybody.bloodtranslation; Destroy(); } } // Blood pool Class mkBloodPool : SWWMNonInteractiveActor { double basesz, sz, accel; Color stepcol; mkBloodPool prevpool, nextpool; bool bRaised; // dead body was revived, fade out faster Property BaseAccel : accel; override void OnDestroy() { Super.OnDestroy(); if ( prevpool ) { prevpool.nextpool = nextpool; if ( nextpool ) nextpool.prevpool = prevpool; } let hnd = SWWMHandler(EventHandler.Find('SWWMHandler')); if ( !hnd ) return; hnd.bloodpools = nextpool; } override void PostBeginPlay() { double fz = CurSector.floorplane.ZAtPoint(pos.xy); SetZ(fz); prev.z = fz; basesz = scale.x; sz = .01; A_SetScale(sz); A_QueueCorpse(); SWWMUtility.SetToSlope(self,FRandom[Blood](0,360)); let hnd = SWWMHandler(EventHandler.Find('SWWMHandler')); if ( !hnd ) return; nextpool = hnd.bloodpools; hnd.bloodpools = self; if ( nextpool ) nextpool.prevpool = self; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; double fz = CurSector.floorplane.ZAtPoint(pos.xy); if ( fz != pos.z ) SetOrigin((pos.x,pos.y,fz),true); if ( (waterlevel > 0) || GetFloorTerrain().isliquid ) A_FadeOut(); if ( !master ) A_FadeOut(.01); else if ( (master.Health > 0) || bRaised ) { bRaised = true; A_FadeOut(); } if ( accel <= 0. ) return; sz += accel; double fact = min(special1++/1200.,1.); accel = SWWMUtility.Lerp(default.accel,0.,fact*fact); A_SetScale(basesz*sz); A_SetSize(50.*basesz*sz); } Default { Radius 1; Height 1; StencilColor "FF 00 00"; mkBloodPool.BaseAccel .0006; } States { Spawn: XZW1 A -1; Stop; } } // Bloody footsteps for player // (Spawning handled also by player) Class mkBloodStep : SWWMNonInteractiveActor { override void PostBeginPlay() { double fz = CurSector.floorplane.ZAtPoint(pos.xy); SetZ(fz); prev.z = fz; } override void Tick() { if ( freezetics > 0 ) { freezetics--; return; } if ( isFrozen() ) return; double fz = CurSector.floorplane.ZAtPoint(pos.xy); if ( fz != pos.z ) SetOrigin((pos.x,pos.y,fz),true); if ( (waterlevel > 0) || GetFloorTerrain().isliquid ) A_FadeOut(); special1++; // start fading out after one whole minute if ( special1 > 2100 ) A_FadeOut(.01); } Default { RenderStyle 'Shaded'; StencilColor "FF 00 00"; } States { Spawn: XZW1 A -1; Stop; } }