- New fun options implemented (omnibusting, unlimited fuel, party time) - Biospark Carbine gets a requested nerf - Candygun combo fire has been buffed (watch out for that splash damage) - All powerup effects are additive (stacc 'em) - Automap hud respects gzdoom's cvars for toggling certain elements - Automap hud shows stats and times in gold when 100% / under par - Weapons and ammo are no longer affected by skill amount modifiers, for balance (and to avoid any weird glitches) - Sorting improvements for menu (weapons by slot, ammo by weapons, other items by value, etc.) - Grilled Cheese Sandwich now saves you from lethal falls properly - Blown kisses instakill nazis - Added non-violent Keen replacement (based on "Less mean-spirited Keen replacement" by SiFi270) - Added gib deaths for hell nobles, pinkies, cacos, revs and viles (sprites by Amuscaria and Ryan Cordell) - Blown kisses can activate use switches - Gestures can be chained by pressing a gesture button while another is playing - Fixed Grilled Cheese Sandwich not avoiding telefrags properly (now also works with voodoo dolls) - More precise weapon kill tracking (fixes some ragekit quirks) - Merge both DLC weaponsets, removing redundant weapons (see FuturePlans.md) - Discarded some collectables for the next updates, to save time - Preparation work for collectables update, including some (partial) lore files - Remove ammo fabricators from store, makes no sense to have them when you can just buy ammo directly - Cosmetic Boss Brain sprite replacements, just for fun - 10 more intermission tips, because yes - Added option to reduce distance at which enemy healthbars are picked - Various minor bugfixes and adjustments (and also some tiny typo fixes) - Ragekit now heals over time and with each hit (so it's more rewarding to go wild) - PNG optimization pass (again lol) - Fix crouched gestures having no facial animation
898 lines
21 KiB
Text
898 lines
21 KiB
Text
// 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);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
if ( !CheckNoDelay() || (tics == -1) ) return;
|
|
if ( tics > 0 ) tics--;
|
|
while ( !tics )
|
|
{
|
|
if ( !SetState(CurState.NextState) )
|
|
return;
|
|
}
|
|
}
|
|
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<sz; i++ )
|
|
{
|
|
let d = Spawn("mkBloodDrop",pos);
|
|
d.SetShade(fillcolor);
|
|
d.CopyBloodColor(self);
|
|
ang = baseang+target.angle+FRandom[Blood](-15.,15.)*str;
|
|
pt = pitch+FRandom[Blood](-15.,15.)*str;
|
|
Vector3 dir = (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt));
|
|
d.vel = dir*str*FRandom[Blood](.8,1.8);
|
|
d.scale *= str*.15*FRandom[Blood](.6,1.4);
|
|
}
|
|
bool flying = IsTargetFlying();
|
|
if ( !flying ) str *= 1.-(.3/sz);
|
|
if ( (str <= .05) || ((cnt-- <= 0) && !flying) ) Destroy();
|
|
}
|
|
}
|
|
|
|
// drop of salsa
|
|
// becomes a decal on crash
|
|
Class mkBloodDrop : Actor
|
|
{
|
|
bool killme;
|
|
bool dead;
|
|
mkBloodDrop prevblod, nextblod;
|
|
Sector tracksector;
|
|
int trackplane;
|
|
|
|
Default
|
|
{
|
|
+MISSILE;
|
|
+NOBLOCKMAP;
|
|
+DROPOFF;
|
|
+NOTELEPORT;
|
|
+THRUACTORS;
|
|
+FORCEXYBILLBOARD;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+NOINTERACTION;
|
|
Scale .35;
|
|
Radius 2;
|
|
Height 2;
|
|
Mass 1;
|
|
RenderStyle "Shaded";
|
|
}
|
|
// try to reduce overhead as much as possible with this
|
|
override void Tick()
|
|
{
|
|
prev = pos; // for interpolation
|
|
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);
|
|
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);
|
|
}
|
|
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<steps; i++ )
|
|
{
|
|
if ( changexy && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) )
|
|
{
|
|
let l = tm.CeilingLine;
|
|
if ( l && l.backsector && l.backsector.GetTexture(1) == skyflatnum )
|
|
{
|
|
let posr = PosRelative(l.backsector);
|
|
if ( pos.z >= 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<FloorSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
|
|
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
|
|
ff = FloorSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff )
|
|
{
|
|
tracksector = ff.model;
|
|
trackplane = 1;
|
|
}
|
|
frame = Random[Blood](5,8);
|
|
return;
|
|
}
|
|
if ( pos.z+height > 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<gibsize; i++ )
|
|
{
|
|
a = Spawn("mkBloodSmoke2",pos+(FRandom[Gibs](-.8,.8)*radius,FRandom[Gibs](-.8,.8)*radius,FRandom[Gibs](0.,.9)*height));
|
|
a.SetShade(shadecol);
|
|
}
|
|
for ( int i=0; i<2*gibsize; i++ )
|
|
{
|
|
[dummy, a] = A_SpawnItemEx("mkFlyingGib",FRandom[Gibs](-.5,.5)*radius,FRandom[Gibs](-.5,.5)*radius,FRandom[Gibs](.1,.9)*height,flags:SXF_ABSOLUTEANGLE|SXF_USEBLOODCOLOR);
|
|
a.translation = translation;
|
|
a.CopyBloodColor(self);
|
|
a.scale *= scale.x;
|
|
a.master = gibbed;
|
|
}
|
|
for ( int i=0; i<3*gibsize; i++ )
|
|
{
|
|
[dummy, a] = A_SpawnItemEx("mkBloodDrop",FRandom[Gibs](-.8,.8)*radius,FRandom[Gibs](-.8,.8)*radius,FRandom[Gibs](0.,.9)*height,flags:SXF_ABSOLUTEANGLE|SXF_USEBLOODCOLOR);
|
|
ang = FRandom[Gibs](0,360);
|
|
pt = FRandom[Gibs](-90,90);
|
|
dir = (cos(pt)*cos(ang),cos(pt)*sin(ang),sin(-pt));
|
|
a.vel = dir*FRandom[Gibs](8.,24.);
|
|
a.scale *= 2.+FRandom[Gibs](.3,1.6);
|
|
a.SetShade(shadecol);
|
|
a.CopyBloodColor(self);
|
|
}
|
|
reactiontime--;
|
|
}
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
gibsize = int(min(max(radius,height)/15,6));
|
|
reactiontime = int(min(max(radius,height)/10,8));
|
|
if ( gibbed && gibbed.bloodcolor ) shadecol = Color(gibbed.bloodcolor.r/2,gibbed.bloodcolor.g/2,gibbed.bloodcolor.b/2);
|
|
else shadecol = Color(80,0,0);
|
|
if ( gibbed ) CopyBloodColor(gibbed);
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
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;
|
|
}
|
|
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";
|
|
}
|
|
}
|
|
|
|
// bare actor used for extra gib deaths
|
|
Class ExtraGibDeaths : Actor
|
|
{
|
|
StateLabel gibstate;
|
|
|
|
static void GibThis( Actor a, Statelabel st )
|
|
{
|
|
if ( !a ) return;
|
|
let g = ExtraGibDeaths(Spawn("ExtraGibDeaths"));
|
|
g.master = a;
|
|
g.gibstate = st;
|
|
}
|
|
|
|
void A_DoGib()
|
|
{
|
|
if ( !master ) return;
|
|
master.SetState(FindState(gibstate));
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1 NoDelay;
|
|
TNT1 A 1 A_DoGib();
|
|
Stop;
|
|
DemonXDeath:
|
|
SARX A 5;
|
|
SARX B 5 A_XScream();
|
|
SARX C 5 A_NoBlocking();
|
|
SARX DEF 5;
|
|
SARX G -1;
|
|
Stop;
|
|
KnightXDeath:
|
|
BO2X A 5;
|
|
BO2X B 5 A_XScream();
|
|
BO2X C 5;
|
|
BO2X D 5 A_NoBlocking();
|
|
BO2X EFGH 5;
|
|
BO2X I -1;
|
|
Stop;
|
|
BaronXDeath:
|
|
BOSX A 5;
|
|
BOSX B 5 A_XScream();
|
|
BOSX C 5;
|
|
BOSX D 5 A_NoBlocking();
|
|
BOSX EFGH 5;
|
|
BOSX I -1 A_BossDeath();
|
|
Stop;
|
|
CacoXDeath:
|
|
CACX A 5;
|
|
CACX B 5 A_XScream();
|
|
CACX C 5 A_NoBlocking();
|
|
CACX D 4;
|
|
CACX E 3;
|
|
CACX F 4;
|
|
CACX G 5;
|
|
CACX H -1;
|
|
Stop;
|
|
BonerXDeath:
|
|
REVX A 3;
|
|
REVX B 4 A_XScream();
|
|
REVX C 5 A_NoBlocking();
|
|
REVX DE 5;
|
|
REVX F -1;
|
|
Stop;
|
|
VileXDeath:
|
|
VILX A 5;
|
|
VILX B 5 A_XScream();
|
|
VILX C 5 A_NoBlocking();
|
|
VILX DEF 5;
|
|
VILX G -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
else if ( e.Thing.GetClass() == "LostSoul" )
|
|
e.Thing.bNOBLOOD = true;
|
|
}
|
|
|
|
override void WorldThingDied( WorldEvent e )
|
|
{
|
|
// force insert gib animations on some vanilla Doom monsters
|
|
int gibhealth = e.Thing.GetGibHealth();
|
|
bool gotgibbed = (!e.Thing.bDONTGIB && ((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));
|
|
if ( !gotgibbed ) return;
|
|
if ( (e.Thing.GetClass() == "Demon") || (e.Thing.GetClass() == "Spectre") )
|
|
ExtraGibDeaths.GibThis(e.Thing,"DemonXDeath");
|
|
else if ( e.Thing.GetClass() == "HellKnight" )
|
|
ExtraGibDeaths.GibThis(e.Thing,"KnightXDeath");
|
|
else if ( e.Thing.GetClass() == "BaronOfHell" )
|
|
ExtraGibDeaths.GibThis(e.Thing,"BaronXDeath");
|
|
else if ( e.Thing.GetClass() == "Cacodemon" )
|
|
ExtraGibDeaths.GibThis(e.Thing,"CacoXDeath");
|
|
else if ( e.Thing.GetClass() == "Revenant" )
|
|
ExtraGibDeaths.GibThis(e.Thing,"BonerXDeath");
|
|
else if ( e.Thing.GetClass() == "Archvile" )
|
|
ExtraGibDeaths.GibThis(e.Thing,"VileXDeath");
|
|
}
|
|
|
|
override void WorldThingDamaged( WorldEvent e )
|
|
{
|
|
if ( e.Thing.Health > 0 ) return;
|
|
// no gib if it was erased
|
|
if ( e.DamageType == 'Ynykron' ) return;
|
|
int gibhealth = e.Thing.GetGibHealth();
|
|
bool gotgibbed = (!e.Thing.bDONTGIB && ((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));
|
|
bool forcegibbed = false;
|
|
// force gib availability for some vanilla Doom monsters
|
|
if ( gotgibbed && ((e.Thing.GetClass() == "Demon") || (e.Thing.GetClass() == "Spectre") || (e.Thing.GetClass() == "HellKnight") || (e.Thing.GetClass() == "BaronOfHell") || (e.Thing.GetClass() == "Cacodemon") || (e.Thing.GetClass() == "Revenant") || (e.Thing.GetClass() == "Archvile")) )
|
|
forcegibbed = true;
|
|
if ( !e.Thing.FindState("XDeath",true) && !e.Thing.FindState("Death.Extreme",true) && !forcegibbed )
|
|
gotgibbed = false;
|
|
// 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
|
|
if ( gotgibbed )
|
|
{
|
|
[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);
|
|
}
|
|
}
|
|
}
|