diff --git a/code/menuui/readyroom.cpp b/code/menuui/readyroom.cpp index 899b427f828..cad49fb1fc1 100644 --- a/code/menuui/readyroom.cpp +++ b/code/menuui/readyroom.cpp @@ -1575,22 +1575,40 @@ void campaign_room_scroll_info_down() void campaign_reset(const SCP_string& campaign_file) { + Assert(Player != nullptr); + + // if the caller is resetting a campaign other than the active one (only reachable via the Lua resetCampaign API), + // we can't go through mission_campaign_load(): that would swap the global Campaign struct over to the target + // campaign and overwrite Player->stats with the wrong running totals. Just delete the CSG; the next time the + // player activates this campaign, mission_campaign_load() will recreate a fresh one. + if (stricmp(campaign_file.c_str(), Campaign.filename) != 0) { + mission_campaign_savefile_delete(campaign_file.c_str()); + return; + } + // note: we do not toss all-time stats from player's performance in campaign up till now + // save stats before the reset because csg_reset_data() will zero them when the deleted CSG is not found + scoring_struct saved_stats = Player->stats; + mission_campaign_savefile_delete(campaign_file.c_str()); const int load_status = mission_campaign_load(campaign_file.c_str(), nullptr, nullptr, 1); + // restore the stats that were cleared during the campaign reload + Player->stats = saved_stats; + // see if we successfully loaded this campaign if (load_status == 0) { // Goober5000 - reinitialize tech database if needed if ((Campaign.flags & CF_CUSTOM_TECH_DATABASE) || !stricmp(Campaign.filename, "freespace2")) { // reset tech database to what's in the tables tech_reset_to_default(); - - // write the savefile so that we don't later load a stale techroom - Pilot.save_savefile(); } + // re-save the savefile so the CSG persists the restored stats (and the tech database reset, if applicable); + // mission_campaign_load() already wrote a fresh CSG with zeroed stats when it found no existing savefile + Pilot.save_savefile(); + if (OnCampaignBeginHook->isActive()) { OnCampaignBeginHook->run(scripting::hook_param_list(scripting::hook_param("Campaign", 's', Campaign.filename))); } diff --git a/code/playerman/player.h b/code/playerman/player.h index 2bf6f6098a3..9518d40839e 100644 --- a/code/playerman/player.h +++ b/code/playerman/player.h @@ -123,7 +123,7 @@ class player int objnum; // object number for this player button_info bi; // structure that holds bit vectors for button presses control_info ci; // control info structure for this player - scoring_struct stats; // scoring and stats info for the player (points to multi_stats or single_stats) + scoring_struct stats; // scoring and stats info for the player that are currently accumulating; loaded from multi_stats or all_time_stats int friendly_hits; // Number of times hit a friendly ship this mission. float friendly_damage; // Total friendly damage done in mission. Diminishes over time. diff --git a/code/scripting/api/objs/player.cpp b/code/scripting/api/objs/player.cpp index 797b3f913cd..9b542f23297 100644 --- a/code/scripting/api/objs/player.cpp +++ b/code/scripting/api/objs/player.cpp @@ -66,7 +66,7 @@ player_h& player_h::operator=(player_h&& other) noexcept ADE_OBJ(l_Player, player_h, "player", "Player handle"); -ADE_VIRTVAR(Stats, l_Player, "scoring_stats stats", "The scoring stats of this player (read-only)", "scoring_stats", "The player stats or invalid handle") { +ADE_VIRTVAR(Stats, l_Player, "scoring_stats stats", "The scoring stats of this player that are currently accumulating, e.g. from a campaign or multiplayer session; read-only", "scoring_stats", "The player stats or invalid handle") { player_h* plr; if (!ade_get_args(L, "o", l_Player.GetPtr(&plr))) return ade_set_error(L, "o", l_ScoringStats.Set(scoring_stats_h())); @@ -74,6 +74,9 @@ ADE_VIRTVAR(Stats, l_Player, "scoring_stats stats", "The scoring stats of this p if (!plr->isValid()) return ade_set_error(L, "o", l_ScoringStats.Set(scoring_stats_h())); + if (ADE_SETTING_VAR) + LuaError(L, "This property is read only."); + return ade_set_args(L, "o", l_ScoringStats.Set(scoring_stats_h(plr->get()->stats, plr->get()))); }