Force LF line endings with gitattributes and convert repo (#52266)

Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
This commit is contained in:
jdawg1290
2020-07-16 03:02:40 +03:00
committed by GitHub
co-authored by Aleksej Komarov
parent 39e8bd721f
commit 62676e72a8
1133 changed files with 266492 additions and 266454 deletions
+197 -197
View File
@@ -1,197 +1,197 @@
#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
var/name //read-only, this is determined by the last portion of the derived entry type
var/config_entry_value
var/default //read-only, just set value directly
var/resident_file //the file which this was loaded from, if any
var/modified = FALSE //set to TRUE if the default has been overridden by a config entry
var/deprecated_by //the /datum/config_entry type that supercedes this one
var/protection = NONE
var/abstract_type = /datum/config_entry //do not instantiate if type matches this
var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless.
var/dupes_allowed = FALSE
/datum/config_entry/New()
if(type == abstract_type)
CRASH("Abstract config entry [type] instatiated!")
name = lowertext(type2top(type))
if(islist(config_entry_value))
var/list/L = config_entry_value
default = L.Copy()
else
default = config_entry_value
/datum/config_entry/Destroy()
config.RemoveEntry(src)
return ..()
/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() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
if(!.)
log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]")
/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
config_entry_value = ""
abstract_type = /datum/config_entry/string
var/auto_trim = TRUE
/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
return TRUE
/datum/config_entry/number
config_entry_value = 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
config_entry_value = 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
/datum/config_entry/number_list
abstract_type = /datum/config_entry/number_list
config_entry_value = 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
config_entry_value = 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 = " "
/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/key_pos = findtext(str_val, splitter)
var/key_name = null
var/key_value = null
if(key_pos || value_mode == VALUE_MODE_FLAG)
key_name = lowertext(copytext(str_val, 1, key_pos))
if(key_pos)
key_value = copytext(str_val, key_pos + length(str_val[key_pos]))
var/new_key
var/new_value
var/continue_check_value
var/continue_check_key
switch(key_mode)
if(KEY_MODE_TEXT)
new_key = key_name
continue_check_key = new_key
if(KEY_MODE_TYPE)
new_key = key_name
if(!ispath(new_key))
new_key = text2path(new_key)
continue_check_key = ispath(new_key)
switch(value_mode)
if(VALUE_MODE_FLAG)
new_value = TRUE
continue_check_value = TRUE
if(VALUE_MODE_NUM)
new_value = text2num(key_value)
continue_check_value = !isnull(new_value)
if(VALUE_MODE_TEXT)
new_value = key_value
continue_check_value = new_value
if(continue_check_value && continue_check_key && ValidateListEntry(new_key, new_value))
config_entry_value[new_key] = new_value
return TRUE
return FALSE
/datum/config_entry/keyed_list/vv_edit_var(var_name, var_value)
return var_name != NAMEOF(src, splitter) && ..()
#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
var/name //read-only, this is determined by the last portion of the derived entry type
var/config_entry_value
var/default //read-only, just set value directly
var/resident_file //the file which this was loaded from, if any
var/modified = FALSE //set to TRUE if the default has been overridden by a config entry
var/deprecated_by //the /datum/config_entry type that supercedes this one
var/protection = NONE
var/abstract_type = /datum/config_entry //do not instantiate if type matches this
var/vv_VAS = TRUE //Force validate and set on VV. VAS proccall guard will run regardless.
var/dupes_allowed = FALSE
/datum/config_entry/New()
if(type == abstract_type)
CRASH("Abstract config entry [type] instatiated!")
name = lowertext(type2top(type))
if(islist(config_entry_value))
var/list/L = config_entry_value
default = L.Copy()
else
default = config_entry_value
/datum/config_entry/Destroy()
config.RemoveEntry(src)
return ..()
/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() && GLOB.LastAdminCalledProc == "ValidateAndSet" && GLOB.LastAdminCalledTargetRef == "[REF(src)]")
if(!.)
log_admin_private("Config set of [type] to [str_val] attempted by [key_name(usr)]")
/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
config_entry_value = ""
abstract_type = /datum/config_entry/string
var/auto_trim = TRUE
/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
return TRUE
/datum/config_entry/number
config_entry_value = 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
config_entry_value = 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
/datum/config_entry/number_list
abstract_type = /datum/config_entry/number_list
config_entry_value = 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
config_entry_value = 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 = " "
/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/key_pos = findtext(str_val, splitter)
var/key_name = null
var/key_value = null
if(key_pos || value_mode == VALUE_MODE_FLAG)
key_name = lowertext(copytext(str_val, 1, key_pos))
if(key_pos)
key_value = copytext(str_val, key_pos + length(str_val[key_pos]))
var/new_key
var/new_value
var/continue_check_value
var/continue_check_key
switch(key_mode)
if(KEY_MODE_TEXT)
new_key = key_name
continue_check_key = new_key
if(KEY_MODE_TYPE)
new_key = key_name
if(!ispath(new_key))
new_key = text2path(new_key)
continue_check_key = ispath(new_key)
switch(value_mode)
if(VALUE_MODE_FLAG)
new_value = TRUE
continue_check_value = TRUE
if(VALUE_MODE_NUM)
new_value = text2num(key_value)
continue_check_value = !isnull(new_value)
if(VALUE_MODE_TEXT)
new_value = key_value
continue_check_value = new_value
if(continue_check_value && continue_check_key && ValidateListEntry(new_key, new_value))
config_entry_value[new_key] = new_value
return TRUE
return FALSE
/datum/config_entry/keyed_list/vv_edit_var(var_name, var_value)
return var_name != NAMEOF(src, splitter) && ..()
+424 -424
View File
@@ -1,424 +1,424 @@
/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
var/static/regex/ic_filter_regex
/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()
LoadModes()
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()
/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
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()
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Edit", src)
stat("[name]:", statclick)
/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/LoadModes()
gamemode_cache = typecacheof(/datum/game_mode, TRUE)
modes = list()
mode_names = list()
mode_reports = list()
mode_false_report_weight = list()
votable_modes = list()
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
for(var/T in gamemode_cache)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
var/datum/game_mode/M = new T()
if(M.config_tag)
if(!(M.config_tag in modes)) // ensure each mode is added only once
modes += M.config_tag
mode_names[M.config_tag] = M.name
probabilities[M.config_tag] = M.probability
mode_reports[M.report_type] = M.generate_report()
if(probabilities[M.config_tag]>0)
mode_false_report_weight[M.report_type] = M.false_report_weight
else
//"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode.
mode_false_report_weight[M.report_type] = min(1, M.false_report_weight)
if(M.votable)
votable_modes += M.config_tag
qdel(M)
votable_modes += "secret"
/datum/controller/configuration/proc/LoadMOTD()
motd = file2text("[directory]/motd.txt")
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:
{
"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("_maps/[data].json")
if(currentmap.defaulted)
log_config("Failed to load map config for [data]!")
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/pick_mode(mode_name)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
// ^ This guy didn't try hard enough
for(var/T in gamemode_cache)
var/datum/game_mode/M = T
var/ct = initial(M.config_tag)
if(ct && ct == mode_name)
return new T
return new /datum/game_mode/extended()
/datum/controller/configuration/proc/get_runnable_modes()
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop)
var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
for(var/T in gamemode_cache)
var/datum/game_mode/M = new T()
if(!(M.config_tag in modes))
qdel(M)
continue
if(probabilities[M.config_tag]<=0)
qdel(M)
continue
if(min_pop[M.config_tag])
M.required_players = min_pop[M.config_tag]
if(max_pop[M.config_tag])
M.maximum_players = max_pop[M.config_tag]
if(M.can_start())
var/final_weight = probabilities[M.config_tag]
if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3)
var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3)
var/adjustment = 0
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0)
final_weight *= ((100-adjustment)/100)
runnable_modes[M] = final_weight
return runnable_modes
/datum/controller/configuration/proc/get_runnable_midround_modes(crew)
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop)
var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop)
for(var/T in (gamemode_cache - SSticker.mode.type))
var/datum/game_mode/M = new T()
if(!(M.config_tag in modes))
qdel(M)
continue
if(probabilities[M.config_tag]<=0)
qdel(M)
continue
if(min_pop[M.config_tag])
M.required_players = min_pop[M.config_tag]
if(max_pop[M.config_tag])
M.maximum_players = max_pop[M.config_tag]
if(M.required_players <= crew)
if(M.maximum_players >= 0 && M.maximum_players < crew)
continue
runnable_modes[M] = probabilities[M.config_tag]
return runnable_modes
/datum/controller/configuration/proc/LoadChatFilter()
var/list/in_character_filter = list()
if(!fexists("[directory]/in_character_filter.txt"))
return
log_config("Loading config file in_character_filter.txt...")
for(var/line in world.file2list("[directory]/in_character_filter.txt"))
if(!line)
continue
if(findtextEx(line,"#",1,2))
continue
in_character_filter += REGEX_QUOTE(line)
ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null
syncChatRegexes()
//Message admins when you can.
/datum/controller/configuration/proc/DelayedMessageAdmins(text)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)
/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
var/static/regex/ic_filter_regex
/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()
LoadModes()
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()
/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
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()
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Edit", src)
stat("[name]:", statclick)
/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/LoadModes()
gamemode_cache = typecacheof(/datum/game_mode, TRUE)
modes = list()
mode_names = list()
mode_reports = list()
mode_false_report_weight = list()
votable_modes = list()
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
for(var/T in gamemode_cache)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
var/datum/game_mode/M = new T()
if(M.config_tag)
if(!(M.config_tag in modes)) // ensure each mode is added only once
modes += M.config_tag
mode_names[M.config_tag] = M.name
probabilities[M.config_tag] = M.probability
mode_reports[M.report_type] = M.generate_report()
if(probabilities[M.config_tag]>0)
mode_false_report_weight[M.report_type] = M.false_report_weight
else
//"impossible" modes will still falsly show up occasionally, else they'll stick out like a sore thumb if an admin decides to force a disabled gamemode.
mode_false_report_weight[M.report_type] = min(1, M.false_report_weight)
if(M.votable)
votable_modes += M.config_tag
qdel(M)
votable_modes += "secret"
/datum/controller/configuration/proc/LoadMOTD()
motd = file2text("[directory]/motd.txt")
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:
{
"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("_maps/[data].json")
if(currentmap.defaulted)
log_config("Failed to load map config for [data]!")
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/pick_mode(mode_name)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
// ^ This guy didn't try hard enough
for(var/T in gamemode_cache)
var/datum/game_mode/M = T
var/ct = initial(M.config_tag)
if(ct && ct == mode_name)
return new T
return new /datum/game_mode/extended()
/datum/controller/configuration/proc/get_runnable_modes()
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop)
var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop)
var/list/repeated_mode_adjust = Get(/datum/config_entry/number_list/repeated_mode_adjust)
for(var/T in gamemode_cache)
var/datum/game_mode/M = new T()
if(!(M.config_tag in modes))
qdel(M)
continue
if(probabilities[M.config_tag]<=0)
qdel(M)
continue
if(min_pop[M.config_tag])
M.required_players = min_pop[M.config_tag]
if(max_pop[M.config_tag])
M.maximum_players = max_pop[M.config_tag]
if(M.can_start())
var/final_weight = probabilities[M.config_tag]
if(SSpersistence.saved_modes.len == 3 && repeated_mode_adjust.len == 3)
var/recent_round = min(SSpersistence.saved_modes.Find(M.config_tag),3)
var/adjustment = 0
while(recent_round)
adjustment += repeated_mode_adjust[recent_round]
recent_round = SSpersistence.saved_modes.Find(M.config_tag,recent_round+1,0)
final_weight *= ((100-adjustment)/100)
runnable_modes[M] = final_weight
return runnable_modes
/datum/controller/configuration/proc/get_runnable_midround_modes(crew)
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
var/list/min_pop = Get(/datum/config_entry/keyed_list/min_pop)
var/list/max_pop = Get(/datum/config_entry/keyed_list/max_pop)
for(var/T in (gamemode_cache - SSticker.mode.type))
var/datum/game_mode/M = new T()
if(!(M.config_tag in modes))
qdel(M)
continue
if(probabilities[M.config_tag]<=0)
qdel(M)
continue
if(min_pop[M.config_tag])
M.required_players = min_pop[M.config_tag]
if(max_pop[M.config_tag])
M.maximum_players = max_pop[M.config_tag]
if(M.required_players <= crew)
if(M.maximum_players >= 0 && M.maximum_players < crew)
continue
runnable_modes[M] = probabilities[M.config_tag]
return runnable_modes
/datum/controller/configuration/proc/LoadChatFilter()
var/list/in_character_filter = list()
if(!fexists("[directory]/in_character_filter.txt"))
return
log_config("Loading config file in_character_filter.txt...")
for(var/line in world.file2list("[directory]/in_character_filter.txt"))
if(!line)
continue
if(findtextEx(line,"#",1,2))
continue
in_character_filter += REGEX_QUOTE(line)
ic_filter_regex = in_character_filter.len ? regex("\\b([jointext(in_character_filter, "|")])\\b", "i") : null
syncChatRegexes()
//Message admins when you can.
/datum/controller/configuration/proc/DelayedMessageAdmins(text)
addtimer(CALLBACK(GLOBAL_PROC, /proc/message_admins, text), 0)
+23 -23
View File
@@ -1,23 +1,23 @@
/datum/config_entry/string/comms_key
protection = CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/comms_key/ValidateAndSet(str_val)
return str_val != "default_pwd" && length(str_val) > 6 && ..()
/datum/config_entry/keyed_list/cross_server
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_TEXT
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/keyed_list/cross_server/ValidateAndSet(str_val)
. = ..()
if(.)
var/list/newv = list()
for(var/I in config_entry_value)
newv[replacetext(I, "+", " ")] = config_entry_value[I]
config_entry_value = newv
/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value)
return key_value != "byond:\\address:port" && ..()
/datum/config_entry/string/cross_comms_name
/datum/config_entry/string/comms_key
protection = CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/comms_key/ValidateAndSet(str_val)
return str_val != "default_pwd" && length(str_val) > 6 && ..()
/datum/config_entry/keyed_list/cross_server
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_TEXT
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/keyed_list/cross_server/ValidateAndSet(str_val)
. = ..()
if(.)
var/list/newv = list()
for(var/I in config_entry_value)
newv[replacetext(I, "+", " ")] = config_entry_value[I]
config_entry_value = newv
/datum/config_entry/keyed_list/cross_server/ValidateListEntry(key_name, key_value)
return key_value != "byond:\\address:port" && ..()
/datum/config_entry/string/cross_comms_name
@@ -1,50 +1,50 @@
/datum/config_entry/flag/sql_enabled // for sql switching
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/string/address
config_entry_value = "localhost"
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/number/port
config_entry_value = 3306
min_val = 0
max_val = 65535
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_database
config_entry_value = "test"
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_login
config_entry_value = "root"
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_password
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_tableprefix
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/number/query_debug_log_timeout
config_entry_value = 70
min_val = 1
protection = CONFIG_ENTRY_LOCKED
deprecated_by = /datum/config_entry/number/blocking_query_timeout
/datum/config_entry/number/query_debug_log_timeout/DeprecationUpdate(value)
return value
/datum/config_entry/number/async_query_timeout
config_entry_value = 10
min_val = 0
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/number/blocking_query_timeout
config_entry_value = 5
min_val = 0
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/number/bsql_thread_limit
config_entry_value = 50
min_val = 1
/datum/config_entry/flag/sql_enabled // for sql switching
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/string/address
config_entry_value = "localhost"
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/number/port
config_entry_value = 3306
min_val = 0
max_val = 65535
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_database
config_entry_value = "test"
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_login
config_entry_value = "root"
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_password
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/string/feedback_tableprefix
protection = CONFIG_ENTRY_LOCKED | CONFIG_ENTRY_HIDDEN
/datum/config_entry/number/query_debug_log_timeout
config_entry_value = 70
min_val = 1
protection = CONFIG_ENTRY_LOCKED
deprecated_by = /datum/config_entry/number/blocking_query_timeout
/datum/config_entry/number/query_debug_log_timeout/DeprecationUpdate(value)
return value
/datum/config_entry/number/async_query_timeout
config_entry_value = 10
min_val = 0
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/number/blocking_query_timeout
config_entry_value = 5
min_val = 0
protection = CONFIG_ENTRY_LOCKED
/datum/config_entry/number/bsql_thread_limit
config_entry_value = 50
min_val = 1
@@ -1,404 +1,404 @@
/datum/config_entry/number_list/repeated_mode_adjust
/datum/config_entry/keyed_list/probability
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name)
return key_name in config.modes
/datum/config_entry/keyed_list/max_pop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name)
return key_name in config.modes
/datum/config_entry/keyed_list/min_pop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value)
return key_name in config.modes
/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value)
return key_name in config.modes
/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value)
return key_name in config.modes
/datum/config_entry/number/damage_multiplier
config_entry_value = 1
integer = FALSE
/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on.
min_val = 0
/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access.
/datum/config_entry/flag/assistants_have_maint_access
/datum/config_entry/flag/security_has_maint_access
/datum/config_entry/flag/everyone_has_maint_access
/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts
/datum/config_entry/flag/force_random_names
/datum/config_entry/flag/humans_need_surnames
/datum/config_entry/flag/allow_ai // allow ai job
/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode
/datum/config_entry/flag/disable_human_mood
/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen.
/datum/config_entry/flag/disable_peaceborg
/datum/config_entry/flag/disable_warops
/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors
config_entry_value = 6
integer = FALSE
min_val = 1
/datum/config_entry/number/brother_scaling_coeff //how many players per brother team
config_entry_value = 25
integer = FALSE
min_val = 1
/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings
config_entry_value = 6
integer = FALSE
min_val = 1
/datum/config_entry/number/ecult_scaling_coeff //how much does the amount of players get divided by to determine e_cult
config_entry_value = 6
integer = FALSE
min_val = 1
/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions
config_entry_value = 8
integer = FALSE
min_val = 1
/datum/config_entry/number/traitor_objectives_amount
config_entry_value = 2
min_val = 0
/datum/config_entry/number/brother_objectives_amount
config_entry_value = 2
min_val = 0
/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors
/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other
/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other
/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff
/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling
/datum/config_entry/flag/use_antag_rep // see game_options.txt for details
/datum/config_entry/number/antag_rep_maximum
config_entry_value = 200
integer = FALSE
min_val = 0
/datum/config_entry/number/default_antag_tickets
config_entry_value = 100
integer = FALSE
min_val = 0
/datum/config_entry/number/max_tickets_per_roll
config_entry_value = 100
integer = FALSE
min_val = 0
/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system)
config_entry_value = 60
integer = FALSE
min_val = 0
/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist
config_entry_value = 0.7
integer = FALSE
min_val = 0
max_val = 1
/datum/config_entry/number/shuttle_refuel_delay
config_entry_value = 12000
integer = FALSE
min_val = 0
/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen
/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go.
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race.
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game
/datum/config_entry/flag/no_summon_guns //No
/datum/config_entry/flag/no_summon_magic //Fun
/datum/config_entry/flag/no_summon_events //Allowed
/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes.
/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station
config_entry_value = 55
integer = FALSE
min_val = 30
/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join
/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join
/datum/config_entry/string/alert_green
config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced."
/datum/config_entry/string/alert_blue_upto
config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted."
/datum/config_entry/string/alert_blue_downto
config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed."
/datum/config_entry/string/alert_red_upto
config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised."
/datum/config_entry/string/alert_red_downto
config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised."
/datum/config_entry/string/alert_delta
config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill."
/datum/config_entry/flag/revival_pod_plants
/datum/config_entry/number/revival_brain_life
config_entry_value = -1
integer = FALSE
min_val = -1
/datum/config_entry/flag/ooc_during_round
/datum/config_entry/number/commendations
integer = FALSE
/datum/config_entry/flag/emojis
/datum/config_entry/keyed_list/multiplicative_movespeed
key_mode = KEY_MODE_TYPE
value_mode = VALUE_MODE_NUM
config_entry_value = list( //DEFAULTS
/mob/living/simple_animal = 1,
/mob/living/silicon/pai = 1,
/mob/living/carbon/alien/humanoid/hunter = -1,
/mob/living/carbon/alien/humanoid/royal/praetorian = 1,
/mob/living/carbon/alien/humanoid/royal/queen = 3
)
/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet()
. = ..()
if(.)
update_config_movespeed_type_lookup(TRUE)
/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value)
. = ..()
if(. && (var_name == NAMEOF(src, config_entry_value)))
update_config_movespeed_type_lookup(TRUE)
/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs.
abstract_type = /datum/config_entry/number/movedelay
/datum/config_entry/number/movedelay/ValidateAndSet()
. = ..()
if(.)
update_mob_config_movespeeds()
/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value)
. = ..()
if(. && (var_name == NAMEOF(src, config_entry_value)))
update_mob_config_movespeeds()
/datum/config_entry/number/movedelay/run_delay
integer = FALSE
/datum/config_entry/number/movedelay/run_delay/ValidateAndSet()
. = ..()
var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/run)
M.sync()
/datum/config_entry/number/movedelay/walk_delay
integer = FALSE
/datum/config_entry/number/movedelay/walk_delay/ValidateAndSet()
. = ..()
var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk)
M.sync()
/////////////////////////////////////////////////Outdated move delay
/datum/config_entry/number/outdated_movedelay
deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed
abstract_type = /datum/config_entry/number/outdated_movedelay
integer = FALSE
var/movedelay_type
/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value)
return "[movedelay_type] [value]"
/datum/config_entry/number/outdated_movedelay/human_delay
movedelay_type = /mob/living/carbon/human
/datum/config_entry/number/outdated_movedelay/robot_delay
movedelay_type = /mob/living/silicon/robot
/datum/config_entry/number/outdated_movedelay/monkey_delay
movedelay_type = /mob/living/carbon/monkey
/datum/config_entry/number/outdated_movedelay/alien_delay
movedelay_type = /mob/living/carbon/alien
/datum/config_entry/number/outdated_movedelay/slime_delay
movedelay_type = /mob/living/simple_animal/slime
/datum/config_entry/number/outdated_movedelay/animal_delay
movedelay_type = /mob/living/simple_animal
/////////////////////////////////////////////////
/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded
/datum/config_entry/flag/roundstart_away //Will random away mission be loaded.
/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled.
config_entry_value = 18000
integer = FALSE
min_val = 0
/datum/config_entry/flag/ghost_interaction
/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death
/datum/config_entry/flag/silent_ai
/datum/config_entry/flag/silent_borg
/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff
/datum/config_entry/number/default_laws //Controls what laws the AI spawns with.
config_entry_value = 0
min_val = 0
max_val = 3
/datum/config_entry/number/silicon_max_law_amount
config_entry_value = 12
min_val = 0
/datum/config_entry/keyed_list/random_laws
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/law_weight
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
splitter = ","
/datum/config_entry/number/max_law_len
config_entry_value = 1024
/datum/config_entry/number/overflow_cap
config_entry_value = -1
min_val = -1
/datum/config_entry/string/overflow_job
config_entry_value = "Assistant"
/datum/config_entry/flag/starlight
/datum/config_entry/flag/grey_assistants
/datum/config_entry/number/lavaland_budget
config_entry_value = 60
integer = FALSE
min_val = 0
/datum/config_entry/number/icemoon_budget
config_entry_value = 90
integer = FALSE
min_val = 0
/datum/config_entry/number/space_budget
config_entry_value = 16
integer = FALSE
min_val = 0
/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set
/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts
config_entry_value = 1
min_val = 0
integer = FALSE
/datum/config_entry/number/events_min_players_mul
config_entry_value = 1
min_val = 0
integer = FALSE
/datum/config_entry/number/mice_roundstart
config_entry_value = 10
min_val = 0
/datum/config_entry/number/bombcap
config_entry_value = 14
min_val = 4
/datum/config_entry/number/bombcap/ValidateAndSet(str_val)
. = ..()
if(.)
GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4)
GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2)
GLOB.MAX_EX_LIGHT_RANGE = config_entry_value
GLOB.MAX_EX_FLASH_RANGE = config_entry_value
GLOB.MAX_EX_FLAME_RANGE = config_entry_value
/datum/config_entry/number/emergency_shuttle_autocall_threshold
min_val = 0
max_val = 1
integer = FALSE
/datum/config_entry/flag/roundstart_traits
/datum/config_entry/flag/enable_night_shifts
/datum/config_entry/flag/randomize_shift_time
/datum/config_entry/flag/shift_time_realtime
/datum/config_entry/keyed_list/antag_rep
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/number/monkeycap
config_entry_value = 64
min_val = 0
/datum/config_entry/number/ratcap
config_entry_value = 64
min_val = 0
/datum/config_entry/number/maxfine
config_entry_value = 1000
min_val = 0
/datum/config_entry/flag/dynamic_config_enabled
/datum/config_entry/number_list/repeated_mode_adjust
/datum/config_entry/keyed_list/probability
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/keyed_list/probability/ValidateListEntry(key_name)
return key_name in config.modes
/datum/config_entry/keyed_list/max_pop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/keyed_list/max_pop/ValidateListEntry(key_name)
return key_name in config.modes
/datum/config_entry/keyed_list/min_pop
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/keyed_list/min_pop/ValidateListEntry(key_name, key_value)
return key_name in config.modes
/datum/config_entry/keyed_list/continuous // which roundtypes continue if all antagonists die
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/continuous/ValidateListEntry(key_name, key_value)
return key_name in config.modes
/datum/config_entry/keyed_list/midround_antag // which roundtypes use the midround antagonist system
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/midround_antag/ValidateListEntry(key_name, key_value)
return key_name in config.modes
/datum/config_entry/number/damage_multiplier
config_entry_value = 1
integer = FALSE
/datum/config_entry/number/minimal_access_threshold //If the number of players is larger than this threshold, minimal access will be turned on.
min_val = 0
/datum/config_entry/flag/jobs_have_minimal_access //determines whether jobs use minimal access or expanded access.
/datum/config_entry/flag/assistants_have_maint_access
/datum/config_entry/flag/security_has_maint_access
/datum/config_entry/flag/everyone_has_maint_access
/datum/config_entry/flag/sec_start_brig //makes sec start in brig instead of dept sec posts
/datum/config_entry/flag/force_random_names
/datum/config_entry/flag/humans_need_surnames
/datum/config_entry/flag/allow_ai // allow ai job
/datum/config_entry/flag/allow_ai_multicam // allow ai multicamera mode
/datum/config_entry/flag/disable_human_mood
/datum/config_entry/flag/disable_secborg // disallow secborg module to be chosen.
/datum/config_entry/flag/disable_peaceborg
/datum/config_entry/flag/disable_warops
/datum/config_entry/number/traitor_scaling_coeff //how much does the amount of players get divided by to determine traitors
config_entry_value = 6
integer = FALSE
min_val = 1
/datum/config_entry/number/brother_scaling_coeff //how many players per brother team
config_entry_value = 25
integer = FALSE
min_val = 1
/datum/config_entry/number/changeling_scaling_coeff //how much does the amount of players get divided by to determine changelings
config_entry_value = 6
integer = FALSE
min_val = 1
/datum/config_entry/number/ecult_scaling_coeff //how much does the amount of players get divided by to determine e_cult
config_entry_value = 6
integer = FALSE
min_val = 1
/datum/config_entry/number/security_scaling_coeff //how much does the amount of players get divided by to determine open security officer positions
config_entry_value = 8
integer = FALSE
min_val = 1
/datum/config_entry/number/traitor_objectives_amount
config_entry_value = 2
min_val = 0
/datum/config_entry/number/brother_objectives_amount
config_entry_value = 2
min_val = 0
/datum/config_entry/flag/reactionary_explosions //If we use reactionary explosions, explosions that react to walls and doors
/datum/config_entry/flag/protect_roles_from_antagonist //If security and such can be traitor/cult/other
/datum/config_entry/flag/protect_assistant_from_antagonist //If assistants can be traitor/cult/other
/datum/config_entry/flag/enforce_human_authority //If non-human species are barred from joining as a head of staff
/datum/config_entry/flag/allow_latejoin_antagonists // If late-joining players can be traitor/changeling
/datum/config_entry/flag/use_antag_rep // see game_options.txt for details
/datum/config_entry/number/antag_rep_maximum
config_entry_value = 200
integer = FALSE
min_val = 0
/datum/config_entry/number/default_antag_tickets
config_entry_value = 100
integer = FALSE
min_val = 0
/datum/config_entry/number/max_tickets_per_roll
config_entry_value = 100
integer = FALSE
min_val = 0
/datum/config_entry/number/midround_antag_time_check // How late (in minutes you want the midround antag system to stay on, setting this to 0 will disable the system)
config_entry_value = 60
integer = FALSE
min_val = 0
/datum/config_entry/number/midround_antag_life_check // A ratio of how many people need to be alive in order for the round not to immediately end in midround antagonist
config_entry_value = 0.7
integer = FALSE
min_val = 0
max_val = 1
/datum/config_entry/number/shuttle_refuel_delay
config_entry_value = 12000
integer = FALSE
min_val = 0
/datum/config_entry/flag/show_game_type_odds //if set this allows players to see the odds of each roundtype on the get revision screen
/datum/config_entry/keyed_list/roundstart_races //races you can play as from the get go.
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/roundstart_no_hard_check // Species contained in this list will not cause existing characters with no-longer-roundstart species set to be resetted to the human race.
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/flag/join_with_mutant_humans //players can pick mutant bodyparts for humans before joining the game
/datum/config_entry/flag/no_summon_guns //No
/datum/config_entry/flag/no_summon_magic //Fun
/datum/config_entry/flag/no_summon_events //Allowed
/datum/config_entry/flag/no_intercept_report //Whether or not to send a communications intercept report roundstart. This may be overridden by gamemodes.
/datum/config_entry/number/arrivals_shuttle_dock_window //Time from when a player late joins on the arrivals shuttle to when the shuttle docks on the station
config_entry_value = 55
integer = FALSE
min_val = 30
/datum/config_entry/flag/arrivals_shuttle_require_undocked //Require the arrivals shuttle to be undocked before latejoiners can join
/datum/config_entry/flag/arrivals_shuttle_require_safe_latejoin //Require the arrivals shuttle to be operational in order for latejoiners to join
/datum/config_entry/string/alert_green
config_entry_value = "All threats to the station have passed. Security may not have weapons visible, privacy laws are once again fully enforced."
/datum/config_entry/string/alert_blue_upto
config_entry_value = "The station has received reliable information about possible hostile activity on the station. Security staff may have weapons visible, random searches are permitted."
/datum/config_entry/string/alert_blue_downto
config_entry_value = "The immediate threat has passed. Security may no longer have weapons drawn at all times, but may continue to have them visible. Random searches are still allowed."
/datum/config_entry/string/alert_red_upto
config_entry_value = "There is an immediate serious threat to the station. Security may have weapons unholstered at all times. Random searches are allowed and advised."
/datum/config_entry/string/alert_red_downto
config_entry_value = "The station's destruction has been averted. There is still however an immediate serious threat to the station. Security may have weapons unholstered at all times, random searches are allowed and advised."
/datum/config_entry/string/alert_delta
config_entry_value = "Destruction of the station is imminent. All crew are instructed to obey all instructions given by heads of staff. Any violations of these orders can be punished by death. This is not a drill."
/datum/config_entry/flag/revival_pod_plants
/datum/config_entry/number/revival_brain_life
config_entry_value = -1
integer = FALSE
min_val = -1
/datum/config_entry/flag/ooc_during_round
/datum/config_entry/number/commendations
integer = FALSE
/datum/config_entry/flag/emojis
/datum/config_entry/keyed_list/multiplicative_movespeed
key_mode = KEY_MODE_TYPE
value_mode = VALUE_MODE_NUM
config_entry_value = list( //DEFAULTS
/mob/living/simple_animal = 1,
/mob/living/silicon/pai = 1,
/mob/living/carbon/alien/humanoid/hunter = -1,
/mob/living/carbon/alien/humanoid/royal/praetorian = 1,
/mob/living/carbon/alien/humanoid/royal/queen = 3
)
/datum/config_entry/keyed_list/multiplicative_movespeed/ValidateAndSet()
. = ..()
if(.)
update_config_movespeed_type_lookup(TRUE)
/datum/config_entry/keyed_list/multiplicative_movespeed/vv_edit_var(var_name, var_value)
. = ..()
if(. && (var_name == NAMEOF(src, config_entry_value)))
update_config_movespeed_type_lookup(TRUE)
/datum/config_entry/number/movedelay //Used for modifying movement speed for mobs.
abstract_type = /datum/config_entry/number/movedelay
/datum/config_entry/number/movedelay/ValidateAndSet()
. = ..()
if(.)
update_mob_config_movespeeds()
/datum/config_entry/number/movedelay/vv_edit_var(var_name, var_value)
. = ..()
if(. && (var_name == NAMEOF(src, config_entry_value)))
update_mob_config_movespeeds()
/datum/config_entry/number/movedelay/run_delay
integer = FALSE
/datum/config_entry/number/movedelay/run_delay/ValidateAndSet()
. = ..()
var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/run)
M.sync()
/datum/config_entry/number/movedelay/walk_delay
integer = FALSE
/datum/config_entry/number/movedelay/walk_delay/ValidateAndSet()
. = ..()
var/datum/movespeed_modifier/config_walk_run/M = get_cached_movespeed_modifier(/datum/movespeed_modifier/config_walk_run/walk)
M.sync()
/////////////////////////////////////////////////Outdated move delay
/datum/config_entry/number/outdated_movedelay
deprecated_by = /datum/config_entry/keyed_list/multiplicative_movespeed
abstract_type = /datum/config_entry/number/outdated_movedelay
integer = FALSE
var/movedelay_type
/datum/config_entry/number/outdated_movedelay/DeprecationUpdate(value)
return "[movedelay_type] [value]"
/datum/config_entry/number/outdated_movedelay/human_delay
movedelay_type = /mob/living/carbon/human
/datum/config_entry/number/outdated_movedelay/robot_delay
movedelay_type = /mob/living/silicon/robot
/datum/config_entry/number/outdated_movedelay/monkey_delay
movedelay_type = /mob/living/carbon/monkey
/datum/config_entry/number/outdated_movedelay/alien_delay
movedelay_type = /mob/living/carbon/alien
/datum/config_entry/number/outdated_movedelay/slime_delay
movedelay_type = /mob/living/simple_animal/slime
/datum/config_entry/number/outdated_movedelay/animal_delay
movedelay_type = /mob/living/simple_animal
/////////////////////////////////////////////////
/datum/config_entry/flag/virtual_reality //Will virtual reality be loaded
/datum/config_entry/flag/roundstart_away //Will random away mission be loaded.
/datum/config_entry/number/gateway_delay //How long the gateway takes before it activates. Default is half an hour. Only matters if roundstart_away is enabled.
config_entry_value = 18000
integer = FALSE
min_val = 0
/datum/config_entry/flag/ghost_interaction
/datum/config_entry/flag/near_death_experience //If carbons can hear ghosts when unconscious and very close to death
/datum/config_entry/flag/silent_ai
/datum/config_entry/flag/silent_borg
/datum/config_entry/flag/sandbox_autoclose // close the sandbox panel after spawning an item, potentially reducing griff
/datum/config_entry/number/default_laws //Controls what laws the AI spawns with.
config_entry_value = 0
min_val = 0
max_val = 3
/datum/config_entry/number/silicon_max_law_amount
config_entry_value = 12
min_val = 0
/datum/config_entry/keyed_list/random_laws
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_FLAG
/datum/config_entry/keyed_list/law_weight
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
splitter = ","
/datum/config_entry/number/max_law_len
config_entry_value = 1024
/datum/config_entry/number/overflow_cap
config_entry_value = -1
min_val = -1
/datum/config_entry/string/overflow_job
config_entry_value = "Assistant"
/datum/config_entry/flag/starlight
/datum/config_entry/flag/grey_assistants
/datum/config_entry/number/lavaland_budget
config_entry_value = 60
integer = FALSE
min_val = 0
/datum/config_entry/number/icemoon_budget
config_entry_value = 90
integer = FALSE
min_val = 0
/datum/config_entry/number/space_budget
config_entry_value = 16
integer = FALSE
min_val = 0
/datum/config_entry/flag/allow_random_events // Enables random events mid-round when set
/datum/config_entry/number/events_min_time_mul // Multipliers for random events minimal starting time and minimal players amounts
config_entry_value = 1
min_val = 0
integer = FALSE
/datum/config_entry/number/events_min_players_mul
config_entry_value = 1
min_val = 0
integer = FALSE
/datum/config_entry/number/mice_roundstart
config_entry_value = 10
min_val = 0
/datum/config_entry/number/bombcap
config_entry_value = 14
min_val = 4
/datum/config_entry/number/bombcap/ValidateAndSet(str_val)
. = ..()
if(.)
GLOB.MAX_EX_DEVESTATION_RANGE = round(config_entry_value / 4)
GLOB.MAX_EX_HEAVY_RANGE = round(config_entry_value / 2)
GLOB.MAX_EX_LIGHT_RANGE = config_entry_value
GLOB.MAX_EX_FLASH_RANGE = config_entry_value
GLOB.MAX_EX_FLAME_RANGE = config_entry_value
/datum/config_entry/number/emergency_shuttle_autocall_threshold
min_val = 0
max_val = 1
integer = FALSE
/datum/config_entry/flag/roundstart_traits
/datum/config_entry/flag/enable_night_shifts
/datum/config_entry/flag/randomize_shift_time
/datum/config_entry/flag/shift_time_realtime
/datum/config_entry/keyed_list/antag_rep
key_mode = KEY_MODE_TEXT
value_mode = VALUE_MODE_NUM
/datum/config_entry/number/monkeycap
config_entry_value = 64
min_val = 0
/datum/config_entry/number/ratcap
config_entry_value = 64
min_val = 0
/datum/config_entry/number/maxfine
config_entry_value = 1000
min_val = 0
/datum/config_entry/flag/dynamic_config_enabled
File diff suppressed because it is too large Load Diff
+102 -102
View File
@@ -1,102 +1,102 @@
/**
* Failsafe
*
* Pretty much pokes the MC to make sure it's still alive.
**/
GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/datum/controller/failsafe // This thing pretty much just keeps poking the master controller
name = "Failsafe"
// The length of time to check on the MC (in deciseconds).
// Set to 0 to disable.
var/processing_interval = 20
// The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC.
var/defcon = 5
//the world.time of the last check, so the mc can restart US if we hang.
// (Real friends look out for *eachother*)
var/lasttick = 0
// Track the MC iteration to make sure its still on track.
var/master_iteration = 0
var/running = TRUE
/datum/controller/failsafe/New()
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(Failsafe != src)
if(istype(Failsafe))
qdel(Failsafe)
Failsafe = src
Initialize()
/datum/controller/failsafe/Initialize()
set waitfor = 0
Failsafe.Loop()
if(!QDELETED(src))
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
/datum/controller/failsafe/Destroy()
running = FALSE
..()
return QDEL_HINT_HARDDEL_NOW
/datum/controller/failsafe/proc/Loop()
while(running)
lasttick = world.time
if(!Master)
// Replace the missing Master! This should never, ever happen.
new /datum/controller/master()
// Only poke it if overrides are not in effect.
if(processing_interval > 0)
if(Master.processing && Master.iteration)
// Check if processing is done yet.
if(Master.iteration == master_iteration)
switch(defcon)
if(4,5)
--defcon
if(3)
message_admins("<span class='adminnotice'>Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.</span>")
--defcon
if(2)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.</span>")
--defcon
if(1)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...</span>")
--defcon
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
to_chat(GLOB.admins, "<span class='boldannounce'>ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.</span>")
//if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again
//no need to handle that specially when defcon 0 can handle it
if(0) //DEFCON 0! (mc failed to restart)
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
else
defcon = min(defcon + 1,5)
master_iteration = Master.iteration
if (defcon <= 1)
sleep(processing_interval*2)
else
sleep(processing_interval)
else
defcon = 5
sleep(initial(processing_interval))
/datum/controller/failsafe/proc/defcon_pretty()
return defcon
/datum/controller/failsafe/stat_entry()
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))
/**
* Failsafe
*
* Pretty much pokes the MC to make sure it's still alive.
**/
GLOBAL_REAL(Failsafe, /datum/controller/failsafe)
/datum/controller/failsafe // This thing pretty much just keeps poking the master controller
name = "Failsafe"
// The length of time to check on the MC (in deciseconds).
// Set to 0 to disable.
var/processing_interval = 20
// The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC.
var/defcon = 5
//the world.time of the last check, so the mc can restart US if we hang.
// (Real friends look out for *eachother*)
var/lasttick = 0
// Track the MC iteration to make sure its still on track.
var/master_iteration = 0
var/running = TRUE
/datum/controller/failsafe/New()
// Highlander-style: there can only be one! Kill off the old and replace it with the new.
if(Failsafe != src)
if(istype(Failsafe))
qdel(Failsafe)
Failsafe = src
Initialize()
/datum/controller/failsafe/Initialize()
set waitfor = 0
Failsafe.Loop()
if(!QDELETED(src))
qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us
/datum/controller/failsafe/Destroy()
running = FALSE
..()
return QDEL_HINT_HARDDEL_NOW
/datum/controller/failsafe/proc/Loop()
while(running)
lasttick = world.time
if(!Master)
// Replace the missing Master! This should never, ever happen.
new /datum/controller/master()
// Only poke it if overrides are not in effect.
if(processing_interval > 0)
if(Master.processing && Master.iteration)
// Check if processing is done yet.
if(Master.iteration == master_iteration)
switch(defcon)
if(4,5)
--defcon
if(3)
message_admins("<span class='adminnotice'>Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.</span>")
--defcon
if(2)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.</span>")
--defcon
if(1)
to_chat(GLOB.admins, "<span class='boldannounce'>Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...</span>")
--defcon
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
else if(rtn < 0)
log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0")
to_chat(GLOB.admins, "<span class='boldannounce'>ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.</span>")
//if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again
//no need to handle that specially when defcon 0 can handle it
if(0) //DEFCON 0! (mc failed to restart)
var/rtn = Recreate_MC()
if(rtn > 0)
defcon = 4
master_iteration = 0
to_chat(GLOB.admins, "<span class='adminnotice'>MC restarted successfully</span>")
else
defcon = min(defcon + 1,5)
master_iteration = Master.iteration
if (defcon <= 1)
sleep(processing_interval*2)
else
sleep(processing_interval)
else
defcon = 5
sleep(initial(processing_interval))
/datum/controller/failsafe/proc/defcon_pretty()
return defcon
/datum/controller/failsafe/stat_entry()
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
stat("Failsafe Controller:", statclick.update("Defcon: [defcon_pretty()] (Interval: [Failsafe.processing_interval] | Iteration: [Failsafe.master_iteration])"))
+56 -56
View File
@@ -1,56 +1,56 @@
GLOBAL_REAL(GLOB, /datum/controller/global_vars)
/datum/controller/global_vars
name = "Global Variables"
var/static/list/gvars_datum_protected_varlist
var/list/gvars_datum_in_built_vars
var/list/gvars_datum_init_order
/datum/controller/global_vars/New()
if(GLOB)
CRASH("Multiple instances of global variable controller created")
GLOB = src
var/datum/controller/exclude_these = new
gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
QDEL_IN(exclude_these, 0) //signal logging isn't ready
log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
Initialize()
/datum/controller/global_vars/Destroy(force)
// This is done to prevent an exploit where admins can get around protected vars
SHOULD_CALL_PARENT(0)
return QDEL_HINT_IWILLGC
/datum/controller/global_vars/stat_entry()
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
stat("Globals:", statclick.update("Edit"))
/datum/controller/global_vars/vv_edit_var(var_name, var_value)
if(gvars_datum_protected_varlist[var_name])
return FALSE
return ..()
/datum/controller/global_vars/Initialize()
gvars_datum_init_order = list()
gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE)
var/list/global_procs = typesof(/datum/controller/global_vars/proc)
var/expected_len = vars.len - gvars_datum_in_built_vars.len
if(global_procs.len != expected_len)
warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!")
if(global_procs.len)
var/list/expected_global_procs = vars - gvars_datum_in_built_vars
for(var/I in global_procs)
expected_global_procs -= replacetext("[I]", "InitGlobal", "")
log_world("Missing procs: [expected_global_procs.Join(", ")]")
for(var/I in global_procs)
var/start_tick = world.time
call(src, I)()
var/end_tick = world.time
if(end_tick - start_tick)
warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!")
GLOBAL_REAL(GLOB, /datum/controller/global_vars)
/datum/controller/global_vars
name = "Global Variables"
var/static/list/gvars_datum_protected_varlist
var/list/gvars_datum_in_built_vars
var/list/gvars_datum_init_order
/datum/controller/global_vars/New()
if(GLOB)
CRASH("Multiple instances of global variable controller created")
GLOB = src
var/datum/controller/exclude_these = new
gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
QDEL_IN(exclude_these, 0) //signal logging isn't ready
log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
Initialize()
/datum/controller/global_vars/Destroy(force)
// This is done to prevent an exploit where admins can get around protected vars
SHOULD_CALL_PARENT(0)
return QDEL_HINT_IWILLGC
/datum/controller/global_vars/stat_entry()
if(!statclick)
statclick = new/obj/effect/statclick/debug(null, "Initializing...", src)
stat("Globals:", statclick.update("Edit"))
/datum/controller/global_vars/vv_edit_var(var_name, var_value)
if(gvars_datum_protected_varlist[var_name])
return FALSE
return ..()
/datum/controller/global_vars/Initialize()
gvars_datum_init_order = list()
gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE)
var/list/global_procs = typesof(/datum/controller/global_vars/proc)
var/expected_len = vars.len - gvars_datum_in_built_vars.len
if(global_procs.len != expected_len)
warning("Unable to detect all global initialization procs! Expected [expected_len] got [global_procs.len]!")
if(global_procs.len)
var/list/expected_global_procs = vars - gvars_datum_in_built_vars
for(var/I in global_procs)
expected_global_procs -= replacetext("[I]", "InitGlobal", "")
log_world("Missing procs: [expected_global_procs.Join(", ")]")
for(var/I in global_procs)
var/start_tick = world.time
call(src, I)()
var/end_tick = world.time
if(end_tick - start_tick)
warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!")
+156 -156
View File
@@ -1,156 +1,156 @@
#define BAD_INIT_QDEL_BEFORE 1
#define BAD_INIT_DIDNT_INIT 2
#define BAD_INIT_SLEPT 4
#define BAD_INIT_NO_HINT 8
SUBSYSTEM_DEF(atoms)
name = "Atoms"
init_order = INIT_ORDER_ATOMS
flags = SS_NO_FIRE
var/old_initialized
var/list/late_loaders = list()
var/list/BadInitializeCalls = list()
/datum/controller/subsystem/atoms/Initialize(timeofday)
GLOB.fire_overlay.appearance_flags = RESET_COLOR
setupGenetics() //to set the mutations' sequence
initialized = INITIALIZATION_INNEW_MAPLOAD
InitializeAtoms()
return ..()
/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms)
if(initialized == INITIALIZATION_INSSATOMS)
return
initialized = INITIALIZATION_INNEW_MAPLOAD
var/count
var/list/mapload_arg = list(TRUE)
if(atoms)
count = atoms.len
for(var/I in atoms)
var/atom/A = I
if(!(A.flags_1 & INITIALIZED_1))
InitAtom(I, mapload_arg)
CHECK_TICK
else
count = 0
for(var/atom/A in world)
if(!(A.flags_1 & INITIALIZED_1))
InitAtom(A, mapload_arg)
++count
CHECK_TICK
testing("Initialized [count] atoms")
pass(count)
initialized = INITIALIZATION_INNEW_REGULAR
if(late_loaders.len)
for(var/I in late_loaders)
var/atom/A = I
A.LateInitialize()
testing("Late initialized [late_loaders.len] atoms")
late_loaders.Cut()
/// Init this specific atom
/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
var/the_type = A.type
if(QDELING(A))
BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
return TRUE
var/start_tick = world.time
var/result = A.Initialize(arglist(arguments))
if(start_tick != world.time)
BadInitializeCalls[the_type] |= BAD_INIT_SLEPT
var/qdeleted = FALSE
if(result != INITIALIZE_HINT_NORMAL)
switch(result)
if(INITIALIZE_HINT_LATELOAD)
if(arguments[1]) //mapload
late_loaders += A
else
A.LateInitialize()
if(INITIALIZE_HINT_QDEL)
qdel(A)
qdeleted = TRUE
else
BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
if(!A) //possible harddel
qdeleted = TRUE
else if(!(A.flags_1 & INITIALIZED_1))
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
else
SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
return qdeleted || QDELING(A)
/datum/controller/subsystem/atoms/proc/map_loader_begin()
old_initialized = initialized
initialized = INITIALIZATION_INSSATOMS
/datum/controller/subsystem/atoms/proc/map_loader_stop()
initialized = old_initialized
/datum/controller/subsystem/atoms/Recover()
initialized = SSatoms.initialized
if(initialized == INITIALIZATION_INNEW_MAPLOAD)
InitializeAtoms()
old_initialized = SSatoms.old_initialized
BadInitializeCalls = SSatoms.BadInitializeCalls
/datum/controller/subsystem/atoms/proc/setupGenetics()
var/list/mutations = subtypesof(/datum/mutation/human)
shuffle_inplace(mutations)
for(var/A in subtypesof(/datum/generecipe))
var/datum/generecipe/GR = A
GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result)
for(var/i in 1 to LAZYLEN(mutations))
var/path = mutations[i] //byond gets pissy when we do it in one line
var/datum/mutation/human/B = new path ()
B.alias = "Mutation [i]"
GLOB.all_mutations[B.type] = B
GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks)
GLOB.alias_mutations[B.alias] = B.type
if(B.locked)
continue
if(B.quality == POSITIVE)
GLOB.good_mutations |= B
else if(B.quality == NEGATIVE)
GLOB.bad_mutations |= B
else if(B.quality == MINOR_NEGATIVE)
GLOB.not_good_mutations |= B
CHECK_TICK
/datum/controller/subsystem/atoms/proc/InitLog()
. = ""
for(var/path in BadInitializeCalls)
. += "Path : [path] \n"
var/fails = BadInitializeCalls[path]
if(fails & BAD_INIT_DIDNT_INIT)
. += "- Didn't call atom/Initialize()\n"
if(fails & BAD_INIT_NO_HINT)
. += "- Didn't return an Initialize hint\n"
if(fails & BAD_INIT_QDEL_BEFORE)
. += "- Qdel'd in New()\n"
if(fails & BAD_INIT_SLEPT)
. += "- Slept during Initialize()\n"
/datum/controller/subsystem/atoms/Shutdown()
var/initlog = InitLog()
if(initlog)
text2file(initlog, "[GLOB.log_directory]/initialize.log")
#undef BAD_INIT_QDEL_BEFORE
#undef BAD_INIT_DIDNT_INIT
#undef BAD_INIT_SLEPT
#undef BAD_INIT_NO_HINT
#define BAD_INIT_QDEL_BEFORE 1
#define BAD_INIT_DIDNT_INIT 2
#define BAD_INIT_SLEPT 4
#define BAD_INIT_NO_HINT 8
SUBSYSTEM_DEF(atoms)
name = "Atoms"
init_order = INIT_ORDER_ATOMS
flags = SS_NO_FIRE
var/old_initialized
var/list/late_loaders = list()
var/list/BadInitializeCalls = list()
/datum/controller/subsystem/atoms/Initialize(timeofday)
GLOB.fire_overlay.appearance_flags = RESET_COLOR
setupGenetics() //to set the mutations' sequence
initialized = INITIALIZATION_INNEW_MAPLOAD
InitializeAtoms()
return ..()
/datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms)
if(initialized == INITIALIZATION_INSSATOMS)
return
initialized = INITIALIZATION_INNEW_MAPLOAD
var/count
var/list/mapload_arg = list(TRUE)
if(atoms)
count = atoms.len
for(var/I in atoms)
var/atom/A = I
if(!(A.flags_1 & INITIALIZED_1))
InitAtom(I, mapload_arg)
CHECK_TICK
else
count = 0
for(var/atom/A in world)
if(!(A.flags_1 & INITIALIZED_1))
InitAtom(A, mapload_arg)
++count
CHECK_TICK
testing("Initialized [count] atoms")
pass(count)
initialized = INITIALIZATION_INNEW_REGULAR
if(late_loaders.len)
for(var/I in late_loaders)
var/atom/A = I
A.LateInitialize()
testing("Late initialized [late_loaders.len] atoms")
late_loaders.Cut()
/// Init this specific atom
/datum/controller/subsystem/atoms/proc/InitAtom(atom/A, list/arguments)
var/the_type = A.type
if(QDELING(A))
BadInitializeCalls[the_type] |= BAD_INIT_QDEL_BEFORE
return TRUE
var/start_tick = world.time
var/result = A.Initialize(arglist(arguments))
if(start_tick != world.time)
BadInitializeCalls[the_type] |= BAD_INIT_SLEPT
var/qdeleted = FALSE
if(result != INITIALIZE_HINT_NORMAL)
switch(result)
if(INITIALIZE_HINT_LATELOAD)
if(arguments[1]) //mapload
late_loaders += A
else
A.LateInitialize()
if(INITIALIZE_HINT_QDEL)
qdel(A)
qdeleted = TRUE
else
BadInitializeCalls[the_type] |= BAD_INIT_NO_HINT
if(!A) //possible harddel
qdeleted = TRUE
else if(!(A.flags_1 & INITIALIZED_1))
BadInitializeCalls[the_type] |= BAD_INIT_DIDNT_INIT
else
SEND_SIGNAL(A,COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZE)
return qdeleted || QDELING(A)
/datum/controller/subsystem/atoms/proc/map_loader_begin()
old_initialized = initialized
initialized = INITIALIZATION_INSSATOMS
/datum/controller/subsystem/atoms/proc/map_loader_stop()
initialized = old_initialized
/datum/controller/subsystem/atoms/Recover()
initialized = SSatoms.initialized
if(initialized == INITIALIZATION_INNEW_MAPLOAD)
InitializeAtoms()
old_initialized = SSatoms.old_initialized
BadInitializeCalls = SSatoms.BadInitializeCalls
/datum/controller/subsystem/atoms/proc/setupGenetics()
var/list/mutations = subtypesof(/datum/mutation/human)
shuffle_inplace(mutations)
for(var/A in subtypesof(/datum/generecipe))
var/datum/generecipe/GR = A
GLOB.mutation_recipes[initial(GR.required)] = initial(GR.result)
for(var/i in 1 to LAZYLEN(mutations))
var/path = mutations[i] //byond gets pissy when we do it in one line
var/datum/mutation/human/B = new path ()
B.alias = "Mutation [i]"
GLOB.all_mutations[B.type] = B
GLOB.full_sequences[B.type] = generate_gene_sequence(B.blocks)
GLOB.alias_mutations[B.alias] = B.type
if(B.locked)
continue
if(B.quality == POSITIVE)
GLOB.good_mutations |= B
else if(B.quality == NEGATIVE)
GLOB.bad_mutations |= B
else if(B.quality == MINOR_NEGATIVE)
GLOB.not_good_mutations |= B
CHECK_TICK
/datum/controller/subsystem/atoms/proc/InitLog()
. = ""
for(var/path in BadInitializeCalls)
. += "Path : [path] \n"
var/fails = BadInitializeCalls[path]
if(fails & BAD_INIT_DIDNT_INIT)
. += "- Didn't call atom/Initialize()\n"
if(fails & BAD_INIT_NO_HINT)
. += "- Didn't return an Initialize hint\n"
if(fails & BAD_INIT_QDEL_BEFORE)
. += "- Qdel'd in New()\n"
if(fails & BAD_INIT_SLEPT)
. += "- Slept during Initialize()\n"
/datum/controller/subsystem/atoms/Shutdown()
var/initlog = InitLog()
if(initlog)
text2file(initlog, "[GLOB.log_directory]/initialize.log")
#undef BAD_INIT_QDEL_BEFORE
#undef BAD_INIT_DIDNT_INIT
#undef BAD_INIT_SLEPT
#undef BAD_INIT_NO_HINT
+349 -349
View File
@@ -1,349 +1,349 @@
SUBSYSTEM_DEF(blackbox)
name = "Blackbox"
wait = 6000
flags = SS_NO_TICK_CHECK
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
init_order = INIT_ORDER_BLACKBOX
var/list/feedback = list() //list of datum/feedback_variable
var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things
var/triggertime = 0
var/sealed = FALSE //time to stop tracking stats?
var/list/versions = list("antagonists" = 3,
"admin_secrets_fun_used" = 2,
"explosion" = 2,
"time_dilation_current" = 3,
"science_techweb_unlock" = 2,
"round_end_stats" = 2,
"testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this
/datum/controller/subsystem/blackbox/Initialize()
triggertime = world.time
record_feedback("amount", "random_seed", Master.random_seed)
record_feedback("amount", "dm_version", DM_VERSION)
record_feedback("amount", "dm_build", DM_BUILD)
record_feedback("amount", "byond_version", world.byond_version)
record_feedback("amount", "byond_build", world.byond_build)
. = ..()
//poll population
/datum/controller/subsystem/blackbox/fire()
set waitfor = FALSE //for population query
CheckPlayerCount()
if(CONFIG_GET(flag/use_exp_tracking))
if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check
update_exp(10,FALSE)
/datum/controller/subsystem/blackbox/proc/CheckPlayerCount()
set waitfor = FALSE
if(!SSdbcore.Connect())
return
var/playercount = LAZYLEN(GLOB.player_list)
var/admincount = GLOB.admins.len
var/datum/db_query/query_record_playercount = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id)
VALUES (:playercount, :admincount, :time, INET_ATON(:server_ip), :server_port, :round_id)
"}, list(
"playercount" = playercount,
"admincount" = admincount,
"time" = SQLtime(),
"server_ip" = world.internet_address || "0",
"server_port" = "[world.port]",
"round_id" = GLOB.round_id,
))
query_record_playercount.Execute()
qdel(query_record_playercount)
/datum/controller/subsystem/blackbox/Recover()
feedback = SSblackbox.feedback
sealed = SSblackbox.sealed
//no touchie
/datum/controller/subsystem/blackbox/vv_get_var(var_name)
if(var_name == "feedback")
return debug_variable(var_name, deepCopyList(feedback), 0, src)
return ..()
/datum/controller/subsystem/blackbox/vv_edit_var(var_name, var_value)
switch(var_name)
if(NAMEOF(src, feedback))
return FALSE
if(NAMEOF(src, sealed))
if(var_value)
return Seal()
return FALSE
return ..()
//Recorded on subsystem shutdown
/datum/controller/subsystem/blackbox/proc/FinalFeedback()
record_feedback("tally", "ahelp_stats", GLOB.ahelp_tickets.active_tickets.len, "unresolved")
for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list)
if (MS.pda_msgs.len)
record_feedback("tally", "radio_usage", MS.pda_msgs.len, "PDA")
if (MS.rc_msgs.len)
record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console")
for(var/player_key in GLOB.player_details)
var/datum/player_details/PD = GLOB.player_details[player_key]
record_feedback("tally", "client_byond_version", 1, PD.byond_version)
/datum/controller/subsystem/blackbox/Shutdown()
sealed = FALSE
FinalFeedback()
if (!SSdbcore.Connect())
return
var/list/special_columns = list(
"datetime" = "NOW()"
)
var/list/sqlrowlist = list()
for (var/datum/feedback_variable/FV in feedback)
sqlrowlist += list(list(
"round_id" = GLOB.round_id,
"key_name" = FV.key,
"key_type" = FV.key_type,
"version" = versions[FV.key] || 1,
"json" = json_encode(FV.json)
))
if (!length(sqlrowlist))
return
SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns)
/datum/controller/subsystem/blackbox/proc/Seal()
if(sealed)
return FALSE
if(IsAdminAdvancedProcCall())
message_admins("[key_name_admin(usr)] sealed the blackbox!")
log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].")
sealed = TRUE
return TRUE
/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq)
if(sealed)
return
switch(freq)
if(FREQ_COMMON)
record_feedback("tally", "radio_usage", 1, "common")
if(FREQ_SCIENCE)
record_feedback("tally", "radio_usage", 1, "science")
if(FREQ_COMMAND)
record_feedback("tally", "radio_usage", 1, "command")
if(FREQ_MEDICAL)
record_feedback("tally", "radio_usage", 1, "medical")
if(FREQ_ENGINEERING)
record_feedback("tally", "radio_usage", 1, "engineering")
if(FREQ_SECURITY)
record_feedback("tally", "radio_usage", 1, "security")
if(FREQ_SYNDICATE)
record_feedback("tally", "radio_usage", 1, "syndicate")
if(FREQ_SERVICE)
record_feedback("tally", "radio_usage", 1, "service")
if(FREQ_SUPPLY)
record_feedback("tally", "radio_usage", 1, "supply")
if(FREQ_CENTCOM)
record_feedback("tally", "radio_usage", 1, "centcom")
if(FREQ_AI_PRIVATE)
record_feedback("tally", "radio_usage", 1, "ai private")
if(FREQ_CTF_RED)
record_feedback("tally", "radio_usage", 1, "CTF red team")
if(FREQ_CTF_BLUE)
record_feedback("tally", "radio_usage", 1, "CTF blue team")
else
record_feedback("tally", "radio_usage", 1, "other")
/datum/controller/subsystem/blackbox/proc/find_feedback_datum(key, key_type)
for(var/datum/feedback_variable/FV in feedback)
if(FV.key == key)
return FV
var/datum/feedback_variable/FV = new(key, key_type)
feedback += FV
return FV
/*
feedback data can be recorded in 5 formats:
"text"
used for simple single-string records i.e. the current map
further calls to the same key will append saved data unless the overwrite argument is true or it already exists
when encoded calls made with overwrite will lack square brackets
calls: SSblackbox.record_feedback("text", "example", 1, "sample text")
SSblackbox.record_feedback("text", "example", 1, "other text")
json: {"data":["sample text","other text"]}
"amount"
used to record simple counts of data i.e. the number of ahelps received
further calls to the same key will add or subtract (if increment argument is a negative) from the saved amount
calls: SSblackbox.record_feedback("amount", "example", 8)
SSblackbox.record_feedback("amount", "example", 2)
json: {"data":10}
"tally"
used to track the number of occurances of multiple related values i.e. how many times each type of gun is fired
further calls to the same key will:
add or subtract from the saved value of the data key if it already exists
append the key and it's value if it doesn't exist
calls: SSblackbox.record_feedback("tally", "example", 1, "sample data")
SSblackbox.record_feedback("tally", "example", 4, "sample data")
SSblackbox.record_feedback("tally", "example", 2, "other data")
json: {"data":{"sample data":5,"other data":2}}
"nested tally"
used to track the number of occurances of structured semi-relational values i.e. the results of arcade machines
similar to running total, but related values are nested in a multi-dimensional array built
the final element in the data list is used as the tracking key, all prior elements are used for nesting
all data list elements must be strings
further calls to the same key will:
add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position
append the key and it's value if it doesn't exist
calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange"))
SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple"))
SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10}},"vegetable":{"orange":{"carrot":1}}}}
tracking values associated with a number can't merge with a nesting value, trying to do so will append the list
call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10},"orange":3},"vegetable":{"orange":{"carrot":1}}}}
"associative"
used to record text that's associated with a value i.e. coordinates
further calls to the same key will append a new list to existing data
calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4))
SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample"))
json: {"data":{"1":{"text":"example","path":"/obj/item","number":"4"},"2":{"number":"7","text":"example","other text":"sample"}}}
Versioning
If the format of a feedback variable is ever changed, i.e. how many levels of nesting are used or a new type of data is added to it, add it to the versions list
When feedback is being saved if a key is in the versions list the value specified there will be used, otherwise all keys are assumed to be version = 1
versions is an associative list, remember to use the same string in it as defined on a feedback variable, example:
list/versions = list("round_end_stats" = 4,
"admin_toggle" = 2,
"gun_fired" = 2)
*/
/datum/controller/subsystem/blackbox/proc/record_feedback(key_type, key, increment, data, overwrite)
if(sealed || !key_type || !istext(key) || !isnum(increment || !data))
return
var/datum/feedback_variable/FV = find_feedback_datum(key, key_type)
switch(key_type)
if("text")
if(!istext(data))
return
if(!islist(FV.json["data"]))
FV.json["data"] = list()
if(overwrite)
FV.json["data"] = data
else
FV.json["data"] |= data
if("amount")
FV.json["data"] += increment
if("tally")
if(!islist(FV.json["data"]))
FV.json["data"] = list()
FV.json["data"]["[data]"] += increment
if("nested tally")
if(!islist(data))
return
if(!islist(FV.json["data"]))
FV.json["data"] = list()
FV.json["data"] = record_feedback_recurse_list(FV.json["data"], data, increment)
if("associative")
if(!islist(data))
return
if(!islist(FV.json["data"]))
FV.json["data"] = list()
var/pos = length(FV.json["data"]) + 1
FV.json["data"]["[pos]"] = list() //in 512 "pos" can be replaced with "[FV.json["data"].len+1]"
for(var/i in data)
if(islist(data[i]))
FV.json["data"]["[pos]"]["[i]"] = data[i] //and here with "[FV.json["data"].len]"
else
FV.json["data"]["[pos]"]["[i]"] = "[data[i]]"
else
CRASH("Invalid feedback key_type: [key_type]")
/datum/controller/subsystem/blackbox/proc/record_feedback_recurse_list(list/L, list/key_list, increment, depth = 1)
if(depth == key_list.len)
if(L.Find(key_list[depth]))
L["[key_list[depth]]"] += increment
else
var/list/LFI = list(key_list[depth] = increment)
L += LFI
else
if(!L.Find(key_list[depth]))
var/list/LGD = list(key_list[depth] = list())
L += LGD
L["[key_list[depth-1]]"] = .(L["[key_list[depth]]"], key_list, increment, ++depth)
return L
/datum/feedback_variable
var/key
var/key_type
var/list/json = list()
/datum/feedback_variable/New(new_key, new_key_type)
key = new_key
key_type = new_key_type
/datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender)
if(!SSdbcore.Connect())
return
var/datum/db_query/query_log_ahelp = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("ticket")] (ticket, action, message, recipient, sender, server_ip, server_port, round_id, timestamp)
VALUES (:ticket, :action, :message, :recipient, :sender, INET_ATON(:server_ip), :server_port, :round_id, :time)
"}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime()))
query_log_ahelp.Execute()
qdel(query_log_ahelp)
/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L)
set waitfor = FALSE
if(sealed)
return
if(!L || !L.key || !L.mind)
return
if(!L.suiciding && !first_death.len)
first_death["name"] = "[(L.real_name == L.name) ? L.real_name : "[L.real_name] as [L.name]"]"
first_death["role"] = null
if(L.mind.assigned_role)
first_death["role"] = L.mind.assigned_role
first_death["area"] = "[AREACOORD(L)]"
first_death["damage"] = "<font color='#FF5555'>[L.getBruteLoss()]</font>/<font color='orange'>[L.getFireLoss()]</font>/<font color='lightgreen'>[L.getToxLoss()]</font>/<font color='lightblue'>[L.getOxyLoss()]</font>/<font color='pink'>[L.getCloneLoss()]</font>"
first_death["last_words"] = L.last_words
if(!SSdbcore.Connect())
return
var/datum/db_query/query_report_death = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide)
VALUES (:pod, :x_coord, :y_coord, :z_coord, :map, INET_ATON(:internet_address), :port, :round_id, :time, :job, :special, :name, :key, :laname, :lakey, :brute, :fire, :brain, :oxy, :tox, :clone, :stamina, :last_words, :suicide)
"}, list(
"name" = L.real_name,
"key" = L.ckey,
"job" = L.mind.assigned_role,
"special" = L.mind.special_role,
"pod" = get_area_name(L, TRUE),
"laname" = L.lastattacker,
"lakey" = L.lastattackerckey,
"brute" = L.getBruteLoss(),
"fire" = L.getFireLoss(),
"brain" = L.getOrganLoss(ORGAN_SLOT_BRAIN) || BRAIN_DAMAGE_DEATH, //getOrganLoss returns null without a brain but a value is required for this column
"oxy" = L.getOxyLoss(),
"tox" = L.getToxLoss(),
"clone" = L.getCloneLoss(),
"stamina" = L.getStaminaLoss(),
"x_coord" = L.x,
"y_coord" = L.y,
"z_coord" = L.z,
"last_words" = L.last_words,
"suicide" = L.suiciding,
"map" = SSmapping.config.map_name,
"internet_address" = world.internet_address || "0",
"port" = "[world.port]",
"round_id" = GLOB.round_id,
"time" = SQLtime(),
))
if(query_report_death)
query_report_death.Execute(async = TRUE)
qdel(query_report_death)
SUBSYSTEM_DEF(blackbox)
name = "Blackbox"
wait = 6000
flags = SS_NO_TICK_CHECK
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
init_order = INIT_ORDER_BLACKBOX
var/list/feedback = list() //list of datum/feedback_variable
var/list/first_death = list() //the first death of this round, assoc. vars keep track of different things
var/triggertime = 0
var/sealed = FALSE //time to stop tracking stats?
var/list/versions = list("antagonists" = 3,
"admin_secrets_fun_used" = 2,
"explosion" = 2,
"time_dilation_current" = 3,
"science_techweb_unlock" = 2,
"round_end_stats" = 2,
"testmerged_prs" = 2) //associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this
/datum/controller/subsystem/blackbox/Initialize()
triggertime = world.time
record_feedback("amount", "random_seed", Master.random_seed)
record_feedback("amount", "dm_version", DM_VERSION)
record_feedback("amount", "dm_build", DM_BUILD)
record_feedback("amount", "byond_version", world.byond_version)
record_feedback("amount", "byond_build", world.byond_build)
. = ..()
//poll population
/datum/controller/subsystem/blackbox/fire()
set waitfor = FALSE //for population query
CheckPlayerCount()
if(CONFIG_GET(flag/use_exp_tracking))
if((triggertime < 0) || (world.time > (triggertime +3000))) //subsystem fires once at roundstart then once every 10 minutes. a 5 min check skips the first fire. The <0 is midnight rollover check
update_exp(10,FALSE)
/datum/controller/subsystem/blackbox/proc/CheckPlayerCount()
set waitfor = FALSE
if(!SSdbcore.Connect())
return
var/playercount = LAZYLEN(GLOB.player_list)
var/admincount = GLOB.admins.len
var/datum/db_query/query_record_playercount = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id)
VALUES (:playercount, :admincount, :time, INET_ATON(:server_ip), :server_port, :round_id)
"}, list(
"playercount" = playercount,
"admincount" = admincount,
"time" = SQLtime(),
"server_ip" = world.internet_address || "0",
"server_port" = "[world.port]",
"round_id" = GLOB.round_id,
))
query_record_playercount.Execute()
qdel(query_record_playercount)
/datum/controller/subsystem/blackbox/Recover()
feedback = SSblackbox.feedback
sealed = SSblackbox.sealed
//no touchie
/datum/controller/subsystem/blackbox/vv_get_var(var_name)
if(var_name == "feedback")
return debug_variable(var_name, deepCopyList(feedback), 0, src)
return ..()
/datum/controller/subsystem/blackbox/vv_edit_var(var_name, var_value)
switch(var_name)
if(NAMEOF(src, feedback))
return FALSE
if(NAMEOF(src, sealed))
if(var_value)
return Seal()
return FALSE
return ..()
//Recorded on subsystem shutdown
/datum/controller/subsystem/blackbox/proc/FinalFeedback()
record_feedback("tally", "ahelp_stats", GLOB.ahelp_tickets.active_tickets.len, "unresolved")
for (var/obj/machinery/telecomms/message_server/MS in GLOB.telecomms_list)
if (MS.pda_msgs.len)
record_feedback("tally", "radio_usage", MS.pda_msgs.len, "PDA")
if (MS.rc_msgs.len)
record_feedback("tally", "radio_usage", MS.rc_msgs.len, "request console")
for(var/player_key in GLOB.player_details)
var/datum/player_details/PD = GLOB.player_details[player_key]
record_feedback("tally", "client_byond_version", 1, PD.byond_version)
/datum/controller/subsystem/blackbox/Shutdown()
sealed = FALSE
FinalFeedback()
if (!SSdbcore.Connect())
return
var/list/special_columns = list(
"datetime" = "NOW()"
)
var/list/sqlrowlist = list()
for (var/datum/feedback_variable/FV in feedback)
sqlrowlist += list(list(
"round_id" = GLOB.round_id,
"key_name" = FV.key,
"key_type" = FV.key_type,
"version" = versions[FV.key] || 1,
"json" = json_encode(FV.json)
))
if (!length(sqlrowlist))
return
SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE, special_columns = special_columns)
/datum/controller/subsystem/blackbox/proc/Seal()
if(sealed)
return FALSE
if(IsAdminAdvancedProcCall())
message_admins("[key_name_admin(usr)] sealed the blackbox!")
log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].")
sealed = TRUE
return TRUE
/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq)
if(sealed)
return
switch(freq)
if(FREQ_COMMON)
record_feedback("tally", "radio_usage", 1, "common")
if(FREQ_SCIENCE)
record_feedback("tally", "radio_usage", 1, "science")
if(FREQ_COMMAND)
record_feedback("tally", "radio_usage", 1, "command")
if(FREQ_MEDICAL)
record_feedback("tally", "radio_usage", 1, "medical")
if(FREQ_ENGINEERING)
record_feedback("tally", "radio_usage", 1, "engineering")
if(FREQ_SECURITY)
record_feedback("tally", "radio_usage", 1, "security")
if(FREQ_SYNDICATE)
record_feedback("tally", "radio_usage", 1, "syndicate")
if(FREQ_SERVICE)
record_feedback("tally", "radio_usage", 1, "service")
if(FREQ_SUPPLY)
record_feedback("tally", "radio_usage", 1, "supply")
if(FREQ_CENTCOM)
record_feedback("tally", "radio_usage", 1, "centcom")
if(FREQ_AI_PRIVATE)
record_feedback("tally", "radio_usage", 1, "ai private")
if(FREQ_CTF_RED)
record_feedback("tally", "radio_usage", 1, "CTF red team")
if(FREQ_CTF_BLUE)
record_feedback("tally", "radio_usage", 1, "CTF blue team")
else
record_feedback("tally", "radio_usage", 1, "other")
/datum/controller/subsystem/blackbox/proc/find_feedback_datum(key, key_type)
for(var/datum/feedback_variable/FV in feedback)
if(FV.key == key)
return FV
var/datum/feedback_variable/FV = new(key, key_type)
feedback += FV
return FV
/*
feedback data can be recorded in 5 formats:
"text"
used for simple single-string records i.e. the current map
further calls to the same key will append saved data unless the overwrite argument is true or it already exists
when encoded calls made with overwrite will lack square brackets
calls: SSblackbox.record_feedback("text", "example", 1, "sample text")
SSblackbox.record_feedback("text", "example", 1, "other text")
json: {"data":["sample text","other text"]}
"amount"
used to record simple counts of data i.e. the number of ahelps received
further calls to the same key will add or subtract (if increment argument is a negative) from the saved amount
calls: SSblackbox.record_feedback("amount", "example", 8)
SSblackbox.record_feedback("amount", "example", 2)
json: {"data":10}
"tally"
used to track the number of occurances of multiple related values i.e. how many times each type of gun is fired
further calls to the same key will:
add or subtract from the saved value of the data key if it already exists
append the key and it's value if it doesn't exist
calls: SSblackbox.record_feedback("tally", "example", 1, "sample data")
SSblackbox.record_feedback("tally", "example", 4, "sample data")
SSblackbox.record_feedback("tally", "example", 2, "other data")
json: {"data":{"sample data":5,"other data":2}}
"nested tally"
used to track the number of occurances of structured semi-relational values i.e. the results of arcade machines
similar to running total, but related values are nested in a multi-dimensional array built
the final element in the data list is used as the tracking key, all prior elements are used for nesting
all data list elements must be strings
further calls to the same key will:
add or subtract from the saved value of the data key if it already exists in the same multi-dimensional position
append the key and it's value if it doesn't exist
calls: SSblackbox.record_feedback("nested tally", "example", 1, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 2, list("fruit", "orange", "orange"))
SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange", "apricot"))
SSblackbox.record_feedback("nested tally", "example", 10, list("fruit", "red", "apple"))
SSblackbox.record_feedback("nested tally", "example", 1, list("vegetable", "orange", "carrot"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10}},"vegetable":{"orange":{"carrot":1}}}}
tracking values associated with a number can't merge with a nesting value, trying to do so will append the list
call: SSblackbox.record_feedback("nested tally", "example", 3, list("fruit", "orange"))
json: {"data":{"fruit":{"orange":{"apricot":4,"orange":2},"red":{"apple":10},"orange":3},"vegetable":{"orange":{"carrot":1}}}}
"associative"
used to record text that's associated with a value i.e. coordinates
further calls to the same key will append a new list to existing data
calls: SSblackbox.record_feedback("associative", "example", 1, list("text" = "example", "path" = /obj/item, "number" = 4))
SSblackbox.record_feedback("associative", "example", 1, list("number" = 7, "text" = "example", "other text" = "sample"))
json: {"data":{"1":{"text":"example","path":"/obj/item","number":"4"},"2":{"number":"7","text":"example","other text":"sample"}}}
Versioning
If the format of a feedback variable is ever changed, i.e. how many levels of nesting are used or a new type of data is added to it, add it to the versions list
When feedback is being saved if a key is in the versions list the value specified there will be used, otherwise all keys are assumed to be version = 1
versions is an associative list, remember to use the same string in it as defined on a feedback variable, example:
list/versions = list("round_end_stats" = 4,
"admin_toggle" = 2,
"gun_fired" = 2)
*/
/datum/controller/subsystem/blackbox/proc/record_feedback(key_type, key, increment, data, overwrite)
if(sealed || !key_type || !istext(key) || !isnum(increment || !data))
return
var/datum/feedback_variable/FV = find_feedback_datum(key, key_type)
switch(key_type)
if("text")
if(!istext(data))
return
if(!islist(FV.json["data"]))
FV.json["data"] = list()
if(overwrite)
FV.json["data"] = data
else
FV.json["data"] |= data
if("amount")
FV.json["data"] += increment
if("tally")
if(!islist(FV.json["data"]))
FV.json["data"] = list()
FV.json["data"]["[data]"] += increment
if("nested tally")
if(!islist(data))
return
if(!islist(FV.json["data"]))
FV.json["data"] = list()
FV.json["data"] = record_feedback_recurse_list(FV.json["data"], data, increment)
if("associative")
if(!islist(data))
return
if(!islist(FV.json["data"]))
FV.json["data"] = list()
var/pos = length(FV.json["data"]) + 1
FV.json["data"]["[pos]"] = list() //in 512 "pos" can be replaced with "[FV.json["data"].len+1]"
for(var/i in data)
if(islist(data[i]))
FV.json["data"]["[pos]"]["[i]"] = data[i] //and here with "[FV.json["data"].len]"
else
FV.json["data"]["[pos]"]["[i]"] = "[data[i]]"
else
CRASH("Invalid feedback key_type: [key_type]")
/datum/controller/subsystem/blackbox/proc/record_feedback_recurse_list(list/L, list/key_list, increment, depth = 1)
if(depth == key_list.len)
if(L.Find(key_list[depth]))
L["[key_list[depth]]"] += increment
else
var/list/LFI = list(key_list[depth] = increment)
L += LFI
else
if(!L.Find(key_list[depth]))
var/list/LGD = list(key_list[depth] = list())
L += LGD
L["[key_list[depth-1]]"] = .(L["[key_list[depth]]"], key_list, increment, ++depth)
return L
/datum/feedback_variable
var/key
var/key_type
var/list/json = list()
/datum/feedback_variable/New(new_key, new_key_type)
key = new_key
key_type = new_key_type
/datum/controller/subsystem/blackbox/proc/LogAhelp(ticket, action, message, recipient, sender)
if(!SSdbcore.Connect())
return
var/datum/db_query/query_log_ahelp = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("ticket")] (ticket, action, message, recipient, sender, server_ip, server_port, round_id, timestamp)
VALUES (:ticket, :action, :message, :recipient, :sender, INET_ATON(:server_ip), :server_port, :round_id, :time)
"}, list("ticket" = ticket, "action" = action, "message" = message, "recipient" = recipient, "sender" = sender, "server_ip" = world.internet_address || "0", "server_port" = world.port, "round_id" = GLOB.round_id, "time" = SQLtime()))
query_log_ahelp.Execute()
qdel(query_log_ahelp)
/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L)
set waitfor = FALSE
if(sealed)
return
if(!L || !L.key || !L.mind)
return
if(!L.suiciding && !first_death.len)
first_death["name"] = "[(L.real_name == L.name) ? L.real_name : "[L.real_name] as [L.name]"]"
first_death["role"] = null
if(L.mind.assigned_role)
first_death["role"] = L.mind.assigned_role
first_death["area"] = "[AREACOORD(L)]"
first_death["damage"] = "<font color='#FF5555'>[L.getBruteLoss()]</font>/<font color='orange'>[L.getFireLoss()]</font>/<font color='lightgreen'>[L.getToxLoss()]</font>/<font color='lightblue'>[L.getOxyLoss()]</font>/<font color='pink'>[L.getCloneLoss()]</font>"
first_death["last_words"] = L.last_words
if(!SSdbcore.Connect())
return
var/datum/db_query/query_report_death = SSdbcore.NewQuery({"
INSERT INTO [format_table_name("death")] (pod, x_coord, y_coord, z_coord, mapname, server_ip, server_port, round_id, tod, job, special, name, byondkey, laname, lakey, bruteloss, fireloss, brainloss, oxyloss, toxloss, cloneloss, staminaloss, last_words, suicide)
VALUES (:pod, :x_coord, :y_coord, :z_coord, :map, INET_ATON(:internet_address), :port, :round_id, :time, :job, :special, :name, :key, :laname, :lakey, :brute, :fire, :brain, :oxy, :tox, :clone, :stamina, :last_words, :suicide)
"}, list(
"name" = L.real_name,
"key" = L.ckey,
"job" = L.mind.assigned_role,
"special" = L.mind.special_role,
"pod" = get_area_name(L, TRUE),
"laname" = L.lastattacker,
"lakey" = L.lastattackerckey,
"brute" = L.getBruteLoss(),
"fire" = L.getFireLoss(),
"brain" = L.getOrganLoss(ORGAN_SLOT_BRAIN) || BRAIN_DAMAGE_DEATH, //getOrganLoss returns null without a brain but a value is required for this column
"oxy" = L.getOxyLoss(),
"tox" = L.getToxLoss(),
"clone" = L.getCloneLoss(),
"stamina" = L.getStaminaLoss(),
"x_coord" = L.x,
"y_coord" = L.y,
"z_coord" = L.z,
"last_words" = L.last_words,
"suicide" = L.suiciding,
"map" = SSmapping.config.map_name,
"internet_address" = world.internet_address || "0",
"port" = "[world.port]",
"round_id" = GLOB.round_id,
"time" = SQLtime(),
))
if(query_report_death)
query_report_death.Execute(async = TRUE)
qdel(query_report_death)
+396 -396
View File
@@ -1,396 +1,396 @@
SUBSYSTEM_DEF(dbcore)
name = "Database"
flags = SS_BACKGROUND
wait = 1 MINUTES
init_order = INIT_ORDER_DBCORE
var/failed_connection_timeout = 0
var/schema_mismatch = 0
var/db_minor = 0
var/db_major = 0
var/failed_connections = 0
var/last_error
var/list/active_queries = list()
var/connection // Arbitrary handle returned from rust_g.
/datum/controller/subsystem/dbcore/Initialize()
//We send warnings to the admins during subsystem init, as the clients will be New'd and messages
//will queue properly with goonchat
switch(schema_mismatch)
if(1)
message_admins("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors")
if(2)
message_admins("Could not get schema version from database")
return ..()
/datum/controller/subsystem/dbcore/fire()
for(var/I in active_queries)
var/datum/db_query/Q = I
if(world.time - Q.last_activity_time > (5 MINUTES))
message_admins("Found undeleted query, please check the server logs and notify coders.")
log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]")
qdel(Q)
if(MC_TICK_CHECK)
return
/datum/controller/subsystem/dbcore/Recover()
connection = SSdbcore.connection
/datum/controller/subsystem/dbcore/Shutdown()
//This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem
if(SSdbcore.Connect())
var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id",
list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id)
)
query_round_shutdown.Execute()
qdel(query_round_shutdown)
if(IsConnected())
Disconnect()
//nu
/datum/controller/subsystem/dbcore/can_vv_get(var_name)
return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..()
/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
if(var_name == NAMEOF(src, connection))
return FALSE
return ..()
/datum/controller/subsystem/dbcore/proc/Connect()
if(IsConnected())
return TRUE
if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter
failed_connections = 0
if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds.
failed_connection_timeout = world.time + 50
return FALSE
if(!CONFIG_GET(flag/sql_enabled))
return FALSE
var/user = CONFIG_GET(string/feedback_login)
var/pass = CONFIG_GET(string/feedback_password)
var/db = CONFIG_GET(string/feedback_database)
var/address = CONFIG_GET(string/address)
var/port = CONFIG_GET(number/port)
var/timeout = max(CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout))
var/thread_limit = CONFIG_GET(number/bsql_thread_limit)
var/result = json_decode(rustg_sql_connect_pool(json_encode(list(
"host" = address,
"port" = port,
"user" = user,
"pass" = pass,
"db_name" = db,
"max_threads" = 5,
"read_timeout" = timeout,
"write_timeout" = timeout,
"max_threads" = thread_limit,
))))
. = (result["status"] == "ok")
if (.)
connection = result["handle"]
else
connection = null
last_error = result["data"]
log_sql("Connect() failed | [last_error]")
++failed_connections
/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
if(CONFIG_GET(flag/sql_enabled))
if(Connect())
log_world("Database connection established.")
var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1")
query_db_version.Execute()
if(query_db_version.NextRow())
db_major = text2num(query_db_version.item[1])
db_minor = text2num(query_db_version.item[2])
if(db_major != DB_MAJOR_VERSION || db_minor != DB_MINOR_VERSION)
schema_mismatch = 1 // flag admin message about mismatch
log_sql("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors")
else
schema_mismatch = 2 //flag admin message about no schema version
log_sql("Could not get schema version from database")
qdel(query_db_version)
else
log_sql("Your server failed to establish a connection with the database.")
else
log_sql("Database is not enabled in configuration.")
/datum/controller/subsystem/dbcore/proc/SetRoundID()
if(!Connect())
return
var/datum/db_query/query_round_initialize = SSdbcore.NewQuery(
"INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)",
list("internet_address" = world.internet_address || "0", "port" = "[world.port]")
)
query_round_initialize.Execute(async = FALSE)
GLOB.round_id = "[query_round_initialize.last_insert_id]"
qdel(query_round_initialize)
/datum/controller/subsystem/dbcore/proc/SetRoundStart()
if(!Connect())
return
var/datum/db_query/query_round_start = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id",
list("round_id" = GLOB.round_id)
)
query_round_start.Execute()
qdel(query_round_start)
/datum/controller/subsystem/dbcore/proc/SetRoundEnd()
if(!Connect())
return
var/datum/db_query/query_round_end = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id",
list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id)
)
query_round_end.Execute()
qdel(query_round_end)
/datum/controller/subsystem/dbcore/proc/Disconnect()
failed_connections = 0
if (connection)
rustg_sql_disconnect_pool(connection)
connection = null
/datum/controller/subsystem/dbcore/proc/IsConnected()
if (!CONFIG_GET(flag/sql_enabled))
return FALSE
if (!connection)
return FALSE
return json_decode(rustg_sql_connected(connection))["status"] == "online"
/datum/controller/subsystem/dbcore/proc/ErrorMsg()
if(!CONFIG_GET(flag/sql_enabled))
return "Database disabled by configuration"
return last_error
/datum/controller/subsystem/dbcore/proc/ReportError(error)
last_error = error
/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments)
if(IsAdminAdvancedProcCall())
log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked")
message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked")
return FALSE
return new /datum/db_query(connection, sql_query, arguments)
/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE)
if (!islist(querys))
if (!istype(querys, /datum/db_query))
CRASH("Invalid query passed to QuerySelect: [querys]")
querys = list(querys)
for (var/thing in querys)
var/datum/db_query/query = thing
if (warn)
INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute)
else
INVOKE_ASYNC(query, /datum/db_query.proc/Execute)
for (var/thing in querys)
var/datum/db_query/query = thing
UNTIL(!query.in_progress)
if (qdel)
qdel(query)
/*
Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query.
Rows missing columns present in other rows will resolve to SQL NULL
You are expected to do your own escaping of the data, and expected to provide your own quotes for strings.
The duplicate_key arg can be true to automatically generate this part of the query
or set to a string that is appended to the end of the query
Ignore_errors instructes mysql to continue inserting rows if some of them have errors.
the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables,
It was included because it is still supported in mariadb.
It does not work with duplicate_key and the mysql server ignores it in those cases
*/
/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null)
if (!table || !rows || !istype(rows))
return
// Prepare column list
var/list/columns = list()
var/list/has_question_mark = list()
for (var/list/row in rows)
for (var/column in row)
columns[column] = "?"
has_question_mark[column] = TRUE
for (var/column in special_columns)
columns[column] = special_columns[column]
has_question_mark[column] = findtext(special_columns[column], "?")
// Prepare SQL query full of placeholders
var/list/query_parts = list("INSERT")
if (delayed)
query_parts += " DELAYED"
if (ignore_errors)
query_parts += " IGNORE"
query_parts += " INTO "
query_parts += table
query_parts += "\n([columns.Join(", ")])\nVALUES"
var/list/arguments = list()
var/has_row = FALSE
for (var/list/row in rows)
if (has_row)
query_parts += ","
query_parts += "\n ("
var/has_col = FALSE
for (var/column in columns)
if (has_col)
query_parts += ", "
if (has_question_mark[column])
var/name = "p[arguments.len]"
query_parts += replacetext(columns[column], "?", ":[name]")
arguments[name] = row[column]
else
query_parts += columns[column]
has_col = TRUE
query_parts += ")"
has_row = TRUE
if (duplicate_key == TRUE)
var/list/column_list = list()
for (var/column in columns)
column_list += "[column] = VALUES([column])"
query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]"
else if (duplicate_key != FALSE)
query_parts += duplicate_key
var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments)
if (warn)
. = Query.warn_execute(async)
else
. = Query.Execute(async)
qdel(Query)
/datum/db_query
// Inputs
var/connection
var/sql
var/arguments
// Status information
var/in_progress
var/last_error
var/last_activity
var/last_activity_time
// Output
var/list/list/rows
var/next_row_to_take = 1
var/affected
var/last_insert_id
var/list/item //list of data values populated by NextRow()
/datum/db_query/New(connection, sql, arguments)
SSdbcore.active_queries[src] = TRUE
Activity("Created")
item = list()
src.connection = connection
src.sql = sql
src.arguments = arguments
/datum/db_query/Destroy()
Close()
SSdbcore.active_queries -= src
return ..()
/datum/db_query/CanProcCall(proc_name)
//fuck off kevinz
return FALSE
/datum/db_query/proc/Activity(activity)
last_activity = activity
last_activity_time = world.time
/datum/db_query/proc/warn_execute(async = TRUE)
. = Execute(async)
if(!.)
to_chat(usr, "<span class='danger'>A SQL error occurred during this operation, check the server logs.</span>")
/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE)
Activity("Execute")
if(in_progress)
CRASH("Attempted to start a new query while waiting on the old one")
if(!SSdbcore.IsConnected())
last_error = "No connection!"
return FALSE
var/start_time
if(!async)
start_time = REALTIMEOFDAY
Close()
. = run_query(async)
var/timed_out = !. && findtext(last_error, "Operation timed out")
if(!. && log_error)
log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]")
if(!async && timed_out)
log_query_debug("Query execution started at [start_time]")
log_query_debug("Query execution ended at [REALTIMEOFDAY]")
log_query_debug("Slow query timeout detected.")
log_query_debug("Query used: [sql]")
slow_query_check()
/datum/db_query/proc/run_query(async)
var/job_result_str
if (async)
var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments))
in_progress = TRUE
UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
in_progress = FALSE
if (job_result_str == RUSTG_JOB_ERROR)
last_error = job_result_str
return FALSE
else
job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments))
var/result = json_decode(job_result_str)
switch (result["status"])
if ("ok")
rows = result["rows"]
affected = result["affected"]
last_insert_id = result["last_insert_id"]
return TRUE
if ("err")
last_error = result["data"]
return FALSE
if ("offline")
last_error = "offline"
return FALSE
/datum/db_query/proc/slow_query_check()
message_admins("HEY! A database query timed out. Did the server just hang? <a href='?_src_=holder;[HrefToken()];slowquery=yes'>\[YES\]</a>|<a href='?_src_=holder;[HrefToken()];slowquery=no'>\[NO\]</a>")
/datum/db_query/proc/NextRow(async = TRUE)
Activity("NextRow")
if (rows && next_row_to_take <= rows.len)
item = rows[next_row_to_take]
next_row_to_take++
return !!item
else
return FALSE
/datum/db_query/proc/ErrorMsg()
return last_error
/datum/db_query/proc/Close()
rows = null
item = null
SUBSYSTEM_DEF(dbcore)
name = "Database"
flags = SS_BACKGROUND
wait = 1 MINUTES
init_order = INIT_ORDER_DBCORE
var/failed_connection_timeout = 0
var/schema_mismatch = 0
var/db_minor = 0
var/db_major = 0
var/failed_connections = 0
var/last_error
var/list/active_queries = list()
var/connection // Arbitrary handle returned from rust_g.
/datum/controller/subsystem/dbcore/Initialize()
//We send warnings to the admins during subsystem init, as the clients will be New'd and messages
//will queue properly with goonchat
switch(schema_mismatch)
if(1)
message_admins("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors")
if(2)
message_admins("Could not get schema version from database")
return ..()
/datum/controller/subsystem/dbcore/fire()
for(var/I in active_queries)
var/datum/db_query/Q = I
if(world.time - Q.last_activity_time > (5 MINUTES))
message_admins("Found undeleted query, please check the server logs and notify coders.")
log_sql("Undeleted query: \"[Q.sql]\" LA: [Q.last_activity] LAT: [Q.last_activity_time]")
qdel(Q)
if(MC_TICK_CHECK)
return
/datum/controller/subsystem/dbcore/Recover()
connection = SSdbcore.connection
/datum/controller/subsystem/dbcore/Shutdown()
//This is as close as we can get to the true round end before Disconnect() without changing where it's called, defeating the reason this is a subsystem
if(SSdbcore.Connect())
var/datum/db_query/query_round_shutdown = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = :end_state WHERE id = :round_id",
list("end_state" = SSticker.end_state, "round_id" = GLOB.round_id)
)
query_round_shutdown.Execute()
qdel(query_round_shutdown)
if(IsConnected())
Disconnect()
//nu
/datum/controller/subsystem/dbcore/can_vv_get(var_name)
return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && ..()
/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
if(var_name == NAMEOF(src, connection))
return FALSE
return ..()
/datum/controller/subsystem/dbcore/proc/Connect()
if(IsConnected())
return TRUE
if(failed_connection_timeout <= world.time) //it's been more than 5 seconds since we failed to connect, reset the counter
failed_connections = 0
if(failed_connections > 5) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect for 5 seconds.
failed_connection_timeout = world.time + 50
return FALSE
if(!CONFIG_GET(flag/sql_enabled))
return FALSE
var/user = CONFIG_GET(string/feedback_login)
var/pass = CONFIG_GET(string/feedback_password)
var/db = CONFIG_GET(string/feedback_database)
var/address = CONFIG_GET(string/address)
var/port = CONFIG_GET(number/port)
var/timeout = max(CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout))
var/thread_limit = CONFIG_GET(number/bsql_thread_limit)
var/result = json_decode(rustg_sql_connect_pool(json_encode(list(
"host" = address,
"port" = port,
"user" = user,
"pass" = pass,
"db_name" = db,
"max_threads" = 5,
"read_timeout" = timeout,
"write_timeout" = timeout,
"max_threads" = thread_limit,
))))
. = (result["status"] == "ok")
if (.)
connection = result["handle"]
else
connection = null
last_error = result["data"]
log_sql("Connect() failed | [last_error]")
++failed_connections
/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
if(CONFIG_GET(flag/sql_enabled))
if(Connect())
log_world("Database connection established.")
var/datum/db_query/query_db_version = NewQuery("SELECT major, minor FROM [format_table_name("schema_revision")] ORDER BY date DESC LIMIT 1")
query_db_version.Execute()
if(query_db_version.NextRow())
db_major = text2num(query_db_version.item[1])
db_minor = text2num(query_db_version.item[2])
if(db_major != DB_MAJOR_VERSION || db_minor != DB_MINOR_VERSION)
schema_mismatch = 1 // flag admin message about mismatch
log_sql("Database schema ([db_major].[db_minor]) doesn't match the latest schema version ([DB_MAJOR_VERSION].[DB_MINOR_VERSION]), this may lead to undefined behaviour or errors")
else
schema_mismatch = 2 //flag admin message about no schema version
log_sql("Could not get schema version from database")
qdel(query_db_version)
else
log_sql("Your server failed to establish a connection with the database.")
else
log_sql("Database is not enabled in configuration.")
/datum/controller/subsystem/dbcore/proc/SetRoundID()
if(!Connect())
return
var/datum/db_query/query_round_initialize = SSdbcore.NewQuery(
"INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(:internet_address), :port)",
list("internet_address" = world.internet_address || "0", "port" = "[world.port]")
)
query_round_initialize.Execute(async = FALSE)
GLOB.round_id = "[query_round_initialize.last_insert_id]"
qdel(query_round_initialize)
/datum/controller/subsystem/dbcore/proc/SetRoundStart()
if(!Connect())
return
var/datum/db_query/query_round_start = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = :round_id",
list("round_id" = GLOB.round_id)
)
query_round_start.Execute()
qdel(query_round_start)
/datum/controller/subsystem/dbcore/proc/SetRoundEnd()
if(!Connect())
return
var/datum/db_query/query_round_end = SSdbcore.NewQuery(
"UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = :game_mode_result, station_name = :station_name WHERE id = :round_id",
list("game_mode_result" = SSticker.mode_result, "station_name" = station_name(), "round_id" = GLOB.round_id)
)
query_round_end.Execute()
qdel(query_round_end)
/datum/controller/subsystem/dbcore/proc/Disconnect()
failed_connections = 0
if (connection)
rustg_sql_disconnect_pool(connection)
connection = null
/datum/controller/subsystem/dbcore/proc/IsConnected()
if (!CONFIG_GET(flag/sql_enabled))
return FALSE
if (!connection)
return FALSE
return json_decode(rustg_sql_connected(connection))["status"] == "online"
/datum/controller/subsystem/dbcore/proc/ErrorMsg()
if(!CONFIG_GET(flag/sql_enabled))
return "Database disabled by configuration"
return last_error
/datum/controller/subsystem/dbcore/proc/ReportError(error)
last_error = error
/datum/controller/subsystem/dbcore/proc/NewQuery(sql_query, arguments)
if(IsAdminAdvancedProcCall())
log_admin_private("ERROR: Advanced admin proc call led to sql query: [sql_query]. Query has been blocked")
message_admins("ERROR: Advanced admin proc call led to sql query. Query has been blocked")
return FALSE
return new /datum/db_query(connection, sql_query, arguments)
/datum/controller/subsystem/dbcore/proc/QuerySelect(list/querys, warn = FALSE, qdel = FALSE)
if (!islist(querys))
if (!istype(querys, /datum/db_query))
CRASH("Invalid query passed to QuerySelect: [querys]")
querys = list(querys)
for (var/thing in querys)
var/datum/db_query/query = thing
if (warn)
INVOKE_ASYNC(query, /datum/db_query.proc/warn_execute)
else
INVOKE_ASYNC(query, /datum/db_query.proc/Execute)
for (var/thing in querys)
var/datum/db_query/query = thing
UNTIL(!query.in_progress)
if (qdel)
qdel(query)
/*
Takes a list of rows (each row being an associated list of column => value) and inserts them via a single mass query.
Rows missing columns present in other rows will resolve to SQL NULL
You are expected to do your own escaping of the data, and expected to provide your own quotes for strings.
The duplicate_key arg can be true to automatically generate this part of the query
or set to a string that is appended to the end of the query
Ignore_errors instructes mysql to continue inserting rows if some of them have errors.
the erroneous row(s) aren't inserted and there isn't really any way to know why or why errored
Delayed insert mode was removed in mysql 7 and only works with MyISAM type tables,
It was included because it is still supported in mariadb.
It does not work with duplicate_key and the mysql server ignores it in those cases
*/
/datum/controller/subsystem/dbcore/proc/MassInsert(table, list/rows, duplicate_key = FALSE, ignore_errors = FALSE, delayed = FALSE, warn = FALSE, async = TRUE, special_columns = null)
if (!table || !rows || !istype(rows))
return
// Prepare column list
var/list/columns = list()
var/list/has_question_mark = list()
for (var/list/row in rows)
for (var/column in row)
columns[column] = "?"
has_question_mark[column] = TRUE
for (var/column in special_columns)
columns[column] = special_columns[column]
has_question_mark[column] = findtext(special_columns[column], "?")
// Prepare SQL query full of placeholders
var/list/query_parts = list("INSERT")
if (delayed)
query_parts += " DELAYED"
if (ignore_errors)
query_parts += " IGNORE"
query_parts += " INTO "
query_parts += table
query_parts += "\n([columns.Join(", ")])\nVALUES"
var/list/arguments = list()
var/has_row = FALSE
for (var/list/row in rows)
if (has_row)
query_parts += ","
query_parts += "\n ("
var/has_col = FALSE
for (var/column in columns)
if (has_col)
query_parts += ", "
if (has_question_mark[column])
var/name = "p[arguments.len]"
query_parts += replacetext(columns[column], "?", ":[name]")
arguments[name] = row[column]
else
query_parts += columns[column]
has_col = TRUE
query_parts += ")"
has_row = TRUE
if (duplicate_key == TRUE)
var/list/column_list = list()
for (var/column in columns)
column_list += "[column] = VALUES([column])"
query_parts += "\nON DUPLICATE KEY UPDATE [column_list.Join(", ")]"
else if (duplicate_key != FALSE)
query_parts += duplicate_key
var/datum/db_query/Query = NewQuery(query_parts.Join(), arguments)
if (warn)
. = Query.warn_execute(async)
else
. = Query.Execute(async)
qdel(Query)
/datum/db_query
// Inputs
var/connection
var/sql
var/arguments
// Status information
var/in_progress
var/last_error
var/last_activity
var/last_activity_time
// Output
var/list/list/rows
var/next_row_to_take = 1
var/affected
var/last_insert_id
var/list/item //list of data values populated by NextRow()
/datum/db_query/New(connection, sql, arguments)
SSdbcore.active_queries[src] = TRUE
Activity("Created")
item = list()
src.connection = connection
src.sql = sql
src.arguments = arguments
/datum/db_query/Destroy()
Close()
SSdbcore.active_queries -= src
return ..()
/datum/db_query/CanProcCall(proc_name)
//fuck off kevinz
return FALSE
/datum/db_query/proc/Activity(activity)
last_activity = activity
last_activity_time = world.time
/datum/db_query/proc/warn_execute(async = TRUE)
. = Execute(async)
if(!.)
to_chat(usr, "<span class='danger'>A SQL error occurred during this operation, check the server logs.</span>")
/datum/db_query/proc/Execute(async = TRUE, log_error = TRUE)
Activity("Execute")
if(in_progress)
CRASH("Attempted to start a new query while waiting on the old one")
if(!SSdbcore.IsConnected())
last_error = "No connection!"
return FALSE
var/start_time
if(!async)
start_time = REALTIMEOFDAY
Close()
. = run_query(async)
var/timed_out = !. && findtext(last_error, "Operation timed out")
if(!. && log_error)
log_sql("[last_error] | Query used: [sql] | Arguments: [json_encode(arguments)]")
if(!async && timed_out)
log_query_debug("Query execution started at [start_time]")
log_query_debug("Query execution ended at [REALTIMEOFDAY]")
log_query_debug("Slow query timeout detected.")
log_query_debug("Query used: [sql]")
slow_query_check()
/datum/db_query/proc/run_query(async)
var/job_result_str
if (async)
var/job_id = rustg_sql_query_async(connection, sql, json_encode(arguments))
in_progress = TRUE
UNTIL((job_result_str = rustg_sql_check_query(job_id)) != RUSTG_JOB_NO_RESULTS_YET)
in_progress = FALSE
if (job_result_str == RUSTG_JOB_ERROR)
last_error = job_result_str
return FALSE
else
job_result_str = rustg_sql_query_blocking(connection, sql, json_encode(arguments))
var/result = json_decode(job_result_str)
switch (result["status"])
if ("ok")
rows = result["rows"]
affected = result["affected"]
last_insert_id = result["last_insert_id"]
return TRUE
if ("err")
last_error = result["data"]
return FALSE
if ("offline")
last_error = "offline"
return FALSE
/datum/db_query/proc/slow_query_check()
message_admins("HEY! A database query timed out. Did the server just hang? <a href='?_src_=holder;[HrefToken()];slowquery=yes'>\[YES\]</a>|<a href='?_src_=holder;[HrefToken()];slowquery=no'>\[NO\]</a>")
/datum/db_query/proc/NextRow(async = TRUE)
Activity("NextRow")
if (rows && next_row_to_take <= rows.len)
item = rows[next_row_to_take]
next_row_to_take++
return !!item
else
return FALSE
/datum/db_query/proc/ErrorMsg()
return last_error
/datum/db_query/proc/Close()
rows = null
item = null
+185 -185
View File
@@ -1,185 +1,185 @@
/*
NOTES:
There is a DB table to track ckeys and associated discord IDs.
This system REQUIRES TGS, and will auto-disable if TGS is not present.
The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown
It only writes to the disk every 5 minutes, and it won't write to disk if the file is the same as it was the last time it was written. This is to save on disk writes
The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server.
##################
# HOW THIS WORKS #
##################
ROUNDSTART:
1] The file is loaded and the discord IDs are extracted
2] A ping is sent to the discord with the IDs of people who wished to be notified
3] The file is emptied
MIDROUND:
1] Someone usees the notify verb, it adds their discord ID to the list.
2] On fire, it will write that to the disk, as long as conditions above are correct
END ROUND:
1] The file is force-saved, incase it hasn't fired at end round
This is an absolute clusterfuck, but its my clusterfuck -aa07
*/
SUBSYSTEM_DEF(discord)
name = "Discord"
wait = 3000
init_order = INIT_ORDER_DISCORD
/// People to save to notify file
var/list/notify_members = list()
/// Copy of previous list, so the SS doesnt have to fire if no new members have been added
var/list/notify_members_cache = list()
/// People to notify on roundstart
var/list/people_to_notify = list()
/// List that holds accounts to link, used in conjunction with TGS
var/list/account_link_cache = list()
/// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown
var/list/reverify_cache = list()
var/notify_file = file("data/notify.json")
/// Is TGS enabled (If not we won't fire because otherwise this is useless)
var/enabled = 0
/datum/controller/subsystem/discord/Initialize(start_timeofday)
// Check for if we are using TGS, otherwise return and disables firing
if(world.TgsAvailable())
enabled = 1 // Allows other procs to use this (Account linking, etc)
else
can_fire = 0 // We dont want excess firing
return ..() // Cancel
try
people_to_notify = json_decode(file2text(notify_file))
catch
pass() // The list can just stay as its default (blank). Pass() exists because it needs a catch
var/notifymsg = jointext(people_to_notify, ", ")
if(notifymsg)
send2chat(trim(notifymsg), CONFIG_GET(string/chat_new_game_notifications)) // Sends the message to the discord, using same config option as the roundstart notification
fdel(notify_file) // Deletes the file
return ..()
/datum/controller/subsystem/discord/fire()
if(!enabled)
return // Dont do shit if its disabled
if(notify_members == notify_members_cache)
return // Dont re-write the file
// If we are all clear
write_notify_file()
/datum/controller/subsystem/discord/Shutdown()
write_notify_file() // Guaranteed force-write on server close
/datum/controller/subsystem/discord/proc/write_notify_file()
if(!enabled) // Dont do shit if its disabled
return
fdel(notify_file) // Deletes the file first to make sure it writes properly
WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file
notify_members_cache = notify_members // Updates the cache list
// Returns ID from ckey
/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey)
//We cast the discord ID to varchar to prevent BYOND mangling
//it into it's scientific notation
var/datum/db_query/query_get_discord_id = SSdbcore.NewQuery(
"SELECT CAST(discord_id AS CHAR(25)) FROM [format_table_name("player")] WHERE ckey = :ckey",
list("ckey" = lookup_ckey)
)
if(!query_get_discord_id.Execute())
qdel(query_get_discord_id)
return
if(query_get_discord_id.NextRow())
. = query_get_discord_id.item[1]
qdel(query_get_discord_id)
// Returns ckey from ID
/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id)
var/datum/db_query/query_get_discord_ckey = SSdbcore.NewQuery(
"SELECT ckey FROM [format_table_name("player")] WHERE discord_id = :discord_id",
list("discord_id" = lookup_id)
)
if(!query_get_discord_ckey.Execute())
qdel(query_get_discord_ckey)
return
if(query_get_discord_ckey.NextRow())
. = query_get_discord_ckey.item[1]
qdel(query_get_discord_ckey)
// Finalises link
/datum/controller/subsystem/discord/proc/link_account(ckey)
var/datum/db_query/link_account = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET discord_id = :discord_id WHERE ckey = :ckey",
list("discord_id" = account_link_cache[ckey], "ckey" = ckey)
)
link_account.Execute()
qdel(link_account)
account_link_cache -= ckey
// Unlink account (Admin verb used)
/datum/controller/subsystem/discord/proc/unlink_account(ckey)
var/datum/db_query/unlink_account = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = :ckey",
list("ckey" = ckey)
)
unlink_account.Execute()
qdel(unlink_account)
// Clean up a discord account mention
/datum/controller/subsystem/discord/proc/id_clean(input)
var/regex/num_only = regex("\[^0-9\]", "g")
return num_only.Replace(input, "")
/datum/controller/subsystem/discord/proc/grant_role(id)
// Ignore this shit if config isn't enabled for it
if(!CONFIG_GET(flag/enable_discord_autorole))
return
var/url = "https://discordapp.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]"
// Make the request
var/datum/http_request/req = new()
req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]"))
req.begin_async()
UNTIL(req.is_complete())
var/datum/http_response/res = req.into_response()
WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]")
/**
* Sends a message to TGS chat channels.
*
* message - The message to send.
* channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s).
*/
/proc/send2chat(message, channel_tag)
if(channel_tag == null || !world.TgsAvailable())
return
var/datum/tgs_version/version = world.TgsVersion()
if(channel_tag == "" || version.suite == 3)
world.TgsTargetedChatBroadcast(message, FALSE)
return
var/list/channels_to_use = list()
for(var/I in world.TgsChatChannelInfo())
var/datum/tgs_chat_channel/channel = I
var/list/applicable_tags = splittext(channel.tag, ",")
if(channel_tag in applicable_tags)
channels_to_use += channel
if(channels_to_use.len)
world.TgsChatBroadcast(message, channels_to_use)
/**
* Sends a message to TGS admin chat channels.
*
* category - The category of the mssage.
* message - The message to send.
*/
/proc/send2adminchat(category, message)
category = replacetext(replacetext(category, "\proper", ""), "\improper", "")
message = replacetext(replacetext(message, "\proper", ""), "\improper", "")
world.TgsTargetedChatBroadcast("[category] | [message]", TRUE)
/*
NOTES:
There is a DB table to track ckeys and associated discord IDs.
This system REQUIRES TGS, and will auto-disable if TGS is not present.
The SS uses fire() instead of just pure shutdown, so people can be notified if it comes back after a crash, where the SS wasn't properly shutdown
It only writes to the disk every 5 minutes, and it won't write to disk if the file is the same as it was the last time it was written. This is to save on disk writes
The system is kept per-server (EG: Terry will not notify people who pressed notify on Sybil), but the accounts are between servers so you dont have to relink on each server.
##################
# HOW THIS WORKS #
##################
ROUNDSTART:
1] The file is loaded and the discord IDs are extracted
2] A ping is sent to the discord with the IDs of people who wished to be notified
3] The file is emptied
MIDROUND:
1] Someone usees the notify verb, it adds their discord ID to the list.
2] On fire, it will write that to the disk, as long as conditions above are correct
END ROUND:
1] The file is force-saved, incase it hasn't fired at end round
This is an absolute clusterfuck, but its my clusterfuck -aa07
*/
SUBSYSTEM_DEF(discord)
name = "Discord"
wait = 3000
init_order = INIT_ORDER_DISCORD
/// People to save to notify file
var/list/notify_members = list()
/// Copy of previous list, so the SS doesnt have to fire if no new members have been added
var/list/notify_members_cache = list()
/// People to notify on roundstart
var/list/people_to_notify = list()
/// List that holds accounts to link, used in conjunction with TGS
var/list/account_link_cache = list()
/// list of people who tried to reverify, so they can only do it once per round as a shitty slowdown
var/list/reverify_cache = list()
var/notify_file = file("data/notify.json")
/// Is TGS enabled (If not we won't fire because otherwise this is useless)
var/enabled = 0
/datum/controller/subsystem/discord/Initialize(start_timeofday)
// Check for if we are using TGS, otherwise return and disables firing
if(world.TgsAvailable())
enabled = 1 // Allows other procs to use this (Account linking, etc)
else
can_fire = 0 // We dont want excess firing
return ..() // Cancel
try
people_to_notify = json_decode(file2text(notify_file))
catch
pass() // The list can just stay as its default (blank). Pass() exists because it needs a catch
var/notifymsg = jointext(people_to_notify, ", ")
if(notifymsg)
send2chat(trim(notifymsg), CONFIG_GET(string/chat_new_game_notifications)) // Sends the message to the discord, using same config option as the roundstart notification
fdel(notify_file) // Deletes the file
return ..()
/datum/controller/subsystem/discord/fire()
if(!enabled)
return // Dont do shit if its disabled
if(notify_members == notify_members_cache)
return // Dont re-write the file
// If we are all clear
write_notify_file()
/datum/controller/subsystem/discord/Shutdown()
write_notify_file() // Guaranteed force-write on server close
/datum/controller/subsystem/discord/proc/write_notify_file()
if(!enabled) // Dont do shit if its disabled
return
fdel(notify_file) // Deletes the file first to make sure it writes properly
WRITE_FILE(notify_file, json_encode(notify_members)) // Writes the file
notify_members_cache = notify_members // Updates the cache list
// Returns ID from ckey
/datum/controller/subsystem/discord/proc/lookup_id(lookup_ckey)
//We cast the discord ID to varchar to prevent BYOND mangling
//it into it's scientific notation
var/datum/db_query/query_get_discord_id = SSdbcore.NewQuery(
"SELECT CAST(discord_id AS CHAR(25)) FROM [format_table_name("player")] WHERE ckey = :ckey",
list("ckey" = lookup_ckey)
)
if(!query_get_discord_id.Execute())
qdel(query_get_discord_id)
return
if(query_get_discord_id.NextRow())
. = query_get_discord_id.item[1]
qdel(query_get_discord_id)
// Returns ckey from ID
/datum/controller/subsystem/discord/proc/lookup_ckey(lookup_id)
var/datum/db_query/query_get_discord_ckey = SSdbcore.NewQuery(
"SELECT ckey FROM [format_table_name("player")] WHERE discord_id = :discord_id",
list("discord_id" = lookup_id)
)
if(!query_get_discord_ckey.Execute())
qdel(query_get_discord_ckey)
return
if(query_get_discord_ckey.NextRow())
. = query_get_discord_ckey.item[1]
qdel(query_get_discord_ckey)
// Finalises link
/datum/controller/subsystem/discord/proc/link_account(ckey)
var/datum/db_query/link_account = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET discord_id = :discord_id WHERE ckey = :ckey",
list("discord_id" = account_link_cache[ckey], "ckey" = ckey)
)
link_account.Execute()
qdel(link_account)
account_link_cache -= ckey
// Unlink account (Admin verb used)
/datum/controller/subsystem/discord/proc/unlink_account(ckey)
var/datum/db_query/unlink_account = SSdbcore.NewQuery(
"UPDATE [format_table_name("player")] SET discord_id = NULL WHERE ckey = :ckey",
list("ckey" = ckey)
)
unlink_account.Execute()
qdel(unlink_account)
// Clean up a discord account mention
/datum/controller/subsystem/discord/proc/id_clean(input)
var/regex/num_only = regex("\[^0-9\]", "g")
return num_only.Replace(input, "")
/datum/controller/subsystem/discord/proc/grant_role(id)
// Ignore this shit if config isn't enabled for it
if(!CONFIG_GET(flag/enable_discord_autorole))
return
var/url = "https://discordapp.com/api/guilds/[CONFIG_GET(string/discord_guildid)]/members/[id]/roles/[CONFIG_GET(string/discord_roleid)]"
// Make the request
var/datum/http_request/req = new()
req.prepare(RUSTG_HTTP_METHOD_PUT, url, "", list("Authorization" = "Bot [CONFIG_GET(string/discord_token)]"))
req.begin_async()
UNTIL(req.is_complete())
var/datum/http_response/res = req.into_response()
WRITE_LOG(GLOB.discord_api_log, "PUT [url] returned [res.status_code] [res.body]")
/**
* Sends a message to TGS chat channels.
*
* message - The message to send.
* channel_tag - Required. If "", the message with be sent to all connected (Game-type for TGS3) channels. Otherwise, it will be sent to TGS4 channels with that tag (Delimited by ','s).
*/
/proc/send2chat(message, channel_tag)
if(channel_tag == null || !world.TgsAvailable())
return
var/datum/tgs_version/version = world.TgsVersion()
if(channel_tag == "" || version.suite == 3)
world.TgsTargetedChatBroadcast(message, FALSE)
return
var/list/channels_to_use = list()
for(var/I in world.TgsChatChannelInfo())
var/datum/tgs_chat_channel/channel = I
var/list/applicable_tags = splittext(channel.tag, ",")
if(channel_tag in applicable_tags)
channels_to_use += channel
if(channels_to_use.len)
world.TgsChatBroadcast(message, channels_to_use)
/**
* Sends a message to TGS admin chat channels.
*
* category - The category of the mssage.
* message - The message to send.
*/
/proc/send2adminchat(category, message)
category = replacetext(replacetext(category, "\proper", ""), "\improper", "")
message = replacetext(replacetext(message, "\proper", ""), "\improper", "")
world.TgsTargetedChatBroadcast("[category] | [message]", TRUE)
+4 -4
View File
@@ -1,4 +1,4 @@
PROCESSING_SUBSYSTEM_DEF(mood)
name = "Mood"
flags = SS_NO_INIT | SS_BACKGROUND
priority = 20
PROCESSING_SUBSYSTEM_DEF(mood)
name = "Mood"
flags = SS_NO_INIT | SS_BACKGROUND
priority = 20
+55 -55
View File
@@ -1,55 +1,55 @@
SUBSYSTEM_DEF(nightshift)
name = "Night Shift"
wait = 600
flags = SS_NO_TICK_CHECK
var/nightshift_active = FALSE
var/nightshift_start_time = 702000 //7:30 PM, station time
var/nightshift_end_time = 270000 //7:30 AM, station time
var/nightshift_first_check = 30 SECONDS
var/high_security_mode = FALSE
/datum/controller/subsystem/nightshift/Initialize()
if(!CONFIG_GET(flag/enable_night_shifts))
can_fire = FALSE
return ..()
/datum/controller/subsystem/nightshift/fire(resumed = FALSE)
if(world.time - SSticker.round_start_time < nightshift_first_check)
return
check_nightshift()
/datum/controller/subsystem/nightshift/proc/announce(message)
priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement")
/datum/controller/subsystem/nightshift/proc/check_nightshift()
var/emergency = GLOB.security_level >= SEC_LEVEL_RED
var/announcing = TRUE
var/time = station_time()
var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time)
if(high_security_mode != emergency)
high_security_mode = emergency
if(night_time)
announcing = FALSE
if(!emergency)
announce("Restoring night lighting configuration to normal operation.")
else
announce("Disabling night lighting: Station is in a state of emergency.")
if(emergency)
night_time = FALSE
if(nightshift_active != night_time)
update_nightshift(night_time, announcing)
/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE)
nightshift_active = active
if(announce)
if (active)
announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.")
else
announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.")
for(var/A in GLOB.apcs_list)
var/obj/machinery/power/apc/APC = A
if (APC.area && (APC.area.type in GLOB.the_station_areas))
APC.set_nightshift(active)
CHECK_TICK
SUBSYSTEM_DEF(nightshift)
name = "Night Shift"
wait = 600
flags = SS_NO_TICK_CHECK
var/nightshift_active = FALSE
var/nightshift_start_time = 702000 //7:30 PM, station time
var/nightshift_end_time = 270000 //7:30 AM, station time
var/nightshift_first_check = 30 SECONDS
var/high_security_mode = FALSE
/datum/controller/subsystem/nightshift/Initialize()
if(!CONFIG_GET(flag/enable_night_shifts))
can_fire = FALSE
return ..()
/datum/controller/subsystem/nightshift/fire(resumed = FALSE)
if(world.time - SSticker.round_start_time < nightshift_first_check)
return
check_nightshift()
/datum/controller/subsystem/nightshift/proc/announce(message)
priority_announce(message, sound='sound/misc/notice2.ogg', sender_override="Automated Lighting System Announcement")
/datum/controller/subsystem/nightshift/proc/check_nightshift()
var/emergency = GLOB.security_level >= SEC_LEVEL_RED
var/announcing = TRUE
var/time = station_time()
var/night_time = (time < nightshift_end_time) || (time > nightshift_start_time)
if(high_security_mode != emergency)
high_security_mode = emergency
if(night_time)
announcing = FALSE
if(!emergency)
announce("Restoring night lighting configuration to normal operation.")
else
announce("Disabling night lighting: Station is in a state of emergency.")
if(emergency)
night_time = FALSE
if(nightshift_active != night_time)
update_nightshift(night_time, announcing)
/datum/controller/subsystem/nightshift/proc/update_nightshift(active, announce = TRUE)
nightshift_active = active
if(announce)
if (active)
announce("Good evening, crew. To reduce power consumption and stimulate the circadian rhythms of some species, all of the lights aboard the station have been dimmed for the night.")
else
announce("Good morning, crew. As it is now day time, all of the lights aboard the station have been restored to their former brightness.")
for(var/A in GLOB.apcs_list)
var/obj/machinery/power/apc/APC = A
if (APC.area && (APC.area.type in GLOB.the_station_areas))
APC.set_nightshift(active)
CHECK_TICK
+34 -34
View File
@@ -1,34 +1,34 @@
SUBSYSTEM_DEF(npcpool)
name = "NPC Pool"
flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND
priority = FIRE_PRIORITY_NPC
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/list/currentrun = list()
/datum/controller/subsystem/npcpool/stat_entry()
var/list/activelist = GLOB.simple_animals[AI_ON]
..("NPCS:[activelist.len]")
/datum/controller/subsystem/npcpool/fire(resumed = FALSE)
if (!resumed)
var/list/activelist = GLOB.simple_animals[AI_ON]
src.currentrun = activelist.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
if(!SA.ckey && !SA.notransform)
if(SA.stat != DEAD)
SA.handle_automated_movement()
if(SA.stat != DEAD)
SA.handle_automated_action()
if(SA.stat != DEAD)
SA.handle_automated_speech()
if (MC_TICK_CHECK)
return
SUBSYSTEM_DEF(npcpool)
name = "NPC Pool"
flags = SS_POST_FIRE_TIMING|SS_NO_INIT|SS_BACKGROUND
priority = FIRE_PRIORITY_NPC
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/list/currentrun = list()
/datum/controller/subsystem/npcpool/stat_entry()
var/list/activelist = GLOB.simple_animals[AI_ON]
..("NPCS:[activelist.len]")
/datum/controller/subsystem/npcpool/fire(resumed = FALSE)
if (!resumed)
var/list/activelist = GLOB.simple_animals[AI_ON]
src.currentrun = activelist.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
var/mob/living/simple_animal/SA = currentrun[currentrun.len]
--currentrun.len
if(!SA.ckey && !SA.notransform)
if(SA.stat != DEAD)
SA.handle_automated_movement()
if(SA.stat != DEAD)
SA.handle_automated_action()
if(SA.stat != DEAD)
SA.handle_automated_speech()
if (MC_TICK_CHECK)
return
+48 -48
View File
@@ -1,48 +1,48 @@
SUBSYSTEM_DEF(pathfinder)
name = "Pathfinder"
init_order = INIT_ORDER_PATH
flags = SS_NO_FIRE
var/datum/flowcache/mobs
var/datum/flowcache/circuits
var/static/space_type_cache
/datum/controller/subsystem/pathfinder/Initialize()
space_type_cache = typecacheof(/turf/open/space)
mobs = new(10)
circuits = new(3)
return ..()
/datum/flowcache
var/lcount
var/run
var/free
var/list/flow
/datum/flowcache/New(var/n)
. = ..()
lcount = n
run = 0
free = 1
flow = new/list(lcount)
/datum/flowcache/proc/getfree(atom/M)
if(run < lcount)
run += 1
while(flow[free])
CHECK_TICK
free = (free % lcount) + 1
var/t = addtimer(CALLBACK(src, /datum/flowcache.proc/toolong, free), 150, TIMER_STOPPABLE)
flow[free] = t
flow[t] = M
return free
else
return 0
/datum/flowcache/proc/toolong(l)
log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]")
found(l)
/datum/flowcache/proc/found(l)
deltimer(flow[l])
flow[l] = null
run -= 1
SUBSYSTEM_DEF(pathfinder)
name = "Pathfinder"
init_order = INIT_ORDER_PATH
flags = SS_NO_FIRE
var/datum/flowcache/mobs
var/datum/flowcache/circuits
var/static/space_type_cache
/datum/controller/subsystem/pathfinder/Initialize()
space_type_cache = typecacheof(/turf/open/space)
mobs = new(10)
circuits = new(3)
return ..()
/datum/flowcache
var/lcount
var/run
var/free
var/list/flow
/datum/flowcache/New(var/n)
. = ..()
lcount = n
run = 0
free = 1
flow = new/list(lcount)
/datum/flowcache/proc/getfree(atom/M)
if(run < lcount)
run += 1
while(flow[free])
CHECK_TICK
free = (free % lcount) + 1
var/t = addtimer(CALLBACK(src, /datum/flowcache.proc/toolong, free), 150, TIMER_STOPPABLE)
flow[free] = t
flow[t] = M
return free
else
return 0
/datum/flowcache/proc/toolong(l)
log_game("Pathfinder route took longer than 150 ticks, src bot [flow[flow[l]]]")
found(l)
/datum/flowcache/proc/found(l)
deltimer(flow[l])
flow[l] = null
run -= 1
@@ -1,51 +1,51 @@
PROCESSING_SUBSYSTEM_DEF(networks)
name = "Networks"
priority = FIRE_PRIORITY_NETWORKS
wait = 1
stat_tag = "NET"
flags = SS_KEEP_TIMING
init_order = INIT_ORDER_NETWORKS
var/datum/ntnet/station/station_network
var/assignment_hardware_id = HID_RESTRICTED_END
var/list/networks_by_id = list() //id = network
var/list/interfaces_by_id = list() //hardware id = component interface
var/resolve_collisions = TRUE
/datum/controller/subsystem/processing/networks/Initialize()
station_network = new
station_network.register_map_supremecy()
. = ..()
/datum/controller/subsystem/processing/networks/proc/register_network(datum/ntnet/network)
if(!networks_by_id[network.network_id])
networks_by_id[network.network_id] = network
return TRUE
return FALSE
/datum/controller/subsystem/processing/networks/proc/unregister_network(datum/ntnet/network)
networks_by_id -= network.network_id
return TRUE
/datum/controller/subsystem/processing/networks/proc/register_interface(datum/component/ntnet_interface/D)
if(!interfaces_by_id[D.hardware_id])
interfaces_by_id[D.hardware_id] = D
return TRUE
return FALSE
/datum/controller/subsystem/processing/networks/proc/unregister_interface(datum/component/ntnet_interface/D)
interfaces_by_id -= D.hardware_id
return TRUE
/datum/controller/subsystem/processing/networks/proc/get_next_HID()
var/string = "[num2text(assignment_hardware_id++, 12)]"
return make_address(string)
/datum/controller/subsystem/processing/networks/proc/make_address(string)
if(!string)
return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null
var/hex = md5(string)
if(!hex)
return //errored
. = "[copytext_char(hex, 1, 9)]" //16 ^ 8 possibilities I think.
if(interfaces_by_id[.])
return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null
PROCESSING_SUBSYSTEM_DEF(networks)
name = "Networks"
priority = FIRE_PRIORITY_NETWORKS
wait = 1
stat_tag = "NET"
flags = SS_KEEP_TIMING
init_order = INIT_ORDER_NETWORKS
var/datum/ntnet/station/station_network
var/assignment_hardware_id = HID_RESTRICTED_END
var/list/networks_by_id = list() //id = network
var/list/interfaces_by_id = list() //hardware id = component interface
var/resolve_collisions = TRUE
/datum/controller/subsystem/processing/networks/Initialize()
station_network = new
station_network.register_map_supremecy()
. = ..()
/datum/controller/subsystem/processing/networks/proc/register_network(datum/ntnet/network)
if(!networks_by_id[network.network_id])
networks_by_id[network.network_id] = network
return TRUE
return FALSE
/datum/controller/subsystem/processing/networks/proc/unregister_network(datum/ntnet/network)
networks_by_id -= network.network_id
return TRUE
/datum/controller/subsystem/processing/networks/proc/register_interface(datum/component/ntnet_interface/D)
if(!interfaces_by_id[D.hardware_id])
interfaces_by_id[D.hardware_id] = D
return TRUE
return FALSE
/datum/controller/subsystem/processing/networks/proc/unregister_interface(datum/component/ntnet_interface/D)
interfaces_by_id -= D.hardware_id
return TRUE
/datum/controller/subsystem/processing/networks/proc/get_next_HID()
var/string = "[num2text(assignment_hardware_id++, 12)]"
return make_address(string)
/datum/controller/subsystem/processing/networks/proc/make_address(string)
if(!string)
return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null
var/hex = md5(string)
if(!hex)
return //errored
. = "[copytext_char(hex, 1, 9)]" //16 ^ 8 possibilities I think.
if(interfaces_by_id[.])
return resolve_collisions? make_address("[num2text(rand(HID_RESTRICTED_END, 999999999), 12)]"):null
@@ -1,23 +1,23 @@
PROCESSING_SUBSYSTEM_DEF(projectiles)
name = "Projectiles"
wait = 1
stat_tag = "PP"
flags = SS_NO_INIT|SS_TICKER
var/global_max_tick_moves = 10
var/global_pixel_speed = 2
var/global_iterations_per_move = 16
/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed)
global_pixel_speed = new_speed
for(var/i in processing)
var/obj/projectile/P = i
if(istype(P)) //there's non projectiles on this too.
P.set_pixel_speed(new_speed)
/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value)
switch(var_name)
if(NAMEOF(src, global_pixel_speed))
set_pixel_speed(var_value)
return TRUE
else
return ..()
PROCESSING_SUBSYSTEM_DEF(projectiles)
name = "Projectiles"
wait = 1
stat_tag = "PP"
flags = SS_NO_INIT|SS_TICKER
var/global_max_tick_moves = 10
var/global_pixel_speed = 2
var/global_iterations_per_move = 16
/datum/controller/subsystem/processing/projectiles/proc/set_pixel_speed(new_speed)
global_pixel_speed = new_speed
for(var/i in processing)
var/obj/projectile/P = i
if(istype(P)) //there's non projectiles on this too.
P.set_pixel_speed(new_speed)
/datum/controller/subsystem/processing/projectiles/vv_edit_var(var_name, var_value)
switch(var_name)
if(NAMEOF(src, global_pixel_speed))
set_pixel_speed(var_value)
return TRUE
else
return ..()
@@ -1,7 +1,7 @@
PROCESSING_SUBSYSTEM_DEF(wet_floors)
name = "Wet floors"
priority = FIRE_PRIORITY_WET_FLOORS
wait = 10
stat_tag = "WFP" //Used for logging
var/temperature_coeff = 2
var/time_ratio = 1.5 SECONDS
PROCESSING_SUBSYSTEM_DEF(wet_floors)
name = "Wet floors"
priority = FIRE_PRIORITY_WET_FLOORS
wait = 10
stat_tag = "WFP" //Used for logging
var/temperature_coeff = 2
var/time_ratio = 1.5 SECONDS
+291 -291
View File
@@ -1,291 +1,291 @@
SUBSYSTEM_DEF(research)
name = "Research"
priority = FIRE_PRIORITY_RESEARCH
wait = 10
init_order = INIT_ORDER_RESEARCH
//TECHWEB STATIC
var/list/techweb_nodes = list() //associative id = node datum
var/list/techweb_designs = list() //associative id = node datum
var/list/datum/techweb/techwebs = list()
var/datum/techweb/science/science_tech
var/datum/techweb/admin/admin_tech
var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console.
var/datum/design/error_design/error_design
//ERROR LOGGING
var/list/invalid_design_ids = list() //associative id = number of times
var/list/invalid_node_ids = list() //associative id = number of times
var/list/invalid_node_boost = list() //associative id = error message
var/list/obj/machinery/rnd/server/servers = list()
var/list/techweb_nodes_starting = list() //associative id = TRUE
var/list/techweb_categories = list() //category name = list(node.id = TRUE)
var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount))
var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default.
var/list/techweb_nodes_experimental = list() //Node ids that are exclusive to the BEPIS.
var/list/techweb_point_items = list( //path = list(point type = value)
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
)
var/list/errored_datums = list()
var/list/point_types = list() //typecache style type = TRUE list
var/list/slime_already_researched = list() //Slime cores that have already been researched
//----------------------------------------------
var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME)
var/multiserver_calculation = FALSE
var/last_income
//^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^
//Aiming for 1.5 hours to max R&D
//[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min]
//Around 450000 points max???
/// The global list of raw anomaly types that have been refined, for hard limits.
var/list/created_anomaly_types = list()
/// The hard limits of cores created for each anomaly type. For faster code lookup without switch statements.
var/list/anomaly_hard_limit_by_type = list(
ANOMALY_CORE_BLUESPACE = MAX_CORES_BLUESPACE,
ANOMALY_CORE_PYRO = MAX_CORES_PYRO,
ANOMALY_CORE_GRAVITATIONAL = MAX_CORES_GRAVITATIONAL,
ANOMALY_CORE_VORTEX = MAX_CORES_VORTEX,
ANOMALY_CORE_FLUX = MAX_CORES_FLUX
)
/datum/controller/subsystem/research/Initialize()
point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES
initialize_all_techweb_designs()
initialize_all_techweb_nodes()
science_tech = new /datum/techweb/science
admin_tech = new /datum/techweb/admin
autosort_categories()
error_design = new
error_node = new
return ..()
/datum/controller/subsystem/research/fire()
var/list/bitcoins = list()
if(multiserver_calculation)
var/eff = calculate_server_coefficient()
for(var/obj/machinery/rnd/server/miner in servers)
var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE.
for(var/i in result)
result[i] *= eff
bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i]
else
for(var/obj/machinery/rnd/server/miner in servers)
if(miner.working)
bitcoins = single_server_income.Copy()
break //Just need one to work.
if (!isnull(last_income))
var/income_time_difference = world.time - last_income
science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account
for(var/i in bitcoins)
bitcoins[i] *= income_time_difference / 10
science_tech.add_point_list(bitcoins)
last_income = world.time
/datum/controller/subsystem/research/proc/calculate_server_coefficient() //Diminishing returns.
var/amt = servers.len
if(!amt)
return 0
var/coeff = 100
coeff = sqrt(coeff / amt)
return coeff
/datum/controller/subsystem/research/proc/autosort_categories()
for(var/i in techweb_nodes)
var/datum/techweb_node/I = techweb_nodes[i]
if(techweb_categories[I.category])
techweb_categories[I.category][I.id] = TRUE
else
techweb_categories[I.category] = list(I.id = TRUE)
/datum/controller/subsystem/research/proc/techweb_node_by_id(id)
return techweb_nodes[id] || error_node
/datum/controller/subsystem/research/proc/techweb_design_by_id(id)
return techweb_designs[id] || error_design
/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D)
for(var/i in techweb_nodes)
var/datum/techweb_node/TN = techwebs[i]
TN.on_design_deletion(TN)
for(var/i in techwebs)
var/datum/techweb/T = i
T.recalculate_nodes(TRUE)
/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN)
for(var/i in techweb_nodes)
var/datum/techweb_node/TN2 = techwebs[i]
TN2.on_node_deletion(TN)
for(var/i in techwebs)
var/datum/techweb/T = i
T.recalculate_nodes(TRUE)
/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE)
if(islist(techweb_nodes) && clearall)
QDEL_LIST(techweb_nodes)
if(islist(techweb_nodes_starting && clearall))
techweb_nodes_starting.Cut()
var/list/returned = list()
for(var/path in subtypesof(/datum/techweb_node))
var/datum/techweb_node/TN = path
if(isnull(initial(TN.id)))
continue
TN = new path
if(returned[initial(TN.id)])
stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]")
errored_datums[TN] = initial(TN.id)
continue
returned[initial(TN.id)] = TN
if(TN.starting_node)
techweb_nodes_starting[TN.id] = TRUE
for(var/id in techweb_nodes)
var/datum/techweb_node/TN = techweb_nodes[id]
TN.Initialize()
techweb_nodes = returned
if (!verify_techweb_nodes()) //Verify all nodes have ids and such.
stack_trace("Invalid techweb nodes detected")
calculate_techweb_nodes()
calculate_techweb_boost_list()
if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly.
CRASH("Invalid techweb nodes detected")
/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE)
if(islist(techweb_designs) && clearall)
QDEL_LIST(techweb_designs)
var/list/returned = list()
for(var/path in subtypesof(/datum/design))
var/datum/design/DN = path
if(isnull(initial(DN.id)))
stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]")
continue
else if(initial(DN.id) == DESIGN_ID_IGNORE)
continue
DN = new path
if(returned[initial(DN.id)])
stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]")
errored_datums[DN] = initial(DN.id)
continue
DN.InitializeMaterials() //Initialize the materials in the design
returned[initial(DN.id)] = DN
techweb_designs = returned
verify_techweb_designs()
/datum/controller/subsystem/research/proc/verify_techweb_nodes()
. = TRUE
for(var/n in techweb_nodes)
var/datum/techweb_node/N = techweb_nodes[n]
if(!istype(N))
WARNING("Invalid research node with ID [n] detected and removed.")
techweb_nodes -= n
research_node_id_error(n)
. = FALSE
for(var/p in N.prereq_ids)
var/datum/techweb_node/P = techweb_nodes[p]
if(!istype(P))
WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.")
N.prereq_ids -= p
research_node_id_error(p)
. = FALSE
for(var/d in N.design_ids)
var/datum/design/D = techweb_designs[d]
if(!istype(D))
WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.")
N.design_ids -= d
design_id_error(d)
. = FALSE
for(var/u in N.unlock_ids)
var/datum/techweb_node/U = techweb_nodes[u]
if(!istype(U))
WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.")
N.unlock_ids -= u
research_node_id_error(u)
. = FALSE
for(var/p in N.boost_item_paths)
if(!ispath(p))
N.boost_item_paths -= p
WARNING("[p] is not a valid path.")
node_boost_error(N.id, "[p] is not a valid path.")
. = FALSE
var/list/points = N.boost_item_paths[p]
if(islist(points))
for(var/i in points)
if(!isnum(points[i]))
WARNING("[points[i]] is not a valid number.")
node_boost_error(N.id, "[points[i]] is not a valid number.")
. = FALSE
else if(!point_types[i])
WARNING("[i] is not a valid point type.")
node_boost_error(N.id, "[i] is not a valid point type.")
. = FALSE
else if(!isnull(points))
N.boost_item_paths -= p
node_boost_error(N.id, "No valid list.")
WARNING("No valid list.")
. = FALSE
CHECK_TICK
/datum/controller/subsystem/research/proc/verify_techweb_designs()
for(var/d in techweb_designs)
var/datum/design/D = techweb_designs[d]
if(!istype(D))
stack_trace("WARNING: Invalid research design with ID [d] detected and removed.")
techweb_designs -= d
CHECK_TICK
/datum/controller/subsystem/research/proc/research_node_id_error(id)
if(invalid_node_ids[id])
invalid_node_ids[id]++
else
invalid_node_ids[id] = 1
/datum/controller/subsystem/research/proc/design_id_error(id)
if(invalid_design_ids[id])
invalid_design_ids[id]++
else
invalid_design_ids[id] = 1
/datum/controller/subsystem/research/proc/calculate_techweb_nodes()
for(var/design_id in techweb_designs)
var/datum/design/D = techweb_designs[design_id]
D.unlocked_by.Cut()
for(var/node_id in techweb_nodes)
var/datum/techweb_node/node = techweb_nodes[node_id]
node.unlock_ids = list()
for(var/i in node.design_ids)
var/datum/design/D = techweb_designs[i]
node.design_ids[i] = TRUE
D.unlocked_by += node.id
if(node.hidden)
techweb_nodes_hidden[node.id] = TRUE
if(node.experimental)
techweb_nodes_experimental[node.id] = TRUE
CHECK_TICK
generate_techweb_unlock_linking()
/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking()
for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication.
var/datum/techweb_node/node = techweb_nodes[node_id]
node.unlock_ids = list()
for(var/node_id in techweb_nodes)
var/datum/techweb_node/node = techweb_nodes[node_id]
for(var/prereq_id in node.prereq_ids)
var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id)
prereq_node.unlock_ids[node.id] = node
/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE)
if(clearall)
techweb_boost_items = list()
for(var/node_id in techweb_nodes)
var/datum/techweb_node/node = techweb_nodes[node_id]
for(var/path in node.boost_item_paths)
if(!ispath(path))
continue
if(length(techweb_boost_items[path]))
techweb_boost_items[path][node.id] = node.boost_item_paths[path]
else
techweb_boost_items[path] = list(node.id = node.boost_item_paths[path])
CHECK_TICK
SUBSYSTEM_DEF(research)
name = "Research"
priority = FIRE_PRIORITY_RESEARCH
wait = 10
init_order = INIT_ORDER_RESEARCH
//TECHWEB STATIC
var/list/techweb_nodes = list() //associative id = node datum
var/list/techweb_designs = list() //associative id = node datum
var/list/datum/techweb/techwebs = list()
var/datum/techweb/science/science_tech
var/datum/techweb/admin/admin_tech
var/datum/techweb_node/error_node/error_node //These two are what you get if a node/design is deleted and somehow still stored in a console.
var/datum/design/error_design/error_design
//ERROR LOGGING
var/list/invalid_design_ids = list() //associative id = number of times
var/list/invalid_node_ids = list() //associative id = number of times
var/list/invalid_node_boost = list() //associative id = error message
var/list/obj/machinery/rnd/server/servers = list()
var/list/techweb_nodes_starting = list() //associative id = TRUE
var/list/techweb_categories = list() //category name = list(node.id = TRUE)
var/list/techweb_boost_items = list() //associative double-layer path = list(id = list(point_type = point_discount))
var/list/techweb_nodes_hidden = list() //Node ids that should be hidden by default.
var/list/techweb_nodes_experimental = list() //Node ids that are exclusive to the BEPIS.
var/list/techweb_point_items = list( //path = list(point type = value)
/obj/item/assembly/signaler/anomaly = list(TECHWEB_POINT_TYPE_GENERIC = 10000)
)
var/list/errored_datums = list()
var/list/point_types = list() //typecache style type = TRUE list
var/list/slime_already_researched = list() //Slime cores that have already been researched
//----------------------------------------------
var/list/single_server_income = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_SINGLE_SERVER_INCOME)
var/multiserver_calculation = FALSE
var/last_income
//^^^^^^^^ ALL OF THESE ARE PER SECOND! ^^^^^^^^
//Aiming for 1.5 hours to max R&D
//[88nodes * 5000points/node] / [1.5hr * 90min/hr * 60s/min]
//Around 450000 points max???
/// The global list of raw anomaly types that have been refined, for hard limits.
var/list/created_anomaly_types = list()
/// The hard limits of cores created for each anomaly type. For faster code lookup without switch statements.
var/list/anomaly_hard_limit_by_type = list(
ANOMALY_CORE_BLUESPACE = MAX_CORES_BLUESPACE,
ANOMALY_CORE_PYRO = MAX_CORES_PYRO,
ANOMALY_CORE_GRAVITATIONAL = MAX_CORES_GRAVITATIONAL,
ANOMALY_CORE_VORTEX = MAX_CORES_VORTEX,
ANOMALY_CORE_FLUX = MAX_CORES_FLUX
)
/datum/controller/subsystem/research/Initialize()
point_types = TECHWEB_POINT_TYPE_LIST_ASSOCIATIVE_NAMES
initialize_all_techweb_designs()
initialize_all_techweb_nodes()
science_tech = new /datum/techweb/science
admin_tech = new /datum/techweb/admin
autosort_categories()
error_design = new
error_node = new
return ..()
/datum/controller/subsystem/research/fire()
var/list/bitcoins = list()
if(multiserver_calculation)
var/eff = calculate_server_coefficient()
for(var/obj/machinery/rnd/server/miner in servers)
var/list/result = (miner.mine()) //SLAVE AWAY, SLAVE.
for(var/i in result)
result[i] *= eff
bitcoins[i] = bitcoins[i]? bitcoins[i] + result[i] : result[i]
else
for(var/obj/machinery/rnd/server/miner in servers)
if(miner.working)
bitcoins = single_server_income.Copy()
break //Just need one to work.
if (!isnull(last_income))
var/income_time_difference = world.time - last_income
science_tech.last_bitcoins = bitcoins // Doesn't take tick drift into account
for(var/i in bitcoins)
bitcoins[i] *= income_time_difference / 10
science_tech.add_point_list(bitcoins)
last_income = world.time
/datum/controller/subsystem/research/proc/calculate_server_coefficient() //Diminishing returns.
var/amt = servers.len
if(!amt)
return 0
var/coeff = 100
coeff = sqrt(coeff / amt)
return coeff
/datum/controller/subsystem/research/proc/autosort_categories()
for(var/i in techweb_nodes)
var/datum/techweb_node/I = techweb_nodes[i]
if(techweb_categories[I.category])
techweb_categories[I.category][I.id] = TRUE
else
techweb_categories[I.category] = list(I.id = TRUE)
/datum/controller/subsystem/research/proc/techweb_node_by_id(id)
return techweb_nodes[id] || error_node
/datum/controller/subsystem/research/proc/techweb_design_by_id(id)
return techweb_designs[id] || error_design
/datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D)
for(var/i in techweb_nodes)
var/datum/techweb_node/TN = techwebs[i]
TN.on_design_deletion(TN)
for(var/i in techwebs)
var/datum/techweb/T = i
T.recalculate_nodes(TRUE)
/datum/controller/subsystem/research/proc/on_node_deletion(datum/techweb_node/TN)
for(var/i in techweb_nodes)
var/datum/techweb_node/TN2 = techwebs[i]
TN2.on_node_deletion(TN)
for(var/i in techwebs)
var/datum/techweb/T = i
T.recalculate_nodes(TRUE)
/datum/controller/subsystem/research/proc/initialize_all_techweb_nodes(clearall = FALSE)
if(islist(techweb_nodes) && clearall)
QDEL_LIST(techweb_nodes)
if(islist(techweb_nodes_starting && clearall))
techweb_nodes_starting.Cut()
var/list/returned = list()
for(var/path in subtypesof(/datum/techweb_node))
var/datum/techweb_node/TN = path
if(isnull(initial(TN.id)))
continue
TN = new path
if(returned[initial(TN.id)])
stack_trace("WARNING: Techweb node ID clash with ID [initial(TN.id)] detected! Path: [path]")
errored_datums[TN] = initial(TN.id)
continue
returned[initial(TN.id)] = TN
if(TN.starting_node)
techweb_nodes_starting[TN.id] = TRUE
for(var/id in techweb_nodes)
var/datum/techweb_node/TN = techweb_nodes[id]
TN.Initialize()
techweb_nodes = returned
if (!verify_techweb_nodes()) //Verify all nodes have ids and such.
stack_trace("Invalid techweb nodes detected")
calculate_techweb_nodes()
calculate_techweb_boost_list()
if (!verify_techweb_nodes()) //Verify nodes and designs have been crosslinked properly.
CRASH("Invalid techweb nodes detected")
/datum/controller/subsystem/research/proc/initialize_all_techweb_designs(clearall = FALSE)
if(islist(techweb_designs) && clearall)
QDEL_LIST(techweb_designs)
var/list/returned = list()
for(var/path in subtypesof(/datum/design))
var/datum/design/DN = path
if(isnull(initial(DN.id)))
stack_trace("WARNING: Design with null ID detected. Build path: [initial(DN.build_path)]")
continue
else if(initial(DN.id) == DESIGN_ID_IGNORE)
continue
DN = new path
if(returned[initial(DN.id)])
stack_trace("WARNING: Design ID clash with ID [initial(DN.id)] detected! Path: [path]")
errored_datums[DN] = initial(DN.id)
continue
DN.InitializeMaterials() //Initialize the materials in the design
returned[initial(DN.id)] = DN
techweb_designs = returned
verify_techweb_designs()
/datum/controller/subsystem/research/proc/verify_techweb_nodes()
. = TRUE
for(var/n in techweb_nodes)
var/datum/techweb_node/N = techweb_nodes[n]
if(!istype(N))
WARNING("Invalid research node with ID [n] detected and removed.")
techweb_nodes -= n
research_node_id_error(n)
. = FALSE
for(var/p in N.prereq_ids)
var/datum/techweb_node/P = techweb_nodes[p]
if(!istype(P))
WARNING("Invalid research prerequisite node with ID [p] detected in node [N.display_name]\[[N.id]\] removed.")
N.prereq_ids -= p
research_node_id_error(p)
. = FALSE
for(var/d in N.design_ids)
var/datum/design/D = techweb_designs[d]
if(!istype(D))
WARNING("Invalid research design with ID [d] detected in node [N.display_name]\[[N.id]\] removed.")
N.design_ids -= d
design_id_error(d)
. = FALSE
for(var/u in N.unlock_ids)
var/datum/techweb_node/U = techweb_nodes[u]
if(!istype(U))
WARNING("Invalid research unlock node with ID [u] detected in node [N.display_name]\[[N.id]\] removed.")
N.unlock_ids -= u
research_node_id_error(u)
. = FALSE
for(var/p in N.boost_item_paths)
if(!ispath(p))
N.boost_item_paths -= p
WARNING("[p] is not a valid path.")
node_boost_error(N.id, "[p] is not a valid path.")
. = FALSE
var/list/points = N.boost_item_paths[p]
if(islist(points))
for(var/i in points)
if(!isnum(points[i]))
WARNING("[points[i]] is not a valid number.")
node_boost_error(N.id, "[points[i]] is not a valid number.")
. = FALSE
else if(!point_types[i])
WARNING("[i] is not a valid point type.")
node_boost_error(N.id, "[i] is not a valid point type.")
. = FALSE
else if(!isnull(points))
N.boost_item_paths -= p
node_boost_error(N.id, "No valid list.")
WARNING("No valid list.")
. = FALSE
CHECK_TICK
/datum/controller/subsystem/research/proc/verify_techweb_designs()
for(var/d in techweb_designs)
var/datum/design/D = techweb_designs[d]
if(!istype(D))
stack_trace("WARNING: Invalid research design with ID [d] detected and removed.")
techweb_designs -= d
CHECK_TICK
/datum/controller/subsystem/research/proc/research_node_id_error(id)
if(invalid_node_ids[id])
invalid_node_ids[id]++
else
invalid_node_ids[id] = 1
/datum/controller/subsystem/research/proc/design_id_error(id)
if(invalid_design_ids[id])
invalid_design_ids[id]++
else
invalid_design_ids[id] = 1
/datum/controller/subsystem/research/proc/calculate_techweb_nodes()
for(var/design_id in techweb_designs)
var/datum/design/D = techweb_designs[design_id]
D.unlocked_by.Cut()
for(var/node_id in techweb_nodes)
var/datum/techweb_node/node = techweb_nodes[node_id]
node.unlock_ids = list()
for(var/i in node.design_ids)
var/datum/design/D = techweb_designs[i]
node.design_ids[i] = TRUE
D.unlocked_by += node.id
if(node.hidden)
techweb_nodes_hidden[node.id] = TRUE
if(node.experimental)
techweb_nodes_experimental[node.id] = TRUE
CHECK_TICK
generate_techweb_unlock_linking()
/datum/controller/subsystem/research/proc/generate_techweb_unlock_linking()
for(var/node_id in techweb_nodes) //Clear all unlock links to avoid duplication.
var/datum/techweb_node/node = techweb_nodes[node_id]
node.unlock_ids = list()
for(var/node_id in techweb_nodes)
var/datum/techweb_node/node = techweb_nodes[node_id]
for(var/prereq_id in node.prereq_ids)
var/datum/techweb_node/prereq_node = techweb_node_by_id(prereq_id)
prereq_node.unlock_ids[node.id] = node
/datum/controller/subsystem/research/proc/calculate_techweb_boost_list(clearall = FALSE)
if(clearall)
techweb_boost_items = list()
for(var/node_id in techweb_nodes)
var/datum/techweb_node/node = techweb_nodes[node_id]
for(var/path in node.boost_item_paths)
if(!ispath(path))
continue
if(length(techweb_boost_items[path]))
techweb_boost_items[path][node.id] = node.boost_item_paths[path]
else
techweb_boost_items[path] = list(node.id = node.boost_item_paths[path])
CHECK_TICK
+59 -59
View File
@@ -1,59 +1,59 @@
SUBSYSTEM_DEF(spacedrift)
name = "Space Drift"
priority = FIRE_PRIORITY_SPACEDRIFT
wait = 5
flags = SS_NO_INIT|SS_KEEP_TIMING
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/list/currentrun = list()
var/list/processing = list()
/datum/controller/subsystem/spacedrift/stat_entry()
..("P:[processing.len]")
/datum/controller/subsystem/spacedrift/fire(resumed = 0)
if (!resumed)
src.currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while (currentrun.len)
var/atom/movable/AM = currentrun[currentrun.len]
currentrun.len--
if (!AM)
processing -= AM
if (MC_TICK_CHECK)
return
continue
if (AM.inertia_next_move > world.time)
if (MC_TICK_CHECK)
return
continue
if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0))
AM.inertia_dir = 0
if (!AM.inertia_dir)
AM.inertia_last_loc = null
processing -= AM
if (MC_TICK_CHECK)
return
continue
var/old_dir = AM.dir
var/old_loc = AM.loc
AM.inertia_moving = TRUE
step(AM, AM.inertia_dir)
AM.inertia_moving = FALSE
AM.inertia_next_move = world.time + AM.inertia_move_delay
if (AM.loc == old_loc)
AM.inertia_dir = 0
AM.setDir(old_dir)
AM.inertia_last_loc = AM.loc
if (MC_TICK_CHECK)
return
SUBSYSTEM_DEF(spacedrift)
name = "Space Drift"
priority = FIRE_PRIORITY_SPACEDRIFT
wait = 5
flags = SS_NO_INIT|SS_KEEP_TIMING
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
var/list/currentrun = list()
var/list/processing = list()
/datum/controller/subsystem/spacedrift/stat_entry()
..("P:[processing.len]")
/datum/controller/subsystem/spacedrift/fire(resumed = 0)
if (!resumed)
src.currentrun = processing.Copy()
//cache for sanic speed (lists are references anyways)
var/list/currentrun = src.currentrun
while (currentrun.len)
var/atom/movable/AM = currentrun[currentrun.len]
currentrun.len--
if (!AM)
processing -= AM
if (MC_TICK_CHECK)
return
continue
if (AM.inertia_next_move > world.time)
if (MC_TICK_CHECK)
return
continue
if (!AM.loc || AM.loc != AM.inertia_last_loc || AM.Process_Spacemove(0))
AM.inertia_dir = 0
if (!AM.inertia_dir)
AM.inertia_last_loc = null
processing -= AM
if (MC_TICK_CHECK)
return
continue
var/old_dir = AM.dir
var/old_loc = AM.loc
AM.inertia_moving = TRUE
step(AM, AM.inertia_dir)
AM.inertia_moving = FALSE
AM.inertia_next_move = world.time + AM.inertia_move_delay
if (AM.loc == old_loc)
AM.inertia_dir = 0
AM.setDir(old_dir)
AM.inertia_last_loc = AM.loc
if (MC_TICK_CHECK)
return
+381 -381
View File
@@ -1,381 +1,381 @@
SUBSYSTEM_DEF(vote)
name = "Vote"
wait = 10
flags = SS_KEEP_TIMING|SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/initiator = null
var/started_time = null
var/time_remaining = 0
var/mode = null
var/question = null
var/list/choices = list()
var/list/voted = list()
var/list/voting = list()
var/list/generated_actions = list()
/datum/controller/subsystem/vote/fire() //called by master_controller
if(mode)
time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
if(time_remaining < 0)
result()
for(var/client/C in voting)
C << browse(null, "window=vote;can_close=0")
reset()
else
var/datum/browser/client_popup
for(var/client/C in voting)
client_popup = new(C, "vote", "Voting Panel")
client_popup.set_window_options("can_close=0")
client_popup.set_content(interface(C))
client_popup.open(FALSE)
/datum/controller/subsystem/vote/proc/reset()
initiator = null
time_remaining = 0
mode = null
question = null
choices.Cut()
voted.Cut()
voting.Cut()
remove_action_buttons()
/datum/controller/subsystem/vote/proc/get_result()
//get the highest number of votes
var/greatest_votes = 0
var/total_votes = 0
for(var/option in choices)
var/votes = choices[option]
total_votes += votes
if(votes > greatest_votes)
greatest_votes = votes
//default-vote for everyone who didn't vote
if(!CONFIG_GET(flag/default_no_vote) && choices.len)
var/list/non_voters = GLOB.directory.Copy()
non_voters -= voted
for (var/non_voter_ckey in non_voters)
var/client/C = non_voters[non_voter_ckey]
if (!C || C.is_afk())
non_voters -= non_voter_ckey
if(non_voters.len > 0)
if(mode == "restart")
choices["Continue Playing"] += non_voters.len
if(choices["Continue Playing"] >= greatest_votes)
greatest_votes = choices["Continue Playing"]
else if(mode == "gamemode")
if(GLOB.master_mode in choices)
choices[GLOB.master_mode] += non_voters.len
if(choices[GLOB.master_mode] >= greatest_votes)
greatest_votes = choices[GLOB.master_mode]
else if(mode == "map")
for (var/non_voter_ckey in non_voters)
var/client/C = non_voters[non_voter_ckey]
if(C.prefs.preferred_map)
if(choices[C.prefs.preferred_map]) //No votes if the map isn't in the vote.
var/preferred_map = C.prefs.preferred_map
choices[preferred_map] += 1
greatest_votes = max(greatest_votes, choices[preferred_map])
else if(config.defaultmap)
if(choices[config.defaultmap]) //No votes if the map isn't in the vote.
var/default_map = config.defaultmap.map_name
choices[default_map] += 1
greatest_votes = max(greatest_votes, choices[default_map])
//get all options with that many votes and return them in a list
. = list()
if(greatest_votes)
for(var/option in choices)
if(choices[option] == greatest_votes)
. += option
return .
/datum/controller/subsystem/vote/proc/announce_result()
var/list/winners = get_result()
var/text
if(winners.len > 0)
if(question)
text += "<b>[question]</b>"
else
text += "<b>[capitalize(mode)] Vote</b>"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
votes = 0
text += "\n<b>[choices[i]]:</b> [votes]"
if(mode != "custom")
if(winners.len > 1)
text = "\n<b>Vote Tied Between:</b>"
for(var/option in winners)
text += "\n\t[option]"
. = pick(winners)
text += "\n<b>Vote Result: [.]</b>"
else
text += "\n<b>Did not vote:</b> [GLOB.clients.len-voted.len]"
else
text += "<b>Vote Result: Inconclusive - No Votes!</b>"
log_vote(text)
remove_action_buttons()
to_chat(world, "\n<font color='purple'>[text]</font>")
return .
/datum/controller/subsystem/vote/proc/result()
. = announce_result()
var/restart = FALSE
if(.)
switch(mode)
if("restart")
if(. == "Restart Round")
restart = TRUE
if("gamemode")
if(GLOB.master_mode != .)
SSticker.save_mode(.)
if(SSticker.HasRoundStarted())
restart = TRUE
else
GLOB.master_mode = .
if("map")
SSmapping.changemap(global.config.maplist[.])
SSmapping.map_voted = TRUE
if(restart)
var/active_admins = FALSE
for(var/client/C in GLOB.admins)
if(!C.is_afk() && check_rights_for(C, R_SERVER))
active_admins = TRUE
break
if(!active_admins)
SSticker.Reboot("Restart vote successful.", "restart vote", 1) //no delay in case the restart is due to lag
else
to_chat(world, "<span style='boldannounce'>Notice:Restart vote will not restart the server automatically because there are active admins on.</span>")
message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.")
return .
/datum/controller/subsystem/vote/proc/submit_vote(vote)
if(mode)
if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder)
return FALSE
if(!(usr.ckey in voted))
if(vote && 1<=vote && vote<=choices.len)
voted += usr.ckey
choices[choices[vote]]++ //check this
return vote
return FALSE
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key)
if(!Master.current_runlevel) //Server is still intializing.
to_chat(usr, "<span class='warning'>Cannot start vote, server is not done initializing.</span>")
return FALSE
var/admin = FALSE
var/ckey = ckey(initiator_key)
if(GLOB.admin_datums[ckey])
admin = TRUE
if(!mode)
if(started_time)
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
if(mode)
to_chat(usr, "<span class='warning'>There is already a vote in progress! please wait for it to finish.</span>")
return FALSE
if(next_allowed_time > world.time && !admin)
to_chat(usr, "<span class='warning'>A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!</span>")
return FALSE
reset()
switch(vote_type)
if("restart")
choices.Add("Restart Round","Continue Playing")
if("gamemode")
choices.Add(config.votable_modes)
if("map")
if(!admin && SSmapping.map_voted)
to_chat(usr, "<span class='warning'>The next map has already been selected.</span>")
return FALSE
for(var/map in config.maplist)
var/datum/map_config/VM = config.maplist[map]
if(!VM.votable || (VM.map_name in SSpersistence.blocked_maps))
continue
choices.Add(VM.map_name)
if("custom")
question = stripped_input(usr,"What is the vote for?")
if(!question)
return FALSE
for(var/i=1,i<=10,i++)
var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish"))
if(!option || mode || !usr.client)
break
choices.Add(option)
else
return FALSE
mode = vote_type
initiator = initiator_key
started_time = world.time
var/text = "[capitalize(mode)] vote started by [initiator]."
if(mode == "custom")
text += "\n[question]"
log_vote(text)
var/vp = CONFIG_GET(number/vote_period)
to_chat(world, "\n<font color='purple'><b>[text]</b>\nType <b>vote</b> or click <a href='?src=[REF(src)]'>here</a> to place your votes.\nYou have [DisplayTimeText(vp)] to vote.</font>")
time_remaining = round(vp/10)
for(var/c in GLOB.clients)
var/client/C = c
var/datum/action/vote/V = new
if(question)
V.name = "Vote: [question]"
C.player_details.player_actions += V
V.Grant(C.mob)
generated_actions += V
if(C.prefs.toggles & SOUND_ANNOUNCEMENTS)
SEND_SOUND(C, sound('sound/misc/bloop.ogg'))
return TRUE
return FALSE
/datum/controller/subsystem/vote/proc/interface(client/C)
if(!C)
return
var/admin = FALSE
var/trialmin = FALSE
if(C.holder)
admin = TRUE
if(check_rights_for(C, R_ADMIN))
trialmin = TRUE
voting |= C
if(mode)
if(question)
. += "<h2>Vote: '[question]'</h2>"
else
. += "<h2>Vote: [capitalize(mode)]</h2>"
. += "Time Left: [time_remaining] s<hr><ul>"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
votes = 0
. += "<li><a href='?src=[REF(src)];vote=[i]'>[choices[i]]</a> ([votes] votes)</li>"
. += "</ul><hr>"
if(admin)
. += "(<a href='?src=[REF(src)];vote=cancel'>Cancel Vote</a>) "
else
. += "<h2>Start a vote:</h2><hr><ul><li>"
//restart
var/avr = CONFIG_GET(flag/allow_vote_restart)
if(trialmin || avr)
. += "<a href='?src=[REF(src)];vote=restart'>Restart</a>"
else
. += "<font color='grey'>Restart (Disallowed)</font>"
if(trialmin)
. += "\t(<a href='?src=[REF(src)];vote=toggle_restart'>[avr ? "Allowed" : "Disallowed"]</a>)"
. += "</li><li>"
//gamemode
var/avm = CONFIG_GET(flag/allow_vote_mode)
if(trialmin || avm)
. += "<a href='?src=[REF(src)];vote=gamemode'>GameMode</a>"
else
. += "<font color='grey'>GameMode (Disallowed)</font>"
if(trialmin)
. += "\t(<a href='?src=[REF(src)];vote=toggle_gamemode'>[avm ? "Allowed" : "Disallowed"]</a>)"
. += "</li>"
//map
var/avmap = CONFIG_GET(flag/allow_vote_map)
if(trialmin || avmap)
. += "<a href='?src=[REF(src)];vote=map'>Map</a>"
else
. += "<font color='grey'>Map (Disallowed)</font>"
if(trialmin)
. += "\t(<a href='?src=[REF(src)];vote=toggle_map'>[avmap ? "Allowed" : "Disallowed"]</a>)"
. += "</li>"
//custom
if(trialmin)
. += "<li><a href='?src=[REF(src)];vote=custom'>Custom</a></li>"
. += "</ul><hr>"
. += "<a href='?src=[REF(src)];vote=close' style='position:absolute;right:50px'>Close</a>"
return .
/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc)
if(!usr || !usr.client)
return //not necessary but meh...just in-case somebody does something stupid
var/trialmin = FALSE
if(usr.client.holder)
if(check_rights_for(usr.client, R_ADMIN))
trialmin = TRUE
switch(href_list["vote"])
if("close")
voting -= usr.client
usr << browse(null, "window=vote")
return
if("cancel")
if(usr.client.holder)
reset()
if("toggle_restart")
if(usr.client.holder && trialmin)
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
if("toggle_gamemode")
if(usr.client.holder && trialmin)
CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode))
if("toggle_map")
if(usr.client.holder && trialmin)
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
if("restart")
if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder)
initiate_vote("restart",usr.key)
if("gamemode")
if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder)
initiate_vote("gamemode",usr.key)
if("map")
if(CONFIG_GET(flag/allow_vote_map) || usr.client.holder)
initiate_vote("map",usr.key)
if("custom")
if(usr.client.holder)
initiate_vote("custom",usr.key)
else
submit_vote(round(text2num(href_list["vote"])))
usr.vote()
/datum/controller/subsystem/vote/proc/remove_action_buttons()
for(var/v in generated_actions)
var/datum/action/vote/V = v
if(!QDELETED(V))
V.remove_from_client()
V.Remove(V.owner)
generated_actions = list()
/mob/verb/vote()
set category = "OOC"
set name = "Vote"
var/datum/browser/popup = new(src, "vote", "Voting Panel")
popup.set_window_options("can_close=0")
popup.set_content(SSvote.interface(client))
popup.open(FALSE)
/datum/action/vote
name = "Vote!"
button_icon_state = "vote"
/datum/action/vote/Trigger()
if(owner)
owner.vote()
remove_from_client()
Remove(owner)
/datum/action/vote/IsAvailable()
return TRUE
/datum/action/vote/proc/remove_from_client()
if(!owner)
return
if(owner.client)
owner.client.player_details.player_actions -= src
else if(owner.ckey)
var/datum/player_details/P = GLOB.player_details[owner.ckey]
if(P)
P.player_actions -= src
SUBSYSTEM_DEF(vote)
name = "Vote"
wait = 10
flags = SS_KEEP_TIMING|SS_NO_INIT
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
var/initiator = null
var/started_time = null
var/time_remaining = 0
var/mode = null
var/question = null
var/list/choices = list()
var/list/voted = list()
var/list/voting = list()
var/list/generated_actions = list()
/datum/controller/subsystem/vote/fire() //called by master_controller
if(mode)
time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
if(time_remaining < 0)
result()
for(var/client/C in voting)
C << browse(null, "window=vote;can_close=0")
reset()
else
var/datum/browser/client_popup
for(var/client/C in voting)
client_popup = new(C, "vote", "Voting Panel")
client_popup.set_window_options("can_close=0")
client_popup.set_content(interface(C))
client_popup.open(FALSE)
/datum/controller/subsystem/vote/proc/reset()
initiator = null
time_remaining = 0
mode = null
question = null
choices.Cut()
voted.Cut()
voting.Cut()
remove_action_buttons()
/datum/controller/subsystem/vote/proc/get_result()
//get the highest number of votes
var/greatest_votes = 0
var/total_votes = 0
for(var/option in choices)
var/votes = choices[option]
total_votes += votes
if(votes > greatest_votes)
greatest_votes = votes
//default-vote for everyone who didn't vote
if(!CONFIG_GET(flag/default_no_vote) && choices.len)
var/list/non_voters = GLOB.directory.Copy()
non_voters -= voted
for (var/non_voter_ckey in non_voters)
var/client/C = non_voters[non_voter_ckey]
if (!C || C.is_afk())
non_voters -= non_voter_ckey
if(non_voters.len > 0)
if(mode == "restart")
choices["Continue Playing"] += non_voters.len
if(choices["Continue Playing"] >= greatest_votes)
greatest_votes = choices["Continue Playing"]
else if(mode == "gamemode")
if(GLOB.master_mode in choices)
choices[GLOB.master_mode] += non_voters.len
if(choices[GLOB.master_mode] >= greatest_votes)
greatest_votes = choices[GLOB.master_mode]
else if(mode == "map")
for (var/non_voter_ckey in non_voters)
var/client/C = non_voters[non_voter_ckey]
if(C.prefs.preferred_map)
if(choices[C.prefs.preferred_map]) //No votes if the map isn't in the vote.
var/preferred_map = C.prefs.preferred_map
choices[preferred_map] += 1
greatest_votes = max(greatest_votes, choices[preferred_map])
else if(config.defaultmap)
if(choices[config.defaultmap]) //No votes if the map isn't in the vote.
var/default_map = config.defaultmap.map_name
choices[default_map] += 1
greatest_votes = max(greatest_votes, choices[default_map])
//get all options with that many votes and return them in a list
. = list()
if(greatest_votes)
for(var/option in choices)
if(choices[option] == greatest_votes)
. += option
return .
/datum/controller/subsystem/vote/proc/announce_result()
var/list/winners = get_result()
var/text
if(winners.len > 0)
if(question)
text += "<b>[question]</b>"
else
text += "<b>[capitalize(mode)] Vote</b>"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
votes = 0
text += "\n<b>[choices[i]]:</b> [votes]"
if(mode != "custom")
if(winners.len > 1)
text = "\n<b>Vote Tied Between:</b>"
for(var/option in winners)
text += "\n\t[option]"
. = pick(winners)
text += "\n<b>Vote Result: [.]</b>"
else
text += "\n<b>Did not vote:</b> [GLOB.clients.len-voted.len]"
else
text += "<b>Vote Result: Inconclusive - No Votes!</b>"
log_vote(text)
remove_action_buttons()
to_chat(world, "\n<font color='purple'>[text]</font>")
return .
/datum/controller/subsystem/vote/proc/result()
. = announce_result()
var/restart = FALSE
if(.)
switch(mode)
if("restart")
if(. == "Restart Round")
restart = TRUE
if("gamemode")
if(GLOB.master_mode != .)
SSticker.save_mode(.)
if(SSticker.HasRoundStarted())
restart = TRUE
else
GLOB.master_mode = .
if("map")
SSmapping.changemap(global.config.maplist[.])
SSmapping.map_voted = TRUE
if(restart)
var/active_admins = FALSE
for(var/client/C in GLOB.admins)
if(!C.is_afk() && check_rights_for(C, R_SERVER))
active_admins = TRUE
break
if(!active_admins)
SSticker.Reboot("Restart vote successful.", "restart vote", 1) //no delay in case the restart is due to lag
else
to_chat(world, "<span style='boldannounce'>Notice:Restart vote will not restart the server automatically because there are active admins on.</span>")
message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.")
return .
/datum/controller/subsystem/vote/proc/submit_vote(vote)
if(mode)
if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder)
return FALSE
if(!(usr.ckey in voted))
if(vote && 1<=vote && vote<=choices.len)
voted += usr.ckey
choices[choices[vote]]++ //check this
return vote
return FALSE
/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key)
if(!Master.current_runlevel) //Server is still intializing.
to_chat(usr, "<span class='warning'>Cannot start vote, server is not done initializing.</span>")
return FALSE
var/admin = FALSE
var/ckey = ckey(initiator_key)
if(GLOB.admin_datums[ckey])
admin = TRUE
if(!mode)
if(started_time)
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
if(mode)
to_chat(usr, "<span class='warning'>There is already a vote in progress! please wait for it to finish.</span>")
return FALSE
if(next_allowed_time > world.time && !admin)
to_chat(usr, "<span class='warning'>A vote was initiated recently, you must wait [DisplayTimeText(next_allowed_time-world.time)] before a new vote can be started!</span>")
return FALSE
reset()
switch(vote_type)
if("restart")
choices.Add("Restart Round","Continue Playing")
if("gamemode")
choices.Add(config.votable_modes)
if("map")
if(!admin && SSmapping.map_voted)
to_chat(usr, "<span class='warning'>The next map has already been selected.</span>")
return FALSE
for(var/map in config.maplist)
var/datum/map_config/VM = config.maplist[map]
if(!VM.votable || (VM.map_name in SSpersistence.blocked_maps))
continue
choices.Add(VM.map_name)
if("custom")
question = stripped_input(usr,"What is the vote for?")
if(!question)
return FALSE
for(var/i=1,i<=10,i++)
var/option = capitalize(stripped_input(usr,"Please enter an option or hit cancel to finish"))
if(!option || mode || !usr.client)
break
choices.Add(option)
else
return FALSE
mode = vote_type
initiator = initiator_key
started_time = world.time
var/text = "[capitalize(mode)] vote started by [initiator]."
if(mode == "custom")
text += "\n[question]"
log_vote(text)
var/vp = CONFIG_GET(number/vote_period)
to_chat(world, "\n<font color='purple'><b>[text]</b>\nType <b>vote</b> or click <a href='?src=[REF(src)]'>here</a> to place your votes.\nYou have [DisplayTimeText(vp)] to vote.</font>")
time_remaining = round(vp/10)
for(var/c in GLOB.clients)
var/client/C = c
var/datum/action/vote/V = new
if(question)
V.name = "Vote: [question]"
C.player_details.player_actions += V
V.Grant(C.mob)
generated_actions += V
if(C.prefs.toggles & SOUND_ANNOUNCEMENTS)
SEND_SOUND(C, sound('sound/misc/bloop.ogg'))
return TRUE
return FALSE
/datum/controller/subsystem/vote/proc/interface(client/C)
if(!C)
return
var/admin = FALSE
var/trialmin = FALSE
if(C.holder)
admin = TRUE
if(check_rights_for(C, R_ADMIN))
trialmin = TRUE
voting |= C
if(mode)
if(question)
. += "<h2>Vote: '[question]'</h2>"
else
. += "<h2>Vote: [capitalize(mode)]</h2>"
. += "Time Left: [time_remaining] s<hr><ul>"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
votes = 0
. += "<li><a href='?src=[REF(src)];vote=[i]'>[choices[i]]</a> ([votes] votes)</li>"
. += "</ul><hr>"
if(admin)
. += "(<a href='?src=[REF(src)];vote=cancel'>Cancel Vote</a>) "
else
. += "<h2>Start a vote:</h2><hr><ul><li>"
//restart
var/avr = CONFIG_GET(flag/allow_vote_restart)
if(trialmin || avr)
. += "<a href='?src=[REF(src)];vote=restart'>Restart</a>"
else
. += "<font color='grey'>Restart (Disallowed)</font>"
if(trialmin)
. += "\t(<a href='?src=[REF(src)];vote=toggle_restart'>[avr ? "Allowed" : "Disallowed"]</a>)"
. += "</li><li>"
//gamemode
var/avm = CONFIG_GET(flag/allow_vote_mode)
if(trialmin || avm)
. += "<a href='?src=[REF(src)];vote=gamemode'>GameMode</a>"
else
. += "<font color='grey'>GameMode (Disallowed)</font>"
if(trialmin)
. += "\t(<a href='?src=[REF(src)];vote=toggle_gamemode'>[avm ? "Allowed" : "Disallowed"]</a>)"
. += "</li>"
//map
var/avmap = CONFIG_GET(flag/allow_vote_map)
if(trialmin || avmap)
. += "<a href='?src=[REF(src)];vote=map'>Map</a>"
else
. += "<font color='grey'>Map (Disallowed)</font>"
if(trialmin)
. += "\t(<a href='?src=[REF(src)];vote=toggle_map'>[avmap ? "Allowed" : "Disallowed"]</a>)"
. += "</li>"
//custom
if(trialmin)
. += "<li><a href='?src=[REF(src)];vote=custom'>Custom</a></li>"
. += "</ul><hr>"
. += "<a href='?src=[REF(src)];vote=close' style='position:absolute;right:50px'>Close</a>"
return .
/datum/controller/subsystem/vote/Topic(href,href_list[],hsrc)
if(!usr || !usr.client)
return //not necessary but meh...just in-case somebody does something stupid
var/trialmin = FALSE
if(usr.client.holder)
if(check_rights_for(usr.client, R_ADMIN))
trialmin = TRUE
switch(href_list["vote"])
if("close")
voting -= usr.client
usr << browse(null, "window=vote")
return
if("cancel")
if(usr.client.holder)
reset()
if("toggle_restart")
if(usr.client.holder && trialmin)
CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart))
if("toggle_gamemode")
if(usr.client.holder && trialmin)
CONFIG_SET(flag/allow_vote_mode, !CONFIG_GET(flag/allow_vote_mode))
if("toggle_map")
if(usr.client.holder && trialmin)
CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map))
if("restart")
if(CONFIG_GET(flag/allow_vote_restart) || usr.client.holder)
initiate_vote("restart",usr.key)
if("gamemode")
if(CONFIG_GET(flag/allow_vote_mode) || usr.client.holder)
initiate_vote("gamemode",usr.key)
if("map")
if(CONFIG_GET(flag/allow_vote_map) || usr.client.holder)
initiate_vote("map",usr.key)
if("custom")
if(usr.client.holder)
initiate_vote("custom",usr.key)
else
submit_vote(round(text2num(href_list["vote"])))
usr.vote()
/datum/controller/subsystem/vote/proc/remove_action_buttons()
for(var/v in generated_actions)
var/datum/action/vote/V = v
if(!QDELETED(V))
V.remove_from_client()
V.Remove(V.owner)
generated_actions = list()
/mob/verb/vote()
set category = "OOC"
set name = "Vote"
var/datum/browser/popup = new(src, "vote", "Voting Panel")
popup.set_window_options("can_close=0")
popup.set_content(SSvote.interface(client))
popup.open(FALSE)
/datum/action/vote
name = "Vote!"
button_icon_state = "vote"
/datum/action/vote/Trigger()
if(owner)
owner.vote()
remove_from_client()
Remove(owner)
/datum/action/vote/IsAvailable()
return TRUE
/datum/action/vote/proc/remove_from_client()
if(!owner)
return
if(owner.client)
owner.client.player_details.player_actions -= src
else if(owner.ckey)
var/datum/player_details/P = GLOB.player_details[owner.ckey]
if(P)
P.player_actions -= src