// Gore FX ported over from Soundless Mound, with some edits // Base blood actor Class mkBlood : Actor { Default { +NOBLOCKMAP; +NOGRAVITY; +NOTELEPORT; +PUFFGETSOWNER; } action 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); } 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 : Actor { double str; int cnt; Vector3 attachofs; double baseang; color shadecol; Default { +NOBLOCKMAP; +NOGRAVITY; +NOTELEPORT; +NOINTERACTION; } 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 ( isFrozen() ) return; Vector3 setofs; setofs = (cos(target.angle)*attachofs.x+sin(target.angle)*attachofs.y,sin(target.angle)*attachofs.x-cos(target.angle)*attachofs.y,attachofs.z); SetOrigin(level.Vec3Offset(target.pos,setofs),false); int sz = max(1,args[0]/2); double ang, pt; for ( int i=0; i 0) || GetFloorTerrain().isliquid ) { scale *= 1.005; A_FadeOut(.01); } return; // we don't need to update states when we're dead } else { FCheckPosition tm; // gravitational pull vel.z -= GetGravity(); // movement subdivision (these damn things are tiny int steps = 8; while ( (abs(vel.x) >= radius*steps) || (abs(vel.y) >= radius*steps) || (abs(vel.z) >= height*steps) ) steps++; bool domove = (vel!=(0,0,0))||((pos.z!=floorz)&&(pos.z!=ceilingz-height)); if ( domove ) { Vector3 steppy = vel/steps; int changexy = steppy.X||steppy.Y; for ( int i=0; i= l.backsector.ceilingplane.ZAtPoint(posr.xy) ) { Destroy(); return; } } // hit wall Vector3 dir = vel.unit(); TraceBleedAngle(20,atan2(dir.y,dir.x),asin(-dir.z)); A_StartSound("misc/blooddrop",volume:.1); Destroy(); return; } AddZ(steppy.z); UpdateWaterLevel(); if ( pos.z <= floorz ) { if ( floorpic == skyflatnum ) { Destroy(); return; } // landed on floor SetZ(floorz); HitFloor(); A_StartSound("misc/blooddrop",volume:.1); dead = true; SWWMUtility.SetToSlope(self,FRandom[Blood](0,360)); tracksector = FloorSector; trackplane = 0; F3DFloor ff; for ( int i=0; i ceilingz ) { if ( ceilingpic == skyflatnum ) { Destroy(); return; } // hit the ceiling // TODO stick to it and drop more blood A_StartSound("misc/blooddrop",volume:.1); Destroy(); return; } CheckPortalTransition(); } } 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(); SWWMGoreHandler.QueueBlod(self); int jumps = Random[Blood](0,3); state dest = ResolveState("Spawn"); SetState(dest+jumps); } override void OnDestroy() { SWWMGoreHandler.DeQueueBlod(self); Super.OnDestroy(); } States { Spawn: SBLD ABCD 2; Loop; } } // chunky salsa in the air Class mkBloodSmoke : Actor { Default { +NOBLOCKMAP; +NOGRAVITY; +NOINTERACTION; +NOTELEPORT; +FORCEXYBILLBOARD; Scale .5; Alpha .35; RenderStyle "Shaded"; } override void PostBeginPlay() { int jumps = Random[Blood](0,19); state dest = ResolveState("Spawn"); SetState(dest+jumps); } override void Tick() { if ( isFrozen() ) return; SetOrigin(level.Vec3Offset(pos,vel),true); UpdateWaterLevel(); if ( waterlevel > 0 ) A_FadeOut(); A_FadeOut(.03); A_SetScale(scale.x*1.02); if ( tics > 0 ) tics--; while ( !tics ) { if ( !SetState(CurState.NextState) ) return; } } States { Spawn: BSMK ABCDEFGHIJKLMNOPQRST 2; Loop; } } Class mkBloodSmoke2 : mkBloodSmoke { Default { Scale .8; Alpha 1.; } } // flying gibs // inspired by Lud's Universal Gibs Class mkFlyingGib : Actor { bool killme; int lastbleed; color shadecol; bool bleeding; double rollvel; mkFlyingGib prevmeat, nextmeat; override void PostBeginPlay() { Super.PostBeginPlay(); frame = Random[Blood](0,5); double ang = FRandom[Gibs](0,360); double pt = FRandom[Gibs](-60,20); Vector3 dir = (cos(pt)*cos(ang),cos(pt)*sin(ang),sin(-pt)); vel += dir*FRandom[Gibs](5.,20.); if ( master ) { vel += master.vel*1.5; CopyBloodColor(master); } rollvel = FRandom[Gibs](10,50)*RandomPick[Gibs](-1,1); 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); bleeding = true; if ( Random[Blood](0,1) ) bXFlip = true; SWWMGoreHandler.QueueMeat(self); } override void OnDestroy() { SWWMGoreHandler.DeQueueMeat(self); Super.OnDestroy(); } override void Tick() { Super.Tick(); if ( isFrozen() ) return; if ( killme ) A_FadeOut(.01); if ( CurState == ResolveState("Death2") ) { if ( vel.length() < .1 ) bleeding = false; return; } roll += rollvel; if ( waterlevel > 0 ) { rollvel *= .98; vel.xy *= .98; return; } if ( bleeding && !Random[Blood](0,3) ) { let s = Spawn("mkBloodSmoke",pos); s.SetShade(shadecol); } } override bool CanCollideWith(Actor other, bool passive) { if ( other == master ) return false; return true; } action void A_Bleed() { double ang; double pt; Vector3 dir; if ( invoker.lastbleed > level.maptime ) return; invoker.lastbleed = level.maptime+5; for ( int i=0; i<8; i++ ) { let b = Spawn("mkBloodDrop",pos); ang = FRandom[Gibs](0,360); pt = FRandom[Gibs](-60,30); dir = (cos(pt)*cos(ang),cos(pt)*sin(ang),sin(-pt)); b.vel = dir*FRandom[Gibs](0.3,2.2)*vel.length(); b.scale *= 2.0; b.SetShade(invoker.shadecol); } TraceBleedAngle(int(vel.length()),angle+180,-pitch); } Default { Radius 4; Height 4; Mass 10; Scale .65; Gravity .5; BounceType "Doom"; BounceFactor .6; +MISSILE; +DROPOFF; +NOBLOCKMAP; +USEBOUNCESTATE; +BOUNCEONCEILINGS; +MOVEWITHSECTOR; +THRUACTORS; +NOTELEPORT; +ROLLSPRITE; +ROLLCENTER; +INTERPOLATEANGLES; } States { Spawn: SGIB # 1; Wait; Bounce: SGIB # 0 { A_Bleed(); A_StartSound("misc/gibhit",CHAN_BODY,CHANF_OVERLAP); } Goto Spawn; Death: SGIB # 1 A_JumpIf(pos.z<=floorz,"Death2"); Wait; Death2: SGIB # -1 { A_StartSound("misc/gibhit",CHAN_BODY,CHANF_OVERLAP); roll = RandomPick[Gibs](0,180)+Random[Gibs](-5,5); A_Stop(); } Stop; } } // Manually added gibbing Class mkGibber : Actor { Actor Gibbed; int gibcount, gibsize; int delay; color shadecol; virtual void BurstGibs() { Actor a; double ang, pt; Vector3 dir; bool dummy; for ( int i=0; i 0 ) { delay--; return; } A_StartSound("misc/gibber"); BurstGibs(); if ( reactiontime <= 0 ) Destroy(); } Default { +NOBLOCKMAP; +NOGRAVITY; +NOTELEPORT; +DONTSPLASH; +NOINTERACTION; Radius 32; Height 16; } } // bare actors used for copying blood color to vanilla monsters Class GreenBloodReference : Actor { Default { BloodColor "Green"; } } Class BlueBloodReference : Actor { Default { BloodColor "Blue"; } } Class PurpleBloodReference : Actor { Default { BloodColor "Purple"; } } // 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; Destroy(); } } Class SWWMGoreHandler : EventHandler { mkBloodDrop blods, blods_end; int blods_cnt, oldmaxblood; mkFlyingGib meats, meats_end; int meats_cnt, oldmaxgibs; override void WorldLoaded( WorldEvent e ) { // recheck queues in case limits changed while ( blods && (blods_cnt > swwm_maxblood) ) DeQueueBlod(blods); while ( meats && (meats_cnt > swwm_maxgibs) ) DeQueueMeat(meats); } static void QueueBlod( mkBloodDrop b ) { let hnd = SWWMGoreHandler(EventHandler.Find("SWWMGoreHandler")); if ( !hnd ) return; hnd.blods_cnt++; if ( !hnd.blods ) { // this is the initial one hnd.blods = b; hnd.blods_end = b; } else { hnd.blods_end.nextblod = b; b.prevblod = hnd.blods_end; hnd.blods_end = b; } while ( hnd.blods && (swwm_maxblood >= 0) && (hnd.blods_cnt > swwm_maxblood) ) DeQueueBlod(hnd.blods); } static void DeQueueBlod( mkBloodDrop b ) { let hnd = SWWMGoreHandler(EventHandler.Find("SWWMGoreHandler")); if ( !hnd || !hnd.blods ) return; if ( (hnd.blods != b) && !b.prevblod && !b.nextblod ) return; hnd.blods_cnt--; if ( !b.prevblod ) hnd.blods = b.nextblod; else b.prevblod.nextblod = b.nextblod; if ( b == hnd.blods_end ) hnd.blods_end = b.prevblod; if ( b.nextblod ) b.nextblod.prevblod = b.prevblod; b.killme = true; b.prevblod = null; b.nextblod = null; } static void QueueMeat( mkFlyingGib m ) { let hnd = SWWMGoreHandler(EventHandler.Find("SWWMGoreHandler")); if ( !hnd ) return; hnd.meats_cnt++; if ( !hnd.meats ) { // this is the initial one hnd.meats = m; hnd.meats_end = m; } else { hnd.meats_end.nextmeat = m; m.prevmeat = hnd.meats_end; hnd.meats_end = m; } while ( hnd.meats && (swwm_maxgibs >= 0) && (hnd.meats_cnt > swwm_maxgibs) ) DeQueueMeat(hnd.meats); } static void DeQueueMeat( mkFlyingGib m ) { let hnd = SWWMGoreHandler(EventHandler.Find("SWWMGoreHandler")); if ( !hnd || !hnd.meats ) return; if ( (hnd.meats != m) && !m.prevmeat && !m.nextmeat ) return; hnd.meats_cnt--; if ( !m.prevmeat ) hnd.meats = m.nextmeat; else m.prevmeat.nextmeat = m.nextmeat; if ( m == hnd.meats_end ) hnd.meats_end = m.prevmeat; if ( m.nextmeat ) m.nextmeat.prevmeat = m.prevmeat; m.killme = true; m.prevmeat = null; m.nextmeat = null; } override void WorldTick() { if ( swwm_maxblood != oldmaxblood ) { while ( blods && (swwm_maxblood >= 0) && (blods_cnt > swwm_maxblood) ) DeQueueBlod(blods); } if ( swwm_maxgibs != oldmaxgibs ) { while ( meats && (swwm_maxgibs >= 0) && (meats_cnt > swwm_maxgibs) ) DeQueueMeat(meats); } oldmaxblood = swwm_maxblood; oldmaxgibs = swwm_maxgibs; } override void CheckReplacement( ReplaceEvent e ) { // only replace vanilla blood if no other gore mod is doing it if ( (e.Replacee == "Blood") && (!e.Replacement || e.Replacement == "Blood") && swwm_blood ) e.Replacement = "mkBlood"; } override void WorldThingSpawned( WorldEvent e ) { // vanilla blood color changes if ( (e.Thing.GetClass() == "BaronOfHell") || (e.Thing.GetClass() == "HellKnight") || (e.Thing.GetClass() == "Bishop") || (e.Thing.GetClass() == "Korax") ) { let gb = Actor.Spawn("GreenBloodReference"); e.Thing.CopyBloodColor(gb); gb.Destroy(); } else if ( e.Thing.GetClass() == "Cacodemon" ) { let bb = Actor.Spawn("BlueBloodReference"); e.Thing.CopyBloodColor(bb); bb.Destroy(); } else if ( (e.Thing.GetClass() == "Wizard") || (e.Thing.GetClass() == "Heresiarch") || (e.Thing.GetClass() == "Sorcerer2") ) { let pb = Actor.Spawn("PurpleBloodReference"); e.Thing.CopyBloodColor(pb); pb.Destroy(); } } override void WorldThingDamaged( WorldEvent e ) { if ( e.Thing.Health > 0 ) return; // no gib if it was erased if ( e.DamageType == 'Ynykron' ) return; // only do special handling if they use our blood if ( (e.Thing.GetBloodType(0) != "mkBlood") || e.Thing.bNOBLOOD ) return; CorpseFallTracker.TrackBody(e.Thing); bool b; Actor a; // special handling of some monsters if ( e.Thing.GetClass() == "Cyberdemon" ) { [b,a] = e.Thing.A_SpawnItemEx("mkGibber",flags:SXF_USEBLOODCOLOR); if ( !b ) return; mkGibber(a).gibbed = e.Thing; mkGibber(a).delay = 40; a.A_SetSize(e.Thing.default.radius,e.Thing.default.height); return; } else if ( e.Thing.GetClass() == "SpiderMastermind" ) { [b,a] = e.Thing.A_SpawnItemEx("mkGibber",flags:SXF_USEBLOODCOLOR); if ( !b ) return; mkGibber(a).gibbed = e.Thing; mkGibber(a).delay = 60; a.A_SetSize(e.Thing.default.radius,e.Thing.default.height); return; } // override gibbing int gibhealth = e.Thing.GetGibHealth(); if ( !e.Thing.bDONTGIB && (e.Thing.FindState("XDeath",true) || e.Thing.FindState("Death.Extreme",true)) && ((e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageSource && e.DamageSource.bEXTREMEDEATH) || (e.DamageType == 'Extreme') || (e.Thing.Health < gibhealth)) && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) && (!e.DamageSource || !e.DamageSource.bNOEXTREMEDEATH) ) { [b,a] = e.Thing.A_SpawnItemEx("mkGibber",flags:SXF_USEBLOODCOLOR); if ( !b ) return; mkGibber(a).gibbed = e.Thing; a.A_SetSize(e.Thing.default.radius,e.Thing.default.height); } } }