From 2241e03623a618b42ec94c223327e52dd4c210d8 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 3 Oct 2024 09:55:05 -0230 Subject: [PATCH 01/28] Initial commit --- game_patch/misc/player.cpp | 1 + game_patch/multi/server.cpp | 133 +++++++++++++++++++++++++++++ game_patch/multi/server_internal.h | 14 +++ game_patch/rf/level.h | 3 + game_patch/rf/multi.h | 2 + 5 files changed, 153 insertions(+) diff --git a/game_patch/misc/player.cpp b/game_patch/misc/player.cpp index 59f77df3..340b24a0 100644 --- a/game_patch/misc/player.cpp +++ b/game_patch/misc/player.cpp @@ -12,6 +12,7 @@ #include "../main/main.h" #include "../multi/multi.h" #include "../hud/multi_spectate.h" +#include #include #include #include diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index e5a53307..50e8a028 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -42,6 +42,8 @@ const char* g_rcon_cmd_whitelist[] = { "map_prev", }; +std::vector> respawn_points; + ServerAdditionalConfig g_additional_server_config; std::string g_prev_level; @@ -554,6 +556,127 @@ static bool check_player_ac_status([[maybe_unused]] rf::Player* player) return true; } +FunHook multi_respawn_create_point_hook{ + // Replace with the actual address of the original function from disassembly + 0x00470190, + [](const char* name, uint8_t team, const rf::Vector3* pos, rf::Matrix3* orient, bool RedTeam, bool blue_team, + bool bot) -> int { + // Log using xlog for debugging + + + // Log position if available + if (pos) { + xlog::warn("Original Position: ({}, {}, {})", pos->x, pos->y, pos->z); + } + + auto respawn_point = std::make_shared(); + respawn_point->name = name; + respawn_point->team = team; + respawn_point->position = *pos; // Copy the position safely + respawn_point->orientation = *orient; // Copy the orientation safely + respawn_point->redTeam = RedTeam; + respawn_point->blueTeam = blue_team; + respawn_point->bot = bot; + + xlog::warn("Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", respawn_point->name, respawn_point->team, respawn_point->redTeam, respawn_point->blueTeam, respawn_point->bot); + + respawn_points.push_back(respawn_point); + + // Call the original function if necessary + //int result = multi_respawn_create_point_hook.call_target(name, team, pos, orient, RedTeam, blue_team, bot); + + //populate stock spawn point counter + rf::multi_respawn_num_points = respawn_points.size(); + + xlog::warn("num points: {}", rf::multi_respawn_num_points); + xlog::warn("Total respawn points stored: {}", respawn_points.size()); + + // Return the result of the original function + //return result; + } +}; + +std::shared_ptr select_random_respawn_point() +{ + // Ensure there are respawn points to choose from + if (respawn_points.empty()) { + xlog::warn("No respawn points available!"); + return nullptr; + } + + // Generate a random index within the bounds of the respawn points vector + int random_index = 3; //temp + + // Log the selected index for debugging purposes + xlog::info("Randomly selected respawn point index: {}", random_index); + + // Return the respawn point at the randomly selected index + return respawn_points[random_index]; +} + +// decide which spawn point the player will spawn at (maybe working? not sure if dtaa type is right) +FunHook multi_respawn_get_next_point_hook{ + // Address of the function (replace with actual address) + 0x00470300, [](rf::Vector3* pos, rf::Matrix3* orient, rf::Player* pp) { + // Print information for debugging + xlog::warn("next spawn point func called"); + + // Log the position if pos is not null + /* if (pos) { + xlog::warn("Position: x = {}, y = {}, z = {}", pos->x, pos->y, pos->z); + } + else { + xlog::warn("Position pointer is null."); + } + + // Log some player information (assuming Player has name, ID, and team attributes) + if (pp) { + xlog::warn("Player Name: {}", pp->name); + xlog::warn("Player Team: {}", pp->team); + } + else { + xlog::warn("Player pointer is null."); + }*/ + + // Call the original function if needed + //multi_respawn_get_next_point_hook.call_target(pos, orient, pp); + + //xlog::warn("num points: {}", rf::multi_respawn_num_points); + } +}; + +CallHook player_create_entity_hook{ + { + 0x0048087D, + 0x00481422, + 0x0045C807 + }, + [](rf::Player* pp, int entity_type, rf::Vector3* pos, rf::Matrix3* orient, int mp_character) -> rf::Entity* { + // Log the original arguments for debugging purposes + xlog::warn("Hooked player_create_entity. Player: {}, Entity Type: {}, MP Character: {}", pp->name, entity_type, + mp_character); + + // Call the random respawn point selection logic + std::shared_ptr random_respawn = select_random_respawn_point(); + + if (mp_character > -1) { + if (random_respawn) { + xlog::info("Player will respawn at: {}. Position: ({}, {}, {})", random_respawn->name, + random_respawn->position.x, random_respawn->position.y, random_respawn->position.z); + + // Overwrite the position and orientation with the selected respawn point's values + *pos = random_respawn->position; + *orient = random_respawn->orientation; + } + else { + xlog::warn("No valid respawn point selected. Proceeding with the original logic."); + } + } + // Call the original function with all original arguments forwarded + return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); + } +}; + FunHook multi_spawn_player_server_side_hook{ 0x00480820, [](rf::Player* player) { @@ -575,6 +698,7 @@ FunHook multi_spawn_player_server_side_hook{ ep->armor = g_additional_server_config.spawn_armor.value(); } } + //xlog::warn("num spawns: {}", rf::multi_respawn_num_points()); }, }; @@ -710,6 +834,15 @@ void server_init() multi_on_new_player_injection.install(); AsmWriter(0x0047B061, 0x0047B064).add(asm_regs::esp, 0x14); + multi_respawn_create_point_hook.install(); + multi_respawn_get_next_point_hook.install(); + player_create_entity_hook.install(); + + // nop original get spawn point + AsmWriter(0x0048085B).nop(5); + + AsmWriter(0x0045C78E).nop(5); + // Support forcing player character multi_spawn_player_server_side_hook.install(); diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index 71317375..da6de342 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -4,6 +4,9 @@ #include #include #include +#include +#include "../rf/math/vector.h" +#include "../rf/math/matrix.h" // Forward declarations namespace rf @@ -26,6 +29,17 @@ struct HitSoundsConfig int rate_limit = 10; }; +struct RespawnPoint +{ + std::string name; + uint8_t team; + rf::Vector3 position; + rf::Matrix3 orientation; + bool redTeam; + bool blueTeam; + bool bot; +}; + struct ServerAdditionalConfig { VoteConfig vote_kick; diff --git a/game_patch/rf/level.h b/game_patch/rf/level.h index da9e53e1..2315c865 100644 --- a/game_patch/rf/level.h +++ b/game_patch/rf/level.h @@ -86,5 +86,8 @@ namespace rf static auto& level = addr_as_ref(0x00645FD8); static auto& level_filename_to_load = addr_as_ref(0x00646140); + static auto& get_player_start_position = addr_as_ref(0x00463D25); + static auto& get_player_start_orientation = addr_as_ref(0x00463D38); + } diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index 92bb5b99..6d3036a3 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -175,6 +175,8 @@ namespace rf static auto& simultaneous_ping = addr_as_ref(0x00599CD8); static auto& tracker_addr = addr_as_ref(0x006FC550); + static auto& multi_respawn_num_points = addr_as_ref(0x006A1458); + enum ChatSayType { CHAT_SAY_GLOBAL = 0, CHAT_SAY_TEAM = 1, From dd3a80964b279e5813b772d1ee824cf4bd44df13 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 3 Oct 2024 10:51:52 -0230 Subject: [PATCH 02/28] Add rng to main --- game_patch/main/main.cpp | 13 +++++++++++++ game_patch/main/main.h | 8 +++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/game_patch/main/main.cpp b/game_patch/main/main.cpp index a164944c..6d7eed6b 100644 --- a/game_patch/main/main.cpp +++ b/game_patch/main/main.cpp @@ -78,6 +78,16 @@ CodeInjection cleanup_game_hook{ }, }; +std::mt19937 rng; +std::uniform_int_distribution dist; + +void initialize_random_generator() +{ + // seed rng with the current time + auto seed = std::chrono::steady_clock::now().time_since_epoch().count(); + rng.seed(static_cast(seed)); +} + static void maybe_autosave() { static int pending_autosave = 0; @@ -338,6 +348,9 @@ extern "C" DWORD __declspec(dllexport) Init([[maybe_unused]] void* unused) init_logging(); init_crash_handler(); + // Init random number generator + initialize_random_generator(); + // Enable Data Execution Prevention if (!SetProcessDEPPolicy(PROCESS_DEP_ENABLE)) xlog::warn("SetProcessDEPPolicy failed (error {})", GetLastError()); diff --git a/game_patch/main/main.h b/game_patch/main/main.h index 2e8ed07e..1816cae7 100644 --- a/game_patch/main/main.h +++ b/game_patch/main/main.h @@ -1,8 +1,14 @@ #pragma once - +#include #include extern GameConfig g_game_config; + +// random number generator +extern std::mt19937 rng; +extern std::uniform_int_distribution dist; +void initialize_random_generator(); + #ifdef _WINDOWS_ extern HMODULE g_hmodule; #endif From 7dd89dbd6b16743d32f7b3d7c68e2a1b3f02fb79 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 4 Oct 2024 00:49:56 -0230 Subject: [PATCH 03/28] support with these defaults: $DF Use New Spawn Logic: true +Try Avoid Last: true +Try Avoid Enemies: true --- game_patch/misc/player.h | 1 + game_patch/multi/server.cpp | 305 ++++++++++++++++++++++++----- game_patch/multi/server_internal.h | 8 + 3 files changed, 260 insertions(+), 54 deletions(-) diff --git a/game_patch/misc/player.h b/game_patch/misc/player.h index 829f1f12..077aa043 100644 --- a/game_patch/misc/player.h +++ b/game_patch/misc/player.h @@ -26,6 +26,7 @@ struct PlayerAdditionalData std::map saves; rf::Vector3 last_teleport_pos; rf::TimestampRealtime last_teleport_timestamp; + int last_spawn_point_index = -1; }; void find_player(const StringMatcher& query, std::function consumer); diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 50e8a028..f1e2e5ab 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -47,6 +47,11 @@ std::vector> respawn_points; ServerAdditionalConfig g_additional_server_config; std::string g_prev_level; +static bool multi_is_team_game_type() +{ + return rf::multi_get_game_type() != rf::NG_TYPE_DM; +} + void parse_vote_config(const char* vote_name, VoteConfig& config, rf::Parser& parser) { std::string vote_option_name = std::format("$DF {}:", vote_name); @@ -76,6 +81,17 @@ void load_additional_server_config(rf::Parser& parser) parse_vote_config("Vote Restart", g_additional_server_config.vote_restart, parser); parse_vote_config("Vote Next", g_additional_server_config.vote_next, parser); parse_vote_config("Vote Previous", g_additional_server_config.vote_previous, parser); + + if (parser.parse_optional("$DF Use New Spawn Logic:")) { + g_additional_server_config.random_spawns.enabled = parser.parse_bool(); + if (parser.parse_optional("+Try Avoid Last:")) { + g_additional_server_config.random_spawns.try_avoid_last = parser.parse_bool(); + } + if (parser.parse_optional("+Try Avoid Enemies:")) { + g_additional_server_config.random_spawns.try_avoid_enemies = parser.parse_bool(); + } + } + if (parser.parse_optional("$DF Spawn Protection Duration:")) { g_additional_server_config.spawn_protection_duration_ms = parser.parse_uint(); } @@ -556,64 +572,244 @@ static bool check_player_ac_status([[maybe_unused]] rf::Player* player) return true; } -FunHook multi_respawn_create_point_hook{ - // Replace with the actual address of the original function from disassembly +FunHook multi_respawn_create_point_hook{ 0x00470190, [](const char* name, uint8_t team, const rf::Vector3* pos, rf::Matrix3* orient, bool RedTeam, bool blue_team, - bool bot) -> int { - // Log using xlog for debugging - - - // Log position if available - if (pos) { - xlog::warn("Original Position: ({}, {}, {})", pos->x, pos->y, pos->z); + bool bot) { + if (g_additional_server_config.random_spawns.enabled) { + auto respawn_point = std::make_shared(); + respawn_point->name = name; + respawn_point->team = team; + respawn_point->position = *pos; + respawn_point->orientation = *orient; + respawn_point->redTeam = RedTeam; + respawn_point->blueTeam = blue_team; + respawn_point->bot = bot; + + respawn_points.push_back(respawn_point); + + // tick up spawn point counter + rf::multi_respawn_num_points += 1; + + xlog::warn("Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", respawn_point->name, + respawn_point->team, respawn_point->redTeam, respawn_point->blueTeam, respawn_point->bot); + if (pos) { + xlog::warn("Position: ({}, {}, {})", pos->x, pos->y, pos->z); + } + } + else { + // if random spawns is off, maintain stock behaviour + multi_respawn_create_point_hook.call_target(name, team, pos, orient, RedTeam, blue_team, bot); } - auto respawn_point = std::make_shared(); - respawn_point->name = name; - respawn_point->team = team; - respawn_point->position = *pos; // Copy the position safely - respawn_point->orientation = *orient; // Copy the orientation safely - respawn_point->redTeam = RedTeam; - respawn_point->blueTeam = blue_team; - respawn_point->bot = bot; + xlog::warn("num points: {}", rf::multi_respawn_num_points); + } +}; - xlog::warn("Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", respawn_point->name, respawn_point->team, respawn_point->redTeam, respawn_point->blueTeam, respawn_point->bot); +// only called when random spawns is turned on +std::pair> select_respawn_point(int team, int last_index) +{ + if (respawn_points.empty()) { + xlog::warn("No respawn points available!"); + return {-1, nullptr}; + } - respawn_points.push_back(respawn_point); + if (multi_is_team_game_type()) { + std::size_t team_count = 0; + std::optional selected_team_index; + + // count valid team-specific spawn points + for (std::size_t i = 0; i < respawn_points.size(); ++i) { + const auto& spawn_point = respawn_points[i]; + + // confirm the spawn point matches the player's team + if ((team == 0 && spawn_point->redTeam) || (team == 1 && spawn_point->blueTeam)) { + ++team_count; + // randomly decide whether to select this one + if (!selected_team_index || (rng() % team_count == 0)) { + selected_team_index = i; + } + } + } - // Call the original function if necessary - //int result = multi_respawn_create_point_hook.call_target(name, team, pos, orient, RedTeam, blue_team, bot); + // if we found a valid team spawn point + if (selected_team_index) { + xlog::warn("Randomly selected team-specific spawn point index: {}", *selected_team_index); + return {*selected_team_index, respawn_points[*selected_team_index]}; + } - //populate stock spawn point counter - rf::multi_respawn_num_points = respawn_points.size(); + // if we didn't find a valid team spawn point + xlog::warn("No team-specific spawn point available."); + } - xlog::warn("num points: {}", rf::multi_respawn_num_points); - xlog::warn("Total respawn points stored: {}", respawn_points.size()); + // randomly select from the full pool of spawns + std::uniform_int_distribution random_range(0, respawn_points.size() - 1); + std::size_t random_index = random_range(rng); - // Return the result of the original function - //return result; - } -}; + xlog::warn("Randomly selected spawn point index from full pool: {}", random_index); + + return {random_index, respawn_points[random_index]}; +} -std::shared_ptr select_random_respawn_point() +// only called if random spawns is turned on +std::shared_ptr select_respawn_point_new(rf::Player* pp) { - // Ensure there are respawn points to choose from if (respawn_points.empty()) { xlog::warn("No respawn points available!"); - return nullptr; + return {nullptr}; + } + + auto& pdata = get_player_additional_data(pp); + int team = pp->team; + int last_index = pdata.last_spawn_point_index; + xlog::warn("Player is on team {}. Last index was {}", team, last_index); + + bool is_team_game = multi_is_team_game_type(); + bool avoid_last = g_additional_server_config.random_spawns.try_avoid_last; + bool avoid_enemies = g_additional_server_config.random_spawns.try_avoid_enemies; + + // Variables to store the furthest spawn point (if avoiding enemies) + std::optional furthest_spawn_index; + std::optional second_furthest_spawn_index; + float max_distance = 0.0f; + float second_max_distance = 0.0f; + + // Get the list of players in the game + auto player_list = SinglyLinkedList{rf::player_list}; + bool has_other_players = false; + bool has_enemies = false; + + // Check if there are any other players and whether they are enemies + for (auto& player : player_list) { + if (&player != pp && !rf::player_is_dead(&player)) { + has_other_players = true; + if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { + has_enemies = true; // There's at least one enemy + break; + } + } } - // Generate a random index within the bounds of the respawn points vector - int random_index = 3; //temp + // **Team game logic**: If it's a team game, prioritize valid team spawns + std::vector valid_team_spawns; + if (is_team_game) { + for (std::size_t i = 0; i < respawn_points.size(); ++i) { + const auto& spawn_point = respawn_points[i]; + + // Only consider spawn points that match the player's team + if ((team == 0 && spawn_point->redTeam) || (team == 1 && spawn_point->blueTeam)) { + valid_team_spawns.push_back(i); + } + } - // Log the selected index for debugging purposes - xlog::info("Randomly selected respawn point index: {}", random_index); + // If no valid team-specific spawn points are found, fall back to non-team-based logic + if (valid_team_spawns.empty()) { + xlog::warn("No team-specific spawn points available. Falling back to random selection."); + } + else { + // **Avoid enemies first** if enabled and enemies are present + if (avoid_enemies && has_enemies) { + // Calculate the furthest spawn point from enemies + for (std::size_t i : valid_team_spawns) { + const auto& spawn_point = respawn_points[i]; + rf::Vector3 spawn_position = spawn_point->position; + + float min_distance_to_enemy = std::numeric_limits::max(); + + // Loop through all players and check if they are enemies + for (auto& player : player_list) { + if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { + // Consider enemies if the player is on another team (in team games) or anyone in deathmatch + if (!rf::player_is_dead(&player)) { + rf::Entity* entity = rf::entity_from_handle(player.entity_handle); + if (entity) { + // Calculate the distance between the spawn point and the enemy player + float distance = (spawn_position - entity->pos).len(); + min_distance_to_enemy = std::min(min_distance_to_enemy, distance); + } + } + } + } + + // Check if this spawn point is the furthest from any enemy player + if (min_distance_to_enemy > max_distance) { + // If it's the last spawn point used, remember it but don't select yet + if (avoid_last && i == last_index) { + second_max_distance = max_distance; + second_furthest_spawn_index = furthest_spawn_index; + continue; // Skip this spawn point if it's the last used + } + + // Update the second furthest point before overwriting the furthest + second_max_distance = max_distance; + second_furthest_spawn_index = furthest_spawn_index; + + max_distance = min_distance_to_enemy; + furthest_spawn_index = i; + } + } + + // If we found a valid spawn point furthest from enemies, use it + if (furthest_spawn_index) { + xlog::warn("Selected team-specific spawn point furthest from enemies: {}", *furthest_spawn_index); + + // If the furthest is the last one used, use the next furthest if available + if (avoid_last && *furthest_spawn_index == last_index && second_furthest_spawn_index) { + xlog::warn("Furthest spawn point is the last one used. Using the second furthest point: {}", + *second_furthest_spawn_index); + pdata.last_spawn_point_index = *second_furthest_spawn_index; + return respawn_points[*second_furthest_spawn_index]; + } + + pdata.last_spawn_point_index = *furthest_spawn_index; + return respawn_points[*furthest_spawn_index]; + } + } - // Return the respawn point at the randomly selected index - return respawn_points[random_index]; + // **Avoid last**: Handle try_avoid_last for team game + if (avoid_last && valid_team_spawns.size() > 1 && + std::find(valid_team_spawns.begin(), valid_team_spawns.end(), last_index) != valid_team_spawns.end()) { + valid_team_spawns.erase(std::remove(valid_team_spawns.begin(), valid_team_spawns.end(), last_index), + valid_team_spawns.end()); + xlog::warn("Excluding last spawn point index: {} from selection.", last_index); + } + + // Randomly select from the remaining valid team spawns + if (!valid_team_spawns.empty()) { + std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); + std::size_t random_team_index = valid_team_spawns[team_range(rng)]; + + pdata.last_spawn_point_index = random_team_index; + xlog::warn("Randomly selected team-specific spawn point index: {}", random_team_index); + return {respawn_points[random_team_index]}; + } + } + } + + // Handle non-team game or if no valid team-specific spawn points were found + std::uniform_int_distribution random_range(0, respawn_points.size() - 1); + std::size_t random_index = random_range(rng); + + // Avoid last_index if possible (if more than 1 spawn point exists and try_avoid_last is true) + if (avoid_last && respawn_points.size() > 1 && random_index == last_index) { + xlog::warn("Randomly selected last spawn point, rerolling."); + random_index = (random_index + 1) % respawn_points.size(); + } + + xlog::warn("Randomly selected spawn point index from full pool: {}", random_index); + pdata.last_spawn_point_index = random_index; + + return {respawn_points[random_index]}; } + + + + + + + + // decide which spawn point the player will spawn at (maybe working? not sure if dtaa type is right) FunHook multi_respawn_get_next_point_hook{ // Address of the function (replace with actual address) @@ -647,29 +843,32 @@ FunHook multi_respawn_get_next_po CallHook player_create_entity_hook{ { - 0x0048087D, - 0x00481422, - 0x0045C807 + 0x0048087D, // in spawn_player_server_side + 0x00481422, //unsure if needed + 0x0045C807 // in load_level }, [](rf::Player* pp, int entity_type, rf::Vector3* pos, rf::Matrix3* orient, int mp_character) -> rf::Entity* { // Log the original arguments for debugging purposes - xlog::warn("Hooked player_create_entity. Player: {}, Entity Type: {}, MP Character: {}", pp->name, entity_type, - mp_character); - - // Call the random respawn point selection logic - std::shared_ptr random_respawn = select_random_respawn_point(); + xlog::warn("Processing player_create_entity request. Player: {}, Team: {}, Entity Type: {}, MP Character: {}", + pp->name, pp->team, entity_type, mp_character); + // don't change single player spawns if (mp_character > -1) { + // get a spawn point + auto random_respawn = select_respawn_point_new(pp); if (random_respawn) { - xlog::info("Player will respawn at: {}. Position: ({}, {}, {})", random_respawn->name, - random_respawn->position.x, random_respawn->position.y, random_respawn->position.z); + xlog::warn("Player will respawn at: {}. Position: ({}, {}, {})", + random_respawn->name, random_respawn->position.x, random_respawn->position.y, random_respawn->position.z); // Overwrite the position and orientation with the selected respawn point's values *pos = random_respawn->position; *orient = random_respawn->orientation; } else { - xlog::warn("No valid respawn point selected. Proceeding with the original logic."); + *pos = rf::level.player_start_pos; + *orient = rf::level.player_start_orient; + + xlog::warn("No valid respawn points. Spawning at player start."); } } // Call the original function with all original arguments forwarded @@ -712,11 +911,6 @@ static float get_weapon_shot_stats_delta(rf::Weapon* wp) return 1.0f / num_projectiles; } -static bool multi_is_team_game_type() -{ - return rf::multi_get_game_type() != rf::NG_TYPE_DM; -} - static void maybe_increment_weapon_hits_stat(int hit_obj_handle, rf::Weapon *wp) { rf::Entity* attacker_ep = rf::entity_from_handle(wp->parent_handle); @@ -780,6 +974,9 @@ CodeInjection multi_limbo_init_injection{ xlog::trace("Wait between levels shortened because server is empty"); addr_as_ref(regs.esp) = 100; } + + // clear list of respawn points on map end + respawn_points.clear(); }, }; diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index da6de342..fa626653 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -29,6 +29,13 @@ struct HitSoundsConfig int rate_limit = 10; }; +struct NewSpawnLogicConfig +{ + bool enabled = true; + bool try_avoid_last = true; + bool try_avoid_enemies = true; +}; + struct RespawnPoint { std::string name; @@ -48,6 +55,7 @@ struct ServerAdditionalConfig VoteConfig vote_restart; VoteConfig vote_next; VoteConfig vote_previous; + NewSpawnLogicConfig random_spawns; int spawn_protection_duration_ms = 1500; std::optional spawn_life; std::optional spawn_armor; From 809ca71e9bd7e875901ac97182cc149757ca7b7e Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 4 Oct 2024 02:40:23 -0230 Subject: [PATCH 04/28] refactor some code and remove unneeded code --- docs/CHANGELOG.md | 1 + game_patch/multi/server.cpp | 151 +++++++++--------------------------- 2 files changed, 37 insertions(+), 115 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1c2cb258..ca66436a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -43,6 +43,7 @@ Version 1.9.0 (not released yet) - Do not load unnecessary VPPs in dedicated server mode - Add level filename to "Level Initializing" console message - Properly handle WM_PAINT in dedicated server, may improve performance (DF bug) +- Add optional improved respawn point selection method for multiplayer Version 1.8.0 (released 2022-09-17) ----------------------------------- diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index f1e2e5ab..d4558322 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -572,6 +572,7 @@ static bool check_player_ac_status([[maybe_unused]] rf::Player* player) return true; } +// add a new spawn point FunHook multi_respawn_create_point_hook{ 0x00470190, [](const char* name, uint8_t team, const rf::Vector3* pos, rf::Matrix3* orient, bool RedTeam, bool blue_team, @@ -596,61 +597,15 @@ FunHookx, pos->y, pos->z); } + xlog::warn("num points: {}", rf::multi_respawn_num_points); } else { // if random spawns is off, maintain stock behaviour multi_respawn_create_point_hook.call_target(name, team, pos, orient, RedTeam, blue_team, bot); } - - xlog::warn("num points: {}", rf::multi_respawn_num_points); } }; -// only called when random spawns is turned on -std::pair> select_respawn_point(int team, int last_index) -{ - if (respawn_points.empty()) { - xlog::warn("No respawn points available!"); - return {-1, nullptr}; - } - - if (multi_is_team_game_type()) { - std::size_t team_count = 0; - std::optional selected_team_index; - - // count valid team-specific spawn points - for (std::size_t i = 0; i < respawn_points.size(); ++i) { - const auto& spawn_point = respawn_points[i]; - - // confirm the spawn point matches the player's team - if ((team == 0 && spawn_point->redTeam) || (team == 1 && spawn_point->blueTeam)) { - ++team_count; - // randomly decide whether to select this one - if (!selected_team_index || (rng() % team_count == 0)) { - selected_team_index = i; - } - } - } - - // if we found a valid team spawn point - if (selected_team_index) { - xlog::warn("Randomly selected team-specific spawn point index: {}", *selected_team_index); - return {*selected_team_index, respawn_points[*selected_team_index]}; - } - - // if we didn't find a valid team spawn point - xlog::warn("No team-specific spawn point available."); - } - - // randomly select from the full pool of spawns - std::uniform_int_distribution random_range(0, respawn_points.size() - 1); - std::size_t random_index = random_range(rng); - - xlog::warn("Randomly selected spawn point index from full pool: {}", random_index); - - return {random_index, respawn_points[random_index]}; -} - // only called if random spawns is turned on std::shared_ptr select_respawn_point_new(rf::Player* pp) { @@ -802,77 +757,49 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) return {respawn_points[random_index]}; } - - - - - - - - -// decide which spawn point the player will spawn at (maybe working? not sure if dtaa type is right) -FunHook multi_respawn_get_next_point_hook{ - // Address of the function (replace with actual address) - 0x00470300, [](rf::Vector3* pos, rf::Matrix3* orient, rf::Player* pp) { - // Print information for debugging - xlog::warn("next spawn point func called"); - - // Log the position if pos is not null - /* if (pos) { - xlog::warn("Position: x = {}, y = {}, z = {}", pos->x, pos->y, pos->z); - } - else { - xlog::warn("Position pointer is null."); - } - - // Log some player information (assuming Player has name, ID, and team attributes) - if (pp) { - xlog::warn("Player Name: {}", pp->name); - xlog::warn("Player Team: {}", pp->team); - } - else { - xlog::warn("Player pointer is null."); - }*/ - - // Call the original function if needed - //multi_respawn_get_next_point_hook.call_target(pos, orient, pp); - - //xlog::warn("num points: {}", rf::multi_respawn_num_points); - } -}; - CallHook player_create_entity_hook{ { - 0x0048087D, // in spawn_player_server_side - 0x00481422, //unsure if needed - 0x0045C807 // in load_level + 0x0048087D, // in spawn_player_server_side, used by servers + 0x0045C807 // in load_level, used by single player and listen servers (initial spawn) }, [](rf::Player* pp, int entity_type, rf::Vector3* pos, rf::Matrix3* orient, int mp_character) -> rf::Entity* { - // Log the original arguments for debugging purposes xlog::warn("Processing player_create_entity request. Player: {}, Team: {}, Entity Type: {}, MP Character: {}", pp->name, pp->team, entity_type, mp_character); - // don't change single player spawns - if (mp_character > -1) { - // get a spawn point - auto random_respawn = select_respawn_point_new(pp); - if (random_respawn) { - xlog::warn("Player will respawn at: {}. Position: ({}, {}, {})", - random_respawn->name, random_respawn->position.x, random_respawn->position.y, random_respawn->position.z); - - // Overwrite the position and orientation with the selected respawn point's values - *pos = random_respawn->position; - *orient = random_respawn->orientation; + // in single player don't touch anything + if (mp_character == -1) { + return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); + } + + // assume no valid spawn until proven otherwise + bool valid_respawn_found = false; + + // do we have any spawn points? + if (rf::multi_respawn_num_points > 0) { + if (g_additional_server_config.random_spawns.enabled) { + // new spawn logic is enabled, try to find a valid respawn point + if (auto random_respawn = select_respawn_point_new(pp)) { + xlog::warn("Spawning {} at ({}, {}, {})", pp->name, random_respawn->position.x, + random_respawn->position.y, random_respawn->position.z); + *pos = random_respawn->position; + *orient = random_respawn->orientation; + valid_respawn_found = true; // valid respawn was found using the new logic + } } else { - *pos = rf::level.player_start_pos; - *orient = rf::level.player_start_orient; - - xlog::warn("No valid respawn points. Spawning at player start."); + // old logic has a spawn point for us + valid_respawn_found = true; } - } - // Call the original function with all original arguments forwarded - return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); + } + + // use player start position if no spawn points were found + if (!valid_respawn_found) { + *pos = rf::level.player_start_pos; + *orient = rf::level.player_start_orient; + xlog::warn("No Multiplayer Respawn Points found. Spawning {} at the Player Start.", pp->name); + } + + return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); } }; @@ -897,7 +824,6 @@ FunHook multi_spawn_player_server_side_hook{ ep->armor = g_additional_server_config.spawn_armor.value(); } } - //xlog::warn("num spawns: {}", rf::multi_respawn_num_points()); }, }; @@ -1031,15 +957,10 @@ void server_init() multi_on_new_player_injection.install(); AsmWriter(0x0047B061, 0x0047B064).add(asm_regs::esp, 0x14); + // Support new spawn logic multi_respawn_create_point_hook.install(); - multi_respawn_get_next_point_hook.install(); player_create_entity_hook.install(); - // nop original get spawn point - AsmWriter(0x0048085B).nop(5); - - AsmWriter(0x0045C78E).nop(5); - // Support forcing player character multi_spawn_player_server_side_hook.install(); From 77d295cb5cdb37d244a4690866253967e1d724ef Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 4 Oct 2024 11:35:03 -0230 Subject: [PATCH 05/28] handle edge cases --- game_patch/multi/server.cpp | 138 +++++++++++++++++------------------- 1 file changed, 64 insertions(+), 74 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index d4558322..9910dda6 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -638,13 +638,65 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) for (auto& player : player_list) { if (&player != pp && !rf::player_is_dead(&player)) { has_other_players = true; - if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { - has_enemies = true; // There's at least one enemy + if ((is_team_game && player.team != team) || (!is_team_game)) { + has_enemies = true; // There's at least one enemy in deathmatch or different team break; } } } + // If avoiding enemies and enemies are present, find the furthest spawn point from them + if (avoid_enemies && has_enemies) { + xlog::warn("Avoiding enemies enabled, searching for furthest spawn point from enemies."); + for (std::size_t i = 0; i < respawn_points.size(); ++i) { + const auto& spawn_point = respawn_points[i]; + rf::Vector3 spawn_position = spawn_point->position; + + float min_distance_to_enemy = std::numeric_limits::max(); + + // Calculate distance to all enemies (all players in deathmatch) + for (auto& player : player_list) { + if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { + if (!rf::player_is_dead(&player)) { + rf::Entity* entity = rf::entity_from_handle(player.entity_handle); + if (entity) { + float distance = (spawn_position - entity->pos).len(); + min_distance_to_enemy = std::min(min_distance_to_enemy, distance); + } + } + } + } + + // Update furthest spawn point based on distance to enemies + if (min_distance_to_enemy > max_distance) { + if (avoid_last && i == last_index) { + second_max_distance = max_distance; + second_furthest_spawn_index = furthest_spawn_index; + continue; // Skip if it's the last used spawn + } + + second_max_distance = max_distance; + second_furthest_spawn_index = furthest_spawn_index; + + max_distance = min_distance_to_enemy; + furthest_spawn_index = i; + } + } + + // If we found a valid furthest spawn point, use it + if (furthest_spawn_index) { + if (avoid_last && *furthest_spawn_index == last_index && second_furthest_spawn_index) { + xlog::warn("Furthest spawn point is the last one used. Using the second furthest point: {}", + *second_furthest_spawn_index); + pdata.last_spawn_point_index = *second_furthest_spawn_index; + return respawn_points[*second_furthest_spawn_index]; + } + + pdata.last_spawn_point_index = *furthest_spawn_index; + return respawn_points[*furthest_spawn_index]; + } + } + // **Team game logic**: If it's a team game, prioritize valid team spawns std::vector valid_team_spawns; if (is_team_game) { @@ -662,86 +714,23 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) xlog::warn("No team-specific spawn points available. Falling back to random selection."); } else { - // **Avoid enemies first** if enabled and enemies are present - if (avoid_enemies && has_enemies) { - // Calculate the furthest spawn point from enemies - for (std::size_t i : valid_team_spawns) { - const auto& spawn_point = respawn_points[i]; - rf::Vector3 spawn_position = spawn_point->position; - - float min_distance_to_enemy = std::numeric_limits::max(); - - // Loop through all players and check if they are enemies - for (auto& player : player_list) { - if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { - // Consider enemies if the player is on another team (in team games) or anyone in deathmatch - if (!rf::player_is_dead(&player)) { - rf::Entity* entity = rf::entity_from_handle(player.entity_handle); - if (entity) { - // Calculate the distance between the spawn point and the enemy player - float distance = (spawn_position - entity->pos).len(); - min_distance_to_enemy = std::min(min_distance_to_enemy, distance); - } - } - } - } - - // Check if this spawn point is the furthest from any enemy player - if (min_distance_to_enemy > max_distance) { - // If it's the last spawn point used, remember it but don't select yet - if (avoid_last && i == last_index) { - second_max_distance = max_distance; - second_furthest_spawn_index = furthest_spawn_index; - continue; // Skip this spawn point if it's the last used - } - - // Update the second furthest point before overwriting the furthest - second_max_distance = max_distance; - second_furthest_spawn_index = furthest_spawn_index; - - max_distance = min_distance_to_enemy; - furthest_spawn_index = i; - } - } - - // If we found a valid spawn point furthest from enemies, use it - if (furthest_spawn_index) { - xlog::warn("Selected team-specific spawn point furthest from enemies: {}", *furthest_spawn_index); - - // If the furthest is the last one used, use the next furthest if available - if (avoid_last && *furthest_spawn_index == last_index && second_furthest_spawn_index) { - xlog::warn("Furthest spawn point is the last one used. Using the second furthest point: {}", - *second_furthest_spawn_index); - pdata.last_spawn_point_index = *second_furthest_spawn_index; - return respawn_points[*second_furthest_spawn_index]; - } - - pdata.last_spawn_point_index = *furthest_spawn_index; - return respawn_points[*furthest_spawn_index]; - } - } - - // **Avoid last**: Handle try_avoid_last for team game - if (avoid_last && valid_team_spawns.size() > 1 && - std::find(valid_team_spawns.begin(), valid_team_spawns.end(), last_index) != valid_team_spawns.end()) { + // Avoid last spawn point if needed + if (avoid_last && valid_team_spawns.size() > 1) { valid_team_spawns.erase(std::remove(valid_team_spawns.begin(), valid_team_spawns.end(), last_index), valid_team_spawns.end()); xlog::warn("Excluding last spawn point index: {} from selection.", last_index); } // Randomly select from the remaining valid team spawns - if (!valid_team_spawns.empty()) { - std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); - std::size_t random_team_index = valid_team_spawns[team_range(rng)]; + std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); + std::size_t random_team_index = team_range(rng); - pdata.last_spawn_point_index = random_team_index; - xlog::warn("Randomly selected team-specific spawn point index: {}", random_team_index); - return {respawn_points[random_team_index]}; - } + pdata.last_spawn_point_index = random_team_index; + return {respawn_points[random_team_index]}; } } - // Handle non-team game or if no valid team-specific spawn points were found + // Non-team or fallback logic: random spawn point selection std::uniform_int_distribution random_range(0, respawn_points.size() - 1); std::size_t random_index = random_range(rng); @@ -751,12 +740,13 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) random_index = (random_index + 1) % respawn_points.size(); } - xlog::warn("Randomly selected spawn point index from full pool: {}", random_index); pdata.last_spawn_point_index = random_index; - + xlog::warn("Randomly selected spawn point index from full pool: {}", random_index); return {respawn_points[random_index]}; } + + CallHook player_create_entity_hook{ { 0x0048087D, // in spawn_player_server_side, used by servers From 2fbfe50a6b2ce553ec33187f11a1e9d49587bc88 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 4 Oct 2024 23:17:35 -0230 Subject: [PATCH 06/28] update logging --- game_patch/multi/server.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 9910dda6..9d30a58f 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -606,7 +606,7 @@ FunHook select_respawn_point_new(rf::Player* pp) { if (respawn_points.empty()) { @@ -617,13 +617,13 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) auto& pdata = get_player_additional_data(pp); int team = pp->team; int last_index = pdata.last_spawn_point_index; - xlog::warn("Player is on team {}. Last index was {}", team, last_index); - bool is_team_game = multi_is_team_game_type(); bool avoid_last = g_additional_server_config.random_spawns.try_avoid_last; bool avoid_enemies = g_additional_server_config.random_spawns.try_avoid_enemies; - // Variables to store the furthest spawn point (if avoiding enemies) + xlog::warn("Giving {} a spawn. They are on team {}, and their last index was {}. We ARE {}avoiding last, and ARE {}avoiding enemies.", pp->name, team, last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); + + // vars to store the furthest spawn point std::optional furthest_spawn_index; std::optional second_furthest_spawn_index; float max_distance = 0.0f; @@ -634,7 +634,7 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) bool has_other_players = false; bool has_enemies = false; - // Check if there are any other players and whether they are enemies + // Check if any enemies are in the game for (auto& player : player_list) { if (&player != pp && !rf::player_is_dead(&player)) { has_other_players = true; From 22476ec151a4988b93e4f317e971531636904758 Mon Sep 17 00:00:00 2001 From: Goober Date: Sat, 5 Oct 2024 14:22:34 -0230 Subject: [PATCH 07/28] Goob --- common/include/common/version/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/include/common/version/version.h b/common/include/common/version/version.h index 8c520adc..5bc57eb4 100644 --- a/common/include/common/version/version.h +++ b/common/include/common/version/version.h @@ -14,7 +14,7 @@ #define VERSION_TYPE_RELEASE 4 // Variables to be modified during the release process -#define PRODUCT_NAME "Dash Faction" +#define PRODUCT_NAME "Goob Faction" #define VERSION_MAJOR 1 #define VERSION_MINOR 9 #define VERSION_PATCH 0 From 5a17790766b1bc6fb8c9d4bfade53357f42cf51f Mon Sep 17 00:00:00 2001 From: Goober Date: Mon, 7 Oct 2024 11:58:03 -0230 Subject: [PATCH 08/28] use g_ convention --- game_patch/main/main.cpp | 19 +++++++++---------- game_patch/main/main.h | 1 - 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/game_patch/main/main.cpp b/game_patch/main/main.cpp index 6d7eed6b..aaeb157c 100644 --- a/game_patch/main/main.cpp +++ b/game_patch/main/main.cpp @@ -44,6 +44,15 @@ GameConfig g_game_config; HMODULE g_hmodule; +std::mt19937 g_rng; + +void initialize_random_generator() +{ + // seed rng with the current time + auto seed = std::chrono::steady_clock::now().time_since_epoch().count(); + g_rng.seed(static_cast(seed)); +} + CallHook rf_init_hook{ 0x004B27CD, []() { @@ -78,16 +87,6 @@ CodeInjection cleanup_game_hook{ }, }; -std::mt19937 rng; -std::uniform_int_distribution dist; - -void initialize_random_generator() -{ - // seed rng with the current time - auto seed = std::chrono::steady_clock::now().time_since_epoch().count(); - rng.seed(static_cast(seed)); -} - static void maybe_autosave() { static int pending_autosave = 0; diff --git a/game_patch/main/main.h b/game_patch/main/main.h index 1816cae7..ffc21b9b 100644 --- a/game_patch/main/main.h +++ b/game_patch/main/main.h @@ -6,7 +6,6 @@ extern GameConfig g_game_config; // random number generator extern std::mt19937 rng; -extern std::uniform_int_distribution dist; void initialize_random_generator(); #ifdef _WINDOWS_ From 6dac0d52bae896d990d320528e814cdf33edd112 Mon Sep 17 00:00:00 2001 From: Goober Date: Mon, 7 Oct 2024 12:47:40 -0230 Subject: [PATCH 09/28] update _g for rng --- game_patch/main/main.h | 2 +- game_patch/multi/server.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/game_patch/main/main.h b/game_patch/main/main.h index ffc21b9b..d95b12d2 100644 --- a/game_patch/main/main.h +++ b/game_patch/main/main.h @@ -5,7 +5,7 @@ extern GameConfig g_game_config; // random number generator -extern std::mt19937 rng; +extern std::mt19937 g_rng; void initialize_random_generator(); #ifdef _WINDOWS_ diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 9d30a58f..40271e12 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -723,7 +723,7 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) // Randomly select from the remaining valid team spawns std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); - std::size_t random_team_index = team_range(rng); + std::size_t random_team_index = team_range(g_rng); pdata.last_spawn_point_index = random_team_index; return {respawn_points[random_team_index]}; @@ -732,7 +732,7 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) // Non-team or fallback logic: random spawn point selection std::uniform_int_distribution random_range(0, respawn_points.size() - 1); - std::size_t random_index = random_range(rng); + std::size_t random_index = random_range(g_rng); // Avoid last_index if possible (if more than 1 spawn point exists and try_avoid_last is true) if (avoid_last && respawn_points.size() > 1 && random_index == last_index) { From 3c1e629c23c782f8d91b8b49b26a3dd10c998826 Mon Sep 17 00:00:00 2001 From: Goober Date: Mon, 7 Oct 2024 13:00:47 -0230 Subject: [PATCH 10/28] clean up --- game_patch/multi/server.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 40271e12..9c36c6cf 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -600,7 +600,7 @@ FunHook select_respawn_point_new(rf::Player* pp) bool avoid_last = g_additional_server_config.random_spawns.try_avoid_last; bool avoid_enemies = g_additional_server_config.random_spawns.try_avoid_enemies; - xlog::warn("Giving {} a spawn. They are on team {}, and their last index was {}. We ARE {}avoiding last, and ARE {}avoiding enemies.", pp->name, team, last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); + xlog::warn("Giving {} a spawn. They are on team {}, and their last index was {}. We {}avoiding last, and {}avoiding enemies.", pp->name, team, last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); // vars to store the furthest spawn point std::optional furthest_spawn_index; @@ -697,7 +697,7 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) } } - // **Team game logic**: If it's a team game, prioritize valid team spawns + // Team game logic: If it's a team game, prioritize valid team spawns std::vector valid_team_spawns; if (is_team_game) { for (std::size_t i = 0; i < respawn_points.size(); ++i) { @@ -769,8 +769,8 @@ CallHook player_ if (g_additional_server_config.random_spawns.enabled) { // new spawn logic is enabled, try to find a valid respawn point if (auto random_respawn = select_respawn_point_new(pp)) { - xlog::warn("Spawning {} at ({}, {}, {})", pp->name, random_respawn->position.x, - random_respawn->position.y, random_respawn->position.z); + xlog::warn("Spawning {} at ({}, {}, {})", + pp->name, random_respawn->position.x, random_respawn->position.y, random_respawn->position.z); *pos = random_respawn->position; *orient = random_respawn->orientation; valid_respawn_found = true; // valid respawn was found using the new logic From ea4b6d354fd4ee1eb17667c844318e4768b79315 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 11 Oct 2024 09:39:54 -0230 Subject: [PATCH 11/28] clean up --- game_patch/multi/server.cpp | 112 +++++++++++++++++------------------- 1 file changed, 53 insertions(+), 59 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 9c36c6cf..1d5823bd 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -592,12 +592,12 @@ FunHookname, + xlog::warn("New spawn point! Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", respawn_point->name, respawn_point->team, respawn_point->redTeam, respawn_point->blueTeam, respawn_point->bot); if (pos) { xlog::warn("Position: ({}, {}, {})", pos->x, pos->y, pos->z); } - xlog::warn("num points: {}", rf::multi_respawn_num_points); + xlog::warn("Current num spawn points: {}", rf::multi_respawn_num_points); } else { // if new spawn logic is off, maintain stock behaviour @@ -623,77 +623,71 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) xlog::warn("Giving {} a spawn. They are on team {}, and their last index was {}. We {}avoiding last, and {}avoiding enemies.", pp->name, team, last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); - // vars to store the furthest spawn point - std::optional furthest_spawn_index; - std::optional second_furthest_spawn_index; - float max_distance = 0.0f; - float second_max_distance = 0.0f; - - // Get the list of players in the game - auto player_list = SinglyLinkedList{rf::player_list}; - bool has_other_players = false; - bool has_enemies = false; - - // Check if any enemies are in the game - for (auto& player : player_list) { - if (&player != pp && !rf::player_is_dead(&player)) { - has_other_players = true; - if ((is_team_game && player.team != team) || (!is_team_game)) { - has_enemies = true; // There's at least one enemy in deathmatch or different team + if (avoid_enemies) { + std::optional furthest_spawn_index; + std::optional second_furthest_spawn_index; + float max_distance = 0.0f; + float second_max_distance = 0.0f; + auto player_list = SinglyLinkedList{rf::player_list}; + bool has_enemies = false; + + for (auto& player : player_list) { // check if we have enemies + if (is_team_game && !rf::player_is_dead(&player) && &player != pp && player.team != team) { + has_enemies = true; // in DM, this means at least one other player break; } } - } - // If avoiding enemies and enemies are present, find the furthest spawn point from them - if (avoid_enemies && has_enemies) { - xlog::warn("Avoiding enemies enabled, searching for furthest spawn point from enemies."); - for (std::size_t i = 0; i < respawn_points.size(); ++i) { - const auto& spawn_point = respawn_points[i]; - rf::Vector3 spawn_position = spawn_point->position; - - float min_distance_to_enemy = std::numeric_limits::max(); - - // Calculate distance to all enemies (all players in deathmatch) - for (auto& player : player_list) { - if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { - if (!rf::player_is_dead(&player)) { - rf::Entity* entity = rf::entity_from_handle(player.entity_handle); - if (entity) { - float distance = (spawn_position - entity->pos).len(); - min_distance_to_enemy = std::min(min_distance_to_enemy, distance); + if (has_enemies) { + // If avoiding enemies and enemies are present, find the furthest spawn point from them + xlog::warn("Avoiding enemies enabled, searching for furthest spawn point from enemies."); + for (std::size_t i = 0; i < respawn_points.size(); ++i) { + const auto& spawn_point = respawn_points[i]; + rf::Vector3 spawn_position = spawn_point->position; + + float min_distance_to_enemy = std::numeric_limits::max(); + + // Calculate distance to all enemies (all players in deathmatch) + for (auto& player : player_list) { + if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { + if (!rf::player_is_dead(&player)) { + rf::Entity* entity = rf::entity_from_handle(player.entity_handle); + if (entity) { + float distance = (spawn_position - entity->pos).len(); + min_distance_to_enemy = std::min(min_distance_to_enemy, distance); + } } } } - } - // Update furthest spawn point based on distance to enemies - if (min_distance_to_enemy > max_distance) { - if (avoid_last && i == last_index) { + // Update furthest spawn point based on distance to enemies + if (min_distance_to_enemy > max_distance) { + if (avoid_last && i == last_index) { + second_max_distance = max_distance; + second_furthest_spawn_index = furthest_spawn_index; + continue; // Skip if it's the last used spawn + } + second_max_distance = max_distance; second_furthest_spawn_index = furthest_spawn_index; - continue; // Skip if it's the last used spawn - } - second_max_distance = max_distance; - second_furthest_spawn_index = furthest_spawn_index; - - max_distance = min_distance_to_enemy; - furthest_spawn_index = i; + max_distance = min_distance_to_enemy; + furthest_spawn_index = i; + } } - } - // If we found a valid furthest spawn point, use it - if (furthest_spawn_index) { - if (avoid_last && *furthest_spawn_index == last_index && second_furthest_spawn_index) { - xlog::warn("Furthest spawn point is the last one used. Using the second furthest point: {}", - *second_furthest_spawn_index); - pdata.last_spawn_point_index = *second_furthest_spawn_index; - return respawn_points[*second_furthest_spawn_index]; - } + // If we found a valid furthest spawn point, use it + if (furthest_spawn_index) { + if (avoid_last && *furthest_spawn_index == last_index && second_furthest_spawn_index) { + xlog::warn("Furthest spawn point is the last one used. Using the second furthest point: {}", + *second_furthest_spawn_index); + pdata.last_spawn_point_index = *second_furthest_spawn_index; + return respawn_points[*second_furthest_spawn_index]; + } - pdata.last_spawn_point_index = *furthest_spawn_index; - return respawn_points[*furthest_spawn_index]; + pdata.last_spawn_point_index = *furthest_spawn_index; + return respawn_points[*furthest_spawn_index]; + } } } From eb41ebdeb357bc094b4c31157827f66fdf67e3c2 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 11 Oct 2024 11:14:17 -0230 Subject: [PATCH 12/28] add +Respect Team Spawns, fix bug in team modes with avoid enemies on, update readme and changelog --- README.md | 8 ++ docs/CHANGELOG.md | 3 +- game_patch/multi/server.cpp | 124 +++++++++++++++++------------ game_patch/multi/server_internal.h | 5 +- 4 files changed, 86 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index d6efb542..3efeb74c 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,14 @@ Configuration example: $DF Vote Next: true // Enable vote previous $DF Vote Previous: true + // Use new improved spawn point selection logic. Full randomness if optional settings are turned off + $DF Use New Spawn Logic: false + // Attempt to avoid spawning players at the same location twice in a row + +Try Avoid Last: true + // Instead of randomness, always spawn players as far as possible from any enemies + +Try Avoid Enemies: false + // If off, disregard team-specific flags on spawn points (only relevant in team modes) + +Respect Team Spawns: true // Duration of player invulnerability after respawn in ms (default is the same as in stock RF - 1500) $DF Spawn Protection Duration: 1500 // Initial player life (health) after spawn diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ca66436a..c2efb851 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -43,7 +43,8 @@ Version 1.9.0 (not released yet) - Do not load unnecessary VPPs in dedicated server mode - Add level filename to "Level Initializing" console message - Properly handle WM_PAINT in dedicated server, may improve performance (DF bug) -- Add optional improved respawn point selection method for multiplayer +- Add improved and more flexible respawn point selection method for multiplayer +- Adjust behaviour in multi if map has no spawn points, now spawns players at Player Start position Version 1.8.0 (released 2022-09-17) ----------------------------------- diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 1d5823bd..3999d61f 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include "server.h" @@ -90,6 +91,9 @@ void load_additional_server_config(rf::Parser& parser) if (parser.parse_optional("+Try Avoid Enemies:")) { g_additional_server_config.random_spawns.try_avoid_enemies = parser.parse_bool(); } + if (parser.parse_optional("+Respect Team Spawns:")) { + g_additional_server_config.random_spawns.respect_team_spawns = parser.parse_bool(); + } } if (parser.parse_optional("$DF Spawn Protection Duration:")) { @@ -610,7 +614,7 @@ FunHook select_respawn_point_new(rf::Player* pp) { if (respawn_points.empty()) { - xlog::warn("No respawn points available!"); + xlog::warn("No spawn points are available!"); return {nullptr}; } @@ -620,10 +624,30 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) bool is_team_game = multi_is_team_game_type(); bool avoid_last = g_additional_server_config.random_spawns.try_avoid_last; bool avoid_enemies = g_additional_server_config.random_spawns.try_avoid_enemies; + bool respect_team_spawns = g_additional_server_config.random_spawns.respect_team_spawns; + + xlog::warn( + "Giving {} a spawn. They are on team {}, last index was {}. We {}avoiding last, and {}avoiding enemies.", + pp->name, (team ? "blue" : "red"), last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); + + // in team modes, restrict available spawns to our team + std::vector valid_team_spawns; + if (is_team_game && respect_team_spawns) { + for (std::size_t i = 0; i < respawn_points.size(); ++i) { + const auto& spawn_point = respawn_points[i]; + // Filter for matching team spawn points + if ((team == 0 && spawn_point->redTeam) || (team == 1 && spawn_point->blueTeam)) { + valid_team_spawns.push_back(i); + } + } - xlog::warn("Giving {} a spawn. They are on team {}, and their last index was {}. We {}avoiding last, and {}avoiding enemies.", pp->name, team, last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); + if (valid_team_spawns.empty()) { + xlog::warn("No spawn points are available for the {} team!", (team ? "blue" : "red")); + } + } - if (avoid_enemies) { + // avoiding enemies + if (avoid_enemies && (!valid_team_spawns.empty() || !is_team_game)) { std::optional furthest_spawn_index; std::optional second_furthest_spawn_index; float max_distance = 0.0f; @@ -631,23 +655,44 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) auto player_list = SinglyLinkedList{rf::player_list}; bool has_enemies = false; - for (auto& player : player_list) { // check if we have enemies - if (is_team_game && !rf::player_is_dead(&player) && &player != pp && player.team != team) { - has_enemies = true; // in DM, this means at least one other player - break; + if (is_team_game) { + // in tdm/ctf, check if the other team has players + for (auto& player : player_list) { + if (!rf::player_is_dead(&player) && &player != pp && player.team != team) { + has_enemies = true; + break; + } + } + } + else { + // in dm, any other player is an enemy + for (auto& player : player_list) { + if (!rf::player_is_dead(&player) && &player != pp) { + has_enemies = true; + break; + } } } - if (has_enemies) { - // If avoiding enemies and enemies are present, find the furthest spawn point from them - xlog::warn("Avoiding enemies enabled, searching for furthest spawn point from enemies."); - for (std::size_t i = 0; i < respawn_points.size(); ++i) { + if (has_enemies) { + std::vector spawn_pool; + + // check if we have team spawns (we only will in team modes) + if (respect_team_spawns && !valid_team_spawns.empty()) { + spawn_pool = valid_team_spawns; + } + else { + spawn_pool.resize(respawn_points.size()); + std::iota(spawn_pool.begin(), spawn_pool.end(), + 0); + } + + for (std::size_t i : spawn_pool) { const auto& spawn_point = respawn_points[i]; rf::Vector3 spawn_position = spawn_point->position; float min_distance_to_enemy = std::numeric_limits::max(); - // Calculate distance to all enemies (all players in deathmatch) for (auto& player : player_list) { if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { if (!rf::player_is_dead(&player)) { @@ -660,12 +705,11 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) } } - // Update furthest spawn point based on distance to enemies if (min_distance_to_enemy > max_distance) { if (avoid_last && i == last_index) { second_max_distance = max_distance; second_furthest_spawn_index = furthest_spawn_index; - continue; // Skip if it's the last used spawn + continue; } second_max_distance = max_distance; @@ -676,11 +720,8 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) } } - // If we found a valid furthest spawn point, use it if (furthest_spawn_index) { if (avoid_last && *furthest_spawn_index == last_index && second_furthest_spawn_index) { - xlog::warn("Furthest spawn point is the last one used. Using the second furthest point: {}", - *second_furthest_spawn_index); pdata.last_spawn_point_index = *second_furthest_spawn_index; return respawn_points[*second_furthest_spawn_index]; } @@ -691,63 +732,44 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) } } - // Team game logic: If it's a team game, prioritize valid team spawns - std::vector valid_team_spawns; - if (is_team_game) { - for (std::size_t i = 0; i < respawn_points.size(); ++i) { - const auto& spawn_point = respawn_points[i]; - - // Only consider spawn points that match the player's team - if ((team == 0 && spawn_point->redTeam) || (team == 1 && spawn_point->blueTeam)) { - valid_team_spawns.push_back(i); - } - } - // If no valid team-specific spawn points are found, fall back to non-team-based logic - if (valid_team_spawns.empty()) { - xlog::warn("No team-specific spawn points available. Falling back to random selection."); + // use RNG and avoid last + if (!valid_team_spawns.empty()) { + if (avoid_last && valid_team_spawns.size() > 1) { + valid_team_spawns.erase(std::remove(valid_team_spawns.begin(), valid_team_spawns.end(), last_index), + valid_team_spawns.end()); + xlog::warn("Excluding last spawn point index: {} from selection.", last_index); } - else { - // Avoid last spawn point if needed - if (avoid_last && valid_team_spawns.size() > 1) { - valid_team_spawns.erase(std::remove(valid_team_spawns.begin(), valid_team_spawns.end(), last_index), - valid_team_spawns.end()); - xlog::warn("Excluding last spawn point index: {} from selection.", last_index); - } - // Randomly select from the remaining valid team spawns - std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); - std::size_t random_team_index = team_range(g_rng); + std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); + std::size_t random_team_index = team_range(g_rng); - pdata.last_spawn_point_index = random_team_index; - return {respawn_points[random_team_index]}; - } + pdata.last_spawn_point_index = valid_team_spawns[random_team_index]; + return {respawn_points[valid_team_spawns[random_team_index]]}; } - // Non-team or fallback logic: random spawn point selection + // RNG fallback logic if DM or team game with no valid team spawns std::uniform_int_distribution random_range(0, respawn_points.size() - 1); std::size_t random_index = random_range(g_rng); - // Avoid last_index if possible (if more than 1 spawn point exists and try_avoid_last is true) if (avoid_last && respawn_points.size() > 1 && random_index == last_index) { - xlog::warn("Randomly selected last spawn point, rerolling."); random_index = (random_index + 1) % respawn_points.size(); } pdata.last_spawn_point_index = random_index; - xlog::warn("Randomly selected spawn point index from full pool: {}", random_index); return {respawn_points[random_index]}; } + CallHook player_create_entity_hook{ { 0x0048087D, // in spawn_player_server_side, used by servers 0x0045C807 // in load_level, used by single player and listen servers (initial spawn) }, [](rf::Player* pp, int entity_type, rf::Vector3* pos, rf::Matrix3* orient, int mp_character) -> rf::Entity* { - xlog::warn("Processing player_create_entity request. Player: {}, Team: {}, Entity Type: {}, MP Character: {}", + xlog::debug("Processing player_create_entity request. Player: {}, Team: {}, Entity Type: {}, MP Character: {}", pp->name, pp->team, entity_type, mp_character); // in single player don't touch anything @@ -763,7 +785,7 @@ CallHook player_ if (g_additional_server_config.random_spawns.enabled) { // new spawn logic is enabled, try to find a valid respawn point if (auto random_respawn = select_respawn_point_new(pp)) { - xlog::warn("Spawning {} at ({}, {}, {})", + xlog::debug("Spawning {} at ({}, {}, {})", pp->name, random_respawn->position.x, random_respawn->position.y, random_respawn->position.z); *pos = random_respawn->position; *orient = random_respawn->orientation; @@ -780,7 +802,7 @@ CallHook player_ if (!valid_respawn_found) { *pos = rf::level.player_start_pos; *orient = rf::level.player_start_orient; - xlog::warn("No Multiplayer Respawn Points found. Spawning {} at the Player Start.", pp->name); + xlog::warn("No spawn point found. Spawning {} at the Player Start.", pp->name); } return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index fa626653..f479be90 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -31,9 +31,10 @@ struct HitSoundsConfig struct NewSpawnLogicConfig { - bool enabled = true; + bool enabled = false; bool try_avoid_last = true; - bool try_avoid_enemies = true; + bool try_avoid_enemies = false; + bool respect_team_spawns = true; }; struct RespawnPoint From 274fbc29f1e107ecc443a3d751bdb2c8ab9be5a6 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 11 Oct 2024 11:24:35 -0230 Subject: [PATCH 13/28] refactor spawn point creation code, remove unneeded logging --- game_patch/multi/server.cpp | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 3999d61f..e8ef7375 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -582,26 +582,32 @@ FunHook(); - respawn_point->name = name; - respawn_point->team = team; - respawn_point->position = *pos; - respawn_point->orientation = *orient; - respawn_point->redTeam = RedTeam; - respawn_point->blueTeam = blue_team; - respawn_point->bot = bot; - - respawn_points.push_back(respawn_point); + respawn_points.emplace_back(std::make_shared( + RespawnPoint{ + .name = name, + .team = team, + .position = *pos, + .orientation = *orient, + .redTeam = RedTeam, + .blueTeam = blue_team, + .bot = bot + } + )); // tick up spawn point counter - rf::multi_respawn_num_points += 1; + rf::multi_respawn_num_points += 1; + + // log spawn point creation info + #ifdef DEBUG + const auto& respawn_point = respawn_points.back(); - xlog::warn("New spawn point! Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", respawn_point->name, + xlog::debug("New spawn point! Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", respawn_point->name, respawn_point->team, respawn_point->redTeam, respawn_point->blueTeam, respawn_point->bot); if (pos) { - xlog::warn("Position: ({}, {}, {})", pos->x, pos->y, pos->z); + xlog::debug("Position: ({}, {}, {})", pos->x, pos->y, pos->z); } - xlog::warn("Current num spawn points: {}", rf::multi_respawn_num_points); + xlog::debug("Current num spawn points: {}", rf::multi_respawn_num_points); + #endif } else { // if new spawn logic is off, maintain stock behaviour @@ -626,7 +632,7 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) bool avoid_enemies = g_additional_server_config.random_spawns.try_avoid_enemies; bool respect_team_spawns = g_additional_server_config.random_spawns.respect_team_spawns; - xlog::warn( + xlog::debug( "Giving {} a spawn. They are on team {}, last index was {}. We {}avoiding last, and {}avoiding enemies.", pp->name, (team ? "blue" : "red"), last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); @@ -738,7 +744,7 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) if (avoid_last && valid_team_spawns.size() > 1) { valid_team_spawns.erase(std::remove(valid_team_spawns.begin(), valid_team_spawns.end(), last_index), valid_team_spawns.end()); - xlog::warn("Excluding last spawn point index: {} from selection.", last_index); + xlog::debug("Excluding last spawn point index: {} from selection.", last_index); } std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); From c280d9d4c644c93e6059b4dda019b562ea6f0f3d Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 11 Oct 2024 11:35:01 -0230 Subject: [PATCH 14/28] commit and push --- game_patch/multi/server.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index e8ef7375..c1b680bd 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -597,7 +597,7 @@ FunHook select_respawn_point_new(rf::Player* pp) } } - // use RNG and avoid last if (!valid_team_spawns.empty()) { if (avoid_last && valid_team_spawns.size() > 1) { From 4a527c512485512b69ce275d5b0323c9f6063e09 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 11 Oct 2024 11:36:54 -0230 Subject: [PATCH 15/28] clean up --- common/include/common/version/version.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/include/common/version/version.h b/common/include/common/version/version.h index 5bc57eb4..8c520adc 100644 --- a/common/include/common/version/version.h +++ b/common/include/common/version/version.h @@ -14,7 +14,7 @@ #define VERSION_TYPE_RELEASE 4 // Variables to be modified during the release process -#define PRODUCT_NAME "Goob Faction" +#define PRODUCT_NAME "Dash Faction" #define VERSION_MAJOR 1 #define VERSION_MINOR 9 #define VERSION_PATCH 0 From c0b9d969a17c445b28242ab7f81d64703f7d8d9e Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 11 Oct 2024 11:40:21 -0230 Subject: [PATCH 16/28] only reset new spawn point array if using new spawn logic --- game_patch/misc/player.cpp | 1 - game_patch/multi/server.cpp | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/game_patch/misc/player.cpp b/game_patch/misc/player.cpp index 340b24a0..59f77df3 100644 --- a/game_patch/misc/player.cpp +++ b/game_patch/misc/player.cpp @@ -12,7 +12,6 @@ #include "../main/main.h" #include "../multi/multi.h" #include "../hud/multi_spectate.h" -#include #include #include #include diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index c1b680bd..47e50286 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -765,9 +765,6 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) return {respawn_points[random_index]}; } - - - CallHook player_create_entity_hook{ { 0x0048087D, // in spawn_player_server_side, used by servers @@ -913,7 +910,9 @@ CodeInjection multi_limbo_init_injection{ } // clear list of respawn points on map end - respawn_points.clear(); + if (g_additional_server_config.random_spawns.enabled) { + respawn_points.clear(); + } }, }; From 663a923b4dcc1cf4139c18bdf746115f34736c3b Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 25 Oct 2024 16:40:39 -0230 Subject: [PATCH 17/28] overhaul logic --- game_patch/multi/server.cpp | 179 +++++++++++++++++++++++++---- game_patch/multi/server_internal.h | 21 +--- game_patch/rf/multi.h | 25 +++- 3 files changed, 188 insertions(+), 37 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 47e50286..b26b4041 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -43,7 +43,9 @@ const char* g_rcon_cmd_whitelist[] = { "map_prev", }; -std::vector> respawn_points; +//std::vector> respawn_points; + +std::vector new_multi_respawn_points; // new storage of spawn points to avoid hard limits ServerAdditionalConfig g_additional_server_config; std::string g_prev_level; @@ -84,15 +86,18 @@ void load_additional_server_config(rf::Parser& parser) parse_vote_config("Vote Previous", g_additional_server_config.vote_previous, parser); if (parser.parse_optional("$DF Use New Spawn Logic:")) { - g_additional_server_config.random_spawns.enabled = parser.parse_bool(); - if (parser.parse_optional("+Try Avoid Last:")) { - g_additional_server_config.random_spawns.try_avoid_last = parser.parse_bool(); + g_additional_server_config.new_spawn_logic.enabled = parser.parse_bool(); + if (parser.parse_optional("+Respect Team Spawns:")) { + g_additional_server_config.new_spawn_logic.respect_team_spawns = parser.parse_bool(); } - if (parser.parse_optional("+Try Avoid Enemies:")) { - g_additional_server_config.random_spawns.try_avoid_enemies = parser.parse_bool(); + if (parser.parse_optional("+Try Avoid Players:")) { + g_additional_server_config.new_spawn_logic.try_avoid_enemies = parser.parse_bool(); } - if (parser.parse_optional("+Respect Team Spawns:")) { - g_additional_server_config.random_spawns.respect_team_spawns = parser.parse_bool(); + if (parser.parse_optional("+Always Avoid Last:")) { + g_additional_server_config.new_spawn_logic.always_avoid_last = parser.parse_bool(); + } + if (parser.parse_optional("+Always Use Furthest:")) { + g_additional_server_config.new_spawn_logic.always_use_furthest = parser.parse_bool(); } } @@ -577,7 +582,8 @@ static bool check_player_ac_status([[maybe_unused]] rf::Player* player) } // add a new spawn point -FunHook multi_respawn_create_point_hook{ +/* FunHook + oldmulti_respawn_create_point_hook{ 0x00470190, [](const char* name, uint8_t team, const rf::Vector3* pos, rf::Matrix3* orient, bool RedTeam, bool blue_team, bool bot) { @@ -611,13 +617,13 @@ FunHook select_respawn_point_new(rf::Player* pp) +/* std::shared_ptr select_respawn_point_new(rf::Player* pp) { if (respawn_points.empty()) { xlog::warn("No spawn points are available!"); @@ -763,9 +769,10 @@ std::shared_ptr select_respawn_point_new(rf::Player* pp) pdata.last_spawn_point_index = random_index; return {respawn_points[random_index]}; -} +}*/ -CallHook player_create_entity_hook{ +//unused +/* CallHook player_create_entity_hook{ { 0x0048087D, // in spawn_player_server_side, used by servers 0x0045C807 // in load_level, used by single player and listen servers (initial spawn) @@ -809,7 +816,21 @@ CallHook player_ return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); } -}; +};*/ + +std::set get_current_player_list(bool include_browsers) +{ + std::set player_list; + auto linked_player_list = SinglyLinkedList{rf::player_list}; + + for (auto& player : linked_player_list) { + if (include_browsers || !get_player_additional_data(&player).is_browser) { + player_list.insert(&player); + } + } + + return player_list; +} FunHook multi_spawn_player_server_side_hook{ 0x00480820, @@ -907,15 +928,130 @@ CodeInjection multi_limbo_init_injection{ if (!rf::player_list) { xlog::trace("Wait between levels shortened because server is empty"); addr_as_ref(regs.esp) = 100; + } + }, +}; + +FunHook multi_respawn_create_point_hook{ + 0x00470190, + [](const char* name, uint8_t team, const rf::Vector3* pos, const rf::Matrix3* orient, bool red_team, bool blue_team, bool bot) + { + constexpr size_t max_respawn_points = 2048; // limit 32 -> 2048 + + if (new_multi_respawn_points.size() >= max_respawn_points) { + return -1; } - // clear list of respawn points on map end - if (g_additional_server_config.random_spawns.enabled) { - respawn_points.clear(); - } - }, + bool dm_only = 0; // todo + + new_multi_respawn_points.emplace_back(rf::RespawnPoint{ + rf::String(name), + team, // unused + dm_only, + *pos, + *orient, + red_team, + blue_team, + bot + }); + + xlog::warn("New spawn point added! Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", + name, team, red_team, blue_team, bot); + + if (pos) { + xlog::warn("Position: ({}, {}, {})", pos->x, pos->y, pos->z); + } + + xlog::warn("Current number of spawn points: {}", new_multi_respawn_points.size()); + + return 0; + } +}; + +// clear spawn point array and reset last spawn index at level start +FunHook multi_respawn_level_init_hook { + 0x00470180, + []() { + new_multi_respawn_points.clear(); + + auto player_list = get_current_player_list(false); + std::for_each(player_list.begin(), player_list.end(), + [](rf::Player* player) { get_player_additional_data(player).last_spawn_point_index = -1; }); + + multi_respawn_level_init_hook.call_target(); + } +}; + +FunHook multi_respawn_get_next_point_hook{ + 0x00470300, + [](rf::Vector3* pos, rf::Matrix3* orient, rf::Player* player) { + + // default return if map has no respawn points + if (new_multi_respawn_points.empty()) { + *pos = rf::level.player_start_pos; + *orient = rf::level.player_start_orient; + xlog::warn("No Multiplayer Respawn Points found. Spawning {} at the Player Start.", player->name); + return -1; + } + + // default return if player is invalid (should never happen) + if (!player) { + std::uniform_int_distribution dist(0, new_multi_respawn_points.size() - 1); + int index = dist(g_rng); + *pos = new_multi_respawn_points[index].position; + *orient = new_multi_respawn_points[index].orientation; + return 0; + } + + auto& pdata = get_player_additional_data(player); + int team = player->team; + int last_index = pdata.last_spawn_point_index; + bool is_team_game = multi_is_team_game_type(); + bool avoid_last = g_additional_server_config.new_spawn_logic.always_avoid_last; + bool avoid_enemies = g_additional_server_config.new_spawn_logic.try_avoid_enemies; + bool use_furthest = g_additional_server_config.new_spawn_logic.always_use_furthest; + bool respect_team_spawns = g_additional_server_config.new_spawn_logic.respect_team_spawns; + + int valid_points = 0; + rf::NetGameType game_type = rf::multi_get_game_type(); + std::vector available_points; + + for (auto& point : new_multi_respawn_points) { + if (game_type == rf::NetGameType::NG_TYPE_CTF || game_type == rf::NetGameType::NG_TYPE_TEAMDM) { + if ((player->team == 1 && point.red_team) || (player->team == 0 && point.blue_team)) { + continue; + } + } + + float dist = rf::get_nearest_other_player_dist_sq(player, &point.position); + point.dist_other_player = dist; + available_points.push_back(&point); + ++valid_points; + } + + if (valid_points == 0) { + std::uniform_int_distribution dist(0, new_multi_respawn_points.size() - 1); + int index = dist(g_rng); + *pos = new_multi_respawn_points[index].position; + *orient = new_multi_respawn_points[index].orientation; + return 1; + } + + std::sort(available_points.begin(), available_points.end(), + [](const rf::RespawnPoint* a, const rf::RespawnPoint* b) { + return a->dist_other_player < b->dist_other_player; + }); + + std::uniform_real_distribution real_dist(0.0, 1.0); + int selected_index = static_cast(std::sqrt(real_dist(g_rng)) * (available_points.size() - 1) + 0.5); + *pos = available_points[selected_index]->position; + *orient = available_points[selected_index]->orientation; + + return 1; + } }; + void server_init() { // Override rcon command whitelist @@ -968,8 +1104,9 @@ void server_init() AsmWriter(0x0047B061, 0x0047B064).add(asm_regs::esp, 0x14); // Support new spawn logic + multi_respawn_level_init_hook.install(); multi_respawn_create_point_hook.install(); - player_create_entity_hook.install(); + multi_respawn_get_next_point_hook.install(); // Support forcing player character multi_spawn_player_server_side_hook.install(); diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index f479be90..c83ac2de 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -32,20 +33,10 @@ struct HitSoundsConfig struct NewSpawnLogicConfig { bool enabled = false; - bool try_avoid_last = true; - bool try_avoid_enemies = false; - bool respect_team_spawns = true; -}; - -struct RespawnPoint -{ - std::string name; - uint8_t team; - rf::Vector3 position; - rf::Matrix3 orientation; - bool redTeam; - bool blueTeam; - bool bot; + bool respect_team_spawns = true; + bool try_avoid_enemies = true; + bool always_avoid_last = true; + bool always_use_furthest = false; }; struct ServerAdditionalConfig @@ -56,7 +47,7 @@ struct ServerAdditionalConfig VoteConfig vote_restart; VoteConfig vote_next; VoteConfig vote_previous; - NewSpawnLogicConfig random_spawns; + NewSpawnLogicConfig new_spawn_logic; int spawn_protection_duration_ms = 1500; std::optional spawn_life; std::optional spawn_armor; diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index 6d3036a3..34189f3a 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -5,6 +5,10 @@ #include "os/timestamp.h" #include "os/string.h" #include "os/array.h" +#include "object.h" +#include "geometry.h" +#include "ai.h" +#include "gr/gr.h" namespace rf { @@ -82,6 +86,22 @@ namespace rf }; static_assert(sizeof(PlayerNetData) == 0x9C8); + struct RespawnPoint + { + String name; + uint8_t team; +#ifdef DASH_FACTION + bool dm_only; +#endif + Vector3 position; + Matrix3 orientation; + bool red_team; + bool blue_team; + bool bot; + float dist_other_player; + }; + static_assert(sizeof(RespawnPoint) == 0x44); + enum NetGameType { NG_TYPE_DM = 0, NG_TYPE_CTF = 1, @@ -175,7 +195,10 @@ namespace rf static auto& simultaneous_ping = addr_as_ref(0x00599CD8); static auto& tracker_addr = addr_as_ref(0x006FC550); - static auto& multi_respawn_num_points = addr_as_ref(0x006A1458); + //static auto& multi_respawn_points = addr_as_ref(0x006C6C40); // original game array, 32 max + //static auto& multi_respawn_num_points = addr_as_ref(0x006A1458); // original game counter, unneeded + static auto& get_nearest_other_player_dist_sq = addr_as_ref(0x00470220); + enum ChatSayType { CHAT_SAY_GLOBAL = 0, From 2214e0c9fc10f2e802c1b999de77c6de030acbcf Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 25 Oct 2024 17:23:40 -0230 Subject: [PATCH 18/28] overhaul2 --- game_patch/multi/server.cpp | 369 ++++++++--------------------- game_patch/multi/server_internal.h | 6 +- game_patch/rf/math/vector.h | 3 +- game_patch/rf/multi.h | 7 + 4 files changed, 107 insertions(+), 278 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index b26b4041..c1ba82cf 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -85,13 +85,12 @@ void load_additional_server_config(rf::Parser& parser) parse_vote_config("Vote Next", g_additional_server_config.vote_next, parser); parse_vote_config("Vote Previous", g_additional_server_config.vote_previous, parser); - if (parser.parse_optional("$DF Use New Spawn Logic:")) { - g_additional_server_config.new_spawn_logic.enabled = parser.parse_bool(); + if (parser.parse_optional("$DF Player Respawn Logic:")) { if (parser.parse_optional("+Respect Team Spawns:")) { g_additional_server_config.new_spawn_logic.respect_team_spawns = parser.parse_bool(); } - if (parser.parse_optional("+Try Avoid Players:")) { - g_additional_server_config.new_spawn_logic.try_avoid_enemies = parser.parse_bool(); + if (parser.parse_optional("+Prefer Avoid Players:")) { + g_additional_server_config.new_spawn_logic.try_avoid_players = parser.parse_bool(); } if (parser.parse_optional("+Always Avoid Last:")) { g_additional_server_config.new_spawn_logic.always_avoid_last = parser.parse_bool(); @@ -99,6 +98,9 @@ void load_additional_server_config(rf::Parser& parser) if (parser.parse_optional("+Always Use Furthest:")) { g_additional_server_config.new_spawn_logic.always_use_furthest = parser.parse_bool(); } + if (parser.parse_optional("+Only Avoid Enemies:")) { + g_additional_server_config.new_spawn_logic.only_avoid_enemies = parser.parse_bool(); + } } if (parser.parse_optional("$DF Spawn Protection Duration:")) { @@ -581,243 +583,6 @@ static bool check_player_ac_status([[maybe_unused]] rf::Player* player) return true; } -// add a new spawn point -/* FunHook - oldmulti_respawn_create_point_hook{ - 0x00470190, - [](const char* name, uint8_t team, const rf::Vector3* pos, rf::Matrix3* orient, bool RedTeam, bool blue_team, - bool bot) { - if (g_additional_server_config.random_spawns.enabled) { - respawn_points.emplace_back(std::make_shared( - RespawnPoint{ - .name = name, - .team = team, - .position = *pos, - .orientation = *orient, - .redTeam = RedTeam, - .blueTeam = blue_team, - .bot = bot - } - )); - - // tick up spawn point counter - rf::multi_respawn_num_points += 1; - - // log spawn point creation info (avoid unneeded var if not debugging) - #ifdef DEBUG - const auto& respawn_point = respawn_points.back(); - - xlog::debug("New spawn point! Name: {}, Team: {}, RedTeam: {}, BlueTeam: {}, Bot: {}", respawn_point->name, - respawn_point->team, respawn_point->redTeam, respawn_point->blueTeam, respawn_point->bot); - if (pos) { - xlog::debug("Position: ({}, {}, {})", pos->x, pos->y, pos->z); - } - xlog::debug("Current num spawn points: {}", rf::multi_respawn_num_points); - #endif - } - else { - // if new spawn logic is off, maintain stock behaviour - oldmulti_respawn_create_point_hook.call_target(name, team, pos, orient, RedTeam, blue_team, bot); - } - } -};*/ - -// new spawn point selection logic -/* std::shared_ptr select_respawn_point_new(rf::Player* pp) -{ - if (respawn_points.empty()) { - xlog::warn("No spawn points are available!"); - return {nullptr}; - } - - auto& pdata = get_player_additional_data(pp); - int team = pp->team; - int last_index = pdata.last_spawn_point_index; - bool is_team_game = multi_is_team_game_type(); - bool avoid_last = g_additional_server_config.random_spawns.try_avoid_last; - bool avoid_enemies = g_additional_server_config.random_spawns.try_avoid_enemies; - bool respect_team_spawns = g_additional_server_config.random_spawns.respect_team_spawns; - - xlog::debug( - "Giving {} a spawn. They are on team {}, last index was {}. We {}avoiding last, and {}avoiding enemies.", - pp->name, (team ? "blue" : "red"), last_index, (avoid_last ? "" : "NOT "), (avoid_enemies ? "" : "NOT ")); - - // in team modes, restrict available spawns to our team - std::vector valid_team_spawns; - if (is_team_game && respect_team_spawns) { - for (std::size_t i = 0; i < respawn_points.size(); ++i) { - const auto& spawn_point = respawn_points[i]; - // Filter for matching team spawn points - if ((team == 0 && spawn_point->redTeam) || (team == 1 && spawn_point->blueTeam)) { - valid_team_spawns.push_back(i); - } - } - - if (valid_team_spawns.empty()) { - xlog::warn("No spawn points are available for the {} team!", (team ? "blue" : "red")); - } - } - - // avoiding enemies - if (avoid_enemies && (!valid_team_spawns.empty() || !is_team_game)) { - std::optional furthest_spawn_index; - std::optional second_furthest_spawn_index; - float max_distance = 0.0f; - float second_max_distance = 0.0f; - auto player_list = SinglyLinkedList{rf::player_list}; - bool has_enemies = false; - - if (is_team_game) { - // in tdm/ctf, check if the other team has players - for (auto& player : player_list) { - if (!rf::player_is_dead(&player) && &player != pp && player.team != team) { - has_enemies = true; - break; - } - } - } - else { - // in dm, any other player is an enemy - for (auto& player : player_list) { - if (!rf::player_is_dead(&player) && &player != pp) { - has_enemies = true; - break; - } - } - } - - if (has_enemies) { - std::vector spawn_pool; - - // check if we have team spawns (we only will in team modes) - if (respect_team_spawns && !valid_team_spawns.empty()) { - spawn_pool = valid_team_spawns; - } - else { - spawn_pool.resize(respawn_points.size()); - std::iota(spawn_pool.begin(), spawn_pool.end(), - 0); - } - - for (std::size_t i : spawn_pool) { - const auto& spawn_point = respawn_points[i]; - rf::Vector3 spawn_position = spawn_point->position; - - float min_distance_to_enemy = std::numeric_limits::max(); - - for (auto& player : player_list) { - if ((is_team_game && player.team != team) || (!is_team_game && &player != pp)) { - if (!rf::player_is_dead(&player)) { - rf::Entity* entity = rf::entity_from_handle(player.entity_handle); - if (entity) { - float distance = (spawn_position - entity->pos).len(); - min_distance_to_enemy = std::min(min_distance_to_enemy, distance); - } - } - } - } - - if (min_distance_to_enemy > max_distance) { - if (avoid_last && i == last_index) { - second_max_distance = max_distance; - second_furthest_spawn_index = furthest_spawn_index; - continue; - } - - second_max_distance = max_distance; - second_furthest_spawn_index = furthest_spawn_index; - - max_distance = min_distance_to_enemy; - furthest_spawn_index = i; - } - } - - if (furthest_spawn_index) { - if (avoid_last && *furthest_spawn_index == last_index && second_furthest_spawn_index) { - pdata.last_spawn_point_index = *second_furthest_spawn_index; - return respawn_points[*second_furthest_spawn_index]; - } - - pdata.last_spawn_point_index = *furthest_spawn_index; - return respawn_points[*furthest_spawn_index]; - } - } - } - - // use RNG and avoid last - if (!valid_team_spawns.empty()) { - if (avoid_last && valid_team_spawns.size() > 1) { - valid_team_spawns.erase(std::remove(valid_team_spawns.begin(), valid_team_spawns.end(), last_index), - valid_team_spawns.end()); - xlog::debug("Excluding last spawn point index: {} from selection.", last_index); - } - - std::uniform_int_distribution team_range(0, valid_team_spawns.size() - 1); - std::size_t random_team_index = team_range(g_rng); - - pdata.last_spawn_point_index = valid_team_spawns[random_team_index]; - return {respawn_points[valid_team_spawns[random_team_index]]}; - } - - // RNG fallback logic if DM or team game with no valid team spawns - std::uniform_int_distribution random_range(0, respawn_points.size() - 1); - std::size_t random_index = random_range(g_rng); - - if (avoid_last && respawn_points.size() > 1 && random_index == last_index) { - random_index = (random_index + 1) % respawn_points.size(); - } - - pdata.last_spawn_point_index = random_index; - return {respawn_points[random_index]}; -}*/ - -//unused -/* CallHook player_create_entity_hook{ - { - 0x0048087D, // in spawn_player_server_side, used by servers - 0x0045C807 // in load_level, used by single player and listen servers (initial spawn) - }, - [](rf::Player* pp, int entity_type, rf::Vector3* pos, rf::Matrix3* orient, int mp_character) -> rf::Entity* { - xlog::debug("Processing player_create_entity request. Player: {}, Team: {}, Entity Type: {}, MP Character: {}", - pp->name, pp->team, entity_type, mp_character); - - // in single player don't touch anything - if (mp_character == -1) { - return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); - } - - // assume no valid spawn until proven otherwise - bool valid_respawn_found = false; - - // do we have any spawn points? - if (rf::multi_respawn_num_points > 0) { - if (g_additional_server_config.random_spawns.enabled) { - // new spawn logic is enabled, try to find a valid respawn point - if (auto random_respawn = select_respawn_point_new(pp)) { - xlog::debug("Spawning {} at ({}, {}, {})", - pp->name, random_respawn->position.x, random_respawn->position.y, random_respawn->position.z); - *pos = random_respawn->position; - *orient = random_respawn->orientation; - valid_respawn_found = true; // valid respawn was found using the new logic - } - } - else { - // old logic has a spawn point for us - valid_respawn_found = true; - } - } - - // use player start position if no spawn points were found - if (!valid_respawn_found) { - *pos = rf::level.player_start_pos; - *orient = rf::level.player_start_orient; - xlog::warn("No spawn point found. Spawning {} at the Player Start.", pp->name); - } - - return player_create_entity_hook.call_target(pp, entity_type, pos, orient, mp_character); - } -};*/ - std::set get_current_player_list(bool include_browsers) { std::set player_list; @@ -936,13 +701,13 @@ FunHook 2048 + constexpr size_t max_respawn_points = 2048; // raise limit 32 -> 2048 if (new_multi_respawn_points.size() >= max_respawn_points) { return -1; } - bool dm_only = 0; // todo + bool dm_only = 0; // todo - will allow level designer to specify spawn points that are only used in DM new_multi_respawn_points.emplace_back(rf::RespawnPoint{ rf::String(name), @@ -955,14 +720,13 @@ FunHookx, pos->y, pos->z); - } + //if (pos) { + // xlog::warn("Position: ({}, {}, {})", pos->x, pos->y, pos->z); + //} - xlog::warn("Current number of spawn points: {}", new_multi_respawn_points.size()); + //xlog::warn("Current number of spawn points: {}", new_multi_respawn_points.size()); return 0; } @@ -982,6 +746,35 @@ FunHook multi_respawn_level_init_hook { } }; +float get_nearest_other_player_new(const rf::Player* player, const rf::Vector3* spawn_pos, + rf::Vector3* other_player_pos_out, bool only_enemies = false) +{ + float min_dist_sq = std::numeric_limits::max(); + const bool is_team_game = multi_is_team_game_type(); + const int player_team = player->team; + + auto player_list = get_current_player_list(false); + + for (const auto* other_player : player_list) { + if (other_player == player || (only_enemies && is_team_game && other_player->team == player_team)) { + continue; + } + + if (auto* other_entity = rf::entity_from_handle(other_player->entity_handle)) { + const float dist_sq = rf::vec_dist_squared(spawn_pos, &other_entity->pos); + + if (dist_sq < min_dist_sq) { + min_dist_sq = dist_sq; + if (other_player_pos_out) { + *other_player_pos_out = other_entity->pos; + } + } + } + } + + return min_dist_sq; +} + FunHook multi_respawn_get_next_point_hook{ 0x00470300, [](rf::Vector3* pos, rf::Matrix3* orient, rf::Player* player) { @@ -1004,54 +797,82 @@ FunHook multi_respawn_get_next_poi } auto& pdata = get_player_additional_data(player); - int team = player->team; - int last_index = pdata.last_spawn_point_index; - bool is_team_game = multi_is_team_game_type(); - bool avoid_last = g_additional_server_config.new_spawn_logic.always_avoid_last; - bool avoid_enemies = g_additional_server_config.new_spawn_logic.try_avoid_enemies; - bool use_furthest = g_additional_server_config.new_spawn_logic.always_use_furthest; - bool respect_team_spawns = g_additional_server_config.new_spawn_logic.respect_team_spawns; - - int valid_points = 0; - rf::NetGameType game_type = rf::multi_get_game_type(); + const int team = player->team; + const int last_index = pdata.last_spawn_point_index; + const bool is_team_game = multi_is_team_game_type(); + + const bool avoid_last = g_additional_server_config.new_spawn_logic.always_avoid_last; + const bool avoid_players = g_additional_server_config.new_spawn_logic.try_avoid_players; + const bool use_furthest = g_additional_server_config.new_spawn_logic.always_use_furthest; + const bool respect_team_spawns = g_additional_server_config.new_spawn_logic.respect_team_spawns; + const bool only_enemies = g_additional_server_config.new_spawn_logic.only_avoid_enemies; + std::vector available_points; for (auto& point : new_multi_respawn_points) { - if (game_type == rf::NetGameType::NG_TYPE_CTF || game_type == rf::NetGameType::NG_TYPE_TEAMDM) { - if ((player->team == 1 && point.red_team) || (player->team == 0 && point.blue_team)) { + if (is_team_game && respect_team_spawns) { + if ((team == 1 && point.red_team) || (team == 0 && point.blue_team)) { continue; } } - float dist = rf::get_nearest_other_player_dist_sq(player, &point.position); + const float dist = get_nearest_other_player_new(player, &point.position, nullptr, only_enemies); point.dist_other_player = dist; available_points.push_back(&point); - ++valid_points; } - if (valid_points == 0) { + if (available_points.empty()) { std::uniform_int_distribution dist(0, new_multi_respawn_points.size() - 1); - int index = dist(g_rng); + const int index = dist(g_rng); *pos = new_multi_respawn_points[index].position; *orient = new_multi_respawn_points[index].orientation; return 1; } - std::sort(available_points.begin(), available_points.end(), - [](const rf::RespawnPoint* a, const rf::RespawnPoint* b) { - return a->dist_other_player < b->dist_other_player; - }); + if (avoid_players || use_furthest) { + std::sort(available_points.begin(), available_points.end(), + [](const rf::RespawnPoint* a, const rf::RespawnPoint* b) { + return a->dist_other_player > b->dist_other_player; + }); + } + + int selected_index = 0; + + if (use_furthest) { + selected_index = std::distance( + new_multi_respawn_points.begin(), + std::find(new_multi_respawn_points.begin(), new_multi_respawn_points.end(), *available_points[0])); + + if (avoid_last && last_index == selected_index && available_points.size() > 1) { + selected_index = std::distance( + new_multi_respawn_points.begin(), + std::find(new_multi_respawn_points.begin(), new_multi_respawn_points.end(), *available_points[1])); + } + } + else { + std::uniform_real_distribution real_dist(0.0, 1.0); + int random_index = static_cast(std::sqrt(real_dist(g_rng)) * (available_points.size() - 1) + 0.5); + + selected_index = std::distance(new_multi_respawn_points.begin(), + std::find(new_multi_respawn_points.begin(), new_multi_respawn_points.end(), + *available_points[random_index])); + + if (avoid_last && last_index == selected_index && available_points.size() > 1) { + selected_index = + std::distance(new_multi_respawn_points.begin(), + std::find(new_multi_respawn_points.begin(), new_multi_respawn_points.end(), + *available_points[random_index == 0 ? 1 : 0])); + } + } - std::uniform_real_distribution real_dist(0.0, 1.0); - int selected_index = static_cast(std::sqrt(real_dist(g_rng)) * (available_points.size() - 1) + 0.5); - *pos = available_points[selected_index]->position; - *orient = available_points[selected_index]->orientation; + *pos = new_multi_respawn_points[selected_index].position; + *orient = new_multi_respawn_points[selected_index].orientation; + pdata.last_spawn_point_index = selected_index; return 1; } }; - void server_init() { // Override rcon command whitelist diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index c83ac2de..0f9008fd 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -32,11 +32,11 @@ struct HitSoundsConfig struct NewSpawnLogicConfig { - bool enabled = false; bool respect_team_spawns = true; - bool try_avoid_enemies = true; - bool always_avoid_last = true; + bool try_avoid_players = true; + bool always_avoid_last = false; bool always_use_furthest = false; + bool only_avoid_enemies = false; }; struct ServerAdditionalConfig diff --git a/game_patch/rf/math/vector.h b/game_patch/rf/math/vector.h index 8a9d4e95..44501dc5 100644 --- a/game_patch/rf/math/vector.h +++ b/game_patch/rf/math/vector.h @@ -171,4 +171,5 @@ namespace rf }; static auto& vec2_zero_vector = addr_as_ref(0x0173C370); -} + static auto& vec_dist_squared = addr_as_ref(0x004FAF00); + } diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index 34189f3a..f1f83ea7 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -99,6 +99,13 @@ namespace rf bool blue_team; bool bot; float dist_other_player; + + bool operator==(const RespawnPoint& other) const + { + return (name == other.name && team == other.team && dm_only == other.dm_only && + position == other.position && orientation == other.orientation && red_team == other.red_team && + blue_team == other.blue_team && bot == other.bot); + } }; static_assert(sizeof(RespawnPoint) == 0x44); From b6bd23a383c943954d57a7ae4bd711ef642517f5 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 25 Oct 2024 18:08:08 -0230 Subject: [PATCH 19/28] update readme and changelog --- README.md | 36 ++++++++++++++++++++---------------- docs/CHANGELOG.md | 4 ++-- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 3efeb74c..611102ac 100644 --- a/README.md +++ b/README.md @@ -150,14 +150,18 @@ Configuration example: $DF Vote Next: true // Enable vote previous $DF Vote Previous: true - // Use new improved spawn point selection logic. Full randomness if optional settings are turned off - $DF Use New Spawn Logic: false - // Attempt to avoid spawning players at the same location twice in a row - +Try Avoid Last: true - // Instead of randomness, always spawn players as far as possible from any enemies - +Try Avoid Enemies: false - // If off, disregard team-specific flags on spawn points (only relevant in team modes) + // Adjust setting related to player respawn logic (defaults match stock game) + $DF Player Respawn Logic: + // In team gamemodes (CTF/TeamDM), only spawn players at points associated with their team +Respect Team Spawns: true + // Players are more likely to spawn at spawn points further away from other players + +Prefer Avoid Players: true + // Avoid spawning players at the same spawn point twice in a row + +Always Avoid Last: false + // Always spawn players at the furthest spawn point from other players (removes RNG) + +Always Use Furthest: false + // Ignore teammates when calculating the distance from spawn points to other players + +Only Avoid Enemies: false // Duration of player invulnerability after respawn in ms (default is the same as in stock RF - 1500) $DF Spawn Protection Duration: 1500 // Initial player life (health) after spawn @@ -205,15 +209,15 @@ Configuration example: // Reward a player for a successful kill $DF Kill Reward: // Increase player health or armor if health is full (armor delta is halved) - +Effective Health: 0 - // Increase player health - +Health: 0 - // Increase player armor - +Armor: 0 - // Limit health reward to 200 instead of 100 - +Health Is Super: - // Limit armor reward to 200 instead of 100 - +Armor Is Super: + +Effective Health: 0 + // Increase player health + +Health: 0 + // Increase player armor + +Armor: 0 + // Limit health reward to 200 instead of 100 + +Health Is Super: + // Limit armor reward to 200 instead of 100 + +Armor Is Super: Building diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index c2efb851..884a9482 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -43,8 +43,8 @@ Version 1.9.0 (not released yet) - Do not load unnecessary VPPs in dedicated server mode - Add level filename to "Level Initializing" console message - Properly handle WM_PAINT in dedicated server, may improve performance (DF bug) -- Add improved and more flexible respawn point selection method for multiplayer -- Adjust behaviour in multi if map has no spawn points, now spawns players at Player Start position +- Make game spawn players at Player Start position if level has no respawn points +- Add respawn point selection logic settings to dedicated server config Version 1.8.0 (released 2022-09-17) ----------------------------------- From aa7199966edc7c6c8190ef0318b7fa459a7e1ec0 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 25 Oct 2024 18:22:53 -0230 Subject: [PATCH 20/28] cleanup --- game_patch/misc/player.h | 2 +- game_patch/multi/server.cpp | 39 +++++++++++++++--------------- game_patch/multi/server_internal.h | 2 +- game_patch/rf/level.h | 3 --- game_patch/rf/math/vector.h | 2 +- game_patch/rf/multi.h | 5 ---- 6 files changed, 23 insertions(+), 30 deletions(-) diff --git a/game_patch/misc/player.h b/game_patch/misc/player.h index 077aa043..64985a1c 100644 --- a/game_patch/misc/player.h +++ b/game_patch/misc/player.h @@ -26,7 +26,7 @@ struct PlayerAdditionalData std::map saves; rf::Vector3 last_teleport_pos; rf::TimestampRealtime last_teleport_timestamp; - int last_spawn_point_index = -1; + std::optional last_spawn_point_index{}; }; void find_player(const StringMatcher& query, std::function consumer); diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index c1ba82cf..ad0e8f11 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -43,18 +43,11 @@ const char* g_rcon_cmd_whitelist[] = { "map_prev", }; -//std::vector> respawn_points; - std::vector new_multi_respawn_points; // new storage of spawn points to avoid hard limits ServerAdditionalConfig g_additional_server_config; std::string g_prev_level; -static bool multi_is_team_game_type() -{ - return rf::multi_get_game_type() != rf::NG_TYPE_DM; -} - void parse_vote_config(const char* vote_name, VoteConfig& config, rf::Parser& parser) { std::string vote_option_name = std::format("$DF {}:", vote_name); @@ -631,6 +624,11 @@ static float get_weapon_shot_stats_delta(rf::Weapon* wp) return 1.0f / num_projectiles; } +static bool multi_is_team_game_type() +{ + return rf::multi_get_game_type() != rf::NG_TYPE_DM; +} + static void maybe_increment_weapon_hits_stat(int hit_obj_handle, rf::Weapon *wp) { rf::Entity* attacker_ep = rf::entity_from_handle(wp->parent_handle); @@ -693,7 +691,7 @@ CodeInjection multi_limbo_init_injection{ if (!rf::player_list) { xlog::trace("Wait between levels shortened because server is empty"); addr_as_ref(regs.esp) = 100; - } + } }, }; @@ -720,13 +718,14 @@ FunHookx, pos->y, pos->z); - //} + if (pos) { + xlog::debug("Position: ({}, {}, {})", pos->x, pos->y, pos->z); + } - //xlog::warn("Current number of spawn points: {}", new_multi_respawn_points.size()); + xlog::debug("Current number of spawn points: {}", new_multi_respawn_points.size()); return 0; } @@ -740,14 +739,16 @@ FunHook multi_respawn_level_init_hook { auto player_list = get_current_player_list(false); std::for_each(player_list.begin(), player_list.end(), - [](rf::Player* player) { get_player_additional_data(player).last_spawn_point_index = -1; }); + [](rf::Player* player) { get_player_additional_data(player).last_spawn_point_index.reset(); }); + multi_respawn_level_init_hook.call_target(); } }; -float get_nearest_other_player_new(const rf::Player* player, const rf::Vector3* spawn_pos, - rf::Vector3* other_player_pos_out, bool only_enemies = false) +// more flexible replacement for get_nearest_other_player_dist_sq in stock game +float get_nearest_other_player(const rf::Player* player, const rf::Vector3* spawn_pos, + rf::Vector3* other_player_pos_out, bool only_enemies = false) { float min_dist_sq = std::numeric_limits::max(); const bool is_team_game = multi_is_team_game_type(); @@ -798,7 +799,7 @@ FunHook multi_respawn_get_next_poi auto& pdata = get_player_additional_data(player); const int team = player->team; - const int last_index = pdata.last_spawn_point_index; + const int last_index = pdata.last_spawn_point_index.value_or(-1); const bool is_team_game = multi_is_team_game_type(); const bool avoid_last = g_additional_server_config.new_spawn_logic.always_avoid_last; @@ -816,7 +817,7 @@ FunHook multi_respawn_get_next_poi } } - const float dist = get_nearest_other_player_new(player, &point.position, nullptr, only_enemies); + const float dist = get_nearest_other_player(player, &point.position, nullptr, only_enemies); point.dist_other_player = dist; available_points.push_back(&point); } @@ -924,7 +925,7 @@ void server_init() multi_on_new_player_injection.install(); AsmWriter(0x0047B061, 0x0047B064).add(asm_regs::esp, 0x14); - // Support new spawn logic + // DF respawn point selection logic multi_respawn_level_init_hook.install(); multi_respawn_create_point_hook.install(); multi_respawn_get_next_point_hook.install(); diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index 0f9008fd..b0233f70 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -30,7 +30,7 @@ struct HitSoundsConfig int rate_limit = 10; }; -struct NewSpawnLogicConfig +struct NewSpawnLogicConfig // defaults match stock game { bool respect_team_spawns = true; bool try_avoid_players = true; diff --git a/game_patch/rf/level.h b/game_patch/rf/level.h index 2315c865..da9e53e1 100644 --- a/game_patch/rf/level.h +++ b/game_patch/rf/level.h @@ -86,8 +86,5 @@ namespace rf static auto& level = addr_as_ref(0x00645FD8); static auto& level_filename_to_load = addr_as_ref(0x00646140); - static auto& get_player_start_position = addr_as_ref(0x00463D25); - static auto& get_player_start_orientation = addr_as_ref(0x00463D38); - } diff --git a/game_patch/rf/math/vector.h b/game_patch/rf/math/vector.h index 44501dc5..63a3119e 100644 --- a/game_patch/rf/math/vector.h +++ b/game_patch/rf/math/vector.h @@ -172,4 +172,4 @@ namespace rf static auto& vec2_zero_vector = addr_as_ref(0x0173C370); static auto& vec_dist_squared = addr_as_ref(0x004FAF00); - } +} diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index f1f83ea7..f36fcc07 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -202,11 +202,6 @@ namespace rf static auto& simultaneous_ping = addr_as_ref(0x00599CD8); static auto& tracker_addr = addr_as_ref(0x006FC550); - //static auto& multi_respawn_points = addr_as_ref(0x006C6C40); // original game array, 32 max - //static auto& multi_respawn_num_points = addr_as_ref(0x006A1458); // original game counter, unneeded - static auto& get_nearest_other_player_dist_sq = addr_as_ref(0x00470220); - - enum ChatSayType { CHAT_SAY_GLOBAL = 0, CHAT_SAY_TEAM = 1, From 7197a40f49e444a9e6faea1f99fc54b71ccededb Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 25 Oct 2024 18:38:21 -0230 Subject: [PATCH 21/28] fix logic for team modes where spawn points have both team flags set --- game_patch/misc/player.h | 3 ++- game_patch/multi/server.cpp | 2 +- game_patch/rf/multi.h | 21 ++++++++++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/game_patch/misc/player.h b/game_patch/misc/player.h index 64985a1c..521c14e8 100644 --- a/game_patch/misc/player.h +++ b/game_patch/misc/player.h @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "../rf/math/vector.h" @@ -26,7 +27,7 @@ struct PlayerAdditionalData std::map saves; rf::Vector3 last_teleport_pos; rf::TimestampRealtime last_teleport_timestamp; - std::optional last_spawn_point_index{}; + std::optional last_spawn_point_index; }; void find_player(const StringMatcher& query, std::function consumer); diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index ad0e8f11..29c596c5 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -812,7 +812,7 @@ FunHook multi_respawn_get_next_poi for (auto& point : new_multi_respawn_points) { if (is_team_game && respect_team_spawns) { - if ((team == 1 && point.red_team) || (team == 0 && point.blue_team)) { + if ((team == 0 && !point.red_team) || (team == 1 && !point.blue_team)) { continue; } } diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index f36fcc07..7fa18b08 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -102,9 +102,24 @@ namespace rf bool operator==(const RespawnPoint& other) const { - return (name == other.name && team == other.team && dm_only == other.dm_only && - position == other.position && orientation == other.orientation && red_team == other.red_team && - blue_team == other.blue_team && bot == other.bot); +#ifdef DASH_FACTION + return (name == other.name && + team == other.team && + dm_only == other.dm_only && + position == other.position && + orientation == other.orientation && + red_team == other.red_team && + blue_team == other.blue_team && + bot == other.bot); +#else + return (name == other.name && + team == other.team && + position == other.position && + orientation == other.orientation && + red_team == other.red_team && + blue_team == other.blue_team && + bot == other.bot); +#endif } }; static_assert(sizeof(RespawnPoint) == 0x44); From 1cbf69d3bab1c754d1bef312e6659a01ee53d55a Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 25 Oct 2024 18:57:58 -0230 Subject: [PATCH 22/28] update changelog --- docs/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 884a9482..862516e3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -45,6 +45,7 @@ Version 1.9.0 (not released yet) - Properly handle WM_PAINT in dedicated server, may improve performance (DF bug) - Make game spawn players at Player Start position if level has no respawn points - Add respawn point selection logic settings to dedicated server config +- Raise limit on number of multiplayer respawn points per level from 32 to 2048 Version 1.8.0 (released 2022-09-17) ----------------------------------- From 658a4b0834284c5858690bec02c82df0d9450900 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 25 Oct 2024 18:59:04 -0230 Subject: [PATCH 23/28] update changelog --- docs/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 862516e3..fd1d9980 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -43,6 +43,7 @@ Version 1.9.0 (not released yet) - Do not load unnecessary VPPs in dedicated server mode - Add level filename to "Level Initializing" console message - Properly handle WM_PAINT in dedicated server, may improve performance (DF bug) +- Fix crash when `verify_level` command is run without a level being loaded - Make game spawn players at Player Start position if level has no respawn points - Add respawn point selection logic settings to dedicated server config - Raise limit on number of multiplayer respawn points per level from 32 to 2048 From 9b4457ab0d9449dece4e87ed1f3b3fc706911857 Mon Sep 17 00:00:00 2001 From: Goober Date: Sat, 26 Oct 2024 02:33:05 -0230 Subject: [PATCH 24/28] add +Allowed Respawn Items --- game_patch/multi/server.cpp | 44 ++++++++++++++++++++++++++++-- game_patch/multi/server_internal.h | 1 + game_patch/rf/multi.h | 2 ++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 29c596c5..84cdf0ce 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include #include "server.h" @@ -22,6 +24,7 @@ #include #include "../rf/player/player.h" #include "../rf/multi.h" +#include "../rf/item.h" #include "../rf/parse.h" #include "../rf/weapon.h" #include "../rf/entity.h" @@ -94,6 +97,12 @@ void load_additional_server_config(rf::Parser& parser) if (parser.parse_optional("+Only Avoid Enemies:")) { g_additional_server_config.new_spawn_logic.only_avoid_enemies = parser.parse_bool(); } + if (parser.parse_optional("+Use Items As Spawn Points:")) { + rf::String item_name; + while (parser.parse_string(&item_name)) { + g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.emplace_back(item_name.c_str()); + } + } } if (parser.parse_optional("$DF Spawn Protection Duration:")) { @@ -718,14 +727,14 @@ FunHookx, pos->y, pos->z); + xlog::warn("Position: ({}, {}, {})", pos->x, pos->y, pos->z); } - xlog::debug("Current number of spawn points: {}", new_multi_respawn_points.size()); + xlog::warn("Current number of spawn points: {}", new_multi_respawn_points.size()); return 0; } @@ -869,13 +878,42 @@ FunHook multi_respawn_get_next_poi *pos = new_multi_respawn_points[selected_index].position; *orient = new_multi_respawn_points[selected_index].orientation; pdata.last_spawn_point_index = selected_index; + xlog::warn("Player {} requested a spawn point. Giving them the spawn point with index {}", player->name, selected_index); return 1; } }; +CallHook item_create_hook{ + 0x00465175, + [](int type, const char* name, int count, int parent_handle, const rf::Vector3* pos, rf::Matrix3* orient, + int respawn_time, bool permanent, bool from_packet) { + + if (rf::is_dedicated_server) { + const auto allowed_types = [] { + std::unordered_set types; + for (const auto& item_name : g_additional_server_config.new_spawn_logic.allowed_respawn_item_names) { + if (int item_type = rf::item_lookup_type(item_name.c_str()); item_type >= 0) { + types.insert(item_type); + } + } + return types; + }(); + + if (allowed_types.contains(type)) { + rf::multi_respawn_create_point(name, 0, pos, orient, true, true, true); + } + } + + return item_create_hook.call_target( + type, name, count, parent_handle, pos, orient, respawn_time, permanent, from_packet); + } +}; + void server_init() { + item_create_hook.install(); + // Override rcon command whitelist write_mem_ptr(0x0046C794 + 1, g_rcon_cmd_whitelist); write_mem_ptr(0x0046C7D1 + 2, g_rcon_cmd_whitelist + std::size(g_rcon_cmd_whitelist)); diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index b0233f70..6729143f 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -37,6 +37,7 @@ struct NewSpawnLogicConfig // defaults match stock game bool always_avoid_last = false; bool always_use_furthest = false; bool only_avoid_enemies = false; + std::vector allowed_respawn_item_names; }; struct ServerAdditionalConfig diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index 7fa18b08..b29308a7 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -200,6 +200,8 @@ namespace rf static auto& multi_ping_player = addr_as_ref(0x00484D00); static auto& send_entity_create_packet = addr_as_ref(0x00475160); static auto& send_entity_create_packet_to_all = addr_as_ref(0x00475110); + static auto& multi_respawn_create_point = addr_as_ref(0x00470190); static auto& multi_find_character = addr_as_ref(0x00476270); static auto& multi_chat_print = addr_as_ref(0x004785A0); static auto& multi_chat_say = addr_as_ref(0x00444150); From 4aeec48b59bc3c8da2df282aaf7f99a0a56aa1c7 Mon Sep 17 00:00:00 2001 From: Goober Date: Sat, 26 Oct 2024 11:37:23 -0230 Subject: [PATCH 25/28] team associate item spawns based on proximity to ctf flags --- game_patch/main/main.cpp | 1 + game_patch/multi/server.cpp | 78 +++++++++++++++++++++++++++--- game_patch/multi/server.h | 1 + game_patch/multi/server_internal.h | 3 +- game_patch/rf/multi.h | 4 ++ 5 files changed, 78 insertions(+), 9 deletions(-) diff --git a/game_patch/main/main.cpp b/game_patch/main/main.cpp index aaeb157c..3c17dcf6 100644 --- a/game_patch/main/main.cpp +++ b/game_patch/main/main.cpp @@ -166,6 +166,7 @@ FunHook level_init_post_hook{ [](bool transition) { level_init_post_hook.call_target(transition); xlog::info("Level loaded: {}{}", rf::level.filename, transition ? " (transition)" : ""); + process_queued_spawn_points_from_items(); }, }; diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 84cdf0ce..9072cd97 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -48,6 +48,8 @@ const char* g_rcon_cmd_whitelist[] = { std::vector new_multi_respawn_points; // new storage of spawn points to avoid hard limits +std::vector> queued_item_spawn_points; + ServerAdditionalConfig g_additional_server_config; std::string g_prev_level; @@ -97,10 +99,14 @@ void load_additional_server_config(rf::Parser& parser) if (parser.parse_optional("+Only Avoid Enemies:")) { g_additional_server_config.new_spawn_logic.only_avoid_enemies = parser.parse_bool(); } - if (parser.parse_optional("+Use Items As Spawn Points:")) { + while (parser.parse_optional("+Use Item As Spawn Points:")) { rf::String item_name; - while (parser.parse_string(&item_name)) { + if (parser.parse_string(&item_name)) { g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.emplace_back(item_name.c_str()); + xlog::info("Added allowed spawn item: {}", item_name.c_str()); + } + else { + xlog::warn("Failed to parse item name after '+Use Item As Spawn Points:'"); } } } @@ -744,12 +750,11 @@ FunHook multi_respawn_level_init_hook { 0x00470180, []() { - new_multi_respawn_points.clear(); + new_multi_respawn_points.clear(); auto player_list = get_current_player_list(false); std::for_each(player_list.begin(), player_list.end(), - [](rf::Player* player) { get_player_additional_data(player).last_spawn_point_index.reset(); }); - + [](rf::Player* player) { get_player_additional_data(player).last_spawn_point_index.reset(); }); multi_respawn_level_init_hook.call_target(); } @@ -884,13 +889,69 @@ FunHook multi_respawn_get_next_poi } }; +bool are_flags_initialized() +{ + return rf::ctf_red_flag_item != nullptr && rf::ctf_blue_flag_item != nullptr; +} + +// returns 1 if closer to red, 0 if closer to blue, nullopt if no flags or flags are the same position +std::optional is_closer_to_red_flag(const rf::Vector3* pos) +{ + if (!are_flags_initialized()) { + return std::nullopt; + } + + rf::Vector3 red_flag_pos, blue_flag_pos; + rf::multi_ctf_get_red_flag_pos(&red_flag_pos); + rf::multi_ctf_get_blue_flag_pos(&blue_flag_pos); + + if (red_flag_pos.x == blue_flag_pos.x && + red_flag_pos.y == blue_flag_pos.y && + red_flag_pos.z == blue_flag_pos.z) { + return std::nullopt; + } + + float dist_to_red_sq = std::pow(pos->x - red_flag_pos.x, 2) + + std::pow(pos->y - red_flag_pos.y, 2) + + std::pow(pos->z - red_flag_pos.z, 2); + + float dist_to_blue_sq = std::pow(pos->x - blue_flag_pos.x, 2) + + std::pow(pos->y - blue_flag_pos.y, 2) + + std::pow(pos->z - blue_flag_pos.z, 2); + + return dist_to_red_sq < dist_to_blue_sq ? 1 : 0; +} + +void create_spawn_point_from_item(const std::string& name, const rf::Vector3* pos, rf::Matrix3* orient) +{ + bool red_spawn = true; + bool blue_spawn = true; + + if (multi_is_team_game_type()) { + if (auto is_closer_to_red = is_closer_to_red_flag(pos); is_closer_to_red.has_value()) { + red_spawn = *is_closer_to_red == 1; + blue_spawn = !red_spawn; + } + } + + rf::multi_respawn_create_point(name.c_str(), 0, pos, orient, red_spawn, blue_spawn, false); +} + +void process_queued_spawn_points_from_items() +{ + for (auto& [name, pos, orient] : queued_item_spawn_points) { + create_spawn_point_from_item(name, &pos, &orient); + } + queued_item_spawn_points.clear(); +} + CallHook item_create_hook{ 0x00465175, [](int type, const char* name, int count, int parent_handle, const rf::Vector3* pos, rf::Matrix3* orient, int respawn_time, bool permanent, bool from_packet) { - if (rf::is_dedicated_server) { - const auto allowed_types = [] { + if (rf::is_dedicated_server && !g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.empty()) { + static const std::unordered_set allowed_types = [] { std::unordered_set types; for (const auto& item_name : g_additional_server_config.new_spawn_logic.allowed_respawn_item_names) { if (int item_type = rf::item_lookup_type(item_name.c_str()); item_type >= 0) { @@ -901,8 +962,9 @@ CallHook #include "../rf/math/vector.h" #include "../rf/math/matrix.h" +#include "../rf/os/string.h" // Forward declarations namespace rf @@ -37,7 +38,7 @@ struct NewSpawnLogicConfig // defaults match stock game bool always_avoid_last = false; bool always_use_furthest = false; bool only_avoid_enemies = false; - std::vector allowed_respawn_item_names; + std::vector allowed_respawn_item_names; }; struct ServerAdditionalConfig diff --git a/game_patch/rf/multi.h b/game_patch/rf/multi.h index b29308a7..e22e2bbd 100644 --- a/game_patch/rf/multi.h +++ b/game_patch/rf/multi.h @@ -190,6 +190,10 @@ namespace rf static auto& multi_ctf_get_blue_flag_player = addr_as_ref(0x00474E70); static auto& multi_ctf_is_red_flag_in_base = addr_as_ref(0x00474E80); static auto& multi_ctf_is_blue_flag_in_base = addr_as_ref(0x00474EA0); + static auto& multi_ctf_get_blue_flag_pos = addr_as_ref(0x00474F40); + static auto& multi_ctf_get_red_flag_pos = addr_as_ref(0x00474EC0); + static auto& ctf_red_flag_item = addr_as_ref(0x006C7560); + static auto& ctf_blue_flag_item = addr_as_ref(0x006C7564); static auto& multi_tdm_get_red_team_score = addr_as_ref(0x004828F0); // returns ubyte in vanilla game static auto& multi_tdm_get_blue_team_score = addr_as_ref(0x00482900); // returns ubyte in vanilla game static auto& multi_num_players = addr_as_ref(0x00484830); From 27f7cfdb9e8c67cd7f7f6b659aa3435d68cd648b Mon Sep 17 00:00:00 2001 From: Goober Date: Sat, 26 Oct 2024 12:55:37 -0230 Subject: [PATCH 26/28] guess at center of map and point generated spawn points at it --- README.md | 2 ++ game_patch/multi/server.cpp | 69 ++++++++++++++++++++++++++++++------- game_patch/rf/math/vector.h | 9 +++++ 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 611102ac..40f329eb 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,8 @@ Configuration example: +Always Use Furthest: false // Ignore teammates when calculating the distance from spawn points to other players +Only Avoid Enemies: false + // Create additional respawn points on items of this type (add a new line for each) + +Use Item As Spawn Points: "Medical Kit" // Duration of player invulnerability after respawn in ms (default is the same as in stock RF - 1500) $DF Spawn Protection Duration: 1500 // Initial player life (health) after spawn diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index 9072cd97..d5d857b6 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -47,8 +47,17 @@ const char* g_rcon_cmd_whitelist[] = { }; std::vector new_multi_respawn_points; // new storage of spawn points to avoid hard limits +std::vector> queued_item_spawn_points; // queued generated spawns +std::optional likely_position_of_central_item; // guess at the center of the map for generated spawns +static const std::vector possible_central_item_names = { + "Multi Damage Amplifier", + "Multi Invulnerability", + "Multi Super Armor", + "shoulder cannon", + "Multi Super Health" +}; // prioritized list of common central items +int current_center_item_priority = possible_central_item_names.size(); -std::vector> queued_item_spawn_points; ServerAdditionalConfig g_additional_server_config; std::string g_prev_level; @@ -103,10 +112,6 @@ void load_additional_server_config(rf::Parser& parser) rf::String item_name; if (parser.parse_string(&item_name)) { g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.emplace_back(item_name.c_str()); - xlog::info("Added allowed spawn item: {}", item_name.c_str()); - } - else { - xlog::warn("Failed to parse item name after '+Use Item As Spawn Points:'"); } } } @@ -733,14 +738,14 @@ FunHookx, pos->y, pos->z); + xlog::debug("Position: ({}, {}, {})", pos->x, pos->y, pos->z); } - xlog::warn("Current number of spawn points: {}", new_multi_respawn_points.size()); + xlog::debug("Current number of spawn points: {}", new_multi_respawn_points.size()); return 0; } @@ -883,7 +888,7 @@ FunHook multi_respawn_get_next_poi *pos = new_multi_respawn_points[selected_index].position; *orient = new_multi_respawn_points[selected_index].orientation; pdata.last_spawn_point_index = selected_index; - xlog::warn("Player {} requested a spawn point. Giving them the spawn point with index {}", player->name, selected_index); + xlog::debug("Player {} requested a spawn point. Giving them index {}", player->name, selected_index); return 1; } @@ -937,12 +942,43 @@ void create_spawn_point_from_item(const std::string& name, const rf::Vector3* po rf::multi_respawn_create_point(name.c_str(), 0, pos, orient, red_spawn, blue_spawn, false); } +int get_item_priority(const std::string& item_name) +{ + auto it = std::find(possible_central_item_names.begin(), possible_central_item_names.end(), item_name); + return it != possible_central_item_names.end() ? + std::distance(possible_central_item_names.begin(), it) : possible_central_item_names.size(); +} + +void adjust_yaw_to_face_center(rf::Matrix3& orient, const rf::Vector3& pos, const rf::Vector3& center) +{ + rf::Vector3 direction = center - pos; + direction.normalize(); + orient.fvec = direction; + orient.uvec = rf::Vector3{0.0f, 1.0f, 0.0f}; + orient.rvec = orient.uvec.cross(orient.fvec); + orient.rvec.normalize(); + orient.uvec = orient.fvec.cross(orient.rvec); + orient.uvec.normalize(); +} + void process_queued_spawn_points_from_items() { + auto map_center = likely_position_of_central_item; + for (auto& [name, pos, orient] : queued_item_spawn_points) { - create_spawn_point_from_item(name, &pos, &orient); + rf::Matrix3 adjusted_orient = orient; + + if (map_center) { + adjust_yaw_to_face_center(adjusted_orient, pos, *map_center); + } + + create_spawn_point_from_item(name, &pos, &adjusted_orient); } + + //reset item generated spawn vars queued_item_spawn_points.clear(); + likely_position_of_central_item.reset(); + current_center_item_priority = possible_central_item_names.size(); } CallHook item_create_hook{ @@ -962,9 +998,17 @@ CallHook Date: Sat, 26 Oct 2024 13:28:44 -0230 Subject: [PATCH 27/28] cleanup --- README.md | 4 ++-- game_patch/multi/server.cpp | 16 ++++++++-------- game_patch/rf/multi.h | 14 -------------- 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 40f329eb..15203506 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ Configuration example: // Ignore teammates when calculating the distance from spawn points to other players +Only Avoid Enemies: false // Create additional respawn points on items of this type (add a new line for each) - +Use Item As Spawn Points: "Medical Kit" + +Use Item As Spawn Point: "Medical Kit" // Duration of player invulnerability after respawn in ms (default is the same as in stock RF - 1500) $DF Spawn Protection Duration: 1500 // Initial player life (health) after spawn @@ -210,7 +210,7 @@ Configuration example: //$DF Welcome Message: "Hello $PLAYER!" // Reward a player for a successful kill $DF Kill Reward: - // Increase player health or armor if health is full (armor delta is halved) + // Increase player health or armor if health is full (armor delta is halved) +Effective Health: 0 // Increase player health +Health: 0 diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index d5d857b6..ef05bb09 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -58,7 +58,6 @@ static const std::vector possible_central_item_names = { }; // prioritized list of common central items int current_center_item_priority = possible_central_item_names.size(); - ServerAdditionalConfig g_additional_server_config; std::string g_prev_level; @@ -108,7 +107,7 @@ void load_additional_server_config(rf::Parser& parser) if (parser.parse_optional("+Only Avoid Enemies:")) { g_additional_server_config.new_spawn_logic.only_avoid_enemies = parser.parse_bool(); } - while (parser.parse_optional("+Use Item As Spawn Points:")) { + while (parser.parse_optional("+Use Item As Spawn Point:")) { rf::String item_name; if (parser.parse_string(&item_name)) { g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.emplace_back(item_name.c_str()); @@ -725,12 +724,9 @@ FunHook multi_respawn_get_next_poi 0x00470300, [](rf::Vector3* pos, rf::Matrix3* orient, rf::Player* player) { - // default return if map has no respawn points + // if map has no respawn points if (new_multi_respawn_points.empty()) { *pos = rf::level.player_start_pos; *orient = rf::level.player_start_orient; @@ -807,7 +803,7 @@ FunHook multi_respawn_get_next_poi return -1; } - // default return if player is invalid (should never happen) + // if player is invalid (should never happen) if (!player) { std::uniform_int_distribution dist(0, new_multi_respawn_points.size() - 1); int index = dist(g_rng); @@ -963,6 +959,10 @@ void adjust_yaw_to_face_center(rf::Matrix3& orient, const rf::Vector3& pos, cons void process_queued_spawn_points_from_items() { + if (g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.empty()) { + return; // early return if not generating any spawns + } + auto map_center = likely_position_of_central_item; for (auto& [name, pos, orient] : queued_item_spawn_points) { @@ -1001,7 +1001,7 @@ CallHook Date: Sat, 26 Oct 2024 21:23:47 -0230 Subject: [PATCH 28/28] add threshold to items as spawns --- README.md | 5 +++-- game_patch/multi/server.cpp | 26 ++++++++++++-------------- game_patch/multi/server_internal.h | 2 +- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 15203506..4de39ac0 100644 --- a/README.md +++ b/README.md @@ -162,8 +162,9 @@ Configuration example: +Always Use Furthest: false // Ignore teammates when calculating the distance from spawn points to other players +Only Avoid Enemies: false - // Create additional respawn points on items of this type (add a new line for each) - +Use Item As Spawn Point: "Medical Kit" + // Create an additional respawn point at each "Medical Kit" item, if the level has less than 9 Multiplayer Respawn Points + // Specify "0" to always create them regardless of how many Multiplayer Respawn Points are in the level + +Use Item As Spawn Point: "Medical Kit" 9 // Duration of player invulnerability after respawn in ms (default is the same as in stock RF - 1500) $DF Spawn Protection Duration: 1500 // Initial player life (health) after spawn diff --git a/game_patch/multi/server.cpp b/game_patch/multi/server.cpp index ef05bb09..5271f222 100644 --- a/game_patch/multi/server.cpp +++ b/game_patch/multi/server.cpp @@ -109,8 +109,10 @@ void load_additional_server_config(rf::Parser& parser) } while (parser.parse_optional("+Use Item As Spawn Point:")) { rf::String item_name; + std::optional threshold = std::nullopt; if (parser.parse_string(&item_name)) { - g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.emplace_back(item_name.c_str()); + threshold = parser.parse_int(); + g_additional_server_config.new_spawn_logic.allowed_respawn_items[item_name.c_str()] = threshold; } } } @@ -959,8 +961,9 @@ void adjust_yaw_to_face_center(rf::Matrix3& orient, const rf::Vector3& pos, cons void process_queued_spawn_points_from_items() { - if (g_additional_server_config.new_spawn_logic.allowed_respawn_item_names.empty()) { - return; // early return if not generating any spawns + + if (g_additional_server_config.new_spawn_logic.allowed_respawn_items.empty()) { + return; // early return if no spawn points are to be generated } auto map_center = likely_position_of_central_item; @@ -986,18 +989,13 @@ CallHook allowed_types = [] { - std::unordered_set types; - for (const auto& item_name : g_additional_server_config.new_spawn_logic.allowed_respawn_item_names) { - if (int item_type = rf::item_lookup_type(item_name.c_str()); item_type >= 0) { - types.insert(item_type); - } - } - return types; - }(); + if (rf::is_dedicated_server && !g_additional_server_config.new_spawn_logic.allowed_respawn_items.empty()) { + const auto& allowed_items = g_additional_server_config.new_spawn_logic.allowed_respawn_items; + + auto it = allowed_items.find(name); + if (it != allowed_items.end() && + (!it->second || it->second == 0 || *it->second > static_cast(new_multi_respawn_points.size()))) { - if (allowed_types.contains(type)) { queued_item_spawn_points.emplace_back(std::string(name), *pos, *orient); } diff --git a/game_patch/multi/server_internal.h b/game_patch/multi/server_internal.h index 6f9fd404..7a8056c2 100644 --- a/game_patch/multi/server_internal.h +++ b/game_patch/multi/server_internal.h @@ -38,7 +38,7 @@ struct NewSpawnLogicConfig // defaults match stock game bool always_avoid_last = false; bool always_use_furthest = false; bool only_avoid_enemies = false; - std::vector allowed_respawn_item_names; + std::map> allowed_respawn_items; }; struct ServerAdditionalConfig