Files
fs2open.github.com/code/pilotfile/csg.cpp
T
Mike Nelson f1f95717d1 Support Custom Gauges in HUD Config UI (#6670)
* Support coloring custom gauges

* remove NULL
2025-05-14 13:18:03 -04:00

2041 lines
54 KiB
C++

#include "controlconfig/controlsconfig.h"
#include "controlconfig/presets.h"
#include "cutscene/cutscenes.h"
#include "freespace.h"
#include "gamesnd/eventmusic.h"
#include "hud/hudconfig.h"
#include "io/joy.h"
#include "io/mouse.h"
#include "menuui/techmenu.h"
#include "mission/missioncampaign.h"
#include "mission/missionload.h"
#include "missionui/missionscreencommon.h"
#include "missionui/missionshipchoice.h"
#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"
#include "stats/medals.h"
#include "utils/string_utils.h"
#include "weapon/weapon.h"
#define REDALERT_INTERNAL
#include "missionui/redalert.h"
#include <iostream>
#include <sstream>
#include <limits>
// this is kinda tricky
enum class TechroomState : ubyte
{
DEFAULT = 0,
ADDED = 1,
REMOVED = 2
};
void pilotfile::csg_read_flags()
{
// tips?
p->tips = (int)cfread_ubyte(cfp);
// avoid having to read everything to get the rank
if (csg_ver >= 5) {
p->stats.rank = cfread_int(cfp);
}
}
void pilotfile::csg_write_flags()
{
startSection(Section::Flags);
// tips
cfwrite_ubyte((ubyte)p->tips, cfp);
// avoid having to read everything to get the rank
cfwrite_int(p->stats.rank, cfp);
endSection();
}
void pilotfile::csg_read_info()
{
char t_string[NAME_LENGTH+1] = { '\0' };
index_list_t ilist;
int idx, list_size = 0;
ubyte allowed = 0;
if ( !m_have_flags ) {
throw "Info before Flags!";
}
//
// NOTE: lists may contain missing/invalid entries for current data
// this is not necessarily fatal
//
// ship list (NOTE: may contain more than MAX_SHIP_CLASSES)
list_size = cfread_int(cfp);
for (idx = 0; idx < list_size; idx++) {
cfread_string_len(t_string, NAME_LENGTH, cfp);
ilist.name = t_string;
ilist.index = ship_info_lookup(t_string);
ship_list.push_back(ilist);
}
// weapon list (NOTE: may contain more than MAX_WEAPON_TYPES)
list_size = cfread_int(cfp);
for (idx = 0; idx < list_size; idx++) {
cfread_string_len(t_string, NAME_LENGTH, cfp);
ilist.name = t_string;
ilist.index = weapon_info_lookup(t_string);
weapon_list.push_back(ilist);
}
// intel list (NOTE: may contain more than MAX_INTEL_ENTRIES)
list_size = cfread_int(cfp);
for (idx = 0; idx < list_size; idx++) {
cfread_string_len(t_string, NAME_LENGTH, cfp);
ilist.name = t_string;
ilist.index = intel_info_lookup(t_string);
intel_list.push_back(ilist);
}
// medals list (NOTE: may contain more than Num_medals)
list_size = cfread_int(cfp);
for (idx = 0; idx < list_size; idx++) {
cfread_string_len(t_string, NAME_LENGTH, cfp);
ilist.name = t_string;
ilist.index = medals_info_lookup(t_string);
medals_list.push_back(ilist);
}
// last ship flown (index into ship_list)
idx = cfread_int(cfp);
// check the idx is within bounds
Assertion ((idx < (int)ship_list.size()), "Campaign file contains an incorrect value for the last flown ship class. No data in ship_list for ship number %d.", idx);
if (idx >= (int)ship_list.size())
idx = -1;
else if (idx != -1)
p->last_ship_flown_si_index = ship_list[idx].index;
else
p->last_ship_flown_si_index = -1;
// progression state
Campaign.prev_mission = cfread_int(cfp);
Campaign.next_mission = cfread_int(cfp);
// check that the next mission won't be greater than the total number of missions
if (Campaign.next_mission >= Campaign.num_missions) {
Campaign.next_mission = 0; // Prevent trying to load from invalid mission data downstream
m_data_invalid = true; // Causes a warning popup to be displayed
}
// loop state
Campaign.loop_reentry = cfread_int(cfp);
Campaign.loop_enabled = cfread_int(cfp);
// missions completed
Campaign.num_missions_completed = cfread_int(cfp);
// allowed ships
list_size = (int)ship_list.size();
for (idx = 0; idx < list_size; idx++) {
allowed = cfread_ubyte(cfp);
if (allowed) {
if (ship_list[idx].index >= 0) {
Campaign.ships_allowed[ship_list[idx].index] = 1;
} else {
mprintf(("Found invalid ship \"%s\" in campaign save file. Skipping...\n", ship_list[idx].name.c_str()));
}
}
}
// allowed weapons
list_size = (int)weapon_list.size();
for (idx = 0; idx < list_size; idx++) {
allowed = cfread_ubyte(cfp);
if (allowed) {
if (weapon_list[idx].index >= 0) {
Campaign.weapons_allowed[weapon_list[idx].index] = 1;
} else {
mprintf(("Found invalid weapon \"%s\" in campaign save file. Skipping...\n",
weapon_list[idx].name.c_str()));
}
}
}
if (csg_ver >= 2) {
// single/campaign squad name & image
cfread_string_len(p->s_squad_name, NAME_LENGTH, cfp);
cfread_string_len(p->s_squad_filename, MAX_FILENAME_LEN, cfp);
}
// if anything we need/use was missing then it should be considered fatal
if (m_data_invalid) {
throw "Invalid data for CSG!";
}
}
void pilotfile::csg_write_info()
{
int idx;
startSection(Section::Info);
// ship list
cfwrite_int(ship_info_size(), cfp);
for (auto &si : Ship_info) {
cfwrite_string_len(si.name, cfp);
}
// weapon list
cfwrite_int(weapon_info_size(), cfp);
for (auto &wi : Weapon_info) {
cfwrite_string_len(wi.name, cfp);
}
// intel list
cfwrite_int(intel_info_size(), cfp);
for (auto &ii : Intel_info) {
cfwrite_string_len(ii.name, cfp);
}
// medals list
cfwrite_int((int)Medals.size(), cfp);
for (idx = 0; idx < (int)Medals.size(); idx++) {
cfwrite_string_len(Medals[idx].name, cfp);
}
// last ship flown
cfwrite_int(p->last_ship_flown_si_index, cfp);
// progression state
cfwrite_int(Campaign.prev_mission, cfp);
cfwrite_int(Campaign.next_mission, cfp);
// loop state
cfwrite_int(Campaign.loop_reentry, cfp);
cfwrite_int(Campaign.loop_enabled, cfp);
// missions completed
cfwrite_int(Campaign.num_missions_completed, cfp);
// allowed ships
for (idx = 0; idx < ship_info_size(); idx++) {
cfwrite_ubyte(Campaign.ships_allowed[idx], cfp);
}
// allowed weapons
for (idx = 0; idx < weapon_info_size(); idx++) {
cfwrite_ubyte(Campaign.weapons_allowed[idx], cfp);
}
// single/campaign squad name & image
cfwrite_string_len(p->s_squad_name, cfp);
cfwrite_string_len(p->s_squad_filename, cfp);
endSection();
}
void pilotfile::csg_read_missions()
{
int i, j, idx, list_size;
cmission *missionp;
if ( !m_have_info ) {
throw "Missions before Info!";
}
for (i = 0; i < Campaign.num_missions_completed; i++) {
idx = cfread_int(cfp);
missionp = &Campaign.missions[idx];
missionp->completed = 1;
// flags
missionp->flags = cfread_int(cfp);
// goals
missionp->goals.clear();
int num_goals = cfread_int(cfp);
for (j = 0; j < num_goals; j++) {
missionp->goals.emplace_back();
auto& stored_goal = missionp->goals.back();
cfread_string_len(stored_goal.name, NAME_LENGTH, cfp);
stored_goal.status = cfread_char(cfp);
}
// events
missionp->events.clear();
int num_events = cfread_int(cfp);
for (j = 0; j < num_events; j++) {
missionp->events.emplace_back();
auto& stored_event = missionp->events.back();
cfread_string_len(stored_event.name, NAME_LENGTH, cfp);
stored_event.status = cfread_char(cfp);
}
// variables
missionp->variables.clear();
int num_variables = cfread_int(cfp);
for (j = 0; j < num_variables; j++) {
missionp->variables.emplace_back();
auto& stored_variable = missionp->variables.back();
stored_variable.type = cfread_int(cfp);
cfread_string_len(missionp->variables[j].text, TOKEN_LENGTH, cfp);
cfread_string_len(missionp->variables[j].variable_name, TOKEN_LENGTH, cfp);
}
// scoring stats
missionp->stats.score = cfread_int(cfp);
missionp->stats.rank = cfread_int(cfp);
missionp->stats.assists = cfread_int(cfp);
missionp->stats.kill_count = cfread_int(cfp);
missionp->stats.kill_count_ok = cfread_int(cfp);
missionp->stats.bonehead_kills = cfread_int(cfp);
missionp->stats.p_shots_fired = cfread_uint(cfp);
missionp->stats.p_shots_hit = cfread_uint(cfp);
missionp->stats.p_bonehead_hits = cfread_uint(cfp);
missionp->stats.s_shots_fired = cfread_uint(cfp);
missionp->stats.s_shots_hit = cfread_uint(cfp);
missionp->stats.s_bonehead_hits = cfread_uint(cfp);
// ship kills (scoring)
list_size = (int)ship_list.size();
for (j = 0; j < list_size; j++) {
idx = cfread_int(cfp);
if (ship_list[j].index >= 0) {
missionp->stats.kills[ship_list[j].index] = idx;
}
}
// medals (scoring)
list_size = (int)medals_list.size();
for (j = 0; j < list_size; j++) {
idx = cfread_int(cfp);
if (medals_list[j].index >= 0) {
missionp->stats.medal_counts[medals_list[j].index] = idx;
}
}
}
}
void pilotfile::csg_write_missions()
{
int idx, j;
cmission *missionp;
startSection(Section::Missions);
for (idx = 0; idx < MAX_CAMPAIGN_MISSIONS; idx++) {
if (Campaign.missions[idx].completed) {
missionp = &Campaign.missions[idx];
cfwrite_int(idx, cfp);
// flags
cfwrite_int(missionp->flags, cfp);
// goals
cfwrite_int((int)missionp->goals.size(), cfp);
for (j = 0; j < (int)missionp->goals.size(); j++) {
cfwrite_string_len(missionp->goals[j].name, cfp);
cfwrite_char(missionp->goals[j].status, cfp);
}
// events
cfwrite_int((int)missionp->events.size(), cfp);
for (j = 0; j < (int)missionp->events.size(); j++) {
cfwrite_string_len(missionp->events[j].name, cfp);
cfwrite_char(missionp->events[j].status, cfp);
}
// variables
cfwrite_int((int)missionp->variables.size(), cfp);
for (j = 0; j < (int)missionp->variables.size(); j++) {
cfwrite_int(missionp->variables[j].type, cfp);
cfwrite_string_len(missionp->variables[j].text, cfp);
cfwrite_string_len(missionp->variables[j].variable_name, cfp);
}
// scoring stats
cfwrite_int(missionp->stats.score, cfp);
cfwrite_int(missionp->stats.rank, cfp);
cfwrite_int(missionp->stats.assists, cfp);
cfwrite_int(missionp->stats.kill_count, cfp);
cfwrite_int(missionp->stats.kill_count_ok, cfp);
cfwrite_int(missionp->stats.bonehead_kills, cfp);
cfwrite_uint(missionp->stats.p_shots_fired, cfp);
cfwrite_uint(missionp->stats.p_shots_hit, cfp);
cfwrite_uint(missionp->stats.p_bonehead_hits, cfp);
cfwrite_uint(missionp->stats.s_shots_fired, cfp);
cfwrite_uint(missionp->stats.s_shots_hit, cfp);
cfwrite_uint(missionp->stats.s_bonehead_hits, cfp);
// ship kills (scoring)
for (j = 0; j < ship_info_size(); j++) {
cfwrite_int(missionp->stats.kills[j], cfp);
}
// medals earned (scoring)
for (j = 0; j < (int)Medals.size(); j++) {
cfwrite_int(missionp->stats.medal_counts[j], cfp);
}
}
}
endSection();
}
void pilotfile::csg_read_techroom()
{
int idx, list_size = 0;
TechroomState state;
if ( !m_have_info ) {
throw "Techroom before Info!";
}
// visible ships
list_size = (int)ship_list.size();
for (idx = 0; idx < list_size; idx++) {
state = (TechroomState) cfread_ubyte(cfp);
if (state != TechroomState::DEFAULT) {
if (ship_list[idx].index >= 0) {
if (state == TechroomState::ADDED) {
Ship_info[ship_list[idx].index].flags.set(Ship::Info_Flags::In_tech_database);
} else if (state == TechroomState::REMOVED) {
Ship_info[ship_list[idx].index].flags.remove(Ship::Info_Flags::In_tech_database);
} else {
mprintf(("Unrecognized techroom state: %d\n", (int) state));
}
} else {
mprintf(("Found invalid ship \"%s\" in campaign save file. "
"Skipping...\n",
ship_list[idx].name.c_str()));
}
}
}
// visible weapons
list_size = (int)weapon_list.size();
for (idx = 0; idx < list_size; idx++) {
state = (TechroomState) cfread_ubyte(cfp);
if (state != TechroomState::DEFAULT) {
if (weapon_list[idx].index >= 0) {
if (state == TechroomState::ADDED) {
Weapon_info[weapon_list[idx].index].wi_flags.set(Weapon::Info_Flags::In_tech_database);
} else if (state == TechroomState::REMOVED) {
Weapon_info[weapon_list[idx].index].wi_flags.remove(Weapon::Info_Flags::In_tech_database);
} else {
mprintf(("Unrecognized techroom state: %d\n", (int) state));
}
} else {
mprintf(("Found invalid weapon \"%s\" in campaign save file. Skipping...\n",
weapon_list[idx].name.c_str()));
}
}
}
// visible intel entries
list_size = (int)intel_list.size();
for (idx = 0; idx < list_size; idx++) {
state = (TechroomState) cfread_ubyte(cfp);
if (state != TechroomState::DEFAULT) {
if (intel_list[idx].index >= 0) {
if (state == TechroomState::ADDED) {
Intel_info[intel_list[idx].index].flags |= IIF_IN_TECH_DATABASE;
} else if (state == TechroomState::REMOVED) {
Intel_info[intel_list[idx].index].flags &= ~IIF_IN_TECH_DATABASE;
} else {
mprintf(("Unrecognized techroom state: %d\n", (int) state));
}
} else {
mprintf(("Found invalid intel entry \"%s\" in campaign save file. Skipping...\n",
intel_list[idx].name.c_str()));
}
}
}
// if anything we need/use was missing then it should be considered fatal
if (m_data_invalid) {
throw "Invalid data for CSG!";
}
}
void pilotfile::csg_write_techroom()
{
TechroomState state;
startSection(Section::Techroom);
// write whether it differs from the default for ships
for (auto &si : Ship_info) {
if (si.flags[Ship::Info_Flags::In_tech_database] == si.flags[Ship::Info_Flags::Default_in_tech_database]) {
state = TechroomState::DEFAULT;
} else if (si.flags[Ship::Info_Flags::In_tech_database]) {
state = TechroomState::ADDED;
} else {
state = TechroomState::REMOVED;
}
cfwrite_ubyte((ubyte) state, cfp);
}
// and for weapons
for (auto &wi : Weapon_info) {
if (wi.wi_flags[Weapon::Info_Flags::In_tech_database] == wi.wi_flags[Weapon::Info_Flags::Default_in_tech_database]) {
state = TechroomState::DEFAULT;
} else if (wi.wi_flags[Weapon::Info_Flags::In_tech_database]) {
state = TechroomState::ADDED;
} else {
state = TechroomState::REMOVED;
}
cfwrite_ubyte((ubyte) state, cfp);
}
// and for intel entries
for (auto &ii : Intel_info) {
if (((ii.flags & IIF_IN_TECH_DATABASE) != 0) == ((ii.flags & IIF_DEFAULT_IN_TECH_DATABASE) != 0)) {
state = TechroomState::DEFAULT;
} else if ((ii.flags & IIF_IN_TECH_DATABASE) != 0) {
state = TechroomState::ADDED;
} else {
state = TechroomState::REMOVED;
}
cfwrite_ubyte((ubyte) state, cfp);
}
endSection();
}
void pilotfile::csg_read_loadout()
{
int j, count, ship_idx = -1, wep_idx = -1;
size_t idx, list_size = 0;
if ( !m_have_info ) {
throw "Loadout before Info!";
}
// base info
cfread_string_len(Player_loadout.filename, MAX_FILENAME_LEN, cfp);
cfread_string_len(Player_loadout.last_modified, DATE_TIME_LENGTH, cfp);
// ship pool
list_size = ship_list.size();
for (idx = 0; idx < list_size; idx++) {
count = cfread_int(cfp);
if (ship_list[idx].index >= 0) {
Player_loadout.ship_pool[ship_list[idx].index] = count;
}
}
// weapon pool
list_size = weapon_list.size();
for (idx = 0; idx < list_size; idx++) {
count = cfread_int(cfp);
if (weapon_list[idx].index >= 0) {
Player_loadout.weapon_pool[weapon_list[idx].index] = count;
}
}
// player ship loadout
list_size = (uint)cfread_ushort(cfp);
for (uint i = 0; i < list_size; i++) {
wss_unit *slot = NULL;
if (i < MAX_WSS_SLOTS) {
slot = &Player_loadout.unit_data[i];
}
// ship
ship_idx = cfread_int(cfp);
if ( (ship_idx >= (int)ship_list.size()) || (ship_idx < -1) ) { // on the casts, assume that ship & weapon lists will never exceed ~2 billion
mprintf(("CSG => Parse Warning: Invalid value for ship index (%d), emptying slot.\n", ship_idx));
ship_idx = -1;
}
if (slot) {
if (ship_idx == -1) { // -1 means no ship in this slot
slot->ship_class = -1;
} else {
slot->ship_class = ship_list[ship_idx].index;
}
}
// primary weapons
count = cfread_int(cfp);
for (j = 0; j < count; j++) {
wep_idx = cfread_int(cfp);
if ( (wep_idx >= (int)weapon_list.size()) || (wep_idx < -1) ) {
mprintf(("CSG => Parse Warning: Invalid value for primary weapon index (%d), emptying slot.\n", wep_idx));
wep_idx = -1;
}
if ( slot && (j < MAX_SHIP_PRIMARY_BANKS) ) {
if (wep_idx == -1) { // -1 means no weapon in this slot
slot->wep[j] = -1;
} else {
slot->wep[j] = weapon_list[wep_idx].index;
}
}
int read_idx = cfread_int(cfp);
if ( slot && (j < MAX_SHIP_PRIMARY_BANKS) ) {
slot->wep_count[j] = read_idx;
}
}
// secondary weapons
count = cfread_int(cfp);
for (j = 0; j < count; j++) {
wep_idx = cfread_int(cfp);
if ( (wep_idx >= (int)weapon_list.size()) || (wep_idx < -1) ) {
mprintf(("CSG => Parse Warning: Invalid value for secondary weapon index (%d), emptying slot.\n", wep_idx));
wep_idx = -1;
}
if ( slot && (j < MAX_SHIP_SECONDARY_BANKS) ) {
if (wep_idx == -1) { // -1 means no weapon in this slot
slot->wep[j+MAX_SHIP_PRIMARY_BANKS] = -1;
} else {
slot->wep[j+MAX_SHIP_PRIMARY_BANKS] = weapon_list[wep_idx].index;
}
}
int read_idx = cfread_int(cfp);
if ( slot && (j < MAX_SHIP_SECONDARY_BANKS) ) {
slot->wep_count[j+MAX_SHIP_PRIMARY_BANKS] = read_idx;
}
}
}
}
void pilotfile::csg_write_loadout()
{
int idx, j;
startSection(Section::Loadout);
// base info
cfwrite_string_len(Player_loadout.filename, cfp);
cfwrite_string_len(Player_loadout.last_modified, cfp);
// ship pool
for (idx = 0; idx < ship_info_size(); idx++) {
cfwrite_int(Player_loadout.ship_pool[idx], cfp);
}
// weapon pool
for (idx = 0; idx < weapon_info_size(); idx++) {
cfwrite_int(Player_loadout.weapon_pool[idx], cfp);
}
// play ship loadout
cfwrite_ushort(MAX_WSS_SLOTS, cfp);
for (idx = 0; idx < MAX_WSS_SLOTS; idx++) {
wss_unit *slot = &Player_loadout.unit_data[idx];
// ship
cfwrite_int(slot->ship_class, cfp);
// primary weapons
cfwrite_int(MAX_SHIP_PRIMARY_BANKS, cfp);
for (j = 0; j < MAX_SHIP_PRIMARY_BANKS; j++) {
cfwrite_int(slot->wep[j], cfp);
cfwrite_int(slot->wep_count[j], cfp);
}
// secondary weapons
cfwrite_int(MAX_SHIP_SECONDARY_BANKS, cfp);
for (j = 0; j < MAX_SHIP_SECONDARY_BANKS; j++) {
cfwrite_int(slot->wep[j+MAX_SHIP_PRIMARY_BANKS], cfp);
cfwrite_int(slot->wep_count[j+MAX_SHIP_PRIMARY_BANKS], cfp);
}
}
endSection();
}
void pilotfile::csg_read_stats()
{
int idx, list_size = 0;
int count;
if ( !m_have_info ) {
throw "Stats before Info!";
}
// scoring stats
p->stats.score = cfread_int(cfp);
p->stats.rank = cfread_int(cfp);
p->stats.assists = cfread_int(cfp);
p->stats.kill_count = cfread_int(cfp);
p->stats.kill_count_ok = cfread_int(cfp);
p->stats.bonehead_kills = cfread_int(cfp);
p->stats.p_shots_fired = cfread_uint(cfp);
p->stats.p_shots_hit = cfread_uint(cfp);
p->stats.p_bonehead_hits = cfread_uint(cfp);
p->stats.s_shots_fired = cfread_uint(cfp);
p->stats.s_shots_hit = cfread_uint(cfp);
p->stats.s_bonehead_hits = cfread_uint(cfp);
p->stats.flight_time = cfread_uint(cfp);
p->stats.missions_flown = cfread_uint(cfp);
p->stats.last_flown = (_fs_time_t)cfread_int(cfp);
p->stats.last_backup = (_fs_time_t)cfread_int(cfp);
// ship kills (scoring)
list_size = (int)ship_list.size();
for (idx = 0; idx < list_size; idx++) {
count = cfread_int(cfp);
if (ship_list[idx].index >= 0) {
p->stats.kills[ship_list[idx].index] = count;
}
}
// medals earned (scoring)
list_size = (int)medals_list.size();
for (idx = 0; idx < list_size; idx++) {
count = cfread_int(cfp);
if (medals_list[idx].index >= 0) {
p->stats.medal_counts[medals_list[idx].index] = count;
}
}
}
void pilotfile::csg_write_stats()
{
int idx;
startSection(Section::Scoring);
// scoring stats
cfwrite_int(p->stats.score, cfp);
cfwrite_int(p->stats.rank, cfp);
cfwrite_int(p->stats.assists, cfp);
cfwrite_int(p->stats.kill_count, cfp);
cfwrite_int(p->stats.kill_count_ok, cfp);
cfwrite_int(p->stats.bonehead_kills, cfp);
cfwrite_uint(p->stats.p_shots_fired, cfp);
cfwrite_uint(p->stats.p_shots_hit, cfp);
cfwrite_uint(p->stats.p_bonehead_hits, cfp);
cfwrite_uint(p->stats.s_shots_fired, cfp);
cfwrite_uint(p->stats.s_shots_hit, cfp);
cfwrite_uint(p->stats.s_bonehead_hits, cfp);
cfwrite_uint(p->stats.flight_time, cfp);
cfwrite_uint(p->stats.missions_flown, cfp);
cfwrite_int((int)p->stats.last_flown, cfp);
cfwrite_int((int)p->stats.last_backup, cfp);
// ship kills (scoring)
for (idx = 0; idx < ship_info_size(); idx++) {
cfwrite_int(p->stats.kills[idx], cfp);
}
// medals earned (scoring)
for (idx = 0; idx < (int)Medals.size(); idx++) {
cfwrite_int(p->stats.medal_counts[idx], cfp);
}
endSection();
}
void pilotfile::csg_read_redalert()
{
int idx, i, j, ship_list_size = 0, wing_list_size = 0;
int count;
char t_string[MAX_FILENAME_LEN+NAME_LENGTH+1] = { '\0' };
float hit;
wep_t weapons;
if ( !m_have_info ) {
throw "RedAlert before Info!";
}
ship_list_size = cfread_int(cfp);
if (ship_list_size > 0) {
cfread_string_len(t_string, MAX_FILENAME_LEN, cfp);
Red_alert_precursor_mission = t_string;
for (idx = 0; idx < ship_list_size; idx++) {
red_alert_ship_status ras;
cfread_string_len(t_string, NAME_LENGTH, cfp);
ras.name = t_string;
ras.hull = cfread_float(cfp);
// ship class, index into ship_list[]
i = cfread_int(cfp);
if ( (i >= (int)ship_list.size()) || (i < RED_ALERT_LOWEST_VALID_SHIP_CLASS) ) {
mprintf(("CSG => Parse Warning: Invalid value for red alert ship index (%d), emptying slot.\n", i));
ras.ship_class = RED_ALERT_DESTROYED_SHIP_CLASS;
} else if ( (i < 0 ) && (i >= RED_ALERT_LOWEST_VALID_SHIP_CLASS) ) { // ship destroyed/exited
ras.ship_class = i;
} else {
ras.ship_class = ship_list[i].index;
}
// subsystem hits
count = cfread_int(cfp);
for (j = 0; j < count; j++) {
hit = cfread_float(cfp);
ras.subsys_current_hits.push_back( hit );
}
// subsystem aggregate hits
count = cfread_int(cfp);
for (j = 0; j < count; j++) {
hit = cfread_float(cfp);
ras.subsys_aggregate_current_hits.push_back( hit );
}
// primary weapon loadout and status
count = cfread_int(cfp);
for (j = 0; j < count; j++) {
i = cfread_int(cfp);
weapons.index = weapon_list[i].index;
weapons.count = cfread_int(cfp);
// triggering this means something is really fubar
if (weapons.index < 0) {
continue;
}
ras.primary_weapons.push_back( weapons );
}
// secondary weapon loadout and status
count = cfread_int(cfp);
for (j = 0; j < count; j++) {
i = cfread_int(cfp);
weapons.index = weapon_list[i].index;
weapons.count = cfread_int(cfp);
// triggering this means something is really fubar
if (weapons.index < 0) {
continue;
}
ras.secondary_weapons.push_back( weapons );
}
// this is quite likely a *bad* thing if it doesn't happen
if (ras.ship_class >= RED_ALERT_LOWEST_VALID_SHIP_CLASS) {
Red_alert_ship_status.push_back( ras );
}
}
}
// old versions of CSG files do not store wing status
if (csg_ver < 8) {
return;
}
wing_list_size = cfread_int(cfp);
if (wing_list_size > 0) {
for (idx = 0; idx < wing_list_size; idx++) {
red_alert_wing_status rws;
cfread_string_len(t_string, NAME_LENGTH, cfp);
rws.name = t_string;
rws.latest_wave = cfread_int(cfp);
rws.wave_count = cfread_int(cfp);
rws.total_arrived_count = cfread_int(cfp);
rws.total_departed = cfread_int(cfp);
rws.total_destroyed = cfread_int(cfp);
rws.total_vanished = cfread_int(cfp);
Red_alert_wing_status.push_back(rws);
}
}
}
void pilotfile::csg_write_redalert()
{
int idx, j, ship_list_size = 0, wing_list_size = 0;
int count;
startSection(Section::RedAlert);
ship_list_size = (int)Red_alert_ship_status.size();
cfwrite_int(ship_list_size, cfp);
if (ship_list_size) {
cfwrite_string_len(Red_alert_precursor_mission.c_str(), cfp);
for (idx = 0; idx < ship_list_size; idx++) {
auto ras = &Red_alert_ship_status[idx];
cfwrite_string_len(ras->name.c_str(), cfp);
cfwrite_float(ras->hull, cfp);
// ship class, should be index into ship_list[] on load
cfwrite_int(ras->ship_class, cfp);
// subsystem hits
count = (int)ras->subsys_current_hits.size();
cfwrite_int(count, cfp);
for (j = 0; j < count; j++) {
cfwrite_float(ras->subsys_current_hits[j], cfp);
}
// subsystem aggregate hits
count = (int)ras->subsys_aggregate_current_hits.size();
cfwrite_int(count, cfp);
for (j = 0; j < count; j++) {
cfwrite_float(ras->subsys_aggregate_current_hits[j], cfp);
}
// primary weapon loadout and status
count = (int)ras->primary_weapons.size();
cfwrite_int(count, cfp);
for (j = 0; j < count; j++) {
cfwrite_int(ras->primary_weapons[j].index, cfp);
cfwrite_int(ras->primary_weapons[j].count, cfp);
}
// secondary weapon loadout and status
count = (int)ras->secondary_weapons.size();
cfwrite_int(count, cfp);
for (j = 0; j < count; j++) {
cfwrite_int(ras->secondary_weapons[j].index, cfp);
cfwrite_int(ras->secondary_weapons[j].count, cfp);
}
}
}
wing_list_size = (int)Red_alert_wing_status.size();
cfwrite_int(wing_list_size, cfp);
if (wing_list_size) {
for (idx = 0; idx < wing_list_size; idx++) {
auto rws = &Red_alert_wing_status[idx];
cfwrite_string_len(rws->name.c_str(), cfp);
cfwrite_int(rws->latest_wave, cfp);
cfwrite_int(rws->wave_count, cfp);
cfwrite_int(rws->total_arrived_count, cfp);
cfwrite_int(rws->total_departed, cfp);
cfwrite_int(rws->total_destroyed, cfp);
cfwrite_int(rws->total_vanished, cfp);
}
}
endSection();
}
void pilotfile::csg_read_hud()
{
const HC_gauge_mappings& gauge_map = HC_gauge_mappings::get_instance();
int strikes = 0;
// flags
int show_flags = cfread_int(cfp);
int show_flags2 = cfread_int(cfp);
int popup_flags = cfread_int(cfp);
int popup_flags2 = cfread_int(cfp);
// Convert show_flags (0-31) and show_flags2 (32-63)
for (int i = 0; i < 64; i++) {
bool is_set = (i < 32) ? (show_flags & (1 << i)) : (show_flags2 & (1 << (i - 32)));
SCP_string gauge_id = gauge_map.get_string_id_from_numeric_id(i);
if (!gauge_id.empty()) {
HUD_config.set_gauge_visibility(gauge_id, is_set);
}
}
// Convert popup_flags (0-31) and popup_flags2 (32-63)
for (int i = 0; i < 64; i++) {
bool is_set = (i < 32) ? (popup_flags & (1 << i)) : (popup_flags2 & (1 << (i - 32)));
SCP_string gauge_id = gauge_map.get_string_id_from_numeric_id(i);
if (!gauge_id.empty()) {
HUD_config.set_gauge_popup(gauge_id, is_set);
}
}
// settings
SCP_UNUSED(cfread_ubyte(cfp));// Deprecated but still read for file compatibility 3/7/2025
SCP_UNUSED(cfread_int(cfp));// Deprecated but still read for file compatibility 3/7/2025
HUD_config.rp_dist = cfread_int(cfp);
if (HUD_config.rp_dist < 0 || HUD_config.rp_dist >= RR_MAX_RANGES) {
ReleaseWarning(LOCATION, "Campaign file has invalid radar range %d, setting to default.\n", HUD_config.rp_dist);
HUD_config.rp_dist = RR_INFINITY;
strikes++;
}
// basic colors
HUD_config.main_color = cfread_int(cfp);
if (HUD_config.main_color < 0 || HUD_config.main_color >= NUM_HUD_COLOR_PRESETS) {
ReleaseWarning(LOCATION, "Campaign file has invalid main color selection %i, setting to default.\n", HUD_config.main_color);
HUD_config.main_color = HUD_COLOR_PRESET_1;
strikes++;
}
HUD_color_alpha = cfread_int(cfp);
if (HUD_color_alpha < HUD_COLOR_ALPHA_USER_MIN || HUD_color_alpha > HUD_COLOR_ALPHA_USER_MAX) {
ReleaseWarning(LOCATION, "Campaign file has invalid alpha color %i, setting to default.\n", HUD_color_alpha);
HUD_color_alpha = HUD_COLOR_ALPHA_DEFAULT;
strikes++;
}
if (strikes == 3) {
ReleaseWarning(LOCATION, "Campaign file has too many hud config errors, and is likely corrupted. Please verify and save your settings in the hud config menu.");
}
hud_config_record_color(HUD_config.main_color);
// gauge-specific colors
int num_gauges = cfread_int(cfp);
for (int idx = 0; idx < num_gauges; idx++) {
ubyte red = cfread_ubyte(cfp);
ubyte green = cfread_ubyte(cfp);
ubyte blue = cfread_ubyte(cfp);
ubyte alpha = cfread_ubyte(cfp);
if (idx >= NUM_HUD_GAUGES) {
continue;
}
SCP_string gauge_id = gauge_map.get_string_id_from_numeric_id(idx);
if (!gauge_id.empty()) {
color clr;
gr_init_alphacolor(&clr, red, green, blue, alpha);
HUD_config.set_gauge_color(gauge_id, clr);
}
}
}
void pilotfile::csg_write_hud()
{
startSection(Section::HUD);
// Get gauge mappings instance
const HC_gauge_mappings& gauge_map = HC_gauge_mappings::get_instance();
// Initialize bitfields
int show_flags = 0, show_flags2 = 0;
int popup_flags = 0, popup_flags2 = 0;
// Convert show_flags_map to bitfield
for (int i = 0; i < 64; i++) {
SCP_string gauge_id = gauge_map.get_string_id_from_numeric_id(i);
if (!gauge_id.empty() && HUD_config.is_gauge_visible(gauge_id)) {
if (i < 32) {
show_flags |= (1 << i);
} else {
show_flags2 |= (1 << (i - 32));
}
}
}
// Convert popup_flags_map to bitfield
for (int i = 0; i < 64; i++) {
SCP_string gauge_id = gauge_map.get_string_id_from_numeric_id(i);
if (!gauge_id.empty() && HUD_config.is_gauge_popup(gauge_id)) {
if (i < 32) {
popup_flags |= (1 << i);
} else {
popup_flags2 |= (1 << (i - 32));
}
}
}
// flags
cfwrite_int(show_flags, cfp);
cfwrite_int(show_flags2, cfp);
cfwrite_int(popup_flags, cfp);
cfwrite_int(popup_flags2, cfp);
// settings
cfwrite_ubyte(0, cfp);// Deprecated but still written for file compatibility 3/7/2025
cfwrite_int(0, cfp);// Deprecated but still written for file compatibility 3/7/2025
cfwrite_int(HUD_config.rp_dist, cfp);
// basic colors
cfwrite_int(HUD_config.main_color, cfp);
cfwrite_int(HUD_color_alpha, cfp);
// gauge-specific colors
cfwrite_int(NUM_HUD_GAUGES, cfp);
for (int idx = 0; idx < NUM_HUD_GAUGES; idx++) {
// Get the gauge string ID from numeric ID
SCP_string gauge_id = gauge_map.get_string_id_from_numeric_id(idx);
color clr = HUD_config.get_gauge_color(gauge_id);
cfwrite_ubyte(clr.red, cfp);
cfwrite_ubyte(clr.green, cfp);
cfwrite_ubyte(clr.blue, cfp);
cfwrite_ubyte(clr.alpha, cfp);
}
endSection();
}
void pilotfile::csg_read_variables()
{
int idx;
int num_variables = cfread_int(cfp);
if (num_variables > 0) {
for (idx = 0; idx < num_variables; idx++) {
sexp_variable temp_var;
memset(&temp_var, 0, sizeof(sexp_variable));
temp_var.type = cfread_int(cfp);
cfread_string_len(temp_var.text, TOKEN_LENGTH, cfp);
cfread_string_len(temp_var.variable_name, TOKEN_LENGTH, cfp);
Campaign.persistent_variables.push_back(temp_var);
}
}
Campaign.red_alert_variables.clear();
if (csg_ver < 4) { // CSG files before version 4 don't have a Red Alert set of CPVs to load, so just copy the regular set.
for (auto& current_pv : Campaign.persistent_variables) {
Campaign.red_alert_variables.push_back(current_pv);
}
} else {
int redalert_num_variables = cfread_int(cfp);
if (redalert_num_variables > 0) {
for (idx = 0; idx < redalert_num_variables; idx++) {
sexp_variable temp_var;
memset(&temp_var, 0, sizeof(sexp_variable));
temp_var.type = cfread_int(cfp);
cfread_string_len(temp_var.text, TOKEN_LENGTH, cfp);
cfread_string_len(temp_var.variable_name, TOKEN_LENGTH, cfp);
Campaign.red_alert_variables.push_back(temp_var);
}
}
}
}
void pilotfile::csg_write_variables()
{
int idx;
startSection(Section::Variables);
cfwrite_int((int)Campaign.persistent_variables.size(), cfp);
for (idx = 0; idx < (int)Campaign.persistent_variables.size(); idx++) {
if (!(Campaign.persistent_variables[idx].type & SEXP_VARIABLE_SAVE_TO_PLAYER_FILE)) {
cfwrite_int(Campaign.persistent_variables[idx].type, cfp);
cfwrite_string_len(Campaign.persistent_variables[idx].text, cfp);
cfwrite_string_len(Campaign.persistent_variables[idx].variable_name, cfp);
}
}
cfwrite_int((int)Campaign.red_alert_variables.size(), cfp);
for (idx = 0; idx < (int)Campaign.red_alert_variables.size(); idx++) {
cfwrite_int(Campaign.red_alert_variables[idx].type, cfp);
cfwrite_string_len(Campaign.red_alert_variables[idx].text, cfp);
cfwrite_string_len(Campaign.red_alert_variables[idx].variable_name, cfp);
}
endSection();
}
void pilotfile::csg_read_settings()
{
clamped_range_warnings.clear();
// sound/voice/music
float temp_volume = cfread_float(cfp);
clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Effects Volume");
snd_set_effects_volume(temp_volume);
options::OptionsManager::instance()->set_ingame_range_option("Audio.Effects", Master_sound_volume);
temp_volume = cfread_float(cfp);
clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Music Volume");
event_music_set_volume(temp_volume);
options::OptionsManager::instance()->set_ingame_range_option("Audio.Music", Master_event_music_volume);
temp_volume = cfread_float(cfp);
clamp_value_with_warn(&temp_volume, 0.f, 1.f, "Voice Volume");
snd_set_voice_volume(temp_volume);
options::OptionsManager::instance()->set_ingame_range_option("Audio.Voice", Master_voice_volume);
Briefing_voice_enabled = cfread_int(cfp) != 0;
options::OptionsManager::instance()->set_ingame_binary_option("Audio.BriefingVoice", Briefing_voice_enabled);
// skill level
Game_skill_level = cfread_int(cfp);
clamp_value_with_warn(&Game_skill_level, 0, 4, "Game Skill Level");
options::OptionsManager::instance()->set_ingame_range_option("Game.SkillLevel", Game_skill_level);
// input options
Use_mouse_to_fly = cfread_int(cfp) != 0;
options::OptionsManager::instance()->set_ingame_binary_option("Input.UseMouse", Use_mouse_to_fly);
Mouse_sensitivity = cfread_int(cfp);
clamp_value_with_warn(&Mouse_sensitivity, 0, 9, "Mouse Sensitivity");
options::OptionsManager::instance()->set_ingame_range_option("Input.MouseSensitivity", Mouse_sensitivity);
Joy_sensitivity = cfread_int(cfp);
clamp_value_with_warn(&Joy_sensitivity, 0, 9, "Joystick Sensitivity");
options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickSensitivity", Joy_sensitivity);
Joy_dead_zone_size = cfread_int(cfp);
clamp_value_with_warn(&Joy_dead_zone_size, 0, 45, "Joystick Deadzone");
options::OptionsManager::instance()->set_ingame_range_option("Input.JoystickDeadZone", Joy_dead_zone_size);
if (csg_ver < 3) {
// detail
int dummy __UNUSED = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
dummy = cfread_int(cfp);
}
if (!clamped_range_warnings.empty()) {
ReleaseWarning(LOCATION, "The following values in the campaign save file were out of bounds and were automatically reset:\n%s\nPlease check your settings!\n", clamped_range_warnings.c_str());
clamped_range_warnings.clear();
}
}
void pilotfile::csg_write_settings()
{
startSection(Section::Settings);
clamped_range_warnings.clear();
// sound/voice/music
clamp_value_with_warn(&Master_sound_volume, 0.f, 1.f, "Effects Volume");
cfwrite_float(Master_sound_volume, cfp);
clamp_value_with_warn(&Master_event_music_volume, 0.f, 1.f, "Music Volume");
cfwrite_float(Master_event_music_volume, cfp);
clamp_value_with_warn(&Master_voice_volume, 0.f, 1.f, "Voice Volume");
cfwrite_float(Master_voice_volume, cfp);
cfwrite_int(Briefing_voice_enabled ? 1 : 0, cfp);
// skill level
clamp_value_with_warn(&Game_skill_level, 0, 4, "Game Skill Level");
cfwrite_int(Game_skill_level, cfp);
// input options
cfwrite_int(Use_mouse_to_fly, cfp);
clamp_value_with_warn(&Mouse_sensitivity, 0, 9, "Mouse Sensitivity");
cfwrite_int(Mouse_sensitivity, cfp);
clamp_value_with_warn(&Joy_sensitivity, 0, 9, "Joystick Sensitivity");
cfwrite_int(Joy_sensitivity, cfp);
clamp_value_with_warn(&Joy_dead_zone_size, 0, 45, "Joystick Deadzone");
cfwrite_int(Joy_dead_zone_size, cfp);
if (!clamped_range_warnings.empty()) {
ReleaseWarning(LOCATION, "The following values were out of bounds when saving the campaign file and were automatically reset.\n%s\nThis shouldn't be possible, please contact the FreeSpace 2 Open Source Code Project!\n", clamped_range_warnings.c_str());
clamped_range_warnings.clear();
}
endSection();
}
void pilotfile::csg_read_controls()
{
if (csg_ver < 7) {
// Pre CSG-7 compatibility
int idx, list_size;
short id1, id2, id3 __UNUSED;
list_size = (int)cfread_ushort(cfp);
for (idx = 0; idx < list_size; idx++) {
id1 = cfread_short(cfp);
id2 = cfread_short(cfp);
id3 = cfread_short(cfp); // unused, at the moment
if (idx < CCFG_MAX) {
Control_config[idx].take(CC_bind(CID_KEYBOARD, id1), 0);
Control_config[idx].take(CC_bind(CID_JOY0, id2), 1);
}
}
// Check that these bindings are in a preset.
// CSG doesn't save invert flags, so use agnostic equals
auto it = control_config_get_current_preset(true);
if (it == Control_config_presets.end()) {
// Not a preset, create one and its file
CC_preset preset;
preset.name = filename;
// strip off extension
auto n = preset.name.find_last_of('.');
preset.name.resize(n);
std::copy(Control_config.begin(), Control_config.end(), std::back_inserter(preset.bindings));
Control_config_presets.push_back(preset);
save_preset_file(preset, true);
}
return;
} else {
// >= CSG-7
char buf[MAX_FILENAME_LEN];
memset(buf, '\0', sizeof(buf));
cfread_string(buf, sizeof(buf), cfp);
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(),
[&buf](const CC_preset& preset) { return preset.name == buf; });
if (it == Control_config_presets.end()) {
Assertion(!Control_config_presets.empty(), "[CSG] Error reading CSG! Control_config_presets empty; Get a coder!");
// Couldn't find the preset, use defaults
it = Control_config_presets.begin();
}
control_config_use_preset(*it);
return;
}
}
void pilotfile::csg_write_controls()
{
auto it = control_config_get_current_preset();
if (it == Control_config_presets.end()) {
// Normally shouldn't happen, may be a new, blank player
// First assert that the presets have been initialized and have at least the defaults preset
Assertion(!Control_config_presets.empty(), "[CSG] Error saving CSG! Control_config_presets empty; Get a coder!");
// Next, just bash the current preset to be defaults
it = Control_config_presets.begin();
}
startSection(Section::Controls);
cfwrite_string(it->name.c_str(), cfp);
endSection();
}
void pilotfile::csg_read_cutscenes() {
size_t list_size = cfread_uint(cfp);
for(size_t i = 0; i < list_size; i++) {
char tempFilename[MAX_FILENAME_LEN];
cfread_string_len(tempFilename, MAX_FILENAME_LEN, cfp);
cutscene_mark_viewable(tempFilename);
}
}
void pilotfile::csg_write_cutscenes() {
SCP_vector<cutscene_info>::iterator cut;
startSection(Section::Cutscenes);
size_t viewableScenes = 0;
for (cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut) {
if (cut->flags[Cutscene::Cutscene_Flags::Viewable]) {
viewableScenes++;
}
}
// Check for possible overflow because we can only write 32 bit integers
Assertion(viewableScenes <= std::numeric_limits<uint>::max(), "Too many viewable cutscenes! Maximum is %ud!", std::numeric_limits<uint>::max());
cfwrite_uint((uint)viewableScenes, cfp);
for (cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut) {
if (cut->flags[Cutscene::Cutscene_Flags::Viewable]) {
cfwrite_string_len(cut->filename, cfp);
}
}
endSection();
}
/*
* Only used for quick start missions
*/
void pilotfile::csg_read_lastmissions()
{
int i;
// restore list of most recently played missions
Num_recent_missions = cfread_int( cfp );
Assert(Num_recent_missions <= MAX_RECENT_MISSIONS);
for ( i = 0; i < Num_recent_missions; i++ ) {
char *cp;
cfread_string_len( Recent_missions[i], MAX_FILENAME_LEN, cfp);
// Remove the extension (safety check: shouldn't exist anyway)
cp = strchr(Recent_missions[i], '.');
if (cp)
*cp = 0;
}
}
/*
* Only used for quick start missions
*/
void pilotfile::csg_write_lastmissions()
{
int i;
startSection(Section::LastMissions);
// store list of most recently played missions
cfwrite_int(Num_recent_missions, cfp);
for (i=0; i<Num_recent_missions; i++) {
cfwrite_string_len(Recent_missions[i], cfp);
}
endSection();
}
void pilotfile::csg_read_containers()
{
const int num_containers = cfread_int(cfp);
for (int idx = 0; idx < num_containers; idx++) {
Campaign.persistent_containers.emplace_back();
auto& container = Campaign.persistent_containers.back();
csg_read_container(container);
}
Campaign.red_alert_containers.clear();
const int redalert_num_containers = cfread_int(cfp);
for (int idx = 0; idx < redalert_num_containers; idx++) {
Campaign.red_alert_containers.emplace_back();
auto& ra_container = Campaign.red_alert_containers.back();
csg_read_container(ra_container);
}
}
void pilotfile::csg_read_container(sexp_container& container)
{
char temp_buf[NAME_LENGTH];
memset(temp_buf, 0, sizeof(temp_buf));
cfread_string_len(temp_buf, sizeof(temp_buf), cfp);
container.container_name = temp_buf;
container.type = (ContainerType)cfread_int(cfp);
container.opf_type = cfread_int(cfp);
const int size = cfread_int(cfp);
if (container.is_list()) {
for (int i = 0; i < size; ++i) {
cfread_string_len(temp_buf, sizeof(temp_buf), cfp);
container.list_data.emplace_back(temp_buf);
}
} else if (container.is_map()) {
char temp_key[NAME_LENGTH];
memset(temp_key, 0, sizeof(temp_key));
for (int i = 0; i < size; ++i) {
cfread_string_len(temp_key, sizeof(temp_key), cfp);
cfread_string_len(temp_buf, sizeof(temp_buf), cfp);
container.map_data.emplace(temp_key, temp_buf);
}
} else {
UNREACHABLE("Unknown container type %d", (int)container.type);
}
}
void pilotfile::csg_write_containers()
{
startSection(Section::Containers);
cfwrite_int((int)Campaign.persistent_containers.size(), cfp);
for (const auto& container : Campaign.persistent_containers) {
Assert(!container.is_eternal()); // eternal containers should be written to player file
csg_write_container(container);
}
cfwrite_int((int)Campaign.red_alert_containers.size(), cfp);
for (const auto& ra_container : Campaign.red_alert_containers) {
csg_write_container(ra_container);
}
endSection();
}
void pilotfile::csg_write_container(const sexp_container &container)
{
cfwrite_string_len(container.container_name.c_str(), cfp);
cfwrite_int((int)container.type, cfp);
cfwrite_int(container.opf_type, cfp);
cfwrite_int(container.size(), cfp);
if (container.is_list()) {
for (const auto& data : container.list_data) {
cfwrite_string_len(data.c_str(), cfp);
}
} else if (container.is_map()) {
for (const auto& key_data : container.map_data) {
cfwrite_string_len(key_data.first.c_str(), cfp);
cfwrite_string_len(key_data.second.c_str(), cfp);
}
} else {
UNREACHABLE("Unknown container type %d", (int)container.type);
}
}
void pilotfile::csg_reset_data(bool reset_ships_and_weapons)
{
int idx;
cmission *missionp;
// internals
m_have_flags = false;
m_have_info = false;
m_data_invalid = false;
// init stats
p->stats.init();
// zero out allowed ships/weapons
if (reset_ships_and_weapons) {
memset(Campaign.ships_allowed, 0, sizeof(Campaign.ships_allowed));
memset(Campaign.weapons_allowed, 0, sizeof(Campaign.weapons_allowed));
}
// reset campaign status
Campaign.prev_mission = -1;
Campaign.next_mission = 0;
Campaign.num_missions_completed = 0;
// techroom reset
tech_reset_to_default();
// clear out variables
Campaign.persistent_variables.clear();
Campaign.red_alert_variables.clear();
// clear out containers
Campaign.persistent_containers.clear();
Campaign.red_alert_containers.clear();
// clear red alert data
Red_alert_ship_status.clear();
Red_alert_wing_status.clear();
// clear out mission stuff
for (idx = 0; idx < MAX_CAMPAIGN_MISSIONS; idx++) {
missionp = &Campaign.missions[idx];
missionp->goals.clear();
missionp->events.clear();
missionp->variables.clear();
missionp->stats.init();
}
}
void pilotfile::csg_close()
{
if (cfp) {
cfclose(cfp);
cfp = NULL;
}
p = NULL;
filename = "";
ship_list.clear();
weapon_list.clear();
intel_list.clear();
medals_list.clear();
m_have_flags = false;
m_have_info = false;
csg_ver = PLR_VERSION_INVALID;
}
bool pilotfile::load_savefile(player *_p, const char *campaign)
{
SCP_string campaign_filename;
if (Game_mode & GM_MULTIPLAYER) {
return false;
}
if ( (campaign == NULL) || !strlen(campaign) ) {
return false;
}
// set player ptr first thing
Assert( (Player_num >= 0) && (Player_num < MAX_PLAYERS) );
p = _p;
auto base = util::get_file_part(campaign);
// do a sanity check, but don't arbitrarily drop any extension in case the filename contains a period
Assertion(!stristr(base, FS_CAMPAIGN_FILE_EXT), "The campaign should not have an extension at this point!");
// build up filename for the savefile...
sprintf(filename, NOX("%s.%s.csg"), p->callsign, base);
// if campaign file doesn't exist, abort so we don't load irrelevant data
campaign_filename = base;
campaign_filename += FS_CAMPAIGN_FILE_EXT;
if ( !cf_exists_full(campaign_filename.c_str(), CF_TYPE_MISSIONS) ) {
mprintf(("CSG => Unable to find campaign file '%s'!\n", campaign_filename.c_str()));
return false;
}
// we need to reset this early, in case open fails and we need to create
m_data_invalid = false;
// open it, hopefully...
cfp = cfopen(filename.c_str(), "rb", CFILE_NORMAL, CF_TYPE_PLAYERS, false,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
if ( !cfp ) {
mprintf(("CSG => Unable to open '%s' for reading!\n", filename.c_str()));
return false;
}
unsigned int csg_id = cfread_uint(cfp);
if (csg_id != CSG_FILE_ID) {
mprintf(("CSG => Invalid header id for '%s'!\n", filename.c_str()));
csg_close();
return false;
}
// version, now used
csg_ver = cfread_ubyte(cfp);
mprintf(("CSG => Loading '%s' with version %d...\n", filename.c_str(), (int)csg_ver));
csg_reset_data(true);
// the point of all this: read in the CSG contents
while ( !cfeof(cfp) ) {
Section section_id = static_cast<Section>(cfread_ushort(cfp));
uint section_size = cfread_uint(cfp);
size_t start_pos = cftell(cfp);
// safety, to help protect against long reads
cf_set_max_read_len(cfp, section_size);
try {
switch (section_id) {
case Section::Flags:
mprintf(("CSG => Parsing: Flags...\n"));
m_have_flags = true;
csg_read_flags();
break;
case Section::Info:
mprintf(("CSG => Parsing: Info...\n"));
m_have_info = true;
csg_read_info();
break;
case Section::Variables:
mprintf(("CSG => Parsing: Variables...\n"));
csg_read_variables();
break;
case Section::HUD:
mprintf(("CSG => Parsing: HUD...\n"));
csg_read_hud();
break;
case Section::RedAlert:
mprintf(("CSG => Parsing: RedAlert...\n"));
csg_read_redalert();
break;
case Section::Scoring:
mprintf(("CSG => Parsing: Scoring...\n"));
csg_read_stats();
break;
case Section::Loadout:
mprintf(("CSG => Parsing: Loadout...\n"));
csg_read_loadout();
break;
case Section::Techroom:
mprintf(("CSG => Parsing: Techroom...\n"));
csg_read_techroom();
break;
case Section::Missions:
mprintf(("CSG => Parsing: Missions...\n"));
csg_read_missions();
break;
case Section::Settings:
mprintf(("CSG => Parsing: Settings...\n"));
csg_read_settings();
break;
case Section::Controls:
mprintf(("CSG => Parsing: Controls...\n"));
csg_read_controls();
break;
case Section::Cutscenes:
mprintf(("CSG => Parsing: Cutscenes...\n"));
csg_read_cutscenes();
break;
case Section::LastMissions:
mprintf(("CSG => Parsing: Last Missions...\n"));
csg_read_lastmissions();
break;
case Section::Containers:
mprintf(("CSG => Parsing: Containers...\n"));
csg_read_containers();
break;
default:
mprintf(("CSG => Skipping unknown section 0x%04x!\n", (uint32_t)section_id));
break;
}
} catch (cfile::max_read_length &msg) {
// read to max section size, move to next section, discarding
// extra/unknown data
mprintf(("CSG => Warning: (0x%04x) %s\n", (uint32_t)section_id, msg.what()));
} catch (const char *err) {
mprintf(("CSG => ERROR: %s\n", err));
csg_close();
return false;
}
// reset safety catch
cf_set_max_read_len(cfp, 0);
// skip to next section (if not already there)
size_t offset_pos = (start_pos + section_size) - cftell(cfp);
if (offset_pos) {
mprintf(("CSG => Warning: (0x%04x) Short read, information may have been lost!\n", (uint32_t)section_id));
cfseek(cfp, (int)offset_pos, CF_SEEK_CUR);
}
}
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();
// if the campaign (for whatever reason) doesn't have a squad image, use the multi one
if (p->s_squad_filename[0] == '\0') {
strcpy_s(p->s_squad_filename, p->m_squad_filename);
}
player_set_squad_bitmap(p, p->s_squad_filename, false);
mprintf(("CSG => Loading complete!\n"));
// cleanup and return
csg_close();
return true;
}
bool pilotfile::save_savefile()
{
if (Game_mode & GM_MULTIPLAYER) {
return false;
}
// set player ptr first thing
Assert( (Player_num >= 0) && (Player_num < MAX_PLAYERS) );
p = &Players[Player_num];
if ( !strlen(Campaign.filename) ) {
return false;
}
auto base = util::get_file_part(Campaign.filename);
// do a sanity check, but don't arbitrarily drop any extension in case the filename contains a period
Assertion(!stristr(base, FS_CAMPAIGN_FILE_EXT), "The campaign should not have an extension at this point!");
// build up filename for the savefile...
sprintf(filename, NOX("%s.%s.csg"), p->callsign, base);
// make sure that we can actually save this safely
if (m_data_invalid) {
mprintf(("CSG => Skipping save of '%s' due to invalid data check!\n", filename.c_str()));
return false;
}
// validate the number of red alert entries
// assertion before writing so that we don't corrupt the .csg by asserting halfway through writing
// assertion should also prevent loss of major campaign progress
// i.e. lose one mission, not several missions worth (in theory)
Assertion(Red_alert_ship_status.size() <= MAX_SHIPS, "Invalid number of Red_alert_ship_status entries: " SIZE_T_ARG "\n", Red_alert_ship_status.size());
Assertion(Red_alert_wing_status.size() <= MAX_WINGS, "Invalid number of Red_alert_wing_status entries: " SIZE_T_ARG "\n", Red_alert_wing_status.size());
// open it, hopefully...
cfp = cfopen(filename.c_str(), "wb", CFILE_NORMAL, CF_TYPE_PLAYERS, false,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
if ( !cfp ) {
mprintf(("CSG => Unable to open '%s' for saving!\n", filename.c_str()));
return false;
}
// header and version
cfwrite_int(CSG_FILE_ID, cfp);
cfwrite_ubyte(CSG_VERSION, cfp);
mprintf(("CSG => Saving '%s' with version %d...\n", filename.c_str(), (int)CSG_VERSION));
// flags and info sections go first
mprintf(("CSG => Saving: Flags...\n"));
csg_write_flags();
mprintf(("CSG => Saving: Info...\n"));
csg_write_info();
// everything else is next, not order specific
mprintf(("CSG => Saving: Missions...\n"));
csg_write_missions();
mprintf(("CSG => Saving: Techroom...\n"));
csg_write_techroom();
mprintf(("CSG => Saving: Loadout...\n"));
csg_write_loadout();
mprintf(("CSG => Saving: Scoring...\n"));
csg_write_stats();
mprintf(("CSG => Saving: RedAlert...\n"));
csg_write_redalert();
mprintf(("CSG => Saving: HUD...\n"));
csg_write_hud();
mprintf(("CSG => Saving: Variables...\n"));
csg_write_variables();
mprintf(("CSG => Saving: Settings...\n"));
csg_write_settings();
mprintf(("CSG => Saving: Controls...\n"));
csg_write_controls();
mprintf(("CSG => Saving: Cutscenes...\n"));
csg_write_cutscenes();
mprintf(("CSG => Saving: Last Missions...\n"));
csg_write_lastmissions();
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"));
csg_close();
return true;
}
void pilotfile::clear_savefile(bool reset_ships_and_weapons)
{
if (Game_mode & GM_MULTIPLAYER) {
return;
}
// set player ptr first thing
Assert((Player_num >= 0) && (Player_num < MAX_PLAYERS));
p = &Players[Player_num];
csg_reset_data(reset_ships_and_weapons);
}
/*
* get_csg_rank: this function is called from plr.cpp & is
* tightly linked with pilotfile::verify()
*/
bool pilotfile::get_csg_rank(int *rank)
{
player t_csg;
// set player ptr first thing
p = &t_csg;
// filename has already been set
cfp = cfopen(filename.c_str(), "rb", CFILE_NORMAL, CF_TYPE_PLAYERS, false,
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
if ( !cfp ) {
mprintf(("CSG => Unable to open '%s'!\n", filename.c_str()));
return false;
}
unsigned int csg_id = cfread_uint(cfp);
if (csg_id != CSG_FILE_ID) {
mprintf(("CSG => Invalid header id for '%s'!\n", filename.c_str()));
csg_close();
return false;
}
// version, now used
csg_ver = cfread_ubyte(cfp);
mprintf(("CSG => Get Rank from '%s' with version %d...\n", filename.c_str(), (int)csg_ver));
// the point of all this: read in the CSG contents
while ( !m_have_flags && !cfeof(cfp) ) {
Section section_id = static_cast<Section>(cfread_ushort(cfp));
uint section_size = cfread_uint(cfp);
size_t start_pos = cftell(cfp);
size_t offset_pos;
// safety, to help protect against long reads
cf_set_max_read_len(cfp, section_size);
try {
switch (section_id) {
case Section::Flags:
mprintf(("CSG => Parsing: Flags...\n"));
m_have_flags = true;
csg_read_flags();
break;
default:
break;
}
} catch (cfile::max_read_length &msg) {
// read to max section size, move to next section, discarding
// extra/unknown data
mprintf(("CSG => (0x%04x) %s\n", (uint32_t)section_id, msg.what()));
} catch (const char *err) {
mprintf(("CSG => ERROR: %s\n", err));
csg_close();
return false;
}
// reset safety catch
cf_set_max_read_len(cfp, 0);
// skip to next section (if not already there)
offset_pos = (start_pos + section_size) - cftell(cfp);
if (offset_pos) {
mprintf(("CSG => Warning: (0x%04x) Short read, information may have been lost!\n", (uint32_t)section_id));
cfseek(cfp, (int)offset_pos, CF_SEEK_CUR);
}
}
// this is what we came for...
*rank = p->stats.rank;
mprintf(("CSG => Get Rank complete!\n"));
// cleanup & return
csg_close();
return true;
}