mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
Ship_info is already an SCP_vector but MAX_SHIP_CLASSES (500) is held in place by fixed-size structures throughout the engine and editors. This converts the seven file-format-adjacent arrays whose persistence touches the .csg campaign save and .plr pilot file formats: campaign::ships_allowed [MAX_SHIP_CLASSES] -> SCP_vector<ubyte> campaign::weapons_allowed [MAX_WEAPON_TYPES] -> SCP_vector<ubyte> loadout_data::ship_pool [MAX_SHIP_CLASSES] -> SCP_vector<int> loadout_data::weapon_pool [MAX_WEAPON_TYPES] -> SCP_vector<int> scoring_struct::kills [MAX_SHIP_CLASSES] -> SCP_vector<int> scoring_struct::m_kills [MAX_SHIP_CLASSES] -> SCP_vector<int> scoring_struct::m_okKills [MAX_SHIP_CLASSES] -> SCP_vector<int> The CSG and PLR file formats already store explicit counts and key by ship/weapon names, so no on-disk format change is needed. Existing read/write loops already iterated by ship_info_size() / weapon_info_size() and just compile through against the new vector types. Vectors are sized to ship_info_size() / weapon_info_size() in scoring_struct::init(), mission_campaign_clear(), player_loadout_init(), and the campaign-pool reset paths in csg.cpp and multi_campaign.cpp. All of these run after weapon_init() / ship_init(), so the sizing invariant holds. scoring_struct globals constructed at static-init time start with empty vectors; first index access is always post-table-parse. Multiplayer stats sync (multimsgs.cpp) now derives offsets and counts from the vector size; added USHRT_MAX assertions on the sender side since the wire protocol packs offset/count as USHORT. The FSTracker sync (multi_fstracker.cpp) clamps vmt->num_ships down to the local mod's kills.size() before transmission and guards the receive-side write so a tracker payload with more ship slots than this mod has won't overrun the vector. The MAX_SHIP_CLASSES cap itself stays in place at ship.cpp:2327; this is one prerequisite toward lifting it. The other ~25 fixed-size ship-class-indexed structures (team_data, Ss_pool_teams, Wl_ships, FRED2 editor arrays, qtFRED Editor::_ship_usage, etc.) remain to be addressed in follow-up PRs. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
293 lines
7.1 KiB
C++
293 lines
7.1 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.
|
|
*
|
|
*/
|
|
|
|
|
|
|
|
|
|
#include "globalincs/systemvars.h"
|
|
#include "hud/hud.h"
|
|
#include "network/multi.h"
|
|
#include "pilotfile/pilotfile.h"
|
|
#include "playerman/player.h"
|
|
#include "stats/stats.h"
|
|
|
|
static const scoring_struct *Player_stats;
|
|
// coverity[GLOBAL_INIT_ORDER] -- safe; default-constructed, no cross-TU dependencies
|
|
static scoring_struct All_time_ever_stats;
|
|
|
|
void show_stats_init()
|
|
{
|
|
if (Game_mode & GM_MULTIPLAYER) {
|
|
set_player_stats(MY_NET_PLAYER_NUM);
|
|
} else {
|
|
Player_stats = &Player->stats;
|
|
}
|
|
|
|
// multi only has current stats and all-time-ever stats, so this is only needed in single-player
|
|
Pilot.export_stats(&All_time_ever_stats);
|
|
}
|
|
|
|
// write out the label for each stat
|
|
void show_stats_label(StatsType type, int sx, int sy, int dy)
|
|
{
|
|
char text[NAME_LENGTH];
|
|
|
|
if (type == StatsType::MISSION_STATS)
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Mission Stats", 114));
|
|
else
|
|
gr_printf_menu(sx, sy, "%s", XSTR("All Time Stats", 128));
|
|
sy += 2 * dy;
|
|
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Total kills", 115));
|
|
sy += 2 * dy;
|
|
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Primary weapon shots", 116));
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Primary weapon hits", 117));
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Primary friendly hits", 118));
|
|
sy += dy;
|
|
|
|
strcpy_s(text, XSTR("Primary hit %", 119));
|
|
replace_one(text, "%%", "%", NAME_LENGTH - 1);
|
|
gr_printf_menu(sx, sy, "%s", text);
|
|
sy += dy;
|
|
|
|
strcpy_s(text, XSTR("Primary friendly hit %", 120));
|
|
replace_one(text, "%%", "%", NAME_LENGTH - 1);
|
|
gr_printf_menu(sx, sy, "%s", text);
|
|
sy += 2 * dy;
|
|
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Secondary weapon shots", 121));
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Secondary weapon hits", 122));
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Secondary friendly hits", 123));
|
|
sy += dy;
|
|
|
|
strcpy_s(text, XSTR("Secondary hit %", 124));
|
|
replace_one(text, "%%", "%", NAME_LENGTH - 1);
|
|
gr_printf_menu(sx, sy, "%s", text);
|
|
sy += dy;
|
|
|
|
strcpy_s(text, XSTR("Secondary friendly hit %", 125));
|
|
replace_one(text, "%%", "%", NAME_LENGTH - 1);
|
|
gr_printf_menu(sx, sy, "%s", text);
|
|
sy += 2 * dy;
|
|
|
|
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Assists", 126));
|
|
sy += 2 * dy;
|
|
|
|
|
|
if (Game_mode & GM_MULTIPLAYER)
|
|
{
|
|
if (type == StatsType::MISSION_STATS)
|
|
{
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Player Deaths", 127));
|
|
sy += 2 * dy;
|
|
}
|
|
|
|
if (type == StatsType::MISSION_STATS)
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Mission score", 1526));
|
|
else
|
|
gr_printf_menu(sx, sy, "%s", XSTR("Score", 1527));
|
|
}
|
|
}
|
|
|
|
void show_stats_numbers(StatsType type, int sx, int sy, int dy)
|
|
{
|
|
switch (type)
|
|
{
|
|
case StatsType::MISSION_STATS:
|
|
show_stats_numbers(*Player_stats, true, sx, sy, dy);
|
|
break;
|
|
|
|
case StatsType::ALL_TIME_CAMPAIGN_STATS:
|
|
Assertion(!(Game_mode & GM_MULTIPLAYER), "ALL_TIME_CAMPAIGN_STATS are not stored for multiplayer!");
|
|
show_stats_numbers(*Player_stats, false, sx, sy, dy);
|
|
break;
|
|
|
|
case StatsType::ALL_TIME_EVER_STATS:
|
|
if (Game_mode & GM_MULTIPLAYER)
|
|
show_stats_numbers(*Player_stats, false, sx, sy, dy); // in multiplayer, the all-time-ever stats are stored in the player
|
|
else
|
|
show_stats_numbers(All_time_ever_stats, false, sx, sy, dy);
|
|
break;
|
|
|
|
default:
|
|
UNREACHABLE("Unhandled StatsType %d in show_stats_numbers()", static_cast<int>(type));
|
|
}
|
|
}
|
|
|
|
int stats_get_kills(StatsType type, int ship_class)
|
|
{
|
|
switch (type)
|
|
{
|
|
case StatsType::MISSION_STATS:
|
|
return stats_get_kills(*Player_stats, true, ship_class);
|
|
|
|
case StatsType::ALL_TIME_CAMPAIGN_STATS:
|
|
return stats_get_kills(*Player_stats, false, ship_class);
|
|
|
|
case StatsType::ALL_TIME_EVER_STATS:
|
|
if (Game_mode & GM_MULTIPLAYER)
|
|
return stats_get_kills(*Player_stats, false, ship_class); // in multiplayer, the all-time-ever stats are stored in the player
|
|
else
|
|
return stats_get_kills(All_time_ever_stats, false, ship_class);
|
|
|
|
default:
|
|
UNREACHABLE("Unhandled StatsType %d in stats_get_kills()", static_cast<int>(type));
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void show_stats_numbers(const scoring_struct &stats, bool use_m_stats, int sx, int sy, int dy)
|
|
{
|
|
float pct;
|
|
int kill_count_ok, p_shots_fired, p_shots_hit, p_bonehead_hits, s_shots_fired, s_shots_hit, s_bonehead_hits, assists, score;
|
|
|
|
if (use_m_stats)
|
|
{
|
|
kill_count_ok = stats.m_kill_count_ok;
|
|
p_shots_fired = stats.mp_shots_fired;
|
|
p_shots_hit = stats.mp_shots_hit;
|
|
p_bonehead_hits = stats.mp_bonehead_hits;
|
|
s_shots_fired = stats.ms_shots_fired;
|
|
s_shots_hit = stats.ms_shots_hit;
|
|
s_bonehead_hits = stats.ms_bonehead_hits;
|
|
assists = stats.m_assists;
|
|
score = stats.m_score;
|
|
}
|
|
else
|
|
{
|
|
kill_count_ok = stats.kill_count_ok;
|
|
p_shots_fired = stats.p_shots_fired;
|
|
p_shots_hit = stats.p_shots_hit;
|
|
p_bonehead_hits = stats.p_bonehead_hits;
|
|
s_shots_fired = stats.s_shots_fired;
|
|
s_shots_hit = stats.s_shots_hit;
|
|
s_bonehead_hits = stats.s_bonehead_hits;
|
|
assists = stats.assists;
|
|
score = stats.score;
|
|
}
|
|
|
|
sy += 2 * dy;
|
|
|
|
// kills stats
|
|
gr_printf_menu(sx, sy, "%d", kill_count_ok);
|
|
sy += 2 * dy;
|
|
|
|
|
|
// primary weapon stats
|
|
gr_printf_menu(sx, sy, "%u", p_shots_fired);
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%u", p_shots_hit);
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%u", p_bonehead_hits);
|
|
sy += dy;
|
|
|
|
if (p_shots_fired > 0)
|
|
pct = 100.0f * i2fl(p_shots_hit) / i2fl(p_shots_fired);
|
|
else
|
|
pct = 0.0f;
|
|
gr_printf_menu(sx, sy, "%d%%", fl2i(pct));
|
|
sy += dy;
|
|
|
|
if (p_shots_fired > 0)
|
|
pct = 100.0f * i2fl(p_bonehead_hits) / i2fl(p_shots_fired);
|
|
else
|
|
pct = 0.0f;
|
|
gr_printf_menu(sx, sy, "%d%%", fl2i(pct));
|
|
sy += 2 * dy;
|
|
|
|
|
|
// secondary weapon stats
|
|
gr_printf_menu(sx, sy, "%u", s_shots_fired);
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%u", s_shots_hit);
|
|
sy += dy;
|
|
|
|
gr_printf_menu(sx, sy, "%u", s_bonehead_hits);
|
|
sy += dy;
|
|
|
|
if (s_shots_fired > 0)
|
|
pct = 100.0f * i2fl(s_shots_hit) / i2fl(s_shots_fired);
|
|
else
|
|
pct = 0.0f;
|
|
gr_printf_menu(sx, sy, "%d%%", fl2i(pct));
|
|
sy += dy;
|
|
|
|
if (s_shots_fired > 0)
|
|
pct = 100.0f * i2fl(s_bonehead_hits) / i2fl(s_shots_fired);
|
|
else
|
|
pct = 0.0f;
|
|
gr_printf_menu(sx, sy, "%d%%", fl2i(pct));
|
|
sy += 2 * dy;
|
|
|
|
|
|
// assists and player rescues (respawns)
|
|
gr_printf_menu(sx, sy, "%d", assists);
|
|
sy += 2 * dy;
|
|
|
|
|
|
if (Game_mode & GM_MULTIPLAYER)
|
|
{
|
|
if (use_m_stats)
|
|
{
|
|
gr_printf_menu(sx, sy, "%d", stats.m_player_deaths);
|
|
sy += 2 * dy;
|
|
}
|
|
|
|
// score
|
|
gr_printf_menu(sx, sy, "%d", score);
|
|
}
|
|
}
|
|
|
|
int stats_get_kills(const scoring_struct &stats, bool use_m_stats, int ship_class)
|
|
{
|
|
Assertion(stats.kills.in_bounds(ship_class), "ship_class is out of bounds!");
|
|
|
|
if (use_m_stats)
|
|
return stats.m_okKills[ship_class];
|
|
else
|
|
return stats.kills[ship_class];
|
|
}
|
|
|
|
void show_stats_close()
|
|
{
|
|
}
|
|
|
|
// initialize the statistics portion of the player structure for multiplayer. Only the host of
|
|
// a netgame needs to be doing this (and if fact, only he *should* be doing this)
|
|
void init_multiplayer_stats( )
|
|
{
|
|
scoring_struct *ptr;
|
|
|
|
for (int idx=0; idx < MAX_PLAYERS; idx++) {
|
|
ptr = &Players[idx].stats;
|
|
scoring_level_init( ptr );
|
|
}
|
|
}
|
|
|
|
void set_player_stats(int pid)
|
|
{
|
|
Player_stats = &Net_players[pid].m_player->stats;
|
|
}
|