From f1f95717d10d486c8e5fb8bfd5d01b06c0877bf4 Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Wed, 14 May 2025 12:18:03 -0500 Subject: [PATCH] Support Custom Gauges in HUD Config UI (#6670) * Support coloring custom gauges * remove NULL --- code/hud/hud.cpp | 30 ++- code/hud/hudconfig.cpp | 119 +++++++----- code/hud/hudconfig.h | 255 ++++++++++++++------------ code/hud/hudobserver.cpp | 2 +- code/pilotfile/csg.cpp | 7 + code/pilotfile/plr.cpp | 13 +- code/pilotfile/plr_hudprefs.cpp | 156 ++++++++++++++++ code/pilotfile/plr_hudprefs.h | 12 ++ code/scripting/api/objs/hudconfig.cpp | 27 +++ code/source_groups.cmake | 2 + 10 files changed, 450 insertions(+), 173 deletions(-) create mode 100644 code/pilotfile/plr_hudprefs.cpp create mode 100644 code/pilotfile/plr_hudprefs.h diff --git a/code/hud/hud.cpp b/code/hud/hud.cpp index 9d3c5b3837..569844c8e6 100644 --- a/code/hud/hud.cpp +++ b/code/hud/hud.cpp @@ -385,11 +385,18 @@ render_for_cockpit_toggle(0), custom_gauge(true), textoffset_x(txtoffset_x), tex const HC_gauge_mappings& gauge_map = HC_gauge_mappings::get_instance(); - gauge_config_id = ""; // gauge_map.get_string_id_from_numeric_id(_gauge_config); + gauge_config_id = create_custom_gauge_id(_custom_name); can_popup = hud_config_can_popup(gauge_map.get_numeric_id_from_string_id(gauge_config_id)); use_iff_color = hud_config_use_iff_color(gauge_map.get_numeric_id_from_string_id(gauge_config_id)); use_tag_color = hud_config_use_tag_color(gauge_map.get_numeric_id_from_string_id(gauge_config_id)); + //Set the gauge type for color backup lookup + HUD_config.set_gauge_type(gauge_config_id, gauge_type); + + // Custom gauges are always default visible and popup false for now + HUD_config.set_gauge_visibility(gauge_config_id, true); + HUD_config.set_gauge_popup(gauge_config_id, false); + popup_timer = timestamp(1); texture_target_fname[0] = '\0'; @@ -583,15 +590,9 @@ void HudGauge::setGaugeColor(int bright_index, bool config) if (config) { color use_color; - if (custom_gauge) { - // Custom gauges currently use the color based on their gauge type - const HC_gauge_mappings& gauge_map = HC_gauge_mappings::get_instance(); - use_color = HUD_config.get_gauge_color(gauge_map.get_string_id_from_numeric_id(gauge_type)); - } else { - use_color = HUD_config.get_gauge_color(gauge_config_id); - } + use_color = HUD_config.get_gauge_color(gauge_config_id); - if (!custom_gauge && HUD_config.is_gauge_visible(gauge_config_id)) { + if (HUD_config.is_gauge_visible(gauge_config_id)) { if (use_iff_color) { // Ditto for target tagging color if (use_tag_color) { @@ -610,11 +611,6 @@ void HudGauge::setGaugeColor(int bright_index, bool config) } gr_init_alphacolor(&use_color, use_color.red, use_color.green, use_color.blue, alpha); gr_set_color_fast(&use_color); - } else if (custom_gauge) { - // Custom gauges currently use the current color but alpha dimmed since they are not selectable - alpha = MAX(use_color.alpha - 50, 25); - gr_init_alphacolor(&use_color, use_color.red, use_color.green, use_color.blue, alpha); - gr_set_color_fast(&use_color); } else { // if its off, make it dark gray gr_init_alphacolor(&use_color, 127, 127, 127, 64); @@ -881,10 +877,10 @@ void HudGauge::render(float /*frametime*/, bool config) if (config) { std::tie(x, y, scale) = hud_config_convert_coord_sys(position[0], position[1], base_w, base_h); - // The following code will be used to allow custom coloring of custom HUD gauges. This will be implemented in the future. - /*int bmw, bmh; + // Commenting out this code below will disable custom gauges from being selectable and individually colorable + int bmw, bmh; bm_get_info(custom_frame.first_frame, &bmw, &bmh); - hud_config_set_mouse_coords(gauge_config_id, x, x + static_cast(bmw * scale), y, y + static_cast(bmh * scale));*/ + hud_config_set_mouse_coords(gauge_config_id, x, x + static_cast(bmw * scale), y, y + static_cast(bmh * scale)); } setGaugeColor(HUD_C_NONE, config); diff --git a/code/hud/hudconfig.cpp b/code/hud/hudconfig.cpp index ad3d66ec1c..0a16e16c17 100644 --- a/code/hud/hudconfig.cpp +++ b/code/hud/hudconfig.cpp @@ -937,31 +937,37 @@ void hud_config_record_color(int in_color) HUD_color_blue = HC_colors[in_color].b; } -// Set the HUD color +// Set the HUD color to one of the color presets void hud_config_set_color(int in_color) { hud_config_record_color(in_color); HUD_init_hud_color_array(); - // apply the color to all gauges - for(int idx=0; idxisCustom()) { hud_config_button_disable(HCB_ON); hud_config_button_disable(HCB_OFF); hud_config_button_disable(HCB_POPUP); @@ -1349,8 +1358,6 @@ void hud_config_set_button_state() hud_config_button_enable(HCB_ON); hud_config_button_enable(HCB_OFF); - const auto gauge = hud_config_get_gauge_pointer(HC_gauge_selected); - // popup is maybe available if (gauge != nullptr && gauge->getConfigCanPopup()) { hud_config_button_enable(HCB_POPUP); @@ -1553,8 +1560,6 @@ void hud_config_close(bool API_Access) // hud_set_default_hud_config() will set the hud configuration to default values void hud_set_default_hud_config(player * /*p*/, const SCP_string& filename) { - int idx; - HUD_color_alpha = HUD_COLOR_ALPHA_DEFAULT; HUD_config.main_color = HC_default_color; HUD_color_red = HC_colors[HUD_config.main_color].r; @@ -1564,20 +1569,31 @@ void hud_set_default_hud_config(player * /*p*/, const SCP_string& filename) color clr; gr_init_alphacolor(&clr, HUD_color_red, HUD_color_green, HUD_color_blue, (HUD_color_alpha + 1) * 16); - for(idx=0; idxisCustom()) { + const SCP_string& gauge_id = gauge_pair.first; + HUD_config.show_flags_map[gauge_id] = true; + } + } + HUD_config.rp_dist = RR_INFINITY; - HUD_config.is_observer = 0; + HUD_config.is_observer = false; // load up the default colors hud_config_color_load(filename.c_str()); @@ -1801,15 +1817,15 @@ void hud_config_recalc_alpha_slider() void hud_config_red_slider() { - int idx; int pos = HCS_CONV(HC_color_sliders[HCS_RED].get_currentItem()) ; - // select all ? if(HC_select_all){ - for(idx=0; idx New String ID + SCP_string get_string_id_from_numeric_id(int numeric_id) const + { + auto it = gauge_map.find(numeric_id); + return (it != gauge_map.end()) ? it->second.second : ""; + } + + // HCF String Name -> New String ID + SCP_string get_string_id_from_hcf_id(const SCP_string& hcf_name) const + { + for (const auto& [id, mapping] : gauge_map) { + if (XSTR(mapping.first.first.c_str(), mapping.first.second) == hcf_name) { + return mapping.second; + } + } + return ""; + } + + // New String ID -> Numeric ID (for saving to player file) + int get_numeric_id_from_string_id(const SCP_string& string_id) const + { + for (const auto& [id, mapping] : gauge_map) { + if (mapping.second == string_id) { + return id; + } + } + return -1; + } + + // New String ID -> HCF String Name (for saving HCF files) + SCP_string get_hcf_name_from_string_id(const SCP_string& string_id) const + { + for (const auto& [id, mapping] : gauge_map) { + if (mapping.second == string_id) { + return XSTR(mapping.first.first.c_str(), mapping.first.second); + } + } + return ""; + } + + private: + HC_gauge_mappings() + { + // Map the old gauge_config numeric IDs, the HCF file IDs (which, yes, were translated...), and the newer string + // IDs for the retail built-in gauges. Custom gauges will only have a string ID and do not need to be mapped. + gauge_map = {{0, {{"lead indicator", 249}, "Builtin::LeadIndicator"}}, + {1, {{"target orientation", 250}, "Builtin::TargetOrientation"}}, + {2, {{"closest attacking hostile", 251}, "Builtin::ClosestAttackingHostile"}}, + {3, {{"current target direction", 252}, "Builtin::CurrentTargetDirection"}}, + {4, {{"mission time", 253}, "Builtin::MissionTime"}}, + {5, {{"reticle", 254}, "Builtin::Reticle"}}, + {6, {{"throttle", 255}, "Builtin::Throttle"}}, + {7, {{"radar", 256}, "Builtin::Radar"}}, + {8, {{"target monitor", 257}, "Builtin::TargetMonitor"}}, + {9, {{"center of reticle", 258}, "Builtin::CenterOfReticle"}}, + {10, {{"extra target info", 259}, "Builtin::ExtraTargetInfo"}}, + {11, {{"target shield", 260}, "Builtin::TargetShield"}}, + {12, {{"player shield", 261}, "Builtin::PlayerShield"}}, + {13, {{"power management", 262}, "Builtin::PowerManagement"}}, + {14, {{"auto-target icon", 263}, "Builtin::AutoTargetIcon"}}, + {15, {{"auto-speed-match icon", 264}, "Builtin::AutoSpeedMatchIcon"}}, + {16, {{"weapons display", 265}, "Builtin::WeaponsDisplay"}}, + {17, {{"monitoring view", 266}, "Builtin::MonitoringView"}}, + {18, {{"directives view", 267}, "Builtin::DirectivesView"}}, + {19, {{"threat gauge", 268}, "Builtin::ThreatGauge"}}, + {20, {{"afterburner energy", 269}, "Builtin::AfterburnerEnergy"}}, + {21, {{"weapons energy", 270}, "Builtin::WeaponsEnergy"}}, + {22, {{"weapon linking", 271}, "Builtin::WeaponLinking"}}, + {23, {{"target hull/shield icon", 272}, "Builtin::TargetHullShieldIcon"}}, + {24, {{"offscreen indicator", 273}, "Builtin::OffscreenIndicator"}}, + {25, {{"comm video", 274}, "Builtin::CommVideo"}}, + {26, {{"damage display", 275}, "Builtin::DamageDisplay"}}, + {27, {{"message output", 276}, "Builtin::MessageOutput"}}, + {28, {{"locked missile direction", 277}, "Builtin::LockedMissileDirection"}}, + {29, {{"countermeasures", 278}, "Builtin::Countermeasures"}}, + {30, {{"objective notify", 279}, "Builtin::ObjectiveNotify"}}, + {31, {{"wingmen status", 280}, "Builtin::WingmenStatus"}}, + {32, {{"offscreen range", 281}, "Builtin::OffscreenRange"}}, + {33, {{"kills gauge", 282}, "Builtin::KillsGauge"}}, + {34, {{"attacking target count", 283}, "Builtin::AttackingTargetCount"}}, + {35, {{"warning flash", 1459}, "Builtin::WarningFlash"}}, + {36, {{"comm menu", 1460}, "Builtin::CommMenu"}}, + {37, {{"support gauge", 1461}, "Builtin::SupportGauge"}}, + {38, {{"lag gauge", 1462}, "Builtin::LagGauge"}}}; + } + + SCP_unordered_map, SCP_string>> gauge_map; +}; + /*! * @brief Vector for storing the filenames of hud preset files * @note main definition in hudconfig.cpp @@ -51,14 +152,15 @@ extern int HC_resize_mode; * @note Is not default init'd. Assumes new player, PLR, or CSG reads will correctly set data. */ typedef struct HUD_CONFIG_TYPE { - int rp_dist; //!< one of RR_ #defines above; Is the maxium radar view distance setting - int is_observer; //!< 1 or 0, observer mode or not, respectively - int main_color; //!< the default HUD_COLOR selection for all gauges; each gauge may override this with a custom RGB + int rp_dist; // one of RR_ #defines above; Is the maxium radar view distance setting + bool is_observer; // 1 or 0, observer mode or not, respectively + int main_color; // the default HUD_COLOR selection for all gauges; each gauge may override this with a custom RGB // Maps for HUD gauge configuration - std::unordered_map gauge_colors; //!< Gauge-specific colors - std::unordered_map show_flags_map; //!< Show/hide state for gauges - std::unordered_map popup_flags_map; //!< Popup state for gauges + SCP_unordered_map gauge_colors; // Gauge-specific colors + SCP_unordered_map show_flags_map; // Show/hide state for gauges + SCP_unordered_map popup_flags_map; // Popup state for gauges + SCP_unordered_map gauge_types; // Gauge types used for backup colors for custom gauges to preseve old behavior static color default_white_color() { @@ -69,37 +171,58 @@ typedef struct HUD_CONFIG_TYPE { // Methods for setting and getting gauge properties where getting will return a default value - void set_gauge_color(const std::string& gauge_id, const color& col) + void set_gauge_color(const SCP_string& gauge_id, const color& col) { gauge_colors[gauge_id] = col; } - // Get the gauge color or white if the gauge is not found - color get_gauge_color(const std::string& gauge_id) const + // Get the gauge color, the color based on it's type, or white if the gauge is not found + color get_gauge_color(const SCP_string& gauge_id) const { auto it = gauge_colors.find(gauge_id); - return (it != gauge_colors.end()) ? it->second : default_white_color(); + + // Got a match? Return it + if (it != gauge_colors.end()) { + return it->second; + } + + // No match.. try using the gauge type + auto type_it = gauge_types.find(gauge_id); + if (type_it != gauge_types.end()) { + const HC_gauge_mappings& gauge_map = HC_gauge_mappings::get_instance(); + + it = gauge_colors.find(gauge_map.get_string_id_from_numeric_id(type_it->second)); + + return it->second; + } + + // Still nothing.. return white + return default_white_color(); } - void set_gauge_visibility(const std::string& gauge_id, bool visible) + void set_gauge_type(const SCP_string& gauge_id, int type) { + gauge_types[gauge_id] = type; + } + + void set_gauge_visibility(const SCP_string& gauge_id, bool visible) { show_flags_map[gauge_id] = visible; } // Get the gauge config visibility setting or false (invisible) if not found - bool is_gauge_visible(const std::string& gauge_id) const + bool is_gauge_visible(const SCP_string& gauge_id) const { auto it = show_flags_map.find(gauge_id); return (it != show_flags_map.end()) ? it->second : false; // Default to invisible } - void set_gauge_popup(const std::string& gauge_id, bool popup) + void set_gauge_popup(const SCP_string& gauge_id, bool popup) { popup_flags_map[gauge_id] = popup; } // Get the gauge config popup setting or false if not found - bool is_gauge_popup(const std::string& gauge_id) const + bool is_gauge_popup(const SCP_string& gauge_id) const { auto it = popup_flags_map.find(gauge_id); return (it != popup_flags_map.end()) ? it->second : false; // Default to not a popup @@ -196,105 +319,6 @@ extern SCP_map> HC_hu extern SCP_map> HC_hud_primary_weapons; extern SCP_map> HC_hud_secondary_weapons; -class HC_gauge_mappings { -public: - static HC_gauge_mappings& get_instance() { - static HC_gauge_mappings instance; - return instance; - } - - // Prevent copying - HC_gauge_mappings(const HC_gauge_mappings&) = delete; - HC_gauge_mappings& operator=(const HC_gauge_mappings&) = delete; - - // Numeric ID -> New String ID - SCP_string get_string_id_from_numeric_id(int numeric_id) const { - auto it = gauge_map.find(numeric_id); - return (it != gauge_map.end()) ? it->second.second : ""; - } - - // HCF String Name -> New String ID - SCP_string get_string_id_from_hcf_id(const SCP_string& hcf_name) const - { - for (const auto& [id, mapping] : gauge_map) { - if (XSTR(mapping.first.first.c_str(), mapping.first.second) == hcf_name) { - return mapping.second; - } - } - return ""; - } - - // New String ID -> Numeric ID (for saving to player file) - int get_numeric_id_from_string_id(const SCP_string& string_id) const { - for (const auto& [id, mapping] : gauge_map) { - if (mapping.second == string_id) { - return id; - } - } - return -1; - } - - // New String ID -> HCF String Name (for saving HCF files) - SCP_string get_hcf_name_from_string_id(const SCP_string& string_id) const - { - for (const auto& [id, mapping] : gauge_map) { - if (mapping.second == string_id) { - return XSTR(mapping.first.first.c_str(), mapping.first.second); - } - } - return ""; - } - -private: - HC_gauge_mappings() { - // Map the old gauge_config numeric IDs, the HCF file IDs (which, yes, were translated...), and the newer string IDs - // for the retail built-in gauges. Custom gauges will only have a string ID and do not need to be mapped. - gauge_map = { - {0, {{"lead indicator", 249}, "Builtin::LeadIndicator"}}, - {1, {{"target orientation", 250}, "Builtin::TargetOrientation"}}, - {2, {{"closest attacking hostile", 251}, "Builtin::ClosestAttackingHostile"}}, - {3, {{"current target direction", 252}, "Builtin::CurrentTargetDirection"}}, - {4, {{"mission time", 253}, "Builtin::MissionTime"}}, - {5, {{"reticle", 254}, "Builtin::Reticle"}}, - {6, {{"throttle", 255}, "Builtin::Throttle"}}, - {7, {{"radar", 256}, "Builtin::Radar"}}, - {8, {{"target monitor", 257}, "Builtin::TargetMonitor"}}, - {9, {{"center of reticle", 258}, "Builtin::CenterOfReticle"}}, - {10, {{"extra target info", 259}, "Builtin::ExtraTargetInfo"}}, - {11, {{"target shield", 260}, "Builtin::TargetShield"}}, - {12, {{"player shield", 261}, "Builtin::PlayerShield"}}, - {13, {{"power management", 262}, "Builtin::PowerManagement"}}, - {14, {{"auto-target icon", 263}, "Builtin::AutoTargetIcon"}}, - {15, {{"auto-speed-match icon", 264}, "Builtin::AutoSpeedMatchIcon"}}, - {16, {{"weapons display", 265}, "Builtin::WeaponsDisplay"}}, - {17, {{"monitoring view", 266}, "Builtin::MonitoringView"}}, - {18, {{"directives view", 267}, "Builtin::DirectivesView"}}, - {19, {{"threat gauge", 268}, "Builtin::ThreatGauge"}}, - {20, {{"afterburner energy", 269}, "Builtin::AfterburnerEnergy"}}, - {21, {{"weapons energy", 270}, "Builtin::WeaponsEnergy"}}, - {22, {{"weapon linking", 271}, "Builtin::WeaponLinking"}}, - {23, {{"target hull/shield icon", 272}, "Builtin::TargetHullShieldIcon"}}, - {24, {{"offscreen indicator", 273}, "Builtin::OffscreenIndicator"}}, - {25, {{"comm video", 274}, "Builtin::CommVideo"}}, - {26, {{"damage display", 275}, "Builtin::DamageDisplay"}}, - {27, {{"message output", 276}, "Builtin::MessageOutput"}}, - {28, {{"locked missile direction", 277}, "Builtin::LockedMissileDirection"}}, - {29, {{"countermeasures", 278}, "Builtin::Countermeasures"}}, - {30, {{"objective notify", 279}, "Builtin::ObjectiveNotify"}}, - {31, {{"wingmen status", 280}, "Builtin::WingmenStatus"}}, - {32, {{"offscreen range", 281}, "Builtin::OffscreenRange"}}, - {33, {{"kills gauge", 282}, "Builtin::KillsGauge"}}, - {34, {{"attacking target count", 283}, "Builtin::AttackingTargetCount"}}, - {35, {{"warning flash", 1459}, "Builtin::WarningFlash"}}, - {36, {{"comm menu", 1460}, "Builtin::CommMenu"}}, - {37, {{"support gauge", 1461}, "Builtin::SupportGauge"}}, - {38, {{"lag gauge", 1462}, "Builtin::LagGauge"}} - }; - } - - SCP_unordered_map, SCP_string>> gauge_map; -}; - /*! * @brief get the gauge pointer for the given gauge index @@ -444,5 +468,10 @@ std::pair hud_config_calc_coords_from_angle(float angle_degrees, i */ float hud_config_find_valid_angle(const SCP_string& gauge, float initial_angle, int centerX, int centerY, float radius); +/*! + * @brief generage a custom gauge id + */ +SCP_string create_custom_gauge_id(const SCP_string& gauge_name); + #endif diff --git a/code/hud/hudobserver.cpp b/code/hud/hudobserver.cpp index 7488b72477..a373f6d1e4 100644 --- a/code/hud/hudobserver.cpp +++ b/code/hud/hudobserver.cpp @@ -43,7 +43,7 @@ void hud_observer_init(ship *shipp, ai_info *aip) Hud_obs_ship.ship_max_shield_strength = shipp->ship_max_shield_strength; Hud_obs_ship.weapons = shipp->weapons; - HUD_config.is_observer = 1; + HUD_config.is_observer = true; HUD_config.show_flags_map.clear(); HUD_config.popup_flags_map.clear(); diff --git a/code/pilotfile/csg.cpp b/code/pilotfile/csg.cpp index f7b833885b..7b22ebbee0 100644 --- a/code/pilotfile/csg.cpp +++ b/code/pilotfile/csg.cpp @@ -15,6 +15,7 @@ #include "options/OptionsManager.h" #include "parse/sexp_container.h" #include "pilotfile/pilotfile.h" +#include "pilotfile/plr_hudprefs.h" #include "playerman/player.h" #include "ship/ship.h" #include "sound/audiostr.h" @@ -1824,6 +1825,9 @@ bool pilotfile::load_savefile(player *_p, const char *campaign) } } + mprintf(("HUDPREFS => Loading extended player HUD preferences...\n")); + hud_config_load_player_prefs(p->callsign); + // Probably don't need to persist these to disk but it'll make sure on next boot we start with these campaign options set // The github tests don't know what to do with the ini file so I guess we'll skip this for now //options::OptionsManager::instance()->persistChanges(); @@ -1923,6 +1927,9 @@ bool pilotfile::save_savefile() mprintf(("CSG => Saving: Containers...\n")); csg_write_containers(); + mprintf(("HUDPREFS => Saving player HUD preferences (testing)...\n")); + hud_config_save_player_prefs(p->callsign); + // Done! mprintf(("CSG => Saving complete!\n")); diff --git a/code/pilotfile/plr.cpp b/code/pilotfile/plr.cpp index 724f680c75..03c9323823 100644 --- a/code/pilotfile/plr.cpp +++ b/code/pilotfile/plr.cpp @@ -16,6 +16,7 @@ #include "pilotfile/pilotfile.h" #include "pilotfile/BinaryFileHandler.h" #include "pilotfile/JSONFileHandler.h" +#include "pilotfile/plr_hudprefs.h" #include "playerman/managepilot.h" #include "playerman/player.h" #include "scripting/hook_api.h" @@ -242,7 +243,11 @@ void pilotfile::plr_read_hud() ReleaseWarning(LOCATION, "Player file has too many hud config errors, and is likely corrupted. Please verify and save your settings in the hud config menu."); } - hud_config_set_color(HUD_config.main_color); + // This seemed to be previously used to ensure a valid color was set + // but under the new method we can rely on the getter to return a valid color + // in all cases. So comment this out to prevent forcing a default color on + // custom gauges that rely on color by their gauge type + //hud_config_set_color(HUD_config.main_color); // gauge-specific colors auto num_gauges = handler->startArrayRead("hud_gauges"); @@ -1215,6 +1220,9 @@ bool pilotfile::load_player(const char* callsign, player* _p, bool force_binary) } handler->endSectionRead(); + mprintf(("HUDPREFS => Loading extended player HUD preferences...\n")); + hud_config_load_player_prefs(callsign); + // Probably don't need to persist these to disk but it'll make sure on next boot we start with these player options set // The github tests don't know what to do with the ini file so I guess we'll skip this for now //options::OptionsManager::instance()->persistChanges(); @@ -1331,6 +1339,9 @@ bool pilotfile::save_player(player *_p) handler->flush(); + mprintf(("HUDPREFS => Saving player HUD preferences (testing)...\n")); + hud_config_save_player_prefs(p->callsign); + // Done! mprintf(("PLR => Saving complete!\n")); diff --git a/code/pilotfile/plr_hudprefs.cpp b/code/pilotfile/plr_hudprefs.cpp new file mode 100644 index 0000000000..2f77cdd672 --- /dev/null +++ b/code/pilotfile/plr_hudprefs.cpp @@ -0,0 +1,156 @@ +#include "plr_hudprefs.h" + +#include "globalincs/pstypes.h" + +#include "cfile/cfile.h" +#include "hud/hudconfig.h" +#include + +// Saves the player's current HUD preferences (visibility, popup, color for all gauges) +// to a specific file (.hdp) in the player directory. +void hud_config_save_player_prefs(const char* callsign) +{ + if (callsign == nullptr || *callsign == '\0') { + mprintf(("HUDPREFS: Cannot save preferences - invalid callsign provided.\n")); + return; + } + + SCP_string filename = callsign; + filename += ".hdp"; + + CFILE* file = cfopen(filename.c_str(), "wt", CFILE_NORMAL, CF_TYPE_DATA); + + if (file == nullptr) { + mprintf(("HUDPREFS: Unable to open file '%s' for writing player HUD preferences.\n", filename.c_str())); + return; + } + + mprintf(("HUDPREFS: Saving player HUD preferences to '%s'...\n", filename.c_str())); + + char format_buffer[256]; + + cfputs("+Version: 1\n\n", file); + + // Iterate ALL gauges + for (const auto& pair : HUD_config.gauge_colors) { + const SCP_string& gauge_id = pair.first; + + if (gauge_id.empty()) { + continue; + } + + // Get current state + bool is_visible = HUD_config.is_gauge_visible(gauge_id); + bool is_popup = HUD_config.is_gauge_popup(gauge_id); + color clr = pair.second; + + // +Gauge: + sprintf(format_buffer, "+Gauge: %s\n", gauge_id.c_str()); + cfputs(format_buffer, file); + + // +Visible: <0_or_1> + sprintf(format_buffer, "+Visible: %d\n", is_visible ? 1 : 0); + cfputs(format_buffer, file); + + // +Popup: <0_or_1> + sprintf(format_buffer, "+Popup: %d\n", is_popup ? 1 : 0); + cfputs(format_buffer, file); + + // +Color: + sprintf(format_buffer, "+Color: %d %d %d %d\n\n", clr.red, clr.green, clr.blue, clr.alpha); + cfputs(format_buffer, file); + } + + cfclose(file); + + mprintf(("HUDPREFS: Finished saving player HUD preferences.\n")); +} + +// Loads the player's HUD preferences (visibility, popup, color for all gauges) +// from the specific file (.hdp) in the player directory, if it exists. +void hud_config_load_player_prefs(const char* callsign) +{ + if (callsign == nullptr || *callsign == '\0') { + mprintf(("HUDPREFS: Cannot load preferences - invalid callsign provided.\n")); + return; + } + + // Construct the filename: .hdp + SCP_string filename = callsign; + filename += ".hdp"; + + // Check if the file exists in the player directories + int file_location = cf_exists(filename.c_str(), CF_TYPE_DATA); + + if (file_location == -1) { + // File doesn't exist, which is fine - means no custom prefs saved yet. + mprintf(("HUDPREFS: No player HUD preferences file found ('%s'). Using defaults/player file settings.\n", filename.c_str())); + return; + } + + mprintf(("HUDPREFS: Loading player HUD preferences from '%s'...\n", filename.c_str())); + + try { + + read_file_text(filename.c_str()); + reset_parse(); + + if (optional_string("+Version:")) { + int version = 1; + stuff_int(&version); + // Can add version checks here later if format changes + if (version != 1) { + mprintf(("HUDPREFS: Warning - Unknown version %d in '%s'. Trying to parse anyway.\n", + version, + filename.c_str())); + } + } + + // Loop through all gauge blocks in the file + while (optional_string("+Gauge:")) { + SCP_string gauge_id; + int visible_int = 0; + int popup_int = 0; + ubyte r = 0, g = 0, b = 0, a = 255; // Default alpha to opaque + + // Parse gauge ID + stuff_string(gauge_id, F_NAME); + + // Parse visibility + required_string("+Visible:"); + stuff_int(&visible_int); + bool is_visible = (visible_int != 0); + + // Parse popup + required_string("+Popup:"); + stuff_int(&popup_int); + bool is_popup = (popup_int != 0); + + // Parse color + required_string("+Color:"); + stuff_ubyte(&r); + stuff_ubyte(&g); + stuff_ubyte(&b); + stuff_ubyte(&a); + + // Validate gauge ID before applying settings + if (!gauge_id.empty()) { + color clr; + gr_init_alphacolor(&clr, r, g, b, a); + + HUD_config.set_gauge_visibility(gauge_id, is_visible); + HUD_config.set_gauge_popup(gauge_id, is_popup); + HUD_config.set_gauge_color(gauge_id, clr); + } else { + mprintf(("HUDPREFS: Warning - Encountered empty gauge ID while parsing '%s'. Skipping entry.\n",filename.c_str())); + } + } + + mprintf(("HUDPREFS: Finished loading player HUD preferences.\n")); + + } catch (const parse::ParseException& e) { + mprintf(("HUDPREFS: Error parsing player HUD preferences file '%s'! Error: %s\n", filename.c_str(), e.what())); + } catch (...) { + mprintf(("HUDPREFS: Unknown error parsing player HUD preferences file '%s'!\n", filename.c_str())); + } +} \ No newline at end of file diff --git a/code/pilotfile/plr_hudprefs.h b/code/pilotfile/plr_hudprefs.h new file mode 100644 index 0000000000..5a160f5257 --- /dev/null +++ b/code/pilotfile/plr_hudprefs.h @@ -0,0 +1,12 @@ +#ifndef _PLAYER_HUDPREFS_H +#define _PLAYER_HUDPREFS_H + +// Saves the player's current HUD preferences (vis/popup/color for all gauges) +// to .hdp +void hud_config_save_player_prefs(const char* callsign); + +// Loads the player's current HUD preferences (vis/popup/color for all gauges) +// from .hdp +void hud_config_load_player_prefs(const char* callsign); + +#endif // _PLAYER_HUDPREFS_H \ No newline at end of file diff --git a/code/scripting/api/objs/hudconfig.cpp b/code/scripting/api/objs/hudconfig.cpp index f8335ef89a..12733a8fd3 100644 --- a/code/scripting/api/objs/hudconfig.cpp +++ b/code/scripting/api/objs/hudconfig.cpp @@ -361,6 +361,33 @@ ADE_VIRTVAR(UsesIffForColor, return ADE_RETURN_TRUE; } +ADE_VIRTVAR(isCustomGauge, + l_Gauge_Config, + nullptr, + "Gets whether or not the gauge is a custom gauge.", + "boolean", + "True if custom, false otherwise") +{ + gauge_config_h current; + if (!ade_get_args(L, "o", l_Gauge_Config.Get(¤t))) { + return ADE_RETURN_NIL; + } + + if (!current.isValid()) { + return ade_set_error(L, "b", false); + } + + if (ADE_SETTING_VAR) { + LuaError(L, "This property is read only."); + } + + if (!current.getGauge()->isCustom()) { + return ADE_RETURN_FALSE; + } + + return ADE_RETURN_TRUE; +} + ADE_FUNC(setSelected, l_Gauge_Config, "boolean", "Sets if the gauge is the currently selected gauge for drawing as selected.", nullptr, nullptr) { gauge_config_h current; diff --git a/code/source_groups.cmake b/code/source_groups.cmake index 4dfc4bbd80..324e2b5d5f 100644 --- a/code/source_groups.cmake +++ b/code/source_groups.cmake @@ -1173,6 +1173,8 @@ add_file_folder("PilotFile" pilotfile/pilotfile_convert.h pilotfile/plr.cpp pilotfile/plr_convert.cpp + pilotfile/plr_hudprefs.cpp + pilotfile/plr_hudprefs.h ) # Playerman files