Files
Goober5000andClaude Opus 4.7 a168f5a9c0 Store the support rearm pool sparsely (#7758)
`support_ship_info::rearm_weapon_pool` was a dense per-weapon-class array where -1 = unlimited (the default), 0 = not rearmable, and >0 = remaining stockpile.  Convert it to a sparse per-team map:

  `std::array<SCP_map<int, int>, MAX_TVT_TEAMS> rearm_weapon_pool;`
  `int rearm_pool_default() const;  // -1, or 0 under rearm_pool_from_loadout`

Mission-file format and save output are unchanged.  Also tightens the two weapon-class bounds checks in ship.cpp.  This removes another `MAX_WEAPON_TYPES` sized structure on the way to lifting the weapon class limit.

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-09-08 10:50:22 -04:00

433 lines
14 KiB
C++

#include "stdafx.h"
#include "FRED.h"
#include "FREDDoc.h"
#include "supportrearmdlg.h"
#include "mission/missionparse.h"
#include "weapon/weapon.h"
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CSupportRearmDlg::CSupportRearmDlg(CWnd* pParent) : CDialog(CSupportRearmDlg::IDD, pParent)
{
m_disallow_support_ships = FALSE;
m_limit_rearm_to_pool = FALSE;
m_support_repairs_hull = FALSE;
m_disallow_support_rearm = FALSE;
m_allow_weapon_precedence = FALSE;
m_rearm_pool_from_loadout = FALSE;
m_max_hull_repair_val = 0.0f;
m_max_subsys_repair_val = 100.0f;
m_weapon_pool_amount = 0;
m_rearm_pool_team = 0;
}
void CSupportRearmDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Check(pDX, IDC_DISALLOW_SUPPORT_SHIPS, m_disallow_support_ships);
DDX_Check(pDX, IDC_LIMIT_SUPPORT_REARM_TO_POOL, m_limit_rearm_to_pool);
DDX_Check(pDX, IDC_SUPPORT_REPAIRS_HULL, m_support_repairs_hull);
DDX_Check(pDX, IDC_DISALLOW_SUPPORT_REARM, m_disallow_support_rearm);
DDX_Check(pDX, IDC_ALLOW_SUPPORT_REARM_PRECEDENCE, m_allow_weapon_precedence);
DDX_Check(pDX, IDC_SUPPORT_REARM_POOL_FROM_LOADOUT, m_rearm_pool_from_loadout);
DDX_Text(pDX, IDC_MAX_HULL_REPAIR_VAL, m_max_hull_repair_val);
DDV_MinMaxFloat(pDX, m_max_hull_repair_val, 0.0f, 100.0f);
DDX_Text(pDX, IDC_MAX_SUBSYS_REPAIR_VAL, m_max_subsys_repair_val);
DDV_MinMaxFloat(pDX, m_max_subsys_repair_val, 0.0f, 100.0f);
DDX_Text(pDX, IDC_SUPPORT_REARM_POOL_AMOUNT, m_weapon_pool_amount);
DDX_CBIndex(pDX, IDC_SUPPORT_REARM_POOL_TEAM, m_rearm_pool_team);
}
BEGIN_MESSAGE_MAP(CSupportRearmDlg, CDialog)
ON_LBN_SELCHANGE(IDC_SUPPORT_REARM_WEAPON_LIST, OnSelchangeWeaponList)
ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_AMOUNT, OnSetPoolAmount)
ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_UNLIMITED, OnSetPoolUnlimited)
ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ZERO, OnSetPoolZero)
ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ALL_AMOUNT, OnSetAllPoolAmount)
ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ALL_UNLIMITED, OnSetAllPoolUnlimited)
ON_BN_CLICKED(IDC_SUPPORT_REARM_SET_ALL_ZERO, OnSetAllPoolZero)
ON_CBN_SELCHANGE(IDC_SUPPORT_REARM_POOL_TEAM, OnSelchangePoolTeam)
ON_BN_CLICKED(IDC_DISALLOW_SUPPORT_SHIPS, OnOptionChanged)
ON_BN_CLICKED(IDC_SUPPORT_REPAIRS_HULL, OnOptionChanged)
ON_BN_CLICKED(IDC_DISALLOW_SUPPORT_REARM, OnOptionChanged)
ON_BN_CLICKED(IDC_LIMIT_SUPPORT_REARM_TO_POOL, OnOptionChanged)
ON_BN_CLICKED(IDC_SUPPORT_REARM_POOL_FROM_LOADOUT, OnRearmPoolFromLoadoutChanged)
ON_BN_CLICKED(IDC_ALLOW_SUPPORT_REARM_PRECEDENCE, OnOptionChanged)
ON_WM_DRAWITEM()
END_MESSAGE_MAP()
BOOL CSupportRearmDlg::OnInitDialog()
{
m_disallow_support_ships = (The_mission.support_ships.max_support_ships == 0) ? TRUE : FALSE;
m_limit_rearm_to_pool = The_mission.flags[Mission::Mission_Flags::Limited_support_rearm_pool] ? TRUE : FALSE;
m_support_repairs_hull = The_mission.flags[Mission::Mission_Flags::Support_repairs_hull] ? TRUE : FALSE;
m_disallow_support_rearm = The_mission.support_ships.disallow_rearm ? TRUE : FALSE;
m_allow_weapon_precedence = The_mission.support_ships.allow_rearm_weapon_precedence ? TRUE : FALSE;
m_rearm_pool_from_loadout = The_mission.support_ships.rearm_pool_from_loadout ? TRUE : FALSE;
m_max_hull_repair_val = The_mission.support_ships.max_hull_repair_val;
m_max_subsys_repair_val = The_mission.support_ships.max_subsys_repair_val;
m_rearm_weapon_pool = The_mission.support_ships.rearm_weapon_pool;
m_rearm_pool_team = 0;
CDialog::OnInitDialog();
populate_team_list();
populate_weapon_list();
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list != nullptr && weapon_list->GetCount() > 0) {
weapon_list->SetCurSel(0);
}
update_weapon_amount_display();
UpdateData(FALSE);
update_control_states();
return TRUE;
}
void CSupportRearmDlg::populate_team_list()
{
auto* team_combo = (CComboBox*)GetDlgItem(IDC_SUPPORT_REARM_POOL_TEAM);
if (team_combo == nullptr) {
return;
}
team_combo->ResetContent();
for (int i = 0; i < Num_teams && i < MAX_TVT_TEAMS; ++i) {
CString text;
text.Format("Team %d", i + 1);
team_combo->AddString(text);
}
if (team_combo->GetCount() > 0) {
team_combo->SetCurSel(m_rearm_pool_team);
}
}
CString CSupportRearmDlg::format_weapon_pool_entry(int weapon_class) const
{
CString text;
const auto& wi = Weapon_info[weapon_class];
int amount = m_rearm_weapon_pool[m_rearm_pool_team].value_or(weapon_class, rearm_pool_default());
if (wi.disallow_rearm) {
text.Format("%s - 0 (disabled by weapon settings)", wi.name);
return text;
}
if (amount < 0) {
text.Format("%s - Unlimited", wi.name);
} else {
text.Format("%s - %d", wi.name, amount);
}
return text;
}
void CSupportRearmDlg::populate_weapon_list()
{
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list == nullptr) {
return;
}
weapon_list->ResetContent();
for (int i = 0; i < weapon_info_size(); ++i) {
if (!Weapon_info[i].wi_flags[Weapon::Info_Flags::Player_allowed]) {
continue;
}
int list_index = weapon_list->AddString(format_weapon_pool_entry(i));
weapon_list->SetItemData(list_index, i);
}
}
int CSupportRearmDlg::get_selected_weapon_class() const
{
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list == nullptr) {
return -1;
}
int sel = weapon_list->GetCurSel();
if (sel == LB_ERR) {
return -1;
}
return (int)weapon_list->GetItemData(sel);
}
void CSupportRearmDlg::update_weapon_amount_display()
{
int weapon_class = get_selected_weapon_class();
if (weapon_class < 0 || weapon_class >= weapon_info_size()) {
m_weapon_pool_amount = 0;
} else {
m_weapon_pool_amount = m_rearm_weapon_pool[m_rearm_pool_team].value_or(weapon_class, rearm_pool_default());
}
UpdateData(FALSE);
}
void CSupportRearmDlg::set_selected_weapon_amount(int amount)
{
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list == nullptr) {
return;
}
const int sel = weapon_list->GetCurSel();
const int weapon_class = get_selected_weapon_class();
if (sel == LB_ERR || weapon_class < 0 || weapon_class >= weapon_info_size()) {
return;
}
if (Weapon_info[weapon_class].disallow_rearm) {
amount = 0;
} else if (amount < 0) {
amount = -1;
}
set_weapon_pool_entry(weapon_class, amount);
weapon_list->DeleteString(sel);
weapon_list->InsertString(sel, format_weapon_pool_entry(weapon_class));
weapon_list->SetItemData(sel, weapon_class);
weapon_list->SetCurSel(sel);
update_weapon_amount_display();
}
void CSupportRearmDlg::set_weapon_pool_entry(int weapon_class, int amount)
{
// don't store the default value; absence means the default
auto &team_pool = m_rearm_weapon_pool[m_rearm_pool_team];
if (amount == rearm_pool_default()) {
team_pool.erase(weapon_class);
} else {
team_pool[weapon_class] = amount;
}
}
void CSupportRearmDlg::set_all_weapon_amount(int amount)
{
const int normalized_amount = (amount < 0) ? -1 : amount;
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list != nullptr) {
for (int i = 0; i < weapon_list->GetCount(); ++i) {
const int weapon_class = static_cast<int>(weapon_list->GetItemData(i));
if (weapon_class < 0 || weapon_class >= weapon_info_size()) {
continue;
}
if (Weapon_info[weapon_class].disallow_rearm) {
set_weapon_pool_entry(weapon_class, 0);
} else {
set_weapon_pool_entry(weapon_class, normalized_amount);
}
}
const int previous_sel = weapon_list->GetCurSel();
populate_weapon_list();
if (previous_sel != LB_ERR && previous_sel < weapon_list->GetCount()) {
weapon_list->SetCurSel(previous_sel);
} else if (weapon_list->GetCount() > 0) {
weapon_list->SetCurSel(0);
}
}
update_weapon_amount_display();
}
void CSupportRearmDlg::update_control_states()
{
const bool support_enabled = (m_disallow_support_ships == FALSE);
const bool rearm_allowed = support_enabled && (m_disallow_support_rearm == FALSE);
const bool pool_controls_enabled =
rearm_allowed && (m_limit_rearm_to_pool != FALSE) && (m_rearm_pool_from_loadout == FALSE);
const int selected_weapon_class = get_selected_weapon_class();
const bool selected_weapon_disallow_rearm =
(selected_weapon_class >= 0 && selected_weapon_class < weapon_info_size() &&
Weapon_info[selected_weapon_class].disallow_rearm);
const bool right_controls_enabled = pool_controls_enabled && !selected_weapon_disallow_rearm;
auto enable = [this](int control_id, bool state) {
if (auto* ctrl = GetDlgItem(control_id)) {
ctrl->EnableWindow(state ? TRUE : FALSE);
}
};
enable(IDC_SUPPORT_REPAIRS_HULL, support_enabled);
enable(IDC_MAX_HULL_REPAIR_VAL, support_enabled);
enable(IDC_MAX_SUBSYS_REPAIR_VAL, support_enabled);
enable(IDC_DISALLOW_SUPPORT_REARM, support_enabled);
const bool limited_pool_enabled = rearm_allowed && (m_limit_rearm_to_pool != FALSE);
enable(IDC_LIMIT_SUPPORT_REARM_TO_POOL, rearm_allowed);
enable(IDC_SUPPORT_REARM_POOL_FROM_LOADOUT, limited_pool_enabled);
enable(IDC_ALLOW_SUPPORT_REARM_PRECEDENCE, limited_pool_enabled);
enable(IDC_SUPPORT_REARM_WEAPON_LIST, pool_controls_enabled);
enable(IDC_SUPPORT_REARM_POOL_TEAM, pool_controls_enabled);
enable(IDC_SUPPORT_REARM_POOL_AMOUNT, right_controls_enabled);
enable(IDC_SUPPORT_REARM_SET_AMOUNT, right_controls_enabled);
enable(IDC_SUPPORT_REARM_SET_UNLIMITED, right_controls_enabled);
enable(IDC_SUPPORT_REARM_SET_ZERO, right_controls_enabled);
enable(IDC_SUPPORT_REARM_SET_ALL_AMOUNT, right_controls_enabled);
enable(IDC_SUPPORT_REARM_SET_ALL_UNLIMITED, right_controls_enabled);
enable(IDC_SUPPORT_REARM_SET_ALL_ZERO, right_controls_enabled);
}
void CSupportRearmDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
if (nIDCtl != IDC_SUPPORT_REARM_WEAPON_LIST || lpDrawItemStruct == nullptr) {
CDialog::OnDrawItem(nIDCtl, lpDrawItemStruct);
return;
}
if (lpDrawItemStruct->itemID == static_cast<UINT>(-1)) {
return;
}
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list == nullptr) {
return;
}
CDC* dc = CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rect(lpDrawItemStruct->rcItem);
const bool selected = (lpDrawItemStruct->itemState & ODS_SELECTED) != 0;
const bool focus = (lpDrawItemStruct->itemState & ODS_FOCUS) != 0;
const int weapon_class = static_cast<int>(weapon_list->GetItemData(lpDrawItemStruct->itemID));
const bool disallow_rearm =
(weapon_class >= 0 && weapon_class < weapon_info_size()) ? Weapon_info[weapon_class].disallow_rearm : false;
COLORREF bk = selected ? GetSysColor(COLOR_HIGHLIGHT) : GetSysColor(COLOR_WINDOW);
COLORREF text = selected ? GetSysColor(COLOR_HIGHLIGHTTEXT) : GetSysColor(COLOR_WINDOWTEXT);
if (disallow_rearm) {
text = GetSysColor(COLOR_GRAYTEXT);
}
dc->FillSolidRect(&rect, bk);
dc->SetBkMode(TRANSPARENT);
dc->SetTextColor(text);
CString item_text;
weapon_list->GetText(lpDrawItemStruct->itemID, item_text);
CRect text_rect = rect;
text_rect.DeflateRect(2, 0);
dc->DrawText(item_text, &text_rect, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
if (focus) {
dc->DrawFocusRect(&rect);
}
}
void CSupportRearmDlg::OnOptionChanged()
{
if (!UpdateData(TRUE)) {
return;
}
update_control_states();
}
void CSupportRearmDlg::OnRearmPoolFromLoadoutChanged()
{
if (!UpdateData(TRUE)) {
return;
}
// this flag changes the pool's default amount, so the displayed entries must be redrawn
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list != nullptr) {
const int previous_sel = weapon_list->GetCurSel();
populate_weapon_list();
if (previous_sel != LB_ERR && previous_sel < weapon_list->GetCount()) {
weapon_list->SetCurSel(previous_sel);
} else if (weapon_list->GetCount() > 0) {
weapon_list->SetCurSel(0);
}
update_weapon_amount_display();
}
update_control_states();
}
void CSupportRearmDlg::OnSelchangeWeaponList()
{
update_weapon_amount_display();
update_control_states();
}
void CSupportRearmDlg::OnSelchangePoolTeam()
{
if (!UpdateData(TRUE)) {
return;
}
populate_weapon_list();
auto* weapon_list = (CListBox*)GetDlgItem(IDC_SUPPORT_REARM_WEAPON_LIST);
if (weapon_list != nullptr && weapon_list->GetCount() > 0) {
weapon_list->SetCurSel(0);
}
update_weapon_amount_display();
update_control_states();
}
void CSupportRearmDlg::OnSetPoolAmount()
{
if (!UpdateData(TRUE)) {
return;
}
set_selected_weapon_amount(m_weapon_pool_amount);
}
void CSupportRearmDlg::OnSetPoolUnlimited()
{
set_selected_weapon_amount(-1);
}
void CSupportRearmDlg::OnSetPoolZero()
{
set_selected_weapon_amount(0);
}
void CSupportRearmDlg::OnSetAllPoolAmount()
{
if (!UpdateData(TRUE)) {
return;
}
set_all_weapon_amount(m_weapon_pool_amount);
}
void CSupportRearmDlg::OnSetAllPoolUnlimited()
{
set_all_weapon_amount(-1);
}
void CSupportRearmDlg::OnSetAllPoolZero()
{
set_all_weapon_amount(0);
}
void CSupportRearmDlg::OnOK()
{
if (!UpdateData(TRUE)) {
return;
}
The_mission.support_ships.max_support_ships = (m_disallow_support_ships != FALSE) ? 0 : -1;
The_mission.flags.set(Mission::Mission_Flags::Limited_support_rearm_pool, m_limit_rearm_to_pool != FALSE);
The_mission.flags.set(Mission::Mission_Flags::Support_repairs_hull, m_support_repairs_hull != FALSE);
The_mission.support_ships.disallow_rearm = (m_disallow_support_rearm != FALSE);
The_mission.support_ships.allow_rearm_weapon_precedence = (m_allow_weapon_precedence != FALSE);
The_mission.support_ships.rearm_pool_from_loadout = (m_rearm_pool_from_loadout != FALSE);
The_mission.support_ships.max_hull_repair_val = m_max_hull_repair_val;
The_mission.support_ships.max_subsys_repair_val = m_max_subsys_repair_val;
The_mission.support_ships.rearm_weapon_pool = m_rearm_weapon_pool;
CDialog::OnOK();
}