summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-27 13:48:27 +0100
committersalmonik <salmonik@tuta.io>2026-10-27 13:48:27 +0100
commit471f022f449a18766fedb1ff0a4204a933b95b9c (patch)
tree499fcd29f1c9f7933aade3f8ffad88b3ef10f377 /src
parentscript: change the map for tests (diff)
downloadhl2h-471f022f449a18766fedb1ff0a4204a933b95b9c.tar.gz
hl2h-471f022f449a18766fedb1ff0a4204a933b95b9c.zip
Jumpbug: optional velocity-timed assist
The unduck window is only 2 units, while a 625-ups fall moves ~9.3 units per 66.7 Hz movement command, so pure jumpbug is ~20% reliable at that speed. - Add Jumpbug -> Assist timing (default off). When the natural next command would skip [36,38], pin the post-command ducked distance to 37 by writing the vertical velocity for that command. Pure jumpbug remains available with the option off. - Resolve the client local m_vecAbsVelocity (0x11C) via the client datamap and the server m_vecAbsVelocity (0x228) via the server datamap. - Write both the authoritative server velocity (what SetupMove reads through GetAbsVelocity) and the local prediction fields, so the next CreateMove sees the assisted origin on both sides. - Do not write the server m_vecVelocity directly: the datamap scan finds two same-named entries (0x1C and 0x29C); SetAbsVelocity syncs it after movement.
Diffstat (limited to '')
-rw-r--r--src/hl2h.cpp82
-rw-r--r--src/hl2h.h3
-rw-r--r--src/menu.cpp17
-rw-r--r--src/netvars.cpp67
-rw-r--r--src/server.cpp23
5 files changed, 174 insertions, 18 deletions
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index 125d94e..4ba30bc 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -186,6 +186,8 @@ static void apply_autostrafe(CUserCmd *cmd, const float *vel) {
186static constexpr float JB_SAFE_FALL = 526.5f; 186static constexpr float JB_SAFE_FALL = 526.5f;
187static constexpr float JB_GRAVITY = 600.0f; 187static constexpr float JB_GRAVITY = 600.0f;
188static constexpr float JB_DUCK_RAISE = 36.0f; 188static constexpr float JB_DUCK_RAISE = 36.0f;
189static constexpr float JB_TARGET_DIST = 37.0f; // middle of the unduck window
190static constexpr float JB_TICK = 0.015f; // Source interval_per_tick
189 191
190static int jb_state = 0; // 0 idle, 1 ducked/waiting for the unduck window 192static int jb_state = 0; // 0 idle, 1 ducked/waiting for the unduck window
191static int jb_local = -1; 193static int jb_local = -1;
@@ -213,6 +215,30 @@ static void jumpbug_reset() {
213 jb_tracked = false; 215 jb_tracked = false;
214} 216}
215 217
218// Choose a vertical velocity so this command's movement ends with the feet
219// `target` units above the floor. The engine does StartGravity (half a tick),
220// moves by velocity*dt, then FinishGravity; displacement is
221// (vz - 0.5*g*dt)*dt. dist is the distance before the command's movement.
222static bool jb_force_next_distance(int local, void *ent, const float *vel,
223 float dist, float target) {
224 if (g_net.absvelocity < 0 || g_net.velocity < 0)
225 return false;
226 float dz = target - dist;
227 float vz = dz / JB_TICK + 0.5f * JB_GRAVITY * JB_TICK;
228 if (vz < -2000.0f)
229 vz = -2000.0f;
230 if (vz > 2000.0f)
231 vz = 2000.0f;
232 if (!server_set_velocity_z(local, vz))
233 return false;
234 // Keep the local prediction in step with the authoritative server entity:
235 // C_BaseEntity::GetAbsVelocity() reads the non-networked m_vecAbsVelocity,
236 // while the networked m_vecVelocity is what the rest of the client sees.
237 *(float *)((uintptr_t)ent + g_net.absvelocity + 8) = vz;
238 ((float *)vel)[2] = vz;
239 return true;
240}
241
216static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags, 242static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags,
217 const float *vel, float frametime) { 243 const float *vel, float frametime) {
218 if (!feature_active(FEAT_JUMPBUG) || !g_iface.engine || 244 if (!feature_active(FEAT_JUMPBUG) || !g_iface.engine ||
@@ -285,6 +311,10 @@ static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags,
285 logf("jumpbug: tracking x=%.1f vz=%.1f impact=%.1f", x, vz, impact); 311 logf("jumpbug: tracking x=%.1f vz=%.1f impact=%.1f", x, vz, impact);
286 } 312 }
287 313
314 bool assist = jumpbug_assist_enabled();
315 if (assist)
316 resolve_server_netvars();
317
288 if (jb_drop <= 0.05f) { 318 if (jb_drop <= 0.05f) {
289 // No measured fall yet. Bias toward the default 15 ms Source tick when 319 // No measured fall yet. Bias toward the default 15 ms Source tick when
290 // the input frametime is smaller: setting the duck up early leaves us 320 // the input frametime is smaller: setting the duck up early leaves us
@@ -310,29 +340,59 @@ static bool jumpbug_apply(CUserCmd *cmd, int local, void *ent, int flags,
310 trigger = true; 340 trigger = true;
311 jb_state = 0; 341 jb_state = 0;
312 jb_tracked = false; 342 jb_tracked = false;
313 if (jb_logs < 20) { 343 if (jb_logs < 30) {
314 jb_logs++; 344 jb_logs++;
315 logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f", x, jb_drop, vz); 345 logf("jumpbug: trigger x=%.2f drop=%.2f vz=%.1f assist=%d", x, jb_drop,
346 vz, (int)assist);
316 } 347 }
317 } else { 348 } else {
318 // Hold the crouch and wait for the window. Staying ducked is required 349 if (assist) {
319 // for the unduck trick; releasing early would just add a phase shift 350 float next_x = x - jb_drop;
320 // and fight a user who is holding crouch. 351 if (next_x <= 38.0f && next_x < 36.0f) {
352 // The natural next command would skip the 2-unit window. Force the
353 // post-command distance to 37 so the next command can unduck+jump.
354 if (jb_force_next_distance(local, ent, vel, x, JB_TARGET_DIST)) {
355 if (jb_logs < 30) {
356 jb_logs++;
357 logf("jumpbug: assist duck x=%.2f next=%.2f -> vz=%.1f", x,
358 next_x, vel[2]);
359 }
360 } else {
361 static bool warned = false;
362 if (!warned) {
363 warned = true;
364 logf("jumpbug: assist unavailable (server velocity failed)");
365 }
366 }
367 }
368 }
369 // Hold the crouch and wait for the window (or for the assisted
370 // position on the next command).
321 cmd->buttons |= IN_DUCK; 371 cmd->buttons |= IN_DUCK;
322 jb_state = 1; 372 jb_state = 1;
323 } 373 }
324 } else { 374 } else {
325 if (jb_state == 1) 375 if (jb_state == 1)
326 jb_state = 0; // expected to be ducked but is not 376 jb_state = 0; // expected to be ducked but is not
327 if (x <= jb_drop + 2.0f) { 377 float setup_limit = jb_drop + (assist ? 4.0f : 2.0f);
328 // Setup frame: raise the feet 36 units here so the next frame starts 378 if (x <= setup_limit) {
329 // near the middle of the unduck window. 379 // Setup frame: raise the feet 36 units here so the next command starts
380 // near the middle of the unduck window. With assist enabled, also pin
381 // the post-command distance to exactly 37.
382 bool forced = false;
383 if (assist)
384 forced = jb_force_next_distance(local, ent, vel, x + JB_DUCK_RAISE,
385 JB_TARGET_DIST);
330 cmd->buttons |= IN_DUCK; 386 cmd->buttons |= IN_DUCK;
331 jb_state = 1; 387 jb_state = 1;
332 if (jb_logs < 20) { 388 if (jb_logs < 30) {
333 jb_logs++; 389 jb_logs++;
334 logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x, jb_drop, 390 if (assist)
335 x + JB_DUCK_RAISE - jb_drop, vz); 391 logf("jumpbug: setup assist x=%.2f drop=%.2f vz=%.1f ok=%d", x,
392 jb_drop, vel[2], (int)forced);
393 else
394 logf("jumpbug: setup x=%.2f drop=%.2f pred=%.2f vz=%.1f", x,
395 jb_drop, x + JB_DUCK_RAISE - jb_drop, vz);
336 } 396 }
337 } 397 }
338 } 398 }
diff --git a/src/hl2h.h b/src/hl2h.h
index 92ae3f1..08eb2ef 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -81,6 +81,7 @@ struct NetVars {
81 int maxs = -1; 81 int maxs = -1;
82 int lifestate = -1; 82 int lifestate = -1;
83 int modelindex = -1; 83 int modelindex = -1;
84 int absvelocity = -1; // client C_BaseEntity::m_vecAbsVelocity (prediction)
84 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72 85 // ponytail: mins/maxs may be absent from some HL2 netvar tables; esp.cpp falls back to h=72
85 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; } 86 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; }
86 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100. 87 // ponytail: m_iMaxHealth isn't a netvar in HL2 SP; esp.cpp falls back to 100.
@@ -177,6 +178,7 @@ bool find_input_global();
177void resolve_netvars(); 178void resolve_netvars();
178bool resolve_server_netvars(); 179bool resolve_server_netvars();
179int server_entity_health(int idx, int *maxhealth, int *lifestate, void **base_out); 180int server_entity_health(int idx, int *maxhealth, int *lifestate, void **base_out);
181bool server_set_velocity_z(int idx, float vz);
180int server_model_index(void *base); 182int server_model_index(void *base);
181void find_modules(); 183void find_modules();
182void init_conmsg(); 184void init_conmsg();
@@ -185,6 +187,7 @@ bool feature_has_bind(int feature);
185bool feature_bind_held(int feature); 187bool feature_bind_held(int feature);
186bool esp_option(int opt); 188bool esp_option(int opt);
187bool bhop_autostrafe_enabled(); 189bool bhop_autostrafe_enabled();
190bool jumpbug_assist_enabled();
188unsigned aimbot_bone_mask(); 191unsigned aimbot_bone_mask();
189int aimbot_smooth_step(); 192int aimbot_smooth_step();
190bool aimbot_vischeck_enabled(); 193bool aimbot_vischeck_enabled();
diff --git a/src/menu.cpp b/src/menu.cpp
index 0ed89d4..4f76529 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -63,6 +63,7 @@ static bool g_bhop_autostrafe = true;
63 63
64static FeatureState g_abh{true, 0}; 64static FeatureState g_abh{true, 0};
65static FeatureState g_jumpbug{true, 0}; 65static FeatureState g_jumpbug{true, 0};
66static bool g_jumpbug_assist = false;
66static FeatureState g_esp{true, 0}; 67static FeatureState g_esp{true, 0};
67static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu 68static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu
68static FeatureState g_triggerbot{true, 603}; // default Mouse4 69static FeatureState g_triggerbot{true, 603}; // default Mouse4
@@ -99,6 +100,7 @@ static MenuNode g_abh_feat{NODE_FEATURE, "ABH", false, 1, {}, nullptr, nullptr,
99static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr}; 100static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr};
100static MenuNode g_jumpbug_feat{NODE_FEATURE, "Jumpbug", false, 1, {}, nullptr, nullptr, nullptr}; 101static MenuNode g_jumpbug_feat{NODE_FEATURE, "Jumpbug", false, 1, {}, nullptr, nullptr, nullptr};
101static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug.active, nullptr, nullptr}; 102static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug.active, nullptr, nullptr};
103static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr};
102static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr}; 104static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr};
103static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr}; 105static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr};
104static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = { 106static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = {
@@ -150,7 +152,8 @@ static void build_tree() {
150 g_abh_feat.children[0] = &g_abh_active; 152 g_abh_feat.children[0] = &g_abh_active;
151 g_abh_feat.child_count = 1; 153 g_abh_feat.child_count = 1;
152 g_jumpbug_feat.children[0] = &g_jumpbug_active; 154 g_jumpbug_feat.children[0] = &g_jumpbug_active;
153 g_jumpbug_feat.child_count = 1; 155 g_jumpbug_feat.children[1] = &g_jumpbug_assist_node;
156 g_jumpbug_feat.child_count = 2;
154 g_esp_feat.children[0] = &g_esp_active; 157 g_esp_feat.children[0] = &g_esp_active;
155 g_aimbot_feat.children[0] = &g_aimbot_active; 158 g_aimbot_feat.children[0] = &g_aimbot_active;
156 g_aimbot_feat.children[1] = &g_aimbot_bind; 159 g_aimbot_feat.children[1] = &g_aimbot_bind;
@@ -302,7 +305,8 @@ static void config_save() {
302 fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active, 305 fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active,
303 g_bhop.bind, g_bhop_autostrafe); 306 g_bhop.bind, g_bhop_autostrafe);
304 fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); 307 fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind);
305 fprintf(f, "[jumpbug]\nactive=%d\n", g_jumpbug.active); 308 fprintf(f, "[jumpbug]\nactive=%d\nassist=%d\n", g_jumpbug.active,
309 (int)g_jumpbug_assist);
306 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" 310 fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n"
307 "fov=%d\nfov_circle=%d\n" 311 "fov=%d\nfov_circle=%d\n"
308 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" 312 "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n"
@@ -332,7 +336,8 @@ static void config_load() {
332 return; 336 return;
333 char line[256]; 337 char line[256];
334 FeatureState *cur = nullptr; 338 FeatureState *cur = nullptr;
335 bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false; 339 bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false,
340 in_jumpbug = false;
336 while (fgets(line, sizeof(line), f)) { 341 while (fgets(line, sizeof(line), f)) {
337 line[strcspn(line, "\r\n")] = 0; 342 line[strcspn(line, "\r\n")] = 0;
338 if (line[0] == '[') { 343 if (line[0] == '[') {
@@ -356,6 +361,7 @@ static void config_load() {
356 in_bhop = (cur == &g_bhop); 361 in_bhop = (cur == &g_bhop);
357 in_aim = (cur == &g_aimbot); 362 in_aim = (cur == &g_aimbot);
358 in_silent = (cur == &g_silentaim); 363 in_silent = (cur == &g_silentaim);
364 in_jumpbug = (cur == &g_jumpbug);
359 } else if (cur) { 365 } else if (cur) {
360 char *eq = strchr(line, '='); 366 char *eq = strchr(line, '=');
361 if (!eq) 367 if (!eq)
@@ -365,6 +371,9 @@ static void config_load() {
365 if (in_bhop && strcmp(key, "autostrafe") == 0) { 371 if (in_bhop && strcmp(key, "autostrafe") == 0) {
366 g_bhop_autostrafe = atoi(val) != 0; 372 g_bhop_autostrafe = atoi(val) != 0;
367 } 373 }
374 if (in_jumpbug && strcmp(key, "assist") == 0) {
375 g_jumpbug_assist = atoi(val) != 0;
376 }
368 if (in_aim) { 377 if (in_aim) {
369 if (strcmp(key, "smooth") == 0) { 378 if (strcmp(key, "smooth") == 0) {
370 int v = atoi(val); 379 int v = atoi(val);
@@ -557,6 +566,8 @@ bool esp_option(int opt) {
557 566
558bool bhop_autostrafe_enabled() { return g_bhop_autostrafe; } 567bool bhop_autostrafe_enabled() { return g_bhop_autostrafe; }
559 568
569bool jumpbug_assist_enabled() { return g_jumpbug_assist; }
570
560unsigned aimbot_bone_mask() { 571unsigned aimbot_bone_mask() {
561 unsigned m = 0; 572 unsigned m = 0;
562 if (g_aim_bones[AIM_BONE_HEAD]) 573 if (g_aim_bones[AIM_BONE_HEAD])
diff --git a/src/netvars.cpp b/src/netvars.cpp
index b4e31d5..98ebe31 100644
--- a/src/netvars.cpp
+++ b/src/netvars.cpp
@@ -185,6 +185,66 @@ static bool resolve_movetype() {
185 return true; 185 return true;
186} 186}
187 187
188static bool resolve_absvelocity() {
189 static const char needle[] = "m_vecAbsVelocity";
190 const size_t nlen = sizeof(needle);
191
192 uintptr_t str_va = 0;
193 for (auto &r : g_client.ranges) {
194 const uint8_t *base = (const uint8_t *)r.lo;
195 size_t span = r.hi - r.lo;
196 for (size_t i = 0; i + nlen <= span; i++) {
197 if (memcmp(base + i, needle, nlen) == 0) {
198 str_va = r.lo + i;
199 break;
200 }
201 }
202 if (str_va)
203 break;
204 }
205 if (!str_va) {
206 logf("m_vecAbsVelocity string not found");
207 return false;
208 }
209
210 uint32_t offs[16];
211 int counts[16] = {};
212 int ncand = 0;
213 const uint32_t target = (uint32_t)str_va;
214 for (auto &r : g_client.ranges) {
215 const uint8_t *base = (const uint8_t *)r.lo;
216 size_t span = r.hi - r.lo;
217 for (size_t off = 4; off + 8 <= span; off += 4) {
218 if (*(const uint32_t *)(base + off) != target)
219 continue;
220 uint32_t ftype = *(const uint32_t *)(base + off - 4);
221 int32_t foff = *(const int32_t *)(base + off + 4);
222 if (ftype != 3 || foff < 0x40 || foff > 0x400)
223 continue;
224 int k;
225 for (k = 0; k < ncand; k++)
226 if (offs[k] == (uint32_t)foff)
227 break;
228 if (k == ncand && ncand < 16) {
229 offs[ncand] = (uint32_t)foff;
230 ncand++;
231 }
232 if (k < ncand)
233 counts[k]++;
234 }
235 }
236 if (ncand == 0) {
237 logf("m_vecAbsVelocity typedescription not found");
238 return false;
239 }
240 int best = 0;
241 for (int k = 1; k < ncand; k++)
242 if (counts[k] > counts[best])
243 best = k;
244 g_net.absvelocity = (int)offs[best];
245 return true;
246}
247
188void resolve_netvars() { 248void resolve_netvars() {
189 using GetAllClassesFn = void *(*)(void *); 249 using GetAllClassesFn = void *(*)(void *);
190 uintptr_t head = 250 uintptr_t head =
@@ -205,10 +265,13 @@ void resolve_netvars() {
205 // movetype unresolved — bhop still works, just no noclip/ladder guard 265 // movetype unresolved — bhop still works, just no noclip/ladder guard
206 logf("movetype guard disabled"); 266 logf("movetype guard disabled");
207 } 267 }
268 if (g_net.absvelocity < 0 && !resolve_absvelocity())
269 logf("m_vecAbsVelocity unresolved; jumpbug assist disabled");
208 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " 270 logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d "
209 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " 271 "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d "
210 "modelindex=%d", 272 "modelindex=%d absvelocity=%d",
211 g_net.flags, g_net.velocity, g_net.movetype, g_net.health, 273 g_net.flags, g_net.velocity, g_net.movetype, g_net.health,
212 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, 274 g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset,
213 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex); 275 g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex,
276 g_net.absvelocity);
214} 277}
diff --git a/src/server.cpp b/src/server.cpp
index 6feb55b..3766906 100644
--- a/src/server.cpp
+++ b/src/server.cpp
@@ -31,6 +31,8 @@ int g_srv_health = -1;
31int g_srv_maxhealth = -1; 31int g_srv_maxhealth = -1;
32int g_srv_lifestate = -1; 32int g_srv_lifestate = -1;
33int g_srv_modelindex = -1; 33int g_srv_modelindex = -1;
34int g_srv_absvelocity = -1;
35int g_srv_velocity = -1;
34static bool g_srv_ready = false; 36static bool g_srv_ready = false;
35static bool g_srv_checked = false; 37static bool g_srv_checked = false;
36 38
@@ -105,12 +107,14 @@ bool resolve_server_netvars() {
105 g_srv_maxhealth = datamap_offset("m_iMaxHealth", FIELD_INTEGER); 107 g_srv_maxhealth = datamap_offset("m_iMaxHealth", FIELD_INTEGER);
106 g_srv_lifestate = datamap_offset("m_lifeState", FIELD_CHARACTER); 108 g_srv_lifestate = datamap_offset("m_lifeState", FIELD_CHARACTER);
107 g_srv_modelindex = datamap_offset("m_nModelIndex", 7); // FIELD_SHORT 109 g_srv_modelindex = datamap_offset("m_nModelIndex", 7); // FIELD_SHORT
110 g_srv_absvelocity = datamap_offset("m_vecAbsVelocity", 3); // FIELD_VECTOR
111 g_srv_velocity = datamap_offset("m_vecVelocity", 3); // FIELD_VECTOR
108 g_srv_ready = g_srv_health > 0; 112 g_srv_ready = g_srv_health > 0;
109 if (g_srv_ready) { 113 if (g_srv_ready) {
110 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " 114 logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d "
111 "modelindex=%d", 115 "modelindex=%d absvel=%d vel=%d",
112 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, 116 g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate,
113 g_srv_modelindex); 117 g_srv_modelindex, g_srv_absvelocity, g_srv_velocity);
114 fail_logged = false; 118 fail_logged = false;
115 } else if (!fail_logged) { 119 } else if (!fail_logged) {
116 fail_logged = true; 120 fail_logged = true;
@@ -175,3 +179,18 @@ int server_model_index(void *base) {
175 return -1; 179 return -1;
176 return (int)*(short *)((uintptr_t)base + g_srv_modelindex); 180 return (int)*(short *)((uintptr_t)base + g_srv_modelindex);
177} 181}
182
183// Set the authoritative vertical velocity of a server entity. m_vecAbsVelocity
184// is what CGameMovement::SetupMove reads through GetAbsVelocity(); writing the
185// networked m_vecVelocity directly is deliberately avoided because the server
186// datamap scan can find a same-named field on another class first. The movement
187// code syncs m_vecVelocity through SetAbsVelocity after the command.
188bool server_set_velocity_z(int idx, float vz) {
189 if (!g_srv_ready || !g_iface.engineserver || g_srv_absvelocity <= 0)
190 return false;
191 void *base = nullptr;
192 if (server_entity_health(idx, nullptr, nullptr, &base) < 0 || !base)
193 return false;
194 *(float *)((uintptr_t)base + g_srv_absvelocity + 8) = vz;
195 return true;
196}