From e5c98b49936b2833d66362d5fef25270dbef5f1f Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 30 Aug 2026 15:02:12 -0400 Subject: [PATCH] bugfix --- docs/CHANGELOG.md | 4 ++ game_patch/debug/obj_debug.cpp | 41 +++++++++++++++++++ game_patch/object/entity.cpp | 72 ++++++++++++++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 661ca8aa4..2b3e6d3be 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -23,6 +23,10 @@ Version 1.5.0 (TBD): Not yet released [@is-this-c](https://github.com/is-this-c) - Let `Caps Lock` capitalize +[@nickalreadyinuse](https://github.com/nickalreadyinuse) +- Fix first person camera height on miner1 and merc based skeletons, which caused other players to appear shorter than you in multiplayer (stock bug since 1.2) + - Servers keep firing origins consistent for clients on older Alpine Faction versions + Version 1.4.0 (Lupin): Released Aug-25-2026 -------------------------------- ### Major features diff --git a/game_patch/debug/obj_debug.cpp b/game_patch/debug/obj_debug.cpp index 2a54c0c2e..6946f8f22 100644 --- a/game_patch/debug/obj_debug.cpp +++ b/game_patch/debug/obj_debug.cpp @@ -7,6 +7,12 @@ #include "../rf/collide.h" #include "../rf/player/camera.h" #include "debug_internal.h" +#include "../rf/physics.h" +#include "../rf/vmesh.h" +#include +#include +#include +#include std::optional g_target_rotate_speed; @@ -165,6 +171,40 @@ ConsoleCommand2 dbg_ai_pause_cmd{ }, }; +// Debug: per-entity origin/eye/eye-tag/csphere heights above the floor +ConsoleCommand2 dbg_eye_heights_cmd{ + "d_eye_heights", + []() { + for (auto& ep : DoublyLinkedList{rf::entity_list}) { + rf::Vector3 p0 = ep.pos; + rf::Vector3 p1 = ep.pos - rf::Vector3{0.0f, 5.0f, 0.0f}; + rf::PCollisionOut col{}; + col.obj_handle = -1; + float floor = rf::collide_linesegment_world(p0, p1, rf::CF_ANY_HIT, &col) ? col.hit_point.y : ep.pos.y - 5.0f; + float tag_y = 0.0f; + if (ep.vmesh && ep.info2->eye_tag >= 0) { + rf::Matrix3 o; + rf::Vector3 t; + rf::vmesh_get_prop_point_transform(ep.vmesh, ep.info2->eye_tag, &ep.orient, &ep.pos, &o, &t); + tag_y = t.y - floor; + } + float bottom = 0.0f; + for (int i = 0; i < ep.info2->collision_spheres.size(); ++i) { + const auto& cs = ep.info2->collision_spheres[i]; + bottom = std::min(bottom, cs.center.y - cs.radius); + } + std::string line = std::format( + "{} [{}] local={} origin={:.3f} eye_pos={:.3f} eye_tag_now={:.3f} sphere_bottom={:.3f} anim={}", + ep.name, ep.info2->name, &ep == rf::local_player_entity, ep.pos.y - floor, ep.eye_pos.y - floor, + tag_y, ep.pos.y + bottom - floor, ep.current_state_anim); + rf::console::print("{}", line); + xlog::info("d_eye_heights: {}", line); + } + }, + "Print entity origin/eye/csphere heights above the floor under them", + "d_eye_heights" +}; + const char* get_obj_type_name(rf::Object& obj) { switch (obj.type) { @@ -246,6 +286,7 @@ void register_obj_debug_commands() dbg_entity_action_cmd.register_cmd(); dbg_spin_cmd.register_cmd(); dbg_ai_pause_cmd.register_cmd(); + dbg_eye_heights_cmd.register_cmd(); } void render_obj_debug_ui() diff --git a/game_patch/object/entity.cpp b/game_patch/object/entity.cpp index a729a6313..66d601e8e 100644 --- a/game_patch/object/entity.cpp +++ b/game_patch/object/entity.cpp @@ -4,9 +4,11 @@ #include #include #include +#include #include #include "../misc/achievements.h" #include "../misc/alpine_settings.h" +#include "../misc/player.h" #include "../os/console.h" #include "../rf/gr/gr_light.h" #include "../rf/entity.h" @@ -681,6 +683,72 @@ CodeInjection clear_stale_movement_input_injection{ }, }; +// Stock samples EntityInfo::local_eye_offset from the "stand" pose, but armed entities idle in "attack_stand", +// which sits ~9cm lower on the miner1/merc skeletons. Resample after stock init so hitboxes stay stock. +static auto& entity_can_crouch = addr_as_ref(0x00428010); +static auto& entity_is_humanoid = addr_as_ref(0x0040A150); +constexpr int EIF_MESH_DATA_INITIALIZED = 0x40000000; +// stock eye Y minus corrected eye Y, per entity type (for pre-1.5 clients on the server rewind path) +static std::unordered_map g_legacy_eye_offset_delta_y; + +FunHook entity_info_setup_bone_damage_modifiers_hook{ + 0x00423B90, + [](rf::Entity* ep) { + rf::EntityInfo* info = ep->info2; + bool was_initialized = info->flags & EIF_MESH_DATA_INITIALIZED; + entity_info_setup_bone_damage_modifiers_hook.call_target(ep); + if (was_initialized || !ep->vmesh || info->eye_tag < 0 || !entity_can_crouch(ep)) { + return; + } + int idx = rf::ENTITY_STATE_ATTACK_STAND; + if (ep->state_anims[idx].vmesh_anim_index < 0) { + idx = rf::ENTITY_STATE_STAND; + } + int anim = ep->state_anims[idx].vmesh_anim_index; + if (anim < 0) { + return; + } + rf::vmesh_reset_actions(ep->vmesh); + rf::vmesh_set_action_weight(ep->vmesh, anim, 1.0f); + rf::vmesh_process(ep->vmesh, 1.0f, 0, nullptr, nullptr, 1); + rf::Matrix3 identity; + identity.make_identity(); + rf::Vector3 zero{}; + rf::Matrix3 out_orient; + rf::Vector3 eye; + rf::vmesh_get_prop_point_transform(ep->vmesh, info->eye_tag, &identity, &zero, &out_orient, &eye); + // restore stock pose + rf::vmesh_reset_actions(ep->vmesh); + if (ep->state_anims[0].vmesh_anim_index >= 0) { + rf::vmesh_set_action_weight(ep->vmesh, ep->state_anims[0].vmesh_anim_index, 1.0f); + } + rf::vmesh_process(ep->vmesh, 0.2f, 0, nullptr, nullptr, 1); + if (entity_is_humanoid(ep)) { + eye.x = 0.0f; + eye.z = 0.0f; + } + g_legacy_eye_offset_delta_y[info] = info->local_eye_offset.y - eye.y; + xlog::info("eye offset resample: type {} state={} stock y={} resampled y={}", info->name, idx, + info->local_eye_offset.y, eye.y); + info->local_eye_offset = eye; + }, +}; + +// Server: burst follow-up rounds have no packet pos, so the shooter's eye_pos is rebuilt from local_eye_offset. +// Pre-1.5 clients still fire from the stock eye height; reproduce it for them. Orient is yaw-only, so y-only. +CallHook lag_comp_shooter_eye_pos_legacy_client_hook{ + 0x004264C3, + [](rf::Entity* ep) { + lag_comp_shooter_eye_pos_legacy_client_hook.call_target(ep); + if (ep->local_player && !is_player_minimum_af_client_version(ep->local_player, 1, 5, 0)) { + auto it = g_legacy_eye_offset_delta_y.find(ep->info2); + if (it != g_legacy_eye_offset_delta_y.end()) { + ep->eye_pos.y += it->second; + } + } + }, +}; + void entity_do_patch() { //player_create_entity_patch.install(); // force team skin experiment @@ -762,6 +830,10 @@ void entity_do_patch() // Cancel movement input when escape menu is open in multiplayer clear_stale_movement_input_injection.install(); + // Fix MP camera height on miner1/merc skeletons (eye offset sampled from wrong idle pose) + entity_info_setup_bone_damage_modifiers_hook.install(); + lag_comp_shooter_eye_pos_legacy_client_hook.install(); + // Commands sp_exposuredamage_cmd.register_cmd(); cl_gorelevel_cmd.register_cmd();