rott/kf_sources/GoodKarma/Classes/NetKActor.uc
2026-07-14 20:27:09 +07:00

741 lines
23 KiB
Ucode

//NetKActor as part of the GoodKarma package
//Build 134 Beta 4.5 Release
//By: Jonathan Zepp
//Original code based of the LawDogsKActor code available from unreal wiki (Thank You!)
//Though the current version has been SEVERELY modified, and modified some more.
//This is intended to add full server playability, and make karma a universal tool.
//Changes to bot pathing have also been made to put some sense into their empty heads...
class NetKActor extends KActor
placeable;
#exec OBJ LOAD FILE="..\Sounds\WeaponSounds.uax"
//Edit in default properties in UnrealEd
var() bool bBlockedPath ; //Enables Basic bot-pathing
var() bool bCriticalObject ; //Will Always function despite server physics detail (not fully functional)
var() float moveDistance ; //associated with bBlockedPath, how far the object must move for the bots to pass it
var() array<Sound> HitSounds ; //A sound to play on impact
var() byte RelativeImpactVolume ; //The relative sound volume of the hit sound
var() bool bShoveable ; //Can the object be shoved by a player?
var() name BlockedPath ; //The name of the associated blocked path
var() float ShoveModifier; //A multiplier to change how hard the object is shoved
var() bool bPuntable ; //Allows a NetKActor to be punted
var() int requiredPuntStrength ; //A value which must be met by the actor calling the DeclarePunt function for a punt to be accepted
var() bool bEnableSpinProtection ; //Overrides standard karma features to make shoving the object always work properly, not reccomended on all objects, especially long ones, unless they have trouble.
var() float hitSoundRepeatModifier ; //How much more often will hit sounds play?
var(Damage) bool bCollisionDamage ; //Enables damage when colliding with a player/vehicle/thing with health
var(Damage) float DamageSpeed ; //Minimum speed to deal damage
var(Damage) class<DamageType> HitDamageType ; //Damage type associated with bCollisionDamage
var(Damage) float HitDamageScale ; //Relative impact damage modifier
var(Damage) int initialHealth ; //The starting health of an object
var(Damage) int damageAbsorbtionAmount ; //Absorbs this much damage off each hit.
var(Damage) bool limitDamagingClass ; //Only allows the constrained class to deal damage (and push it around)
var(Damage) class<Weapon> DamageConstraintClass ; //The constraint class (only allowed class to damage it)
var(Destruction) int explodeDamage ; //When dieing, how much damage does it deal to its fragments/people near it
var(Destruction) float explodeRadius ; //How close does something have to be to be damaged when dieing
var(Destruction) int explodeForce ; //How hard does it push fragments and other objects when dieing
var(Destruction) bool bDestroyable ; //Does health and damage apply to this object?
var(Destruction) sound DestructionSound ; //Sound played when the object is destroyed
var(Destruction) byte DestructionVolume ; //Volume of the destruction sound
var(Destruction) class <Emitter> DestroyedEffect ; //The effect created when the object is destroyed
var(Destruction) vector EffectOffset ; //Where the effect is in relation to the object when destroyed
var(Destruction) bool bOrientDestructionEffect ; //Does the destruction effect have the same orientation as the hit which killed it?
var(Destruction) float DestroyedMass ; //What the mass is when destroyed
var(Destruction) float DestroyedDamping ; //What the damping is when destroyed
var(Destruction) float DestroyedFriction ; //What the friction is when destroyed
var(Destruction) float DestroyedBuoyancy ; //What the buoyancy is when destroyed
var(Destruction) float DestroyedRestitution ; //What the restitution is when destroyed
var(Destruction) StaticMesh DestroyedStaticMesh ; //The static mesh used for the object after it is destroyed (anything but none enables the 5 destroyed karma changing values)
var(Respawning) float PreRespawnEffectTime ; //How long before the object respawns will the respawn effect play?
var(Respawning) bool bRespawnEffect ; //Will a respawn effect play?
var(Respawning) class<emitter> RespawnEffect ; //The effect spawned when the object respawns
var(Respawning) bool bInactivityReset ; //Will the object reset automatically if moved and then idle?
var(Respawning) float inactivityTimeout ; //How long of idleness will trigger an inactivity Reset?
var(Respawning) sound RespawnSound ; //Sound played when the object is respawned
var(Respawning) byte RespawnVolume ; //Volume of the respawning sound
var(Respawning) float AutoRespawnTime ; //How long after it is destroyed does it wait to respawn on its own (-1 means it dosen't respawn)
//Internal
var vector startLocation ;
var KRigidBodyState KState, KRepState, initialKState;
var bool bPendingPunt, bPendingRespawn, bRespawnEffectPlayed, bInactive, bWaitTillMove, notifyPlayHit, bDead ;
var int StateCount, LastStateCount, Health ;
var actor Punter ;
var float PuntTimeout, respawnClock, inactiveClock, lastShoveTimer, replaySoundClock ;
var rotator startRotation, hitDirection ;
var float initialRest, initialFric, initialMass, initialDampLin, initialDampAng, initialBuoy ;
var StaticMesh initialMesh ;
var byte clientPlay ; //0 for nothing, 1 for death emitters, 2 for respawn emitter
// shadow variables
var Projector Shadow;
var ShadowProjector PlayerShadow;
var globalconfig bool bBlobShadow;
var () bool bKActorShadows;
// ZOMG paint stains!
/*
var (Stains) bool bLeaveStains;
var (Stains) float StainStreakInterval;
var transient float LastStreakTime;
var (Stains) float StainSpeedThreshold;
*/
//var Actor LastTouchedActor;
var vector DieVect;
var vector lasthitlocation;
var pawn lastinstigatedby,LastStander;
replication
{
reliable if(Role == ROLE_Authority)
KRepState, StateCount, bDead, Health, hitDirection, clientPlay ;
reliable if(Role == ROLE_Authority)
clientDeathEmitters, clientRespawnEmitter ,DestroyedEffect;
reliable if(bNetInitial)
startRotation, startLocation ;
}
function PreBeginPlay()
{
startLocation = Location ;
startRotation = Rotation ;
KGetRigidBodyState(initialKState) ;
if(!bCriticalObject && Level.PhysicsDetailLevel == PDL_Low)
SetPhysics(PHYS_None) ;
initialRest = KParams.KRestitution ;
initialFric = KParams.KFriction ;
initialMass = (KarmaParams(KParams)).KMass ;
initialDampLin = (KarmaParams(KParams)).KLinearDamping ;
initialDampAng = (KarmaParams(KParams)).KAngularDamping ;
initialBuoy = (KarmaParams(KParams)).KBuoyancy ;
initialMesh = getObject(StaticMesh) ;
respawn() ;
}
simulated function PostNetBeginPlay()
{
if ( bKActorShadows == true)
{
PlayerShadow = Spawn(class'ShadowProjector',Self,'',Location);
PlayerShadow.ShadowActor = self;
PlayerShadow.bBlobShadow = bBlobShadow;
PlayerShadow.LightDirection = Normal(vect(1,1,3));
PlayerShadow.LightDistance = 320;
PlayerShadow.MaxTraceDistance = 350;
PlayerShadow.InitShadow();
PlayerShadow.bShadowActive = true;
}
// hardcode destruction effect to Material
/*
if (SurfaceType == EST_Metal)
{
DestroyedEffect = class'DoorExplodeMetalStandard';
DestructionSound = Sound'PatchSounds.MetalCrash';
}
else
if (SurfaceType == EST_Wood)
DestroyedEffect = class'DoorExplodeWoodStandard';
else
DestroyedEffect = none;
*/
}
function respawn()
{
setPhysics(PHYS_None) ;
setLocation(StartLocation) ;
setRotation(startRotation) ;
SetStaticMesh(initialMesh) ;
SetPhysics(PHYS_Karma) ;
SetCollision(true, true, true) ;
KSetMass(initialMass) ;
KSetRestitution(initialRest) ;
KSetDampingProps(initialDampLin, initialDampAng) ;
KSetFriction(initialFric) ;
(KarmaParams(KParams)).KBuoyancy = initialBuoy ;
KRepState = initialKState ;
Health = initialHealth ;
bBlockedPath = default.bBlockedPath ;
bPendingPunt = false ;
bHidden = false ;
bPendingRespawn = false ;
if(bRespawnEffect && !bRespawnEffectPlayed)
clientPlay = 2 ;
bRespawnEffectPlayed = false ;
bWaitTillMove = true ;
inactiveClock = -999.0 ;
bDead = false ;
lastShoveTimer = 0.0 ;
if(!((KarmaParams(KParams)).KStartEnabled))
{
KRepState.linVel = KRBVecFromVector(Vect(0,0,0)) ;
KRepState.angVel = KRBVecFromVector(Vect(0,0,0)) ;
}
lastStateCount = StateCount ;
//DEBUG
//log("Respawn at "$Location$" Karma: "$KRBVecToVector(KRepState.Position)) ;
//log(tag$" respawned at "$location$" and should be at "$startLocation) ;
}
function bool declarePunt(Actor newPunter, float maxTimeTo, bool bPriority, int Strength)
{
if(!bPuntable)
return false ;
if(((Punter != none && PuntTimeout <= level.timeSeconds) || Punter == none || bPriority) && Strength > RequiredPuntStrength)
{
Punter = newPunter ;
PuntTimeout = level.timeSeconds + maxTimeTo ;
bInactive = false ;
return true ;
}
return false ;
}
simulated event Bump(Actor Other)
{
local vector Shove;
local int damage ;
local float Speed;
local class<damageType> damageTypeClass ;
// local KFBloodSplatter Streak;
// local vector WallHit, WallNormal;
// local Actor WallActor;
if(Pawn(Other) == None || Other.role != ROLE_Authority)
return ;
if(limitDamagingClass && (Other != DamageConstraintClass))
return ;
if(bShoveable == false)
return ;
if(lastShoveTimer > level.timeSeconds)
return ;
// no spinning wildly when people stand on you!
if (pawn(Other) != none && pawn(Other).Base == self)
{
LastStander = pawn(Other);
return;
}
if(!KIsAwake())
KWake() ;
damageTypeClass = class'DamTypeKick' ;
damage = 3 ;
/*
if (bLeaveStains && Speed > StainSpeedThreshold)
{
StainStreakInterval -= (0.001 * VSize(Velocity));
if (Other != none && Other.bWorldGeometry)
WallActor = Trace(WallHit, WallNormal, Location + 50 * Velocity, Location, false);
//Log(WallActor);
if ( WallActor != None && Level.TimeSeconds > LastStreakTime + StainStreakInterval)
{
Streak= spawn(class 'KFMod.KFBloodSplatter',,,WallHit * (WallNormal + VRand()), rotator(-WallNormal));
if (Streak != none)
Streak.SetRotation(Rotator(Velocity));
LastStreakTime = Level.TimeSeconds;
}
}
*/
/*
if (Other.IsA('KFHumanPawn'))
{
SetPhysics(PHYS_None);
Other.attachtobone(self,'Bone_weapon');
}
*/
if(PuntTimeout >= level.timeSeconds && bPuntable)
{
if(Punter == Other)
{
damageTypeClass = class'DamTypePunt' ;
damage = 9 ;
}
}
else
Punter = None ;
Speed = VSize(Other.Velocity);
Shove = (Normal(Other.Velocity) / (Mass * 0.4)) * ShoveModifier ;
if(Other.Velocity.Z < -90.0)
{
Shove.Z *= 0.5 ;
Shove.X *= 0.8 ;
Shove.Y *= 0.8 ;
}
if(IsInWater(Other))
{
Shove.Z *= 0.75 ;
Shove.X *= 0.5 ;
Shove.Y *= 0.5 ;
}
if(shove.z == 0.0)
shove.z = (1.6 / Mass) * 0.4 * ShoveModifier;
if(bEnableSpinProtection)
TakeDamage(damage, Pawn(Other), vect(0,0,0), Shove, damageTypeClass) ;
else
TakeDamage(damage, Pawn(Other), location, Shove, damageTypeClass) ;
lastShoveTimer = level.timeSeconds + 0.1 ;
}
simulated event RanInto(Actor Other)
{
local vector Momentum;
local float Speed;
// if (Other.bWorldGeometry)
// LastTouchedActor = Other;
if(bCollisionDamage && Other != None && lastShoveTimer <= level.timeSeconds)
{
if ((Pawn(Other) == None) || (Vehicle(Other) != None) || Other == Instigator || Other.Role != ROLE_Authority)
return;
Speed = VSize(Velocity) ;
//log(Velocity) ;
if (Speed > DamageSpeed)
{
Momentum += Speed * 0.25 * Other.Mass * Normal(Velocity cross vect(0,0,1));
Other.TakeDamage(int(Speed * 0.045 * HitDamageScale), Instigator, Other.Location, Momentum, HitDamageType);
if(HitSounds.length != 0)
notifyPlayHit = true ;
//DEBUG
//log(Tag$" injured "$Other.Tag$" dealing "$(int(Speed * 0.048 * HitDamageScale))$" Damage!") ;
Velocity.z = Velocity.z * 0.45 ;
if((Speed * 0.045 * HitDamageScale) >= 70)
TakeDamage(3, instigator, Location, ((Velocity * -0.85) / (Mass + 1)), class'GoodKarma.DamTypeKick') ;
else
TakeDamage(7, instigator, Location, ((Velocity * -0.65) / (Mass + 1)), class'GoodKarma.DamTypeKick') ;
}
else if(Pawn(Other) != None)
TakeDamage(4, instigator, Location, (Velocity * -1.03), class'GoodKarma.DamTypeKick') ;
lastShoveTimer = level.TimeSeconds + 0.07 ;
//log(Velocity) ;
}
//DEBUG
//log(Tag$" is now moving at "$Velocity) ;
}
function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex)
{
local vector ApplyImpulse;
local Actor A;
if(limitDamagingClass && (instigatedBy.Weapon != DamageConstraintClass))
return ;
lasthitlocation = hitlocation;
lastinstigatedby = instigatedBy;
// When shot, trigger self.
TriggerEvent(Event, A, instigatedBy);
Damage = Damage - DamageAbsorbtionAmount ;
if(Damage < 0)
Damage = 0 ;
Health = Health - Damage ;
if(Health <= 0 && bDestroyable && !bPendingRespawn)
die() ;
if(damageType.default.KDamageImpulse > 0)
{
if(VSize(momentum) < 0.001)
{
return;
}
ApplyImpulse = Normal(momentum) * damageType.default.KDamageImpulse;
//DEBUG
//log("********************") ;
//log(ApplyImpulse) ;
KAddImpulse(ApplyImpulse, hitlocation) ;
}
hitDirection = rotator(momentum) ;
//DEBUG
//log("TakeDamage") ;
}
function bool IsInWater(Actor Other)
{
local PhysicsVolume V;
ForEach Other.TouchingActors(class'PhysicsVolume',V)
{
if(V.bWaterVolume == true)
return true ;
}
return false;
}
simulated function KImpact(actor other, vector pos, vector impactVel, vector impactNorm)
{
Super.KImpact(Other,pos,impactVel,impactNorm);
if (VSize(Velocity) != 0)
{
if(HitSounds.length != 0 )
playServerHit();
}
}
simulated function Tick(float Delta)
{
local pawn Stander;
if(clientPlay != 0)
{
switch(clientPlay)
{
case 1: clientDeathEmitters() ;
break ;
case 2: clientRespawnEmitter() ;
break ;
default:log("clientPlay invalid value") ;
}
clientPlay = 0 ;
}
PackState();
if(bPendingRespawn && respawnClock <= level.timeSeconds)
respawn() ;
if(bPendingRespawn && bRespawnEffect && !bRespawnEffectPlayed && (level.timeSeconds >= (respawnClock - PreRespawnEffectTime)))
{
clientPlay = 2 ;
playServerSoundRespawn() ;
bRespawnEffectPlayed = true ;
}
if(bPendingDelete)
{
die() ;
bPendingDelete = false ;
}
if(bInactive && bInactivityReset && !bWaitTillMove)
{
if(inactiveClock == -999.0)
inactiveClock = inactivityTimeout + level.timeSeconds ;
else if(inactiveClock <= level.timeSeconds)
{
//DEBUG
//log(tag$" is pending inactivity respawn") ;
bPendingRespawn = true ;
respawnClock = level.TimeSeconds + 1.5 ;
inactiveClock = -999.0 ;
}
}
// If someone's standing on me, i can't go flying.
if (VSize(Velocity) != 0)
{
foreach BasedActors( class 'pawn',Stander)
{
if (Stander != none)
{
//Log("I'm being stood on. Halting movement");
Velocity = vect(0,0,0);
}
}
}
// No super speeding objects
if (Mass >= 0.5 && VSize(Velocity) > 100)
Velocity *= 0.5;
}
//Pack current state to be replicated
function PackState()
{
local bool bChanged;
if(!KIsAwake())
return;
KGetRigidBodyState(KState);
bChanged = false ;
bChanged = bChanged || VSize(KRBVecToVector(KState.Position) - KRBVecToVector(KRepState.Position)) > 2.5;
bChanged = bChanged || VSize(KRBVecToVector(KState.LinVel) - KRBVecToVector(KRepState.LinVel)) > 0.5;
bChanged = bChanged || VSize(KRBVecToVector(KState.AngVel) - KRBVecToVector(KRepState.AngVel)) > 0.5;
if(bChanged)
{
KRepState = KState;
StateCount++;
if(bBlockedPath && stateCount > 1) //Does it need to trigger the blocked path?
{
if(VSize(startLocation - KRBVecToVector(KRepState.Position)) > moveDistance)
{
bBlockedPath=false ;
TriggerEvent(BlockedPath, self, None) ; //DO IT!
//DEBUG
log(Tag$" is "$VSize(startLocation - KRBVecToVector(KRepState.Position))$" units from its original position!") ;
}
}
bInactive = false ;
bWaitTillMove = false ;
}
else
{
bInactive = true ;
return;
}
}
simulated event PostNetReceive()
{
if(StateCount == LastStateCount)
return;
}
simulated event bool KUpdateState(out KRigidBodyState newState)
{
if(StateCount == LastStateCount)
return false;
newState = KRepState;
lastStateCount = StateCount ;
return true;
}
/*
simulated function UsedBy( Pawn user )
{
Log("I was just used...and I liked it");
if (KFHumanPawn(user) != none)
{
SetPhysics(PHYS_None);
user.AttachToBone( self, 'Bone_weapon' );
}
}
*/
simulated function Die()
{
DieVect = lasthitlocation-lastinstigatedBy.location;
//DEBUG
//log("Die at "$location) ;
bDead = true ;
if(bDestroyable)
{
playServerSoundDestroy() ;
if ( EffectIsRelevant(Location,false) )
spawn(DestroyedEffect,,,Location,rotator(DieVect) );
ClientDeathEmitters();
//DestroyedFX();
HurtRadius(explodeDamage ,explodeRadius, class'DamTypeKick', explodeForce, Location) ;
if(DestroyedStaticMesh == none)
{
bHidden = true ;
SetPhysics(PHYS_None) ;
SetCollision(false, false, false) ;
Health = 0 ;
Punter = none ;
bPendingPunt = false ;
}
else
{
KWake() ;
SetStaticMesh(DestroyedStaticMesh) ;
KSetMass(DestroyedMass) ;
KSetRestitution(DestroyedRestitution) ;
KSetDampingProps(DestroyedDamping, DestroyedDamping) ;
KSetFriction(DestroyedFriction) ;
(KarmaParams(KParams)).KBuoyancy = DestroyedBuoyancy ;
Health = 1 ;
}
}
else
{
bHidden = true ;
SetPhysics(PHYS_None) ;
SetCollision(false, false, false) ;
Health = 0 ;
Punter = none ;
bPendingPunt = false ;
}
if(AutoRespawnTime != -1)
{
bPendingRespawn = true ;
respawnClock = level.timeseconds + AutoRespawnTime ;
bInactive = false ;
}
clientPlay = 1 ;
}
function playServerSoundDestroy()
{
PlaySound(DestructionSound, SLOT_None, DestructionVolume) ;
//log("Server-SoundDestroy") ;
}
function playServerSoundRespawn()
{
PlaySound(RespawnSound, SLOT_None, RespawnVolume) ;
//log("Server-SoundRespawn") ;
}
function playServerHit()
{
local int a ;
local float b ;
if(replaySoundClock <= level.TimeSeconds)
{
a = Rand(HitSounds.length) ;
b = rand(4) * 0.1 ;
PlaySound(HitSounds[a] , SLOT_None, RelativeImpactVolume);
notifyPlayHit = false ;
replaySoundClock = level.timeSeconds + ((0.55 + b) * hitSoundRepeatModifier) ;
}
//log("Server-SoundHit") ;
}
simulated event FellOutOfWorld(eKillZType KillType)
{Die() ;}
function StaticMesh getObject(StaticMesh A)
{ return A ;}
simulated function ClientDeathEmitters()
{
if(level.NetMode != NM_DedicatedServer)
return ;
if(bOrientDestructionEffect)
Spawn(DestroyedEffect,,,Location + EffectOffset, hitDirection) ;
else
Spawn(DestroyedEffect,,,Location + EffectOffset) ;
//log("Client-EmittersDestroy") ;
}
simulated function ClientRespawnEmitter()
{
if(level.NetMode != NM_DedicatedServer)
return ;
Spawn(RespawnEffect,,,startLocation + EffectOffset) ;
//log("Client-EmittersRespawn") ;
}
defaultproperties
{
bKActorShadows = true
bBlockedPath=True
moveDistance=50.000000
RelativeImpactVolume=127
bShoveable=True
ShoveModifier=1.500000
hitSoundRepeatModifier=1.000000
bCollisionDamage=True
DamageSpeed=100.000000 //600
HitDamageType=Class'GoodKarma.DamTypeBludgeon'
HitDamageScale=1.000000
initialHealth=100
damageAbsorbtionAmount=25
explodeRadius=15.000000
bDestroyable=True
DestructionVolume=200
bOrientDestructionEffect=True
DestroyedMass=0.500000
DestroyedDamping=1.000000
DestroyedFriction=0.700000
DestroyedBuoyancy=0.300000
DestroyedRestitution=0.100000
PreRespawnEffectTime=1.000000
inactivityTimeout=15.000000
RespawnSound=none//Sound'GameSounds.Combo.ComboActivated' KFTODO: Replace this
RespawnVolume=180
AutoRespawnTime=-1.000000
bRespawnEffectPlayed=True
bWaitTillMove=True
bdead=True
LastStateCount=-1
inactiveClock=-999.000000
StaticMesh=StaticMesh'GKStaticMeshes.basicShapes.BasicCube'
bUseDynamicLights=True
RemoteRole=ROLE_SimulatedProxy
bNetNotify=True
Begin Object Class=KarmaParams Name=KarmaParams0
KMass=0.500000
bHighDetailOnly=False
bClientOnly=False
KFriction=0.500000
KRestitution=0.200000
KImpactThreshold=1.000000
End Object
KParams=KarmaParams'GoodKarma.NetKActor.KarmaParams0'
bEnableSpinProtection = true
//HitSounds(0) = Sound'PatchSounds.MetalCrash'
bHardAttach=True
bDetailAttachment = true
}