Files
Mike Nelson 021eb8e49a Fix two HUD Config preset issues (#6830)
* always use exact match

* custom gauges should use the type for colors with old preset files
2025-07-24 16:51:45 -04:00

486 lines
16 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#ifndef _HUDCONFIG_H
#define _HUDCONFIG_H
#include "hud/hud.h"
#include "hud/hudshield.h"
#include "ui/ui.h"
class player;
class ship;
struct ai_info;
#define HUD_COLOR_PRESET_1 0
#define HUD_COLOR_PRESET_2 1
#define HUD_COLOR_PRESET_3 2
#define NUM_HUD_COLOR_PRESETS 3 // Number of default colors. Keep this up to date.
// specify the max distance that the radar should detect objects
// Index in Radar_ranges[] array to get values
#define RR_MAX_RANGES 3 // keep up to date
#define RR_SHORT 0
#define RR_LONG 1
#define RR_INFINITY 2
extern float Radar_ranges[RR_MAX_RANGES];
extern const char *Radar_range_text(int range_num);
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<int, std::pair<std::pair<SCP_string, int>, SCP_string>> gauge_map;
};
/*!
* @brief Vector for storing the filenames of hud preset files
* @note main definition in hudconfig.cpp
*/
extern SCP_vector<SCP_string> HC_preset_filenames;
extern SCP_vector<SCP_string> observer_visible_gauges;
extern const int HC_gauge_config_coords[GR_NUM_RESOLUTIONS][4];
extern int HC_resize_mode;
/*!
* @brief get the gauge pointer for the given gauge index
*/
HudGauge* hud_config_get_gauge_pointer(const SCP_string& gauge_id);
/**
* @brief Contains core HUD configuration data
* @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
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
SCP_unordered_map<SCP_string, color> gauge_colors; // Gauge-specific colors
SCP_unordered_map<SCP_string, bool> show_flags_map; // Show/hide state for gauges
SCP_unordered_map<SCP_string, bool> popup_flags_map; // Popup state for gauges
SCP_unordered_map<SCP_string, int> gauge_types; // Gauge types used for backup colors for custom gauges to preseve old behavior
static color default_white_color()
{
color white;
gr_init_alphacolor(&white, 255, 255, 255, 255);
return white;
}
// Methods for setting and getting gauge properties where getting will return a default value
void set_gauge_color(const SCP_string& gauge_id, const color& col)
{
gauge_colors[gauge_id] = col;
}
// Get the gauge color, the color based on its 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);
// Got a match? Return it
if (it != gauge_colors.end()) {
return it->second;
}
// No match yet.. 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));
if (it != gauge_colors.end()) {
return it->second;
}
}
// Still nothing.. return white
return default_white_color();
}
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 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 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 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
}
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
bool is_gauge_shown_in_config(const SCP_string& gauge_id) const
{
HudGauge* gauge = hud_config_get_gauge_pointer(gauge_id);
return gauge && gauge->getVisibleInConfig();
}
} HUD_CONFIG_TYPE;
extern HUD_CONFIG_TYPE HUD_config;
// HUD Color presets
typedef struct hc_col {
int r, g, b;
SCP_string name;
int xstr = -1;
} hc_col;
extern hc_col HC_colors[NUM_HUD_COLOR_PRESETS];
extern int HC_default_color;
extern SCP_string HC_default_preset_file;
/**
* @struct HC_gauge_setting
* @brief Contains core HUD configuration data
*/
struct HC_gauge_setting
{
bool use_iff_color; // if the gauge uses target IFF color for its color
bool can_popup; // if the gauge can use the popup method
bool use_tag_color; // If the gauge color respects target tagging?
HC_gauge_setting(bool iff, bool cp, bool cl) : use_iff_color(iff), can_popup(cp), use_tag_color(cl){}
};
class BoundingBox {
public:
int x1, x2, y1, y2;
// Default constructor (initializes to invalid state)
constexpr BoundingBox() : x1(-1), x2(-1), y1(-1), y2(-1) {}
// Constructor
constexpr BoundingBox(int nx1, int nx2, int ny1, int ny2) : x1(nx1), x2(nx2), y1(ny1), y2(ny2) {}
// Equality operator
bool operator==(const BoundingBox& other) const
{
return x1 == other.x1 && x2 == other.x2 && y1 == other.y1 && y2 == other.y2;
}
bool isOverlapping(const BoundingBox& other) const
{
return (x2 >= other.x1 && // Not completely to the left
x1 <= other.x2 && // Not completely to the right
y2 >= other.y1 && // Not completely above
y1 <= other.y2); // Not completely below
}
// Check if the bounding box is valid (no negative coordinates)
bool isValid() const
{
return x1 >= 0 && x2 >= 0 && y1 >= 0 && y2 >= 0;
}
// Static function to check if any bounding box in an array overlaps with a new one
static bool isOverlappingAny(const SCP_vector<std::pair<SCP_string, BoundingBox>>& mouse_coords, const BoundingBox& newBox, SCP_string self_id)
{
return std::any_of(mouse_coords.begin(), mouse_coords.end(), [&](const std::pair<SCP_string, BoundingBox>& item) {
const auto& [gauge_id, bbox] = item;
if (gauge_id == self_id) {
return false; // Skip checking against itself
}
return bbox.isValid() && bbox.isOverlapping(newBox);
});
}
};
extern char HC_wingam_gauge_status_names[MAX_SQUADRON_WINGS][32];
extern SCP_vector<std::pair<SCP_string, HudGauge*>> HC_gauge_map;
extern SCP_string HC_gauge_hot;
extern SCP_string HC_gauge_selected;
extern SCP_vector<std::pair<size_t, SCP_string>> HC_available_huds;
extern int HC_chosen_hud;
extern bool HC_select_all;
extern int HC_gauge_coordinates[6]; // x1, x2, y1, y2, w, h for gauge rendering
extern SCP_vector<std::pair<SCP_string, BoundingBox>> HC_gauge_mouse_coords;
extern int HC_talking_head_frame;
extern SCP_string HC_head_anim_filename;
extern bool HC_show_default_hud;
extern std::unordered_set<SCP_string> HC_ignored_huds;
extern SCP_map<SCP_string, std::array<SCP_string, num_shield_gauge_types>> HC_hud_shield_ships;
extern SCP_map<SCP_string, SCP_vector<SCP_string>> HC_hud_primary_weapons;
extern SCP_map<SCP_string, SCP_vector<SCP_string>> HC_hud_secondary_weapons;
/*!
* @brief init hud config screen, setting up the hud preview display
*
* param[in] API_Access whether or not this method has been called from the lua api
* param[in] x the x coord to render the preview display
* param[in] y the y coord to render the preview display
* param[in] w the width to render the preview display
* param[in] h the height to render the preview display
*/
void hud_config_init(bool API_Access = false, int x = 0, int y = 0, int w = -1, int h = -1);
/*!
* @brief do a hud config frame, including rendering the preview display and checking for button presses
*
* param[in] frametime unused
* param[in] API_Access whether or not this method has been called from the lua api
* param[in] mx mouse x coordinate
* param[in] my mouse y coordinate
*/
void hud_config_do_frame(float frametime, bool API_Access = false, int mx = 0, int my = 0);
/*!
* @brief close hud config and release all the bitmaps
*
* param[in] API_Access whether or not this method has been called from the lua api
*/
void hud_config_close(bool API_Access = false);
/*!
* @brief toggles selecting of all gauges for color modification
*
* param[bool] toggle true to toggle on, false for off
* param[in] API_Access whether or not this method has been called from the lua api
*/
void hud_config_select_all_toggle(bool toggle, bool API_Access = false);
/*!
* @brief sets no gauges as selected
*/
void hud_config_select_none();
/*!
* @brief sets no gauges as selected
*/
void hud_config_select_hud(bool next);
/*!
* @brief init the list of preset files found by cfile
*/
void hud_config_preset_init();
/*!
* @brief set the entire hud to the default setting or the filename provided
*
* param[in] filename the preset filename to use as the default
*/
void hud_set_default_hud_config(player* p, const SCP_string& filename);
/*!
* @brief delete a preset file and remove it from the preset vector
*/
void hud_config_delete_preset(SCP_string filename);
/*!
* @brief set gauge flags
*
* param[in] gauge_index the gauge to modify
* param[in] on_flag if the gauge is on or off, 1 for on, 0 for off
* param[in] popup_flag if the gauge is set to popup, 1 for popup, 0 otherwise
*/
void hud_config_set_gauge_flags(const SCP_string& gauge, bool on_flag, bool popup_flag);
void hud_config_restore();
void hud_config_backup();
void hud_config_as_observer(ship *shipp,ai_info *aif);
void hud_config_as_player();
/*!
* @brief leave hud config without accepting the player's changes
*/
void hud_config_cancel(bool change_state = true);
/*!
* @brief leave hud config and save the player's changes
*/
void hud_config_commit();
void hud_config_record_color(int color);
void hud_config_set_color(int color);
/*!
* @brief load a preset file to the hud gauges
*/
void hud_config_color_load(const char *name);
/*!
* @brief save the current hud gauges settings to a preset file
*/
void hud_config_color_save(const char* name, int version = 2);
/*!
* @brief same as hud_config_covert_coords but only returns the scale and doesn't convert any coords
*/
float hud_config_get_scale(int baseW, int baseH);
/*!
* @brief converts a set of coordinates to HUD Config's rendering coordinates
*/
std::pair<int, int> hud_config_convert_coords(int x, int y, float scale);
/*!
* @brief converts a set of coordinates to HUD Config's rendering coordinates
*/
std::pair<float, float> hud_config_convert_coords(float x, float y, float scale);
/*!
* @brief convert the given HUD gauge position coordinates to a set more appropriate for the HUD Config UI and return the smaller scale value used
* so that other offsets and positions can be multiplied in turn
*/
std::tuple<int, int, float> hud_config_convert_coord_sys(int x, int y, int baseW, int baseH);
/*!
* @brief convert the given HUD gauge position coordinates to a set more appropriate for the HUD Config UI and return
* the smaller scale value used so that other offsets and positions can be multiplied in turn
*/
std::tuple<float, float, float> hud_config_convert_coord_sys(float x, float y, int baseW, int baseH);
/*!
* @brief save gauge coords during rendering time so hud config can check if the mouse is hovering over the gauge
*/
void hud_config_set_mouse_coords(const SCP_string& gauge_id, int x1, int x2, int y1, int y2);
/*!
* @brief Function to calculate screen coordinates based on angle. Used for radial positioning gauges
*/
std::pair<float, float> hud_config_calc_coords_from_angle(float angle_degrees, int centerX, int centerY, float radius);
/*!
* @brief try to find an angle with no overlapping mouse coordinates for target-related gauges
*/
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