mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-27 07:08:00 +01:00
Force LF line endings with gitattributes and convert repo (#52266)
Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
This commit is contained in:
co-authored by
Aleksej Komarov
parent
39e8bd721f
commit
62676e72a8
@@ -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) && ..()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user