This time we're going to face
fuser2
parameter that was introduced in CS 1.6, so we will have to use
pm_shared.cpp from
ReGameDLL project instead of
pm_shared.cpp from
HLSDK.
Let's consider
PM_PlayerMove function from the article about strafe physics in more detail:
As you can see, there are several types of movement, and the functions we have considered, such as
PM_Friction,
PM_WalkMove and
PM_AirMove, are called for type
MOVETYPE_WALK
. With
PM_Duck, which is responsible for the duck process, we will get acquainted in the next article, but for now we will be interested in the call of
PM_Jump before
PM_Friction. Note that this call occurs if
IN_JUMP
bit is set in the
cmd.buttons
variable that stores the states of the buttons. When we press the jump button,
+jump command is sent to the engine, and
IN_JUMP
bit becomes 1. When the button is released, the
–jump command is sent, and at the same frame
IN_JUMP
is reset. Read more about this and why we can jump using the scroll, under this
spoiler
Let's take a look at input.cpp and find the function InitInput. Here we find that command +jump initiates a call of the function IN_JumpDown, which changes the variable state
of the jump button: the first and second bits of state
are set. The first bit means that the button is pressed in general, and the second - that the button was pressed just now. The –jump command initiates a call of IN_JumpUp, which clears the first bit (button is no longer pressed) and sets the third bit (the button is released just now).
Further in CL_ButtonBits we find that the IN_JUMP
bit in cmd.buttons
is set if state
has the first or the second bit set. Thus, if we jumped with the help of Space for example, then IN_JUMP
will be set starting from the frame when we pressed the Space. At the frame when the Space was released, the IN_JUMP
bit has been cleared. If we jumped with the help of a scroll, then the commands +jump and –jump were sent one after another at the same frame. Then at this frame the first bit of state
is zero, that is, the jump button is considered not pressed. However, since the second bit of state
is set, then IN_JUMP
in cmd.buttons
will also be set, which means PM_Jump will be called. That's why we can jump with the scroll.
Now it's time to see
PM_Jump:
Note that if there was a bit
IN_JUMP
at the previous frame, then there won't be a jump at the current frame. Otherwise, we could do bhop just by holding the
Space. Moreover, this condition also means that the command
+jump;-jump sent with the scroll will push us off from the ground only if there was no jump command at the previous frame. How does this affect our bhop?
We introduce the term
FOG (frames on the ground) - the number of frames that player spends on the ground during one bhop.
Theoretically bhop can be done with
Space, but human reaction is not good enough to send a jump command in time. By scrolling we send several jump commands at once, and it allows us to minimize FOG and lose less speed. However, too fast scrolling will lead to the fact that several jump commands can go in a row, which means that only the first one can cause a jump, the rest will be useless. A good distribution of jump commands is when we alternate frames with
+jump and without it. In this case, we guarantee 1 or 2 FOG. In practice, the distribution for every player is different, but, nevertheless, with time everyone succeeds in minimizing the number of bhops with more than 2 FOG.
Horizontal speed
However does lesser FOG always means lesser loss of speed? First of all, it should be clarified that we are interested in horizontal speed now. After all, horizontal speed before hitting the ground is what lj stats shows us as a bhop prestrafe. For simplicity we will not pay attention to the variable
fuser2
for now, and also let the vertical speed on the ground be zero. Consider two cases:
1) 1 FOG bhop. A successful jump occurred at the same frame when we were on the ground. Inside
PM_Jump, the variable
pmove->onground
became equal to -1, therefore
PM_Friction in
PM_PlayerMove will not be called. The only function that can affect horizontal speed is
PM_PreventMegaBunnyJumping in
PM_Jump. Here's what it looks like:
Here
pmove->maxspeed
(with usp or knife) is 250 units/s, so
maxscaledspeed = 300
units/s. If the speed is more than 300 it becomes equal to
maxscaledspeed * 0.8 = 240
units/s (keeping the direction). Otherwise speed does not change at all!
2) 2 FOG bhop. A successful jump occurred one frame after landing. At the first frame on the ground, only
PM_Friction affected us taking away 4 percent of the speed, and at the second - only
PM_PreventMegaBunnyJumping. That is, at the first frame the speed will be lost in any case, but if after that it turns out to be not more than 300 units/s, then the second frame will pass without loss.
Thus, we could do without a loss of speed if we performed a bhop of 1 FOG at a speed of not more than 300 units/s. However, in practice, the large distances between the blocks and the need to change direction during bhop force us to pick up speed over 300. And then it turns out to be more profitable to make 2 FOG bhop! For example, let our speed before landing be 310 units/s. Then 1 FOG bhop will cut it to 240, and 2 FOG bhop will cut it to 297.6 units/s. Below we will correct these calculations taking into account the vertical speed and the variable
fuser2
, but generally the result will be the same.
While we can somehow control the speed with strafes, it is physiologically impossible to fully control the amount of FOG. Scrolling can give us a high chance of making FOG less than 3, however whether it is 1 or 2 FOG - partly player experience, partly random element. On the one hand, this dependence on luck is not very pleasant when it comes to competition. On the other hand, the unpredictability of FOG teaches the player to assume before each bhop the possibility of the worst option and to quickly make a decision after bhop. And therein lies another highlight of kreedz, because of which it became interesting to such a large number of people.
Vertical speed
Each frame reduces the vertical speed due to simulated gravity, which is specified by the cvar
sv_gravity, an analogue of the acceleration of gravity. This happens in the functions
PM_AddCorrectGravity and
PM_FixupGravityVelocity, which essentially do the following:
Here
ent_gravity
can be considered equal to 1,
pmove->movevars->gravity
is exactly the value of
sv_gravity (800 by default), and the duration of the frame in seconds
pmove->frametime
at 100 fps is 0.01. In total, the vertical speed inside the function will decrease by 4 units/s. If
PM_Jump is not called inside
PM_PlayerMove, then
PM_AddCorrectGravity and
PM_FixupGravityVelocity will reduce the vertical speed by 8 units/s, and then, if we are on the ground, it will be reset. If
PM_Jump is called, then the vertical speed in it becomes
sqrt (2 * 800 * 45) = 268.33
unit/s, and then decreases by 8 units/s due to two calls of
PM_FixupGravityVelocity (inside
PM_Jump and later in
PM_PlayerMove).
From here it is easy to get the height of the jump. We can assume that on the ground the initial speed is
V0 = sqrt (2 * 800 * 45)
unit/s, the acceleration is directed down and equal to
a = 800
units/s^2 . Then the height will be
H = V0 ^ 2 / (2 * a) = 2 * 800 * 45 / (2 * 800) = 45
units.
Note that if fps decreases, then a smaller number of frames will be necessary for the jump, however, due to the fact that the formula uses
pmove->frametime
, the height of the jump will remain almost the same. If you look again at the article about strafe physics, you will find that the same principle is laid down in the functions
PM_Accelerate,
PM_AirAccelerate and
PM_Friction.
And one more important point: when we make a jump, we have
PM_AddCorrectGravity called before
PM_PreventMegaBunnyJumping, so the vertical speed at this moment will not be zero, but -4 units/s. Consequently, the total speed will be more than 300 when the horizontal component is greater than
sqrt (300^2 - 4^2) = 299.973
unit/s. So when jump statistics says that a good prestrafe must be less than 300 units/s, it deceives you a little.
fuser2
And now the most interesting part. In the article about HighJump physics we became more familiar with the friction in the GoldSource engine. During the evolution of Counter-Strike, the parameter
fuser2
was added to this friction, which affects all three components of speed. At the moment of the jump in
PM_Jump function the variable
fuser2
becomes equal to
1315.789429
(looks like a magic number, but I suppose it is
100 * 1000 * 19.0 / 4.0
). Then each frame in
PM_PlayerMove we have a call of
PM_ReduceTimers, which along with other things reduces
fuser2
by the length of the frame in milliseconds (i.e. at 100 fps 10 is subtracted from
fuser2
). If within 1.31 seconds a new jump does not occur, then
fuser2
reaches zero and no longer affects physics.
However even on a flat surface
fuser2
does not have time to be reset between bhops. Because of this in
PM_Jump, before the call of
PM_FixupGravityVelocity, the vertical speed is truncated. The horizontal speed at the beginning of the function
PM_WalkMove also changes in the same way:
PM_WalkMove is called only on the ground, and we feel it when doing normal jumps on any climb section. The less time we spent in the air, the longer we have to pause after landing before the next jump. Now you understand that the
fuser2
variable is to blame for it.
Let's go back to a flat surface. First, we just run and jump. At this moment,
fuser2
is zero, so at 100 fps we know for sure that the vertical speed is 268.33 units/s, and that we will stay in the air for 66 frames (we are not considering stand-up bhop yet), and that the maximum jump height is 45 units. Suppose that by the end of the flight we reached a speed of 310 units/s. As it was found earlier, it would be more profitable for us to make 2 FOG bhop. Let's check if this result changes with the account of
fuser2
.
At the first frame on the ground
PM_Friction will take 4 percent from the horizontal speed, leaving us 297.6 units/s. After 66 frames in the air and 1 frame on the ground
fuser2 =1315.789429 - 67 * 10 = 645.789429
, therefore after
PM_WalkMove the horizontal speed will become equal to
297.6 * (100.0 - 645.789429 * 0.001 * 19.0) * 0.01 = 261.08
unit/s. At the second frame inside
PM_PreventMegaBunnyJumping, we find that
261.08 < 299.973
, so the horizontal speed will not change. In the case of 1 FOG, the speed would be cut to
299.973 * 0.8 = 239.98
units/s, so 2 FOG in this case really remained more profitable. By the way, if we did DropBhop, during which we had to pick up a fairly large horizontal speed, then 3+ FOG bhop could be even more profitable.
As for the vertical speed, at the second frame on the ground
fuser2
will decrease by another 10, and the vertical speed before calling
PM_FixupGravityVelocity will be
268.33 * (100.0 - 635.789429 * 0.001 * 19.0) * 0.01 = 235.92
unit/s. Then, after bhop we will spend 58 frames in the air, and the jump height will be just 34.78 units. At the next bhop, depending on
fuser2
and the number of FOG, we will be in the air for about 56-58 frames.
If we used a stand-up bhop instead of the usual one on a flat surface, we would have spent more (about 64-65) frames in the air between jumps cause of ducking. Due to this,
fuser2
would have time to decrease more, and jumps would be about 1 unit higher, and the speed in the case of 2 FOG bhop would not decrease so much. However, this does not mean that stand-up bhop is always better than usual one, because it takes more time. That is why experienced players often try to do a regular bhop instead of a stand-up if possible.
We will return to the topic of stand-up bhop and explore the work of duck in the next article.