Live mod option changes (in-game overlay P5)

psx_mod_set_option_live records a session override for a committed
feature's option and calls the plugin's new option-changed callback in
that plugin's context; psx_mod_option_value answers the override. It
returns 1 when applied live, 0 when the change waits for a restart, -1
during netplay (the plan is negotiated between peers). Nothing changes
unless an overlay calls it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Shokunin
2026-10-08 18:54:03 -07:00
parent 3534e21aa5
commit f739d1feb7
3 changed files with 98 additions and 3 deletions
+50 -3
View File
@@ -2824,6 +2824,51 @@ extern "C" int psx_mod_game_started(void) {
return fntrace_is_game_started();
}
/* ---- Live option overrides (in-game overlay P5) ------------------------- */
namespace {
std::map<std::string, std::string>& live_option_values() {
static std::map<std::string, std::string> m;
return m;
}
std::map<std::string, PSXModOptionChangedCallback>& option_changed_plugins() {
static std::map<std::string, PSXModOptionChangedCallback> m;
return m;
}
std::string live_option_key(const char* p, const char* f, const char* o) {
return std::string(p) + '\x1f' + f + '\x1f' + o;
}
} // namespace
extern "C" int psx_mod_register_option_changed_plugin(const char* id,
PSXModOptionChangedCallback cb) {
if (!id || !*id || !cb) return 0;
option_changed_plugins()[id] = cb;
return 1;
}
extern "C" int psx_mod_set_option_live(const char* package_id, const char* feature_id,
const char* option_id, const char* value) {
using namespace PSXRecompV4;
if (!package_id || !feature_id || !option_id || !value) return 0;
if (psx_netplay_active()) return -1;
RuntimeMods& s = state();
if (!s.initialized || !s.plan.ok) return 0;
bool in_plan = false;
for (const ModResolution::Plugin& plugin : s.plan.plugins)
if (plugin.package_id == package_id && plugin.feature_id == feature_id) in_plan = true;
if (!in_plan) return 0;
live_option_values()[live_option_key(package_id, feature_id, option_id)] = value;
int live = 0;
for (const ModResolution::Plugin& plugin : s.plan.plugins) {
if (plugin.package_id != package_id || plugin.feature_id != feature_id) continue;
auto it = option_changed_plugins().find(plugin.id);
if (it == option_changed_plugins().end()) continue;
PluginCallbackScope scope(s, &plugin);
if (it->second(option_id, value)) live = 1;
}
return live;
}
extern "C" int psx_mod_option_value(const char* package_id,
const char* feature_id,
const char* option_id,
@@ -2839,9 +2884,11 @@ extern "C" int psx_mod_option_value(const char* package_id,
* treat an empty string as a value. */
if (!s.initialized || !s.plan.ok) return 0;
/* Read the committed plan's selection, which includes features its
* [[requirement]]s activated, not live launcher state. */
const std::string value = s.manager.feature_option_value(
s.plan, package_id, feature_id, option_id);
* [[requirement]]s activated, not live launcher state -- unless the
* in-game menu changed it this session (psx_mod_set_option_live). */
auto live = live_option_values().find(live_option_key(package_id, feature_id, option_id));
const std::string value = live != live_option_values().end() ? live->second :
s.manager.feature_option_value(s.plan, package_id, feature_id, option_id);
if (value.empty()) return 0;
if (value.size() + 1 > (size_t)out_size) return 0;
std::memcpy(out, value.c_str(), value.size() + 1);