mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
* Add command-line option overrides to options system - Extend `OptionsManager` to support storing and displaying reasons for overrides. - Update related code paths to prioritize command-line overrides, ensure correct option resolution, and display override details in the UI. - Add unit tests for override behavior and round-trip validation. - Remove unused legacy functions (`removeVSyncOption`, `removeWindowModeOption`) in favor of the new system. * Add unit tests for `Graphics.Anisotropy` option validation with and without OpenGL * Add support for associating command-line option names to `OptionsManager` overrides - Update HDR, shadow rendering, and graphics API option overrides to include their respective command-line flag names. - Refactor `Graphics.RenderAPI` to use the `GraphicsAPI` enum instead of raw integers for better type safety and clarity. - Ensure Vulkan support logic and fallback behavior function correctly with the updated options system. - Update unit tests to validate override functionality and enum-based API configuration. * Remove unused `removeVSyncOption` function * Apply the PR review feedback for the command-line option overrides Delete the unit-test run artifacts that this branch committed by mistake, and add .gitignore rules for the files that the unittests binary writes. Give an override priority over an in-session edit again. The options UI now shows a disabled control for an overridden option, and setConfigValue() refuses an edit for an overridden key. Before this change, a save wrote the edit to the config file and applied it to the bound global, but getValue() kept returning the override. The game and the options menu then showed different values for the rest of the session. Add a setOverride() overload that takes a JSON value. Every command-line flag now builds its override with json_integer, json_real, json_true, json_false or json_pack, in place of a hand-formatted string. Make Graphics.CockpitFOV and Graphics.CockpitFOVToggle independent of the order in which the options manager loads them, and remove the forced override on the player's toggle preference. The toggle listener also replaces an identical bind_to() call that made the listener dead code. Add 16 to the values of Graphics.MSAASamples. The -msaa flag accepts 16, so an override with that value made printValues() report an invalid value. Keep the render API that the caller gives to gr_init(). Only a Default value now comes from the Graphics.RenderAPI option, so FRED and the unit tests keep the backend that they ask for. Make the default_func of Graphics.Shadows and Graphics.ShadowRenderMethod return their global. A constant discarded the value that -enable_shadows and the mod table set. Set Max_rt_shadow_local_lights to 4, because 3 is not one of the values that the option offers. Also: resolve -lang one time, share the render API name with version.cpp, correct the stale comments, and add unit tests with a fixture that clears the overrides after each test. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UGh4ZM2u4oUGSbrP122Uyr * Fix the order-dependent crash in the MSAA option test The test `MsaaOverrideAcceptsEverySampleCountTheFlagAllows` calls `getValidValues()`. `Option<T>` makes a display string for each value, and the display function of `Graphics.MSAASamples` calls `XSTR()`. `XSTR()` runs `Int3()` if `lcl_xstr_init()` did not run before. Only `FSTestFixture` runs it, and `Xstr_inited` stays set for the remainder of the process. Thus the shuffled test order decided if the test passed or if it stopped the test binary with SIGILL. Move that one test to a new fixture that derives from `FSTestFixture`. The fixture initializes localization, and it also clears the overrides after each test. Refresh the comment that told that the test binary never runs `lcl_init()`. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01UGh4ZM2u4oUGSbrP122Uyr --------- Co-authored-by: Claude Opus 5 <[email protected]>
291 lines
9.0 KiB
C++
291 lines
9.0 KiB
C++
//
|
|
//
|
|
|
|
#include "option.h"
|
|
#include "enums.h"
|
|
|
|
namespace scripting {
|
|
namespace api {
|
|
|
|
ADE_OBJ(l_ValueDescription, options::ValueDescription, "ValueDescription",
|
|
"An option value that contains a displayable string and the serialized value.");
|
|
|
|
ADE_FUNC(__tostring, l_ValueDescription, nullptr, "Value display string", "string",
|
|
"The display string or nil on error")
|
|
{
|
|
options::ValueDescription* desc;
|
|
if (!ade_get_args(L, "o", l_ValueDescription.GetPtr(&desc))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "s", desc->display.c_str());
|
|
}
|
|
ADE_FUNC(__eq, l_ValueDescription, "ValueDescription other", "Compares two value descriptions", "string",
|
|
"True if equal, false otherwise")
|
|
{
|
|
options::ValueDescription* desc;
|
|
options::ValueDescription* other;
|
|
if (!ade_get_args(L, "oo", l_ValueDescription.GetPtr(&desc), l_ValueDescription.GetPtr(&other))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "b", desc->serialized == other->serialized);
|
|
}
|
|
ADE_VIRTVAR(Display, l_ValueDescription, nullptr, "Value display string", "string",
|
|
"The display string or nil on error")
|
|
{
|
|
options::ValueDescription* desc;
|
|
if (!ade_get_args(L, "o", l_ValueDescription.GetPtr(&desc))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "s", desc->display.c_str());
|
|
}
|
|
ADE_VIRTVAR(Serialized, l_ValueDescription, nullptr, "Serialized string value of the contained value", "string",
|
|
"The serialized string or nil on error")
|
|
{
|
|
options::ValueDescription* desc;
|
|
if (!ade_get_args(L, "o", l_ValueDescription.GetPtr(&desc))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "s", desc->serialized.c_str());
|
|
}
|
|
|
|
option_h::option_h(const options::OptionBase* opt) : _opt(opt) {}
|
|
bool option_h::isValid() const { return _opt != nullptr; }
|
|
const options::OptionBase* option_h::get() { return _opt; }
|
|
|
|
//**********HANDLE: Object
|
|
ADE_OBJ(l_Option, option_h, "option", "Option handle");
|
|
|
|
ADE_VIRTVAR(Title, l_Option, nullptr, "The title of this option (read-only)", "string", "The title or nil on error")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
return ade_set_args(L, "s", opt->get()->getTitle().c_str());
|
|
}
|
|
ADE_VIRTVAR(Description, l_Option, nullptr, "The description of this option (read-only)", "string",
|
|
"The description or nil on error")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
return ade_set_args(L, "s", opt->get()->getDescription().c_str());
|
|
}
|
|
ADE_VIRTVAR(Key, l_Option, nullptr, "The configuration key of this option. This will be a unique string. (read-only)",
|
|
"string", "The key or nil on error")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
return ade_set_args(L, "s", opt->get()->getConfigKey().c_str());
|
|
}
|
|
ADE_VIRTVAR(Category, l_Option, nullptr, "The category of this option. (read-only)", "string",
|
|
"The category or nil on error")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
return ade_set_args(L, "s", opt->get()->getCategory().c_str());
|
|
}
|
|
ADE_VIRTVAR(Type, l_Option, nullptr, "The type of this option. One of the OPTION_TYPE_* values. (read-only)",
|
|
"enumeration", "An OPTION_TYPE_* enumeration, or nil on error")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
lua_enum enum_val = ENUM_INVALID;
|
|
switch (opt->get()->getType()) {
|
|
case options::OptionType::Selection:
|
|
enum_val = LE_OPTION_TYPE_SELECTION;
|
|
break;
|
|
case options::OptionType::Range:
|
|
enum_val = LE_OPTION_TYPE_RANGE;
|
|
break;
|
|
}
|
|
|
|
return ade_set_args(L, "o", l_Enum.Set(enum_h(enum_val)));
|
|
}
|
|
ADE_VIRTVAR(Value, l_Option, "ValueDescription", "The current value of this option.", "ValueDescription",
|
|
"The current value or nil on error")
|
|
{
|
|
option_h* opt;
|
|
options::ValueDescription* new_val = nullptr;
|
|
if (!ade_get_args(L, "o|o", l_Option.GetPtr(&opt), l_ValueDescription.GetPtr(&new_val))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (ADE_SETTING_VAR && new_val != nullptr) {
|
|
opt->get()->setValueDescription(*new_val);
|
|
}
|
|
return ade_set_args(L, "o", l_ValueDescription.Set(opt->get()->getCurrentValueDescription()));
|
|
}
|
|
ADE_VIRTVAR(Flags,
|
|
l_Option,
|
|
nullptr,
|
|
"Contains a list mapping a flag name to its value. Possible names are:"
|
|
"<ul>"
|
|
"<li><b>ForceMultiValueSelection:</b> If true, a selection option with two values should be displayed the "
|
|
"same as an option with more possible values</li>"
|
|
"<li><b>RetailBuiltinOption:</b> If true, the option is one of the original retail options</li>"
|
|
"<li><b>RangeTypeInteger:</b> If true, this range option requires an integer for the range value rather than a float</li>"
|
|
"</ul>",
|
|
"{ string => boolean ... }",
|
|
"The table of flags values.")
|
|
{
|
|
option_h* opt;
|
|
options::ValueDescription* new_val = nullptr;
|
|
if (!ade_get_args(L, "o|o", l_Option.GetPtr(&opt), l_ValueDescription.GetPtr(&new_val))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
luacpp::LuaTable t = luacpp::LuaTable::create(L);
|
|
|
|
t.addValue("ForceMultiValueSelection", opt->get()->getFlags()[options::OptionFlags::ForceMultiValueSelection]);
|
|
t.addValue("RetailBuiltinOption", opt->get()->getFlags()[options::OptionFlags::RetailBuiltinOption]);
|
|
t.addValue("RangeTypeInteger", opt->get()->getFlags()[options::OptionFlags::RangeTypeInteger]);
|
|
|
|
return ade_set_args(L, "t", &t);
|
|
}
|
|
ADE_FUNC(getValueFromRange, l_Option, "number interpolant",
|
|
"Gets a value from an option range. The specified value must be between 0 and 1.", "ValueDescription",
|
|
"The value at the specifiedposition")
|
|
{
|
|
option_h* opt;
|
|
float f;
|
|
if (!ade_get_args(L, "of", l_Option.GetPtr(&opt), &f)) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (opt->get()->getType() != options::OptionType::Range) {
|
|
LuaError(L, "This option is not a range option!");
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (f < 0.0f || f > 1.0f) {
|
|
LuaError(L, "Invalid interpolant value %f!", f);
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "o", l_ValueDescription.Set(opt->get()->getValueFromRange(f)));
|
|
}
|
|
ADE_FUNC(getInterpolantFromValue, l_Option, "ValueDescription value",
|
|
"From a value description of this option, determines the range value.", "number",
|
|
"The range value or 0 on error.")
|
|
{
|
|
option_h* opt;
|
|
options::ValueDescription* value = nullptr;
|
|
if (!ade_get_args(L, "oo", l_Option.GetPtr(&opt), l_ValueDescription.GetPtr(&value))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (opt->get()->getType() != options::OptionType::Range) {
|
|
LuaError(L, "This option is not a range option!");
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "f", opt->get()->getInterpolantFromValue(*value));
|
|
}
|
|
ADE_FUNC(getValidValues,
|
|
l_Option,
|
|
nullptr,
|
|
"Gets the valid values of this option. The order or the returned values must be maintained in the UI. This is "
|
|
"only valid for selection or boolean options.",
|
|
"ValueDescription[]",
|
|
"A table containing the possible values or nil on error.")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (opt->get()->getType() != options::OptionType::Selection) {
|
|
LuaError(L, "This option is not a selection option!");
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
auto values = opt->get()->getValidValues();
|
|
|
|
auto table = luacpp::LuaTable::create(L);
|
|
auto i = 1;
|
|
for (auto& value : values) {
|
|
table.addValue(i, l_ValueDescription.Set(value));
|
|
++i;
|
|
}
|
|
|
|
return ade_set_args(L, "t", &table);
|
|
}
|
|
ADE_VIRTVAR(OverrideReason,
|
|
l_Option,
|
|
nullptr,
|
|
"If this option's value is currently forced by something outside the options system (e.g. a command line "
|
|
"flag), this is a human-readable description of the source. nil if the option is not currently overridden.",
|
|
"string",
|
|
"The override reason, or nil if not overridden or on error")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
|
|
auto reason = options::OptionsManager::instance()->getOverrideReason(opt->get()->getConfigKey());
|
|
if (!reason.has_value()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "s", reason->c_str());
|
|
}
|
|
ADE_FUNC(persistChanges, l_Option, nullptr,
|
|
"Immediately persists any changes made to this specific option.", "boolean",
|
|
"true if the change was applied successfully, false otherwise. nil on error.")
|
|
{
|
|
option_h* opt;
|
|
if (!ade_get_args(L, "o", l_Option.GetPtr(&opt))) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
if (!opt->isValid()) {
|
|
return ADE_RETURN_NIL;
|
|
}
|
|
return ade_set_args(L, "b", opt->get()->persistChanges());
|
|
}
|
|
|
|
} // namespace api
|
|
} // namespace scripting
|