The three virtues of a programmer: Laziness, Impatience, and Hubris. – Larry Wall
Legacy:Karma Ragdoll Injury System
Note to reader: This page represents an incomplete investigation into switching players' pawns into and out of ragdoll mode. Some considerable mileage was made but the implementation does not work in network play.
(Note: this text cut from Karma Ragdoll. Karma Ragdoll should be definition and description. All these ideas deserve their own page).
This is an idea first proposed on the Wiki by Soldat. Essentially, the idea is that when a player is damaged by a weapon, they will go into Karma Ragdoll mode for a brief period, depending on the severity of the hit, and then get back up. So, for instance, a hit from an assault rifle may merely cause you to stumble for a split second before regaining control while a direct hit from a rocket would send you through the air to end up as a heap on the ground, which (if still alive) would then attempt to crawl back up onto its feet. Soldat came up with the following function which will activate the Ragdoll Physics on a Pawn. It needs to be placed in a subclass of Pawn or called on the Pawn externally (it would require some modification to do that, but could be modified to work in a mutators' damage modification function):
exec function KarmaMe() { local KarmaParamsSkel SkelParams; local String RagSkelName; RagSkelName = Species.static.GetRagSkelName(GetMeshName()); KMakeRagdollAvailable(); // allocate a space for the incoming ragdoll skelParams = KarmaParamsSkel(KParams); // create a new skelParams object based off current KParams skelParams.KSkeleton = RagSkelName; // with our new ragdoll KParams = skelParams; // and now, voila! KParams is the new skelParams KSetBlockKarma(true); // without this line, I fall THROUGH the floor... gross SetPhysics(PHYS_KarmaRagdoll); // actually create the ragdoll }
While a neat system, and easy enough to get implemented mostly, it has run into a slight problem: neither Soldat nor myself (I am also trying to implement this, though slightly differently, for my mod) has been able to find a "perfect" way to get the player to get back up after becoming a ragdoll. I have merged our attempts into a single list and described why each did not work quite as planned:
Contents
Removing the Actors Skeleton/Resetting the Karma Parameters, and changing Physics back to PHYS_Walking[edit]
exec function UnKarmaMe() { KarmaParamsSkel(KParams).KSkeleton = ""; SetPhysics(PHYS_Walking); } exec function UnKarmaMe2() { KParams = default.KParams; SetPhysics(PHYS_Walking); }
Though in effect the same thing (Removing the Skeleton and Setting the Karma Parameters back to default) it was worth trying both. Neither had any effect. When the physics was set back to PHYS_Walking, the player fell through the floor and out of the world. This was later found to be a result of Karma Ragdoll simulation disabling the regular physics' collision in order to use its own. Setting bCollideWorld to true when you set the physics back to PHYS_Walking will prevent the player from falling through the floor, but it still won't get back up or do anything except look around and shoot. (Because its "state" hasn't changed in the Pawn class, it still thinks it's walking.) However, no animations will play and it will not move anywhere.
Freezing the Ragdoll[edit]
exec function UnKarmaMe3() { KFreezeRagdoll(); SetPhysics(PHYS_Walking); }
This was tried by Soldat and caused the ragdoll to freeze. Aside from not falling into the floor, this was no more successful than removing the skeleton.
Spawn a New Pawn above the Ragdoll[edit]
Soldat's initial idea was to simply destroy the ragdoll and spawn a new pawn on top of where the ragdoll used to be. The problem with this, was that it would telefrag anyone the ragdoll fell under. Another problem was that it didn't have a cool animation to show the person getting back up. :P The telefragging was solved with a suggestion from Mysterial and expanded by CheshireCat to disable the collision on the new pawn until it was no longer touching anyone else, then reenable it. The only existing problem with this method is that there is no real smooth way of showing the player get back up. As I understand, this is the current method that Soldat is experimenting with.
Animating a Ragdoll[edit]
A short study revealed that merely setting the Physics to PHYS_KarmaRagdoll with cause serious problems with animation and will prevent any animations from being played, even if physics is reset to PHYS_Walking immediately after the call to change it to PHYS_KarmaRagdoll. In other words, the pawn does not have to spend any time being a ragdoll to cause this effect. I am still researching this find out a way to get animations to work after returning the physics to normal. Soldat (I believe) is using the last method detailed above (making a new pawn to replace the old one) and is trying to find a way to make it look like the new pawn is actually the old pawn getting up.
Discussion[edit]
Foxpaw: This page has been mainly refactored from Words About Karma Ragdoll Deaths along with some stuff I wrote myself. Credit for all of the ideas provided has been given to their respective authors, I believe. I hope I didn't leave anyone out. By the way, Soldat, I had an idea - you may be able to set the person to a crouching idle animation and tween to a standing animation, then, you could make it so when the player lands flat on the ground, it kicks up a dust cloud or something like that, that would obscure the jump from lying down to the crouching position. Let me know how that turns out if you try it, I'm going to continue working on a way to get the original pawn to move again.
Foxpaw: Eureka! I've gotten the thing to animate again after returning from ragdoll mode. It wipes out the current position and replaces it with the default but at least it doesn't require spawning a new pawn and moving over all of the health, etc. from it. All you have to do is change the mesh. So basically, call something like the following:
// Reenables animations after returning from Karma Ragdoll simulation. Requires a global // variable SafeMesh, which is a mesh object which we can be guaranteed that this object // is not. If we try to do this with the same mesh that we are it will not work, therefore, // it is imperative that a player never be the mesh stored in safemesh. An alternative // is to have two "safe meshes" and check to see if the players mesh == the first safemesh, // if so use the second safemesh. Otherwise just use the first. This function assumes a // safemesh that is guaranteed to never be what the player is set to. function ReinitializeMesh() { local Mesh OldMesh; OldMesh = Mesh; LinkMesh( SafeMesh ); LinkMesh( OldMesh ); return; }
I also noticed that you must be careful to not engage the ragdoll simulation for too short of a time. When ragdoll simulation first begins, your limbs are all concentrated in one spot and they spread out until they are in the correct position. This takes only a fraction of a second, though it may vary on different computers. If you try to use the karma injury system for something mild like a glancing blow that just moves you for a split second it will make you turn into a ball of limbs instead!
Foxpaw: I noticed a small problem, but it may be solveable by a bit of number fudging. The momentum imparted by various weapons varies immensely. Assault Rifle and Lightning gun are only 1, minigun is 1200, each flak shard is 10000, rockets are 50000, shock gun primary 60000, flak grenades 75000, and the shock combo in excess of 130000. The problem, here, of course, is if I set it up so that a rocket puts him down for 3 seconds, he's out like a light if he gets hit by a shock combo. If, however, it's set so that a shock combo merely keeps him out for 5 seconds, the minigun/AR/lightning gun do diddly squat. Aside from the fact that you have to wait a while for the target to recover, it's pretty fun. I did notice a couple of things though: A) your ragdoll assumes you were standing. Getting ragdolled while crouching does not produce the same effect. B) Ragdolls have a tendancy to "fall through" very thin surfaces. (IE A 'grate' over a pit of slime) Though cool, the pawn gets stuck when it recovers. This occurs with my "pawns", regular pawns may act differently.
Chema: Great! and... weird!! I had been trying the UnKarma stuff for a few days. I also got stuck by the animation. Here is a full (I think) UnKarmaMe(), incluiding the double mesh link:
exec function UnKarmaMe() { local Mesh OldMesh; SetPhysics(PHYS_Walking); // Reset physics bCollideWorld = true; // And world collision // (KActors use KSetBlockKarma for that) SetCollision(true, true , true); // Block actors and players again OldMesh = Mesh; LinkMesh( SafeMesh ); // Global mesh, you will not see it LinkMesh( OldMesh ); // This puts your mesh back }
The doble mesh link is really weird, but works. And it is dificult to find what to do whit all those native functions, wich we don't have an idea what really do.
Now, to fix the abrupt standing, we could try an immediate crouch, or better, I saw somewhere a function to initialize the mesh anim before spawn.
Foxpaw: My pawn comes up in the crouching position. It was as simple as adding PlayAnim( 'Crouch' ); after you relink the oldmesh. (I'm pretty sure that those parameters are optional. I use a wrapper for animation with my "pawns," so my syntax is slightly different.)The real problem is not the starting animation, but how to get the starting animation to be something very close to what the ragdoll was using, I think. I have had ragdolls get their foot caught in tiles on the floor and not end up falling over, just kind of going limp - in that instance the standing is better. I've been experimenting the the dark magic of manual skeleton rotation - I have a hunch but I'm not quite sure how I'll go about it. If you could read the rotation of all of the bones, you could manually set them after relinking the mesh and then from there tween to an animation. However, obtaining the bones rotation is not as simple as calling the GetBoneRotation, as that only returns the rotation relative to where the current animation says that it should be. Bones seem to use a haphazard mix of absolute and relative locations and rotations, as well as even having their own internal coordinate system for each bone! For that reason it's a bit confusing. Skeletal Mesh has a listing of skeletal animation functions and a short description of those that I have experimented with. Unforunately, I haven't played with all of the parameters of the functions. There's another interesting thing that should be considered, but maybe this is only a problem with the juggernaut skeleton that I'm using: The juggernaut skeleton has an overwhelming tendancy to fall to its knees, then fall flat to its face. This may be because of the limits on the bones though. It generally performs this if hit by a "singular, direct" type of attack, like a direct hit from rocket, shock rifle, etc. Multiple-hit type weapons like flak work fine, and if a rocket hits the ground nearby the results are believable. One more thing. My pawns don't have this problem because of the way that they are scripted, but the built in pawns suffer a set amount of impulse whenever they get hit as a ragdoll. It does not use the actual momentum of the gun, and I don't think it applies it to the actual bone hit either. The following is a slighly modified excerpt from the code I use (the code I use is in an else block and has an argument Bone instead of declaring it.)
local float Dummy; // Just to satisfy an out argument. local name Bone; if ( Physics == PHYS_KarmaRagdoll ) { Bone = GetClosestBone( HitLocation, Normal(Momentum), Dummy ); KAddImpulse(momentum, hitlocation, Bone); } // If you wanted you could collapse this to take up less space you could also just do: local float Dummy; // Just to satisfy an out argument. if ( Physics == PHYS_KarmaRagdoll ) KAddImpulse(momentum, hitlocation, GetClosestBone( HitLocation, Normal(Momentum), Dummy ));
I have no idea why Digital Extremes used that RagSkelVel and SkelParams.KShotStrength nonsense. This looks way more ragdollish and much cooler as it allows for multiple sources of force to be acting in rapid succession. IE when you get nailed at close range with the flak cannon. (Of course you probrably wouldn't survive anyways but maybe if you had lots of shields and health)
Foxpaw: I got the getting up part a bit better. Your position on the ground is now recreated once the mesh is reset, but only on key bones. So now I'm a running dilapitated pile of crumpled up bones. :P I'll try to find an efficient way to tween out the dilapitatedness into normal movement.
Chema: Houston, I can get up... but can't crouch!
Adding PlayAnim( 'Crouch' ); to UnKarmaMe doesn't work. Actually, it works normally, but not when I'm Karmed: I stand up, but don't crouch. If I call UnKarmaMe again, from console, then it works. Simply put: I can't animate the mesh in the same call that UnKarmaMe, no matter if I put in a different function.
Any idea whats wrong with me? And with my code? (hehe). Are you doing anything special apart of the stuff in the previous UnKarmaMe()? I'm setting my TestxPawn trough the URL (dm-gael?Game=Test.TestGame?Class=Test.TestxPawn), but there should be a scripted way to do it for a game. An equivalent of the CheckReplacement of the Mutators? (Thanks for all your help!)
Foxpaw: It may be that the animation won't play on the same call. As you may have noticed, (maybe, it's kind of hard to see.) For a short time after becoming a ragdoll you are just a ball of concentrated body parts. It is possible that your ragdollness persists until the next tick. Or something. The 'personnell' on my mod are completely custom - I've written my own classes for everything below actor, because Pawns have lots of built in junk. :P It has an attached "Mesh Controller" which monitors animation, etc. and would play the idling animation the next opportunity it got... so if it cannot indeed be called from the same function it would try again the next tick. That may be why mine goes back to the idling animation while yours requires a second call. I believe that you can use checkreplacement in mutators to replace your xpawn with the one you have made.. I haven't tested it but I believe that you can because I think that checkreplacement is called from within spawn, before the controller would make its Possess call, which I believe does not occur until after the spawn has returned. I haven't tested that part out, the order that the controller stuff goes in because I just wrap the controller class, so I don't really use it for anything.
Foxpaw: I noticed another thing, but it might not be a big deal for your application. If you really clobber a ragdoll sometimes it will suffer a simerror as a result of its joints being spun so fast. This will, by default, result in it being destroyed - definately a bad thing if the player was merely injured! You can set bDestroyOnSimError in its KParams to prevent this, but it doesn't protect against everything. bKImportantRagdoll (maybe doesn't have a K, I don't remember for sure) might also be good because it prevents the ragdoll from being destroyed if the system runs low on memory and wants to make a new ragdoll. The simerror thing may not apply in your application, because generally that will only result from things such as flak to the head at point blank or similar events, which would kill the pawn anyway. It might also be worth investigating what effects mutators that affect the ragdolls will have. I'm not sure about Floaty Cadavers, but I know that Slow Motion Corpses works by slowing down the entire Karma Ragdoll simulation system. This would slow down injured players as well. I don't know if that's a problem or not, but it's worth checking out.
OBWANDO: I took a few hours fighting with this, and it wouldnt work at all in any way. Heres what I found... If you want to get the pawns to go back to their original animated mesh, you need to link with another VALID different mesh. If you try to link with a null or non-specified mesh, it will simply not link at all which then you will wind up linking the original named mesh and that does absolutely nothing. In the end heres what seemed to work for me:
In my pawn class (RBxPawn.uc):
var (Global) mesh SafeMesh; exec function KarmaMe() { local KarmaParamsSkel SkelParams; local String RagSkelName; SafeMesh = Mesh; // This is where I am getting the pawn's original mesh RagSkelName = Species.static.GetRagSkelName(GetMeshName()); KMakeRagdollAvailable(); // allocate a space for the incoming ragdoll skelParams = KarmaParamsSkel(KParams); // create a new skelParams object based off current KParams skelParams.KSkeleton = RagSkelName; // with our new ragdoll KParams = skelParams; // and now, voila! KParams is the new skelParams KSetBlockKarma(true); // without this line, I fall THROUGH the floor... gross SetPhysics(PHYS_KarmaRagdoll); // actually create the ragdoll } exec function UnKarmaMe() { KarmaParamsSkel(KParams).KSkeleton = ""; SetPhysics(PHYS_Walking); // Reset physics bCollideWorld = true; // And world collision // (KActors use KSetBlockKarma for that) SetCollision(true, true , true); // Block actors and players again LinkMesh( SkeletalMesh'Weapons.AssaultRifle_1st' ); // I am using a valid mesh which is not the same as the pawn's original mesh // You should probably use a precached mesh for performance reasons, the gun seemed good here LinkMesh( SafeMesh ); // Link the pawn's original mesh and we're done! }
Foxpaw: I believe that is the same as what I wrote above. SafeMesh DOES need to be defined as a mesh, I guess I should have explicitly stated that. The OldMesh is the mesh that we want to return to and is read in the unkarmatize function on my "livingthing" class. (This is the "livingthing" equivalent of the unkarmame function.) I could have sworn we discussed the idea of using two safemeshes in case, for whatever reason, the pawn was using one of those meshes, I don't see that anywhere on this page though so maybe I just imagined it. :P Should we remove the failed attempts listed above or leave them so others can learn that those methods don't work and why the didn't work?
OBWANDO: I would say leave it in place, it shows the evolution of the learning curve to a final conclusion. You did mention (or i assumed) that there were 2 safemeshes, but I did not know that 1 had to be explicitly defined, and that was what threw me, and could others. I wouldn't have made it this far without everything being available to read.
Foxpaw: Okay. Both safemeshes would actually need to be defined, but you can usually get away with using just one, depending on the application. In this instance it's highly unlikely that a pawn would have a first person weapon mesh as their mesh so you could get away with the way you did it.
Foxpaw: I've noticed one small issue but it's not that big of a deal. If you rotate bones using Unrealscript, the karma ragdoll is not rotated appropriately. The visual representation of it is, but the actual collision/simulation information remains unchanged. That's probrably not a big issue, I've been experimenting with rotating bones to have the player "get back up" from whatever position they ended up in. In this instance, the simulation can be slightly innaccurate if you are hit again while getting up.
Foxpaw: I almost forgot - there's a big problem with this code, but it might not be that hard to work around. There's a limit on the number of ragdolls that you can have in play at any time. I believe it may be per zone, however. The limit appears to be rather low, unfortunately, approximately 3 ragdolls per zone. If you had two carcasses and two injured players, one would be denied a ragdoll and would just stand still. You could work around this by using the IsRagdollAvailible, and if one is not the player could just play some other injury animation and be simulated using regular physics.
OBWANDO: I agree, I listed a piece of simple code to get the idea, but to prevent the problems from occuring here is a small snip that you could use as well to combat the problems of unavailability.
exec function KarmaMe() { local KarmaParamsSkel SkelParams; local String RagSkelName; SafeMesh = Mesh; // This is where I am getting the pawn's original mesh if( RagdollOverride != "") // Try to get the rag-doll setup. RagSkelName = RagdollOverride; // If there is an override, use it first else if(Species != None) // if not, use species. RagSkelName = Species.static.GetRagSkelName( GetMeshName() ); if( RagSkelName != "" ) // If we did find one, try to allocate the space. { KMakeRagdollAvailable(); // allocate a space for the incoming ragdoll } if( KIsRagdollAvailable() && RagSkelName != "" ) { skelParams = KarmaParamsSkel(KParams); // create a new skelParams object based off current KParams skelParams.KSkeleton = RagSkelName; // with our new ragdoll KParams = skelParams; // and now, voila! KParams is the new skelParams KSetBlockKarma(true); // without this line, I fall THROUGH the floor... gross SetPhysics(PHYS_KarmaRagdoll); // actually create the ragdoll } }
OBWANDO: One thing I have noticed that that you can crash the client pretty easily if you try to do this in a dedicated network mode. On a local client there are no problems, but from what I have seen, a dedicated server will not allow the physics to change to ragdoll, so your client will show a ragdoll, but your server will still show the last known physics mode (like PHYS_Walking, or PHYS_Falling). Maybe we should look at starting a thread or page on network implementation.
Foxpaw: I don't understand what you mean by that.. how do you mean the server will "show" the last known physics mode, if it is a dedicated server? The server/client problem you are having is likely due to the KarmaMe function not being replicated.. or something like that. I haven't tested mine over network yet but it is set to be triggered when hit, if the imparted momentum is over a certain threshold. I am not sure how the autonomous proxy treats exec functions so the problem could be with that. If you want to ensure that the ragdolling is consistant on both client and server you could have a boolean variable that is replicated reliably from server to client, and a check in tick - if the variable is true and we are not in PHYS_KarmaRagdoll, then call the KarmaMe function, if the variable is false and we are in PHYS_KarmaRagdoll, then call the UnKarmaMe function. This, of course, would require those functions split into two pieces because you wouldn't want to call an exec function and have the cause the server to request that same function to be called on the client, which would then cause the server to request that same function to be called on the client again, etc. Enough talk! :P The implementation could be something like this: (I didn't actually compile and test this, there could be typos. It would also maybe need some tweaking to allow for running out of ragdolls. You could use a timer or whatever you want for unkarmame. I use a manually implemented timer in tick.)
var bool bIsRagdolling; var float RagdollThreshold; replication { reliable if ( ROLE==ROLE_Authority ) bIsRagdolling; } simulated function TakeDamage(int Damage, Pawn Instigator, Vector HitLocation, Vector Momentum, class<DamageType> DamageType) { Super.TakeDamage(Damage, Instigator, HitLocation, Momentum, DamageType); if (Role == ROLE_Authority && VSize(Momentum) > RagdollThreshold) bIsRagdolling = true; } simulated function Tick(float DeltaTime) { Super.Tick(DeltaTime); if (Role < ROLE_Authority) if (bIsRagdolling && Physics != PHYS_KarmaRagdoll) KarmaMe(); else if (!bIsRagdolling && Physics == PHYS_KarmaRagdoll) UnKarmaMe(); } simulated function KarmaMe() { // Do your karmame stuff here. } simulated function UnKarmaMe() { // Do your unkarmame stuff here. }
OBWANDO: Heres what I've seen. In most of the Epic code, you will see that they have the karma sequences set up so that the server's pawn doesnt actually get its physics set into KarmaRagdoll mode (Level.NetMode != NM_DedicatedServer). Only the client does. Now this may seem simple in theory, but the reality is that if you touch a floor surface, or its a bot (or a player) that jumps then the physics on the server will change and as a result the physics from the server will be replicated as part of the normal parent class replication to that physics mode. Thats where the problems begin since on the client you are in KarmaRagdoll, and the server replicates a PHYS_Walking, or whatever, but its not Ragdoll, so the client crashes since you cannot have a walking pawn that is in PHYS_KarmaRagdoll. This means you have to address the problem to prevent the client from getting the physics change unless you have restored the physics and mesh back and are ready for that PHYS mode.
Foxpaw: Ah, yes. The ragdolls aren't simulated on the server usually because that just wastes resources. They are "torn off" when they are created, so they cease to replicate. I think that the implementation I listed above should work.. I hope. I haven't tested that or played around with it, I don't really know all of the nuances of the pawn class since I don't use it for anything, but it seems logical.
Foxpaw: Apparently, the limit placed on ragdolls is optional. If you just remove the MakeRagdollAvailible part and the IsRagdollAvailible check, you can apparently make as many ragdolls as you want. Finally, my battlefields can have carcasses littered all around, instead of that stupid de-res thing. :P
PlaneGuy: I tried the MP ragdoll code above, but it still crashes. I was thinking if being ragdolled is a state, you can set the pawn to ignore any non-ragdoll/karma related action in that state. Thoughts?
OBWANDO: Here is the code I created that successfully works. I used a manual timer since the SetTimer was too flaky for me. The server first of all is the only one that will REALLY know what mode our client is supposed to be in. When we are ragdolling, we should probably prevent the bots from moving the ragdoll, or when we come back out, the client will show the pawn pretty far away from where it should have been. and most importantly, we need to make sure the client does not try to change the physics via update from the server.
var bool bKarma; // server paramater to client if you need to be set to karma mode var bool bInKarma; // server parameter letting the client know to override any physics changes to karma. var bool bTickMe; // my tick timer boolean var bool bClKarma; // the client flag that lets us know that the client is indeed in karma mode. (remember Physics will be changed from replication) var int iTickTime; // a timer counter since SetTimer was too flaky to use over network. replication { reliable if (Role == ROLE_Authority ) bKarma, bInKarma; } simulated event PostNetReceive() { if ( PlayerReplicationInfo != None ) { Setup(class'xUtil'.static.FindPlayerRecord(PlayerReplicationInfo.CharacterName)); if ( AmbientGlow == 70 ) AmbientGlow = Default.AmbientGlow; //bNetNotify = false; } if ( (bInKarma == false) && (bKarma == false) && (Physics == PHYS_KarmaRagdoll) ) UnKarmaMe(); // ok release karma mode go back to a pawn } simulated function Tick(float DeltaTime) { if (Role == ROLE_Authority ) { if (bTickMe) { iTickTime--; if (iTickTime < 1) { bTickMe = false; UnKarmaMe(); } } } if ( (bInKarma && !bTearOff) ) // if we're in karma, but not dead, prevent player induced motion from occuring. { Acceleration = vect(0,0,0); Velocity = vect(0,0,0); } if ( (Physics != PHYS_KarmaRagdoll) && (bKarma == true) && (bInKarma == false) ) KarmaMe(); //set the Ragdoll Mode if ( Level.NetMode == NM_DedicatedServer ) return; //****** ONLY LOCAL AND CLIENT STUFF BELOW HERE if ( (Physics != PHYS_KarmaRagdoll) && (bClKarma == false) && (bInKarma == true) ) KarmaMe(); //Seems replication isnt perfect, so lets make sure if ( Controller != None ) OldController = Controller; if ( (bInKarma == true) && (bKarma == false) && (Physics != PHYS_KarmaRagdoll) ) SetPhysics(PHYS_KarmaRagdoll); // Enforce Ragdoll on clients TickFX(DeltaTime); if ( bDeRes ) { TickDeRes(DeltaTime); } // assume dead if bTearOff - for remote clients unfff unfff if ( bTearOff ) { if ( !bPlayedDeath ) PlayDying(HitDamageType, TakeHitLocation); return; } } simulated function UnKarmaMe() { if (ROLE != Role_Authority) { KarmaParamsSkel(KParams).KSkeleton = ""; SetPhysics(PHYS_Falling); // Reset physics bCollideWorld = true; // And world collision // (KActors use KSetBlockKarma for that) SetCollision(true, true , true); // Block actors and players again LinkMesh( SkeletalMesh'Weapons.AssaultRifle_1st' ); LinkMesh( SafeMesh ); bClKarma = false; } if ( Role == ROLE_Authority ) { //servers set the parameters bKarma = false; bInKarma = false; } }
In your KarmaMe() routine, add this to your karma code:
if (ROLE == Role_Authority) { bKarma = false; bInKarma = true; } if ( Level.NetMode != NM_DedicatedServer ) } ... karma code etc... SetPhysics(PHYS_KarmaRagdoll); bClKarma = true; // dont forget to let its own client know its mesh should stay in karma mode. }
Remember, you need to set the bClKarma on the client only, the server has no idea as to what that boolean parameter is.
So why check for unkarma in PostNetReceive? Simple, I need to check when it changes, and not every tick. Since normally you are not in karma, and you're karma flag is false when you are playing, no point tying up CPU cycles testing a scenario that can be predicted from the server.
When you are ready to invoke the karma mode, use this piece of code:
if (Role == ROLE_Authority ) // we need to set the karma ragdoll time and flag true if applicable on server only { if ( (bKarma == false) && (bInKarma == false) ) { // if you're already in karma mode, no need to do it agin. bKarma=true; } iTickTime = iTickTime + vSize(momentum)/30; // or whatever you want to use. bTickMe = true; }
I use it in my TakeDamage function.
One last thing, in you class add these to your defaultproperties:
defaultproperties { bKarma = false bInKarma= false bAlwaysRelevant=True // should already be set if its a pawn bGameRelevant=True // i believe same here as above bTickMe = false iTickTime = 0 bNetNotify=true // important bClKarma = false }
Hopefully this will shed some light on the karna networking topic and get more mods looking and feeling realistic. I will add my code soon that will allow for you to successfully get your pawn back up correctly using bone lifters, and timing (notice I set the timer from the momentum parm in the takedamage function). I'm sure the code could be optimized and done in many ways, but it works, and its something to start from.
Good luck, and if you have any questions post em, or look for me on IRC... im usually around in #unrealwiki, #unrealed, or #unrealscript.
Foxpaw: I would not recommend using bone lifters for the "getting back up" portion.. bone lifters require collision to be disabled, effectively making the player invulnerable while recovering. It would also be kind of wierd looking. I think that tweening from the dead body position back to crouching animation would be better and is probrably quite possible.
Foxpaw: I got tweening back to animation to "work." So far it doesn't look all that natural though. I have an idea how to make it more natural but I'd like to do so without any hacks.
Foxpaw: Improved the tweening.. making it faster makes the wierd nuances unnoticeable and bringing them back to the crouched position instead of standing helped as well. Now I only have one problem: the relinking of the mesh starts the person off in a standing position. This causes the person to tween from standing to crouching when they get up which looks a bit wierd. I know how I could probrably fix that but it's a hack, so I don't want to do it that way. Any ideas of how I could do this cleanly?
OBWANDO: Drop some code in here, lets take a look. I was playing with LinkSkelAnim( (anim), (mesh) ) instead of LinkMesh. Its a native as well, but from what I see in the actor.uc, it will start the mesh up in that animation.
Foxpaw: Unfortunately, since my class heirarchy is completely different, the function calls, etcetera will be different. I'll put the contents of the "skeletoncontroller" class here anyway, you can probrably see the idea. An explanation would probrably serve you better: When we return from ragdolling, before relinking the mesh we read the orientation of all of the main bones, using GetBoneCoords. The X axis of that coordinate system that it returns is a vector pointing along the length of the bone, thus giving us its rotation. I store this information, relink the meshes, then use SetBoneDirection to recreate the ragdoll's position after the relinking. The SetBoneDirection has an alpha parameter that allows us to define how much of that direction should be imposed and how much should be left to SetBoneRotation/the animations. By tweening the alpha from 1 to 0 always with the same directions, the body will go from its ragdolled position to the position dictated by its animation over the course of the time that it takes to tween the alpha. This can create wierd effects if you do it slowly, though, because of the bone heirarchy and all. If done quickly enough, however, it appears fairly smooth. The only problem with this setup is that it uses some fair network bandwidth, because for some reason setbonedirection is not simulated, even though SetBoneRotation is.
There are some functions that are not related to the ragdolling in here, and some interplay with a class "UniversalThing" and "SkeletalMeshController," but you should be able to get the idea from this:
// The skeleton of a skeletal mesh. class SkeletonController extends UniversalThing notplaceable; // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // struct BoneData // A stored bone structure, used for ragdoll recovery. { var name BoneName; // The name of this bone. var coords BoneCoords; // The 'coordinates' of this bone. var rotator Rotation; // The current added rotation of this bone. }; struct TweenBoneDirection { var name BoneName; // The name of the bone. var rotator Direction; // The direction of the bone. var float TweenTime; // Time remaining to tween. var float StartTweenTime; // How long to tween in total. }; var Mesh SafeMesh1, SafeMesh2; // TODO: This is duplicated in the meshcontroller. var array<BoneData> Bones; var array<TweenBoneDirection> BoneTweenData; // Data for tweening of bone directions. var array<name> BoneNames; // Important bones that will be retained through ragdoll recovery. var array<name> AimBones; // Bones that take part in aiming. var name HeldBone; // Bone that we use for holding things. var name ViewBone; // Bone that the camera should view from. var actor Flesh; // Our "flesh." // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // simulated function postbeginplay() { Super.postbeginplay(); } simulated function Initialize() { local int i; Flesh.AnimBlendParams(1, 1.0, 0.0, 0.2, AimBones[0]); for (i=0;i<BoneNames.Length;i++) SetBoneRot( BoneNames[i], Rot(0,0,0) ); } simulated function BoneData StoreBone( name BoneName ) { local BoneData NewBone; if ( Flesh == None ) return NewBone; NewBone.BoneName = BoneName; NewBone.BoneCoords = Flesh.GetBoneCoords( BoneName ); return NewBone; } simulated function array<BoneData> StoreBoneData() { local int i; local array<BoneData> BoneData; if ( Flesh == None ) return BoneData; for (i=0;i<Default.BoneNames.Length;i++) BoneData[BoneData.Length] = StoreBone( Default.BoneNames[i] ); return BoneData; } // Set me back up for animation after I've ragdolled. simulated function SolidifyRagdoll() { local array<BoneData> BoneData; if ( Flesh == None ) return; BoneData = StoreBoneData(); if ( ( Flesh.Mesh == Default.SafeMesh1 || Default.SafeMesh1 == None ) && ( Default.SafeMesh2 != None ) ) Flesh.LinkMesh( Default.SafeMesh2 ); else if ( Default.SafeMesh1 != None ) Flesh.LinkMesh( Default.SafeMesh1 ); if ( UniversalThing( Flesh ) != None ) UniversalThing(Flesh).InitializeMesh(); RecoverBoneData( BoneData ); } simulated function RecoverBoneData( array<BoneData> BoneData ) { local int i; if ( Flesh == None ) return; for (i=0;i<BoneData.Length;i++) RecoverBone( BoneData[i] ); } simulated function RecoverBone( BoneData BoneData ) { if ( Flesh == None ) return; Flesh.SetBoneLocation( BoneData.BoneName, BoneData.BoneCoords.Origin ); SetBoneDir( BoneData.BoneName, Rotator( BoneData.BoneCoords.XAxis ),0.25 ); } simulated function TweakBoneRotation( name BoneName, out rotator Rot ) { if ( BoneName == HeldBone ) Rot += Rot(16384,0,0); ///// Note: This function is best described as a "hack." It allows a meshcontroller to ///// manually override bone directions by assigning a fixed direction to them. It will ///// not work properly on bones that take part in aiming, etc. It merely exists as a ///// method to retrofit existing models with bones in wierd orientations. } // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // simulated function SetAim( rotator Aim ) { local int i; if ( Flesh == None ) return; // TODO: Hackriffic. Make the transormation matrix confusitroniness. Maybe make a "skeleton" class, // which would be owned by the Thing, that stores our current animation positions. Technically that // should go here so maybe the meshcontroller could be instantiated. Flesh.SetRotation( Aim ); Aim -= Flesh.Rotation; for (i=0;i<Default.AimBones.Length;i++) { Aim.Roll = Aim.Yaw; Aim.Yaw = -Aim.Pitch; Aim.Pitch = 0; SetBoneRot( Default.AimBones[i], Aim / Default.AimBones.Length ); } return; } simulated function vector FindBoneLocation( name Bone, optional out vector Rot ) { local Coords Coords; if ( Flesh == None || Bone == '' ) return Vect(0,0,0); Coords = Flesh.GetBoneCoords( Bone ); Rot = Coords.XAxis; return Coords.Origin; } simulated function rotator FindBoneRotation( name Bone ) { if ( Flesh == None || Bone == '' ) return Rot(0,0,0); return Flesh.GetBoneRotation( Bone ); } // TODO: This function name is misleading. simulated function vector FindHeldLocation( optional out vector Rot ) { if ( Flesh == None || Default.HeldBone == '' ) return Vect(0,0,0); return FindBoneLocation( Default.HeldBone, Rot ); } // TODO: This function name is misleading. simulated function vector FindHeldRotation( optional out vector Loc ) { if ( Flesh == None || Default.HeldBone == '' ) return Vect(0,0,0); Loc = FindBoneLocation( Default.HeldBone ); return Vector( FindBoneRotation( Default.HeldBone ) ); } simulated function SetBoneRot( name BoneName, rotator Rot ) { if ( Flesh == None ) return; TweakBoneRotation( BoneName, Rot ); Flesh.SetBoneRotation( BoneName, Rot ); } function SetBoneDir( name BoneName, rotator Dir, optional float TweenTime ) { local TweenBoneDirection NewTween; if ( Flesh == None ) return; if ( TweenTime != 0 ) { NewTween.BoneName = BoneName; NewTween.Direction = Dir; NewTween.TweenTime = TweenTime; NewTween.StartTweenTime = TweenTime; BoneTweenData[BoneTweenData.Length] = NewTween; } Flesh.SetBoneDirection( BoneName, Dir,,1 ); } function bool TweenBone( out TweenBoneDirection BoneData, float Delta ) { local bool Result; Result = false; if ( Flesh == None ) return Result; BoneData.TweenTime -= Delta; if ( BoneData.TweenTime < 0 ) { BoneData.TweenTime = 0; Result = true; } Flesh.SetBoneDirection( BoneData.BoneName, BoneData.Direction,,BoneData.TweenTime/BoneData.StartTweenTime ); return Result; } // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // simulated function Tick( float Delta ) { local int i; for (i=0;i<BoneTweenData.Length;i++) if ( TweenBone( BoneTweenData[i], Delta ) ) BoneTweenData.Remove(i,1); Super.Tick( Delta ); } // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // // **************************************************************************************** // defaultproperties { Bonenames(0)=Spine Bonenames(1)=Head Bonenames(2)=LShoulder Bonenames(3)=RShoulder Bonenames(4)=LThigh Bonenames(5)=RThigh Bonenames(6)=LFARM Bonenames(7)=RFARM Bonenames(8)=RightHand Bonenames(9)=LFoot Bonenames(10)=RFoot Bonenames(11)=Bone_Weapon AimBones(0)=Spine HeldBone=Bone_Weapon ViewBone=Head SafeMesh1=Mesh'Jugg.JuggMaleA' SafeMesh2=Mesh'Jugg.JuggFemaleA' }
AlphaOne: Could I ask anyone to please taker their time to post a complete solution(s) here because I'm having a hard time putting your code together. It would be REALLY nice if it was downloadable...(I like being spoon fed)
Foxpaw: Unfortunately, because all of the people working on this have different applications it's kind of hard to package one solution.. however, I suspect that a mutator could be made of this (I think theres a modifydamage function or something that could cause the karmatizing and an array of pointers to pawns and their respective karma-ing specific values could possibly be stored there. Unfortunately, since my application is highly different, I can't really be the one to do this. Most of the snippets I've provided here are fairly general, but the last one is highly specific and wouldn't work without extensive modification.
Plane Guy: RAGDOLL POSTULATION OUT OF NOWHERE. My mod involves grabbing a player pawn and dragging him around. Thing is, with the ragdoll I have to attach to a bone and karma yank the doll around BUT the server has no ragdoll (else it crashes). Would it be plausible to karma impulse the "standing" player pawn on the server and get karma-based dragging client-side? Does the flippity-floppity nature of ragdolls require I act on it directly with Karma, or will the client recognize the server has moved the doll and flail its limbs accordingly?
Foxpaw: Hmm, I'm pretty sure that it's possible to have an actor using PHYS_KarmaRagdoll on the server.. though I don't know how well xPawns handle that. Anyways, Karma ragdolls don't replicate very well, so it would probrably be best to do the bone attach to whatever they're getting dragged behind and have the dragging completely client side. (And have whatever is dragging them get replicated)
Plane Guy: Hmmmm. Well I had it working on client-side only, but when the dragging stops, the pawn reappears where it was first ragdolled. Would I have to replicate that movement BACK to the server (server is NOT the man, oh noes)? Wouldn't I have to, once replicated to the server, replicate it back to the other clients? Gee, this is getting complicated.
Foxpaw: I recall someone else having a similar issue. This seems to be a behaviour of the xPawn class, though I can't say that with 100% certainty. xPawns store movements and then go off of their saved movement - this reduces apparent lag in network games. However, it also means that if you move a Pawn by a means other than the Pawn was intended to move, the stored movements don't get updated. I'm not sure if there's an easy way around that or not, since I haven't really looked at the network stuff for Pawn.
Dirk Fist: I have'nt done much with 2k3, But could'nt the Actor>NavigationPoint>SmallNavigationPoint>Teleporter accept() function, be modified for setting an actors location?
Plane Guy: I don't know if it matters, but I'm in 2K4 now. Perhaps a "ragdoll location" variable, and have the server actor.move() to it while ragdolled. It'd be choppy though, though since the server is going to update the client with its old location.
The other idea I had was to karma impluse the same on all clients - replicate the impulse vector - and actor.move() only on the server. Everyone'd get a slightly different karma show, but I don't think they'd notice.
OlympusMons: Ok guys just an idea. I was thinking after reading this, I know it need a Category:Legacy Refactor Me Please! Anyways I was thinking maybe you could subclass svehicle or ONSvehicle or one of those and since those are pawns, which use skeletal anims and karma, maybe you could make the player use a pawn and hope in then when you get KO'd you hope out of the vehicle, play KO stuff, then hop back in. Just an idea anyways Im not really skilled at uscript, yet hehe, so i'm not going to attempt this, yet hehe. Well feel free to give me what for if I dunno what Im talking about.