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
//============================================================================= // 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
//============================================================================= // 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:It seems it'd be way easier to get away with doing this kind of thing in GZDoom's ZScript, isn't it?Code: Select all
//============================================================================= // 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' }