From d5ee5d91d8efec2a3fc2af6df1ae17b4f5e60626 Mon Sep 17 00:00:00 2001 From: newby Date: Mon, 9 Oct 2023 21:16:08 -0700 Subject: [PATCH 1/3] pm: add prediction for grenades (explosive and conc) --- csqc/pmove.qc | 76 ++++++++++++++++++++++++++++++++++++------ csqc/weapon_predict.qc | 6 ++-- share/physics.qc | 20 +++++++++-- share/weapons.qc | 4 +-- ssqc/scout.qc | 2 ++ ssqc/tfort.qc | 1 + 6 files changed, 91 insertions(+), 18 deletions(-) diff --git a/csqc/pmove.qc b/csqc/pmove.qc index 76fff3cd..0d4fcc96 100644 --- a/csqc/pmove.qc +++ b/csqc/pmove.qc @@ -239,8 +239,9 @@ static void PM_ActivatePMS(PMS_Data *pmsd) { pm.ent.pmove_flags = 0; // With auto-bunny we only care about clearing }; +static float cache_invalidated; static PMS_Data* SimPMSD() { - return CVARF(fopm_nocache) ? &pm_s : &pm_c; + return (cache_invalidated || CVARF(fopm_nocache)) ? &pm_s : &pm_c; } void PM_InputFrame(); @@ -250,6 +251,7 @@ enum { NT_CONC, NT_DASH, NT_EXPLOSION, + NT_BOUNCE, }; enum { @@ -271,6 +273,7 @@ struct PM_Nudge { float aux; }; +const static float NUDGE_EPS = 3 * SERVER_FRAME_DT; static PM_Nudge nudges[5]; static float num_nudges; @@ -281,9 +284,9 @@ static float nudge_expired(PM_Nudge* n) { switch (n->nat) { case NA_NONE: return TRUE; - case NA_SEQ: return n->seq < servercommandframe; - case NA_SEQTIME: return n->seq + ceil(n->itime / SERVER_FRAME_DT) < servercommandframe; - case NA_ITIME: return n->itime < pstate_server.server_time; + case NA_SEQ: return n->seq >= servercommandframe; + case NA_SEQTIME: return n->seq + ceil(n->itime / SERVER_FRAME_DT) >= servercommandframe; + case NA_ITIME: return n->itime >= pstate_server.server_time + NUDGE_EPS; // Slack } return TRUE; @@ -296,6 +299,7 @@ static PM_Nudge* find_nudge_slot() { if (!nudge_expired(n)) continue; + cache_invalidated = TRUE; n->nat = NA_NONE; n->src_no = 0; n->src = 0; @@ -381,7 +385,7 @@ static void PM_NudgeExplosion(PM_Nudge* nudge, entity ent) { // Can be either by {seq + itime} for predicted projectiles, or by {itime} for // server projectiles. -void PM_AddNudgeExplosion(float nat, float seq, float itime, entity ent, float dmg) { +static void PM_AddNudgeExplosion(float nat, float seq, float itime, entity ent, float dmg) { if (vlen(PM_Org() - ent.origin) > dmg * 3) // Fine tune this.. return; @@ -400,11 +404,58 @@ void PM_AddNudgeExplosion(float nat, float seq, float itime, entity ent, float d n->src_no = ent.entnum; ent.nudge = n; + ent.removefunc = PM_RemoveSelfNudges; +} + +static void PM_AddNudgeBounce(float itime, entity ent, float dmg) { + if (vlen(PM_Org() - ent.origin) > dmg * 3) // Fine tune this.. + return; + + PM_Nudge* n = find_nudge_slot(); + if (!n) + return; + n->type = NT_BOUNCE; + n->nat = NA_ITIME; + n->itime = itime; + n->aux = dmg; + + n->src = ent; + n->src_no = ent.entnum; + + ent.nudge = n; ent.removefunc = PM_RemoveSelfNudges; } -DEFCVAR_FLOAT(fo_beta_nudge_explosion, 0); +static void PM_NudgeBounce(PM_Nudge* nudge, entity ent) { + float dmg = nudge->aux; + entity src = nudge->src; + + if (vlen(ent.origin - src.origin) > dmg + 40) + return; + + vector targ_org = ent.origin + (ent.mins + ent.maxs) * 0.5; + float points = dmg - vlen(targ_org - src.origin) / 2; + + ent.velocity = (targ_org - src.origin) * points/20; +} + + +void PM_AddGrenadeExplosion(float itime, entity ent) { + if (!PM_Enabled() || !CVARF(fo_beta_nudge_explosion)) + return; + + FO_GrenExp exp; + FO_GrenGetExp(ent.fpp.gren_type, exp); + + float dmg = exp.dmg; + + if (exp.type == kRadiusDamage) + PM_AddNudgeExplosion(NA_ITIME, 0, itime, ent, dmg); + else + PM_AddNudgeBounce(itime, ent, dmg); +} + void PM_AddSimExplosion(float itime, entity ent) { if (!PM_Enabled() || !CVARF(fo_beta_nudge_explosion)) return; @@ -454,8 +505,7 @@ float MatchNudge(PM_Nudge* n, float seq, float sitime, float eitime) { } // TODO: This obviously wants to be smarter -static vector PM_ApplyNudges(entity ent, float seq, float sitime, float eitime) { - vector result = '0 0 0'; +static void PM_ApplyNudges(entity ent, float seq, float sitime, float eitime) { for (float i = 0; i < nudges.length; i++) { PM_Nudge* n = &nudges[i]; @@ -472,10 +522,11 @@ static vector PM_ApplyNudges(entity ent, float seq, float sitime, float eitime) case NT_EXPLOSION: PM_NudgeExplosion(n, ent); break; + case NT_BOUNCE: + PM_NudgeBounce(n, ent); + break; } } - - return result; } static void RunPlayerPhysics() { @@ -517,6 +568,9 @@ static void PM_RunMovement(PMS_Data* pmsd, float endframe) { RunPlayerPhysics(); if (!CVARF(fopm_nonudge)) { + float start_time = pm.interp_t; + if (pm.seq == pm.server_seq + 1) + start_time -= NUDGE_EPS; PM_ApplyNudges(ent, pm.seq, pm.interp_t, pm.interp_t + input_timelength); } @@ -576,7 +630,6 @@ static void PM_UpdateError() { err -= ent.origin; float nerr = vlen(err); - if (nerr > 64) { // teleport pm.error = '0 0 0'; pm.errortime = 0; @@ -694,6 +747,7 @@ void PM_Update(float sendflags) { if (clientcommandframe > servercommandframe + 1) PM_RunMovement(&pm_s, clientcommandframe - 1); PM_SavePMS(&pm_c); + cache_invalidated = FALSE; } if (enabled && was_enabled) diff --git a/csqc/weapon_predict.qc b/csqc/weapon_predict.qc index 4ae79879..26ce6a86 100644 --- a/csqc/weapon_predict.qc +++ b/csqc/weapon_predict.qc @@ -586,6 +586,8 @@ void PredProj_Sound(int proj_type, float vol = 1) { void PM_Update(float sendflags); +DEFCVAR_FLOAT(fo_beta_nudge_explosion, 0); + void WP_ServerUpdate(float sendflags) { pstate_server.seq = servercommandframe; @@ -604,8 +606,8 @@ void WP_ServerUpdate(float sendflags) { pstate_server.weaponframe += 1; } - if (CVARF(fo_wpp_beta) & 4) - phys_sim_dt = (pstate_server.client_ping + 2 * SERVER_FRAME_MS) / 1000.0; + if (CVARF(fo_beta_nudge_explosion)) + phys_sim_dt = (pstate_server.client_ping + 4 * SERVER_FRAME_MS) / 1000.0; else phys_sim_dt = -1; diff --git a/share/physics.qc b/share/physics.qc index 66e887aa..f4e54746 100644 --- a/share/physics.qc +++ b/share/physics.qc @@ -60,6 +60,8 @@ static void Phys_Impact(entity e, float dt, float phys_flags) { static vector get_followed_origin() { return self.aiment.origin; } + +static void Phys_Expired(entity e, float phys_flags) {} #else DEFCVAR_FLOAT(fo_phys_debug, 0); @@ -93,7 +95,15 @@ static void Phys_Impact(entity e, float dt, float phys_flags) { get_phys_time(e)); } -static void Phys_Expired(entity e, float phys_flags) {} +void PM_AddGrenadeExplosion(float itime, entity ent); + +static void Phys_Expired(entity e, float phys_flags) { + e.voided = TRUE; + if (e.phys_sim_voided_at) // Only one of sim or !sim + return; + + PM_AddGrenadeExplosion(e.fpp.expires_at, e); +} static vector get_followed_origin() { if (!(float)getentity(self.aiment_num, GE_ACTIVE)) @@ -224,7 +234,7 @@ float Phys_Advance(entity e, float target_time, float phys_flags) { } // If there's an expiry, make sure we get to the exact position. - if (e.fpp.expires_at && target_time > e.fpp.expires_at) { + if (e.fpp.expires_at && target_time >= e.fpp.expires_at) { target_time = e.fpp.expires_at; phys_flags |= PHYSF_CONSUME_ALL; } @@ -280,6 +290,10 @@ float Phys_Advance(entity e, float target_time, float phys_flags) { if (step > 0) // Origin can have moved. setorigin(e, e.origin); + + if (e.fpp.expires_at && e.phys_time >= e.fpp.expires_at) + Phys_Expired(e, phys_flags); + return total; } @@ -301,7 +315,7 @@ void Phys_Sim(entity e) { return; float ptime = time + phys_sim_dt; - if (ptime < self.phys_sim_last + SERVER_FRAME_MS) + if (ptime < self.phys_sim_last + SERVER_FRAME_DT) return; self.phys_sim_last = ptime; diff --git a/share/weapons.qc b/share/weapons.qc index dccb3480..8a0c09c6 100644 --- a/share/weapons.qc +++ b/share/weapons.qc @@ -843,7 +843,7 @@ const float KF_BOTH_PLAYER = KF_SRC_PLAYER | KF_TARG_PLAYER; // terrible mistake. float CalcRadiusDamage(entity src, entity targ, float flags, float damage) { - vector targ_origin = (targ.absmin + targ.absmax) * 0.5; // (*) + vector targ_origin = targ.origin + (targ.mins + targ.maxs) * 0.5; // (*) damage -= vlen(src.origin - targ_origin) / 2; if (flags & KF_SELF) @@ -857,7 +857,7 @@ vector CalcKnock(entity src, entity targ, float flags, float moment) { if (moment < 60 && (flags & (KF_BOTH_PLAYER | KF_SELF) == KF_BOTH_PLAYER)) mult = 11; - vector src_origin = (src.absmin + src.absmax) * 0.5; // (*) + vector src_origin = src.origin + (src.mins + src.maxs) * 0.5; // (*) vector dir = normalize(targ.origin - src_origin); vector result = dir * moment * mult; diff --git a/ssqc/scout.qc b/ssqc/scout.qc index 8205ea70..cd8ee594 100644 --- a/ssqc/scout.qc +++ b/ssqc/scout.qc @@ -858,6 +858,8 @@ void (entity inflictor, entity attacker, float bounce, && (points > 0)) { head.velocity = org - inflictor.origin; head.velocity = head.velocity * (points / 20); + Predict_AddFilterEnt(head, inflictor); + if (head.classname != "player") { if (head.flags & FL_ONGROUND) head.flags = head.flags - FL_ONGROUND; diff --git a/ssqc/tfort.qc b/ssqc/tfort.qc index 4a5035e6..8d061796 100644 --- a/ssqc/tfort.qc +++ b/ssqc/tfort.qc @@ -2420,6 +2420,7 @@ void () TeamFortress_ExplodePerson = { proj.fpp.gren_type = gtype; proj.fpp.flags |= FPF_NO_REWIND; + proj.fpp.expires_at = expires; proj.skin = gdesc->skin; proj.avelocity = gdesc->avelocity; proj.touch = ste->touch; From c2a66bf01ab83750a97c2b8581d4ad057ef9dd1c Mon Sep 17 00:00:00 2001 From: newby Date: Tue, 10 Oct 2023 01:43:18 -0700 Subject: [PATCH 2/3] pm: re-enable conc prediction & improve accuracy --- csqc/pmove.qc | 9 +++++++++ csqc/weapon_predict.qc | 2 +- share/classes.qc | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/csqc/pmove.qc b/csqc/pmove.qc index 0d4fcc96..6a6edfdf 100644 --- a/csqc/pmove.qc +++ b/csqc/pmove.qc @@ -259,6 +259,7 @@ enum { NA_SEQ, NA_SEQTIME, NA_ITIME, + NA_CONC, // itime but with expiration driven by conc_state }; struct PM_Nudge { @@ -287,6 +288,7 @@ static float nudge_expired(PM_Nudge* n) { case NA_SEQ: return n->seq >= servercommandframe; case NA_SEQTIME: return n->seq + ceil(n->itime / SERVER_FRAME_DT) >= servercommandframe; case NA_ITIME: return n->itime >= pstate_server.server_time + NUDGE_EPS; // Slack + case NA_CONC: return n->itime <= pstate_server.conc_state.next; } return TRUE; @@ -334,6 +336,7 @@ void PM_AddNudgeConc(float itime, float mag, float flip) { return; n->type = NT_CONC; + n->nat = NA_CONC; n->itime = itime; n->vec.x = mag; n->vec.y = flip; @@ -498,6 +501,7 @@ float MatchNudge(PM_Nudge* n, float seq, float sitime, float eitime) { switch (n->nat) { case NA_SEQ: return n->seq == seq; case NA_SEQTIME: return n->seq + floor(n->itime / SERVER_FRAME_DT) == seq; + case NA_CONC: case NA_ITIME: return n->itime > sitime && n->itime <= eitime; default: return FALSE; @@ -710,6 +714,11 @@ static void PM_HandleRemovedNudges() { if (nudge_expired(n)) continue; + if (n->type == NT_CONC && n->itime < pstate_server.conc_state.next) { + n->expire_frame = -1; + continue; + } + for (float j = 0; j < pstate_server.num_filter_ents; j++) { if (n->src_no == pstate_server.filter_ents[j]) { n->nat = NA_SEQ; diff --git a/csqc/weapon_predict.qc b/csqc/weapon_predict.qc index 26ce6a86..77468264 100644 --- a/csqc/weapon_predict.qc +++ b/csqc/weapon_predict.qc @@ -2059,7 +2059,7 @@ static void WP_UpdatePredict() { pengine.is_effectframe = TRUE; pengine.last_effectframe = pframe; PM_SyncTo(pframe); - /* Conc_Update(&pstate_pred.conc_state, world, pstate_pred.server_time); */ + Conc_Update(&pstate_pred.conc_state, world, pstate_pred.server_time); } else { pengine.is_effectframe = FALSE; } diff --git a/share/classes.qc b/share/classes.qc index 25048ce2..831dd0db 100644 --- a/share/classes.qc +++ b/share/classes.qc @@ -125,10 +125,10 @@ static inline void ConcAction(entity ent, float itime, float mag, float flip) { Conc_Stumble(ent, mag, flip); } #else -void PM_AddConcNudge(float itime, float mag, float flip); +void PM_AddNudgeConc(float itime, float mag, float flip); static inline void ConcAction(entity ent, float itime, float mag, float flip) { - PM_AddConcNudge(itime, mag, flip); + PM_AddNudgeConc(itime, mag, flip); } #endif From 887da6995688b3c2846fb0a06b005f495909fa07 Mon Sep 17 00:00:00 2001 From: newby Date: Tue, 10 Oct 2023 01:50:03 -0700 Subject: [PATCH 3/3] pm: clean up filter-ent quickfix --- share/prediction.qc | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/share/prediction.qc b/share/prediction.qc index bfa0d353..9aa81c13 100644 --- a/share/prediction.qc +++ b/share/prediction.qc @@ -51,6 +51,8 @@ enumflags { PF_PMOVE_ACTIVATING, }; +static float MAX_FILTER_ENTS = 4; + struct predict_tf_state { int playerclass; int predict_flags, effects; @@ -84,9 +86,9 @@ struct predict_tf_state { float num_filter_ents; #ifdef SSQC - entity filter_ents[4]; + entity filter_ents[MAX_FILTER_ENTS]; #else - float filter_ents[4]; + float filter_ents[MAX_FILTER_ENTS]; #endif #ifdef CSQC @@ -398,8 +400,8 @@ void Predict_AddFilterEnt(entity p, entity filter) { return; predict_tf_state* ps = &p.predict_state; - if (ps->num_filter_ents >= 4) - return + if (ps->num_filter_ents >= MAX_FILTER_ENTS) + return; ps->filter_ents[ps->num_filter_ents++] = filter; }