Files
Bubberstation/code/controllers/configuration/configuration.dm
Zonespace 1481db3dab Improves the "disable ERP" config (#16411)
* i'm stealing the erp

* h

* Apply suggestions from code review

Co-authored-by: Tastyfish <crazychris32@gmail.com>

Co-authored-by: Tastyfish <crazychris32@gmail.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
2022-10-02 14:20:24 +00:00

476 lines
16 KiB
Plaintext

/datum/controller/configuration
name = "Configuration"
var/directory = "config"
var/warned_deprecated_configs = FALSE
var/hiding_entries_by_type = TRUE //Set for readability, admins can set this to FALSE if they want to debug it
var/list/entries
var/list/entries_by_type
var/list/maplist
var/datum/map_config/defaultmap
var/list/modes // allowed modes
var/list/gamemode_cache
var/list/votable_modes // votable modes
var/list/mode_names
var/list/mode_reports
var/list/mode_false_report_weight
var/motd
var/policy
/// If the configuration is loaded
var/loaded = FALSE
/// A regex that matches words blocked IC
var/static/regex/ic_filter_regex
/// A regex that matches words blocked OOC
var/static/regex/ooc_filter_regex
/// A regex that matches words blocked IC, but not in PDAs
var/static/regex/ic_outside_pda_filter_regex
/// A regex that matches words soft blocked IC
var/static/regex/soft_ic_filter_regex
/// A regex that matches words soft blocked OOC
var/static/regex/soft_ooc_filter_regex
/// A regex that matches words soft blocked IC, but not in PDAs
var/static/regex/soft_ic_outside_pda_filter_regex
/// An assoc list of blocked IC words to their reasons
var/static/list/ic_filter_reasons
/// An assoc list of words that are blocked IC, but not in PDAs, to their reasons
var/static/list/ic_outside_pda_filter_reasons
/// An assoc list of words that are blocked both IC and OOC to their reasons
var/static/list/shared_filter_reasons
/// An assoc list of soft blocked IC words to their reasons
var/static/list/soft_ic_filter_reasons
/// An assoc list of words that are soft blocked IC, but not in PDAs, to their reasons
var/static/list/soft_ic_outside_pda_filter_reasons
/// An assoc list of words that are soft blocked both IC and OOC to their reasons
var/static/list/soft_shared_filter_reasons
/datum/controller/configuration/proc/admin_reload()
if(IsAdminAdvancedProcCall())
return
log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.")
message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.")
full_wipe()
Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER])
/datum/controller/configuration/proc/Load(_directory)
if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything.
return
if(_directory)
directory = _directory
if(entries)
CRASH("/datum/controller/configuration/Load() called more than once!")
InitEntries()
if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1)
var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt")
for(var/I in legacy_configs)
if(fexists("[directory]/[I]"))
log_config("No $include directives found in config.txt! Loading legacy [legacy_configs.Join("/")] files...")
for(var/J in legacy_configs)
LoadEntries(J)
break
loadmaplist(CONFIG_MAPS_FILE)
LoadMOTD()
LoadPolicy()
LoadChatFilter()
// SKYRAT EDIT ADDITION START
populate_interaction_instances()
remove_erp_things()
// SKYRAT EDIT ADDITION END
loaded = TRUE
if (Master)
Master.OnConfigLoad()
/datum/controller/configuration/proc/full_wipe()
if(IsAdminAdvancedProcCall())
return
entries_by_type.Cut()
QDEL_LIST_ASSOC_VAL(entries)
entries = null
QDEL_LIST_ASSOC_VAL(maplist)
maplist = null
QDEL_NULL(defaultmap)
/datum/controller/configuration/Destroy()
full_wipe()
config = null
return ..()
/datum/controller/configuration/proc/InitEntries()
var/list/_entries = list()
entries = _entries
var/list/_entries_by_type = list()
entries_by_type = _entries_by_type
for(var/I in typesof(/datum/config_entry)) //typesof is faster in this case
var/datum/config_entry/E = I
if(initial(E.abstract_type) == I)
continue
E = new I
var/esname = E.name
var/datum/config_entry/test = _entries[esname]
if(test)
log_config("Error: [test.type] has the same name as [E.type]: [esname]! Not initializing [E.type]!")
qdel(E)
continue
_entries[esname] = E
_entries_by_type[I] = E
/datum/controller/configuration/proc/RemoveEntry(datum/config_entry/CE)
entries -= CE.name
entries_by_type -= CE.type
/datum/controller/configuration/proc/LoadEntries(filename, list/stack = list())
if(IsAdminAdvancedProcCall())
return
var/filename_to_test = world.system_type == MS_WINDOWS ? lowertext(filename) : filename
if(filename_to_test in stack)
log_config("Warning: Config recursion detected ([english_list(stack)]), breaking!")
return
stack = stack + filename_to_test
log_config("Loading config file [filename]...")
var/list/lines = world.file2list("[directory]/[filename]")
var/list/_entries = entries
for(var/L in lines)
L = trim(L)
if(!L)
continue
var/firstchar = L[1]
if(firstchar == "#")
continue
var/lockthis = firstchar == "@"
if(lockthis)
L = copytext(L, length(firstchar) + 1)
var/pos = findtext(L, " ")
var/entry = null
var/value = null
if(pos)
entry = lowertext(copytext(L, 1, pos))
value = copytext(L, pos + length(L[pos]))
else
entry = lowertext(L)
if(!entry)
continue
if(entry == "$include")
if(!value)
log_config("Warning: Invalid $include directive: [value]")
else
LoadEntries(value, stack)
++.
continue
// Reset directive, used for setting a config value back to defaults. Useful for string list config types
if (entry == "$reset")
var/datum/config_entry/resetee = _entries[lowertext(value)]
if (!value || !resetee)
log_config("Warning: invalid $reset directive: [value]")
continue
resetee.set_default()
log_config("Reset configured value for [value] to original defaults")
continue
var/datum/config_entry/E = _entries[entry]
if(!E)
log_config("Unknown setting in configuration: '[entry]'")
continue
if(lockthis)
E.protection |= CONFIG_ENTRY_LOCKED
if(E.deprecated_by)
var/datum/config_entry/new_ver = entries_by_type[E.deprecated_by]
var/new_value = E.DeprecationUpdate(value)
var/good_update = istext(new_value)
log_config("Entry [entry] is deprecated and will be removed soon. Migrate to [new_ver.name]![good_update ? " Suggested new value is: [new_value]" : ""]")
if(!warned_deprecated_configs)
DelayedMessageAdmins("This server is using deprecated configuration settings. Please check the logs and update accordingly.")
warned_deprecated_configs = TRUE
if(good_update)
value = new_value
E = new_ver
else
warning("[new_ver.type] is deprecated but gave no proper return for DeprecationUpdate()")
var/validated = E.ValidateAndSet(value)
if(!validated)
log_config("Failed to validate setting \"[value]\" for [entry]")
else
if(E.modified && !E.dupes_allowed)
log_config("Duplicate setting for [entry] ([value], [E.resident_file]) detected! Using latest.")
E.resident_file = filename
if(validated)
E.modified = TRUE
++.
/datum/controller/configuration/can_vv_get(var_name)
return (var_name != NAMEOF(src, entries_by_type) || !hiding_entries_by_type) && ..()
/datum/controller/configuration/vv_edit_var(var_name, var_value)
var/list/banned_edits = list(NAMEOF(src, entries_by_type), NAMEOF(src, entries), NAMEOF(src, directory))
return !(var_name in banned_edits) && ..()
/datum/controller/configuration/stat_entry(msg)
msg = "Edit"
return msg
/datum/controller/configuration/proc/Get(entry_type)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
if(entry_is_abstract)
CRASH("Tried to retrieve an abstract config_entry: [entry_type]")
E = entries_by_type[entry_type]
if(!E)
CRASH("Missing config entry for [entry_type]!")
if((E.protection & CONFIG_ENTRY_HIDDEN) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Get" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
log_admin_private("Config access of [entry_type] attempted by [key_name(usr)]")
return
return E.config_entry_value
/datum/controller/configuration/proc/Set(entry_type, new_val)
var/datum/config_entry/E = entry_type
var/entry_is_abstract = initial(E.abstract_type) == entry_type
if(entry_is_abstract)
CRASH("Tried to set an abstract config_entry: [entry_type]")
E = entries_by_type[entry_type]
if(!E)
CRASH("Missing config entry for [entry_type]!")
if((E.protection & CONFIG_ENTRY_LOCKED) && IsAdminAdvancedProcCall() && GLOB.LastAdminCalledProc == "Set" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
log_admin_private("Config rewrite of [entry_type] to [new_val] attempted by [key_name(usr)]")
return
return E.ValidateAndSet("[new_val]")
/datum/controller/configuration/proc/LoadMOTD()
var/list/motd_contents = list()
var/list/motd_list = CONFIG_GET(str_list/motd)
if (motd_list.len == 0 && fexists("[directory]/motd.txt"))
motd_list = list("motd.txt")
for (var/motd_file in motd_list)
if (fexists("[directory]/[motd_file]"))
motd_contents += file2text("[directory]/[motd_file]")
else
log_config("MOTD file [motd_file] didn't exist")
DelayedMessageAdmins("MOTD file [motd_file] didn't exist")
motd = motd_contents.Join("\n")
var/tm_info = GLOB.revdata.GetTestMergeInfo()
if(motd || tm_info)
motd = motd ? "[motd]<br>[tm_info]" : tm_info
/*
Policy file should be a json file with a single object.
Value is raw html.
Possible keywords :
Job titles / Assigned roles (ghost spawners for example) : Assistant , Captain , Ash Walker
Mob types : /mob/living/simple_animal/hostile/carp
Antagonist types : /datum/antagonist/highlander
Species types : /datum/species/lizard
special keywords defined in _DEFINES/admin.dm
Example config:
{
JOB_ASSISTANT : "Don't kill everyone",
"/datum/antagonist/highlander" : "<b>Kill everyone</b>",
"Ash Walker" : "Kill all spacemans"
}
*/
/datum/controller/configuration/proc/LoadPolicy()
policy = list()
var/rawpolicy = file2text("[directory]/policy.json")
if(rawpolicy)
var/parsed = safe_json_decode(rawpolicy)
if(!parsed)
log_config("JSON parsing failure for policy.json")
DelayedMessageAdmins("JSON parsing failure for policy.json")
else
policy = parsed
/datum/controller/configuration/proc/loadmaplist(filename)
log_config("Loading config file [filename]...")
filename = "[directory]/[filename]"
var/list/Lines = world.file2list(filename)
var/datum/map_config/currentmap = null
for(var/t in Lines)
if(!t)
continue
t = trim(t)
if(length(t) == 0)
continue
else if(t[1] == "#")
continue
var/pos = findtext(t, " ")
var/command = null
var/data = null
if(pos)
command = lowertext(copytext(t, 1, pos))
data = copytext(t, pos + length(t[pos]))
else
command = lowertext(t)
if(!command)
continue
if (!currentmap && command != "map")
continue
switch (command)
if ("map")
currentmap = load_map_config(data, MAP_DIRECTORY_MAPS)
if(currentmap.defaulted)
var/error_message = "Failed to load map config for [data]!"
log_config(error_message)
log_mapping(error_message, TRUE)
currentmap = null
if ("minplayers","minplayer")
currentmap.config_min_users = text2num(data)
if ("maxplayers","maxplayer")
currentmap.config_max_users = text2num(data)
if ("weight","voteweight")
currentmap.voteweight = text2num(data)
if ("default","defaultmap")
defaultmap = currentmap
if ("votable")
currentmap.votable = TRUE
if ("endmap")
LAZYINITLIST(maplist)
maplist[currentmap.map_name] = currentmap
currentmap = null
if ("disabled")
currentmap = null
else
log_config("Unknown command in map vote config: '[command]'")
/datum/controller/configuration/proc/LoadChatFilter()
if(!fexists("[directory]/word_filter.toml"))
load_legacy_chat_filter()
return
log_config("Loading config file word_filter.toml...")
var/list/result = rustg_raw_read_toml_file("[directory]/word_filter.toml")
if(!result["success"])
var/message = "The word filter is not configured correctly! [result["content"]]"
log_config(message)
DelayedMessageAdmins(message)
return
var/list/word_filter = json_decode(result["content"])
ic_filter_reasons = try_extract_from_word_filter(word_filter, "ic")
ic_outside_pda_filter_reasons = try_extract_from_word_filter(word_filter, "ic_outside_pda")
shared_filter_reasons = try_extract_from_word_filter(word_filter, "shared")
soft_ic_filter_reasons = try_extract_from_word_filter(word_filter, "soft_ic")
soft_ic_outside_pda_filter_reasons = try_extract_from_word_filter(word_filter, "soft_ic_outside_pda")
soft_shared_filter_reasons = try_extract_from_word_filter(word_filter, "soft_shared")
update_chat_filter_regexes()
/datum/controller/configuration/proc/load_legacy_chat_filter()
if (!fexists("[directory]/in_character_filter.txt"))
return
log_config("Loading config file in_character_filter.txt...")
ic_filter_reasons = list()
ic_outside_pda_filter_reasons = list()
shared_filter_reasons = list()
soft_ic_filter_reasons = list()
soft_ic_outside_pda_filter_reasons = list()
soft_shared_filter_reasons = list()
for (var/line in world.file2list("[directory]/in_character_filter.txt"))
if (!line)
continue
if (findtextEx(line, "#", 1, 2))
continue
// The older filter didn't apply to PDA
ic_outside_pda_filter_reasons[line] = "No reason available"
update_chat_filter_regexes()
/// Will update the internal regexes of the chat filter based on the filter reasons
/datum/controller/configuration/proc/update_chat_filter_regexes()
ic_filter_regex = compile_filter_regex(ic_filter_reasons + ic_outside_pda_filter_reasons + shared_filter_reasons)
ic_outside_pda_filter_regex = compile_filter_regex(ic_filter_reasons + shared_filter_reasons)
ooc_filter_regex = compile_filter_regex(shared_filter_reasons)
soft_ic_filter_regex = compile_filter_regex(soft_ic_filter_reasons + soft_ic_outside_pda_filter_reasons + soft_shared_filter_reasons)
soft_ic_outside_pda_filter_regex = compile_filter_regex(soft_ic_filter_reasons + soft_shared_filter_reasons)
soft_ooc_filter_regex = compile_filter_regex(soft_shared_filter_reasons)
/datum/controller/configuration/proc/try_extract_from_word_filter(list/word_filter, key)
var/list/banned_words = word_filter[key]
if (isnull(banned_words))
return list()
else if (!islist(banned_words))
var/message = "The word filter configuration's '[key]' key was invalid, contact someone with configuration access to make sure it's setup properly."
log_config(message)
DelayedMessageAdmins(message)
return list()
var/list/formatted_banned_words = list()
for (var/banned_word in banned_words)
formatted_banned_words[lowertext(banned_word)] = banned_words[banned_word]
return formatted_banned_words
/datum/controller/configuration/proc/compile_filter_regex(list/banned_words)
if (isnull(banned_words) || banned_words.len == 0)
return null
var/static/regex/should_join_on_word_bounds = regex(@"^\w+$")
// Stuff like emoticons needs another split, since there's no way to get ":)" on a word bound.
// Furthermore, normal words need to be on word bounds, so "(adminhelp)" gets filtered.
var/list/to_join_on_whitespace_splits = list()
var/list/to_join_on_word_bounds = list()
for (var/banned_word in banned_words)
if (findtext(banned_word, should_join_on_word_bounds))
to_join_on_word_bounds += REGEX_QUOTE(banned_word)
else
to_join_on_whitespace_splits += REGEX_QUOTE(banned_word)
// We don't want a whitespace_split part if there's no stuff that requires it
var/whitespace_split = to_join_on_whitespace_splits.len > 0 ? @"(?:(?:^|\s+)(" + jointext(to_join_on_whitespace_splits, "|") + @")(?:$|\s+))" : ""
var/word_bounds = @"(\b(" + jointext(to_join_on_word_bounds, "|") + @")\b)"
var/regex_filter = whitespace_split != "" ? "([whitespace_split]|[word_bounds])" : word_bounds
return regex(regex_filter, "i")
//Message admins when you can.
/datum/controller/configuration/proc/DelayedMessageAdmins(text)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)