//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 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 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 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 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 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 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, 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 }