Circle jump physics

  • golden_elite13

    Hi, you guys may know walljumping & circle jumping are unvolontarily the best gameplay mechanics of the game, if we put aside traditional competitive gameplay mechanics (of course players who master those glitches fly as gods and this fucks up the gameplay devs imaginated), and I always wondered how the devs have put those magical things unvolontarily. I've seen recently that Comrade (https://github.com/ccomrade/crymp-client) found the code responsible for the circle jump patch (just 1 line of code did the job to remove CJ) ; and I wanted to know if someone (maybe Comrade himself or anybody who understands how Crysis' physics code behave) could explain me how this dumb but wonderful code make the player go up in the air if he turns properly. I have sufficient understanding of C++ in general but I'm not really into physics computing and don't really understand how the game handles this and make this glitch possible. If someone else is interested, the code is here : https://github.com/ccomrade/crymp-client/blob/a2a073d560e136e8c1991e2983ccbcabae3b9d3f/Code/CryGame/Actors/Player/PlayerMovement.cpp#L1346
    line 1346 ; or in Mods/CrysisMod/Code/PlayerMovement.cpp if you installed the ModSDK.

    I would really like to know how it works, at the point where I could implement it in my own game if I wanted, but in a subtle way not easy to master, just like it is in Crysis.

    Thanks :)

    1
  • golden_elite13

    Little update on the personal code review I'm trying to do with Crysis code relative to jump physics and circle jump, I just discovered the "passive" circle jump we can still do in 6156 version (controling direction in the air) was a feature and not a bug : just put the following block in a comment to get a "realistic" game where air control is not possible (gameplay becomes just like other fps, i.e. boring, but I thought it was interesting to share) :

    else if (move.len2()>0.01f)//"passive" air control, the player can air control as long as it is to decelerate
    	{	
     		/*Vec3 currVelFlat(m_stats.velocity - m_stats.velocity * baseMtxZ);
    		Vec3 moveFlat(move - move * baseMtxZ);
     
    		float dot(currVelFlat.GetNormalizedSafe(ZERO) * moveFlat.GetNormalizedSafe(ZERO));
    
    		if (dot<-0.001f)
    		{
    			desiredVel = (moveFlat-currVelFlat)*max(abs(dot)*0.3f,0.1f);
    		}
    		else
    		{
    			desiredVel = moveFlat*max(0.5f,1.0f-dot);
    		}
    
    		float currVelModSq(currVelFlat.len2());
    		float desiredVelModSq(desiredVel.len2());
    
    		if (desiredVelModSq>currVelModSq)
    		{
    			desiredVel.Normalize();
    			desiredVel *= max(1.5f,sqrtf(currVelModSq));
    		}*/
    	}


    But, on the contrary, if you want to make the CJ even more cheaty and easier, just change the line responsible for its patch to this (line 1346) :
    desiredVel += (desiredVel + vz);


    0
  • Comrade

    Unfortunately, i didn't find the circle jump fix by trying to understand what the player movement code exactly does. It would be really cool, but the code is very messy and i also forgot a lot from my linear algebra lessons. That's why i just picked a few functions that could possibly contain the code responsible for the circle jump bug and then spent a few days by comparing their disassembly from 5767 and 6156. And it worked. Everything looked more or less the same, except the CPlayerMovement::ProcessOnGroundOrJumping function, which contains additional instructions in 6156. After replacing them with nops, circle jump immediately started to work. Tracing these instructions back to C++ was only a formality. So yes, nothing special actually.

    By the way, here's the original commit:

    https://github.com/ccomrade/crymp-client/commit/c450b300f03fb6e85c0d668cc3fb6e1dd2b17f08

    As you already said, they just added this to get rid of the circle jump bug:
    //be sure desired velocity is flat to the ground
    Vec3 vz = desiredVel * baseMtxZ;
    desiredVel -= vz;

    It's only a workaround. The magic lies above it somewhere in all those vector calculations. Also, they changed the player movement code in Crysis Wars. Jedi95 said that removing this workaround has no effect there.

    1
  • golden_elite13

    Thank you dude. This bug really seems magic to me, and as I understood, it only seems to work when you start jumping from a sloping surface; and I can't understand how and why. As you said those vector calculations seem too messy and dark to me to fully understand, and maybe the whole thing lies in the physics engine finally (as at this time I've not traced all the places of computation that together does the CJ happen) so this may be impossible to know why this bug happen. The passive air control is pretty easy to understand, but why the fuck you go up in the air if you started from a certain point makes me wonder. I find this stuff interesting but I doubt I'll be finally able to understand the computation behind that. If so, It would be really fun then to export the code to make and work in any other game, with just the same way and the same feeling as it works in Crysis :D Of course we could emulate it already but it would be less funny and subtle I guess.

    Thank you for your answer dude. If you look around all this stuff and find something interesting, let me know! :)

    edit: I added those lines at the place of the patch code :
    //be sure desired velocity is flat to the ground
    		Vec3 vz = desiredVel * baseMtxZ;
    		//desiredVel -= vz;
    		float white[4] = {1,1,1,1};
    		gEnv->pRenderer->Draw2dLabel( 100, 50, 4, white, false, "%f %f %f", vz.x, vz.y, vz.z );

    It does print the vz vector, and what happens is that the Z value is moving up while you run on a sloped surface (upwards) and returns to 0 once your movement is done. And when you start the CJ from the sloped place, the Z value goes up again at the moment you go up while turning in the air. Of course the Z value stays at 0 if you start jumping from a flat surface, from which you can practice the usual "patched" circle jump which is simple air control, but without going up and ultimately falling down (like in 6156). If someone finds out some interesting logic around that which could explain the behaviour of CJ, he's welcome : This is the topic of circle jump studies :o)

    0
  • Zi;

    https://github.com/ccomrade/crymp-client/blob/a2a073d560e136e8c1991e2983ccbcabae3b9d3f/Code/CryGame/Actors/Player/PlayerMovement.cpp#L1299

    Game basically remembers ground normal where you jump, adjusts velocity given the original ground normal and then compensates back the Z direction of velocity ( line 1345 ). If you set cl_circleJump to 1, then nothing is compensated back on Z-axis and you go flying.

    Edit: I wouldn't really search for physics or anything, I bet Crytek devs just tried really hard to approximate somewhat satisfying movement and that's how movement code was created :D

    2
  • golden_elite13

    Ohh, thank you dude =)

    So, basically, the initial ground normal is used in addition to player speed when he jumps to compute velocity during hover time ; and they forgot to substract the Z velocity which still applies during the fall and make the player goes up ? However I'm still wondering how the "go up" phase only applies cyclically and not during the whole air movement. Also it's crazy how the CJ works well when you start from certain spots and not-at-all from others ; it must be sensibly linked to the starting-point normal.

    Thank you for your analysis anyway =)

    If I get some time I'll make a little 3D program using the same computations to study the behaviour of the code and have a bit fun with it :o)

    0
  • Zi;

    I think you can find answer in this branch
    https://github.com/ccomrade/crymp-client/blob/a2a073d560e136e8c1991e2983ccbcabae3b9d3f/Code/CryGame/Actors/Player/PlayerMovement.cpp#L1270
    Basically this branch takes care of passive air control (making you able to change directions when already air... as well all know, in real life this is impossible, but yeah... whatever :D). Btw, it's pretty fun if you comment this entire branch out, you will get realistic jump behavior

    Edit:
    after some poking around, it looks like part that creates circle jump is this line
    https://github.com/ccomrade/crymp-client/blob/a2a073d560e136e8c1991e2983ccbcabae3b9d3f/Code/CryGame/Actors/Player/PlayerMovement.cpp#L1283
    even if everything else is commented out besides this line, it brings CJ.
    So basically all you need is dot product of current velocity and desired movement (basically camera direction) and just glue
    finalVelocity = moveFlat * max(0.5f, 1.0f - dot);

    it together

    0
  • golden_elite13

    Thank you for update. As I pointed out in a previous comment, yeah, it seems that passive air control was a feature and not a bug :D

    Anyway I can't understand why CJ only applies cyclically (you turn left you go up, you turn right you start falling, you turn left you go up again, etc (or vice versa)) : how the z vel varies when in air whereas, as you pointed out, the normal is saved before the jump and should not change during the air time.

    0
  • Zi;

    You can always record mouse input + change of position data in Crysis, train ML model and replicate it in your game that way :D

    I think, core reason could be this line:

    https://github.com/ccomrade/crymp-client/blob/a2a073d560e136e8c1991e2983ccbcabae3b9d3f/Code/CryGame/Actors/Player/PlayerMovement.cpp#L1335

    Because modifiedSlopeNormal still contains Z component, which is this way added to desiredVel => it affects up/down velocity, and if cl_circleJump is 1
    https://github.com/ccomrade/crymp-client/blob/a2a073d560e136e8c1991e2983ccbcabae3b9d3f/Code/CryGame/Actors/Player/PlayerMovement.cpp#L1345
    then Z component is not subtracted from desiredVel, because (1.0f - g_pGameCVars->cl_circleJump) * desiredVel * baseMtxZ evaluates to (1-1)*... = 0*... = 0, and there you go with lift

    And as to, why it works only with left/right cycles, answer could be this line
    https://github.com/ccomrade/crymp-client/blob/a2a073d560e136e8c1991e2983ccbcabae3b9d3f/Code/CryGame/Actors/Player/PlayerMovement.cpp#L1325
    Multiplier for that modifiedSlopeNormal lift is alignment and that is simply dot product of desiredVel and modifiedGroundNormal, and as you know from basic geometry, dot product is at it's highest when angle between vectors is 90°, the higher the angle between ground normal and desired velocity, the more of modifiedSlopeNormal is added to desiredVel

    0