diff --git a/SQL/database_changelog.txt b/SQL/database_changelog.txt
index 7063273378..e2a172e107 100644
--- a/SQL/database_changelog.txt
+++ b/SQL/database_changelog.txt
@@ -1,15 +1,34 @@
Any time you make a change to the schema files, remember to increment the database schema version. Generally increment the minor number, major should be reserved for significant changes to the schema. Both values go up to 255.
-The latest database version is 3.4; The query to update the schema revision table is:
+The latest database version is 4.0; The query to update the schema revision table is:
-INSERT INTO `schema_revision` (`major`, `minor`) VALUES (3, 4);
+INSERT INTO `schema_revision` (`major`, `minor`) VALUES (4, 0);
or
-INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (3, 4);
+INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (4, 0);
In any query remember to add a prefix to the table names if you use one.
----------------------------------------------------
+12 November 2017, by Jordie0608
+Modified feedback table to use json, a python script is used to migrate data to this new format.
+
+See the file 'feedback_conversion_2017-11-12.py' for instructions on how to use the script.
+
+A new json feedback table can be created with:
+CREATE TABLE `feedback` (
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `datetime` datetime NOT NULL,
+ `round_id` int(11) unsigned NOT NULL,
+ `key_name` varchar(32) NOT NULL,
+ `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL,
+ `version` tinyint(3) unsigned NOT NULL,
+ `json` json NOT NULL,
+ PRIMARY KEY (`id`)
+) ENGINE=MyISAM
+
+----------------------------------------------------
+
28 August 2017, by MrStonedOne
Modified table 'messages', adding a deleted column and editing all indexes to include it
diff --git a/SQL/feedback_conversion_2017-11-12.py b/SQL/feedback_conversion_2017-11-12.py
new file mode 100644
index 0000000000..b8577460b3
--- /dev/null
+++ b/SQL/feedback_conversion_2017-11-12.py
@@ -0,0 +1,532 @@
+#Python 3+ Script for jsonifying feedback table data as of 2017-11-12 made by Jordie0608
+#Apologies for the boilerplated and squirrely code in parts, this has been my first foray into python
+#
+#Before starting ensure you have installed the mysqlclient package https://github.com/PyMySQL/mysqlclient-python
+#It can be downloaded from command line with pip:
+#pip install mysqlclient
+#
+#You will also have to create a new feedback table for inserting converted data to per the schema:
+#CREATE TABLE `feedback_new` (
+# `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+# `datetime` datetime NOT NULL,
+# `round_id` int(11) unsigned NOT NULL,
+# `key_name` varchar(32) NOT NULL,
+# `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL,
+# `version` tinyint(3) unsigned NOT NULL,
+# `json` json NOT NULL,
+# PRIMARY KEY (`id`)
+#) ENGINE=MyISAM
+#This is to prevent the destruction of legacy data and allow rollbacks to be performed in the event of an error during conversion
+#Once conversion is complete remember to rename the old and new feedback tables; it's up to you if you want to keep the old table
+#
+#To view the parameters for this script, execute it with the argument --help
+#All the positional arguments are required, remember to include prefixes in your table names if you use them
+#An example of the command used to execute this script from powershell:
+#python feedback_conversion_2017-11-12.py "localhost" "root" "password" "feedback" "SS13_feedback" "SS13_feedback_new"
+#I found that this script would complete conversion of 10000 rows approximately every 2-3 seconds
+#Depending on the size of your feedback table and the computer used it may take several minutes for the script to finish
+#
+#The script has been tested to complete with tgstation's feedback table as of 2017-10-23 01:34:06
+#Due to the complexity of data that has potentially changed formats multiple times and suffered errors when recording I cannot guarantee it'll always execute successfully
+#In the event of an error the new feedback table is automatically truncated
+#The source table is never modified so you don't have to worry about losing any data due to errors
+#Note that some feedback keys are renamed or coalesced into one, additionnaly some have been entirely removed
+#
+#While this script can be run with your game server(s) active, it may interfere with other database operations and any feedback created after the script has started won't be converted
+
+import MySQLdb
+import argparse
+import json
+import re
+import sys
+from datetime import datetime
+
+def parse_text(details):
+ if not details:
+ return
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1] #the first and last " aren't removed by splitting the dictionary
+ details = details.split('" | "')
+ else:
+ if "_" in details:
+ details = details.split(' ')
+ return details
+
+def parse_tally(details):
+ if not details:
+ return
+ overflowed = None
+ if len(details) >= 65535: #a string this long means the data hit the 64KB character limit of TEXT columns
+ overflowed = True
+ if details.startswith('"') and details.endswith('"'):
+ details = details[details.find('"')+1:details.rfind('"')] #unlike others some of the tally data has extra characters to remove
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ if overflowed:
+ split_details = split_details[:-1] #since the string overflowed the last element will be incomplete and needs to be ignored
+ details = {}
+ for i in split_details:
+ increment = 1
+ if '|' in i and i[i.find('|')+1:]:
+ increment = float(i[i.find('|')+1:])
+ i = i[:i.find('|')]
+ if i in details:
+ details[i] += increment
+ else:
+ details[i] = increment
+ for i in details:
+ details[i] = '{0:g}'.format(details[i]) #remove .0 from floats that have it to conform with DM
+ return details
+
+def parse_nested(var_name, details):
+ if not details:
+ return
+ #group by data before pipe
+ if var_name in ("admin_toggle", "preferences_verb", "changeling_objective", "cult_objective", "traitor_objective", "wizard_objective", "mining_equipment_bought", "vending_machine_usage", "changeling_powers", "wizard_spell_improved", "testmerged_prs"):
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ details = {}
+ for i in split_details:
+ if "|" in i and i[:i.find('|')] not in details:
+ details[i[:i.find('|')]] = {}
+ elif "|" not in i and i[i.find('|')+1:] not in details:
+ details[i[i.find('|')+1:]] = 0
+ for i in split_details:
+ if "|" in i:
+ if details[i[:i.find('|')]] is not dict:
+ continue
+ if i[i.find('|')+1:] in details[i[:i.find('|')]]:
+ details[i[:i.find('|')]][i[i.find('|')+1:]] += 1
+ else:
+ details[i[:i.find('|')]][i[i.find('|')+1:]] = 1
+ else:
+ if i in details and type(details[i]) is not dict: #sometimes keys that should have a value after a pipe just don't and would otherwise error here
+ details[i] += 1
+ return details
+ #group by data after pipe
+ elif var_name in ("cargo_imports", "traitor_uplink_items_bought", "export_sold_cost", "item_used_for_combat", "played_url"):
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ details = {}
+ for i in split_details:
+ if i == i[i.rfind('|')+1:]: #there's no pipe and data to group by, so we fill it in
+ i = "{0}|missing data".format(i)
+ details[i[i.rfind('|')+1:]] = {}
+ for i in split_details:
+ if i == i[i.rfind('|')+1:]:
+ i = "{0}|missing data".format(i)
+ if i[:i.find('|')] in details[i[i.rfind('|')+1:]]:
+ details[i[i.rfind('|')+1:]][i[:i.find('|')]] += 1
+ else:
+ details[i[i.rfind('|')+1:]][i[:i.find('|')]] = 1
+ return details
+ elif var_name == "hivelord_core":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ details = {}
+ for i in split_details:
+ if i[:i.find('|')] not in details:
+ details[i[:i.find('|')]] = {}
+ if "used" in i:
+ if "used" not in details:
+ details[i[:i.find('|')]]["used"] = {}
+ for i in split_details:
+ if "used" in i:
+ if "used" not in details[i[:i.find('|')]]:
+ details[i[:i.find('|')]]["used"] = {}
+ details[i[:i.find('|')]]["used"][i[i.rfind('|')+1:]] = 1
+ else:
+ if i[i.rfind('|')+1:] in details[i[:i.find('|')]]["used"]:
+ details[i[:i.find('|')]]["used"][i[i.rfind('|')+1:]] += 1
+ else:
+ details[i[:i.find('|')]]["used"][i[i.rfind('|')+1:]] = 1
+ elif "|" in i:
+ if i[i.find('|')+1:] in details[i[:i.find('|')]]:
+ details[i[:i.find('|')]][i[i.find('|')+1:]] += 1
+ else:
+ details[i[:i.find('|')]][i[i.find('|')+1:]] = 1
+ return details
+ elif var_name == "job_preferences":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('|-" | "|')
+ else:
+ split_details = details.split('|- |')
+ details = {}
+ for i in split_details:
+ if i.startswith('|'):
+ i = i[1:]
+ if i[:i.find('|')] not in details:
+ details[i[:i.find('|')]] = {}
+ for i in split_details:
+ if i.startswith('|'):
+ i = i[1:]
+ if i.endswith('-'):
+ i = i[:-2]
+ sub_split = i.split('|')
+ job = sub_split[0]
+ sub_split = sub_split[1:]
+ for o in sub_split:
+ details[job][o[:o.find('=')].lower()] = o[o.find('=')+1:]
+ return details
+
+def parse_associative(var_name, details):
+ if not details:
+ return
+ if var_name == "colonies_dropped":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('|')
+ details = {}
+ details["1"] = {"x" : split_details[0], "y" : split_details[1], "z" : split_details[2]}
+ return details
+ elif var_name == "commendation":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ if '}" | "{' in details:
+ split_details = details.split('}" | "{')
+ else:
+ split_details = details.split('} {')
+ details = {}
+ for i in split_details:
+ params = []
+ sub_split = i.split(',')
+ for o in sub_split:
+ o = re.sub('[^A-Za-z0-9 ]', '', o[o.find(':')+1:]) #remove all the formatting and escaped characters from being pre-encoded as json
+ params.append(o)
+ details[len(details)+1] = {"commender" : params[0], "commendee" : params[1], "medal" : params[2], "reason" : params[3]}
+ return details
+ elif var_name == "high_research_level":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ details = {}
+ levels = {}
+ for i in split_details:
+ x = {i[:-1] : i[-1:]}
+ levels.update(x)
+ details["1"] = levels
+ return details
+
+def parse_special(var_name, var_value, details):
+ #old data is essentially a tally in text form
+ if var_name == "immortality_talisman":
+ if details.startswith('"') and details.endswith('"'):
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ return len(split_details)
+ #now records channel names, so we have to fill in whats missing
+ elif var_name == "newscaster_channels":
+ details = ["missing data"]
+ details *= var_value
+ return details
+ #all the channels got renamed, plus we ignore any with an amount of zero
+ elif var_name == "radio_usage":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[details.find('C'):-1]
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ details = {}
+ new_keys = {"COM":"common", "SCI":"science", "HEA":"command", "MED":"medical", "ENG":"engineering", "SEC":"security", "DTH":"centcom", "SYN":"syndicate", "SRV":"service", "CAR":"supply", "OTH":"other", "PDA":"PDA", "RC":"request console"}
+ for i in split_details:
+ if i.endswith('0'):
+ continue
+ if i[:i.find('-')] not in new_keys:
+ continue
+ details[new_keys[i[:i.find('-')]]] = i[i.find('-')+1:]
+ return details
+ #all of the data tracked by this is invalid due to recording the incorrect type
+ elif var_name == "shuttle_gib":
+ return {"missing data":1}
+ #all records have a prefix of 'slimebirth_' that needs to be removed
+ elif var_name == "slime_babies_born":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ details = {}
+ for i in split_details:
+ if i[i.find('_')+1:].replace('_', ' ') in details:
+ details[i[i.find('_')+1:].replace('_', ' ')] += 1
+ else:
+ details[i[i.find('_')+1:].replace('_', ' ')] = 1
+ return details
+ #spaces were replaced by underscores, we need to undo this
+ elif var_name == "slime_core_harvested":
+ if details.startswith('"') and details.endswith('"'):
+ details = details[1:-1]
+ split_details = details.split('" | "')
+ else:
+ split_details = details.split(' ')
+ details = {}
+ for i in split_details:
+ if i.replace('_', ' ') in details:
+ details[i.replace('_', ' ')] += 1
+ else:
+ details[i.replace('_', ' ')] = 1
+ return details
+
+def parse_multirow(var_name, var_value, details, multirows_completed):
+ if var_name in ("ahelp_close", "ahelp_icissue", "ahelp_reject", "ahelp_reopen", "ahelp_resolve", "ahelp_unresolved"):
+ ahelp_vars = {"ahelp_close":"closed", "ahelp_icissue":"IC", "ahelp_reject":"rejected", "ahelp_reopen":"reopened", "ahelp_resolve":"resolved", "ahelp_unresolved":"unresolved"}
+ details = {ahelp_vars[var_name]:var_value}
+ del ahelp_vars[var_name]
+ query_where = "round_id = {0} AND (".format(query_row[2])
+ for c, i in enumerate(ahelp_vars):
+ if c:
+ query_where += " OR "
+ query_where += "var_name = \"{0}\"".format(i)
+ query_where += ")"
+ cursor.execute("SELECT var_name, var_value FROM {0} WHERE {1}".format(current_table, query_where))
+ rows = cursor.fetchall()
+ if rows:
+ for r in rows:
+ details[ahelp_vars[r[0]]] = r[1]
+ keys = list(ahelp_vars.keys())
+ keys.append(var_name)
+ multirows_completed += keys
+ return details
+ elif var_name in ("alert_comms_blue", "alert_comms_green"):
+ level_vars = {"alert_comms_blue":"1", "alert_comms_green":"0"}
+ details = {level_vars[var_name]:var_value}
+ del level_vars[var_name]
+ i = list(level_vars)[0]
+ cursor.execute("SELECT var_value FROM {0} WHERE round_id = {1} AND var_name = \"{2}\"".format(current_table, query_row[2], i))
+ row = cursor.fetchone()
+ if row:
+ details[level_vars[i]] = row[0]
+ keys = list(level_vars.keys())
+ keys.append(var_name)
+ multirows_completed += keys
+ return details
+ elif var_name in ("alert_keycard_auth_bsa", "alert_keycard_auth_maint"):
+ auth_vars = {"alert_keycard_auth_maint":("emergency maintenance access", "enabled"), "alert_keycard_auth_bsa":("bluespace artillery", "unlocked")}
+ i = list(auth_vars[var_name])
+ details = {i[0]:{i[1]:var_value}}
+ del auth_vars[var_name]
+ i = list(auth_vars)[0]
+ cursor.execute("SELECT var_value FROM {0} WHERE round_id = {1} AND var_name = \"{2}\"".format(current_table, query_row[2], i))
+ row = cursor.fetchone()
+ if row:
+ o = list(auth_vars[i])
+ details[o[0]] = {o[1]:row[0]}
+ keys = list(auth_vars.keys())
+ keys.append(var_name)
+ multirows_completed += keys
+ return details
+ elif var_name in ("arcade_loss_hp_emagged", "arcade_loss_hp_normal", "arcade_loss_mana_emagged", "arcade_loss_mana_normal", "arcade_win_emagged", "arcade_win_normal"):
+ result_vars = {"arcade_loss_hp_emagged":("loss", "hp", "emagged"), "arcade_loss_hp_normal":("loss", "hp", "normal"), "arcade_loss_mana_emagged":("loss", "mana", "emagged"), "arcade_loss_mana_normal":("loss", "mana", "normal"), "arcade_win_emagged":("win", "emagged"), "arcade_win_normal":("win", "normal")}
+ i = list(result_vars[var_name])
+ del result_vars[var_name]
+ if i[0] == "loss":
+ details = {i[0]:{i[1]:{i[2]:var_value}}}
+ else:
+ details = {i[0]:{i[1]:var_value}}
+ query_where = "round_id = {0} AND (".format(query_row[2])
+ for c, i in enumerate(result_vars):
+ if c:
+ query_where += " OR "
+ query_where += "var_name = \"{0}\"".format(i)
+ query_where += ")"
+ cursor.execute("SELECT var_name, var_value FROM {0} WHERE {1}".format(current_table, query_where))
+ rows = cursor.fetchall()
+ if rows:
+ for r in rows:
+ i = list(result_vars[r[0]])
+ if i[0] not in details:
+ details[i[0]] = {}
+ if i[0] == "loss":
+ if i[1] not in details[i[0]]:
+ details[i[0]][i[1]] = {}
+ details[i[0]][i[1]][i[2]] = r[1]
+ else:
+ details[i[0]][i[1]] = r[1]
+ keys = list(result_vars.keys())
+ keys.append(var_name)
+ multirows_completed += keys
+ return details
+ elif var_name in ("cyborg_engineering", "cyborg_janitor", "cyborg_medical", "cyborg_miner", "cyborg_peacekeeper", "cyborg_security", "cyborg_service", "cyborg_standard"):
+ module_vars = {"cyborg_engineering":"Engineering", "cyborg_janitor":"Janitor", "cyborg_medical":"Medical", "cyborg_miner":"Miner", "cyborg_peacekeeper":"Peacekeeper", "cyborg_security":"Security", "cyborg_service":"Service", "cyborg_standard":"Standard"}
+ details = {module_vars[var_name]:var_value}
+ del module_vars[var_name]
+ query_where = "round_id = {0} AND (".format(query_row[2])
+ for c, i in enumerate(module_vars):
+ if c:
+ query_where += " OR "
+ query_where += "var_name = \"{0}\"".format(i)
+ query_where += ")"
+ cursor.execute("SELECT var_name, var_value FROM {0} WHERE {1}".format(current_table, query_where))
+ rows = cursor.fetchall()
+ if rows:
+ for r in rows:
+ details[module_vars[r[0]]] = r[1]
+ keys = list(module_vars.keys())
+ keys.append(var_name)
+ multirows_completed += keys
+ return details
+ elif var_name in ("escaped_human", "escaped_total", "round_end_clients", "round_end_ghosts", "survived_human", "survived_total"):
+ round_vars = {"escaped_human":("escapees", "human"), "escaped_total":("escapees", "total"), "round_end_clients":("clients"), "round_end_ghosts":("ghosts"), "survived_human":("survivors", "human"), "survived_total":("survivors", "total")}
+ if var_name in ("round_end_clients", "round_end_ghosts"):
+ i = round_vars[var_name]
+ details = {i:var_value}
+ else:
+ i = list(round_vars[var_name])
+ details = {i[0]:{i[1]:var_value}}
+ del round_vars[var_name]
+ query_where = "round_id = {0} AND (".format(query_row[2])
+ for c, i in enumerate(round_vars):
+ if c:
+ query_where += " OR "
+ query_where += "var_name = \"{0}\"".format(i)
+ query_where += ")"
+ cursor.execute("SELECT var_name, var_value FROM {0} WHERE {1}".format(current_table, query_where))
+ rows = cursor.fetchall()
+ if rows:
+ for r in rows:
+ if r[0] in ("round_end_clients", "round_end_ghosts"):
+ i = round_vars[r[0]]
+ details[i] = r[1]
+ else:
+ i = list(round_vars[r[0]])
+ if i[0] not in details:
+ details[i[0]] = {}
+ details[i[0]][i[1]] = r[1]
+ keys = list(round_vars.keys())
+ keys.append(var_name)
+ multirows_completed += keys
+ return details
+ elif var_name in ("mecha_durand_created", "mecha_firefighter_created", "mecha_gygax_created", "mecha_honker_created", "mecha_odysseus_created", "mecha_phazon_created", "mecha_ripley_created"):
+ mecha_vars ={"mecha_durand_created":"Durand", "mecha_firefighter_created":"APLU \"Firefighter\"", "mecha_gygax_created":"Gygax", "mecha_honker_created":"H.O.N.K", "mecha_odysseus_created":"Odysseus", "mecha_phazon_created":"Phazon", "mecha_ripley_created":"APLU \"Ripley\""}
+ details = {mecha_vars[var_name]:var_value}
+ del mecha_vars[var_name]
+ query_where = "round_id = {0} AND (".format(query_row[2])
+ for c, i in enumerate(mecha_vars):
+ if c:
+ query_where += " OR "
+ query_where += "var_name = \"{0}\"".format(i)
+ query_where += ")"
+ cursor.execute("SELECT var_name, var_value FROM {0} WHERE {1}".format(current_table, query_where))
+ rows = cursor.fetchall()
+ if rows:
+ for r in rows:
+ details[mecha_vars[r[0]]] = r[1]
+ keys = list(mecha_vars.keys())
+ keys.append(var_name)
+ multirows_completed += keys
+ return details
+
+def pick_parsing(var_name, var_value, details, multirows_completed):
+ if var_name in text_keys:
+ return parse_text(details)
+ elif var_name in amount_keys:
+ return var_value
+ elif var_name in simple_tallies:
+ return parse_tally(details)
+ elif var_name in nested_tallies:
+ return parse_nested(var_name, details)
+ elif var_name in associatives:
+ return parse_associative(var_name, details)
+ elif var_name in special_cases:
+ return parse_special(var_name, var_value, details)
+ elif var_name in multirow:
+ return parse_multirow(var_name, var_value, details, multirows_completed)
+ else:
+ return False
+
+if sys.version_info[0] < 3:
+ raise Exception("Python must be at least version 3 for this script.")
+text_keys = ["religion_book", "religion_deity", "religion_name", "shuttle_fasttravel", "shuttle_manipulator", "shuttle_purchase", "shuttle_reason", "station_renames"]
+amount_keys = ["admin_cookies_spawned", "cyborg_ais_created", "cyborg_frames_built", "cyborg_mmis_filled", "newscaster_newspapers_printed", "newscaster_stories", "nuclear_challenge_mode"]
+simple_tallies = ["admin_secrets_fun_used", "admin_verb", "assembly_made", "brother_success", "cell_used", "changeling_power_purchase", "changeling_success", "chaplain_weapon", "chemical_reaction", "circuit_printed", "clockcult_scripture_recited", "contamination", "cult_runes_scribed", "engine_started", "event_admin_cancelled", "event_ran", "food_harvested", "food_made", "gun_fired", "handcuffs", "item_deconstructed", "item_printed", "jaunter", "lazarus_injector", "megafauna_kills", "mining_voucher_redeemed", "mobs_killed_mining", "object_crafted", "ore_mined", "pick_used_mining", "slime_cores_used", "surgeries_completed", "time_dilation_current", "traitor_random_uplink_items_gotten", "traitor_success", "voice_of_god", "warp_cube", "wisp_lantern", "wizard_spell_learned", "wizard_success", "zone_targeted"]
+nested_tallies = ["admin_toggle", "cargo_imports", "changeling_objective", "changeling_powers", "cult_objective", "export_sold_cost", "hivelord_core", "item_used_for_combat", "job_preferences", "mining_equipment_bought", "played_url", "preferences_verb", "testmerged_prs", "traitor_objective", "traitor_uplink_items_bought", "vending_machine_usage", "wizard_objective", "wizard_spell_improved"]
+associatives = ["colonies_dropped", "commendation", "high_research_level"]
+special_cases = ["immortality_talisman", "newscaster_channels", "radio_usage", "shuttle_gib", "slime_babies_born", "slime_core_harvested"]
+multirow = ["ahelp_close", "ahelp_icissue", "ahelp_reject", "ahelp_reopen", "ahelp_resolve", "ahelp_unresolved", "alert_comms_blue", "alert_comms_green", "alert_keycard_auth_bsa", "alert_keycard_auth_maint", "arcade_loss_hp_emagged", "arcade_loss_hp_normal", "arcade_loss_mana_emagged", "arcade_loss_mana_normal", "arcade_win_emagged", "arcade_win_normal", "cyborg_engineering", "cyborg_janitor", "cyborg_medical", "cyborg_miner", "cyborg_peacekeeper", "cyborg_security", "cyborg_service", "cyborg_standard", "escaped_human", "escaped_total", "mecha_durand_created", "mecha_firefighter_created", "mecha_gygax_created", "mecha_honker_created", "mecha_odysseus_created", "mecha_phazon_created", "mecha_ripley_created", "round_end_clients", "round_end_ghosts", "survived_human", "survived_total"]
+renames = {"ahelp_stats":["ahelp_close", "ahelp_icissue", "ahelp_reject", "ahelp_reopen", "ahelp_resolve", "ahelp_unresolved"], "ais_created":["cyborg_ais_created"], "arcade_results":["arcade_loss_hp_emagged", "arcade_loss_hp_normal", "arcade_loss_mana_emagged", "arcade_loss_mana_normal", "arcade_win_emagged", "arcade_win_normal"], "cyborg_modules":["cyborg_engineering", "cyborg_janitor", "cyborg_medical", "cyborg_miner", "cyborg_peacekeeper", "cyborg_security", "cyborg_service", "cyborg_standard"], "immortality_talisman_uses":["immortality_talisman"], "keycard_auths":["alert_keycard_auth_bsa", "alert_keycard_auth_maint"], "mechas_created":["mecha_durand_created", "mecha_firefighter_created", "mecha_gygax_created", "mecha_honker_created", "mecha_odysseus_created", "mecha_phazon_created", "mecha_ripley_created"], "mmis_filled":["cyborg_mmis_filled"], "newspapers_printed":["newscaster_newspapers_printed"], "round_end_stats":["escaped_human", "escaped_total", "round_end_clients", "round_end_ghosts", "survived_human", "survived_total"], "security_level_changes":["alert_comms_blue", "alert_comms_green"]}
+key_types = {"amount":["ais_created", "immortality_talisman_uses", "mmis_filled", "newspapers_printed", "admin_cookies_spawned", "cyborg_frames_built", "newscaster_stories", "nuclear_challenge_mode"],
+"associative":["colonies_dropped", "commendation", "high_research_level"],
+"nested tally":["admin_toggle", "arcade_results", "cargo_imports", "changeling_objective", "changeling_powers", "cult_objective", "export_sold_cost", "hivelord_core", "item_used_for_combat", "job_preferences", "keycard_auths", "mining_equipment_bought", "played_url", "preferences_verb", "round_end_stats", "testmerged_prs", "traitor_objective", "traitor_uplink_items_bought", "vending_machine_usage", "wizard_objective", "wizard_spell_improved"],
+"tally":[ "admin_secrets_fun_used", "admin_verb", "ahelp_stats", "assembly_made", "brother_success", "cell_used", "changeling_power_purchase", "changeling_success", "chaplain_weapon", "chemical_reaction", "circuit_printed", "clockcult_scripture_recited", "contamination", "cult_runes_scribed", "cyborg_modules", "engine_started", "event_admin_cancelled", "event_ran", "food_harvested", "food_made", "gun_fired", "handcuffs", "item_deconstructed", "item_printed", "jaunter", "lazarus_injector", "mechas_created", "megafauna_kills", "mining_voucher_redeemed", "mobs_killed_mining", "object_crafted", "ore_mined", "pick_used_mining", "radio_usage", "security_level_changes", "shuttle_gib", "slime_babies_born", "slime_cores_used", "slime_core_harvested", "surgeries_completed", "time_dilation_current", "traitor_random_uplink_items_gotten", "traitor_success", "voice_of_god", "warp_cube", "wisp_lantern", "wizard_spell_learned", "wizard_success", "zone_targeted"],
+"text":["shuttle_fasttravel", "shuttle_manipulator", "shuttle_purchase", "shuttle_reason", "newscaster_channels", "religion_book", "religion_deity", "religion_name", "station_renames"]}
+multirows_completed = []
+query_values = ""
+current_round = 0
+parser = argparse.ArgumentParser()
+parser.add_argument("address", help="MySQL server address (use localhost for the current computer)")
+parser.add_argument("username", help="MySQL login username")
+parser.add_argument("password", help="MySQL login username")
+parser.add_argument("database", help="Database name")
+parser.add_argument("curtable", help="Name of the current feedback table (remember prefixes if you use them)")
+parser.add_argument("newtable", help="Name of the new table to insert to, can't be same as the source table (remember prefixes)")
+args = parser.parse_args()
+db=MySQLdb.connect(host=args.address, user=args.username, passwd=args.password, db=args.database)
+cursor=db.cursor()
+current_table = args.curtable
+new_table = args.newtable
+cursor.execute("SELECT max(id) FROM {0}".format(current_table))
+query_id = cursor.fetchone()
+max_id = query_id[0]
+start_time = datetime.now()
+print("Beginning conversion at {0}".format(start_time.strftime("%Y-%m-%d %H:%M:%S")))
+try:
+ for current_id in range(max_id):
+ if current_id % 10000 == 0:
+ cur_time = datetime.now()
+ print("Reached row ID {0} Duration: {1}".format(current_id, cur_time - start_time))
+ cursor.execute("SELECT * FROM {0} WHERE id = {1}".format(current_table, current_id))
+ query_row = cursor.fetchone()
+ if not query_row:
+ continue
+ else:
+ if current_round != query_row[2]:
+ multirows_completed.clear()
+ if query_values:
+ query_values = query_values[:-1]
+ query_values += ';'
+ cursor.execute("INSERT INTO {0} (datetime, round_id, key_name, key_type, version, json) VALUES {1}".format(new_table, query_values))
+ query_values = ""
+ current_round = query_row[2]
+ if query_row[3] in multirows_completed:
+ continue
+ parsed_data = pick_parsing(query_row[3], query_row[4], query_row[5], multirows_completed)
+ if not parsed_data:
+ continue
+ json_data = {}
+ json_data["data"] = parsed_data
+ new_key = query_row[3]
+ for r in renames:
+ if new_key in renames[r]:
+ new_key = r
+ break
+ new_key_type = None
+ for t in key_types:
+ if new_key in key_types[t]:
+ new_key_type = t
+ break
+ dequoted_json = re.sub("\'", "\\'", json.dumps(json_data))
+ query_values += "('{0}',{1},'{2}','{3}',{4},'{5}'),".format(query_row[1], query_row[2], new_key, new_key_type, 1, dequoted_json)
+ end_time = datetime.now()
+ print("Conversion completed at {0}".format(datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
+ print("Script duration: {0}".format(end_time - start_time))
+except Exception as e:
+ end_time = datetime.now()
+ print("Error encountered on row ID {0} at {1}".format(current_id, datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
+ print("Script duration: {0}".format(end_time - start_time))
+ cursor.execute("TRUNCATE {0} ".format(new_table))
+ raise e
+cursor.close()
diff --git a/SQL/tgstation_schema.sql b/SQL/tgstation_schema.sql
index 1edd5ad12b..2d914161d3 100644
--- a/SQL/tgstation_schema.sql
+++ b/SQL/tgstation_schema.sql
@@ -163,12 +163,13 @@ DROP TABLE IF EXISTS `feedback`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `feedback` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `time` datetime NOT NULL,
- `round_id` int(8) NOT NULL,
- `var_name` varchar(32) NOT NULL,
- `var_value` int(16) DEFAULT NULL,
- `details` text,
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `datetime` datetime NOT NULL,
+ `round_id` int(11) unsigned NOT NULL,
+ `key_name` varchar(32) NOT NULL,
+ `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL,
+ `version` tinyint(3) unsigned NOT NULL,
+ `json` json NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql
index 72045e50fb..2d44c8eac0 100644
--- a/SQL/tgstation_schema_prefixed.sql
+++ b/SQL/tgstation_schema_prefixed.sql
@@ -163,12 +163,13 @@ DROP TABLE IF EXISTS `SS13_feedback`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `SS13_feedback` (
- `id` int(11) NOT NULL AUTO_INCREMENT,
- `time` datetime NOT NULL,
- `round_id` int(8) NOT NULL,
- `var_name` varchar(32) NOT NULL,
- `var_value` int(16) DEFAULT NULL,
- `details` text,
+ `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+ `datetime` datetime NOT NULL,
+ `round_id` int(11) unsigned NOT NULL,
+ `key_name` varchar(32) NOT NULL,
+ `version` tinyint(3) unsigned NOT NULL,
+ `key_type` enum('text', 'amount', 'tally', 'nested tally', 'associative') NOT NULL,
+ `json` json NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = @saved_cs_client */;
diff --git a/code/_compile_options.dm b/code/_compile_options.dm
index 1f20614c17..a0b13211f6 100644
--- a/code/_compile_options.dm
+++ b/code/_compile_options.dm
@@ -79,5 +79,5 @@
//Update this whenever the db schema changes
//make sure you add an update to the schema_version stable in the db changelog
-#define DB_MAJOR_VERSION 3
-#define DB_MINOR_VERSION 4
+#define DB_MAJOR_VERSION 4
+#define DB_MINOR_VERSION 0
diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm
index fa10d398dd..19fef28597 100644
--- a/code/controllers/admin.dm
+++ b/code/controllers/admin.dm
@@ -45,9 +45,9 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick)
switch(controller)
if("Master")
Recreate_MC()
- SSblackbox.add_details("admin_verb","Restart Master Controller")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Master Controller")
if("Failsafe")
new /datum/controller/failsafe()
- SSblackbox.add_details("admin_verb","Restart Failsafe Controller")
+ SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller")
message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.")
diff --git a/code/controllers/subsystem/blackbox.dm b/code/controllers/subsystem/blackbox.dm
index fe7cf33070..f7abc72cbd 100644
--- a/code/controllers/subsystem/blackbox.dm
+++ b/code/controllers/subsystem/blackbox.dm
@@ -5,21 +5,11 @@ SUBSYSTEM_DEF(blackbox)
runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME
init_order = INIT_ORDER_BLACKBOX
- var/list/msg_common = list()
- var/list/msg_science = list()
- var/list/msg_command = list()
- var/list/msg_medical = list()
- var/list/msg_engineering = list()
- var/list/msg_security = list()
- var/list/msg_deathsquad = list()
- var/list/msg_syndicate = list()
- var/list/msg_service = list()
- var/list/msg_cargo = list()
- var/list/msg_other = list()
-
var/list/feedback = list() //list of datum/feedback_variable
var/triggertime = 0
var/sealed = FALSE //time to stop tracking stats?
+ var/list/research_levels = list() //list of highest tech levels attained that isn't lost lost by destruction of RD computers
+ var/list/versions = list() //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()
@@ -44,20 +34,7 @@ SUBSYSTEM_DEF(blackbox)
/datum/controller/subsystem/blackbox/Recover()
- msg_common = SSblackbox.msg_common
- msg_science = SSblackbox.msg_science
- msg_command = SSblackbox.msg_command
- msg_medical = SSblackbox.msg_medical
- msg_engineering = SSblackbox.msg_engineering
- msg_security = SSblackbox.msg_security
- msg_deathsquad = SSblackbox.msg_deathsquad
- msg_syndicate = SSblackbox.msg_syndicate
- msg_service = SSblackbox.msg_service
- msg_cargo = SSblackbox.msg_cargo
- msg_other = SSblackbox.msg_other
-
feedback = SSblackbox.feedback
-
sealed = SSblackbox.sealed
//no touchie
@@ -71,32 +48,14 @@ SUBSYSTEM_DEF(blackbox)
/datum/controller/subsystem/blackbox/Shutdown()
sealed = FALSE
- set_val("ahelp_unresolved", GLOB.ahelp_tickets.active_tickets.len)
-
- var/pda_msg_amt = 0
- var/rc_msg_amt = 0
-
+ record_feedback("tally", "ahelp_stats", GLOB.ahelp_tickets.active_tickets.len, "unresolved")
for (var/obj/machinery/message_server/MS in GLOB.message_servers)
- if (MS.pda_msgs.len > pda_msg_amt)
- pda_msg_amt = MS.pda_msgs.len
- if (MS.rc_msgs.len > rc_msg_amt)
- rc_msg_amt = MS.rc_msgs.len
-
- set_details("radio_usage","")
-
- add_details("radio_usage","COM-[msg_common.len]")
- add_details("radio_usage","SCI-[msg_science.len]")
- add_details("radio_usage","HEA-[msg_command.len]")
- add_details("radio_usage","MED-[msg_medical.len]")
- add_details("radio_usage","ENG-[msg_engineering.len]")
- add_details("radio_usage","SEC-[msg_security.len]")
- add_details("radio_usage","DTH-[msg_deathsquad.len]")
- add_details("radio_usage","SYN-[msg_syndicate.len]")
- add_details("radio_usage","SRV-[msg_service.len]")
- add_details("radio_usage","CAR-[msg_cargo.len]")
- add_details("radio_usage","OTH-[msg_other.len]")
- add_details("radio_usage","PDA-[pda_msg_amt]")
- add_details("radio_usage","RC-[rc_msg_amt]")
+ 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(research_levels.len)
+ SSblackbox.record_feedback("associative", "high_research_level", 1, research_levels)
if (!SSdbcore.Connect())
return
@@ -104,79 +63,182 @@ SUBSYSTEM_DEF(blackbox)
var/list/sqlrowlist = list()
for (var/datum/feedback_variable/FV in feedback)
- sqlrowlist += list(list("time" = "Now()", "round_id" = GLOB.round_id, "var_name" = "'[sanitizeSQL(FV.get_variable())]'", "var_value" = FV.get_value(), "details" = "'[sanitizeSQL(FV.get_details())]'"))
+ 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
+ if(IsAdminAdvancedProcCall())
+ message_admins("[key_name_admin(usr)] sealed the blackbox!")
+ log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].")
+ sealed = TRUE
-/datum/controller/subsystem/blackbox/proc/LogBroadcast(blackbox_msg, freq)
+/datum/controller/subsystem/blackbox/proc/log_research(tech, level)
+ if(!(tech in research_levels) || research_levels[tech] < level)
+ research_levels[tech] = level
+
+/datum/controller/subsystem/blackbox/proc/LogBroadcast(freq)
if(sealed)
return
switch(freq)
if(1459)
- msg_common += blackbox_msg
- if(1351)
- msg_science += blackbox_msg
- if(1353)
- msg_command += blackbox_msg
- if(1355)
- msg_medical += blackbox_msg
- if(1357)
- msg_engineering += blackbox_msg
- if(1359)
- msg_security += blackbox_msg
- if(1441)
- msg_deathsquad += blackbox_msg
- if(1213)
- msg_syndicate += blackbox_msg
- if(1349)
- msg_service += blackbox_msg
- if(1347)
- msg_cargo += blackbox_msg
+ record_feedback("tally", "radio_usage", 1, "common")
+ if(GLOB.SCI_FREQ)
+ record_feedback("tally", "radio_usage", 1, "science")
+ if(GLOB.COMM_FREQ)
+ record_feedback("tally", "radio_usage", 1, "command")
+ if(GLOB.MED_FREQ)
+ record_feedback("tally", "radio_usage", 1, "medical")
+ if(GLOB.ENG_FREQ)
+ record_feedback("tally", "radio_usage", 1, "engineering")
+ if(GLOB.SEC_FREQ)
+ record_feedback("tally", "radio_usage", 1, "security")
+ if(GLOB.SYND_FREQ)
+ record_feedback("tally", "radio_usage", 1, "syndicate")
+ if(GLOB.SERV_FREQ)
+ record_feedback("tally", "radio_usage", 1, "service")
+ if(GLOB.SUPP_FREQ)
+ record_feedback("tally", "radio_usage", 1, "supply")
+ if(GLOB.CENTCOM_FREQ)
+ record_feedback("tally", "radio_usage", 1, "centcom")
+ if(GLOB.AIPRIV_FREQ)
+ record_feedback("tally", "radio_usage", 1, "ai private")
+ if(GLOB.REDTEAM_FREQ)
+ record_feedback("tally", "radio_usage", 1, "CTF red team")
+ if(GLOB.BLUETEAM_FREQ)
+ record_feedback("tally", "radio_usage", 1, "CTF blue team")
else
- msg_other += blackbox_msg
+ record_feedback("tally", "radio_usage", 1, "other")
-/datum/controller/subsystem/blackbox/proc/find_feedback_datum(variable)
+/datum/controller/subsystem/blackbox/proc/find_feedback_datum(key, key_type)
for(var/datum/feedback_variable/FV in feedback)
- if(FV.get_variable() == variable)
+ if(FV.key == key)
return FV
- var/datum/feedback_variable/FV = new(variable)
+ 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 recieved
+ 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
+ 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"}}}
-/datum/controller/subsystem/blackbox/proc/set_val(variable, value)
- if(sealed)
+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(variable)
- FV.set_value(value)
+ 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)
+ FV.json["data"]["[pos]"]["[i]"] = "[data[i]]" //and here with "[FV.json["data"].len]"
-/datum/controller/subsystem/blackbox/proc/inc(variable, value)
- if(sealed)
- return
- var/datum/feedback_variable/FV = find_feedback_datum(variable)
- FV.inc(value)
+/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/controller/subsystem/blackbox/proc/dec(variable,value)
- if(sealed)
- return
- var/datum/feedback_variable/FV = find_feedback_datum(variable)
- FV.dec(value)
+/datum/feedback_variable
+ var/key
+ var/key_type
+ var/list/json = list()
-/datum/controller/subsystem/blackbox/proc/set_details(variable,details)
- if(sealed)
- return
- var/datum/feedback_variable/FV = find_feedback_datum(variable)
- FV.set_details(details)
-
-/datum/controller/subsystem/blackbox/proc/add_details(variable,details)
- if(sealed)
- return
- var/datum/feedback_variable/FV = find_feedback_datum(variable)
- FV.add_details(details)
+/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)
if(sealed)
@@ -208,69 +270,3 @@ SUBSYSTEM_DEF(blackbox)
var/map = sanitizeSQL(SSmapping.config.map_name)
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])")
query_report_death.Execute()
-
-/datum/controller/subsystem/blackbox/proc/Seal()
- if(sealed)
- return
- if(IsAdminAdvancedProcCall())
- var/msg = "[key_name_admin(usr)] sealed the blackbox!"
- message_admins(msg)
- log_game("Blackbox sealed[IsAdminAdvancedProcCall() ? " by [key_name(usr)]" : ""].")
- sealed = TRUE
-
-//feedback variable datum, for storing all kinds of data
-/datum/feedback_variable
- var/variable
- var/value
- var/list/details
-
-/datum/feedback_variable/New(param_variable, param_value = 0)
- variable = param_variable
- value = param_value
-
-/datum/feedback_variable/proc/inc(num = 1)
- if (isnum(value))
- value += num
- else
- value = text2num(value)
- if (isnum(value))
- value += num
- else
- value = num
-
-/datum/feedback_variable/proc/dec(num = 1)
- if (isnum(value))
- value -= num
- else
- value = text2num(value)
- if (isnum(value))
- value -= num
- else
- value = -num
-
-/datum/feedback_variable/proc/set_value(num)
- if (isnum(num))
- value = num
-
-/datum/feedback_variable/proc/get_value()
- if (!isnum(value))
- return 0
- return value
-
-/datum/feedback_variable/proc/get_variable()
- return variable
-
-/datum/feedback_variable/proc/set_details(deets)
- details = list("\"[deets]\"")
-
-/datum/feedback_variable/proc/add_details(deets)
- if (!details)
- set_details(deets)
- else
- details += "\"[deets]\""
-
-/datum/feedback_variable/proc/get_details()
- return details ? details.Join(" | ") : null
-
-/datum/feedback_variable/proc/get_parsed()
- return list(variable,value,details.Join(" | "))
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index 5645f0527f..f87b672ae0 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -462,36 +462,37 @@ SUBSYSTEM_DEF(job)
/datum/controller/subsystem/job/proc/HandleFeedbackGathering()
for(var/datum/job/job in occupations)
- var/tmp_str = "|[job.title]|"
-
- var/level1 = 0 //high
- var/level2 = 0 //medium
- var/level3 = 0 //low
- var/level4 = 0 //never
- var/level5 = 0 //banned
- var/level6 = 0 //account too young
+ var/high = 0 //high
+ var/medium = 0 //medium
+ var/low = 0 //low
+ var/never = 0 //never
+ var/banned = 0 //banned
+ var/young = 0 //account too young
for(var/mob/dead/new_player/player in GLOB.player_list)
if(!(player.ready == PLAYER_READY_TO_PLAY && player.mind && !player.mind.assigned_role))
continue //This player is not ready
if(jobban_isbanned(player, job.title))
- level5++
+ banned++
continue
if(!job.player_old_enough(player.client))
- level6++
+ young++
continue
if(job.required_playtime_remaining(player.client))
- level6++
+ young++
continue
if(player.client.prefs.GetJobDepartment(job, 1) & job.flag)
- level1++
+ high++
else if(player.client.prefs.GetJobDepartment(job, 2) & job.flag)
- level2++
+ medium++
else if(player.client.prefs.GetJobDepartment(job, 3) & job.flag)
- level3++
- else level4++ //not selected
-
- tmp_str += "HIGH=[level1]|MEDIUM=[level2]|LOW=[level3]|NEVER=[level4]|BANNED=[level5]|YOUNG=[level6]|-"
- SSblackbox.add_details("job_preferences",tmp_str)
+ low++
+ else never++ //not selected
+ SSblackbox.record_feedback("nested tally", "job_preferences", high, list("[job.title]", "high"))
+ SSblackbox.record_feedback("nested tally", "job_preferences", medium, list("[job.title]", "medium"))
+ SSblackbox.record_feedback("nested tally", "job_preferences", low, list("[job.title]", "low"))
+ SSblackbox.record_feedback("nested tally", "job_preferences", never, list("[job.title]", "never"))
+ SSblackbox.record_feedback("nested tally", "job_preferences", banned, list("[job.title]", "banned"))
+ SSblackbox.record_feedback("nested tally", "job_preferences", young, list("[job.title]", "young"))
/datum/controller/subsystem/job/proc/PopcapReached()
var/hpc = CONFIG_GET(number/hard_popcap)
diff --git a/code/controllers/subsystem/radiation.dm b/code/controllers/subsystem/radiation.dm
index 7883bf1d68..0b69e003fc 100644
--- a/code/controllers/subsystem/radiation.dm
+++ b/code/controllers/subsystem/radiation.dm
@@ -13,6 +13,6 @@ PROCESSING_SUBSYSTEM_DEF(radiation)
return
warned_atoms[ref] = TRUE
var/atom/master = contamination.parent
- SSblackbox.add_details("contaminated", "[master.type]")
+ SSblackbox.record_feedback("tally", "contaminated", 1, master.type)
var/msg = "has become contamintaed with enough radiation to contaminate other objects. || Source: [contamination.source] || Strength: [contamination.strength]"
master.investigate_log(msg, INVESTIGATE_RADIATION)
\ No newline at end of file
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index 5fa770179e..352492f835 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -167,7 +167,7 @@ SUBSYSTEM_DEF(shuttle)
var/mob/M = I
if(M.stat != DEAD)
++alive
-
+
var/total = GLOB.joined_player_list.len
if(alive / total <= threshold)
@@ -247,7 +247,7 @@ SUBSYSTEM_DEF(shuttle)
log_game("[key_name(user)] has called the shuttle.")
if(call_reason)
- SSblackbox.add_details("shuttle_reason", call_reason)
+ SSblackbox.record_feedback("text", "shuttle_reason", 1, "[call_reason]")
log_game("Shuttle call reason: [call_reason]")
message_admins("[key_name_admin(user)] has called the shuttle. (TRIGGER CENTCOM RECALL)")
diff --git a/code/controllers/subsystem/time_track.dm b/code/controllers/subsystem/time_track.dm
index fecba4769c..5ccfb8af19 100644
--- a/code/controllers/subsystem/time_track.dm
+++ b/code/controllers/subsystem/time_track.dm
@@ -35,4 +35,4 @@ SUBSYSTEM_DEF(time_track)
last_tick_realtime = current_realtime
last_tick_byond_time = current_byondtime
last_tick_tickcount = current_tickcount
- SSblackbox.add_details("time_dilation_current", time_dilation_current)
\ No newline at end of file
+ SSblackbox.record_feedback("tally", "time_dilation_current", 1, time_dilation_current)
diff --git a/code/datums/antagonists/changeling.dm b/code/datums/antagonists/changeling.dm
index d72cb64b81..5ba398af5e 100644
--- a/code/datums/antagonists/changeling.dm
+++ b/code/datums/antagonists/changeling.dm
@@ -179,7 +179,7 @@
to_chat(owner.current, "We have removed our evolutions from this form, and are now ready to readapt.")
reset_powers()
canrespec = 0
- SSblackbox.add_details("changeling_power_purchase","Readapt")
+ SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, "Readapt")
return 1
else
to_chat(owner.current, "You lack the power to readapt your evolutions!")
@@ -279,7 +279,7 @@
if(stored_profiles.len > dna_max)
if(!push_out_profile())
return
-
+
if(!first_prof)
first_prof = prof
diff --git a/code/datums/components/archaeology.dm b/code/datums/components/archaeology.dm
index 7c61613c41..d079950345 100644
--- a/code/datums/components/archaeology.dm
+++ b/code/datums/components/archaeology.dm
@@ -38,7 +38,7 @@
to_chat(user, "You dig a hole.")
gets_dug()
dug = TRUE
- SSblackbox.add_details("pick_used_mining",W.type)
+ SSblackbox.record_feedback("tally", "pick_used_mining", 1, W.type)
return TRUE
return FALSE
diff --git a/code/datums/helper_datums/construction_datum.dm b/code/datums/helper_datums/construction_datum.dm
index 7768a929da..313a93943c 100644
--- a/code/datums/helper_datums/construction_datum.dm
+++ b/code/datums/helper_datums/construction_datum.dm
@@ -68,6 +68,7 @@
var/obj/item/oldcell = locate (/obj/item/stock_parts/cell) in m
QDEL_NULL(oldcell)
m.CheckParts(holder.contents)
+ SSblackbox.record_feedback("tally", "mechas_created", 1, m.name)
QDEL_NULL(holder)
/datum/construction/proc/set_desc(index as num)
diff --git a/code/datums/helper_datums/getrev.dm b/code/datums/helper_datums/getrev.dm
index df22be910a..ec39bcec67 100644
--- a/code/datums/helper_datums/getrev.dm
+++ b/code/datums/helper_datums/getrev.dm
@@ -23,7 +23,7 @@
if(line)
var/tmcommit = testmerge[line]["commit"]
log_world("Test merge active of PR #[line] commit [tmcommit]")
- SSblackbox.add_details("testmerged_prs","[line]|[tmcommit]")
+ SSblackbox.record_feedback("nested tally", "testmerged_prs", 1, list("[line]", "[tmcommit]"))
log_world("Based off origin/master commit [originmastercommit]")
else
log_world(originmastercommit)
diff --git a/code/game/gamemodes/brother/traitor_bro.dm b/code/game/gamemodes/brother/traitor_bro.dm
index ff83a3764c..be662ed35e 100644
--- a/code/game/gamemodes/brother/traitor_bro.dm
+++ b/code/game/gamemodes/brother/traitor_bro.dm
@@ -1,6 +1,6 @@
/datum/objective_team/brother_team
- name = "brotherhood"
- member_name = "blood brother"
+ name = "brotherhood"
+ member_name = "blood brother"
var/list/objectives = list()
var/meeting_area
@@ -71,7 +71,7 @@
num_teams = max(1, round(num_players() / bsc))
for(var/j = 1 to num_teams)
- if(possible_brothers.len < min_team_size || antag_candidates.len <= required_enemies)
+ if(possible_brothers.len < min_team_size || antag_candidates.len <= required_enemies)
break
var/datum/objective_team/brother_team/team = new
var/team_size = prob(10) ? min(3, possible_brothers.len) : 2
@@ -114,18 +114,19 @@
for(var/datum/objective/objective in team.objectives)
if(objective.check_completion())
text += "
Objective #[objective_count]: [objective.explanation_text] Success!"
- SSblackbox.add_details("traitor_objective","[objective.type]|SUCCESS")
+ SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "SUCCESS"))
else
text += "
Objective #[objective_count]: [objective.explanation_text] Fail."
- SSblackbox.add_details("traitor_objective","[objective.type]|FAIL")
+ SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "FAIL"))
win = FALSE
objective_count++
if(win)
text += "
The blood brothers were successful!"
- SSblackbox.add_details("brother_success","SUCCESS")
+ SSblackbox.record_feedback("tally", "brother_success", 1, "SUCCESS")
else
text += "
The blood brothers have failed!"
- SSblackbox.add_details("brother_success","FAIL")
+ SSblackbox.record_feedback("tally", "brother_success", 1, "FAIL")
+
text += "
"
to_chat(world, text)
diff --git a/code/game/gamemodes/changeling/changeling.dm b/code/game/gamemodes/changeling/changeling.dm
index e84477b2cb..914937e9e3 100644
--- a/code/game/gamemodes/changeling/changeling.dm
+++ b/code/game/gamemodes/changeling/changeling.dm
@@ -113,6 +113,7 @@ GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our th
if(changeling.objectives.len)
var/count = 1
for(var/datum/objective/objective in changeling.objectives)
+<<<<<<< HEAD
if(istype(objective, /datum/objective/crew))
if(objective.check_completion())
text += "
Objective #[count]: [objective.explanation_text] Success! (Optional)"
@@ -128,14 +129,23 @@ GLOBAL_VAR(changeling_team_objective_type) //If this is not null, we hand our th
text += "
Objective #[count]: [objective.explanation_text] Fail."
SSblackbox.add_details("changeling_objective","[objective.type]|FAIL")
changelingwin = 0
+=======
+ if(objective.check_completion())
+ text += "
Objective #[count]: [objective.explanation_text] Success!"
+ SSblackbox.record_feedback("nested tally", "changeling_objective", 1, list("[objective.type]", "SUCCESS"))
+ else
+ text += "
Objective #[count]: [objective.explanation_text] Fail."
+ SSblackbox.record_feedback("nested tally", "changeling_objective", 1, list("[objective.type]", "FAIL"))
+ changelingwin = 0
+>>>>>>> 8b19b49... JSON feedback (#32188)
count++
if(changelingwin)
text += "
The changeling was successful!"
- SSblackbox.add_details("changeling_success","SUCCESS")
+ SSblackbox.record_feedback("tally", "changeling_success", 1, "SUCCESS")
else
text += "
The changeling has failed."
- SSblackbox.add_details("changeling_success","FAIL")
+ SSblackbox.record_feedback("tally", "changeling_success", 1, "FAIL")
text += "
"
to_chat(world, text)
diff --git a/code/game/gamemodes/changeling/changeling_power.dm b/code/game/gamemodes/changeling/changeling_power.dm
index ee6a0bee8c..5b07e0e369 100644
--- a/code/game/gamemodes/changeling/changeling_power.dm
+++ b/code/game/gamemodes/changeling/changeling_power.dm
@@ -19,7 +19,7 @@
/obj/effect/proc_holder/changeling/proc/on_purchase(mob/user, is_respec)
if(!is_respec)
- SSblackbox.add_details("changeling_power_purchase",name)
+ SSblackbox.record_feedback("tally", "changeling_power_purchase", 1, name)
/obj/effect/proc_holder/changeling/proc/on_refund(mob/user)
return
@@ -35,7 +35,7 @@
return
var/datum/antagonist/changeling/c = user.mind.has_antag_datum(/datum/antagonist/changeling)
if(sting_action(user, target))
- SSblackbox.add_details("changeling_powers",name)
+ SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]"))
sting_feedback(user, target)
c.chem_charges -= chemical_cost
diff --git a/code/game/gamemodes/changeling/powers/absorb.dm b/code/game/gamemodes/changeling/powers/absorb.dm
index d7ef971406..4584dc5a67 100644
--- a/code/game/gamemodes/changeling/powers/absorb.dm
+++ b/code/game/gamemodes/changeling/powers/absorb.dm
@@ -41,13 +41,13 @@
to_chat(target, "You feel a sharp stabbing pain!")
target.take_overall_damage(40)
- SSblackbox.add_details("changeling_powers","Absorb DNA|[i]")
+ SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "[i]"))
if(!do_mob(user, target, 150))
to_chat(user, "Our absorption of [target] has been interrupted!")
changeling.isabsorbing = 0
return
- SSblackbox.add_details("changeling_powers","Absorb DNA|4")
+ SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4"))
user.visible_message("[user] sucks the fluids from [target]!", "We have absorbed [target].")
to_chat(target, "You are absorbed by the changeling!")
diff --git a/code/game/gamemodes/changeling/powers/linglink.dm b/code/game/gamemodes/changeling/powers/linglink.dm
index 7a93306785..f5143f1d04 100644
--- a/code/game/gamemodes/changeling/powers/linglink.dm
+++ b/code/game/gamemodes/changeling/powers/linglink.dm
@@ -56,7 +56,7 @@
to_chat(target, "You can now communicate in the changeling hivemind, say \":g message\" to communicate!")
target.reagents.add_reagent("salbutamol", 40) // So they don't choke to death while you interrogate them
sleep(1800)
- SSblackbox.add_details("changeling_powers","Hivemind Link|[i]")
+ SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("[name]", "[i]"))
if(!do_mob(user, target, 20))
to_chat(user, "Our link with [target] has ended!")
changeling.islinking = 0
diff --git a/code/game/gamemodes/clock_cult/clock_scripture.dm b/code/game/gamemodes/clock_cult/clock_scripture.dm
index 8a019ed74b..94983923f9 100644
--- a/code/game/gamemodes/clock_cult/clock_scripture.dm
+++ b/code/game/gamemodes/clock_cult/clock_scripture.dm
@@ -66,7 +66,7 @@ Applications: 8 servants, 3 caches, and 100 CV
else
successful = TRUE
if(slab && !slab.no_cost && !GLOB.ratvar_awakens) //if the slab exists and isn't debug and ratvar isn't up, log the scripture as being used
- SSblackbox.add_details("clockcult_scripture_recited", name)
+ SSblackbox.record_feedback("tally", "clockcult_scripture_recited", 1, name)
if(slab)
slab.busy = null
post_recital()
diff --git a/code/game/gamemodes/cult/cult.dm b/code/game/gamemodes/cult/cult.dm
index 1391f1dbb5..f20dd25802 100644
--- a/code/game/gamemodes/cult/cult.dm
+++ b/code/game/gamemodes/cult/cult.dm
@@ -237,27 +237,27 @@
if("survive")
if(!check_survive())
explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. ([acolytes_survived] escaped) Success!"
- SSblackbox.add_details("cult_objective","cult_survive|SUCCESS|[acolytes_needed]")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_survive", "SUCCESS"))
SSticker.news_report = CULT_ESCAPE
else
explanation = "Make sure at least [acolytes_needed] acolytes escape on the shuttle. ([acolytes_survived] escaped) Fail."
- SSblackbox.add_details("cult_objective","cult_survive|FAIL|[acolytes_needed]")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_survive", "FAIL"))
SSticker.news_report = CULT_FAILURE
if("sacrifice")
if(GLOB.sac_complete)
explanation = "Sacrifice [GLOB.sac_mind], the [GLOB.sac_mind.assigned_role]. Success!"
- SSblackbox.add_details("cult_objective","cult_sacrifice|SUCCESS")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_sacrifice", "SUCCESS"))
else
explanation = "Sacrifice [GLOB.sac_mind], the [GLOB.sac_mind.assigned_role]. Fail."
- SSblackbox.add_details("cult_objective","cult_sacrifice|FAIL")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_sacrifice", "FAIL"))
if("eldergod")
if(!eldergod)
explanation = "Summon Nar-Sie. The summoning can only be accomplished in [english_list(GLOB.summon_spots)].Success!"
- SSblackbox.add_details("cult_objective","cult_narsie|SUCCESS")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_narsie", "SUCCESS"))
SSticker.news_report = CULT_SUMMON
else
explanation = "Summon Nar-Sie. The summoning can only be accomplished in [english_list(GLOB.summon_spots)]Fail."
- SSblackbox.add_details("cult_objective","cult_narsie|FAIL")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_narsie", "FAIL"))
SSticker.news_report = CULT_FAILURE
text += "
Objective #[obj_count]: [explanation]"
@@ -297,18 +297,18 @@
if(GLOB.sac_mind)
if(GLOB.sac_complete)
explanation = "Sacrifice [GLOB.sac_mind], the [GLOB.sac_mind.assigned_role]. Success!"
- SSblackbox.add_details("cult_objective","cult_sacrifice|SUCCESS")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_sacrifice", "SUCCESS"))
else
explanation = "Sacrifice [GLOB.sac_mind], the [GLOB.sac_mind.assigned_role]. Fail."
- SSblackbox.add_details("cult_objective","cult_sacrifice|FAIL")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_sacrifice", "FAIL"))
if("eldergod")
if(!eldergod)
explanation = "Summon Nar-Sie. Success!"
- SSblackbox.add_details("cult_objective","cult_narsie|SUCCESS")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_narsie", "SUCCESS"))
SSticker.news_report = CULT_SUMMON
else
explanation = "Summon Nar-Sie. Fail."
- SSblackbox.add_details("cult_objective","cult_narsie|FAIL")
+ SSblackbox.record_feedback("nested tally", "cult_objective", 1, list("cult_narsie", "FAIL"))
SSticker.news_report = CULT_FAILURE
text += "
Objective #[obj_count]: [explanation]"
to_chat(world, text)
diff --git a/code/game/gamemodes/cult/ritual.dm b/code/game/gamemodes/cult/ritual.dm
index 4dd5f70731..2adaa57a2f 100644
--- a/code/game/gamemodes/cult/ritual.dm
+++ b/code/game/gamemodes/cult/ritual.dm
@@ -246,7 +246,7 @@ This file contains the arcane tome files.
var/obj/effect/rune/R = new rune_to_scribe(Turf, chosen_keyword)
R.add_mob_blood(user)
to_chat(user, "The [lowertext(R.cultist_name)] rune [R.cultist_desc]")
- SSblackbox.add_details("cult_runes_scribed", R.cultist_name)
+ SSblackbox.record_feedback("tally", "cult_runes_scribed", 1, R.cultist_name)
/obj/item/tome/proc/check_rune_turf(turf/T, mob/user)
if(isspaceturf(T))
diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm
index 7909b0677f..50bde92f9e 100644
--- a/code/game/gamemodes/game_mode.dm
+++ b/code/game/gamemodes/game_mode.dm
@@ -266,18 +266,19 @@
if(isobserver(M))
ghosts++
- if(clients > 0)
- SSblackbox.set_val("round_end_clients",clients)
- if(ghosts > 0)
- SSblackbox.set_val("round_end_ghosts",ghosts)
- if(surviving_humans > 0)
- SSblackbox.set_val("survived_human",surviving_humans)
- if(surviving_total > 0)
- SSblackbox.set_val("survived_total",surviving_total)
- if(escaped_humans > 0)
- SSblackbox.set_val("escaped_human",escaped_humans)
- if(escaped_total > 0)
- SSblackbox.set_val("escaped_total",escaped_total)
+ if(clients)
+ SSblackbox.record_feedback("nested tally", "round_end_stats", clients, list("clients"))
+ if(ghosts)
+ SSblackbox.record_feedback("nested tally", "round_end_stats", ghosts, list("ghosts"))
+ if(surviving_humans)
+ SSblackbox.record_feedback("nested tally", "round_end_stats", surviving_humans, list("survivors", "human"))
+ if(surviving_total)
+ SSblackbox.record_feedback("nested tally", "round_end_stats", surviving_total, list("survivors", "total"))
+ if(escaped_humans)
+ SSblackbox.record_feedback("nested tally", "round_end_stats", escaped_humans, list("escapees", "human"))
+ if(escaped_total)
+ SSblackbox.record_feedback("nested tally", "round_end_stats", escaped_total, list("escapees", "total"))
+
send2irc("Server", "Round just ended.")
if(cult.len && !istype(SSticker.mode, /datum/game_mode/cult))
datum_cult_completion()
diff --git a/code/game/gamemodes/nuclear/nuclear_challenge.dm b/code/game/gamemodes/nuclear/nuclear_challenge.dm
index 6e7a567324..c9314ece1a 100644
--- a/code/game/gamemodes/nuclear/nuclear_challenge.dm
+++ b/code/game/gamemodes/nuclear/nuclear_challenge.dm
@@ -59,7 +59,8 @@
U.hidden_uplink.telecrystals = CHALLENGE_TELECRYSTALS
U.hidden_uplink.set_gamemode(/datum/game_mode/nuclear)
CONFIG_SET(number/shuttle_refuel_delay, max(CONFIG_GET(number/shuttle_refuel_delay), CHALLENGE_SHUTTLE_DELAY))
- SSblackbox.set_val("nuclear_challenge_mode",1)
+ SSblackbox.record_feedback("amount", "nuclear_challenge_mode", 1)
+
qdel(src)
/obj/item/device/nuclear_challenge/proc/check_allowed(mob/living/user)
diff --git a/code/game/gamemodes/traitor/traitor.dm b/code/game/gamemodes/traitor/traitor.dm
index a97e5a924b..9bb2013947 100644
--- a/code/game/gamemodes/traitor/traitor.dm
+++ b/code/game/gamemodes/traitor/traitor.dm
@@ -113,6 +113,7 @@
if(traitor.objectives.len)//If the traitor had no objectives, don't need to process this.
var/count = 1
for(var/datum/objective/objective in traitor.objectives)
+<<<<<<< HEAD
if(istype(objective, /datum/objective/crew))
if(objective.check_completion())
objectives += "
Objective #[count]: [objective.explanation_text] Success! (Optional)"
@@ -120,9 +121,14 @@
else
objectives += "
Objective #[count]: [objective.explanation_text] Fail. (Optional)"
SSblackbox.add_details("traitor_objective","[objective.type]|FAIL")
+=======
+ if(objective.check_completion())
+ objectives += "
Objective #[count]: [objective.explanation_text] Success!"
+ SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "SUCCESS"))
+>>>>>>> 8b19b49... JSON feedback (#32188)
else
objectives += "
Objective #[count]: [objective.explanation_text] Fail."
- SSblackbox.add_details("traitor_objective","[objective.type]|FAIL")
+ SSblackbox.record_feedback("nested tally", "traitor_objective", 1, list("[objective.type]", "FAIL"))
traitorwin = FALSE
count++
@@ -143,10 +149,10 @@
if(traitorwin)
text += "
The [special_role_text] was successful!"
- SSblackbox.add_details("traitor_success","SUCCESS")
+ SSblackbox.record_feedback("tally", "traitor_success", 1, "SUCCESS")
else
text += "
The [special_role_text] has failed!"
- SSblackbox.add_details("traitor_success","FAIL")
+ SSblackbox.record_feedback("tally", "traitor_success", 1, "FAIL")
SEND_SOUND(traitor.current, 'sound/ambience/ambifailure.ogg')
text += "
"
diff --git a/code/game/gamemodes/wizard/spellbook.dm b/code/game/gamemodes/wizard/spellbook.dm
index 5b87255d37..4a407a4ef6 100644
--- a/code/game/gamemodes/wizard/spellbook.dm
+++ b/code/game/gamemodes/wizard/spellbook.dm
@@ -57,10 +57,10 @@
aspell.name = "Instant [aspell.name]"
if(aspell.spell_level >= aspell.level_max)
to_chat(user, "This spell cannot be strengthened any further.")
- SSblackbox.add_details("wizard_spell_improved", "[name]|[aspell.level]")
+ SSblackbox.record_feedback("nested tally", "wizard_spell_improved", 1, list("[name]", "[aspell.level]"))
return 1
//No same spell found - just learn it
- SSblackbox.add_details("wizard_spell_learned", name)
+ SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
user.mind.AddSpell(S)
to_chat(user, "You have learned [S.name].")
return 1
@@ -265,7 +265,7 @@
/datum/spellbook_entry/item/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
new item_path(get_turf(user))
- SSblackbox.add_details("wizard_spell_learned", name)
+ SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
return 1
/datum/spellbook_entry/item/GetInfo()
@@ -464,7 +464,7 @@
return TRUE
/datum/spellbook_entry/summon/ghosts/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
- SSblackbox.add_details("wizard_spell_learned", name)
+ SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
new /datum/round_event/wizard/ghost()
active = TRUE
to_chat(user, "You have cast summon ghosts!")
@@ -481,7 +481,7 @@
return !CONFIG_GET(flag/no_summon_guns)
/datum/spellbook_entry/summon/guns/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- SSblackbox.add_details("wizard_spell_learned", name)
+ SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
rightandwrong(0, user, 25)
active = 1
playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1)
@@ -498,7 +498,7 @@
return !CONFIG_GET(flag/no_summon_magic)
/datum/spellbook_entry/summon/magic/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- SSblackbox.add_details("wizard_spell_learned", name)
+ SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
rightandwrong(1, user, 25)
active = 1
playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1)
@@ -516,7 +516,7 @@
return !CONFIG_GET(flag/no_summon_events)
/datum/spellbook_entry/summon/events/Buy(mob/living/carbon/human/user,obj/item/spellbook/book)
- SSblackbox.add_details("wizard_spell_learned", name)
+ SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
summonevents()
times++
playsound(get_turf(user), 'sound/magic/castsummon.ogg', 50, 1)
diff --git a/code/game/gamemodes/wizard/wizard.dm b/code/game/gamemodes/wizard/wizard.dm
index 304f80b470..dc392946da 100644
--- a/code/game/gamemodes/wizard/wizard.dm
+++ b/code/game/gamemodes/wizard/wizard.dm
@@ -89,6 +89,7 @@
var/count = 1
var/wizardwin = 1
for(var/datum/objective/objective in wizard.objectives)
+<<<<<<< HEAD
if(istype(objective, /datum/objective/crew))
if(objective.check_completion())
text += "
Objective #[count]: [objective.explanation_text] Success! (Optional)"
@@ -104,14 +105,23 @@
text += "
Objective #[count]: [objective.explanation_text] Fail."
SSblackbox.add_details("wizard_objective","[objective.type]|FAIL")
wizardwin = 0
+=======
+ if(objective.check_completion())
+ text += "
Objective #[count]: [objective.explanation_text] Success!"
+ SSblackbox.record_feedback("nested tally", "wizard_objective", 1, list("[objective.type]", "SUCCESS"))
+ else
+ text += "
Objective #[count]: [objective.explanation_text] Fail."
+ SSblackbox.record_feedback("nested tally", "wizard_objective", 1, list("[objective.type]", "FAIL"))
+ wizardwin = 0
+>>>>>>> 8b19b49... JSON feedback (#32188)
count++
if(wizard.current && wizard.current.stat!=2 && wizardwin)
text += "
The wizard was successful!"
- SSblackbox.add_details("wizard_success","SUCCESS")
+ SSblackbox.record_feedback("tally", "wizard_success", 1, "SUCCESS")
else
text += "
The wizard has failed!"
- SSblackbox.add_details("wizard_success","FAIL")
+ SSblackbox.record_feedback("tally", "wizard_success", 1, "FAIL")
if(wizard.spell_list.len>0)
text += "
[wizard.name] used the following spells: "
var/i = 1
diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm
index 4203971522..89a4e8b130 100644
--- a/code/game/machinery/computer/arcade.dm
+++ b/code/game/machinery/computer/arcade.dm
@@ -229,7 +229,6 @@
playsound(loc, 'sound/arcade/win.ogg', 50, 1, extrarange = -3, falloff = 10)
if(emagged)
- SSblackbox.inc("arcade_win_emagged")
new /obj/effect/spawner/newbomb/timer/syndicate(loc)
new /obj/item/clothing/head/collectable/petehat(loc)
message_admins("[key_name_admin(usr)] has outbombed Cuban Pete and been awarded a bomb.")
@@ -237,8 +236,9 @@
Reset()
emagged = FALSE
else
- SSblackbox.inc("arcade_win_normal")
prizevend()
+ SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("win", (emagged ? "emagged":"normal")))
+
else if (emagged && (turtle >= 4))
var/boomamt = rand(5,10)
@@ -259,10 +259,8 @@
temp = "You have been drained! GAME OVER"
playsound(loc, 'sound/arcade/lose.ogg', 50, 1, extrarange = -3, falloff = 10)
if(emagged)
- SSblackbox.inc("arcade_loss_mana_emagged")
usr.gib()
- else
- SSblackbox.inc("arcade_loss_mana_normal")
+ SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "mana", (emagged ? "emagged":"normal")))
else if ((enemy_hp <= 10) && (enemy_mp > 4))
temp = "[enemy_name] heals for 4 health!"
@@ -281,10 +279,8 @@
temp = "You have been crushed! GAME OVER"
playsound(loc, 'sound/arcade/lose.ogg', 50, 1, extrarange = -3, falloff = 10)
if(emagged)
- SSblackbox.inc("arcade_loss_hp_emagged")
usr.gib()
- else
- SSblackbox.inc("arcade_loss_hp_normal")
+ SSblackbox.record_feedback("nested tally", "arcade_results", 1, list("loss", "hp", (emagged ? "emagged":"normal")))
blocked = FALSE
return
diff --git a/code/game/machinery/computer/communications.dm b/code/game/machinery/computer/communications.dm
index ed8a2f3aec..6223011736 100755
--- a/code/game/machinery/computer/communications.dm
+++ b/code/game/machinery/computer/communications.dm
@@ -115,11 +115,6 @@
//Only notify the admins if an actual change happened
log_game("[key_name(usr)] has changed the security level to [get_security_level()].")
message_admins("[key_name_admin(usr)] has changed the security level to [get_security_level()].")
- switch(GLOB.security_level)
- if(SEC_LEVEL_GREEN)
- SSblackbox.inc("alert_comms_green",1)
- if(SEC_LEVEL_BLUE)
- SSblackbox.inc("alert_comms_blue",1)
tmp_alertlevel = 0
else
to_chat(usr, "You are not authorized to do this!")
@@ -178,7 +173,7 @@
SSshuttle.points -= S.credit_cost
minor_announce("[usr.name] has purchased [S.name] for [S.credit_cost] credits." , "Shuttle Purchase")
message_admins("[key_name_admin(usr)] purchased [S.name].")
- SSblackbox.add_details("shuttle_purchase", S.name)
+ SSblackbox.record_feedback("text", "shuttle_purchase", 1, "[S.name]")
else
to_chat(usr, "Something went wrong! The shuttle exchange system seems to be down.")
else
@@ -387,11 +382,6 @@
//Only notify the admins if an actual change happened
log_game("[key_name(usr)] has changed the security level to [get_security_level()].")
message_admins("[key_name_admin(usr)] has changed the security level to [get_security_level()].")
- switch(GLOB.security_level)
- if(SEC_LEVEL_GREEN)
- SSblackbox.inc("alert_comms_green",1)
- if(SEC_LEVEL_BLUE)
- SSblackbox.inc("alert_comms_blue",1)
tmp_alertlevel = 0
aistate = STATE_DEFAULT
if("ai-changeseclevel")
diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm
index 577e7d23eb..18acd9ece7 100644
--- a/code/game/machinery/newscaster.dm
+++ b/code/game/machinery/newscaster.dm
@@ -533,7 +533,7 @@ GLOBAL_LIST_EMPTY(allCasters)
if(choice=="Confirm")
scan_user(usr)
GLOB.news_network.CreateFeedChannel(channel_name, scanned_user, c_locked)
- SSblackbox.inc("newscaster_channels",1)
+ SSblackbox.record_feedback("text", "newscaster_channels", 1, "[channel_name]")
screen=5
updateUsrDialog()
else if(href_list["set_channel_receiving"])
@@ -556,7 +556,7 @@ GLOBAL_LIST_EMPTY(allCasters)
screen=6
else
GLOB.news_network.SubmitArticle("[parsemarkdown(msg, usr)]", scanned_user, channel_name, photo, 0, allow_comments)
- SSblackbox.inc("newscaster_stories",1)
+ SSblackbox.record_feedback("amount", "newscaster_stories", 1)
screen=4
msg = ""
updateUsrDialog()
@@ -846,7 +846,7 @@ GLOBAL_LIST_EMPTY(allCasters)
return
/obj/machinery/newscaster/proc/print_paper()
- SSblackbox.inc("newscaster_newspapers_printed",1)
+ SSblackbox.record_feedback("amount", "newspapers_printed", 1)
var/obj/item/newspaper/NEWSPAPER = new /obj/item/newspaper
for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels)
NEWSPAPER.news_content += FC
diff --git a/code/game/machinery/telecomms/broadcasting.dm b/code/game/machinery/telecomms/broadcasting.dm
index 8ede0a7852..aebccdddd2 100644
--- a/code/game/machinery/telecomms/broadcasting.dm
+++ b/code/game/machinery/telecomms/broadcasting.dm
@@ -142,9 +142,7 @@
if(length(receive))
// --- This following recording is intended for research and feedback in the use of department radio channels ---
-
- var/blackbox_msg = "[AM] [AM.say_quote(message, spans)]"
- SSblackbox.LogBroadcast(blackbox_msg, freq)
+ SSblackbox.LogBroadcast(freq)
sleep(50)
if(!QDELETED(virt)) //It could happen to YOU
diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm
index 89fc1260f8..45bf5ab1c8 100644
--- a/code/game/machinery/vending.dm
+++ b/code/game/machinery/vending.dm
@@ -510,7 +510,7 @@
if(icon_vend) //Show the vending animation if needed
flick(icon_vend,src)
new R.product_path(get_turf(src))
- SSblackbox.add_details("vending_machine_usage","[type]|[R.product_path]")
+ SSblackbox.record_feedback("nested tally", "vending_machine_usage", 1, list("[type]", "[R.product_path]"))
vend_ready = 1
return
diff --git a/code/game/mecha/mecha_construction_paths.dm b/code/game/mecha/mecha_construction_paths.dm
index d8f435190f..c8de01badc 100644
--- a/code/game/mecha/mecha_construction_paths.dm
+++ b/code/game/mecha/mecha_construction_paths.dm
@@ -304,13 +304,6 @@
holder.icon_state = "ripley14"
return TRUE
-/datum/construction/reversible/mecha/ripley/spawn_mecha_result()
- ..()
- SSblackbox.inc("mecha_ripley_created",1)
- return
-
-
-
/datum/construction/mecha/gygax_chassis
steps = list(list("key"=/obj/item/mecha_parts/part/gygax_torso), //1
list("key"=/obj/item/mecha_parts/part/gygax_left_arm), //2
@@ -617,11 +610,6 @@
holder.icon_state = "gygax20"
return TRUE
-/datum/construction/reversible/mecha/gygax/spawn_mecha_result()
- ..()
- SSblackbox.inc("mecha_gygax_created",1)
- return
-
/datum/construction/mecha/firefighter_chassis
steps = list(list("key"=/obj/item/mecha_parts/part/ripley_torso), //1
list("key"=/obj/item/mecha_parts/part/ripley_left_arm), //2
@@ -866,13 +854,6 @@
holder.icon_state = "fireripley15"
return TRUE
-/datum/construction/reversible/mecha/firefighter/spawn_mecha_result()
- ..()
- SSblackbox.inc("mecha_firefighter_created",1)
- return
-
-
-
/datum/construction/mecha/honker_chassis
steps = list(list("key"=/obj/item/mecha_parts/part/honker_torso), //1
list("key"=/obj/item/mecha_parts/part/honker_left_arm), //2
@@ -953,10 +934,6 @@
qdel(used_atom)
return TRUE
-/datum/construction/mecha/honker/spawn_mecha_result()
- ..()
- SSblackbox.inc("mecha_honker_created",1)
-
/datum/construction/mecha/durand_chassis
steps = list(list("key"=/obj/item/mecha_parts/part/durand_torso), //1
list("key"=/obj/item/mecha_parts/part/durand_left_arm), //2
@@ -1264,10 +1241,6 @@
holder.icon_state = "durand20"
return TRUE
-/datum/construction/reversible/mecha/durand/spawn_mecha_result()
- ..()
- SSblackbox.inc("mecha_durand_created",1)
-
//PHAZON
/datum/construction/mecha/phazon_chassis
@@ -1620,10 +1593,6 @@
spawn_mecha_result()
return TRUE
-/datum/construction/reversible/mecha/phazon/spawn_mecha_result()
- ..()
- SSblackbox.inc("mecha_phazon_created",1)
-
//ODYSSEUS
/datum/construction/mecha/odysseus_chassis
@@ -1857,7 +1826,3 @@
user.visible_message("[user] unfastens the external armor layer.", "You unfasten the external armor layer.")
holder.icon_state = "odysseus14"
return TRUE
-
-/datum/construction/reversible/mecha/odysseus/spawn_mecha_result()
- ..()
- SSblackbox.inc("mecha_odysseus_created",1)
diff --git a/code/game/objects/items/charter.dm b/code/game/objects/items/charter.dm
index 2527b573d3..c550a0c872 100644
--- a/code/game/objects/items/charter.dm
+++ b/code/game/objects/items/charter.dm
@@ -45,7 +45,7 @@
if(response_timer_id)
to_chat(user, "You're still waiting for approval from your employers about your proposed name change, it'd be best to wait for now.")
return
-
+
if(!new_name)
return
log_game("[key_name(user)] has proposed to name the station as \
@@ -85,7 +85,7 @@
name = "station charter for [station_name()]"
desc = "An official document entrusting the governance of \
[station_name()] and surrounding space to Captain [uname]."
- SSblackbox.set_details("station_renames","[station_name()]")
+ SSblackbox.record_feedback("text", "station_renames", 1, "[station_name()]")
if(!unlimited_uses)
used = TRUE
@@ -112,7 +112,7 @@
log_game("[ukey] has renamed the planet as [station_name()].")
name = "banner of [station_name()]"
desc = "The banner bears the official coat of arms of Nanotrasen, signifying that [station_name()] has been claimed by Captain [uname] in the name of the company."
- SSblackbox.set_details("station_renames","[station_name()]")
+ SSblackbox.record_feedback("text", "station_renames", 1, "[station_name()]")
if(!unlimited_uses)
used = TRUE
diff --git a/code/game/objects/items/handcuffs.dm b/code/game/objects/items/handcuffs.dm
index 8ebb7b6061..3085019b51 100644
--- a/code/game/objects/items/handcuffs.dm
+++ b/code/game/objects/items/handcuffs.dm
@@ -47,7 +47,7 @@
if(do_mob(user, C, 30) && (C.get_num_arms() >= 2 || C.get_arm_ignore()))
apply_cuffs(C,user)
to_chat(user, "You handcuff [C].")
- SSblackbox.add_details("handcuffs","[type]")
+ SSblackbox.record_feedback("tally", "handcuffs", 1, type)
add_logs(user, C, "handcuffed")
else
@@ -298,7 +298,7 @@
C.legcuffed = src
src.loc = C
C.update_inv_legcuffed()
- SSblackbox.add_details("handcuffs","[type]")
+ SSblackbox.record_feedback("tally", "handcuffs", 1, type)
else if(isanimal(L))
var/mob/living/simple_animal/SA = L
if(SA.mob_size > MOB_SIZE_TINY)
@@ -359,7 +359,7 @@
C.legcuffed = src
src.loc = C
C.update_inv_legcuffed()
- SSblackbox.add_details("handcuffs","[type]")
+ SSblackbox.record_feedback("tally", "handcuffs", 1, type)
to_chat(C, "\The [src] ensnares you!")
C.Knockdown(knockdown)
diff --git a/code/game/objects/items/holy_weapons.dm b/code/game/objects/items/holy_weapons.dm
index a59f655b46..cde3f127ed 100644
--- a/code/game/objects/items/holy_weapons.dm
+++ b/code/game/objects/items/holy_weapons.dm
@@ -42,7 +42,7 @@
SSreligion.holy_weapon_type = holy_weapon.type
- SSblackbox.set_details("chaplain_weapon","[choice]")
+ SSblackbox.record_feedback("tally", "chaplain_weapon", 1, "[choice]")
if(holy_weapon)
holy_weapon.reskinned = TRUE
diff --git a/code/game/objects/items/robot/robot_parts.dm b/code/game/objects/items/robot/robot_parts.dm
index 9c0199e70f..09e3ccdeb0 100644
--- a/code/game/objects/items/robot/robot_parts.dm
+++ b/code/game/objects/items/robot/robot_parts.dm
@@ -58,7 +58,7 @@
if(src.l_arm && src.r_arm)
if(src.l_leg && src.r_leg)
if(src.chest && src.head)
- SSblackbox.inc("cyborg_frames_built",1)
+ SSblackbox.record_feedback("amount", "cyborg_frames_built", 1)
return 1
return 0
@@ -234,9 +234,7 @@
qdel(O.mmi)
O.mmi = W //and give the real mmi to the borg.
O.updatename()
-
- SSblackbox.inc("cyborg_birth",1)
-
+ SSblackbox.record_feedback("amount", "cyborg_birth", 1)
forceMove(O)
O.robot_suit = src
@@ -335,4 +333,3 @@
add_fingerprint(usr)
Interact(usr)
-
diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm
index 0712fec228..a00e97b8fe 100644
--- a/code/game/objects/items/storage/book.dm
+++ b/code/game/objects/items/storage/book.dm
@@ -65,7 +65,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "bible",
SSreligion.bible_icon_state = B.icon_state
SSreligion.bible_item_state = B.item_state
- SSblackbox.set_details("religion_book","[biblename]")
+ SSblackbox.record_feedback("text", "religion_book", 1, "[biblename]")
usr << browse(null, "window=editicon")
/obj/item/storage/book/bible/proc/bless(mob/living/carbon/human/H, mob/living/user)
diff --git a/code/game/objects/structures/ai_core.dm b/code/game/objects/structures/ai_core.dm
index d01cd5a191..4a529571a4 100644
--- a/code/game/objects/structures/ai_core.dm
+++ b/code/game/objects/structures/ai_core.dm
@@ -185,7 +185,7 @@
if(brain.force_replace_ai_name)
A.fully_replace_character_name(A.name, brain.replacement_ai_name())
- SSblackbox.inc("cyborg_ais_created",1)
+ SSblackbox.record_feedback("amount", "ais_created", 1)
qdel(src)
else
state = AI_READY_CORE
diff --git a/code/game/turfs/simulated/minerals.dm b/code/game/turfs/simulated/minerals.dm
index 1a5d0b78ec..cbd5b23bf3 100644
--- a/code/game/turfs/simulated/minerals.dm
+++ b/code/game/turfs/simulated/minerals.dm
@@ -66,7 +66,7 @@
if(ismineralturf(src))
to_chat(user, "You finish cutting into the rock.")
gets_drilled(user)
- SSblackbox.add_details("pick_used_mining","[P.type]")
+ SSblackbox.record_feedback("tally", "pick_used_mining", 1, P.type)
else
return attack_hand(user)
@@ -75,7 +75,7 @@
var/i
for(i in 1 to mineralAmt)
new mineralType(src)
- SSblackbox.add_details("ore_mined",mineralType)
+ SSblackbox.record_feedback("tally", "ore_mined", 1, mineralType)
for(var/obj/effect/temp_visual/mining_overlay/M in src)
qdel(M)
ChangeTurf(turf_type, FALSE, defer_change)
diff --git a/code/modules/admin/NewBan.dm b/code/modules/admin/NewBan.dm
index bd32533a7f..ef222f683d 100644
--- a/code/modules/admin/NewBan.dm
+++ b/code/modules/admin/NewBan.dm
@@ -146,7 +146,6 @@ GLOBAL_PROTECT(Banlist)
ban_unban_log_save("[key_name(usr)] unbanned [key]")
log_admin_private("[key_name(usr)] unbanned [key]")
message_admins("[key_name_admin(usr)] unbanned: [key]")
- SSblackbox.inc("ban_unban",1)
usr.client.holder.DB_ban_unban( ckey(key), BANTYPE_ANY_FULLBAN)
for (var/A in GLOB.Banlist.dir)
GLOB.Banlist.cd = "/base/[A]"
@@ -235,4 +234,3 @@ GLOBAL_PROTECT(Banlist)
GLOB.Banlist.cd = "/base"
for (var/A in GLOB.Banlist.dir)
RemoveBan(A)
-
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 3edd7e810c..3ee99900de 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -173,7 +173,7 @@
body += "