Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into tggenetics
This commit is contained in:
+154
-154
@@ -1,154 +1,154 @@
|
||||
#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
|
||||
|
||||
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
|
||||
|
||||
LAZYINITLIST(late_loaders)
|
||||
|
||||
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()
|
||||
|
||||
/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
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
LAZYINITLIST(late_loaders)
|
||||
|
||||
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()
|
||||
|
||||
/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
|
||||
|
||||
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)
|
||||
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,335 +1,335 @@
|
||||
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) //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", "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 = 0
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(M.client)
|
||||
playercount += 1
|
||||
var/admincount = GLOB.admins.len
|
||||
var/datum/DBQuery/query_record_playercount = SSdbcore.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) VALUES ([playercount], [admincount], '[SQLtime()]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[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("feedback")
|
||||
return FALSE
|
||||
if("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/sqlrowlist = list()
|
||||
|
||||
for (var/datum/feedback_variable/FV in feedback)
|
||||
var/sqlversion = 1
|
||||
if(FV.key in versions)
|
||||
sqlversion = versions[FV.key]
|
||||
sqlrowlist += list(list("datetime" = "Now()", "round_id" = GLOB.round_id, "key_name" = "'[sanitizeSQL(FV.key)]'", "key_type" = "'[FV.key_type]'", "version" = "[sqlversion]", "json" = "'[sanitizeSQL(json_encode(FV.json))]'"))
|
||||
|
||||
if (!length(sqlrowlist))
|
||||
return
|
||||
|
||||
SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE)
|
||||
|
||||
/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/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
|
||||
var/sqlname = L.real_name
|
||||
var/sqlkey = L.ckey
|
||||
var/sqljob = L.mind.assigned_role
|
||||
var/sqlspecial = L.mind.special_role
|
||||
var/sqlpod = get_area_name(L, TRUE)
|
||||
var/laname = L.lastattacker
|
||||
var/lakey = L.lastattackerckey
|
||||
var/sqlbrute = L.getBruteLoss()
|
||||
var/sqlfire = L.getFireLoss()
|
||||
var/sqlbrain = L.getOrganLoss(ORGAN_SLOT_BRAIN)
|
||||
var/sqloxy = L.getOxyLoss()
|
||||
var/sqltox = L.getToxLoss()
|
||||
var/sqlclone = L.getCloneLoss()
|
||||
var/sqlstamina = L.getStaminaLoss()
|
||||
var/x_coord = L.x
|
||||
var/y_coord = L.y
|
||||
var/z_coord = L.z
|
||||
var/last_words = L.last_words
|
||||
var/suicide = L.suiciding
|
||||
var/map = SSmapping.config.map_name
|
||||
|
||||
if(!SSdbcore.Connect())
|
||||
return
|
||||
|
||||
sqlname = sanitizeSQL(sqlname)
|
||||
sqlkey = sanitizeSQL(sqlkey)
|
||||
sqljob = sanitizeSQL(sqljob)
|
||||
sqlspecial = sanitizeSQL(sqlspecial)
|
||||
sqlpod = sanitizeSQL(sqlpod)
|
||||
laname = sanitizeSQL(laname)
|
||||
lakey = sanitizeSQL(lakey)
|
||||
sqlbrute = sanitizeSQL(sqlbrute)
|
||||
sqlfire = sanitizeSQL(sqlfire)
|
||||
sqlbrain = sanitizeSQL(sqlbrain)
|
||||
sqloxy = sanitizeSQL(sqloxy)
|
||||
sqltox = sanitizeSQL(sqltox)
|
||||
sqlclone = sanitizeSQL(sqlclone)
|
||||
sqlstamina = sanitizeSQL(sqlstamina)
|
||||
x_coord = sanitizeSQL(x_coord)
|
||||
y_coord = sanitizeSQL(y_coord)
|
||||
z_coord = sanitizeSQL(z_coord)
|
||||
last_words = sanitizeSQL(last_words)
|
||||
suicide = sanitizeSQL(suicide)
|
||||
map = sanitizeSQL(map)
|
||||
var/datum/DBQuery/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 ('[sqlpod]', '[x_coord]', '[y_coord]', '[z_coord]', '[map]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', [GLOB.round_id], '[SQLtime()]', '[sqljob]', '[sqlspecial]', '[sqlname]', '[sqlkey]', '[laname]', '[lakey]', [sqlbrute], [sqlfire], [sqlbrain], [sqloxy], [sqltox], [sqlclone], [sqlstamina], '[last_words]', [suicide])")
|
||||
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) //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", "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 = 0
|
||||
for(var/mob/M in GLOB.player_list)
|
||||
if(M.client)
|
||||
playercount += 1
|
||||
var/admincount = GLOB.admins.len
|
||||
var/datum/DBQuery/query_record_playercount = SSdbcore.NewQuery("INSERT INTO [format_table_name("legacy_population")] (playercount, admincount, time, server_ip, server_port, round_id) VALUES ([playercount], [admincount], '[SQLtime()]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', '[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("feedback")
|
||||
return FALSE
|
||||
if("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/sqlrowlist = list()
|
||||
|
||||
for (var/datum/feedback_variable/FV in feedback)
|
||||
var/sqlversion = 1
|
||||
if(FV.key in versions)
|
||||
sqlversion = versions[FV.key]
|
||||
sqlrowlist += list(list("datetime" = "Now()", "round_id" = GLOB.round_id, "key_name" = "'[sanitizeSQL(FV.key)]'", "key_type" = "'[FV.key_type]'", "version" = "[sqlversion]", "json" = "'[sanitizeSQL(json_encode(FV.json))]'"))
|
||||
|
||||
if (!length(sqlrowlist))
|
||||
return
|
||||
|
||||
SSdbcore.MassInsert(format_table_name("feedback"), sqlrowlist, ignore_errors = TRUE, delayed = TRUE)
|
||||
|
||||
/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/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
|
||||
var/sqlname = L.real_name
|
||||
var/sqlkey = L.ckey
|
||||
var/sqljob = L.mind.assigned_role
|
||||
var/sqlspecial = L.mind.special_role
|
||||
var/sqlpod = get_area_name(L, TRUE)
|
||||
var/laname = L.lastattacker
|
||||
var/lakey = L.lastattackerckey
|
||||
var/sqlbrute = L.getBruteLoss()
|
||||
var/sqlfire = L.getFireLoss()
|
||||
var/sqlbrain = L.getOrganLoss(ORGAN_SLOT_BRAIN)
|
||||
var/sqloxy = L.getOxyLoss()
|
||||
var/sqltox = L.getToxLoss()
|
||||
var/sqlclone = L.getCloneLoss()
|
||||
var/sqlstamina = L.getStaminaLoss()
|
||||
var/x_coord = L.x
|
||||
var/y_coord = L.y
|
||||
var/z_coord = L.z
|
||||
var/last_words = L.last_words
|
||||
var/suicide = L.suiciding
|
||||
var/map = SSmapping.config.map_name
|
||||
|
||||
if(!SSdbcore.Connect())
|
||||
return
|
||||
|
||||
sqlname = sanitizeSQL(sqlname)
|
||||
sqlkey = sanitizeSQL(sqlkey)
|
||||
sqljob = sanitizeSQL(sqljob)
|
||||
sqlspecial = sanitizeSQL(sqlspecial)
|
||||
sqlpod = sanitizeSQL(sqlpod)
|
||||
laname = sanitizeSQL(laname)
|
||||
lakey = sanitizeSQL(lakey)
|
||||
sqlbrute = sanitizeSQL(sqlbrute)
|
||||
sqlfire = sanitizeSQL(sqlfire)
|
||||
sqlbrain = sanitizeSQL(sqlbrain)
|
||||
sqloxy = sanitizeSQL(sqloxy)
|
||||
sqltox = sanitizeSQL(sqltox)
|
||||
sqlclone = sanitizeSQL(sqlclone)
|
||||
sqlstamina = sanitizeSQL(sqlstamina)
|
||||
x_coord = sanitizeSQL(x_coord)
|
||||
y_coord = sanitizeSQL(y_coord)
|
||||
z_coord = sanitizeSQL(z_coord)
|
||||
last_words = sanitizeSQL(last_words)
|
||||
suicide = sanitizeSQL(suicide)
|
||||
map = sanitizeSQL(map)
|
||||
var/datum/DBQuery/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 ('[sqlpod]', '[x_coord]', '[y_coord]', '[z_coord]', '[map]', INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]', [GLOB.round_id], '[SQLtime()]', '[sqljob]', '[sqlspecial]', '[sqlname]', '[sqlkey]', '[laname]', '[lakey]', [sqlbrute], [sqlfire], [sqlbrain], [sqloxy], [sqltox], [sqlclone], [sqlstamina], '[last_words]', [suicide])")
|
||||
if(query_report_death)
|
||||
query_report_death.Execute(async = TRUE)
|
||||
qdel(query_report_death)
|
||||
|
||||
@@ -1,365 +1,365 @@
|
||||
SUBSYSTEM_DEF(dbcore)
|
||||
name = "Database"
|
||||
flags = SS_BACKGROUND
|
||||
wait = 1 MINUTES
|
||||
init_order = INIT_ORDER_DBCORE
|
||||
var/const/FAILED_DB_CONNECTION_CUTOFF = 5
|
||||
|
||||
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/datum/BSQL_Connection/connection
|
||||
var/datum/BSQL_Operation/connectOperation
|
||||
|
||||
/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/DBQuery/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
|
||||
connectOperation = SSdbcore.connectOperation
|
||||
|
||||
/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/DBQuery/query_round_shutdown = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = '[sanitizeSQL(SSticker.end_state)]' WHERE id = [GLOB.round_id]")
|
||||
query_round_shutdown.Execute()
|
||||
qdel(query_round_shutdown)
|
||||
if(IsConnected())
|
||||
Disconnect()
|
||||
world.BSQL_Shutdown()
|
||||
|
||||
//nu
|
||||
/datum/controller/subsystem/dbcore/can_vv_get(var_name)
|
||||
return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && var_name != NAMEOF(src, connectOperation) && ..()
|
||||
|
||||
/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
|
||||
if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/Connect()
|
||||
if(IsConnected())
|
||||
return TRUE
|
||||
|
||||
if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore.
|
||||
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)
|
||||
|
||||
connection = new /datum/BSQL_Connection(BSQL_CONNECTION_TYPE_MARIADB, CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout), CONFIG_GET(number/bsql_thread_limit))
|
||||
var/error
|
||||
if(QDELETED(connection))
|
||||
connection = null
|
||||
error = last_error
|
||||
else
|
||||
SSdbcore.last_error = null
|
||||
connectOperation = connection.BeginConnect(address, port, user, pass, db)
|
||||
if(SSdbcore.last_error)
|
||||
CRASH(SSdbcore.last_error)
|
||||
UNTIL(connectOperation.IsComplete())
|
||||
error = connectOperation.GetError()
|
||||
. = !error
|
||||
if (!.)
|
||||
last_error = error
|
||||
log_sql("Connect() failed | [error]")
|
||||
++failed_connections
|
||||
QDEL_NULL(connection)
|
||||
QDEL_NULL(connectOperation)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
|
||||
if(CONFIG_GET(flag/sql_enabled))
|
||||
if(Connect())
|
||||
log_world("Database connection established.")
|
||||
var/datum/DBQuery/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/DBQuery/query_round_initialize = SSdbcore.NewQuery("INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]')")
|
||||
query_round_initialize.Execute()
|
||||
qdel(query_round_initialize)
|
||||
var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()")
|
||||
query_round_last_id.Execute()
|
||||
if(query_round_last_id.NextRow())
|
||||
GLOB.round_id = query_round_last_id.item[1]
|
||||
qdel(query_round_last_id)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/SetRoundStart()
|
||||
if(!Connect())
|
||||
return
|
||||
var/datum/DBQuery/query_round_start = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = [GLOB.round_id]")
|
||||
query_round_start.Execute()
|
||||
qdel(query_round_start)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/SetRoundEnd()
|
||||
if(!Connect())
|
||||
return
|
||||
var/sql_station_name = sanitizeSQL(station_name())
|
||||
var/datum/DBQuery/query_round_end = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = '[sanitizeSQL(SSticker.mode_result)]', station_name = '[sql_station_name]' WHERE id = [GLOB.round_id]")
|
||||
query_round_end.Execute()
|
||||
qdel(query_round_end)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/Disconnect()
|
||||
failed_connections = 0
|
||||
QDEL_NULL(connectOperation)
|
||||
QDEL_NULL(connection)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/IsConnected()
|
||||
if(!CONFIG_GET(flag/sql_enabled))
|
||||
return FALSE
|
||||
//block until any connect operations finish
|
||||
var/datum/BSQL_Connection/_connection = connection
|
||||
var/datum/BSQL_Operation/op = connectOperation
|
||||
UNTIL(QDELETED(_connection) || op.IsComplete())
|
||||
return !QDELETED(connection) && !op.GetError()
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/Quote(str)
|
||||
if(connection)
|
||||
return connection.Quote(str)
|
||||
|
||||
/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)
|
||||
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/DBQuery(sql_query, connection)
|
||||
|
||||
/*
|
||||
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)
|
||||
if (!table || !rows || !istype(rows))
|
||||
return
|
||||
var/list/columns = list()
|
||||
var/list/sorted_rows = list()
|
||||
|
||||
for (var/list/row in rows)
|
||||
var/list/sorted_row = list()
|
||||
sorted_row.len = columns.len
|
||||
for (var/column in row)
|
||||
var/idx = columns[column]
|
||||
if (!idx)
|
||||
idx = columns.len + 1
|
||||
columns[column] = idx
|
||||
sorted_row.len = columns.len
|
||||
|
||||
sorted_row[idx] = row[column]
|
||||
sorted_rows[++sorted_rows.len] = sorted_row
|
||||
|
||||
if (duplicate_key == TRUE)
|
||||
var/list/column_list = list()
|
||||
for (var/column in columns)
|
||||
column_list += "[column] = VALUES([column])"
|
||||
duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n"
|
||||
else if (duplicate_key == FALSE)
|
||||
duplicate_key = null
|
||||
|
||||
if (ignore_errors)
|
||||
ignore_errors = " IGNORE"
|
||||
else
|
||||
ignore_errors = null
|
||||
|
||||
if (delayed)
|
||||
delayed = " DELAYED"
|
||||
else
|
||||
delayed = null
|
||||
|
||||
var/list/sqlrowlist = list()
|
||||
var/len = columns.len
|
||||
for (var/list/row in sorted_rows)
|
||||
if (length(row) != len)
|
||||
row.len = len
|
||||
for (var/value in row)
|
||||
if (value == null)
|
||||
value = "NULL"
|
||||
sqlrowlist += "([row.Join(", ")])"
|
||||
|
||||
sqlrowlist = " [sqlrowlist.Join(",\n ")]"
|
||||
var/datum/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]")
|
||||
if (warn)
|
||||
. = Query.warn_execute(async)
|
||||
else
|
||||
. = Query.Execute(async)
|
||||
qdel(Query)
|
||||
|
||||
/datum/DBQuery
|
||||
var/sql // The sql query being executed.
|
||||
var/list/item //list of data values populated by NextRow()
|
||||
|
||||
var/last_activity
|
||||
var/last_activity_time
|
||||
|
||||
var/last_error
|
||||
var/skip_next_is_complete
|
||||
var/in_progress
|
||||
var/datum/BSQL_Connection/connection
|
||||
var/datum/BSQL_Operation/Query/query
|
||||
|
||||
/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection)
|
||||
SSdbcore.active_queries[src] = TRUE
|
||||
Activity("Created")
|
||||
item = list()
|
||||
src.connection = connection
|
||||
sql = sql_query
|
||||
|
||||
/datum/DBQuery/Destroy()
|
||||
Close()
|
||||
SSdbcore.active_queries -= src
|
||||
return ..()
|
||||
|
||||
/datum/DBQuery/CanProcCall(proc_name)
|
||||
//fuck off kevinz
|
||||
return FALSE
|
||||
|
||||
/datum/DBQuery/proc/SetQuery(new_sql)
|
||||
if(in_progress)
|
||||
CRASH("Attempted to set new sql while waiting on active query")
|
||||
Close()
|
||||
sql = new_sql
|
||||
|
||||
/datum/DBQuery/proc/Activity(activity)
|
||||
last_activity = activity
|
||||
last_activity_time = world.time
|
||||
|
||||
/datum/DBQuery/proc/warn_execute(async = FALSE)
|
||||
. = Execute(async)
|
||||
if(!.)
|
||||
to_chat(usr, "<span class='danger'>A SQL error occurred during this operation, check the server logs.</span>")
|
||||
|
||||
/datum/DBQuery/proc/Execute(async = FALSE, log_error = TRUE)
|
||||
Activity("Execute")
|
||||
if(in_progress)
|
||||
CRASH("Attempted to start a new query while waiting on the old one")
|
||||
|
||||
if(QDELETED(connection))
|
||||
last_error = "No connection!"
|
||||
return FALSE
|
||||
|
||||
var/start_time
|
||||
var/timed_out
|
||||
if(!async)
|
||||
start_time = REALTIMEOFDAY
|
||||
Close()
|
||||
query = connection.BeginQuery(sql)
|
||||
if(!async)
|
||||
timed_out = !query.WaitForCompletion()
|
||||
else
|
||||
in_progress = TRUE
|
||||
UNTIL(query.IsComplete())
|
||||
in_progress = FALSE
|
||||
skip_next_is_complete = TRUE
|
||||
var/error = QDELETED(query) ? "Query object deleted!" : query.GetError()
|
||||
last_error = error
|
||||
. = !error
|
||||
if(!. && log_error)
|
||||
log_sql("[error] | Query used: [sql]")
|
||||
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/DBQuery/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/DBQuery/proc/NextRow(async)
|
||||
Activity("NextRow")
|
||||
UNTIL(!in_progress)
|
||||
if(!skip_next_is_complete)
|
||||
if(!async)
|
||||
query.WaitForCompletion()
|
||||
else
|
||||
in_progress = TRUE
|
||||
UNTIL(query.IsComplete())
|
||||
in_progress = FALSE
|
||||
else
|
||||
skip_next_is_complete = FALSE
|
||||
|
||||
last_error = query.GetError()
|
||||
var/list/results = query.CurrentRow()
|
||||
. = results != null
|
||||
|
||||
item.Cut()
|
||||
//populate item array
|
||||
for(var/I in results)
|
||||
item += results[I]
|
||||
|
||||
/datum/DBQuery/proc/ErrorMsg()
|
||||
return last_error
|
||||
|
||||
/datum/DBQuery/proc/Close()
|
||||
item.Cut()
|
||||
QDEL_NULL(query)
|
||||
|
||||
/world/BSQL_Debug(message)
|
||||
if(!CONFIG_GET(flag/bsql_debug))
|
||||
return
|
||||
|
||||
//strip sensitive stuff
|
||||
if(findtext(message, ": CreateConnection("))
|
||||
message = "CreateConnection CENSORED"
|
||||
|
||||
log_sql("BSQL_DEBUG: [message]")
|
||||
SUBSYSTEM_DEF(dbcore)
|
||||
name = "Database"
|
||||
flags = SS_BACKGROUND
|
||||
wait = 1 MINUTES
|
||||
init_order = INIT_ORDER_DBCORE
|
||||
var/const/FAILED_DB_CONNECTION_CUTOFF = 5
|
||||
|
||||
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/datum/BSQL_Connection/connection
|
||||
var/datum/BSQL_Operation/connectOperation
|
||||
|
||||
/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/DBQuery/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
|
||||
connectOperation = SSdbcore.connectOperation
|
||||
|
||||
/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/DBQuery/query_round_shutdown = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET shutdown_datetime = Now(), end_state = '[sanitizeSQL(SSticker.end_state)]' WHERE id = [GLOB.round_id]")
|
||||
query_round_shutdown.Execute()
|
||||
qdel(query_round_shutdown)
|
||||
if(IsConnected())
|
||||
Disconnect()
|
||||
world.BSQL_Shutdown()
|
||||
|
||||
//nu
|
||||
/datum/controller/subsystem/dbcore/can_vv_get(var_name)
|
||||
return var_name != NAMEOF(src, connection) && var_name != NAMEOF(src, active_queries) && var_name != NAMEOF(src, connectOperation) && ..()
|
||||
|
||||
/datum/controller/subsystem/dbcore/vv_edit_var(var_name, var_value)
|
||||
if(var_name == NAMEOF(src, connection) || var_name == NAMEOF(src, connectOperation))
|
||||
return FALSE
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/Connect()
|
||||
if(IsConnected())
|
||||
return TRUE
|
||||
|
||||
if(failed_connections > FAILED_DB_CONNECTION_CUTOFF) //If it failed to establish a connection more than 5 times in a row, don't bother attempting to connect anymore.
|
||||
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)
|
||||
|
||||
connection = new /datum/BSQL_Connection(BSQL_CONNECTION_TYPE_MARIADB, CONFIG_GET(number/async_query_timeout), CONFIG_GET(number/blocking_query_timeout), CONFIG_GET(number/bsql_thread_limit))
|
||||
var/error
|
||||
if(QDELETED(connection))
|
||||
connection = null
|
||||
error = last_error
|
||||
else
|
||||
SSdbcore.last_error = null
|
||||
connectOperation = connection.BeginConnect(address, port, user, pass, db)
|
||||
if(SSdbcore.last_error)
|
||||
CRASH(SSdbcore.last_error)
|
||||
UNTIL(connectOperation.IsComplete())
|
||||
error = connectOperation.GetError()
|
||||
. = !error
|
||||
if (!.)
|
||||
last_error = error
|
||||
log_sql("Connect() failed | [error]")
|
||||
++failed_connections
|
||||
QDEL_NULL(connection)
|
||||
QDEL_NULL(connectOperation)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/CheckSchemaVersion()
|
||||
if(CONFIG_GET(flag/sql_enabled))
|
||||
if(Connect())
|
||||
log_world("Database connection established.")
|
||||
var/datum/DBQuery/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/DBQuery/query_round_initialize = SSdbcore.NewQuery("INSERT INTO [format_table_name("round")] (initialize_datetime, server_ip, server_port) VALUES (Now(), INET_ATON(IF('[world.internet_address]' LIKE '', '0', '[world.internet_address]')), '[world.port]')")
|
||||
query_round_initialize.Execute()
|
||||
qdel(query_round_initialize)
|
||||
var/datum/DBQuery/query_round_last_id = SSdbcore.NewQuery("SELECT LAST_INSERT_ID()")
|
||||
query_round_last_id.Execute()
|
||||
if(query_round_last_id.NextRow())
|
||||
GLOB.round_id = query_round_last_id.item[1]
|
||||
qdel(query_round_last_id)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/SetRoundStart()
|
||||
if(!Connect())
|
||||
return
|
||||
var/datum/DBQuery/query_round_start = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET start_datetime = Now() WHERE id = [GLOB.round_id]")
|
||||
query_round_start.Execute()
|
||||
qdel(query_round_start)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/SetRoundEnd()
|
||||
if(!Connect())
|
||||
return
|
||||
var/sql_station_name = sanitizeSQL(station_name())
|
||||
var/datum/DBQuery/query_round_end = SSdbcore.NewQuery("UPDATE [format_table_name("round")] SET end_datetime = Now(), game_mode_result = '[sanitizeSQL(SSticker.mode_result)]', station_name = '[sql_station_name]' WHERE id = [GLOB.round_id]")
|
||||
query_round_end.Execute()
|
||||
qdel(query_round_end)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/Disconnect()
|
||||
failed_connections = 0
|
||||
QDEL_NULL(connectOperation)
|
||||
QDEL_NULL(connection)
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/IsConnected()
|
||||
if(!CONFIG_GET(flag/sql_enabled))
|
||||
return FALSE
|
||||
//block until any connect operations finish
|
||||
var/datum/BSQL_Connection/_connection = connection
|
||||
var/datum/BSQL_Operation/op = connectOperation
|
||||
UNTIL(QDELETED(_connection) || op.IsComplete())
|
||||
return !QDELETED(connection) && !op.GetError()
|
||||
|
||||
/datum/controller/subsystem/dbcore/proc/Quote(str)
|
||||
if(connection)
|
||||
return connection.Quote(str)
|
||||
|
||||
/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)
|
||||
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/DBQuery(sql_query, connection)
|
||||
|
||||
/*
|
||||
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)
|
||||
if (!table || !rows || !istype(rows))
|
||||
return
|
||||
var/list/columns = list()
|
||||
var/list/sorted_rows = list()
|
||||
|
||||
for (var/list/row in rows)
|
||||
var/list/sorted_row = list()
|
||||
sorted_row.len = columns.len
|
||||
for (var/column in row)
|
||||
var/idx = columns[column]
|
||||
if (!idx)
|
||||
idx = columns.len + 1
|
||||
columns[column] = idx
|
||||
sorted_row.len = columns.len
|
||||
|
||||
sorted_row[idx] = row[column]
|
||||
sorted_rows[++sorted_rows.len] = sorted_row
|
||||
|
||||
if (duplicate_key == TRUE)
|
||||
var/list/column_list = list()
|
||||
for (var/column in columns)
|
||||
column_list += "[column] = VALUES([column])"
|
||||
duplicate_key = "ON DUPLICATE KEY UPDATE [column_list.Join(", ")]\n"
|
||||
else if (duplicate_key == FALSE)
|
||||
duplicate_key = null
|
||||
|
||||
if (ignore_errors)
|
||||
ignore_errors = " IGNORE"
|
||||
else
|
||||
ignore_errors = null
|
||||
|
||||
if (delayed)
|
||||
delayed = " DELAYED"
|
||||
else
|
||||
delayed = null
|
||||
|
||||
var/list/sqlrowlist = list()
|
||||
var/len = columns.len
|
||||
for (var/list/row in sorted_rows)
|
||||
if (length(row) != len)
|
||||
row.len = len
|
||||
for (var/value in row)
|
||||
if (value == null)
|
||||
value = "NULL"
|
||||
sqlrowlist += "([row.Join(", ")])"
|
||||
|
||||
sqlrowlist = " [sqlrowlist.Join(",\n ")]"
|
||||
var/datum/DBQuery/Query = NewQuery("INSERT[delayed][ignore_errors] INTO [table]\n([columns.Join(", ")])\nVALUES\n[sqlrowlist]\n[duplicate_key]")
|
||||
if (warn)
|
||||
. = Query.warn_execute(async)
|
||||
else
|
||||
. = Query.Execute(async)
|
||||
qdel(Query)
|
||||
|
||||
/datum/DBQuery
|
||||
var/sql // The sql query being executed.
|
||||
var/list/item //list of data values populated by NextRow()
|
||||
|
||||
var/last_activity
|
||||
var/last_activity_time
|
||||
|
||||
var/last_error
|
||||
var/skip_next_is_complete
|
||||
var/in_progress
|
||||
var/datum/BSQL_Connection/connection
|
||||
var/datum/BSQL_Operation/Query/query
|
||||
|
||||
/datum/DBQuery/New(sql_query, datum/BSQL_Connection/connection)
|
||||
SSdbcore.active_queries[src] = TRUE
|
||||
Activity("Created")
|
||||
item = list()
|
||||
src.connection = connection
|
||||
sql = sql_query
|
||||
|
||||
/datum/DBQuery/Destroy()
|
||||
Close()
|
||||
SSdbcore.active_queries -= src
|
||||
return ..()
|
||||
|
||||
/datum/DBQuery/CanProcCall(proc_name)
|
||||
//fuck off kevinz
|
||||
return FALSE
|
||||
|
||||
/datum/DBQuery/proc/SetQuery(new_sql)
|
||||
if(in_progress)
|
||||
CRASH("Attempted to set new sql while waiting on active query")
|
||||
Close()
|
||||
sql = new_sql
|
||||
|
||||
/datum/DBQuery/proc/Activity(activity)
|
||||
last_activity = activity
|
||||
last_activity_time = world.time
|
||||
|
||||
/datum/DBQuery/proc/warn_execute(async = FALSE)
|
||||
. = Execute(async)
|
||||
if(!.)
|
||||
to_chat(usr, "<span class='danger'>A SQL error occurred during this operation, check the server logs.</span>")
|
||||
|
||||
/datum/DBQuery/proc/Execute(async = FALSE, log_error = TRUE)
|
||||
Activity("Execute")
|
||||
if(in_progress)
|
||||
CRASH("Attempted to start a new query while waiting on the old one")
|
||||
|
||||
if(QDELETED(connection))
|
||||
last_error = "No connection!"
|
||||
return FALSE
|
||||
|
||||
var/start_time
|
||||
var/timed_out
|
||||
if(!async)
|
||||
start_time = REALTIMEOFDAY
|
||||
Close()
|
||||
query = connection.BeginQuery(sql)
|
||||
if(!async)
|
||||
timed_out = !query.WaitForCompletion()
|
||||
else
|
||||
in_progress = TRUE
|
||||
UNTIL(query.IsComplete())
|
||||
in_progress = FALSE
|
||||
skip_next_is_complete = TRUE
|
||||
var/error = QDELETED(query) ? "Query object deleted!" : query.GetError()
|
||||
last_error = error
|
||||
. = !error
|
||||
if(!. && log_error)
|
||||
log_sql("[error] | Query used: [sql]")
|
||||
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/DBQuery/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/DBQuery/proc/NextRow(async)
|
||||
Activity("NextRow")
|
||||
UNTIL(!in_progress)
|
||||
if(!skip_next_is_complete)
|
||||
if(!async)
|
||||
query.WaitForCompletion()
|
||||
else
|
||||
in_progress = TRUE
|
||||
UNTIL(query.IsComplete())
|
||||
in_progress = FALSE
|
||||
else
|
||||
skip_next_is_complete = FALSE
|
||||
|
||||
last_error = query.GetError()
|
||||
var/list/results = query.CurrentRow()
|
||||
. = results != null
|
||||
|
||||
item.Cut()
|
||||
//populate item array
|
||||
for(var/I in results)
|
||||
item += results[I]
|
||||
|
||||
/datum/DBQuery/proc/ErrorMsg()
|
||||
return last_error
|
||||
|
||||
/datum/DBQuery/proc/Close()
|
||||
item.Cut()
|
||||
QDEL_NULL(query)
|
||||
|
||||
/world/BSQL_Debug(message)
|
||||
if(!CONFIG_GET(flag/bsql_debug))
|
||||
return
|
||||
|
||||
//strip sensitive stuff
|
||||
if(findtext(message, ": CreateConnection("))
|
||||
message = "CreateConnection CENSORED"
|
||||
|
||||
log_sql("BSQL_DEBUG: [message]")
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
SUBSYSTEM_DEF(medals)
|
||||
name = "Medals"
|
||||
flags = SS_NO_FIRE
|
||||
var/hub_enabled = FALSE
|
||||
|
||||
/datum/controller/subsystem/medals/Initialize(timeofday)
|
||||
if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password))
|
||||
hub_enabled = TRUE
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player)
|
||||
set waitfor = FALSE
|
||||
if(!medal || !hub_enabled)
|
||||
return
|
||||
if(isnull(world.SetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
hub_enabled = FALSE
|
||||
log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to award [medal] medal to [player.key]!")
|
||||
return
|
||||
to_chat(player, "<span class='greenannounce'><B>Achievement unlocked: [medal]!</B></span>")
|
||||
|
||||
|
||||
/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force)
|
||||
set waitfor = FALSE
|
||||
if(!score || !hub_enabled)
|
||||
return
|
||||
|
||||
var/list/oldscore = GetScore(score, player, TRUE)
|
||||
if(increment)
|
||||
if(!oldscore[score])
|
||||
oldscore[score] = 1
|
||||
else
|
||||
oldscore[score] = (text2num(oldscore[score]) + 1)
|
||||
else
|
||||
oldscore[score] = force
|
||||
|
||||
var/newscoreparam = list2params(oldscore)
|
||||
|
||||
if(isnull(world.SetScores(player.ckey, newscoreparam, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
hub_enabled = FALSE
|
||||
log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to set [score] score for [player.key]!")
|
||||
|
||||
/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist)
|
||||
if(!score || !hub_enabled)
|
||||
return
|
||||
|
||||
var/scoreget = world.GetScores(player.ckey, score, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
|
||||
if(isnull(scoreget))
|
||||
hub_enabled = FALSE
|
||||
log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to get score: [score] for [player.key]!")
|
||||
return
|
||||
. = params2list(scoreget)
|
||||
if(!returnlist)
|
||||
return .[score]
|
||||
|
||||
/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player)
|
||||
if(!medal || !hub_enabled)
|
||||
return
|
||||
|
||||
if(isnull(world.GetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
hub_enabled = FALSE
|
||||
log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player: [player.key]")
|
||||
message_admins("Error! Failed to contact hub to get [medal] medal for [player.key]!")
|
||||
return
|
||||
to_chat(player, "[medal] is unlocked")
|
||||
|
||||
/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player)
|
||||
if(!player || !medal || !hub_enabled)
|
||||
return
|
||||
var/result = world.ClearMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
|
||||
switch(result)
|
||||
if(null)
|
||||
hub_enabled = FALSE
|
||||
log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to clear [medal] medal for [player.key]!")
|
||||
if(TRUE)
|
||||
message_admins("Medal: [medal] removed for [player.key]")
|
||||
if(FALSE)
|
||||
message_admins("Medal: [medal] was not found for [player.key]. Unable to clear.")
|
||||
|
||||
|
||||
/datum/controller/subsystem/medals/proc/ClearScore(client/player)
|
||||
if(isnull(world.SetScores(player.ckey, "", CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.key]!")
|
||||
message_admins("Error! Failed to contact hub to clear scores for [player.key]!")
|
||||
SUBSYSTEM_DEF(medals)
|
||||
name = "Medals"
|
||||
flags = SS_NO_FIRE
|
||||
var/hub_enabled = FALSE
|
||||
|
||||
/datum/controller/subsystem/medals/Initialize(timeofday)
|
||||
if(CONFIG_GET(string/medal_hub_address) && CONFIG_GET(string/medal_hub_password))
|
||||
hub_enabled = TRUE
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/medals/proc/UnlockMedal(medal, client/player)
|
||||
set waitfor = FALSE
|
||||
if(!medal || !hub_enabled)
|
||||
return
|
||||
if(isnull(world.SetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
hub_enabled = FALSE
|
||||
log_game("MEDAL ERROR: Could not contact hub to award medal:[medal] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to award [medal] medal to [player.key]!")
|
||||
return
|
||||
to_chat(player, "<span class='greenannounce'><B>Achievement unlocked: [medal]!</B></span>")
|
||||
|
||||
|
||||
/datum/controller/subsystem/medals/proc/SetScore(score, client/player, increment, force)
|
||||
set waitfor = FALSE
|
||||
if(!score || !hub_enabled)
|
||||
return
|
||||
|
||||
var/list/oldscore = GetScore(score, player, TRUE)
|
||||
if(increment)
|
||||
if(!oldscore[score])
|
||||
oldscore[score] = 1
|
||||
else
|
||||
oldscore[score] = (text2num(oldscore[score]) + 1)
|
||||
else
|
||||
oldscore[score] = force
|
||||
|
||||
var/newscoreparam = list2params(oldscore)
|
||||
|
||||
if(isnull(world.SetScores(player.ckey, newscoreparam, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
hub_enabled = FALSE
|
||||
log_game("SCORE ERROR: Could not contact hub to set score. Score:[score] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to set [score] score for [player.key]!")
|
||||
|
||||
/datum/controller/subsystem/medals/proc/GetScore(score, client/player, returnlist)
|
||||
if(!score || !hub_enabled)
|
||||
return
|
||||
|
||||
var/scoreget = world.GetScores(player.ckey, score, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
|
||||
if(isnull(scoreget))
|
||||
hub_enabled = FALSE
|
||||
log_game("SCORE ERROR: Could not contact hub to get score. Score:[score] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to get score: [score] for [player.key]!")
|
||||
return
|
||||
. = params2list(scoreget)
|
||||
if(!returnlist)
|
||||
return .[score]
|
||||
|
||||
/datum/controller/subsystem/medals/proc/CheckMedal(medal, client/player)
|
||||
if(!medal || !hub_enabled)
|
||||
return
|
||||
|
||||
if(isnull(world.GetMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
hub_enabled = FALSE
|
||||
log_game("MEDAL ERROR: Could not contact hub to get medal:[medal] player: [player.key]")
|
||||
message_admins("Error! Failed to contact hub to get [medal] medal for [player.key]!")
|
||||
return
|
||||
to_chat(player, "[medal] is unlocked")
|
||||
|
||||
/datum/controller/subsystem/medals/proc/LockMedal(medal, client/player)
|
||||
if(!player || !medal || !hub_enabled)
|
||||
return
|
||||
var/result = world.ClearMedal(medal, player, CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))
|
||||
switch(result)
|
||||
if(null)
|
||||
hub_enabled = FALSE
|
||||
log_game("MEDAL ERROR: Could not contact hub to clear medal:[medal] player:[player.key]")
|
||||
message_admins("Error! Failed to contact hub to clear [medal] medal for [player.key]!")
|
||||
if(TRUE)
|
||||
message_admins("Medal: [medal] removed for [player.key]")
|
||||
if(FALSE)
|
||||
message_admins("Medal: [medal] was not found for [player.key]. Unable to clear.")
|
||||
|
||||
|
||||
/datum/controller/subsystem/medals/proc/ClearScore(client/player)
|
||||
if(isnull(world.SetScores(player.ckey, "", CONFIG_GET(string/medal_hub_address), CONFIG_GET(string/medal_hub_password))))
|
||||
log_game("MEDAL ERROR: Could not contact hub to clear scores for [player.key]!")
|
||||
message_admins("Error! Failed to contact hub to clear scores for [player.key]!")
|
||||
|
||||
@@ -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(FALSE)
|
||||
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(FALSE)
|
||||
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_KEEP_TIMING | SS_NO_INIT
|
||||
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_KEEP_TIMING | SS_NO_INIT
|
||||
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
|
||||
|
||||
@@ -8,7 +8,7 @@ SUBSYSTEM_DEF(pai)
|
||||
var/spam_delay = 100
|
||||
var/list/pai_card_list = list()
|
||||
|
||||
/datum/controller/subsystem/pai/Topic(href, href_list[])
|
||||
/datum/controller/subsystem/pai/Topic(href, href_list)
|
||||
if(href_list["download"])
|
||||
var/datum/paiCandidate/candidate = locate(href_list["candidate"]) in candidates
|
||||
var/obj/item/paicard/card = locate(href_list["device"]) in pai_card_list
|
||||
|
||||
@@ -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(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(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/item/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/item/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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
|
||||
+676
-676
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user