So for context, I'm making a Mutator for Unreal Tournament (1999) that changes all the blood sprites to be STY_Modulated based, in addition, prevent the triggering of decap death animations, remove gibs, and make all Carcasses completely impervious to being gibbed.
This so far has required me to make four different classes just to accomplish such a simple (to a end user) task.
The most important code is shared here: (Spoiler tags because I don't want to clutter the post)
Spoiler:
- Code: Select all • Expand view
//=============================================================================
// RVMute
// Realistic Violence
// A mutator to disable decapiations, gibbings, and changes the style of blood effects.
//=============================================================================
class RVMute expands Mutator;
var private bool bInitOnceOnly;
var private const editconst class<NoGibCarcass> NoGibCarcassClass; // Hack to force load this class into level.
var private transient Pawn LatentKilled, LatentKiller; // Temporary storage of information for deferred Died call.
var private transient name LatentdamageType;
var private transient vector LatentHitLocation;
function PreBeginPlay()
{
Disable('Tick');
}
function PostBeginPlay()
{
if ( !bInitOnceOnly )
{
bInitOnceOnly = True;
Disable('Tick');
Level.Game.RegisterDamageMutator(self);
Level.Spawn(Class'RealisticViolence.RVCarcassNotify'); // Primarily prevents any gibs from existing.
Level.Spawn(Class'RealisticViolence.RVBloodNotify'); // Makes blood modulated.
}
}
function Tick( float DeltaTime )
{
if( LatentKilled != None ) // Latent death handling.
{
if( (LatentKiller != None) && LatentKiller != LatentKilled )
{
if (Level.Game.LocalLog != None) // Do the headshot code here.
Level.Game.LocalLog.LogSpecialEvent("headshot", LatentKiller.PlayerReplicationInfo.PlayerID, LatentKilled.PlayerReplicationInfo.PlayerID);
if (Level.Game.WorldLog != None)
Level.Game.WorldLog.LogSpecialEvent("headshot", LatentKiller.PlayerReplicationInfo.PlayerID, LatentKilled.PlayerReplicationInfo.PlayerID);
LatentKiller.ReceiveLocalizedMessage( Class'BotPack.DecapitationMessage' );
}
LatentKilled.Health = 0; // Perform the deferred kill.
LatentKilled.Died(LatentKiller, LatentdamageType, LatentHitLocation);
LatentKilled = None; // Clear all latent variables.
LatentKiller = None;
LatentdamageType = '';
LatentHitLocation = vect(0.000000,0.000000,0.000000);
}
Disable('Tick');
}
function bool PreventDeath(Pawn Killed, Pawn Killer, name damageType, vector HitLocation)
{
local vector TelefragMomentum;
if( Killed == None )
return false;
if ( (NextMutator == None) || !NextMutator.PreventDeath(Killed, Killer, damageType, HitLocation) )
{
if ( Killed.Health <= -1000 && damageType == 'Gibbed' && HitLocation == Killed.Location ) // Check for telefrag.
{
Killed.Health = 0;
TelefragMomentum = VRand()*2500.0;
if ( TelefragMomentum.Z < 0.000000 )
TelefragMomentum.Z = -TelefragMomentum.Z; // Only positive Z allowed.
Killed.AddVelocity( TelefragMomentum ); // Hurl the telefragged person comically out of the way.
return false;
}
if ( damageType == 'Decapitated' ) // Intercept and prevent decap death.
{
if( HitLocation.Z - Killed.Location.Z > 0.7 * Killed.CollisionHeight )
HitLocation.Z -= Killed.CollisionHeight-(0.7 * Killed.CollisionHeight)+1;
LatentHitLocation = HitLocation;
if( (Killer != None) && Killer.Weapon != None )
{
LatentdamageType = Killer.Weapon.MyDamageType; // Try to get a damageType substitute.
if( LatentdamageType == '' )
LatentdamageType = Killer.Weapon.AltDamageType;
}
if( LatentdamageType == '' )
LatentdamageType = 'shot'; // Fallback
Killed.SetCollision( false ); // Turn actor collision off;
LatentKilled = Killed; // Save who died and who killed who.
LatentKiller = Killer;
Enable('Tick'); // Kill after next Tick.
return true; // Intercept the death.
}
if ( Killed.Health < 0 ); // Do not allow health below 0.
Killed.Health = 0;
}
return false;
}
function bool IsRelevant(Actor Other, out byte bSuperRelevant)
{
if( Other.IsA('Pawn') ) // Replace CarcassType with NoGibCarcass universally.
Other.SetPropertyText( "CarcassType", "RealisticViolence.NoGibCarcass" );
return Super(Mutator).IsRelevant(Other, bSuperRelevant);
}
function MutatorTakeDamage( out int ActualDamage, Pawn Victim, Pawn InstigatedBy, out Vector HitLocation,
out Vector Momentum, name DamageType)
{
if( Victim != None )
{
if( Victim.Health <= 0 || (LatentKilled != None && LatentKilled == Victim) )
ActualDamage = 0; // Don't allow damage if health is already basically nothing.
if( HitLocation.Z - Victim.Location.Z > 0.7 * Victim.CollisionHeight ) // Prevent triggering decap animations.
HitLocation.Z -= Victim.CollisionHeight-(0.7 * Victim.CollisionHeight)+1;
}
if ( NextDamageMutator != None )
NextDamageMutator.MutatorTakeDamage( ActualDamage, Victim, InstigatedBy, HitLocation, Momentum, DamageType );
}
defaultproperties
{
bInitOnceOnly=False
NoGibCarcassClass=Class'RealisticViolence.NoGibCarcass'
}
Spoiler:
- Code: Select all • Expand view
//=============================================================================
// NoGibCarcass.
//=============================================================================
class NoGibCarcass extends UTHumanCarcass;
var float LastHit;
var bool bJerking;
var name Jerks[4];
replication
{
// Things the server should send to the client.
unreliable if( Role==ROLE_Authority )
LastHit, bJerking;
}
function GibSound();
function CreateReplacement();
function SpawnHead();
function Initfor(actor Other)
{
Super.Initfor(Other);
if( (Mesh == LodMesh'BotPack.FCommando' || Mesh == LodMesh'Botpack.SGirl')
&& AnimSequence == 'Dead6' )
AnimSequence = 'Dead3';
if( (Mesh == LodMesh'BotPack.Commando' || Mesh == LodMesh'Botpack.Soldier')
&& AnimSequence == 'Dead4' )
AnimSequence = 'Dead7';
}
function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation,
Vector Momentum, name DamageType)
{
local UT_BloodBurst b;
if ( bJerking || (AnimSequence == 'Dead9') )
{
bJerking = true;
if ( Damage < 23 )
LastHit = Level.TimeSeconds;
else
bJerking = false;
}
b = Spawn(class'UT_BloodHit',,,HitLocation, rotator(Momentum));
if ( bGreenBlood )
b.GreenBlood();
if ( !bPermanent )
{
if ( (DamageType == 'Corroded') && (Damage >= 100) )
{
bCorroding = true;
GotoState('Corroding');
}
else
{
if ( !bDecorative )
{
bBobbing = false;
SetPhysics(PHYS_Falling);
}
if ( (Physics == PHYS_None) && (Momentum.Z < 0) )
Momentum.Z *= -1;
Velocity += 3 * momentum/(Mass + 200);
if ( bDecorative )
Velocity = vect(0,0,0);
}
}
if ( bJerking )
{
CumulativeDamage = 50;
Velocity.Z = FMax(Velocity.Z, 40);
if ( InstigatedBy == None )
{
bJerking = false;
PlayAnim('Dead9B', 1.1, 0.1);
}
}
if ( bJerking && (VSize(InstigatedBy.Location - Location) < 150)
&& (InstigatedBy.Acceleration != vect(0,0,0))
&& ((Normal(InstigatedBy.Velocity) Dot Normal(Location - InstigatedBy.Location)) > 0.7) )
{
bJerking = false;
PlayAnim('Dead9B', 1.1, 0.1);
}
}
function ChunkUp(int Damage);
simulated function Landed(vector HitNormal)
{
local rotator finalRot;
local float OldHeight;
finalRot = Rotation;
finalRot.Roll = 0;
finalRot.Pitch = 0;
setRotation(finalRot);
SetPhysics(PHYS_None);
SetCollision(bCollideActors, false, false);
if ( HitNormal.Z < 0.99 )
ReducedHeightFactor = 0.1;
if ( HitNormal.Z < 0.93 )
ReducedHeightFactor = 0.0;
if ( !IsAnimating() )
LieStill();
if ( Level.NetMode == NM_DedicatedServer )
return;
if ( Pool == None )
Pool = Spawn(class'UTBloodPool2',,,Location, rotator(HitNormal));
else
Spawn(class'BloodSplat',,,Location, rotator(HitNormal + 0.5 * VRand()));
}
function AnimEnd()
{
local name NewAnim;
if ( AnimSequence == 'Dead9' )
bJerking = true;
if ( !bJerking )
Super.AnimEnd();
else if ( (Level.TimeSeconds - LastHit < 0.2) && (FRand() > 0.02) )
{
NewAnim = Jerks[Rand(4)];
if ( NewAnim == AnimSequence )
{
if ( NewAnim == Jerks[0] )
NewAnim = Jerks[1];
else
NewAnim = Jerks[0];
}
TweenAnim(NewAnim, 0.15);
}
else
{
bJerking = false;
PlayAnim('Dead9B', 1.1, 0.1);
}
}
defaultproperties
{
Jerks(0)=GutHit
Jerks(1)=HeadHit
Jerks(2)=LeftHit
Jerks(3)=RightHit
MasterReplacement=Class'Botpack.TMaleMasterChunk'
AnimSequence=Dead1
AnimFrame=0.000000
bBlockActors=True
bBlockPlayers=True
Mass=100.000000
}
Spoiler:
- Code: Select all • Expand view
//=============================================================================
// RVBloodNotify
// Changes the style of blood to STY_Modulated complete with new sprites, and reduces the sizes.
//=============================================================================
class RVBloodNotify expands SpawnNotify;
// Modulated blood droplets import.
#exec TEXTURE IMPORT FILE=Textures\MBD10.pcx GROUP=Blood MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBD3.pcx GROUP=Blood MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBD4.pcx GROUP=Blood MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBD6.pcx GROUP=Blood MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBD9.pcx GROUP=Blood MIPS=OFF
// Modulated blood puffs import.
#exec TEXTURE IMPORT FILE=Textures\MBp6_A00.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBp6_A01.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBp6_A02.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBp6_A03.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBp6_A04.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBp6_A05.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBp6_A06.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\MBp6_A07.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A00.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A01.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A02.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A03.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A04.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A05.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A06.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A07.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A08.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp8_A09.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A00.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A01.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A02.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A03.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A04.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A05.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A06.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A07.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A08.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A09.pcx GROUP=BloodyPuff MIPS=OFF
#exec TEXTURE IMPORT FILE=Textures\Mbp_A10.pcx GROUP=BloodyPuff MIPS=OFF
simulated function Actor SpawnNotification(Actor A)
{
local Actor Replacement;
if( A.IsA('Blood2') ) // Forcefully replace UnrealI Blood when encountered.
{
A.bHidden = True;
A.Style = STY_None;
A.DrawType = DT_None;
A.Mesh = None;
Replacement = Spawn(Class'BotPack.UT_BloodHit',A.Owner,,A.Location,A.Rotation);
A.Destroy();
return Replacement;
}
else if( A.IsA('UT_Blood2') ) // Make blood modulated, and slightly reduce size.
{
A.Style = STY_Modulated;
A.Texture = Texture'RealisticViolence.Blood.MBD3';
A.DrawScale = 0.100000;
A.AmbientGlow = 0;
A.MultiSkins[0] = Texture'RealisticViolence.Blood.MBD3';
A.MultiSkins[1] = Texture'RealisticViolence.Blood.MBD4';
A.MultiSkins[2] = Texture'RealisticViolence.Blood.MBD6';
A.MultiSkins[3] = Texture'RealisticViolence.Blood.MBD9';
A.MultiSkins[4] = Texture'RealisticViolence.Blood.MBD10';
A.MultiSkins[5] = Texture'RealisticViolence.Blood.MBD3';
A.MultiSkins[6] = Texture'RealisticViolence.Blood.MBD4';
A.MultiSkins[7] = Texture'RealisticViolence.Blood.MBD6';
}
if( A.IsA('BloodPuff') ) // Forcefully replace UnrealI Blood when encountered.
{
A.bHidden = True;
A.Style = STY_None;
A.DrawType = DT_None;
A.Mesh = None;
Replacement = Spawn(Class'BotPack.UT_BloodPuff',A.Owner,,A.Location,A.Rotation);
A.Destroy();
return Replacement;
}
else if( A.IsA('UT_BloodPuff') ) // Make blood modulated, and slightly reduce size.
{
UT_SpriteSmokePuff(A).SSprites[0] = Texture'RealisticViolence.BloodyPuff.Mbp_A00';
UT_SpriteSmokePuff(A).SSprites[1] = Texture'RealisticViolence.BloodyPuff.Mbp8_A00';
UT_SpriteSmokePuff(A).SSprites[2] = Texture'RealisticViolence.BloodyPuff.MBp6_A00';
A.DrawScale = 0.500000;
A.Style = STY_Modulated;
A.Texture = Texture'RealisticViolence.BloodyPuff.Mbp_A00';
A.ScaleGlow = 1.000000;
}
return A;
}
defaultproperties
{
ActorClass=Class'Engine.Effects'
}
It seems it'd be way easier to get away with doing this kind of thing in GZDoom's ZScript, isn't it?