mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2025-12-19 14:51:27 +00:00
* last words * i can name files properly * Update SQL/paradise_schema.sql Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> * Update code/controllers/subsystem/SSblackbox.dm Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> * Update code/controllers/subsystem/SSblackbox.dm Co-authored-by: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com> * move to version 51 instead --------- Co-authored-by: AffectedArc07 <25063394+AffectedArc07@users.noreply.github.com> Co-authored-by: SteelSlayer <42044220+SteelSlayer@users.noreply.github.com>
336 lines
10 KiB
Plaintext
336 lines
10 KiB
Plaintext
// Dont touch this subsystem unless you ABSOLUTELY know what you are doing
|
|
|
|
SUBSYSTEM_DEF(blackbox)
|
|
name = "Blackbox"
|
|
// On Master.Shutdown(), it shuts down subsystems in the REVERSE order
|
|
// The database SS has INIT_ORDER_DBCORE=20, and this SS has INIT_ORDER_BLACKBOX=19
|
|
// So putting this ensures it shuts down in the right order
|
|
init_order = INIT_ORDER_BLACKBOX
|
|
wait = 10 MINUTES
|
|
runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT
|
|
offline_implications = "Player count and admin count statistics will no longer be logged to the database. No immediate action is needed."
|
|
cpu_display = SS_CPUDISPLAY_LOW
|
|
|
|
/// List of all recorded feedback
|
|
var/list/datum/feedback_variable/feedback = list()
|
|
/// Is it time to stop tracking stats?
|
|
var/sealed = FALSE
|
|
/// List of highest tech levels attained that isn't lost lost by destruction of RD computers
|
|
var/list/research_levels = list()
|
|
/// Associative list of any feedback variables that have had their format changed since creation and their current version, remember to update this
|
|
var/list/versions = list()
|
|
|
|
/datum/controller/subsystem/blackbox/Initialize()
|
|
if(!SSdbcore.IsConnected())
|
|
flags |= SS_NO_FIRE // Disable firing if SQL is disabled
|
|
|
|
/datum/controller/subsystem/blackbox/fire(resumed = 0)
|
|
sql_poll_players()
|
|
|
|
/datum/controller/subsystem/blackbox/proc/sql_poll_players()
|
|
var/datum/db_query/statquery = SSdbcore.NewQuery(
|
|
"INSERT INTO legacy_population (playercount, admincount, time, server_id) VALUES (:playercount, :admincount, NOW(), :server_id)",
|
|
list(
|
|
"playercount" = length(GLOB.clients),
|
|
"admincount" = length(GLOB.admins),
|
|
"server_id" = GLOB.configuration.system.instance_id
|
|
)
|
|
)
|
|
statquery.warn_execute()
|
|
qdel(statquery)
|
|
|
|
|
|
/datum/controller/subsystem/blackbox/Recover()
|
|
feedback = SSblackbox.feedback
|
|
sealed = SSblackbox.sealed
|
|
|
|
//no touchie
|
|
/datum/controller/subsystem/blackbox/can_vv_get(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 ..()
|
|
|
|
/**
|
|
* Shutdown Helper
|
|
*
|
|
* Dumps all feedback stats to the DB. Doesnt get much simpler than that.
|
|
*/
|
|
/datum/controller/subsystem/blackbox/Shutdown()
|
|
sealed = FALSE
|
|
for(var/obj/machinery/message_server/MS in GLOB.message_servers)
|
|
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")
|
|
|
|
if(length(research_levels))
|
|
record_feedback("associative", "high_research_level", 1, research_levels)
|
|
|
|
if(!SSdbcore.IsConnected())
|
|
return
|
|
|
|
var/list/datum/db_query/queries = list()
|
|
|
|
for(var/datum/feedback_variable/FV in feedback)
|
|
var/sqlversion = 1
|
|
if(FV.key in versions)
|
|
sqlversion = versions[FV.key]
|
|
|
|
var/datum/db_query/query_feedback_save = SSdbcore.NewQuery({"
|
|
INSERT IGNORE INTO feedback (datetime, round_id, key_name, key_type, version, json)
|
|
VALUES (NOW(), :rid, :keyname, :keytype, :version, :json)"}, list(
|
|
"rid" = text2num(GLOB.round_id),
|
|
"keyname" = FV.key,
|
|
"keytype" = FV.key_type,
|
|
"version" = text2num(sqlversion),
|
|
"json" = json_encode(FV.json)
|
|
))
|
|
queries += query_feedback_save
|
|
|
|
SSdbcore.MassExecute(queries, TRUE, TRUE)
|
|
|
|
/**
|
|
* Blackbox Sealer
|
|
*
|
|
* Seals the blackbox, preventing new data from being stored. This is to avoid data being bloated during end round grief
|
|
*/
|
|
/datum/controller/subsystem/blackbox/proc/Seal()
|
|
if(sealed)
|
|
return FALSE
|
|
log_game("Blackbox sealed")
|
|
sealed = TRUE
|
|
return TRUE
|
|
|
|
/**
|
|
* Research level broadcast logging helper
|
|
*
|
|
* This is called on R&D updates for a safe way of logging tech levels if an R&D console is destroyed
|
|
*
|
|
* Arguments:
|
|
* * tech - Research technology name
|
|
* * level - Research technology level
|
|
*/
|
|
/datum/controller/subsystem/blackbox/proc/log_research(tech, level)
|
|
if(!(tech in research_levels) || research_levels[tech] < level)
|
|
research_levels[tech] = level
|
|
|
|
|
|
/**
|
|
* Radio broadcast logging helper
|
|
*
|
|
* Called during [/proc/broadcast_message()] to log a message to the blackbox.
|
|
* Translates the specific frequency to a name
|
|
*
|
|
* Arguments:
|
|
* * freq - Frequency of the transmission
|
|
*/
|
|
/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq)
|
|
if(sealed)
|
|
return
|
|
switch(freq)
|
|
if(PUB_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "common")
|
|
if(SCI_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "science")
|
|
if(COMM_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "command")
|
|
if(MED_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "medical")
|
|
if(ENG_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "engineering")
|
|
if(SEC_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "security")
|
|
if(DTH_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "deathsquad")
|
|
if(SYND_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "syndicate")
|
|
if(SYNDTEAM_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "syndicate team")
|
|
if(SUP_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "supply")
|
|
if(SRV_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "service")
|
|
if(PROC_FREQ)
|
|
record_feedback("tally", "radio_usage", 1, "procedure")
|
|
else
|
|
record_feedback("tally", "radio_usage", 1, "other")
|
|
|
|
|
|
/**
|
|
* Helper to find and return a feeedback datum
|
|
*
|
|
* Pass in a feedback datum key and key_type to do a lookup.
|
|
* It will create the feedback datum if it doesnt exist
|
|
*
|
|
* Arguments:
|
|
* * key - Key of the variable to lookup
|
|
* * key_type - Type of feedback to be recorded if the feedback datum cant be found
|
|
*/
|
|
/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
|
|
|
|
/**
|
|
* Main feedback recording proc
|
|
*
|
|
* This is the bulk of this subsystem and is in charge of creating and using the variables.
|
|
* See .github/USING_FEEDBACK_DATA.md for instructions
|
|
* Note that feedback is not recorded to the DB during this function. That happens at round end.
|
|
*
|
|
* Arguments:
|
|
* * key_type - Type of key. Either "text", "amount", "tally", "nested tally", "associative"
|
|
* * key - Key of the data to be used (EG: "admin_verb")
|
|
* * increment - If using "amount", how much to increment why
|
|
* * data - The actual data to logged
|
|
* * overwrite - Do we want to overwrite the existing key
|
|
* * ignore_seal - Does the feedback go in regardless of blackbox sealed status? (EG: map vote results)
|
|
*/
|
|
/datum/controller/subsystem/blackbox/proc/record_feedback(key_type, key, increment, data, overwrite, ignore_seal)
|
|
if((sealed && !ignore_seal) || !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()
|
|
for(var/i in data)
|
|
FV.json["data"]["[pos]"]["[i]"] = "[data[i]]"
|
|
|
|
/**
|
|
* Recursive list recorder
|
|
*
|
|
* Used by the above proc for nested tallies
|
|
*
|
|
* Arguments:
|
|
* * L - List to use
|
|
* * key_list - List of keys to add
|
|
* * increment - How much to increase by
|
|
* * depth - Depth to use
|
|
*/
|
|
/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/list_found_index = list(key_list[depth] = increment)
|
|
L += list_found_index
|
|
else
|
|
if(!L.Find(key_list[depth]))
|
|
var/list/list_go_down = list(key_list[depth] = list())
|
|
L += list_go_down
|
|
L["[key_list[depth-1]]"] = .(L["[key_list[depth]]"], key_list, increment, ++depth)
|
|
return L
|
|
|
|
/**
|
|
* # feedback_variable
|
|
*
|
|
* Datum to hold feedback data, which gets logged at round end
|
|
*
|
|
* Holds all the information being logged
|
|
*/
|
|
/datum/feedback_variable
|
|
var/key
|
|
var/key_type
|
|
var/list/json = list()
|
|
|
|
// Basically just takes some args and sets them
|
|
/datum/feedback_variable/New(new_key, new_key_type)
|
|
key = new_key
|
|
key_type = new_key_type
|
|
|
|
/**
|
|
* Death reporting proc
|
|
*
|
|
* Called when humans and cyborgs die, and logs death info to the `death` table
|
|
*
|
|
* Arguments:
|
|
* * L - The human or cyborg to be logged
|
|
*/
|
|
/datum/controller/subsystem/blackbox/proc/ReportDeath(mob/living/L)
|
|
if(sealed)
|
|
return
|
|
if(!SSdbcore.IsConnected())
|
|
return
|
|
if(!L)
|
|
return
|
|
if(!L.key || !L.mind)
|
|
return
|
|
|
|
var/area/placeofdeath = get_area(L.loc)
|
|
var/podname = "Unknown"
|
|
if(placeofdeath)
|
|
podname = placeofdeath.name
|
|
|
|
// Empty string is important here!
|
|
var/laname = ""
|
|
var/lakey = ""
|
|
if(L.lastattacker)
|
|
laname = L.lastattacker
|
|
if(L.lastattackerckey)
|
|
lakey = L.lastattackerckey
|
|
|
|
var/datum/db_query/deathquery = SSdbcore.NewQuery({"
|
|
INSERT INTO death (name, byondkey, job, special, pod, tod, laname, lakey, gender, bruteloss, fireloss, brainloss, oxyloss, coord, server_id, death_rid, last_words)
|
|
VALUES (:name, :key, :job, :special, :pod, NOW(), :laname, :lakey, :gender, :bruteloss, :fireloss, :brainloss, :oxyloss, :coord, :server_id, :rid, :last_words)"},
|
|
list(
|
|
"name" = L.real_name,
|
|
"key" = L.key,
|
|
"job" = L.mind.assigned_role,
|
|
"special" = L.mind.special_role || "",
|
|
"pod" = podname,
|
|
"laname" = laname,
|
|
"lakey" = lakey,
|
|
"gender" = L.gender,
|
|
"bruteloss" = L.getBruteLoss(),
|
|
"fireloss" = L.getFireLoss(),
|
|
"brainloss" = L.getBrainLoss(),
|
|
"oxyloss" = L.getOxyLoss(),
|
|
"coord" = "[L.x], [L.y], [L.z]",
|
|
"server_id" = GLOB.configuration.system.instance_id,
|
|
"rid" = GLOB.round_id,
|
|
"last_words" = length(L.say_log) > 0 ? L.say_log[length(L.say_log)] : null
|
|
)
|
|
)
|
|
deathquery.warn_execute()
|
|
qdel(deathquery)
|