#include #include "loader/component_loader.hpp" #include "scheduler.hpp" #include "command.hpp" #include "console.hpp" #include "game/game.hpp" #include "game/dvars.hpp" #include #include #include namespace pathnodes { namespace { game::dvar_t* g_connect_paths; utils::hook::detour path_init_paths_hook; utils::memory::allocator allocator; game::pathnode_tree_t* allocate_tree() { ++game::pathData->nodeTreeCount; return reinterpret_cast( game::Hunk_AllocAlignInternal(sizeof(game::pathnode_tree_t), 4)); } game::pathnode_tree_t* build_node_tree(unsigned short* node_indexes, unsigned int num_nodes) { if (num_nodes < 4) { const auto result = allocate_tree(); result->axis = -1; result->u.s.nodeCount = num_nodes; result->u.s.nodes = node_indexes; return result; } game::vec2_t maxs{}; game::vec2_t mins{}; const auto start_node = &game::pathData->nodes[*node_indexes]; maxs[0] = start_node->constant.vLocalOrigin[0]; mins[0] = maxs[0]; maxs[1] = start_node->constant.vLocalOrigin[1]; mins[1] = maxs[1]; for (auto i = 1u; i < num_nodes; i++) { for (auto axis = 0; axis < 2; axis++) { const auto node = &game::pathData->nodes[node_indexes[i]]; const auto value = node->constant.vLocalOrigin[axis]; if (mins[axis] <= value) { if (value > maxs[axis]) { maxs[axis] = value; } } else { mins[axis] = value; } } } const auto axis = (maxs[1] - mins[1]) > (maxs[0] - mins[0]); if ((maxs[axis] - mins[axis]) > 192.f) { const auto dist = (maxs[axis] + mins[axis]) * 0.5f; auto left = 0u; for (auto right = num_nodes - 1; ; --right) { while (dist > game::pathData->nodes[node_indexes[left]].constant.vLocalOrigin[axis]) { ++left; } while (game::pathData->nodes[node_indexes[right]].constant.vLocalOrigin[axis] > dist) { --right; } if (left >= right) { break; } const auto swap_node = node_indexes[left]; node_indexes[left] = node_indexes[right]; node_indexes[right] = swap_node; ++left; } while (2 * left < num_nodes && game::pathData->nodes[node_indexes[left]].constant.vLocalOrigin[axis] == dist) { ++left; } while (2 * left < num_nodes && game::pathData->nodes[node_indexes[left - 1]].constant.vLocalOrigin[axis] == dist) { --left; } game::pathnode_tree_t* child[2]{}; child[0] = build_node_tree(node_indexes, left); child[1] = build_node_tree(&node_indexes[left], num_nodes - left); const auto result = allocate_tree(); result->axis = axis; result->dist = dist; result->u.child[0] = child[0]; result->u.child[1] = child[1]; return result; } else { const auto result = allocate_tree(); result->axis = -1; result->u.s.nodeCount = num_nodes; result->u.s.nodes = node_indexes; return result; } } bool is_negotation_link(game::pathnode_t* from, game::pathnode_t* to) { return from->constant.type == game::NODE_NEGOTIATION_BEGIN && to->constant.type == game::NODE_NEGOTIATION_END && from->constant.target == to->constant.targetname; } float vec2_normalize(float* v) { const auto len = std::sqrt(v[0] * v[0] + v[1] * v[1]); v[0] /= len; v[1] /= len; return len; } float vec2_length(const float* v) { return std::sqrt(v[0] * v[0] + v[1] * v[1]); } float vec3_normalize(float* v) { const auto len = std::sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); v[0] /= len; v[1] /= len; v[2] /= len; return len; } void vector_scale(float* v, const int size, const float scale) { for (auto i = 0; i < size; i++) { v[i] *= scale; } } bool is_deflection_ok(const float* start, const float* origin, const float* move_dir) { game::vec2_t deflection{}; deflection[0] = origin[0] - start[0]; deflection[1] = origin[1] - start[1]; const auto value = ((deflection[0] * move_dir[0]) + (deflection[1] * move_dir[1])) * -1.f; const auto d = ( ((value * move_dir[0]) + deflection[0]) * ((value * move_dir[0]) + deflection[0]) + ((value * move_dir[1]) + deflection[1]) * ((value * move_dir[1]) + deflection[1]) ); return 0.3f > d; } void vector_copy(const float* a, float* b, const int size) { for (auto i = 0; i < size; i++) { b[i] = a[i]; } } bool vector_cmp(const float* a, const float* b, const int size) { for (auto i = 0; i < size; i++) { if (a[i] != b[i]) { return false; } } return true; } float distance_squared(const float* a, const float* b) { return ((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1])); } void actor_physics(game::pmove_t* pm, game::pml_t* pml) { pml->previous_velocity[0] = pm->ps->velocity[0]; pml->previous_velocity[1] = pm->ps->velocity[1]; pml->previous_velocity[2] = pm->ps->velocity[2]; pml->previous_origin[0] = pm->ps->origin[0]; pml->previous_origin[1] = pm->ps->origin[1]; pml->previous_origin[2] = pm->ps->origin[2]; pml->groundTrace.hitId = 3998; game::PM_GroundTrace(pm, pml); if (pml->walking) { game::PM_WalkMove(pm, pml); } else { game::PM_AirMove(pm, pml); } } void vector_cross(const float* a, const float* b, float* out) { out[0] = (a[1] * b[2]) - (a[2] * b[1]); out[1] = (a[0] * b[2]) - (a[2] * b[0]); out[2] = (a[0] * b[1]) - (a[1] * b[0]); } bool phys_trace_passed(const float* from, const float* to, float* dist) { game::pml_t pml{}; game::pmove_t pm{}; pm.tracemask = 0x281C011; pm.bounds = *reinterpret_cast(0x140984950); pm.ps = reinterpret_cast(&game::g_entities[0].client); pm.ps->origin[0] = from[0]; pm.ps->origin[1] = from[1]; pm.ps->origin[2] = from[2]; pm.ps->gravity = 800; pm.ps->velocity[0] = 0.f; pm.ps->velocity[1] = 0.f; pm.ps->velocity[2] = 0.f; pml.previous_origin[0] = from[0]; pml.previous_origin[1] = from[1]; pml.previous_origin[2] = from[2]; pml.msec = 50; pml.frametime = static_cast(pml.msec) * 0.001f; game::vec3_t move_dir{}; move_dir[0] = to[0] - from[0]; move_dir[1] = to[1] - from[1]; game::Vec2Normalize(move_dir); pml.forward[0] = move_dir[0]; pml.forward[1] = move_dir[1]; pml.forward[2] = move_dir[2]; pml.up[0] = 0.f; pml.up[1] = 0.f; pml.up[2] = 1.f; vector_cross(pml.forward, pml.up, pml.right); pm.cmd.forwardmove = 127; pm.cmd.rightmove = 0; pm.cmd.unk_float = 1.f; auto dist_squared = 100000.f; auto last_ground_plane_altitude = -3.4028235e38f; for (auto i = 0; i < 96; i++) { dist_squared = ( ((to[0] - pm.ps->origin[0]) * (to[0] - pm.ps->origin[0])) + ((to[1] - pm.ps->origin[1]) * (to[1] - pm.ps->origin[1])) ); if (dist_squared <= 16.f) { break; } game::vec3_t start{}; vector_copy(pm.ps->origin, start, 3); actor_physics(&pm, &pml); if (vector_cmp(start, pm.ps->origin, 3) || distance_squared(pm.ps->origin, to) > 65536.f) { return false; } const auto has_ground_plane = pml.groundPlane && pml.groundTrace.normal[2] >= 0.3f; if (has_ground_plane) { last_ground_plane_altitude = pm.ps->origin[2]; } if ((last_ground_plane_altitude - pm.ps->origin[2]) > 32.f) { return false; } if (pml.groundTrace.hitId != 3998 && !is_deflection_ok(from, pm.ps->origin, move_dir)) { return false; } } if ((last_ground_plane_altitude - to[2]) > 32.f) { return false; } *dist = std::sqrtf(dist_squared); return dist_squared <= 16.f && std::abs(pm.ps->origin[2] - to[2]) <= 18.f; } bool can_link_nodes(game::pathnode_t* from, game::pathnode_t* to, float* dist, bool* negotiation_link) { if (is_negotation_link(from, to)) { *negotiation_link = true; *dist = 15.f; return true; } else { game::vec3_t delta{}; delta[0] = to->constant.vLocalOrigin[0] - from->constant.vLocalOrigin[0]; delta[1] = to->constant.vLocalOrigin[1] - from->constant.vLocalOrigin[1]; delta[2] = to->constant.vLocalOrigin[2] - from->constant.vLocalOrigin[2]; if (std::abs(delta[2]) > 128.f) { return false; } if ((delta[0] * delta[0] + delta[1] * delta[1]) > 65536.f) { return false; } game::vec2_t move_dir{}; move_dir[0] = to->constant.vLocalOrigin[0] - from->constant.vLocalOrigin[0]; move_dir[1] = to->constant.vLocalOrigin[1] - from->constant.vLocalOrigin[1]; *dist = game::Vec2Normalize(move_dir); *negotiation_link = false; return phys_trace_passed(from->constant.vLocalOrigin, to->constant.vLocalOrigin, dist); } } bool try_link_nodes(game::pathnode_t* from, game::pathnode_t* to, game::pathlink_s* links, int max_links) { float dist{}; bool negotiation_link{}; if (max_links <= 0) { console::error("[Connect paths] Out of available links, increase link buffer size\n"); return false; } if (!can_link_nodes(from, to, &dist, &negotiation_link)) { return false; } const auto link = &links[from->constant.totalLinkCount++]; link->nodeNum = static_cast(to - game::pathData->nodes); link->fDist = dist; link->disconnectCount = 0; link->negotiationLink = negotiation_link; return true; } void link_pathnodes() { constexpr auto max_links = 0x80000; const auto links_buffer = allocator.allocate_array(max_links); auto total_link_count = 0; for (auto i = 0u; i < game::pathData->nodeCount; i++) { const auto node = &game::pathData->nodes[i]; if ((node->constant.spawnflags & 1) || !node->constant.type) { continue; } for (auto o = 0u; o < game::pathData->nodeCount; o++) { const auto other = &game::pathData->nodes[o]; if (o == i || (other->constant.spawnflags & 1) || !other->constant.type) { continue; } try_link_nodes(node, other, &links_buffer[total_link_count], max_links - total_link_count); } total_link_count += node->constant.totalLinkCount; if (node->constant.totalLinkCount == 0) { console::info("[Connect paths] Pathnode at (%f %f %f) has no links\n", node->constant.vLocalOrigin[0], node->constant.vLocalOrigin[1], node->constant.vLocalOrigin[2] ); } } console::info("[Connect paths] Total links: %i\n", total_link_count); auto accounted_links = 0; for (auto i = 0u; i < game::pathData->nodeCount; i++) { if (game::pathData->nodes[i].constant.totalLinkCount) { game::pathData->nodes[i].constant.Links = &links_buffer[accounted_links]; accounted_links += game::pathData->nodes[i].constant.totalLinkCount; } } } float distance(float* a, float* b) { return std::sqrtf((a[0] - b[0]) * (a[0] - b[0]) + (a[1] - b[1]) * (a[1] - b[1])); } void connect_paths() { console::info("[Connect paths] Node count: %i\n", game::pathData->nodeCount); auto original_link_count = 0; for (auto i = 0u; i < game::pathData->nodeCount; i++) { original_link_count += game::pathData->nodes[i].constant.totalLinkCount; } console::info("[Connect paths] Original link count: %i\n", original_link_count); for (auto i = 0u; i < game::pathData->nodeCount; i++) { if (game::pathData->nodes[i].constant.Links != nullptr) { console::warn("[Connect paths] Path nodes already linked\n"); return; } } game::pathData->dynamicNodeGroupCount = 0; game::pathData->visBytes = 0; link_pathnodes(); const auto node_indexes = allocator.allocate_array(game::pathData->nodeCount); for (auto i = 0u; i < game::pathData->nodeCount; i++) { node_indexes[i] = static_cast(i); } console::info("[Connect paths] Building pathnode trees...\n"); game::pathData->nodeTreeCount = 0; game::pathData->nodeTree = build_node_tree(node_indexes, game::pathData->nodeCount); console::info("[Connect paths] Total trees: %i\n", game::pathData->nodeTreeCount); } float pm_cmd_scale_walk_stub(void*, void*, void*) { return 1.f; } char patch_bytes[2][5]{}; void patch_functions() { std::memcpy(&patch_bytes[0], reinterpret_cast(0x1406887D0), 5); std::memcpy(&patch_bytes[1], reinterpret_cast(0x1403C8414), 5); utils::hook::jump(0x1406887D0, pm_cmd_scale_walk_stub); utils::hook::nop(0x1403C8414, 5); } void restore_code(const size_t ptr, char* data, const size_t size) { const auto ptr_ = reinterpret_cast(ptr); DWORD old_protect; VirtualProtect(ptr_, size, PAGE_EXECUTE_READWRITE, &old_protect); for (auto i = 0; i < size; i++) { ptr_[i] = data[i]; } VirtualProtect(ptr_, size, old_protect, &old_protect); FlushInstructionCache(GetCurrentProcess(), ptr_, size); } void restore_functions() { restore_code(0x1406887D0, patch_bytes[0], 5); restore_code(0x1403C8414, patch_bytes[1], 5); } void path_init_paths_stub(void* a1, void* a2, void* a3, void* a4) { path_init_paths_hook.invoke(a1, a2, a3, a4); if (g_connect_paths->current.enabled) { patch_functions(); const auto _0 = gsl::finally(restore_functions); connect_paths(); } } } class component final : public component_interface { public: void post_unpack() override { g_connect_paths = dvars::register_bool("g_connectPaths", false, 0, "Connect paths"); path_init_paths_hook.create(0x140522250, path_init_paths_stub); } }; } REGISTER_COMPONENT(pathnodes::component)