#include "sv_dodging.qh"
+// TODO the CSQC blocks in this sv_ file are currently not compiled but will be when dodging prediction gets enabled
+
#define PHYS_DODGING g_dodging
#define PHYS_DODGING_DELAY autocvar_sv_dodging_delay
#define PHYS_DODGING_DISTANCE_THRESHOLD autocvar_sv_dodging_wall_distance_threshold
-#define PHYS_DODGING_FROZEN_NODOUBLETAP autocvar_sv_dodging_frozen_doubletap
+#define PHYS_DODGING_FROZEN_DOUBLETAP autocvar_sv_dodging_frozen_doubletap
#define PHYS_DODGING_HEIGHT_THRESHOLD autocvar_sv_dodging_height_threshold
-#define PHYS_DODGING_HORIZ_SPEED autocvar_sv_dodging_horiz_speed
-#define PHYS_DODGING_HORIZ_SPEED_FROZEN autocvar_sv_dodging_horiz_speed_frozen
+#define PHYS_DODGING_HORIZ_SPEED_MIN autocvar_sv_dodging_horiz_speed_min
+#define PHYS_DODGING_HORIZ_SPEED_MAX autocvar_sv_dodging_horiz_speed_max
+#define PHYS_DODGING_HORIZ_FORCE_SLOWEST autocvar_sv_dodging_horiz_force_slowest
+#define PHYS_DODGING_HORIZ_FORCE_FASTEST autocvar_sv_dodging_horiz_force_fastest
+#define PHYS_DODGING_HORIZ_FORCE_FROZEN autocvar_sv_dodging_horiz_force_frozen
#define PHYS_DODGING_RAMP_TIME autocvar_sv_dodging_ramp_time
#define PHYS_DODGING_UP_SPEED autocvar_sv_dodging_up_speed
#define PHYS_DODGING_WALL autocvar_sv_dodging_wall_dodging
#define PHYS_DODGING_AIR autocvar_sv_dodging_air_dodging
#define PHYS_DODGING_MAXSPEED autocvar_sv_dodging_maxspeed
+#define PHYS_DODGING_AIR_MAXSPEED autocvar_sv_dodging_air_maxspeed
// we ran out of stats slots! TODO: re-enable this when prediction is available for dodging
#if 0
#define PHYS_DODGING STAT(DODGING, this)
#define PHYS_DODGING_DELAY STAT(DODGING_DELAY, this)
#define PHYS_DODGING_DISTANCE_THRESHOLD STAT(DODGING_DISTANCE_THRESHOLD, this)
-#define PHYS_DODGING_FROZEN_NODOUBLETAP STAT(DODGING_FROZEN_NO_DOUBLETAP, this)
+#define PHYS_DODGING_FROZEN_DOUBLETAP STAT(DODGING_FROZEN_DOUBLETAP, this)
#define PHYS_DODGING_HEIGHT_THRESHOLD STAT(DODGING_HEIGHT_THRESHOLD, this)
-#define PHYS_DODGING_HORIZ_SPEED STAT(DODGING_HORIZ_SPEED, this)
-#define PHYS_DODGING_HORIZ_SPEED_FROZEN STAT(DODGING_HORIZ_SPEED_FROZEN, this)
+#define PHYS_DODGING_HORIZ_SPEED_MIN STAT(DODGING_HORIZ_SPEED_MIN, this)
+#define PHYS_DODGING_HORIZ_SPEED_MAX STAT(DODGING_HORIZ_SPEED_MAX, this)
+#define PHYS_DODGING_HORIZ_FORCE_SLOWEST STAT(DODGING_HORIZ_FORCE_SLOWEST, this)
+#define PHYS_DODGING_HORIZ_FORCE_FASTEST STAT(DODGING_HORIZ_FORCE_FASTEST, this)
+#define PHYS_DODGING_HORIZ_FORCE_FROZEN STAT(DODGING_HORIZ_FORCE_FROZEN, this)
#define PHYS_DODGING_RAMP_TIME STAT(DODGING_RAMP_TIME, this)
#define PHYS_DODGING_UP_SPEED STAT(DODGING_UP_SPEED, this)
#define PHYS_DODGING_WALL STAT(DODGING_WALL, this)
#define PHYS_DODGING_AIR STAT(DODGING_AIR, this)
#define PHYS_DODGING_MAXSPEED STAT(DODGING_MAXSPEED, this)
+#define PHYS_DODGING_AIR_MAXSPEED STAT(DODGING_AIR_MAXSPEED, this)
#endif
#ifdef CSQC
bool autocvar_sv_dodging_sound;
-// set to 1 to indicate dodging has started.. reset by physics hook after dodge has been done..
-.float dodging_action;
-
-// the jump part of the dodge cannot be ramped
-.float dodging_single_action;
-
#include <common/animdecide.qh>
#include <common/physics/player.qh>
// the jump part of the dodge cannot be ramped
.float dodging_single_action;
-
// these are used to store the last key press time for each of the keys..
.float last_FORWARD_KEY_time;
.float last_BACKWARD_KEY_time;
// and to ramp up the dodge acceleration in the physics hook.
.float last_dodging_time;
-// This is the velocity gain to be added over the ramp time.
-// It will decrease from frame to frame during dodging_action = 1
-// until it's 0.
-.float dodging_velocity_gain;
+// the total speed that will be added over the ramp time
+.float dodging_force_total;
+// the part of total yet to be added
+.float dodging_force_remaining;
#ifdef CSQC
.int pressedkeys;
#endif
-// returns true if the player is close to a wall
-bool check_close_to_wall(entity this, float threshold)
-{
- if (PHYS_DODGING_WALL == 0) { return false; }
-
#define X(dir) \
tracebox(this.origin, this.mins, this.maxs, this.origin + threshold * dir, true, this); \
if (trace_fraction < 1 && !(trace_dphitq3surfaceflags & Q3SURFACEFLAG_SKY)) \
return true;
+// returns true if the player is close to a wall
+bool is_close_to_wall(entity this, float threshold)
+{
X(v_right);
X(-v_right);
X(v_forward);
X(-v_forward);
-#undef X
return false;
}
-bool check_close_to_ground(entity this, float threshold)
+bool is_close_to_ground(entity this, float threshold)
{
- return IS_ONGROUND(this) ? true : false;
+ if (IS_ONGROUND(this)) return true;
+ X(-v_up); // necessary for dodging down a slope using doubletap (using `+dodge` works anyway)
+
+ return false;
+}
+
+#undef X
+
+float determine_force(entity player) {
+ if (PHYS_FROZEN(player)) return PHYS_DODGING_HORIZ_FORCE_FROZEN;
+
+ float horiz_vel = vlen(vec2(player.velocity));
+ return map_bound_ranges(horiz_vel,
+ PHYS_DODGING_HORIZ_SPEED_MIN, PHYS_DODGING_HORIZ_SPEED_MAX,
+ PHYS_DODGING_HORIZ_FORCE_SLOWEST, PHYS_DODGING_HORIZ_FORCE_FASTEST);
}
bool PM_dodging_checkpressedkeys(entity this)
{
- if(!PHYS_DODGING)
- return false;
-
bool frozen_dodging = (PHYS_FROZEN(this) && PHYS_DODGING_FROZEN(this));
- bool frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_NODOUBLETAP);
+ bool frozen_no_doubletap = (frozen_dodging && !PHYS_DODGING_FROZEN_DOUBLETAP);
+
+ float tap_direction_x = 0;
+ float tap_direction_y = 0;
+ bool dodge_detected = false;
+ vector mymovement = PHYS_CS(this).movement;
- // first check if the last dodge is far enough back in time so we can dodge again
+ #define X(COND,BTN,RESULT) \
+ if (mymovement_##COND) { \
+ /* is this a state change? */ \
+ if(!(PHYS_DODGING_PRESSED_KEYS(this) & KEY_##BTN) || frozen_no_doubletap) { \
+ tap_direction_##RESULT; \
+ if ((time - this.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(this) || frozen_no_doubletap) { \
+ dodge_detected = true; \
+ } else if(PHYS_INPUT_BUTTON_DODGE(this)) { \
+ dodge_detected = true; \
+ } \
+ this.last_##BTN##_KEY_time = time; \
+ } \
+ }
+ X(x < 0, BACKWARD, x--);
+ X(x > 0, FORWARD, x++);
+ X(y < 0, LEFT, y--);
+ X(y > 0, RIGHT, y++);
+ #undef X
+
+ if (!dodge_detected) return false;
+
+ // this check has to be after checking keys:
+ // the first key press of the double tap is allowed to be before dodging delay,
+ // only the second has to be after, otherwise +dodge gives an advantage because typical repress time is 0.1 s
+ // or higher which means players using +dodge would be able to do it more often
if ((time - this.last_dodging_time) < PHYS_DODGING_DELAY)
return false;
makevectors(this.angles);
- bool wall_dodge = false;
+ bool can_dodge = (is_close_to_ground(this, PHYS_DODGING_HEIGHT_THRESHOLD) && (PHYS_DODGING_MAXSPEED == 0 || vdist(this.velocity, <, PHYS_DODGING_MAXSPEED)));
+ bool can_wall_dodge = (PHYS_DODGING_WALL && is_close_to_wall(this, PHYS_DODGING_DISTANCE_THRESHOLD));
+ bool can_air_dodge = (PHYS_DODGING_AIR && (PHYS_DODGING_AIR_MAXSPEED == 0 || vdist(this.velocity, <, PHYS_DODGING_AIR_MAXSPEED)));
+ if (!can_dodge && !can_wall_dodge && !can_air_dodge) return false;
- if(!PHYS_DODGING_AIR)
- if(!check_close_to_ground(this, PHYS_DODGING_HEIGHT_THRESHOLD))
- {
- wall_dodge = check_close_to_wall(this, PHYS_DODGING_DISTANCE_THRESHOLD);
- if(!wall_dodge) // we're not on the ground, and wall dodging isn't allowed, end it!
- return true;
- }
+ this.last_dodging_time = time;
- if(!wall_dodge && PHYS_DODGING_MAXSPEED && vdist(this.velocity, >, PHYS_DODGING_MAXSPEED))
- return false;
+ this.dodging_action = 1;
+ this.dodging_single_action = 1;
- float tap_direction_x = 0;
- float tap_direction_y = 0;
- bool dodge_detected = false;
- vector mymovement = PHYS_CS(this).movement;
+ this.dodging_force_total = determine_force(this);
+ this.dodging_force_remaining = this.dodging_force_total;
- #define X(COND,BTN,RESULT) \
- if (mymovement_##COND) \
- /* is this a state change? */ \
- if(!(PHYS_DODGING_PRESSED_KEYS(this) & KEY_##BTN) || frozen_no_doubletap) { \
- tap_direction_##RESULT; \
- if ((time - this.last_##BTN##_KEY_time) < PHYS_DODGING_TIMEOUT(this) || frozen_no_doubletap) \
- dodge_detected = true; \
- if(PHYS_INPUT_BUTTON_DODGE(this)) \
- dodge_detected = true; \
- this.last_##BTN##_KEY_time = time; \
- }
- X(x < 0, BACKWARD, x--);
- X(x > 0, FORWARD, x++);
- X(y < 0, LEFT, y--);
- X(y > 0, RIGHT, y++);
- #undef X
-
- if (dodge_detected)
- {
- this.last_dodging_time = time;
-
- this.dodging_action = 1;
- this.dodging_single_action = 1;
+ this.dodging_direction.x = tap_direction_x;
+ this.dodging_direction.y = tap_direction_y;
- this.dodging_velocity_gain = PHYS_DODGING_HORIZ_SPEED;
+ // normalize the dodging_direction vector.. (unlike UT99) XD
+ float length = sqrt(this.dodging_direction.x ** 2 + this.dodging_direction.y ** 2);
- this.dodging_direction_x = tap_direction_x;
- this.dodging_direction_y = tap_direction_y;
+ this.dodging_direction.x = this.dodging_direction.x / length;
+ this.dodging_direction.y = this.dodging_direction.y / length;
- // normalize the dodging_direction vector.. (unlike UT99) XD
- float length = this.dodging_direction_x * this.dodging_direction_x
- + this.dodging_direction_y * this.dodging_direction_y;
- length = sqrt(length);
-
- this.dodging_direction_x = this.dodging_direction_x * 1.0 / length;
- this.dodging_direction_y = this.dodging_direction_y * 1.0 / length;
- return true;
- }
- return false;
+ return true;
}
void PM_dodging(entity this)
{
- if (!PHYS_DODGING)
- return;
+ // can't use return value from PM_dodging_checkpressedkeys because they're called from different hooks
+ if (!this.dodging_action) return;
// when swimming or dead, no dodging allowed..
if (this.waterlevel >= WATERLEVEL_SWIMMING || IS_DEAD(this))
{
this.dodging_action = 0;
- this.dodging_direction_x = 0;
- this.dodging_direction_y = 0;
+ this.dodging_direction.x = 0;
+ this.dodging_direction.y = 0;
return;
}
else
makevectors(this.angles);
+ // fraction of the force to apply each frame
// if we have e.g. 0.5 sec ramptime and a frametime of 0.25, then the ramp code
// will be called ramp_time/frametime times = 2 times. so, we need to
// add 0.5 * the total speed each frame until the dodge action is done..
float common_factor = PHYS_DODGING_FRAMETIME / PHYS_DODGING_RAMP_TIME;
+ // NOTE: depending on cl_netfps the client may (and probably will) send more input frames during each server frame
+ // but common_factor uses server frame rate so players with higher cl_netfps will ramp slightly faster
- // if ramp time is smaller than frametime we get problems ;D
- common_factor = min(common_factor, 1);
-
- float horiz_speed = PHYS_FROZEN(this) ? PHYS_DODGING_HORIZ_SPEED_FROZEN : PHYS_DODGING_HORIZ_SPEED;
- float new_velocity_gain = this.dodging_velocity_gain - (common_factor * horiz_speed);
- new_velocity_gain = max(0, new_velocity_gain);
-
- float velocity_difference = this.dodging_velocity_gain - new_velocity_gain;
-
- // ramp up dodging speed by adding some velocity each frame.. TODO: do it! :D
- if (this.dodging_action == 1)
- {
- //disable jump key during dodge accel phase
- if(PHYS_CS(this).movement.z > 0) { PHYS_CS(this).movement_z = 0; }
-
- this.velocity += ((this.dodging_direction_y * velocity_difference) * v_right)
- + ((this.dodging_direction_x * velocity_difference) * v_forward);
-
- this.dodging_velocity_gain = this.dodging_velocity_gain - velocity_difference;
- }
+ float velocity_increase = min(common_factor * this.dodging_force_total, this.dodging_force_remaining);
+ this.dodging_force_remaining -= velocity_increase;
+ this.velocity += this.dodging_direction.x * velocity_increase * v_forward
+ + this.dodging_direction.y * velocity_increase * v_right;
// the up part of the dodge is a single shot action
if (this.dodging_single_action == 1)
this.dodging_single_action = 0;
}
- // are we done with the dodging ramp yet?
- if((this.dodging_action == 1) && ((time - this.last_dodging_time) > PHYS_DODGING_RAMP_TIME))
+ if(this.dodging_force_remaining <= 0)
{
// reset state so next dodge can be done correctly
this.dodging_action = 0;
- this.dodging_direction_x = 0;
- this.dodging_direction_y = 0;
+ this.dodging_direction.x = 0;
+ this.dodging_direction.y = 0;
}
}
+#ifdef CSQC
void PM_dodging_GetPressedKeys(entity this)
{
-#ifdef CSQC
- if(!PHYS_DODGING) { return; }
-
PM_dodging_checkpressedkeys(this);
int keys = this.pressedkeys;
keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
this.pressedkeys = keys;
-#endif
}
+#endif
MUTATOR_HOOKFUNCTION(dodging, PlayerPhysics)
{
- entity player = M_ARGV(0, entity);
+ entity player = M_ARGV(0, entity);
- // print("dodging_PlayerPhysics\n");
+#ifdef CSQC
PM_dodging_GetPressedKeys(player);
+#endif
PM_dodging(player);
}
REPLICATE(cvar_cl_dodging_timeout, float, "cl_dodging_timeout");
-MUTATOR_HOOKFUNCTION(dodging, PlayerPreThink)
-{
- entity player = M_ARGV(0, entity);
-
- STAT(DODGING_TIMEOUT, player) = CS(player).cvar_cl_dodging_timeout;
-}
-
MUTATOR_HOOKFUNCTION(dodging, GetPressedKeys)
{
entity player = M_ARGV(0, entity);
bool autocvar_sv_dodging_frozen;
bool autocvar_sv_dodging_frozen_doubletap;
float autocvar_sv_dodging_height_threshold;
-float autocvar_sv_dodging_horiz_speed;
-float autocvar_sv_dodging_horiz_speed_frozen;
+float autocvar_sv_dodging_horiz_speed_min;
+float autocvar_sv_dodging_horiz_speed_max;
+float autocvar_sv_dodging_horiz_force_slowest;
+float autocvar_sv_dodging_horiz_force_fastest;
+float autocvar_sv_dodging_horiz_force_frozen;
float autocvar_sv_dodging_ramp_time;
float autocvar_sv_dodging_up_speed;
bool autocvar_sv_dodging_wall_dodging;
bool autocvar_sv_dodging_air_dodging;
-float autocvar_sv_dodging_maxspeed = 450;
+float autocvar_sv_dodging_maxspeed;
+float autocvar_sv_dodging_air_maxspeed;
#endif
#if 0
REGISTER_STAT(DODGING, int, g_dodging)
REGISTER_STAT(DODGING_DELAY, float, autocvar_sv_dodging_delay)
REGISTER_STAT(DODGING_DISTANCE_THRESHOLD, float, autocvar_sv_dodging_wall_distance_threshold)
-REGISTER_STAT(DODGING_FROZEN_NO_DOUBLETAP, int, autocvar_sv_dodging_frozen_doubletap)
+REGISTER_STAT(DODGING_FROZEN_DOUBLETAP, int, autocvar_sv_dodging_frozen_doubletap)
REGISTER_STAT(DODGING_HEIGHT_THRESHOLD, float, autocvar_sv_dodging_height_threshold)
-REGISTER_STAT(DODGING_HORIZ_SPEED, float, autocvar_sv_dodging_horiz_speed)
-REGISTER_STAT(DODGING_HORIZ_SPEED_FROZEN, float, autocvar_sv_dodging_horiz_speed_frozen)
+REGISTER_STAT(DODGING_HORIZ_SPEED_MIN, float, autocvar_sv_dodging_horiz_speed_min)
+REGISTER_STAT(DODGING_HORIZ_SPEED_MAX, float, autocvar_sv_dodging_horiz_speed_max)
+REGISTER_STAT(DODGING_HORIZ_FORCE_SLOWEST, float, autocvar_sv_dodging_horiz_force_slowest)
+REGISTER_STAT(DODGING_HORIZ_FORCE_FASTEST, float, autocvar_sv_dodging_horiz_force_fastest)
+REGISTER_STAT(DODGING_HORIZ_FORCE_FROZEN, float, autocvar_sv_dodging_horiz_force_frozen)
REGISTER_STAT(DODGING_RAMP_TIME, float, autocvar_sv_dodging_ramp_time)
REGISTER_STAT(DODGING_UP_SPEED, float, autocvar_sv_dodging_up_speed)
REGISTER_STAT(DODGING_WALL, bool, autocvar_sv_dodging_wall_dodging)
REGISTER_STAT(DODGING_AIR, bool, autocvar_sv_dodging_air_dodging)
REGISTER_STAT(DODGING_MAXSPEED, float, autocvar_sv_dodging_maxspeed)
+REGISTER_STAT(DODGING_AIR_MAXSPEED, float, autocvar_sv_dodging_air_maxspeed)
#endif
/** cvar loopback */
REGISTER_STAT(DODGING_FROZEN, int, autocvar_sv_dodging_frozen)