mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-10 09:42:29 +00:00
* Tackles various problems with keyed_list config entries, fixing broken roundstart races and more! (#62359) While helping RaveRadbury debug some issues with enabling Halloween species early via the brute force method of enabling them in the config rather than the gentleman's solution of testmerging a PR that changes the Halloween date, we discovered something dreadful. Cloth golems cannot be enabled! Infact, any species with a space in the ID cannot be enabled. It uses the splitter despite VALUE_MODE_FLAG being set. So a key entry like ROUNDSTART_RACES cloth golem would get parsed as cloth = golem, then entered into the config as cloth = TRUE NEW AND IMPROVED PART HERE I've re-written how keyed_list config entries are parsed, splitting it into a number of procs to do some discrete block of logic. Based on feedback from MSO, he expected that VALUE_MODE_FLAG keyed_list entries could have elements overridden. However, this functionality was not present in the code. I have implemented it. We now support 3 methods of setting VALUE_MODE_FLAGS. Implicitly enable the config entry: CONFIG_ENTRY config_key_goes_here Explicitly enable the config entry: CONFIG_ENTRY config_key_goes_here 1 Explicitly disable the config entry: CONFIG_ENTRY config_key_goes_here 0 There have been functionality changes too. Previously, everything before the first splitter was the key and everything after was the value. However, in ambiguous config entries (Such as ROUNDSTART_RACES cloth golem 0) it would be unclear if the intent was (cloth, golem 0) or (cloth golem, 0) or indeed if the intent was (cloth golem 0, 1). As a result, there is now the following paradigm in place: Everything after the LAST splitter is the value, everything before is the key and a log_config warning is now given explaining the problem and showing how it was resolved. [2021-10-27 19:48:12.840] WARNING: Multiple splitter characters (" ") found. Using "cloth golem" as config key and "1" as config value. This warning will trigger if multiple splitters are present for any keyed_list config entry, and will trigger on implicit VALUE_MODE_FLAGS entries that have splitters. The example above is it triggering on ROUNDSTART_RACES cloth golem - It has detected that there is potential ambiguity between (cloth, golem) or (cloth golem, 1), has picked a sensible option for the data type and has warned about it. The intent is that no config entry should be ambiguous. It should be clear what is key and what is value when dealing with keyed_list config entries. There's probably more work to do on other config entries to bring them up to this standard, but this is the thing I'm hitting in this PR. Similarly, I have improved the validation aspect of keyed_list config entries with additional logging in general. [2021-10-27 19:47:53.135] ERROR: Invalid KEY_MODE_TYPE typepath. Is not a valid typepath: /mob/living/carbon/monkey I have added a unit test to make sure species IDs do not contain splitters from the two keyed_list subtypes relating to species. I have added sanity checking to the race config subtypes since we have a big dick global list of all races sorted by ID, so a race not existing will fail validation and output a meaningful config log entry. I have removed /datum/config_entry/keyed_list/probability from the code as it is unused with the removal of all game modes except Dynamic. The config change necessitated the renaming of all golem species IDs. Doing so and renaming the clothgolem.ts file to match has fixed the broken cloth golem page too. * Tackles various problems with keyed_list config entries, fixing broken roundstart races and more! Co-authored-by: Timberpoes <silent_insomnia_pp@hotmail.co.uk>
306 lines
10 KiB
Plaintext
306 lines
10 KiB
Plaintext
#define VALUE_MODE_NUM 0
|
|
#define VALUE_MODE_TEXT 1
|
|
#define VALUE_MODE_FLAG 2
|
|
|
|
#define KEY_MODE_TEXT 0
|
|
#define KEY_MODE_TYPE 1
|
|
|
|
/datum/config_entry
|
|
/// Read-only, this is determined by the last portion of the derived entry type
|
|
var/name
|
|
/// The configured value for this entry. This shouldn't be initialized in code, instead set default
|
|
var/config_entry_value
|
|
/// Read-only default value for this config entry, used for resetting value to defaults when necessary. This is what config_entry_value is initially set to
|
|
var/default
|
|
/// The file which this was loaded from, if any
|
|
var/resident_file
|
|
/// Set to TRUE if the default has been overridden by a config entry
|
|
var/modified = FALSE
|
|
/// The config name of a configuration type that depricates this, if it exists
|
|
var/deprecated_by
|
|
/// The /datum/config_entry type that supercedes this one
|
|
var/protection = NONE
|
|
/// Do not instantiate if type matches this
|
|
var/abstract_type = /datum/config_entry
|
|
/// Force validate and set on VV. VAS proccall guard will run regardless.
|
|
var/vv_VAS = TRUE
|
|
/// Controls if error is thrown when duplicate configuration values for this entry type are encountered
|
|
var/dupes_allowed = FALSE
|
|
/// Stores the original protection configuration, used for set_default()
|
|
var/default_protection
|
|
|
|
/datum/config_entry/New()
|
|
if(type == abstract_type)
|
|
CRASH("Abstract config entry [type] instatiated!")
|
|
name = lowertext(type2top(type))
|
|
default_protection = protection
|
|
set_default()
|
|
|
|
/datum/config_entry/Destroy()
|
|
config.RemoveEntry(src)
|
|
return ..()
|
|
|
|
/**
|
|
* Returns the value of the configuration datum to its default, used for resetting a config value. Note this also sets the protection back to default.
|
|
*/
|
|
/datum/config_entry/proc/set_default()
|
|
if ((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall())
|
|
log_admin_private("[key_name(usr)] attempted to reset locked config entry [type] to its default")
|
|
return
|
|
if (islist(default))
|
|
var/list/L = default
|
|
config_entry_value = L.Copy()
|
|
else
|
|
config_entry_value = default
|
|
protection = default_protection
|
|
resident_file = null
|
|
modified = FALSE
|
|
|
|
/datum/config_entry/can_vv_get(var_name)
|
|
. = ..()
|
|
if(var_name == NAMEOF(src, config_entry_value) || var_name == NAMEOF(src, default))
|
|
. &= !(protection & CONFIG_ENTRY_HIDDEN)
|
|
|
|
/datum/config_entry/vv_edit_var(var_name, var_value)
|
|
var/static/list/banned_edits = list(NAMEOF(src, name), NAMEOF(src, vv_VAS), NAMEOF(src, default), NAMEOF(src, resident_file), NAMEOF(src, protection), NAMEOF(src, abstract_type), NAMEOF(src, modified), NAMEOF(src, dupes_allowed))
|
|
if(var_name == NAMEOF(src, config_entry_value))
|
|
if(protection & CONFIG_ENTRY_LOCKED)
|
|
return FALSE
|
|
if(vv_VAS)
|
|
. = ValidateAndSet("[var_value]")
|
|
if(.)
|
|
datum_flags |= DF_VAR_EDITED
|
|
return
|
|
else
|
|
return ..()
|
|
if(var_name in banned_edits)
|
|
return FALSE
|
|
return ..()
|
|
|
|
/datum/config_entry/proc/VASProcCallGuard(str_val)
|
|
. = !((protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall())
|
|
if(!.)
|
|
log_admin_private("[key_name(usr)] attempted to set locked config entry [type] to '[str_val]'")
|
|
|
|
/datum/config_entry/proc/ValidateAndSet(str_val)
|
|
VASProcCallGuard(str_val)
|
|
CRASH("Invalid config entry type!")
|
|
|
|
/datum/config_entry/proc/ValidateListEntry(key_name, key_value)
|
|
return TRUE
|
|
|
|
/datum/config_entry/proc/DeprecationUpdate(value)
|
|
return
|
|
|
|
/datum/config_entry/string
|
|
default = ""
|
|
abstract_type = /datum/config_entry/string
|
|
var/auto_trim = TRUE
|
|
/// whether the string will be lowercased on ValidateAndSet or not.
|
|
var/lowercase = FALSE
|
|
|
|
/datum/config_entry/string/vv_edit_var(var_name, var_value)
|
|
return var_name != NAMEOF(src, auto_trim) && ..()
|
|
|
|
/datum/config_entry/string/ValidateAndSet(str_val)
|
|
if(!VASProcCallGuard(str_val))
|
|
return FALSE
|
|
config_entry_value = auto_trim ? trim(str_val) : str_val
|
|
if(lowercase)
|
|
config_entry_value = lowertext(config_entry_value)
|
|
return TRUE
|
|
|
|
/datum/config_entry/number
|
|
default = 0
|
|
abstract_type = /datum/config_entry/number
|
|
var/integer = TRUE
|
|
var/max_val = INFINITY
|
|
var/min_val = -INFINITY
|
|
|
|
/datum/config_entry/number/ValidateAndSet(str_val)
|
|
if(!VASProcCallGuard(str_val))
|
|
return FALSE
|
|
var/temp = text2num(trim(str_val))
|
|
if(!isnull(temp))
|
|
config_entry_value = clamp(integer ? round(temp) : temp, min_val, max_val)
|
|
if(config_entry_value != temp && !(datum_flags & DF_VAR_EDITED))
|
|
log_config("Changing [name] from [temp] to [config_entry_value]!")
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/datum/config_entry/number/vv_edit_var(var_name, var_value)
|
|
var/static/list/banned_edits = list(NAMEOF(src, max_val), NAMEOF(src, min_val), NAMEOF(src, integer))
|
|
return !(var_name in banned_edits) && ..()
|
|
|
|
/datum/config_entry/flag
|
|
default = FALSE
|
|
abstract_type = /datum/config_entry/flag
|
|
|
|
/datum/config_entry/flag/ValidateAndSet(str_val)
|
|
if(!VASProcCallGuard(str_val))
|
|
return FALSE
|
|
config_entry_value = text2num(trim(str_val)) != 0
|
|
return TRUE
|
|
|
|
/// List config entry, used for configuring a list of strings
|
|
/datum/config_entry/str_list
|
|
abstract_type = /datum/config_entry/str_list
|
|
default = list()
|
|
dupes_allowed = TRUE
|
|
/// whether the string elements will be lowercased on ValidateAndSet or not.
|
|
var/lowercase = FALSE
|
|
|
|
/datum/config_entry/str_list/ValidateAndSet(str_val)
|
|
if (!VASProcCallGuard(str_val))
|
|
return FALSE
|
|
str_val = trim(str_val)
|
|
if (str_val != "")
|
|
config_entry_value += lowercase ? lowertext(str_val) : str_val
|
|
return TRUE
|
|
|
|
/datum/config_entry/number_list
|
|
abstract_type = /datum/config_entry/number_list
|
|
default = list()
|
|
|
|
/datum/config_entry/number_list/ValidateAndSet(str_val)
|
|
if(!VASProcCallGuard(str_val))
|
|
return FALSE
|
|
str_val = trim(str_val)
|
|
var/list/new_list = list()
|
|
var/list/values = splittext(str_val," ")
|
|
for(var/I in values)
|
|
var/temp = text2num(I)
|
|
if(isnull(temp))
|
|
return FALSE
|
|
new_list += temp
|
|
if(!new_list.len)
|
|
return FALSE
|
|
config_entry_value = new_list
|
|
return TRUE
|
|
|
|
/datum/config_entry/keyed_list
|
|
abstract_type = /datum/config_entry/keyed_list
|
|
default = list()
|
|
dupes_allowed = TRUE
|
|
vv_VAS = FALSE //VAS will not allow things like deleting from lists, it'll just bug horribly.
|
|
var/key_mode
|
|
var/value_mode
|
|
var/splitter = " "
|
|
/// whether the key names will be lowercased on ValidateAndSet or not.
|
|
var/lowercase_key = TRUE
|
|
|
|
/datum/config_entry/keyed_list/New()
|
|
. = ..()
|
|
if(isnull(key_mode) || isnull(value_mode))
|
|
CRASH("Keyed list of type [type] created with null key or value mode!")
|
|
|
|
/datum/config_entry/keyed_list/ValidateAndSet(str_val)
|
|
if(!VASProcCallGuard(str_val))
|
|
return FALSE
|
|
|
|
str_val = trim(str_val)
|
|
|
|
var/list/new_entry = parse_key_and_value(str_val)
|
|
|
|
var/new_key = new_entry["config_key"]
|
|
var/new_value = new_entry["config_value"]
|
|
|
|
if(!isnull(new_value) && !isnull(new_key) && ValidateListEntry(new_key, new_value))
|
|
config_entry_value[new_key] = new_value
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/datum/config_entry/keyed_list/proc/parse_key_and_value(option_string)
|
|
// Blank or null option string? Bad mojo!
|
|
if(!option_string)
|
|
log_config("ERROR: Keyed list config tried to parse with no key or value data.")
|
|
return null
|
|
|
|
var/list/config_entry_words = splittext(option_string, splitter)
|
|
var/config_value
|
|
var/config_key
|
|
var/is_ambiguous = FALSE
|
|
|
|
// If this config entry's value mode is flag, the value can either be TRUE or FALSE.
|
|
// However, the config supports implicitly setting a config entry to TRUE by omitting the value.
|
|
// This value mode should also support config overrides disabling it too.
|
|
// The following code supports config entries as such:
|
|
// Implicitly enable the config entry: CONFIG_ENTRY config key goes here
|
|
// Explicitly enable the config entry: CONFIG_ENTRY config key goes here 1
|
|
// Explicitly disable the config entry: CONFIG_ENTRY config key goes here 0
|
|
if(value_mode == VALUE_MODE_FLAG)
|
|
var/value = peek(config_entry_words)
|
|
config_value = TRUE
|
|
|
|
if(value == "0")
|
|
config_key = jointext(config_entry_words, splitter, length(config_entry_words) - 1)
|
|
config_value = FALSE
|
|
is_ambiguous = (length(config_entry_words) > 2)
|
|
else if(value == "1")
|
|
config_key = jointext(config_entry_words, splitter, length(config_entry_words) - 1)
|
|
is_ambiguous = (length(config_entry_words) > 2)
|
|
else
|
|
config_key = option_string
|
|
is_ambiguous = (length(config_entry_words) > 1)
|
|
// Else it has to be a key value pair and we parse it under that assumption.
|
|
else
|
|
// If config_entry_words only has 1 or 0 words in it and isn't value_mode == VALUE_MODE_FLAG then it's an invalid config entry.
|
|
if(length(config_entry_words) <= 1)
|
|
log_config("ERROR: Could not parse value from config entry string: [option_string]")
|
|
return null
|
|
|
|
config_value = pop(config_entry_words)
|
|
config_key = jointext(config_entry_words, splitter)
|
|
|
|
if(lowercase_key)
|
|
config_key = lowertext(config_key)
|
|
|
|
is_ambiguous = (length(config_entry_words) > 2)
|
|
|
|
config_key = validate_config_key(config_key)
|
|
config_value = validate_config_value(config_value)
|
|
|
|
// If there are multiple splitters, it's definitely ambiguous and we'll warn about how we parsed it. Helps with debugging config issues.
|
|
if(is_ambiguous)
|
|
log_config("WARNING: Multiple splitter characters (\"[splitter]\") found. Using \"[config_key]\" as config key and \"[config_value]\" as config value.")
|
|
|
|
return list("config_key" = config_key, "config_value" = config_value)
|
|
|
|
/// Takes a given config key and validates it. If successful, returns the formatted key. If unsuccessful, returns null.
|
|
/datum/config_entry/keyed_list/proc/validate_config_key(key)
|
|
switch(key_mode)
|
|
if(KEY_MODE_TEXT)
|
|
return key
|
|
if(KEY_MODE_TYPE)
|
|
if(ispath(key))
|
|
return key
|
|
|
|
var/key_path = text2path(key)
|
|
if(isnull(key_path))
|
|
log_config("ERROR: Invalid KEY_MODE_TYPE typepath. Is not a valid typepath: [key]")
|
|
return
|
|
|
|
return key_path
|
|
|
|
|
|
/// Takes a given config value and validates it. If successful, returns the formatted key. If unsuccessful, returns null.
|
|
/datum/config_entry/keyed_list/proc/validate_config_value(value)
|
|
switch(value_mode)
|
|
if(VALUE_MODE_FLAG)
|
|
return value
|
|
if(VALUE_MODE_NUM)
|
|
if(isnum(value))
|
|
return value
|
|
|
|
var/value_num = text2num(value)
|
|
if(isnull(value_num))
|
|
log_config("ERROR: Invalid VALUE_MODE_NUM number. Could not parse a valid number: [value]")
|
|
return
|
|
|
|
return value_num
|
|
if(VALUE_MODE_TEXT)
|
|
return value
|
|
|
|
/datum/config_entry/keyed_list/vv_edit_var(var_name, var_value)
|
|
return var_name != NAMEOF(src, splitter) && ..()
|