From a88d4a2dd4971da9fe0fbb07ff6c2963daa80a51 Mon Sep 17 00:00:00 2001 From: Goober Date: Wed, 25 Sep 2024 11:00:13 -0230 Subject: [PATCH 01/13] initial working read --- game_patch/misc/misc.cpp | 96 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/game_patch/misc/misc.cpp b/game_patch/misc/misc.cpp index 8fb5f446..128a7d0b 100644 --- a/game_patch/misc/misc.cpp +++ b/game_patch/misc/misc.cpp @@ -8,6 +8,11 @@ #include #include #include +#include +#include +#include +#include +#include #include "misc.h" #include "../sound/sound.h" #include "../os/console.h" @@ -18,6 +23,7 @@ #include "../rf/gameseq.h" #include "../rf/os/os.h" #include "../rf/misc.h" +#include "../rf/parse.h" #include "../rf/vmesh.h" #include "../rf/level.h" #include "../rf/file/file.h" @@ -396,6 +402,93 @@ CallHook level_init_pre_console_output_hook{ }, }; +std::string trim(const std::string& str) { + auto start = str.begin(); + while (start != str.end() && std::isspace(*start)) { + start++; + } + + auto end = str.end(); + do { + end--; + } while (std::distance(start, end) > 0 && std::isspace(*end)); + + return std::string(start, end + 1); +} + +// Class to encapsulate file reading and parsing logic +class DashOptionsParser { +public: + DashOptionsParser(const std::string& file_path) : file_path(file_path) {} + + bool open_file() { + if (dashoptions_file.open(file_path.c_str()) != 0) { + xlog::error("Failed to open {}", file_path); + return false; + } + xlog::warn("Successfully opened {}", file_path); + return true; + } + + void close_file() { + dashoptions_file.close(); + } + + bool read_next_line(std::string& line) { + char buffer[256]; // Adjust buffer size as needed + int bytes_read = dashoptions_file.read(buffer, sizeof(buffer) - 1); + if (bytes_read <= 0) { + return false; + } + + buffer[bytes_read] = '\0'; // Null-terminate the buffer + line = std::string(buffer); + line = trim(line); + return true; + } + + void parse() { + std::string line; + while (read_next_line(line)) { + // Check if the line starts with the $Print directive + if (line.rfind("$Print:", 0) == 0) { + // Extract the value after $Print: + std::string print_value = line.substr(7); // Skip "$Print:" + print_value = trim(print_value); // Trim any extra spaces + + // Remove quotes if present + if (print_value.front() == '"' && print_value.back() == '"') { + print_value = print_value.substr(1, print_value.size() - 2); + } + + // Log the value using xlog + xlog::warn("Dash Options - Print: {}", print_value); + } + } + } + +private: + std::string file_path; + rf::File dashoptions_file; +}; + +// Code injection block where the parsing logic is used +CodeInjection all_table_files_loaded_injection{ + 0x004B249E, + []() { + DashOptionsParser parser("dashoptions.tbl"); + + // Open the file using the game's file system + if (parser.open_file()) { + // Parse the file if opened successfully + parser.parse(); + + // Close the file when done + parser.close_file(); + } + } +}; + void misc_init() { // Window title (client and server) @@ -503,6 +596,9 @@ void misc_init() // Add level name to "-- Level Initializing --" message level_init_pre_console_output_hook.install(); + // Load dashoptionstbl + all_table_files_loaded_injection.install(); + // Apply patches from other files apply_main_menu_patches(); apply_save_restore_patches(); From e33d86bbc87c1249377378a2ffa1eb9fe608d577 Mon Sep 17 00:00:00 2001 From: Goober Date: Wed, 25 Sep 2024 12:19:57 -0230 Subject: [PATCH 02/13] break into dashoptions.cpp --- game_patch/CMakeLists.txt | 2 + game_patch/misc/dashoptions.cpp | 162 ++++++++++++++++++++++++++++++++ game_patch/misc/dashoptions.h | 44 +++++++++ game_patch/misc/misc.cpp | 84 +---------------- 4 files changed, 211 insertions(+), 81 deletions(-) create mode 100644 game_patch/misc/dashoptions.cpp create mode 100644 game_patch/misc/dashoptions.h diff --git a/game_patch/CMakeLists.txt b/game_patch/CMakeLists.txt index 4c7671c4..6f633708 100644 --- a/game_patch/CMakeLists.txt +++ b/game_patch/CMakeLists.txt @@ -169,6 +169,8 @@ set(SRCS misc/camera.cpp misc/ui.cpp misc/game.cpp + misc/dashoptions.h + misc/dashoptions.cpp sound/sound.cpp sound/sound.h sound/sound_ds.cpp diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp new file mode 100644 index 00000000..ba763580 --- /dev/null +++ b/game_patch/misc/dashoptions.cpp @@ -0,0 +1,162 @@ +#include "dashoptions.h" +#include "../rf/file/file.h" +#include +#include +#include +#include +#include + +namespace dashopt +{ +// Encapsulate file handling within a function-local static +rf::File& get_dashoptions_file() +{ + static rf::File dashoptions_file; + return dashoptions_file; +} + +// Helper function to trim leading and trailing whitespace +std::string trim(const std::string& str) +{ + auto start = str.begin(); + while (start != str.end() && std::isspace(*start)) { + start++; + } + + auto end = str.end(); + do { + end--; + } while (std::distance(start, end) > 0 && std::isspace(*end)); + + return std::string(start, end + 1); +} + + +// Encapsulate option handlers within a safe function-local static +std::map>>& get_option_handlers() +{ + static std::map>> option_handlers; + return option_handlers; +} + +bool open_file(const std::string& file_path) +{ + auto& dashoptions_file = get_dashoptions_file(); // Use the encapsulated file instance + if (dashoptions_file.open(file_path.c_str()) != 0) { + xlog::error("Failed to open {}", file_path); + return false; + } + xlog::warn("Successfully opened {}", file_path); + return true; +} + +void close_file() +{ + auto& dashoptions_file = get_dashoptions_file(); // Use the encapsulated file instance + dashoptions_file.close(); +} + +void add_option_handler(const std::string& option_name, OptionType type, + std::function handler) +{ + get_option_handlers()[option_name] = std::make_pair(type, handler); +} + +void parse() +{ + std::string line; + char buffer[256]; + auto& dashoptions_file = get_dashoptions_file(); // Use the encapsulated file instance + + while (int bytes_read = dashoptions_file.read(buffer, sizeof(buffer) - 1)) { + if (bytes_read <= 0) + break; + + buffer[bytes_read] = '\0'; + line = std::string(buffer); + line = trim(line); + + auto delimiter_pos = line.find(':'); + if (delimiter_pos == std::string::npos) + continue; + + std::string option_name = trim(line.substr(0, delimiter_pos)); + std::string option_value = trim(line.substr(delimiter_pos + 1)); + + auto& option_handlers = get_option_handlers(); + if (option_handlers.find(option_name) != option_handlers.end()) { + auto [expected_type, handler] = option_handlers[option_name]; + ConfigOption option; + option.type = expected_type; + + // Parse based on the expected type + switch (expected_type) { + case OptionType::String: + if (option_value.front() == '"' && option_value.back() == '"') { + option.string_value = option_value.substr(1, option_value.size() - 2); + } + break; + case OptionType::Float: + try { + option.float_value = std::stof(option_value); + } + catch (...) { + xlog::warn("Invalid float for option: {}", option_name); + } + break; + case OptionType::Integer: + try { + option.int_value = std::stoi(option_value); + } + catch (...) { + xlog::warn("Invalid integer for option: {}", option_name); + } + break; + } + + // Call the handler with the parsed option + handler(option); + } + else { + xlog::warn("Unknown option: {}", option_name); + } + } +} + +// Function to load dashoptions configuration +void load_dashoptions_config() +{ + // Open the file using the game's file system + if (!open_file("dashoptions.tbl")) { + return; + } + + // Register handlers for each expected option + add_option_handler("$Print", OptionType::String, [](const ConfigOption& option) { + if (option.string_value) { + xlog::warn("Dash Options - Print: {}", option.string_value.value()); + } + }); + + add_option_handler("$Player Walk Speed", OptionType::Float, [](const ConfigOption& option) { + if (option.float_value) { + xlog::warn("Player Walk Speed set to {}", option.float_value.value()); + // Set player walk speed in your system here + } + }); + + add_option_handler("$Num Shotgun Projectiles", OptionType::Integer, [](const ConfigOption& option) { + if (option.int_value) { + xlog::warn("Num Shotgun Projectiles set to {}", option.int_value.value()); + // Set shotgun projectiles in your system here + } + }); + + // Parse the file and handle the options + parse(); + + // Close the file when done + close_file(); +} + +} // namespace dashopt diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h new file mode 100644 index 00000000..bc5256b2 --- /dev/null +++ b/game_patch/misc/dashoptions.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include + +// Enumeration for supported option types +enum class OptionType +{ + String, + Float, + Integer +}; + +// Structure to hold each configuration option with its expected type and value +struct ConfigOption +{ + OptionType type; + std::optional string_value; + std::optional float_value; + std::optional int_value; +}; + +// Namespace to encapsulate dash options parsing logic +namespace dashopt +{ +// Open file function +bool open_file(const std::string& file_path); + +// Close file function +void close_file(); + +// Parse function that iterates through the file and parses each option +void parse(); + +// Add option handler for each configuration option +void add_option_handler(const std::string& option_name, OptionType type, + std::function handler); + +// Load DashOptions configuration and register handlers +void load_dashoptions_config(); + +} // namespace dashopt diff --git a/game_patch/misc/misc.cpp b/game_patch/misc/misc.cpp index 128a7d0b..7a710fae 100644 --- a/game_patch/misc/misc.cpp +++ b/game_patch/misc/misc.cpp @@ -13,6 +13,7 @@ #include #include #include +#include "dashoptions.h" #include "misc.h" #include "../sound/sound.h" #include "../os/console.h" @@ -402,90 +403,11 @@ CallHook level_init_pre_console_output_hook{ }, }; -std::string trim(const std::string& str) { - auto start = str.begin(); - while (start != str.end() && std::isspace(*start)) { - start++; - } - - auto end = str.end(); - do { - end--; - } while (std::distance(start, end) > 0 && std::isspace(*end)); - - return std::string(start, end + 1); -} - -// Class to encapsulate file reading and parsing logic -class DashOptionsParser { -public: - DashOptionsParser(const std::string& file_path) : file_path(file_path) {} - - bool open_file() { - if (dashoptions_file.open(file_path.c_str()) != 0) { - xlog::error("Failed to open {}", file_path); - return false; - } - xlog::warn("Successfully opened {}", file_path); - return true; - } - - void close_file() { - dashoptions_file.close(); - } - - bool read_next_line(std::string& line) { - char buffer[256]; // Adjust buffer size as needed - int bytes_read = dashoptions_file.read(buffer, sizeof(buffer) - 1); - if (bytes_read <= 0) { - return false; - } - - buffer[bytes_read] = '\0'; // Null-terminate the buffer - line = std::string(buffer); - line = trim(line); - return true; - } - - void parse() { - std::string line; - while (read_next_line(line)) { - // Check if the line starts with the $Print directive - if (line.rfind("$Print:", 0) == 0) { - // Extract the value after $Print: - std::string print_value = line.substr(7); // Skip "$Print:" - print_value = trim(print_value); // Trim any extra spaces - - // Remove quotes if present - if (print_value.front() == '"' && print_value.back() == '"') { - print_value = print_value.substr(1, print_value.size() - 2); - } - - // Log the value using xlog - xlog::warn("Dash Options - Print: {}", print_value); - } - } - } - -private: - std::string file_path; - rf::File dashoptions_file; -}; - -// Code injection block where the parsing logic is used CodeInjection all_table_files_loaded_injection{ 0x004B249E, []() { - DashOptionsParser parser("dashoptions.tbl"); - - // Open the file using the game's file system - if (parser.open_file()) { - // Parse the file if opened successfully - parser.parse(); - - // Close the file when done - parser.close_file(); - } + // Load the dashoptions.tbl file and configure the options + dashopt::load_dashoptions_config(); } }; From c162670e26947e59a6beceaf72353bc83a724c0d Mon Sep 17 00:00:00 2001 From: Goober Date: Wed, 25 Sep 2024 17:14:55 -0230 Subject: [PATCH 03/13] working commit --- game_patch/hud/multi_scoreboard.cpp | 6 +- game_patch/misc/dashoptions.cpp | 236 +++++++++++++++------------- game_patch/misc/dashoptions.h | 60 +++---- game_patch/misc/misc.cpp | 7 +- 4 files changed, 169 insertions(+), 140 deletions(-) diff --git a/game_patch/hud/multi_scoreboard.cpp b/game_patch/hud/multi_scoreboard.cpp index 86cff2a0..dffcc450 100644 --- a/game_patch/hud/multi_scoreboard.cpp +++ b/game_patch/hud/multi_scoreboard.cpp @@ -4,6 +4,7 @@ #include #include "multi_scoreboard.h" #include "../multi/multi.h" +#include "../misc/dashoptions.h" #include "../rf/gr/gr.h" #include "../rf/gr/gr_font.h" #include "../rf/multi.h" @@ -46,7 +47,10 @@ int draw_scoreboard_header(int x, int y, int w, rf::NetGameType game_type, bool int cur_y = y; if (!dry_run) { rf::gr::set_color(0xFF, 0xFF, 0xFF, 0xFF); - static int score_rflogo_bm = rf::bm::load("score_rflogo.tga", -1, false); + // load custom scoreboard logo from dashoptions.tbl if specified + static int score_rflogo_bm = rf::bm::load(g_dash_options_config.is_option_loaded(DashOptionID::ScoreboardLogo) + ? g_dash_options_config.scoreboard_logo.value().c_str() + : "score_rflogo.tga", -1, false); rf::gr::bitmap(score_rflogo_bm, x_center - 170, cur_y); } cur_y += 30; diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index ba763580..17b613c5 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -1,48 +1,98 @@ #include "dashoptions.h" #include "../rf/file/file.h" #include +#include #include -#include -#include #include -namespace dashopt -{ -// Encapsulate file handling within a function-local static -rf::File& get_dashoptions_file() +DashOptionsConfig g_dash_options_config; + +void mark_option_loaded(DashOptionID id) { - static rf::File dashoptions_file; - return dashoptions_file; + g_dash_options_config.options_loaded[static_cast(id)] = true; } -// Helper function to trim leading and trailing whitespace +namespace dashopt +{ +std::unique_ptr dashoptions_file; + +// trim leading and trailing whitespace std::string trim(const std::string& str) { - auto start = str.begin(); - while (start != str.end() && std::isspace(*start)) { - start++; + auto start = std::find_if_not(str.begin(), str.end(), [](unsigned char ch) { return std::isspace(ch); }); + auto end = std::find_if_not(str.rbegin(), str.rend(), [](unsigned char ch) { return std::isspace(ch); }).base(); + + if (start >= end) { + return ""; // Return empty string if nothing remains after trimming } + return std::string(start, end); +} + +// for values provided in quotes, dump the quotes before storing the values +std::optional extract_quoted_value(const std::string& value) +{ + std::string trimmed_value = trim(value); - auto end = str.end(); - do { - end--; - } while (std::distance(start, end) > 0 && std::isspace(*end)); + // check if trimmed value starts and ends with quotes, if so, extract the content inside them + if (trimmed_value.size() >= 2 && trimmed_value.front() == '"' && trimmed_value.back() == '"') { + std::string extracted_value = + trimmed_value.substr(1, trimmed_value.size() - 2); + return extracted_value; + } - return std::string(start, end + 1); + // if not wrapped in quotes, assume valid + xlog::warn("String value is not enclosed in quotes, accepting it anyway: '{}'", trimmed_value); + return trimmed_value; } +// consolidated processing for options +template +void set_option(DashOptionID option_id, std::optional& option_field, const std::string& option_value) +{ + try { + if constexpr (std::is_same_v) { + // strip quotes for strings + if (auto quoted_value = extract_quoted_value(option_value)) { + option_field = quoted_value.value(); + xlog::warn("Successfully extracted string: {}", quoted_value.value()); + } + else { + xlog::warn("Invalid string format: {}", option_value); + return; + } + } + else if constexpr (std::is_same_v) { + option_field = std::stof(option_value); + } + else if constexpr (std::is_same_v) { + option_field = std::stoi(option_value); + } + // mark option as loaded + mark_option_loaded(option_id); + xlog::warn("Parsed value has been saved: {}", option_field.value()); + } + catch (const std::exception& e) { + xlog::warn("Failed to parse value for option: {}. Error: {}", option_value, e.what()); + } +} -// Encapsulate option handlers within a safe function-local static -std::map>>& get_option_handlers() +// identify custom options and parse where found +void process_dashoption_line(const std::string& option_name, const std::string& option_value) { - static std::map>> option_handlers; - return option_handlers; + xlog::warn("Found an option! Attempting to process {} with value {}", option_name, option_value); + + if (option_name == "$Scoreboard Logo") { + set_option(DashOptionID::ScoreboardLogo, g_dash_options_config.scoreboard_logo, option_value); + } + else { + xlog::warn("Ignoring unsupported option: {}", option_name); + } } bool open_file(const std::string& file_path) { - auto& dashoptions_file = get_dashoptions_file(); // Use the encapsulated file instance - if (dashoptions_file.open(file_path.c_str()) != 0) { + dashoptions_file = std::make_unique(); + if (dashoptions_file->open(file_path.c_str()) != 0) { xlog::error("Failed to open {}", file_path); return false; } @@ -52,111 +102,83 @@ bool open_file(const std::string& file_path) void close_file() { - auto& dashoptions_file = get_dashoptions_file(); // Use the encapsulated file instance - dashoptions_file.close(); -} - -void add_option_handler(const std::string& option_name, OptionType type, - std::function handler) -{ - get_option_handlers()[option_name] = std::make_pair(type, handler); + if (dashoptions_file) { + xlog::warn("Closing file."); + dashoptions_file->close(); + dashoptions_file.reset(); + } } void parse() { - std::string line; + std::string line, full_line; char buffer[256]; - auto& dashoptions_file = get_dashoptions_file(); // Use the encapsulated file instance + bool in_options_section = false; // track section, eventually this should be enum and support multiple sections - while (int bytes_read = dashoptions_file.read(buffer, sizeof(buffer) - 1)) { - if (bytes_read <= 0) + xlog::warn("Start parsing dashoptions.tbl"); + + while (int bytes_read = dashoptions_file->read(buffer, sizeof(buffer) - 1)) { + if (bytes_read <= 0) { + xlog::warn("End of file or read error in dashoptions.tbl."); break; + } buffer[bytes_read] = '\0'; - line = std::string(buffer); - line = trim(line); - - auto delimiter_pos = line.find(':'); - if (delimiter_pos == std::string::npos) - continue; - - std::string option_name = trim(line.substr(0, delimiter_pos)); - std::string option_value = trim(line.substr(delimiter_pos + 1)); - - auto& option_handlers = get_option_handlers(); - if (option_handlers.find(option_name) != option_handlers.end()) { - auto [expected_type, handler] = option_handlers[option_name]; - ConfigOption option; - option.type = expected_type; - - // Parse based on the expected type - switch (expected_type) { - case OptionType::String: - if (option_value.front() == '"' && option_value.back() == '"') { - option.string_value = option_value.substr(1, option_value.size() - 2); - } - break; - case OptionType::Float: - try { - option.float_value = std::stof(option_value); - } - catch (...) { - xlog::warn("Invalid float for option: {}", option_name); - } - break; - case OptionType::Integer: - try { - option.int_value = std::stoi(option_value); - } - catch (...) { - xlog::warn("Invalid integer for option: {}", option_name); - } - break; + std::istringstream file_stream(buffer); + while (std::getline(file_stream, line)) { + line = trim(line); + xlog::warn("Parsing line: '{}'", line); + + if (line == "#General") { + xlog::warn("Entering General section"); + in_options_section = true; + continue; + } + else if (line == "#End") { + xlog::warn("Exiting General section"); + in_options_section = false; + break; // stop parsing after reaching the end } - // Call the handler with the parsed option - handler(option); - } - else { - xlog::warn("Unknown option: {}", option_name); + // do not parse anything outside the options section + if (!in_options_section) { + xlog::warn("Skipping line outside of General section"); + continue; + } + + // Skip empty lines and comments + if (line.empty() || line.find("//") == 0) { + xlog::warn("Skipping line because it's empty or a comment"); + continue; + } + + // Parse options + auto delimiter_pos = line.find(':'); + if (delimiter_pos == std::string::npos) { + xlog::warn("Skipping malformed line: {}", line); + continue; + } + + std::string option_name = trim(line.substr(0, delimiter_pos)); + std::string option_value = trim(line.substr(delimiter_pos + 1)); + + process_dashoption_line(option_name, option_value); } } } -// Function to load dashoptions configuration + void load_dashoptions_config() { - // Open the file using the game's file system + xlog::warn("Mod launched, attempting to load Dash Options configuration"); + if (!open_file("dashoptions.tbl")) { return; } - // Register handlers for each expected option - add_option_handler("$Print", OptionType::String, [](const ConfigOption& option) { - if (option.string_value) { - xlog::warn("Dash Options - Print: {}", option.string_value.value()); - } - }); - - add_option_handler("$Player Walk Speed", OptionType::Float, [](const ConfigOption& option) { - if (option.float_value) { - xlog::warn("Player Walk Speed set to {}", option.float_value.value()); - // Set player walk speed in your system here - } - }); - - add_option_handler("$Num Shotgun Projectiles", OptionType::Integer, [](const ConfigOption& option) { - if (option.int_value) { - xlog::warn("Num Shotgun Projectiles set to {}", option.int_value.value()); - // Set shotgun projectiles in your system here - } - }); - - // Parse the file and handle the options parse(); - - // Close the file when done close_file(); -} -} // namespace dashopt + xlog::warn("Dash Options configuration loaded"); +} +} diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index bc5256b2..e4e53e2b 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -1,44 +1,44 @@ #pragma once -#include -#include +#include #include #include +#include -// Enumeration for supported option types -enum class OptionType +enum class DashOptionID { - String, - Float, - Integer + ScoreboardLogo, + _optioncount // dummy option for determining total num of options }; -// Structure to hold each configuration option with its expected type and value -struct ConfigOption +// convert enum to its underlying type +constexpr std::size_t to_index(DashOptionID option_id) { - OptionType type; - std::optional string_value; - std::optional float_value; - std::optional int_value; -}; + return static_cast(option_id); +} -// Namespace to encapsulate dash options parsing logic -namespace dashopt -{ -// Open file function -bool open_file(const std::string& file_path); +// total number of options in DashOptionID +constexpr std::size_t option_count = to_index(DashOptionID::_optioncount) + 1; -// Close file function -void close_file(); - -// Parse function that iterates through the file and parses each option -void parse(); +struct DashOptionsConfig +{ + //std::optional float_something; // template for float + //std::optional int_something; // template for int + std::optional scoreboard_logo; + + // track options that are loaded + std::array options_loaded = {}; + + // check if specific option is loaded + bool is_option_loaded(DashOptionID option_id) const + { + return options_loaded[to_index(option_id)]; + } +}; -// Add option handler for each configuration option -void add_option_handler(const std::string& option_name, OptionType type, - std::function handler); +extern DashOptionsConfig g_dash_options_config; -// Load DashOptions configuration and register handlers +namespace dashopt +{ void load_dashoptions_config(); - -} // namespace dashopt +} diff --git a/game_patch/misc/misc.cpp b/game_patch/misc/misc.cpp index 7a710fae..956928a5 100644 --- a/game_patch/misc/misc.cpp +++ b/game_patch/misc/misc.cpp @@ -406,8 +406,11 @@ CallHook level_init_pre_console_output_hook{ CodeInjection all_table_files_loaded_injection{ 0x004B249E, []() { - // Load the dashoptions.tbl file and configure the options - dashopt::load_dashoptions_config(); + // after all other tbl files have been loaded, load dashoptions.tbl and parse it + // only if a TC mod is loaded + if (rf::mod_param.found()) { + dashopt::load_dashoptions_config(); + } } }; From 35b4f92aa83325b80a51027e6ce7ef4008eca993 Mon Sep 17 00:00:00 2001 From: Goober Date: Wed, 25 Sep 2024 21:11:36 -0230 Subject: [PATCH 04/13] add $Use Base Game Players Config --- game_patch/misc/dashoptions.cpp | 30 ++++++++++++++++++++++++++++++ game_patch/misc/dashoptions.h | 2 ++ 2 files changed, 32 insertions(+) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index 17b613c5..268b531a 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -1,4 +1,9 @@ #include "dashoptions.h" +#include +#include +#include +#include +#include #include "../rf/file/file.h" #include #include @@ -45,6 +50,21 @@ std::optional extract_quoted_value(const std::string& value) return trimmed_value; } +void disable_mod_playercfg_patch() +{ + if (g_dash_options_config.use_stock_game_players_config) { + // set mod to not make its own players.cfg but instead use the stock game one + AsmWriter(0x004A8F99).jmp(0x004A9010); + AsmWriter(0x004A8DCC).jmp(0x004A8E53); + } +} + +void apply_dashoptions_patches() +{ + xlog::warn("Applying Dash Options patches"); + disable_mod_playercfg_patch(); +} + // consolidated processing for options template void set_option(DashOptionID option_id, std::optional& option_field, const std::string& option_value) @@ -67,6 +87,10 @@ void set_option(DashOptionID option_id, std::optional& option_field, const st else if constexpr (std::is_same_v) { option_field = std::stoi(option_value); } + else if constexpr (std::is_same_v) { // handle bools with every reasonable capitalization + option_field = + (option_value == "1" || option_value == "true" || option_value == "True" || option_value == "TRUE"); + } // mark option as loaded mark_option_loaded(option_id); xlog::warn("Parsed value has been saved: {}", option_field.value()); @@ -84,6 +108,9 @@ void process_dashoption_line(const std::string& option_name, const std::string& if (option_name == "$Scoreboard Logo") { set_option(DashOptionID::ScoreboardLogo, g_dash_options_config.scoreboard_logo, option_value); } + if (option_name == "$Use Base Game Players Config") { + set_option(DashOptionID::UseStockPlayersConfig, g_dash_options_config.use_stock_game_players_config, option_value); + } else { xlog::warn("Ignoring unsupported option: {}", option_name); } @@ -106,6 +133,9 @@ void close_file() xlog::warn("Closing file."); dashoptions_file->close(); dashoptions_file.reset(); + + // after parsing is done, apply patches + apply_dashoptions_patches(); } } diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index e4e53e2b..0a825835 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -8,6 +8,7 @@ enum class DashOptionID { ScoreboardLogo, + UseStockPlayersConfig, _optioncount // dummy option for determining total num of options }; @@ -25,6 +26,7 @@ struct DashOptionsConfig //std::optional float_something; // template for float //std::optional int_something; // template for int std::optional scoreboard_logo; + std::optional use_stock_game_players_config; // track options that are loaded std::array options_loaded = {}; From 542331f8f13efef17eb6fcf48edf95282b21c289 Mon Sep 17 00:00:00 2001 From: Goober Date: Wed, 25 Sep 2024 22:53:05 -0230 Subject: [PATCH 05/13] add support for uint32_t color options, add $Assault Rifle Ammo Counter Color, add fixed assault_digits.vbm (thx HeyItsDuke) --- game_patch/misc/dashoptions.cpp | 24 ++++++++++++++++++++++++ game_patch/misc/dashoptions.h | 2 ++ resources/CMakeLists.txt | 1 + resources/images/assault_digits.vbm | Bin 0 -> 20512 bytes 4 files changed, 27 insertions(+) create mode 100644 resources/images/assault_digits.vbm diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index 268b531a..ae66c401 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -5,6 +5,7 @@ #include #include #include "../rf/file/file.h" +#include "../rf/gr/gr.h" #include #include #include @@ -59,10 +60,26 @@ void disable_mod_playercfg_patch() } } +CodeInjection fpgun_ar_ammo_digit_color_injection{ + 0x004ABC03, + [](auto& regs) { + if (g_dash_options_config.is_option_loaded(DashOptionID::AssaultRifleAmmoColor)) { + uint32_t color = g_dash_options_config.ar_ammo_color.value(); + rf::gr::set_color((color >> 24) & 0xFF, // r + (color >> 16) & 0xFF, // g + (color >> 8) & 0xFF, // b + color & 0xFF // a + ); + } + regs.eip = 0x004ABC08; + } +}; + void apply_dashoptions_patches() { xlog::warn("Applying Dash Options patches"); disable_mod_playercfg_patch(); + fpgun_ar_ammo_digit_color_injection.install(); } // consolidated processing for options @@ -81,6 +98,10 @@ void set_option(DashOptionID option_id, std::optional& option_field, const st return; } } + else if constexpr (std::is_same_v) { // handle color values + option_field = static_cast( + std::stoul(extract_quoted_value(option_value).value_or(option_value), nullptr, 16)); + } else if constexpr (std::is_same_v) { option_field = std::stof(option_value); } @@ -111,6 +132,9 @@ void process_dashoption_line(const std::string& option_name, const std::string& if (option_name == "$Use Base Game Players Config") { set_option(DashOptionID::UseStockPlayersConfig, g_dash_options_config.use_stock_game_players_config, option_value); } + if (option_name == "$Assault Rifle Ammo Counter Color") { + set_option(DashOptionID::AssaultRifleAmmoColor, g_dash_options_config.ar_ammo_color, option_value); + } else { xlog::warn("Ignoring unsupported option: {}", option_name); } diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index 0a825835..41a7d734 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -9,6 +9,7 @@ enum class DashOptionID { ScoreboardLogo, UseStockPlayersConfig, + AssaultRifleAmmoColor, _optioncount // dummy option for determining total num of options }; @@ -27,6 +28,7 @@ struct DashOptionsConfig //std::optional int_something; // template for int std::optional scoreboard_logo; std::optional use_stock_game_players_config; + std::optional ar_ammo_color; // track options that are loaded std::array options_loaded = {}; diff --git a/resources/CMakeLists.txt b/resources/CMakeLists.txt index 66badfe8..a1e82b8d 100644 --- a/resources/CMakeLists.txt +++ b/resources/CMakeLists.txt @@ -36,6 +36,7 @@ add_packfile(dashfaction.vpp images/ammo_bar_power_1.tga images/ammo_signal_green_1.tga images/ammo_signal_red_1.tga + images/assault_digits.vbm images/bullet_icon_1.tga images/bullet_icon_50cal_1.tga images/bullet_icon_556_1.tga diff --git a/resources/images/assault_digits.vbm b/resources/images/assault_digits.vbm new file mode 100644 index 0000000000000000000000000000000000000000..9c81e57b2780646fd14b168a16aab1066164be90 GIT binary patch literal 20512 zcmeHPJ+9(N5cWzAtQFEacmV;yI(PvAD>)z_AaOuIK;R=RA=(IOPe4e>6<)|S2)PC! zSJ)}_w)(4|Zrl4tJCBj4^`?Kn?yBl?RsY!h@%Nv9{gP$b@BjXNi?ZLo{ryUb@!uDd zz<#ow-&bW*Htm*&dQ&#{)nRceVVrR^8)E8WL--Vf_q=^lolsV%2qKN*TID(pe> zUT@BIQGL9*P*msozTzXCYs8;xtgUja3u$aqHlaMr5P#fZ4W{c&=pl};H;BPB@({=K z8pI=&yj8sB|Hprp|If<*(hi^hkGC+hlqwt4zwP{7Ki9kk*T3QV|Cr&-WEtyCwVgTpt$u3AP{8qXd3d36Ry0p+B$3on-_1;w>zd^}(lX zSvOvKa!yGX9z7%>>kazcF8X=fOY7EJNI`mh&bjv@{qb4mxkn#11fOLj0q#ME;j`@H zjdM8^&(Yj7$+|xIar_{GKJt=n0=i58CDP@8G#+iBP2pHNO5kUZK$SPe$J_j!H2%KQ zfy#^ORC>?Cd2R~a#o<#c)AvtY`*h20Dyyv7g+qvwWxUF*B<4FAcJ@kH?O6j;f(l{Jn}W;vItY$DMYRjlBZ&eP{_)`D5m5yW;Co<54dWrqzIx1>LXX}Q zygi+ko$Kv<{(hU@rl*qRNH~pjbBp>*+{N0t*7|?MKV~#qI!Fm1o^xX7k`AcJ}k(VNxKm^YzleCXj_hUYuh%vwOtxU z^(pjiHU>-eErwow;0YjNDLsGpt;pEeJi~cwuBp^|PHezTL+73nROfjdzsvq5tEJo&q0m_AagT zel)v2=(%z9R5?mPQk$ytZBuPfe{B8Z>m%Q%kL3(s+Mxe9u1|ZMk5kh2m=J6E$@Noc zYo)CD>$?J}H@19;ll|m2rE^a|mo6sTN7j$lRkrQ&QW)b=0zZ=k_7m?_LacpfjAPu> zhNt+tjJ3z|Dv#~Q`ry%5hLK&W8=tmiX?#&xclrO)f z=*Ciu?~B(wV@Wsv`0VO*xBN8*OMWz-ZpM7znvGPB=}`i&Dginj?jJOl+JB_1*w@DE zw|~N3$feTsTe}mD;3X(Ohxe!N?i{x%(!C;nKjCNfI1|^Uz57NywztyO3Sch1~AN2iivi;a}XCP_wd7KMh=3}h0=|>5* zTaWWG;xFxUeO(5MD8=7w(emf@yf>ZWYbT;1wPM$Ji$lG3);KD$ZXdlANKfeg%DVrg znMs|0`aZ^Zl)&pspvq6>VL{&jNH6H#e@(4^``+L3_rtubhMeH@x5r%%_R}6K^`rWj zTX#YocfsrHyKIm1G1@PDd(PKWj3)zFPHE(x@9bntJC7dUd3b*p@9g+t@$r^Ay+nLb zrL4Ind;SGNOy<7ak`IpW+r?w;BZuKN1sw0sO@geeJiUHEy!MW+ALJ+W>{9sb=+p6T ze7F8#3%!1k`j6V*Zl(Tf`3*b4uyhEY0F;!Kw)}tpF91?D%zpt8o_&|1_y5agJJ*^a zXL8Js5_m-k^xyp#m30SeJ*TlUz5f_yKfWS9B|m)gk7T0#S6X8@-d;b&dyqS$^htv3 zN8Zd@b1yL=FYj?a$9e4CkuV-l{aDU%y?1G?Qxi@t&*6%KZjo%C$1x!%IUnY? z1tGmg!043LACw<`KhMAaAnix)^bDbNwRXArL^|XQBlVVaseNC(HM?}fk;0>PQajZC zdV{>XUL^hebOA@Q($@N2k6|HQnI)nA`WKrYdjF4p|Kz#my6{Bv7Gr*t zz^h82%5PJ;{gVElz6MNt|GsCyws*=he!d!i_Nm5_ZW_UBwg2Y!-<|#kD{$ON>m(__ zT}O;Vq(S2GUCEe-L+|)s@aV%O9)Cf`rXO4A8IbZrUahnopL;h8!OJurJt%K$ Date: Thu, 26 Sep 2024 02:39:16 -0230 Subject: [PATCH 06/13] improve parsing logic, add geo mesh override options --- game_patch/misc/dashoptions.cpp | 131 +++++++++++++++++++++++++++----- game_patch/misc/dashoptions.h | 8 ++ game_patch/misc/misc.cpp | 2 +- game_patch/os/commands.cpp | 1 + game_patch/rf/misc.h | 5 +- 5 files changed, 126 insertions(+), 21 deletions(-) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index ae66c401..8bf3ec9e 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -6,6 +6,7 @@ #include #include "../rf/file/file.h" #include "../rf/gr/gr.h" +#include "../rf/misc.h" #include #include #include @@ -75,14 +76,83 @@ CodeInjection fpgun_ar_ammo_digit_color_injection{ } }; +// set default geo mesh +CallHook default_geomod_shape_create_hook{ + 0x004374CF, + [](const char* filename) -> int { + std::string original_filename{filename}; + // set value, default to the original one if its somehow missing + std::string modded_filename = g_dash_options_config.geomodmesh_default.value_or(original_filename); + return default_geomod_shape_create_hook.call_target(modded_filename.c_str()); + } +}; + +// set driller double geo mesh +CallHook driller_double_geomod_shape_create_hook{ + 0x004374D9, [](const char* filename) -> int { + std::string original_filename{filename}; + // set value, default to the original one if its somehow missing + std::string modded_filename = g_dash_options_config.geomodmesh_driller_double.value_or(original_filename); + return driller_double_geomod_shape_create_hook.call_target(modded_filename.c_str()); + } +}; + +// set driller single geo mesh +CallHook driller_single_geomod_shape_create_hook{ + 0x004374E3, [](const char* filename) -> int { + std::string original_filename{filename}; + // set value, default to the original one if its somehow missing + std::string modded_filename = g_dash_options_config.geomodmesh_driller_single.value_or(original_filename); + return driller_single_geomod_shape_create_hook.call_target(modded_filename.c_str()); + } +}; + +// set apc geo mesh +CallHook apc_geomod_shape_create_hook{ + 0x004374ED, [](const char* filename) -> int { + std::string original_filename{filename}; + // set value, default to the original one if its somehow missing + std::string modded_filename = g_dash_options_config.geomodmesh_apc.value_or(original_filename); + return apc_geomod_shape_create_hook.call_target(modded_filename.c_str()); + } +}; + +void apply_geomod_mesh_patch() +{ + // array of geomod mesh options + std::array, 4> geomod_mesh_hooks = { + {{DashOptionID::GeomodMesh_Default, [] { default_geomod_shape_create_hook.install(); }}, + {DashOptionID::GeomodMesh_DrillerDouble, [] { driller_double_geomod_shape_create_hook.install(); }}, + {DashOptionID::GeomodMesh_DrillerSingle, [] { driller_single_geomod_shape_create_hook.install(); }}, + {DashOptionID::GeomodMesh_APC, [] { apc_geomod_shape_create_hook.install(); }}}}; + + bool any_option_loaded = false; + + // install only the hooks for the ones that were set + for (const auto& [option_id, install_fn] : geomod_mesh_hooks) { + if (g_dash_options_config.is_option_loaded(option_id)) { + install_fn(); + any_option_loaded = true; + } + } + + // if any one was set, apply the necessary patches + if (any_option_loaded) { + AsmWriter(0x00437543).call(0x004ECED0); // Replace the call to load v3d instead of embedded + rf::geomod_shape_init(); // Reinitialize geomod shapes + } +} + void apply_dashoptions_patches() { xlog::warn("Applying Dash Options patches"); disable_mod_playercfg_patch(); fpgun_ar_ammo_digit_color_injection.install(); + apply_geomod_mesh_patch(); + // dont init geo meshes at the start, thought was needed but not + //AsmWriter(0x004B229F).nop(5); } -// consolidated processing for options template void set_option(DashOptionID option_id, std::optional& option_field, const std::string& option_value) { @@ -127,13 +197,32 @@ void process_dashoption_line(const std::string& option_name, const std::string& xlog::warn("Found an option! Attempting to process {} with value {}", option_name, option_value); if (option_name == "$Scoreboard Logo") { - set_option(DashOptionID::ScoreboardLogo, g_dash_options_config.scoreboard_logo, option_value); + set_option(DashOptionID::ScoreboardLogo, + g_dash_options_config.scoreboard_logo, option_value); + } + else if (option_name == "$Default Geomod Mesh") { + set_option(DashOptionID::GeomodMesh_Default, + g_dash_options_config.geomodmesh_default, option_value); + } + else if (option_name == "$Driller Double Geomod Mesh") { + set_option(DashOptionID::GeomodMesh_DrillerDouble, + g_dash_options_config.geomodmesh_driller_double, option_value); } - if (option_name == "$Use Base Game Players Config") { - set_option(DashOptionID::UseStockPlayersConfig, g_dash_options_config.use_stock_game_players_config, option_value); + else if (option_name == "$Driller Single Geomod Mesh") { + set_option(DashOptionID::GeomodMesh_DrillerSingle, + g_dash_options_config.geomodmesh_driller_single, option_value); } - if (option_name == "$Assault Rifle Ammo Counter Color") { - set_option(DashOptionID::AssaultRifleAmmoColor, g_dash_options_config.ar_ammo_color, option_value); + else if (option_name == "$APC Geomod Mesh") { + set_option(DashOptionID::GeomodMesh_APC, + g_dash_options_config.geomodmesh_apc, option_value); + } + else if (option_name == "$Use Base Game Players Config") { + set_option(DashOptionID::UseStockPlayersConfig, + g_dash_options_config.use_stock_game_players_config, option_value); + } + else if (option_name == "$Assault Rifle Ammo Counter Color") { + set_option(DashOptionID::AssaultRifleAmmoColor, + g_dash_options_config.ar_ammo_color, option_value); } else { xlog::warn("Ignoring unsupported option: {}", option_name); @@ -165,24 +254,28 @@ void close_file() void parse() { - std::string line, full_line; - char buffer[256]; + std::string line; bool in_options_section = false; // track section, eventually this should be enum and support multiple sections xlog::warn("Start parsing dashoptions.tbl"); - while (int bytes_read = dashoptions_file->read(buffer, sizeof(buffer) - 1)) { + while (true) { + std::string buffer(2048, '\0'); // handle lines up to 2048 bytes, should be plenty + int bytes_read = dashoptions_file->read(&buffer[0], buffer.size() - 1); + if (bytes_read <= 0) { xlog::warn("End of file or read error in dashoptions.tbl."); break; } - buffer[bytes_read] = '\0'; + buffer.resize(bytes_read); // trim unused space if fewer bytes were read std::istringstream file_stream(buffer); + while (std::getline(file_stream, line)) { line = trim(line); xlog::warn("Parsing line: '{}'", line); + // could be expanded to support multiple sections if (line == "#General") { xlog::warn("Entering General section"); in_options_section = true; @@ -191,28 +284,29 @@ void parse() else if (line == "#End") { xlog::warn("Exiting General section"); in_options_section = false; - break; // stop parsing after reaching the end + break; // stop, reached the end of section } - // do not parse anything outside the options section + // skip anything outside the options section if (!in_options_section) { xlog::warn("Skipping line outside of General section"); continue; } - // Skip empty lines and comments + // skip empty lines and comments if (line.empty() || line.find("//") == 0) { - xlog::warn("Skipping line because it's empty or a comment"); + xlog::warn("Skipping empty or comment line"); continue; } - // Parse options - auto delimiter_pos = line.find(':'); - if (delimiter_pos == std::string::npos) { - xlog::warn("Skipping malformed line: {}", line); + // valid option lines start with $ and contain delimiter : + if (line[0] != '$' || line.find(':') == std::string::npos) { + xlog::warn("Skipping malformed line: '{}'", line); continue; } + // parse valid options lines + auto delimiter_pos = line.find(':'); std::string option_name = trim(line.substr(0, delimiter_pos)); std::string option_value = trim(line.substr(delimiter_pos + 1)); @@ -221,7 +315,6 @@ void parse() } } - void load_dashoptions_config() { xlog::warn("Mod launched, attempting to load Dash Options configuration"); diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index 41a7d734..f7c33256 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -8,6 +8,10 @@ enum class DashOptionID { ScoreboardLogo, + GeomodMesh_Default, + GeomodMesh_DrillerDouble, + GeomodMesh_DrillerSingle, + GeomodMesh_APC, UseStockPlayersConfig, AssaultRifleAmmoColor, _optioncount // dummy option for determining total num of options @@ -27,6 +31,10 @@ struct DashOptionsConfig //std::optional float_something; // template for float //std::optional int_something; // template for int std::optional scoreboard_logo; + std::optional geomodmesh_default; + std::optional geomodmesh_driller_double; + std::optional geomodmesh_driller_single; + std::optional geomodmesh_apc; std::optional use_stock_game_players_config; std::optional ar_ammo_color; diff --git a/game_patch/misc/misc.cpp b/game_patch/misc/misc.cpp index 956928a5..b21063bf 100644 --- a/game_patch/misc/misc.cpp +++ b/game_patch/misc/misc.cpp @@ -409,7 +409,7 @@ CodeInjection all_table_files_loaded_injection{ // after all other tbl files have been loaded, load dashoptions.tbl and parse it // only if a TC mod is loaded if (rf::mod_param.found()) { - dashopt::load_dashoptions_config(); + dashopt::load_dashoptions_config(); } } }; diff --git a/game_patch/os/commands.cpp b/game_patch/os/commands.cpp index 5bfdf978..6e0afdf4 100644 --- a/game_patch/os/commands.cpp +++ b/game_patch/os/commands.cpp @@ -129,6 +129,7 @@ void console_commands_init() register_builtin_command("system_info", "Show system information", 0x00525A60); register_builtin_command("trilinear_filtering", "Toggle trilinear filtering", 0x0054F050); register_builtin_command("detail_textures", "Toggle detail textures", 0x0054F0B0); + //register_builtin_command("drop_entity", "Drop any entity", 0x00418740); // for testing, will remove #ifdef DEBUG register_builtin_command("drop_fixed_cam", "Drop a fixed camera", 0x0040D220); diff --git a/game_patch/rf/misc.h b/game_patch/rf/misc.h index 6472ac7f..f30e072b 100644 --- a/game_patch/rf/misc.h +++ b/game_patch/rf/misc.h @@ -10,4 +10,7 @@ namespace rf static auto& default_player_weapon = addr_as_ref(0x007C7600); static auto& get_file_checksum = addr_as_ref(0x00436630); -} + static auto& geomod_shape_init = addr_as_ref(0x004374C0); + static auto& geomod_shape_create = addr_as_ref(0x00437500); + static auto& geomod_shape_shutdown = addr_as_ref(0x00437460); + } From 0ec84924bcbd4c3d2aff2c1a646d16e5b394f0d9 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 26 Sep 2024 13:05:05 -0230 Subject: [PATCH 07/13] refactor some code to remove duplicate logic, add support for configuring geo smoke emitters, ice geo crater texture, setting training level filename, and disabling menu buttons in mods that dont support mp/sp --- game_patch/misc/dashoptions.cpp | 209 +++++++++++++++++++++++++------- game_patch/misc/dashoptions.h | 12 ++ 2 files changed, 177 insertions(+), 44 deletions(-) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index 8bf3ec9e..4afd7385 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -6,6 +6,7 @@ #include #include "../rf/file/file.h" #include "../rf/gr/gr.h" +#include "../rf/geometry.h" #include "../rf/misc.h" #include #include @@ -52,68 +53,56 @@ std::optional extract_quoted_value(const std::string& value) return trimmed_value; } -void disable_mod_playercfg_patch() -{ - if (g_dash_options_config.use_stock_game_players_config) { - // set mod to not make its own players.cfg but instead use the stock game one - AsmWriter(0x004A8F99).jmp(0x004A9010); - AsmWriter(0x004A8DCC).jmp(0x004A8E53); - } -} - CodeInjection fpgun_ar_ammo_digit_color_injection{ 0x004ABC03, [](auto& regs) { - if (g_dash_options_config.is_option_loaded(DashOptionID::AssaultRifleAmmoColor)) { - uint32_t color = g_dash_options_config.ar_ammo_color.value(); - rf::gr::set_color((color >> 24) & 0xFF, // r - (color >> 16) & 0xFF, // g - (color >> 8) & 0xFF, // b - color & 0xFF // a - ); - } + uint32_t color = g_dash_options_config.ar_ammo_color.value(); + rf::gr::set_color((color >> 24) & 0xFF, // r + (color >> 16) & 0xFF, // g + (color >> 8) & 0xFF, // b + color & 0xFF); // a regs.eip = 0x004ABC08; } }; -// set default geo mesh +// consolidated logic for handling geo mesh changes +int handle_geomod_shape_create(const char* filename, const std::optional& config_value, + CallHook& hook) +{ + std::string original_filename{filename}; + std::string modded_filename = config_value.value_or(original_filename); + return hook.call_target(modded_filename.c_str()); +} + +// Set default geo mesh CallHook default_geomod_shape_create_hook{ - 0x004374CF, - [](const char* filename) -> int { - std::string original_filename{filename}; - // set value, default to the original one if its somehow missing - std::string modded_filename = g_dash_options_config.geomodmesh_default.value_or(original_filename); - return default_geomod_shape_create_hook.call_target(modded_filename.c_str()); + 0x004374CF, [](const char* filename) -> int { + return handle_geomod_shape_create(filename, + g_dash_options_config.geomodmesh_default, default_geomod_shape_create_hook); } }; -// set driller double geo mesh +// Set driller double geo mesh CallHook driller_double_geomod_shape_create_hook{ 0x004374D9, [](const char* filename) -> int { - std::string original_filename{filename}; - // set value, default to the original one if its somehow missing - std::string modded_filename = g_dash_options_config.geomodmesh_driller_double.value_or(original_filename); - return driller_double_geomod_shape_create_hook.call_target(modded_filename.c_str()); + return handle_geomod_shape_create(filename, + g_dash_options_config.geomodmesh_driller_double, driller_double_geomod_shape_create_hook); } }; -// set driller single geo mesh +// Set driller single geo mesh CallHook driller_single_geomod_shape_create_hook{ 0x004374E3, [](const char* filename) -> int { - std::string original_filename{filename}; - // set value, default to the original one if its somehow missing - std::string modded_filename = g_dash_options_config.geomodmesh_driller_single.value_or(original_filename); - return driller_single_geomod_shape_create_hook.call_target(modded_filename.c_str()); + return handle_geomod_shape_create(filename, + g_dash_options_config.geomodmesh_driller_single, driller_single_geomod_shape_create_hook); } }; -// set apc geo mesh +// Set apc geo mesh CallHook apc_geomod_shape_create_hook{ 0x004374ED, [](const char* filename) -> int { - std::string original_filename{filename}; - // set value, default to the original one if its somehow missing - std::string modded_filename = g_dash_options_config.geomodmesh_apc.value_or(original_filename); - return apc_geomod_shape_create_hook.call_target(modded_filename.c_str()); + return handle_geomod_shape_create(filename, + g_dash_options_config.geomodmesh_apc, apc_geomod_shape_create_hook); } }; @@ -124,7 +113,8 @@ void apply_geomod_mesh_patch() {{DashOptionID::GeomodMesh_Default, [] { default_geomod_shape_create_hook.install(); }}, {DashOptionID::GeomodMesh_DrillerDouble, [] { driller_double_geomod_shape_create_hook.install(); }}, {DashOptionID::GeomodMesh_DrillerSingle, [] { driller_single_geomod_shape_create_hook.install(); }}, - {DashOptionID::GeomodMesh_APC, [] { apc_geomod_shape_create_hook.install(); }}}}; + {DashOptionID::GeomodMesh_APC, [] { apc_geomod_shape_create_hook.install(); }} + }}; bool any_option_loaded = false; @@ -143,14 +133,121 @@ void apply_geomod_mesh_patch() } } +// consolidated logic for handling geomod smoke emitter overrides +int handle_geomod_emitter_change(const char* emitter_name, + const std::optional& new_emitter_name_opt, CallHook& hook) { + std::string original_emitter_name{emitter_name}; + std::string new_emitter_name = new_emitter_name_opt.value_or(original_emitter_name); + return hook.call_target(new_emitter_name.c_str()); +} + +// Override default geomod smoke emitter +CallHook default_geomod_emitter_get_index_hook{ + 0x00437150, [](const char* emitter_name) -> int { + return handle_geomod_emitter_change(emitter_name, + g_dash_options_config.geomodemitter_default, default_geomod_emitter_get_index_hook); + } +}; + +// Override driller geomod smoke emitter +CallHook driller_geomod_emitter_get_index_hook{ + 0x0043715F, [](const char* emitter_name) -> int { + return handle_geomod_emitter_change(emitter_name, + g_dash_options_config.geomodemitter_driller, driller_geomod_emitter_get_index_hook); + } +}; + +// geomod crater texture PPM broken currently and disabled, need to fix +CallHook geomod_set_autotexture_ppm_hook{ + 0x00466BD4, + [](rf::GSolid* this_ptr, float ppm) { + xlog::info("Original PPM: {}", ppm); + + float custom_ppm = 24.0f; + xlog::info("Modified PPM: {}", custom_ppm); + + geomod_set_autotexture_ppm_hook.call_target(this_ptr, custom_ppm); + } +}; + +// replace ice geomod region texture +CallHook ice_geo_crater_bm_load_hook { + { + 0x004673B5, // chunks + 0x00466BEF, // crater + }, + [](const char* filename, int path_id, bool generate_mipmaps) -> int { + std::string original_filename{filename}; + std::string new_filename = g_dash_options_config.geomodtexture_ice.value_or(original_filename); + return ice_geo_crater_bm_load_hook.call_target(new_filename.c_str(), path_id, generate_mipmaps); + } +}; + +// replace training level filename for use from new game menu +CallHook training_load_level_hook{ + 0x00443A85, + [](const char* level_name) { + std::string original_level_name{level_name}; + std::string new_level_name = g_dash_options_config.training_level_filename.value_or(original_level_name); + training_load_level_hook.call_target(new_level_name.c_str()); + }}; + void apply_dashoptions_patches() { xlog::warn("Applying Dash Options patches"); - disable_mod_playercfg_patch(); - fpgun_ar_ammo_digit_color_injection.install(); + // avoid unnecessary hooks by hooking only if corresponding options are specified + + // apply UseStockPlayersConfig + if (g_dash_options_config.is_option_loaded(DashOptionID::UseStockPlayersConfig) && + g_dash_options_config.use_stock_game_players_config) { + // set mod to not make its own players.cfg but instead use the stock game one + AsmWriter(0x004A8F99).jmp(0x004A9010); + AsmWriter(0x004A8DCC).jmp(0x004A8E53); + } + + // apply AR ammo counter coloring + if (g_dash_options_config.is_option_loaded(DashOptionID::AssaultRifleAmmoColor)) { + fpgun_ar_ammo_digit_color_injection.install(); + } + + // whether should apply is determined in helper function apply_geomod_mesh_patch(); - // dont init geo meshes at the start, thought was needed but not - //AsmWriter(0x004B229F).nop(5); + + // apply default geomod smoke emitter + if (g_dash_options_config.is_option_loaded(DashOptionID::GeomodEmitter_Default)) { + default_geomod_emitter_get_index_hook.install(); + } + + // apply driller geomod smoke emitter + if (g_dash_options_config.is_option_loaded(DashOptionID::GeomodEmitter_Driller)) { + driller_geomod_emitter_get_index_hook.install(); + } + + // broken currently, need to fix + //geomod_set_autotexture_ppm_hook.install(); + + if (g_dash_options_config.is_option_loaded(DashOptionID::GeomodTexture_Ice)) { + ice_geo_crater_bm_load_hook.install(); + } + + // training level filename from new game menu + if (g_dash_options_config.is_option_loaded(DashOptionID::TrainingLevelFilename)) { + training_load_level_hook.install(); + } + + // disable multiplayer button + if (g_dash_options_config.is_option_loaded(DashOptionID::DisableMultiplayerButton) && + g_dash_options_config.disable_multiplayer_button) { + AsmWriter(0x0044391F).nop(5); // multi + } + + // disable single player buttons + if (g_dash_options_config.is_option_loaded(DashOptionID::DisableSingleplayerButtons) && + g_dash_options_config.disable_singleplayer_buttons) { + AsmWriter(0x00443906).nop(5); // save + AsmWriter(0x004438ED).nop(5); // load + AsmWriter(0x004438D4).nop(5); // new game + } } template @@ -216,6 +313,30 @@ void process_dashoption_line(const std::string& option_name, const std::string& set_option(DashOptionID::GeomodMesh_APC, g_dash_options_config.geomodmesh_apc, option_value); } + else if (option_name == "$Default Geomod Smoke Emitter") { + set_option(DashOptionID::GeomodEmitter_Default, + g_dash_options_config.geomodemitter_default, option_value); + } + else if (option_name == "$Driller Geomod Smoke Emitter") { + set_option(DashOptionID::GeomodEmitter_Driller, + g_dash_options_config.geomodemitter_driller, option_value); + } + else if (option_name == "$Ice Geomod Texture") { + set_option(DashOptionID::GeomodTexture_Ice, + g_dash_options_config.geomodtexture_ice, option_value); + } + else if (option_name == "$Training Level Filename") { + set_option(DashOptionID::TrainingLevelFilename, + g_dash_options_config.training_level_filename, option_value); + } + else if (option_name == "$Disable Multiplayer Button") { + set_option(DashOptionID::DisableMultiplayerButton, + g_dash_options_config.disable_multiplayer_button, option_value); + } + else if (option_name == "$Disable Singleplayer Buttons") { + set_option(DashOptionID::DisableSingleplayerButtons, + g_dash_options_config.disable_singleplayer_buttons, option_value); + } else if (option_name == "$Use Base Game Players Config") { set_option(DashOptionID::UseStockPlayersConfig, g_dash_options_config.use_stock_game_players_config, option_value); diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index f7c33256..de857f2d 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -12,6 +12,12 @@ enum class DashOptionID GeomodMesh_DrillerDouble, GeomodMesh_DrillerSingle, GeomodMesh_APC, + GeomodEmitter_Default, + GeomodEmitter_Driller, + GeomodTexture_Ice, + TrainingLevelFilename, + DisableMultiplayerButton, + DisableSingleplayerButtons, UseStockPlayersConfig, AssaultRifleAmmoColor, _optioncount // dummy option for determining total num of options @@ -35,6 +41,12 @@ struct DashOptionsConfig std::optional geomodmesh_driller_double; std::optional geomodmesh_driller_single; std::optional geomodmesh_apc; + std::optional geomodemitter_default; + std::optional geomodemitter_driller; + std::optional geomodtexture_ice; + std::optional training_level_filename; + std::optional disable_multiplayer_button; + std::optional disable_singleplayer_buttons; std::optional use_stock_game_players_config; std::optional ar_ammo_color; From 7d48681e4ada51891c4fdea2f9b85e35ba7f5355 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 26 Sep 2024 16:16:44 -0230 Subject: [PATCH 08/13] fix bool parsing error, add $First Level Filename option --- game_patch/misc/dashoptions.cpp | 32 ++++++++++++++++++++++++-------- game_patch/misc/dashoptions.h | 2 ++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index 4afd7385..9f1fd08d 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -183,6 +183,15 @@ CallHook ice_geo_crater_bm_load_hook { } }; +// replace first level filename for use from new game menu +CallHook first_load_level_hook{ + 0x00443B15, [](const char* level_name) { + std::string original_level_name{level_name}; + std::string new_level_name = g_dash_options_config.first_level_filename.value_or(original_level_name); + first_load_level_hook.call_target(new_level_name.c_str()); + } +}; + // replace training level filename for use from new game menu CallHook training_load_level_hook{ 0x00443A85, @@ -190,7 +199,8 @@ CallHook training_load_level_hook{ std::string original_level_name{level_name}; std::string new_level_name = g_dash_options_config.training_level_filename.value_or(original_level_name); training_load_level_hook.call_target(new_level_name.c_str()); - }}; + } +}; void apply_dashoptions_patches() { @@ -198,8 +208,7 @@ void apply_dashoptions_patches() // avoid unnecessary hooks by hooking only if corresponding options are specified // apply UseStockPlayersConfig - if (g_dash_options_config.is_option_loaded(DashOptionID::UseStockPlayersConfig) && - g_dash_options_config.use_stock_game_players_config) { + if (g_dash_options_config.use_stock_game_players_config.value_or(false)) { // set mod to not make its own players.cfg but instead use the stock game one AsmWriter(0x004A8F99).jmp(0x004A9010); AsmWriter(0x004A8DCC).jmp(0x004A8E53); @@ -230,20 +239,23 @@ void apply_dashoptions_patches() ice_geo_crater_bm_load_hook.install(); } + // first level filename from new game menu + if (g_dash_options_config.is_option_loaded(DashOptionID::FirstLevelFilename)) { + first_load_level_hook.install(); + } + // training level filename from new game menu if (g_dash_options_config.is_option_loaded(DashOptionID::TrainingLevelFilename)) { training_load_level_hook.install(); } // disable multiplayer button - if (g_dash_options_config.is_option_loaded(DashOptionID::DisableMultiplayerButton) && - g_dash_options_config.disable_multiplayer_button) { + if (g_dash_options_config.disable_multiplayer_button.value_or(false)) { AsmWriter(0x0044391F).nop(5); // multi } - // disable single player buttons - if (g_dash_options_config.is_option_loaded(DashOptionID::DisableSingleplayerButtons) && - g_dash_options_config.disable_singleplayer_buttons) { + // disable singleplayer buttons + if (g_dash_options_config.disable_singleplayer_buttons.value_or(false)) { AsmWriter(0x00443906).nop(5); // save AsmWriter(0x004438ED).nop(5); // load AsmWriter(0x004438D4).nop(5); // new game @@ -325,6 +337,10 @@ void process_dashoption_line(const std::string& option_name, const std::string& set_option(DashOptionID::GeomodTexture_Ice, g_dash_options_config.geomodtexture_ice, option_value); } + else if (option_name == "$First Level Filename") { + set_option(DashOptionID::FirstLevelFilename, + g_dash_options_config.first_level_filename, option_value); + } else if (option_name == "$Training Level Filename") { set_option(DashOptionID::TrainingLevelFilename, g_dash_options_config.training_level_filename, option_value); diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index de857f2d..3884ffcd 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -15,6 +15,7 @@ enum class DashOptionID GeomodEmitter_Default, GeomodEmitter_Driller, GeomodTexture_Ice, + FirstLevelFilename, TrainingLevelFilename, DisableMultiplayerButton, DisableSingleplayerButtons, @@ -44,6 +45,7 @@ struct DashOptionsConfig std::optional geomodemitter_default; std::optional geomodemitter_driller; std::optional geomodtexture_ice; + std::optional first_level_filename; std::optional training_level_filename; std::optional disable_multiplayer_button; std::optional disable_singleplayer_buttons; From 7f053412933eea9950d3635a1735e2bbe54da2e4 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 26 Sep 2024 20:21:27 -0230 Subject: [PATCH 09/13] add customization of Summoner Trailer button, consolidate and refactor some code --- game_patch/misc/dashoptions.cpp | 117 ++++++++++++++++++++++++++++---- game_patch/misc/dashoptions.h | 15 +++- game_patch/rf/misc.h | 1 + 3 files changed, 119 insertions(+), 14 deletions(-) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index 9f1fd08d..24ae45fe 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -6,11 +6,16 @@ #include #include "../rf/file/file.h" #include "../rf/gr/gr.h" +#include "../rf/sound/sound.h" #include "../rf/geometry.h" #include "../rf/misc.h" #include #include #include +#include +#include +#include +#include #include DashOptionsConfig g_dash_options_config; @@ -53,6 +58,24 @@ std::optional extract_quoted_value(const std::string& value) return trimmed_value; } +void open_url(const std::string& url) +{ + try { + if (url.empty()) { + xlog::error("URL is empty"); + return; + } + xlog::info("Opening URL: {}", url); + HINSTANCE result = ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOW); + if (reinterpret_cast(result) <= 32) { + xlog::error("Failed to open URL. Error code: {}", reinterpret_cast(result)); + } + } + catch (const std::exception& ex) { + xlog::error("Exception occurred while trying to open URL: {}", ex.what()); + } +} + CodeInjection fpgun_ar_ammo_digit_color_injection{ 0x004ABC03, [](auto& regs) { @@ -183,25 +206,70 @@ CallHook ice_geo_crater_bm_load_hook { } }; -// replace first level filename for use from new game menu +// Consolidated logic for handling level filename overrides +inline void handle_level_name_change(const char* level_name, + const std::optional& new_level_name_opt, CallHook& hook) { + std::string new_level_name = new_level_name_opt.value_or(level_name); + hook.call_target(new_level_name.c_str()); +} + +// Override first level filename for new game menu CallHook first_load_level_hook{ 0x00443B15, [](const char* level_name) { - std::string original_level_name{level_name}; - std::string new_level_name = g_dash_options_config.first_level_filename.value_or(original_level_name); - first_load_level_hook.call_target(new_level_name.c_str()); + handle_level_name_change(level_name, g_dash_options_config.first_level_filename, first_load_level_hook); } }; -// replace training level filename for use from new game menu +// Override training level filename for new game menu CallHook training_load_level_hook{ - 0x00443A85, - [](const char* level_name) { - std::string original_level_name{level_name}; - std::string new_level_name = g_dash_options_config.training_level_filename.value_or(original_level_name); - training_load_level_hook.call_target(new_level_name.c_str()); + 0x00443A85, [](const char* level_name) { + handle_level_name_change(level_name, g_dash_options_config.training_level_filename, training_load_level_hook); } }; +// Implement demo_extras_summoner_trailer_click using FunHook +FunHook extras_summoner_trailer_click_hook{ + 0x0043EC80, [](int x, int y) { + xlog::warn("Summoner trailer button clicked"); + int action = g_dash_options_config.sumtrailer_button_action.value_or(0); + switch (action) { + case 1: + // open URL + if (g_dash_options_config.sumtrailer_button_url) { + const std::string& url = *g_dash_options_config.sumtrailer_button_url; + open_url(url); + } + break; + case 2: + // disable button + break; + default: + // play bink video, is case 0 but also default + std::string trailer_path = g_dash_options_config.sumtrailer_button_bik_filename.value_or("sumtrailer.bik"); + xlog::warn("Playing BIK file: {}", trailer_path); + rf::snd_pause(true); + rf::bink_play(trailer_path.c_str()); + rf::snd_pause(false); + break; + } + } +}; + +void handle_summoner_trailer_button() +{ + if (int action = g_dash_options_config.sumtrailer_button_action.value_or(-1); action != -1) { + xlog::warn("Action ID: {}", g_dash_options_config.sumtrailer_button_action.value_or(-1)); + if (action == 3) { + // action 3 means remove the button + AsmWriter(0x0043EE14).nop(5); + } + else { + // otherwise, install the hook + extras_summoner_trailer_click_hook.install(); + } + } +} + void apply_dashoptions_patches() { xlog::warn("Applying Dash Options patches"); @@ -260,6 +328,11 @@ void apply_dashoptions_patches() AsmWriter(0x004438ED).nop(5); // load AsmWriter(0x004438D4).nop(5); // new game } + + // customize behaviour of Summoner Trailer button + if (g_dash_options_config.is_option_loaded(DashOptionID::SumTrailerButtonAction)) { + handle_summoner_trailer_button(); + } } template @@ -305,6 +378,7 @@ void process_dashoption_line(const std::string& option_name, const std::string& { xlog::warn("Found an option! Attempting to process {} with value {}", option_name, option_value); + //core options if (option_name == "$Scoreboard Logo") { set_option(DashOptionID::ScoreboardLogo, g_dash_options_config.scoreboard_logo, option_value); @@ -361,6 +435,25 @@ void process_dashoption_line(const std::string& option_name, const std::string& set_option(DashOptionID::AssaultRifleAmmoColor, g_dash_options_config.ar_ammo_color, option_value); } + else if (option_name == "$Summoner Trailer Button Action") { + set_option(DashOptionID::SumTrailerButtonAction, + g_dash_options_config.sumtrailer_button_action, option_value); + // 0 = play_bik (default), 1 = open_url, 2 = disable, 3 = remove + } + + //extended options + else if (option_name == "+Summoner Trailer Button URL" && + g_dash_options_config.is_option_loaded(DashOptionID::SumTrailerButtonAction)) { + set_option(DashOptionID::SumTrailerButtonURL, + g_dash_options_config.sumtrailer_button_url, option_value); + } + else if (option_name == "+Summoner Trailer Button Bink Filename" && + g_dash_options_config.is_option_loaded(DashOptionID::SumTrailerButtonAction)) { + set_option(DashOptionID::SumTrailerButtonBikFile, + g_dash_options_config.sumtrailer_button_bik_filename, option_value); + } + + //unknown option else { xlog::warn("Ignoring unsupported option: {}", option_name); } @@ -436,8 +529,8 @@ void parse() continue; } - // valid option lines start with $ and contain delimiter : - if (line[0] != '$' || line.find(':') == std::string::npos) { + // valid option lines start with $ or + and contain a delimiter : + if ((line[0] != '$' && line[0] != '+') || line.find(':') == std::string::npos) { xlog::warn("Skipping malformed line: '{}'", line); continue; } diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index 3884ffcd..15bb5586 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -21,6 +21,9 @@ enum class DashOptionID DisableSingleplayerButtons, UseStockPlayersConfig, AssaultRifleAmmoColor, + SumTrailerButtonAction, + SumTrailerButtonURL, + SumTrailerButtonBikFile, _optioncount // dummy option for determining total num of options }; @@ -37,6 +40,8 @@ struct DashOptionsConfig { //std::optional float_something; // template for float //std::optional int_something; // template for int + + //core options std::optional scoreboard_logo; std::optional geomodmesh_default; std::optional geomodmesh_driller_double; @@ -51,11 +56,17 @@ struct DashOptionsConfig std::optional disable_singleplayer_buttons; std::optional use_stock_game_players_config; std::optional ar_ammo_color; + std::optional sumtrailer_button_action; + + //extended options + //from sumtrailer_button_action + std::optional sumtrailer_button_url; + std::optional sumtrailer_button_bik_filename; - // track options that are loaded + // track core options that are loaded std::array options_loaded = {}; - // check if specific option is loaded + // check if specific core option is loaded bool is_option_loaded(DashOptionID option_id) const { return options_loaded[to_index(option_id)]; diff --git a/game_patch/rf/misc.h b/game_patch/rf/misc.h index f30e072b..f50d011a 100644 --- a/game_patch/rf/misc.h +++ b/game_patch/rf/misc.h @@ -13,4 +13,5 @@ namespace rf static auto& geomod_shape_init = addr_as_ref(0x004374C0); static auto& geomod_shape_create = addr_as_ref(0x00437500); static auto& geomod_shape_shutdown = addr_as_ref(0x00437460); + static auto& bink_play = addr_as_ref(0x00520A90); } From 86c205fb6f25f2a38d12d7ed17a8079d7f4d6333 Mon Sep 17 00:00:00 2001 From: Goober Date: Fri, 27 Sep 2024 02:28:30 -0230 Subject: [PATCH 10/13] optimize and streamline processing logic --- game_patch/misc/dashoptions.cpp | 176 ++++++++++++++++---------------- 1 file changed, 86 insertions(+), 90 deletions(-) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index 24ae45fe..eb0ff3e4 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -340,19 +341,12 @@ void set_option(DashOptionID option_id, std::optional& option_field, const st { try { if constexpr (std::is_same_v) { - // strip quotes for strings - if (auto quoted_value = extract_quoted_value(option_value)) { - option_field = quoted_value.value(); - xlog::warn("Successfully extracted string: {}", quoted_value.value()); - } - else { - xlog::warn("Invalid string format: {}", option_value); - return; - } + // extract quoted string + option_field = extract_quoted_value(option_value).value_or(option_value); } - else if constexpr (std::is_same_v) { // handle color values - option_field = static_cast( - std::stoul(extract_quoted_value(option_value).value_or(option_value), nullptr, 16)); + else if constexpr (std::is_same_v) { + // parse hex color values + option_field = std::stoul(option_value, nullptr, 0); } else if constexpr (std::is_same_v) { option_field = std::stof(option_value); @@ -360,10 +354,12 @@ void set_option(DashOptionID option_id, std::optional& option_field, const st else if constexpr (std::is_same_v) { option_field = std::stoi(option_value); } - else if constexpr (std::is_same_v) { // handle bools with every reasonable capitalization + else if constexpr (std::is_same_v) { + // accept every reasonable representation of "true" for a boolean option_field = (option_value == "1" || option_value == "true" || option_value == "True" || option_value == "TRUE"); } + // mark option as loaded mark_option_loaded(option_id); xlog::warn("Parsed value has been saved: {}", option_field.value()); @@ -376,84 +372,84 @@ void set_option(DashOptionID option_id, std::optional& option_field, const st // identify custom options and parse where found void process_dashoption_line(const std::string& option_name, const std::string& option_value) { - xlog::warn("Found an option! Attempting to process {} with value {}", option_name, option_value); - - //core options - if (option_name == "$Scoreboard Logo") { - set_option(DashOptionID::ScoreboardLogo, - g_dash_options_config.scoreboard_logo, option_value); - } - else if (option_name == "$Default Geomod Mesh") { - set_option(DashOptionID::GeomodMesh_Default, - g_dash_options_config.geomodmesh_default, option_value); - } - else if (option_name == "$Driller Double Geomod Mesh") { - set_option(DashOptionID::GeomodMesh_DrillerDouble, - g_dash_options_config.geomodmesh_driller_double, option_value); - } - else if (option_name == "$Driller Single Geomod Mesh") { - set_option(DashOptionID::GeomodMesh_DrillerSingle, - g_dash_options_config.geomodmesh_driller_single, option_value); - } - else if (option_name == "$APC Geomod Mesh") { - set_option(DashOptionID::GeomodMesh_APC, - g_dash_options_config.geomodmesh_apc, option_value); - } - else if (option_name == "$Default Geomod Smoke Emitter") { - set_option(DashOptionID::GeomodEmitter_Default, - g_dash_options_config.geomodemitter_default, option_value); - } - else if (option_name == "$Driller Geomod Smoke Emitter") { - set_option(DashOptionID::GeomodEmitter_Driller, - g_dash_options_config.geomodemitter_driller, option_value); - } - else if (option_name == "$Ice Geomod Texture") { - set_option(DashOptionID::GeomodTexture_Ice, - g_dash_options_config.geomodtexture_ice, option_value); - } - else if (option_name == "$First Level Filename") { - set_option(DashOptionID::FirstLevelFilename, - g_dash_options_config.first_level_filename, option_value); - } - else if (option_name == "$Training Level Filename") { - set_option(DashOptionID::TrainingLevelFilename, - g_dash_options_config.training_level_filename, option_value); - } - else if (option_name == "$Disable Multiplayer Button") { - set_option(DashOptionID::DisableMultiplayerButton, - g_dash_options_config.disable_multiplayer_button, option_value); - } - else if (option_name == "$Disable Singleplayer Buttons") { - set_option(DashOptionID::DisableSingleplayerButtons, - g_dash_options_config.disable_singleplayer_buttons, option_value); - } - else if (option_name == "$Use Base Game Players Config") { - set_option(DashOptionID::UseStockPlayersConfig, - g_dash_options_config.use_stock_game_players_config, option_value); - } - else if (option_name == "$Assault Rifle Ammo Counter Color") { - set_option(DashOptionID::AssaultRifleAmmoColor, - g_dash_options_config.ar_ammo_color, option_value); - } - else if (option_name == "$Summoner Trailer Button Action") { - set_option(DashOptionID::SumTrailerButtonAction, - g_dash_options_config.sumtrailer_button_action, option_value); - // 0 = play_bik (default), 1 = open_url, 2 = disable, 3 = remove - } - - //extended options - else if (option_name == "+Summoner Trailer Button URL" && - g_dash_options_config.is_option_loaded(DashOptionID::SumTrailerButtonAction)) { - set_option(DashOptionID::SumTrailerButtonURL, - g_dash_options_config.sumtrailer_button_url, option_value); - } - else if (option_name == "+Summoner Trailer Button Bink Filename" && - g_dash_options_config.is_option_loaded(DashOptionID::SumTrailerButtonAction)) { - set_option(DashOptionID::SumTrailerButtonBikFile, - g_dash_options_config.sumtrailer_button_bik_filename, option_value); + static const std::unordered_map option_map = { + {"$Scoreboard Logo", DashOptionID::ScoreboardLogo}, + {"$Default Geomod Mesh", DashOptionID::GeomodMesh_Default}, + {"$Driller Double Geomod Mesh", DashOptionID::GeomodMesh_DrillerDouble}, + {"$Driller Single Geomod Mesh", DashOptionID::GeomodMesh_DrillerSingle}, + {"$APC Geomod Mesh", DashOptionID::GeomodMesh_APC}, + {"$Default Geomod Smoke Emitter", DashOptionID::GeomodEmitter_Default}, + {"$Driller Geomod Smoke Emitter", DashOptionID::GeomodEmitter_Driller}, + {"$Ice Geomod Texture", DashOptionID::GeomodTexture_Ice}, + {"$First Level Filename", DashOptionID::FirstLevelFilename}, + {"$Training Level Filename", DashOptionID::TrainingLevelFilename}, + {"$Disable Multiplayer Button", DashOptionID::DisableMultiplayerButton}, + {"$Disable Singleplayer Buttons", DashOptionID::DisableSingleplayerButtons}, + {"$Use Base Game Players Config", DashOptionID::UseStockPlayersConfig}, + {"$Assault Rifle Ammo Counter Color", DashOptionID::AssaultRifleAmmoColor}, + {"$Summoner Trailer Button Action", DashOptionID::SumTrailerButtonAction}, + {"+Summoner Trailer Button URL", DashOptionID::SumTrailerButtonURL}, + {"+Summoner Trailer Button Bink Filename", DashOptionID::SumTrailerButtonBikFile}}; + + // save option values for options that are found + auto it = option_map.find(option_name); + if (it != option_map.end()) { + switch (it->second) { + case DashOptionID::ScoreboardLogo: + set_option(it->second, g_dash_options_config.scoreboard_logo, option_value); + break; + case DashOptionID::GeomodMesh_Default: + set_option(it->second, g_dash_options_config.geomodmesh_default, option_value); + break; + case DashOptionID::GeomodMesh_DrillerDouble: + set_option(it->second, g_dash_options_config.geomodmesh_driller_double, option_value); + break; + case DashOptionID::GeomodMesh_DrillerSingle: + set_option(it->second, g_dash_options_config.geomodmesh_driller_single, option_value); + break; + case DashOptionID::GeomodMesh_APC: + set_option(it->second, g_dash_options_config.geomodmesh_apc, option_value); + break; + case DashOptionID::GeomodEmitter_Default: + set_option(it->second, g_dash_options_config.geomodemitter_default, option_value); + break; + case DashOptionID::GeomodEmitter_Driller: + set_option(it->second, g_dash_options_config.geomodemitter_driller, option_value); + break; + case DashOptionID::GeomodTexture_Ice: + set_option(it->second, g_dash_options_config.geomodtexture_ice, option_value); + break; + case DashOptionID::FirstLevelFilename: + set_option(it->second, g_dash_options_config.first_level_filename, option_value); + break; + case DashOptionID::TrainingLevelFilename: + set_option(it->second, g_dash_options_config.training_level_filename, option_value); + break; + case DashOptionID::DisableMultiplayerButton: + set_option(it->second, g_dash_options_config.disable_multiplayer_button, option_value); + break; + case DashOptionID::DisableSingleplayerButtons: + set_option(it->second, g_dash_options_config.disable_singleplayer_buttons, option_value); + break; + case DashOptionID::UseStockPlayersConfig: + set_option(it->second, g_dash_options_config.use_stock_game_players_config, option_value); + break; + case DashOptionID::AssaultRifleAmmoColor: + set_option(it->second, g_dash_options_config.ar_ammo_color, option_value); + break; + case DashOptionID::SumTrailerButtonAction: + set_option(it->second, g_dash_options_config.sumtrailer_button_action, option_value); + break; + case DashOptionID::SumTrailerButtonURL: + set_option(it->second, g_dash_options_config.sumtrailer_button_url, option_value); + break; + case DashOptionID::SumTrailerButtonBikFile: + set_option(it->second, g_dash_options_config.sumtrailer_button_bik_filename, option_value); + break; + default: + xlog::warn("Unrecognized DashOptionID: {}", it->first); + } } - - //unknown option else { xlog::warn("Ignoring unsupported option: {}", option_name); } From 82b8a073c9a8471a3731365708da1c9a52363300 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 17 Oct 2024 20:53:40 -0230 Subject: [PATCH 11/13] major clean up, fix color parsers, add sniper/pr scope color options --- game_patch/misc/dashoptions.cpp | 161 ++++++++++++++++++++------------ game_patch/misc/dashoptions.h | 8 ++ 2 files changed, 109 insertions(+), 60 deletions(-) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index eb0ff3e4..74481a5d 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -4,6 +4,7 @@ #include #include #include +#include "../os/console.h" #include "../rf/file/file.h" #include "../rf/gr/gr.h" #include "../rf/sound/sound.h" @@ -55,7 +56,7 @@ std::optional extract_quoted_value(const std::string& value) } // if not wrapped in quotes, assume valid - xlog::warn("String value is not enclosed in quotes, accepting it anyway: '{}'", trimmed_value); + xlog::debug("String value is not enclosed in quotes, accepting it anyway: '{}'", trimmed_value); return trimmed_value; } @@ -77,15 +78,50 @@ void open_url(const std::string& url) } } -CodeInjection fpgun_ar_ammo_digit_color_injection{ +//consolidated logic for parsing colors +std::tuple extract_color_components(uint32_t color) +{ + return std::make_tuple((color >> 24) & 0xFF, // red + (color >> 16) & 0xFF, // green + (color >> 8) & 0xFF, // blue + color & 0xFF // alpha + ); +} + +CallHook fpgun_ar_ammo_digit_color_hook{ 0x004ABC03, - [](auto& regs) { - uint32_t color = g_dash_options_config.ar_ammo_color.value(); - rf::gr::set_color((color >> 24) & 0xFF, // r - (color >> 16) & 0xFF, // g - (color >> 8) & 0xFF, // b - color & 0xFF); // a - regs.eip = 0x004ABC08; + [](int red, int green, int blue, int alpha) { + std::tie(red, green, blue, alpha) = extract_color_components(g_dash_options_config.ar_ammo_color.value()); + fpgun_ar_ammo_digit_color_hook.call_target(red, green, blue, alpha); + } +}; + +CallHook precision_rifle_scope_color_hook{ + 0x004AC850, [](int red, int green, int blue, int alpha) { + std::tie(red, green, blue, alpha) = extract_color_components(g_dash_options_config.pr_scope_color.value()); + precision_rifle_scope_color_hook.call_target(red, green, blue, alpha); + } +}; + +CallHook sniper_rifle_scope_color_hook{ + 0x004AC458, [](int red, int green, int blue, int alpha) { + std::tie(red, green, blue, alpha) = extract_color_components(g_dash_options_config.sr_scope_color.value()); + sniper_rifle_scope_color_hook.call_target(red, green, blue, alpha); + } +}; + +CallHook rail_gun_fire_glow_hook{ + 0x004AC00E, [](int red, int green, int blue, int alpha) { + std::tie(red, green, blue, alpha) = extract_color_components(g_dash_options_config.rail_glow_color.value()); + rail_gun_fire_glow_hook.call_target(red, green, blue, alpha); + } +}; + +CallHook rail_gun_fire_flash_hook{ + 0x004AC04A, [](int red, int green, int blue, int alpha) { + std::tie(red, green, blue, std::ignore) = + extract_color_components(g_dash_options_config.rail_flash_color.value()); + rail_gun_fire_flash_hook.call_target(red, green, blue, alpha); } }; @@ -181,19 +217,6 @@ CallHook driller_geomod_emitter_get_index_hook{ } }; -// geomod crater texture PPM broken currently and disabled, need to fix -CallHook geomod_set_autotexture_ppm_hook{ - 0x00466BD4, - [](rf::GSolid* this_ptr, float ppm) { - xlog::info("Original PPM: {}", ppm); - - float custom_ppm = 24.0f; - xlog::info("Modified PPM: {}", custom_ppm); - - geomod_set_autotexture_ppm_hook.call_target(this_ptr, custom_ppm); - } -}; - // replace ice geomod region texture CallHook ice_geo_crater_bm_load_hook { { @@ -231,7 +254,7 @@ CallHook training_load_level_hook{ // Implement demo_extras_summoner_trailer_click using FunHook FunHook extras_summoner_trailer_click_hook{ 0x0043EC80, [](int x, int y) { - xlog::warn("Summoner trailer button clicked"); + xlog::debug("Summoner trailer button clicked"); int action = g_dash_options_config.sumtrailer_button_action.value_or(0); switch (action) { case 1: @@ -247,7 +270,7 @@ FunHook extras_summoner_trailer_click_hook{ default: // play bink video, is case 0 but also default std::string trailer_path = g_dash_options_config.sumtrailer_button_bik_filename.value_or("sumtrailer.bik"); - xlog::warn("Playing BIK file: {}", trailer_path); + xlog::debug("Playing BIK file: {}", trailer_path); rf::snd_pause(true); rf::bink_play(trailer_path.c_str()); rf::snd_pause(false); @@ -259,7 +282,7 @@ FunHook extras_summoner_trailer_click_hook{ void handle_summoner_trailer_button() { if (int action = g_dash_options_config.sumtrailer_button_action.value_or(-1); action != -1) { - xlog::warn("Action ID: {}", g_dash_options_config.sumtrailer_button_action.value_or(-1)); + //xlog::debug("Action ID: {}", g_dash_options_config.sumtrailer_button_action.value_or(-1)); if (action == 3) { // action 3 means remove the button AsmWriter(0x0043EE14).nop(5); @@ -273,64 +296,67 @@ void handle_summoner_trailer_button() void apply_dashoptions_patches() { - xlog::warn("Applying Dash Options patches"); + xlog::debug("Applying Dash Options patches"); // avoid unnecessary hooks by hooking only if corresponding options are specified - // apply UseStockPlayersConfig if (g_dash_options_config.use_stock_game_players_config.value_or(false)) { - // set mod to not make its own players.cfg but instead use the stock game one AsmWriter(0x004A8F99).jmp(0x004A9010); AsmWriter(0x004A8DCC).jmp(0x004A8E53); } - // apply AR ammo counter coloring if (g_dash_options_config.is_option_loaded(DashOptionID::AssaultRifleAmmoColor)) { - fpgun_ar_ammo_digit_color_injection.install(); - } + fpgun_ar_ammo_digit_color_hook.install(); + } + + if (g_dash_options_config.is_option_loaded(DashOptionID::PrecisionRifleScopeColor)) { + precision_rifle_scope_color_hook.install(); + } + + if (g_dash_options_config.is_option_loaded(DashOptionID::SniperRifleScopeColor)) { + sniper_rifle_scope_color_hook.install(); + } + + if (g_dash_options_config.is_option_loaded(DashOptionID::RailDriverFireGlowColor)) { + rail_gun_fire_glow_hook.install(); + } + + if (g_dash_options_config.is_option_loaded(DashOptionID::RailDriverFireFlashColor)) { + rail_gun_fire_flash_hook.install(); + } // whether should apply is determined in helper function apply_geomod_mesh_patch(); - // apply default geomod smoke emitter if (g_dash_options_config.is_option_loaded(DashOptionID::GeomodEmitter_Default)) { default_geomod_emitter_get_index_hook.install(); } - // apply driller geomod smoke emitter if (g_dash_options_config.is_option_loaded(DashOptionID::GeomodEmitter_Driller)) { driller_geomod_emitter_get_index_hook.install(); } - // broken currently, need to fix - //geomod_set_autotexture_ppm_hook.install(); - if (g_dash_options_config.is_option_loaded(DashOptionID::GeomodTexture_Ice)) { ice_geo_crater_bm_load_hook.install(); } - // first level filename from new game menu if (g_dash_options_config.is_option_loaded(DashOptionID::FirstLevelFilename)) { first_load_level_hook.install(); } - // training level filename from new game menu if (g_dash_options_config.is_option_loaded(DashOptionID::TrainingLevelFilename)) { training_load_level_hook.install(); } - // disable multiplayer button if (g_dash_options_config.disable_multiplayer_button.value_or(false)) { AsmWriter(0x0044391F).nop(5); // multi } - // disable singleplayer buttons if (g_dash_options_config.disable_singleplayer_buttons.value_or(false)) { AsmWriter(0x00443906).nop(5); // save AsmWriter(0x004438ED).nop(5); // load AsmWriter(0x004438D4).nop(5); // new game } - // customize behaviour of Summoner Trailer button if (g_dash_options_config.is_option_loaded(DashOptionID::SumTrailerButtonAction)) { handle_summoner_trailer_button(); } @@ -341,12 +367,11 @@ void set_option(DashOptionID option_id, std::optional& option_field, const st { try { if constexpr (std::is_same_v) { - // extract quoted string option_field = extract_quoted_value(option_value).value_or(option_value); } else if constexpr (std::is_same_v) { - // parse hex color values - option_field = std::stoul(option_value, nullptr, 0); + // hex color values + option_field = std::stoul(extract_quoted_value(option_value).value_or(option_value), nullptr, 16); } else if constexpr (std::is_same_v) { option_field = std::stof(option_value); @@ -362,10 +387,10 @@ void set_option(DashOptionID option_id, std::optional& option_field, const st // mark option as loaded mark_option_loaded(option_id); - xlog::warn("Parsed value has been saved: {}", option_field.value()); + xlog::debug("Parsed value has been saved: {}", option_field.value()); } catch (const std::exception& e) { - xlog::warn("Failed to parse value for option: {}. Error: {}", option_value, e.what()); + xlog::debug("Failed to parse value for option: {}. Error: {}", option_value, e.what()); } } @@ -387,6 +412,10 @@ void process_dashoption_line(const std::string& option_name, const std::string& {"$Disable Singleplayer Buttons", DashOptionID::DisableSingleplayerButtons}, {"$Use Base Game Players Config", DashOptionID::UseStockPlayersConfig}, {"$Assault Rifle Ammo Counter Color", DashOptionID::AssaultRifleAmmoColor}, + {"$Precision Rifle Scope Color", DashOptionID::PrecisionRifleScopeColor}, + {"$Sniper Rifle Scope Color", DashOptionID::SniperRifleScopeColor}, + {"$Rail Driver Fire Glow Color", DashOptionID::RailDriverFireGlowColor}, + {"$Rail Driver Fire Flash Color", DashOptionID::RailDriverFireFlashColor}, {"$Summoner Trailer Button Action", DashOptionID::SumTrailerButtonAction}, {"+Summoner Trailer Button URL", DashOptionID::SumTrailerButtonURL}, {"+Summoner Trailer Button Bink Filename", DashOptionID::SumTrailerButtonBikFile}}; @@ -437,6 +466,18 @@ void process_dashoption_line(const std::string& option_name, const std::string& case DashOptionID::AssaultRifleAmmoColor: set_option(it->second, g_dash_options_config.ar_ammo_color, option_value); break; + case DashOptionID::PrecisionRifleScopeColor: + set_option(it->second, g_dash_options_config.pr_scope_color, option_value); + break; + case DashOptionID::SniperRifleScopeColor: + set_option(it->second, g_dash_options_config.sr_scope_color, option_value); + break; + case DashOptionID::RailDriverFireGlowColor: + set_option(it->second, g_dash_options_config.rail_glow_color, option_value); + break; + case DashOptionID::RailDriverFireFlashColor: + set_option(it->second, g_dash_options_config.rail_flash_color, option_value); + break; case DashOptionID::SumTrailerButtonAction: set_option(it->second, g_dash_options_config.sumtrailer_button_action, option_value); break; @@ -447,7 +488,7 @@ void process_dashoption_line(const std::string& option_name, const std::string& set_option(it->second, g_dash_options_config.sumtrailer_button_bik_filename, option_value); break; default: - xlog::warn("Unrecognized DashOptionID: {}", it->first); + xlog::debug("Unrecognized DashOptionID: {}", it->first); } } else { @@ -459,17 +500,17 @@ bool open_file(const std::string& file_path) { dashoptions_file = std::make_unique(); if (dashoptions_file->open(file_path.c_str()) != 0) { - xlog::error("Failed to open {}", file_path); + xlog::debug("Failed to open {}", file_path); return false; } - xlog::warn("Successfully opened {}", file_path); + xlog::debug("Successfully opened {}", file_path); return true; } void close_file() { if (dashoptions_file) { - xlog::warn("Closing file."); + xlog::debug("Closing file."); dashoptions_file->close(); dashoptions_file.reset(); @@ -483,14 +524,14 @@ void parse() std::string line; bool in_options_section = false; // track section, eventually this should be enum and support multiple sections - xlog::warn("Start parsing dashoptions.tbl"); + xlog::debug("Start parsing dashoptions.tbl"); while (true) { std::string buffer(2048, '\0'); // handle lines up to 2048 bytes, should be plenty int bytes_read = dashoptions_file->read(&buffer[0], buffer.size() - 1); if (bytes_read <= 0) { - xlog::warn("End of file or read error in dashoptions.tbl."); + xlog::debug("End of file or read error in dashoptions.tbl."); break; } @@ -499,35 +540,35 @@ void parse() while (std::getline(file_stream, line)) { line = trim(line); - xlog::warn("Parsing line: '{}'", line); + xlog::debug("Parsing line: '{}'", line); // could be expanded to support multiple sections if (line == "#General") { - xlog::warn("Entering General section"); + xlog::debug("Entering General section"); in_options_section = true; continue; } else if (line == "#End") { - xlog::warn("Exiting General section"); + xlog::debug("Exiting General section"); in_options_section = false; break; // stop, reached the end of section } // skip anything outside the options section if (!in_options_section) { - xlog::warn("Skipping line outside of General section"); + xlog::debug("Skipping line outside of General section"); continue; } // skip empty lines and comments if (line.empty() || line.find("//") == 0) { - xlog::warn("Skipping empty or comment line"); + xlog::debug("Skipping empty or comment line"); continue; } // valid option lines start with $ or + and contain a delimiter : if ((line[0] != '$' && line[0] != '+') || line.find(':') == std::string::npos) { - xlog::warn("Skipping malformed line: '{}'", line); + xlog::debug("Skipping malformed line: '{}'", line); continue; } @@ -543,7 +584,7 @@ void parse() void load_dashoptions_config() { - xlog::warn("Mod launched, attempting to load Dash Options configuration"); + xlog::debug("Mod launched, attempting to load Dash Options configuration"); if (!open_file("dashoptions.tbl")) { return; @@ -552,6 +593,6 @@ void load_dashoptions_config() parse(); close_file(); - xlog::warn("Dash Options configuration loaded"); + rf::console::print("Dash Options configuration loaded"); } } diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index 15bb5586..45375ade 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -21,6 +21,10 @@ enum class DashOptionID DisableSingleplayerButtons, UseStockPlayersConfig, AssaultRifleAmmoColor, + PrecisionRifleScopeColor, + SniperRifleScopeColor, + RailDriverFireGlowColor, + RailDriverFireFlashColor, SumTrailerButtonAction, SumTrailerButtonURL, SumTrailerButtonBikFile, @@ -56,6 +60,10 @@ struct DashOptionsConfig std::optional disable_singleplayer_buttons; std::optional use_stock_game_players_config; std::optional ar_ammo_color; + std::optional pr_scope_color; + std::optional sr_scope_color; + std::optional rail_glow_color; + std::optional rail_flash_color; std::optional sumtrailer_button_action; //extended options From 7c9f39515f92f73fa34aaf9e631cc185ba2a6ba0 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 17 Oct 2024 21:15:22 -0230 Subject: [PATCH 12/13] add options to ignore swap controls commands (they break some mods) --- game_patch/misc/dashoptions.cpp | 8 ++++++++ game_patch/misc/dashoptions.h | 4 ++++ game_patch/misc/player.cpp | 18 ++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/game_patch/misc/dashoptions.cpp b/game_patch/misc/dashoptions.cpp index 74481a5d..f79a55c3 100644 --- a/game_patch/misc/dashoptions.cpp +++ b/game_patch/misc/dashoptions.cpp @@ -411,6 +411,8 @@ void process_dashoption_line(const std::string& option_name, const std::string& {"$Disable Multiplayer Button", DashOptionID::DisableMultiplayerButton}, {"$Disable Singleplayer Buttons", DashOptionID::DisableSingleplayerButtons}, {"$Use Base Game Players Config", DashOptionID::UseStockPlayersConfig}, + {"$Ignore Swap Assault Rifle Controls", DashOptionID::IgnoreSwapAssaultRifleControls}, + {"$Ignore Swap Grenade Controls", DashOptionID::IgnoreSwapGrenadeControls}, {"$Assault Rifle Ammo Counter Color", DashOptionID::AssaultRifleAmmoColor}, {"$Precision Rifle Scope Color", DashOptionID::PrecisionRifleScopeColor}, {"$Sniper Rifle Scope Color", DashOptionID::SniperRifleScopeColor}, @@ -463,6 +465,12 @@ void process_dashoption_line(const std::string& option_name, const std::string& case DashOptionID::UseStockPlayersConfig: set_option(it->second, g_dash_options_config.use_stock_game_players_config, option_value); break; + case DashOptionID::IgnoreSwapAssaultRifleControls: + set_option(it->second, g_dash_options_config.ignore_swap_assault_rifle_controls, option_value); + break; + case DashOptionID::IgnoreSwapGrenadeControls: + set_option(it->second, g_dash_options_config.ignore_swap_grenade_controls, option_value); + break; case DashOptionID::AssaultRifleAmmoColor: set_option(it->second, g_dash_options_config.ar_ammo_color, option_value); break; diff --git a/game_patch/misc/dashoptions.h b/game_patch/misc/dashoptions.h index 45375ade..fddb6bf7 100644 --- a/game_patch/misc/dashoptions.h +++ b/game_patch/misc/dashoptions.h @@ -20,6 +20,8 @@ enum class DashOptionID DisableMultiplayerButton, DisableSingleplayerButtons, UseStockPlayersConfig, + IgnoreSwapAssaultRifleControls, + IgnoreSwapGrenadeControls, AssaultRifleAmmoColor, PrecisionRifleScopeColor, SniperRifleScopeColor, @@ -59,6 +61,8 @@ struct DashOptionsConfig std::optional disable_multiplayer_button; std::optional disable_singleplayer_buttons; std::optional use_stock_game_players_config; + std::optional ignore_swap_assault_rifle_controls; + std::optional ignore_swap_grenade_controls; std::optional ar_ammo_color; std::optional pr_scope_color; std::optional sr_scope_color; diff --git a/game_patch/misc/player.cpp b/game_patch/misc/player.cpp index 59f77df3..bf16dd5f 100644 --- a/game_patch/misc/player.cpp +++ b/game_patch/misc/player.cpp @@ -8,8 +8,10 @@ #include "../rf/weapon.h" #include "../rf/hud.h" #include "../rf/input.h" +#include "../rf/os/os.h" #include "../os/console.h" #include "../main/main.h" +#include "../misc/dashoptions.h" #include "../multi/multi.h" #include "../hud/multi_spectate.h" #include @@ -83,10 +85,14 @@ bool should_swap_weapon_alt_fire(rf::Player* player) return false; } - if (g_game_config.swap_assault_rifle_controls && entity->ai.current_primary_weapon == rf::assault_rifle_weapon_type) + if (g_game_config.swap_assault_rifle_controls && + !g_dash_options_config.ignore_swap_assault_rifle_controls && + entity->ai.current_primary_weapon == rf::assault_rifle_weapon_type) return true; - if (g_game_config.swap_grenade_controls && entity->ai.current_primary_weapon == rf::grenade_weapon_type) + if (g_game_config.swap_grenade_controls && + !g_dash_options_config.ignore_swap_grenade_controls && + entity->ai.current_primary_weapon == rf::grenade_weapon_type) return true; return false; @@ -148,6 +154,10 @@ ConsoleCommand2 swap_assault_rifle_controls_cmd{ g_game_config.save(); rf::console::print("Swap assault rifle controls: {}", g_game_config.swap_assault_rifle_controls ? "enabled" : "disabled"); + if (g_dash_options_config.ignore_swap_assault_rifle_controls) { + rf::console::print("Note: This setting is disabled in the {} mod and will have no effect.", + rf::mod_param.get_arg()); + } }, "Swap Assault Rifle controls", }; @@ -159,6 +169,10 @@ ConsoleCommand2 swap_grenade_controls_cmd{ g_game_config.save(); rf::console::print("Swap grenade controls: {}", g_game_config.swap_grenade_controls ? "enabled" : "disabled"); + if (g_dash_options_config.ignore_swap_grenade_controls) { + rf::console::print("Note: This setting is disabled in the {} mod and will have no effect.", + rf::mod_param.get_arg()); + } }, "Swap grenade controls", }; From 4279a4861d3ce0503aa29b38c644879c2a42deb1 Mon Sep 17 00:00:00 2001 From: Goober Date: Thu, 17 Oct 2024 21:32:28 -0230 Subject: [PATCH 13/13] removed commented test code --- game_patch/os/commands.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/game_patch/os/commands.cpp b/game_patch/os/commands.cpp index 6e0afdf4..5bfdf978 100644 --- a/game_patch/os/commands.cpp +++ b/game_patch/os/commands.cpp @@ -129,7 +129,6 @@ void console_commands_init() register_builtin_command("system_info", "Show system information", 0x00525A60); register_builtin_command("trilinear_filtering", "Toggle trilinear filtering", 0x0054F050); register_builtin_command("detail_textures", "Toggle detail textures", 0x0054F0B0); - //register_builtin_command("drop_entity", "Drop any entity", 0x00418740); // for testing, will remove #ifdef DEBUG register_builtin_command("drop_fixed_cam", "Drop a fixed camera", 0x0040D220);