From 337ef499a1b92ea269ad3e9aaf82a53bed60920b Mon Sep 17 00:00:00 2001 From: Neerti Date: Wed, 5 Oct 2016 21:40:07 -0400 Subject: [PATCH] Cleans Up WIP Event System + New Grid Check For It Separates the 'count and assess everything' stuff to it's own datum, called the metric datum, which I plan to add on to in the future to make counting and metrics easier. Makes decision process a bit more weight-based, will probably continue tweaking later. Makes the admin debug UI have links to change settings easily. Adds replacement for grid check event, which works similar to the old one, but is now based on a physical machine in the game world, that Engineering can hack to make the event end faster, if so desired. Note that the machine is not mapped in, and won't be mapped in until the event system is ready for launch. Adds grid_check variables to SMESes and APCs to make them stop doing work without draining the battery. Grid checks in the new system are caused by a "power spike" which originates from the engine and will cause bad things, should no grid checker machine be connected to the power-net. These power spikes occur when the GM decides that a grid check is a good event to have. The grid checker can be built and deconstructed using the standard machine construction methods. --- code/__defines/misc.dm | 12 ++ code/datums/wires/grid_checker.dm | 66 +++++++++ .../weapons/circuitboards/machinery/power.dm | 7 + code/global.dm | 1 + .../gamemaster/actions/comms_blackout.dm | 5 +- code/modules/gamemaster/actions/grid_check.dm | 17 ++- .../gamemaster/actions/waste_disposal.dm | 5 +- code/modules/gamemaster/controller.dm | 58 ++++++-- code/modules/gamemaster/defines.dm | 11 +- code/modules/gamemaster/game_master.dm | 117 ++++------------ code/modules/gamemaster/helpers.dm | 65 +-------- code/modules/metric/activity.dm | 82 ++++++++++++ code/modules/metric/department.dm | 72 ++++++++++ code/modules/metric/metric.dm | 15 +++ code/modules/power/apc.dm | 37 +++++- code/modules/power/generator.dm | 5 + code/modules/power/grid_checker.dm | 125 ++++++++++++++++++ code/modules/power/power.dm | 23 ++++ code/modules/power/smes.dm | 10 +- code/modules/power/terminal.dm | 4 + code/modules/research/designs.dm | 8 ++ icons/obj/power.dmi | Bin 28684 -> 29270 bytes polaris.dme | 5 + 23 files changed, 564 insertions(+), 186 deletions(-) create mode 100644 code/datums/wires/grid_checker.dm create mode 100644 code/modules/metric/activity.dm create mode 100644 code/modules/metric/department.dm create mode 100644 code/modules/metric/metric.dm create mode 100644 code/modules/power/grid_checker.dm diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index 68954b0dc5..6a009677e1 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -167,3 +167,15 @@ #define ANTAG_HIDDEN "Hidden" #define ANTAG_SHARED "Shared" #define ANTAG_KNOWN "Known" + +// Job groups +#define ROLE_COMMAND "command" +#define ROLE_SECURITY "security" +#define ROLE_ENGINEERING "engineering" +#define ROLE_MEDICAL "medical" +#define ROLE_RESEARCH "research" +#define ROLE_CARGO "cargo" +#define ROLE_CIVILIAN "civilian" +#define ROLE_SYNTHETIC "synthetic" +#define ROLE_UNKNOWN "unknown" +#define ROLE_EVERYONE "everyone" diff --git a/code/datums/wires/grid_checker.dm b/code/datums/wires/grid_checker.dm new file mode 100644 index 0000000000..1e09be577e --- /dev/null +++ b/code/datums/wires/grid_checker.dm @@ -0,0 +1,66 @@ +/datum/wires/grid_checker + holder_type = /obj/machinery/power/grid_checker + wire_count = 8 + +var/const/GRID_CHECKER_WIRE_REBOOT = 1 // This wire causes the grid-check to end, if pulsed. +var/const/GRID_CHECKER_WIRE_LOCKOUT = 2 // If cut or pulsed, locks the user out for half a minute. +var/const/GRID_CHECKER_WIRE_ALLOW_MANUAL_1 = 4 // Needs to be cut for REBOOT to be possible. +var/const/GRID_CHECKER_WIRE_ALLOW_MANUAL_2 = 8 // Needs to be cut for REBOOT to be possible. +var/const/GRID_CHECKER_WIRE_ALLOW_MANUAL_3 = 16 // Needs to be cut for REBOOT to be possible. +var/const/GRID_CHECKER_WIRE_SHOCK = 32 // Shocks the user if not wearing gloves. +var/const/GRID_CHECKER_WIRE_NOTHING_1 = 64 // Does nothing, but makes it a bit harder. +var/const/GRID_CHECKER_WIRE_NOTHING_2 = 128 // Does nothing, but makes it a bit harder. + + +/datum/wires/grid_checker/CanUse(var/mob/living/L) + var/obj/machinery/power/grid_checker/G = holder + if(G.opened) + return TRUE + return FALSE + + +/datum/wires/grid_checker/GetInteractWindow() + var/obj/machinery/power/grid_checker/G = holder + . += ..() + . += "The green light is [G.power_failing ? "off" : "on"].
" + . += "The red light is [G.wire_locked_out ? "on" : "off"].
" + . += "The blue light is [G.wire_allow_manual_1 && G.wire_allow_manual_2 && G.wire_allow_manual_3 ? "on" : "off"]." + + +/datum/wires/grid_checker/UpdateCut(var/index, var/mended) + var/obj/machinery/power/grid_checker/G = holder + switch(index) + if(GRID_CHECKER_WIRE_LOCKOUT) + G.wire_locked_out = !mended + if(GRID_CHECKER_WIRE_ALLOW_MANUAL_1) + G.wire_allow_manual_1 = !mended + if(GRID_CHECKER_WIRE_ALLOW_MANUAL_2) + G.wire_allow_manual_2 = !mended + if(GRID_CHECKER_WIRE_ALLOW_MANUAL_3) + G.wire_allow_manual_3 = !mended + if(GRID_CHECKER_WIRE_SHOCK) + if(G.wire_locked_out) + return + G.shock(usr, 70) + + +/datum/wires/grid_checker/UpdatePulsed(var/index) + var/obj/machinery/power/grid_checker/G = holder + switch(index) + if(GRID_CHECKER_WIRE_REBOOT) + if(G.wire_locked_out) + return + + if(G.power_failing && G.wire_allow_manual_1 && G.wire_allow_manual_2 && G.wire_allow_manual_3) + G.end_power_failure(TRUE) + if(GRID_CHECKER_WIRE_LOCKOUT) + if(G.wire_locked_out) + return + + G.wire_locked_out = TRUE + spawn(30 SECONDS) + G.wire_locked_out = FALSE + if(GRID_CHECKER_WIRE_SHOCK) + if(G.wire_locked_out) + return + G.shock(usr, 70) \ No newline at end of file diff --git a/code/game/objects/items/weapons/circuitboards/machinery/power.dm b/code/game/objects/items/weapons/circuitboards/machinery/power.dm index 6e5667ebe5..57d8822df6 100644 --- a/code/game/objects/items/weapons/circuitboards/machinery/power.dm +++ b/code/game/objects/items/weapons/circuitboards/machinery/power.dm @@ -22,3 +22,10 @@ build_path = /obj/machinery/power/smes/batteryrack/makeshift board_type = new /datum/frame/frame_types/machine req_components = list(/obj/item/weapon/cell = 3) + +/obj/item/weapon/circuitboard/grid_checker + name = T_BOARD("power grid checker") + build_path = /obj/machinery/power/grid_checker + board_type = new /datum/frame/frame_types/machine + origin_tech = list(TECH_POWER = 4, TECH_ENGINEERING = 3) + req_components = list(/obj/item/weapon/stock_parts/capacitor = 3, /obj/item/stack/cable_coil = 10) diff --git a/code/global.dm b/code/global.dm index cd27fbc8ec..70171ac081 100644 --- a/code/global.dm +++ b/code/global.dm @@ -119,6 +119,7 @@ var/join_motd = null var/datum/nanomanager/nanomanager = new() // NanoManager, the manager for Nano UIs. var/datum/event_manager/event_manager = new() // Event Manager, the manager for events. var/datum/game_master/game_master = new() // Game Master, an AI for choosing events. +var/datum/metric/metric = new() // Metric datum, used to keep track of the round. var/list/awaydestinations = list() // Away missions. A list of landmarks that the warpgate can take you to. diff --git a/code/modules/gamemaster/actions/comms_blackout.dm b/code/modules/gamemaster/actions/comms_blackout.dm index 77814c3f93..75359085ac 100644 --- a/code/modules/gamemaster/actions/comms_blackout.dm +++ b/code/modules/gamemaster/actions/comms_blackout.dm @@ -3,4 +3,7 @@ /datum/gm_action/comms_blackout name = "communications blackout" departments = list(ROLE_ENGINEERING, ROLE_EVERYONE) - chaotic = 35 \ No newline at end of file + chaotic = 35 + +/datum/gm_action/comms_blackout/get_weight() + return 50 + (metric.count_people_in_department(ROLE_ENGINEERING) * 40) \ No newline at end of file diff --git a/code/modules/gamemaster/actions/grid_check.dm b/code/modules/gamemaster/actions/grid_check.dm index f46f0b162d..08c817fdf9 100644 --- a/code/modules/gamemaster/actions/grid_check.dm +++ b/code/modules/gamemaster/actions/grid_check.dm @@ -1,9 +1,22 @@ // New grid check event: // Very similar to the old one, power goes out in most of the colony, however the new feature is the ability for engineering to // get power back on sooner, if they are able to reach a special machine and initiate a manual reboot. If no one is able to do so, -// it will reboot itself after a few minutes, just like the old one. +// it will reboot itself after a few minutes, just like the old one. Bad things happen if there is no grid checker machine protecting +// the powernet when this event fires. /datum/gm_action/grid_check name = "grid check" departments = list(ROLE_ENGINEERING, ROLE_EVERYONE) - chaotic = 20 \ No newline at end of file + chaotic = 20 + +/datum/gm_action/grid_check/get_weight() + return 50 + (metric.count_people_in_department(ROLE_ENGINEERING) * 30) + +/datum/gm_action/grid_check/start() + // This sets off a chain of events that lead to the actual grid check (or perhaps worse). + // First, the Supermatter engine makes a power spike. + for(var/obj/machinery/power/generator/engine in machines) + engine.power_spike() + break // Just one engine, please. + // After that, the engine checks if a grid checker exists on the same powernet, and if so, it triggers a blackout. + // If not, lots of stuff breaks. See code/modules/power/generator.dm for that piece of code. \ No newline at end of file diff --git a/code/modules/gamemaster/actions/waste_disposal.dm b/code/modules/gamemaster/actions/waste_disposal.dm index 4edfba9a1e..e7ba856e78 100644 --- a/code/modules/gamemaster/actions/waste_disposal.dm +++ b/code/modules/gamemaster/actions/waste_disposal.dm @@ -3,4 +3,7 @@ /datum/gm_action/waste_disposal name = "waste disposal" departments = list(ROLE_CARGO) - chaotic = 0 \ No newline at end of file + chaotic = 0 + +/datum/gm_action/waste_disposal/get_weight() + return metric.count_people_in_department(ROLE_CARGO) * 50 \ No newline at end of file diff --git a/code/modules/gamemaster/controller.dm b/code/modules/gamemaster/controller.dm index abff64ea11..079c535164 100644 --- a/code/modules/gamemaster/controller.dm +++ b/code/modules/gamemaster/controller.dm @@ -11,30 +11,70 @@ var/HTML = "Game Master AI" - HTML += "Staleness: [staleness]
" - HTML += "Danger: [danger]

" + HTML += "\[Toggle Time Restrictions\] | \ + \[Toggle GM\] | \ + \[Force Event Decision\]
" + + HTML += "Status: [pre_action_checks() ? "Ready" : "Suppressed"]

" + + HTML += "Staleness: [staleness] \[Adjust\]
" + HTML += "Danger: [danger] \[Adjust\]

" HTML += "Actions available;
" for(var/datum/gm_action/action in available_actions) if(action.enabled == FALSE) continue - HTML += "[action.name] ([english_list(action.departments)])
" + HTML += "[action.name] ([english_list(action.departments)]) (weight: [action.get_weight()])
" HTML += "
" - HTML += "All living mobs activity: [assess_all_living_mobs()]
" + HTML += "All living mobs activity: [metric.assess_all_living_mobs()]%
" + HTML += "All ghost activity: [metric.assess_all_dead_mobs()]%
" HTML += "
" HTML += "Departmental activity;
" - for(var/department in departments) - var/number_of_people = count_people_in_department(department) - HTML += " [department] : [assess_department(department)] / [number_of_people * 100]
" + for(var/department in metric.departments) + HTML += " [department] : [metric.assess_department(department)]%
" HTML += "
" HTML += "Activity of players;
" for(var/mob/player in player_list) - HTML += " [player] : [assess_player_activity(player)]
" + HTML += " [player] ([player.key]) : [metric.assess_player_activity(player)]%
" HTML +="" - user << browse(HTML, "window=log;size=400x450;border=1;can_resize=1;can_close=1;can_minimize=1") \ No newline at end of file + user << browse(HTML, "window=log;size=400x450;border=1;can_resize=1;can_close=1;can_minimize=1") + +/datum/game_master/Topic(href, href_list) + if(..()) + return + + if(!is_admin(usr)) + message_admins("[usr] has attempted to modify the Game Master values without being an admin.") + return + + if(href_list["toggle_time_restrictions"]) + ignore_time_restrictions = !ignore_time_restrictions + message_admins("GM event time restrictions was [ignore_time_restrictions ? "dis" : "en"]abled by [usr.key].") + + if(href_list["force_choose_event"]) + start_action() + message_admins("[usr.key] forced the Game Master to choose an event immediately.") + + if(href_list["suspend"]) + suspended = !suspended + message_admins("GM was [suspended ? "dis" : "en"]abled by [usr.key].") + + if(href_list["adjust_staleness"]) + var/amount = input(usr, "How much staleness should be added or subtracted?", "Game Master") as null|num + if(amount) + adjust_staleness(amount) + message_admins("GM staleness was adjusted by [amount] by [usr.key].") + + if(href_list["adjust_danger"]) + var/amount = input(usr, "How much danger should be added or subtracted?", "Game Master") as null|num + if(amount) + adjust_danger(amount) + message_admins("GM danger was adjusted by [amount] by [usr.key].") + + interact(usr) // To refresh the UI. \ No newline at end of file diff --git a/code/modules/gamemaster/defines.dm b/code/modules/gamemaster/defines.dm index 796eb014f8..2e486ee23b 100644 --- a/code/modules/gamemaster/defines.dm +++ b/code/modules/gamemaster/defines.dm @@ -1,10 +1 @@ -#define ROLE_COMMAND "command" -#define ROLE_SECURITY "security" -#define ROLE_ENGINEERING "engineering" -#define ROLE_MEDICAL "medical" -#define ROLE_RESEARCH "research" -#define ROLE_CARGO "cargo" -#define ROLE_CIVILIAN "civilian" -#define ROLE_SYNTHETIC "synthetic" -#define ROLE_UNKNOWN "unknown" -#define ROLE_EVERYONE "everyone" \ No newline at end of file +#define EVENT_BASELINE_WEIGHT 200 \ No newline at end of file diff --git a/code/modules/gamemaster/game_master.dm b/code/modules/gamemaster/game_master.dm index e21b9631bf..180892f4c9 100644 --- a/code/modules/gamemaster/game_master.dm +++ b/code/modules/gamemaster/game_master.dm @@ -4,7 +4,8 @@ // the round. /datum/game_master - var/suspended = FALSE // If true, it will not do anything. + var/suspended = TRUE // If true, it will not do anything. + var/ignore_time_restrictions = FALSE// Useful for debugging without needing to wait 20 minutes each time. var/list/available_actions = list() // A list of 'actions' that the GM has access to, to spice up a round, such as events. var/danger = 0 // The GM's best guess at how chaotic the round is. High danger makes it hold back. var/staleness = -20 // Determines liklihood of the GM doing something, increases over time. @@ -12,31 +13,20 @@ var/staleness_modifier = 1 // Ditto. Higher numbers generally result in more events occuring in a round. var/ticks_completed = 0 // Counts amount of ticks completed. Note that this ticks once a minute. var/next_action = 0 // Minimum amount of time of nothingness until the GM can pick something again. - var/departments = list( // List of departments the GM considers for choosing events for. - ROLE_COMMAND, - ROLE_SECURITY, - ROLE_ENGINEERING, - ROLE_MEDICAL, - ROLE_RESEARCH, - ROLE_CARGO, - ROLE_CIVILIAN, - ROLE_SYNTHETIC - ) + var/last_department_used = null // If an event was done for a specific department, it is written here, so it doesn't do it again. + /datum/game_master/New() ..() available_actions = init_subtypes(/datum/gm_action) -// var/actions = typesof(/datum/gm_actions) -// for(var/type in actions) -// available_actions.Add(new type) /datum/game_master/proc/process() - if(ticker && ticker.current_state == GAME_STATE_PLAYING) + if(ticker && ticker.current_state == GAME_STATE_PLAYING && !suspended) adjust_staleness(1) adjust_danger(-1) ticks_completed++ - var/global_afk = assess_all_living_mobs() + var/global_afk = metric.assess_all_living_mobs() global_afk -= 100 global_afk = abs(global_afk) global_afk = round(global_afk / 100, 0.1) @@ -46,19 +36,15 @@ log_debug("Game Master going to start something.") start_action() -/datum/game_master/proc/assess_all_living_mobs() - var/num = 0 - for(var/mob/living/L in player_list) // Ghosts being AFK isn't that much of a concern. - . += assess_player_activity(L) - num++ - if(num) - . = round(. / num, 0.1) - // This is run before committing to an action/event. /datum/game_master/proc/pre_action_checks() if(!ticker || ticker.current_state != GAME_STATE_PLAYING) log_debug("Game Master unable to start event: Ticker is nonexistant, or the game is not ongoing.") return FALSE + if(suspended) + return FALSE + if(ignore_time_restrictions) + return TRUE // Last minute antagging is bad for humans to do, so the GM will respect the start and end of the round. var/mills = round_duration_in_ticks var/mins = round((mills % 36000) / 600) @@ -76,43 +62,21 @@ if(!pre_action_checks()) // Make sure we're not doing last minute events, or early events. return log_debug("Game Master now starting action decision.") - var/list/best_actions = assess_round() // Checks the whole round for active people, and returns a list of the most activie departments. - if(best_actions && best_actions.len) - var/datum/gm_action/choice = pick(best_actions) - if(choice) -// log_debug("[choice.name] was chosen by the Game Master, and is now being ran.") -// choice.set_up() -// choice.start() -// choice.annnounce() - next_action = world.time + rand(15 MINUTES, 30 MINUTES) - -/datum/game_master/proc/assess_round() - var/list/activity = list() - for(var/department in departments) - activity[department] = assess_department(department) - log_debug("Assessing department [department]. They have activity of [activity[department]].") - - var/list/most_active_departments = list() // List of winners. - var/highest_activity = null // Department who is leading in activity, if one exists. - var/highest_number = 0 // Activity score needed to beat to be the most active department. - for(var/i = 1, i <= 3, i++) - log_debug("Doing [i]\th round of counting.") - for(var/department in activity) - if(activity[department] > highest_number && activity[department] > 0) // More active than the current highest department? - highest_activity = department - highest_number = activity[department] - - if(highest_activity) // Someone's a winner. - most_active_departments.Add(highest_activity) // Add to the list of most active. - activity.Remove(highest_activity) // Remove them from the other list so they don't win more than once. - log_debug("[highest_activity] has won the [i]\th round of activity counting.") - highest_activity = null // Now reset for the next round. - highest_number = 0 - //todo: finish + var/list/most_active_departments = metric.assess_all_departments(3, list(last_department_used)) var/list/best_actions = decide_best_action(most_active_departments) - return best_actions - // By now, we should have a list of departments populated. The GM will prefer events tailored to these departments. + if(best_actions && best_actions.len) + var/list/weighted_actions = list() + for(var/datum/gm_action/action in best_actions) + weighted_actions[action] = action.get_weight() + + var/datum/gm_action/choice = pickweight(weighted_actions) + if(choice) + log_debug("[choice.name] was chosen by the Game Master, and is now being ran.") + choice.set_up() + choice.start() + next_action = world.time + rand(15 MINUTES, 30 MINUTES) + last_department_used = choice.departments[1] @@ -169,39 +133,4 @@ else log_debug("Game Master failed to find a suitable event, something very wrong is going on.") -// This checks a whole department's viability to receive an event. -/datum/game_master/proc/assess_department(var/department) - if(!department) - return - var/departmental_activitiy = 0 - for(var/mob/M in player_list) - if(guess_department(M) != department) // Ignore people outside the department we're assessing. - continue - departmental_activitiy += assess_player_activity(M) - return departmental_activitiy - -// This checks an individual player's activity level. People who have been afk for a few minutes aren't punished as much as those -// who were afk for hours, as they're most likely gone for good. -/datum/game_master/proc/assess_player_activity(var/mob/M) - . = 100 - if(!M) - . = 0 - return - - if(!M.mind || !M.client) // Logged out. They might come back but we can't do any meaningful assessments for now. - . = 0 - return - - var/afk = M.client.is_afk(1 MINUTE) - if(afk) // Deduct points based on length of AFK-ness. - switch(afk) // One minute is equal to 600, for reference. - if(1 MINUTE to 10 MINUTES) // People gone for this emough of time hopefully will come back soon. - . -= round( (afk / 200), 1) - // . -= 30 - if(10 MINUTES to 30 MINUTES) - . -= round( (afk / 150), 1) - // . -= 70 - if(30 MINUTES to INFINITY) // They're probably not coming back if it's been 30 minutes. - . -= 100 - . = max(. , 0) // No negative numbers, or else people could drag other, non-afk players down. diff --git a/code/modules/gamemaster/helpers.dm b/code/modules/gamemaster/helpers.dm index a0972207a3..80fc133931 100644 --- a/code/modules/gamemaster/helpers.dm +++ b/code/modules/gamemaster/helpers.dm @@ -6,67 +6,4 @@ // Tell the game master that something interesting happened. /datum/game_master/proc/adjust_staleness(var/amt) amt = amt * staleness_modifier - staleness = round( Clamp(staleness + amt, -50, 200), 0.1) - -// This proc tries to find the department of an arbitrary mob. -/datum/game_master/proc/guess_department(var/mob/M) - var/datum/data/record/R = find_general_record("name", M.real_name) - . = ROLE_UNKNOWN - if(R) // We found someone with a record. - var/recorded_rank = R.fields["real_rank"] - . = role_name_to_department(recorded_rank) - if(. != ROLE_UNKNOWN) // We found the correct department, so we can stop now. - return - - // They have a custom title, aren't crew, or someone deleted their record, so we need a fallback method. - // Let's check the mind. - if(M.mind) - . = role_name_to_department(M.mind.assigned_role) - if(. != ROLE_UNKNOWN) - return - - // At this point, they don't have a mind, or for some reason assigned_role didn't work. - if(ishuman(M)) - var/mob/living/carbon/human/H = M - . = role_name_to_department(H.job) - if(. != ROLE_UNKNOWN) - return - - return ROLE_UNKNOWN // Welp. - - -// Feed this proc the name of a job, and it will try to figure out what department they are apart of. -/datum/game_master/proc/role_name_to_department(var/role_name) - if(role_name in security_positions) - return ROLE_SECURITY - - if(role_name in engineering_positions) - return ROLE_ENGINEERING - - if(role_name in medical_positions) - return ROLE_MEDICAL - - if(role_name in science_positions) - return ROLE_RESEARCH - - if(role_name in cargo_positions) - return ROLE_CARGO - - if(role_name in civilian_positions) - return ROLE_CIVILIAN - - if(role_name in nonhuman_positions) - return ROLE_SYNTHETIC - - if(role_name in command_positions) // We do command last, so that only the Captain and command secretaries get caught. - return ROLE_COMMAND - - return ROLE_UNKNOWN - -/datum/game_master/proc/count_people_in_department(var/department) - if(!department) - return - for(var/mob/M in player_list) - if(guess_department(M) != department) // Ignore people outside the department we're counting. - continue - . += 1 \ No newline at end of file + staleness = round( Clamp(staleness + amt, -50, 200), 0.1) \ No newline at end of file diff --git a/code/modules/metric/activity.dm b/code/modules/metric/activity.dm new file mode 100644 index 0000000000..370ae0eb2f --- /dev/null +++ b/code/modules/metric/activity.dm @@ -0,0 +1,82 @@ +// This checks an individual player's activity level. People who have been afk for a few minutes aren't punished as much as those +// who were afk for hours, as they're most likely gone for good. +/datum/metric/proc/assess_player_activity(var/mob/M) + . = 100 + if(!M) + . = 0 + return + + if(!M.mind || !M.client) // Logged out. They might come back but we can't do any meaningful assessments for now. + . = 0 + return + + var/afk = M.client.is_afk(1 MINUTE) + if(afk) // Deduct points based on length of AFK-ness. + switch(afk) // One minute is equal to 600, for reference. + if(1 MINUTE to 10 MINUTES) // People gone for this emough of time hopefully will come back soon. + . -= round( (afk / 200), 1) + if(10 MINUTES to 30 MINUTES) + . -= round( (afk / 150), 1) + if(30 MINUTES to INFINITY) // They're probably not coming back if it's been 30 minutes. + . -= 100 + . = max(. , 0) // No negative numbers, or else people could drag other, non-afk players down. + +// This checks a whole department's collective activity. +/datum/metric/proc/assess_department(var/department) + if(!department) + return + var/departmental_activity = 0 + var/departmental_size = 0 + for(var/mob/M in player_list) + if(guess_department(M) != department) // Ignore people outside the department we're assessing. + continue + departmental_activity += assess_player_activity(M) + departmental_size++ + if(departmental_size) + departmental_activity = departmental_activity / departmental_size // Average it out. + return departmental_activity + +/datum/metric/proc/assess_all_departments(var/cutoff_number = 3, var/list/department_blacklist = list()) + var/list/activity = list() + for(var/department in departments) + activity[department] = assess_department(department) + log_debug("Assessing department [department]. They have activity of [activity[department]].") + + var/list/most_active_departments = list() // List of winners. + var/highest_activity = null // Department who is leading in activity, if one exists. + var/highest_number = 0 // Activity score needed to beat to be the most active department. + for(var/i = 1, i <= cutoff_number, i++) + log_debug("Doing [i]\th round of counting.") + for(var/department in activity) + if(department in department_blacklist) // Blacklisted? + continue + if(activity[department] > highest_number && activity[department] > 0) // More active than the current highest department? + highest_activity = department + highest_number = activity[department] + + if(highest_activity) // Someone's a winner. + most_active_departments.Add(highest_activity) // Add to the list of most active. + activity.Remove(highest_activity) // Remove them from the other list so they don't win more than once. + log_debug("[highest_activity] has won the [i]\th round of activity counting.") + highest_activity = null // Now reset for the next round. + highest_number = 0 + //todo: finish + return most_active_departments + +/datum/metric/proc/assess_all_living_mobs() // Living refers to the type, not the stat variable. + . = 0 + var/num = 0 + for(var/mob/living/L in player_list) + . += assess_player_activity(L) + num++ + if(num) + . = round(. / num, 0.1) + +/datum/metric/proc/assess_all_dead_mobs() // Ditto. + . = 0 + var/num = 0 + for(var/mob/observer/dead/O in player_list) + . += assess_player_activity(O) + num++ + if(num) + . = round(. / num, 0.1) \ No newline at end of file diff --git a/code/modules/metric/department.dm b/code/modules/metric/department.dm new file mode 100644 index 0000000000..ef146de506 --- /dev/null +++ b/code/modules/metric/department.dm @@ -0,0 +1,72 @@ + +// This proc tries to find the department of an arbitrary mob. +/datum/metric/proc/guess_department(var/mob/M) + var/list/found_roles = list() + . = ROLE_UNKNOWN + + // Records are usually the most reliable way to get what job someone is. + var/datum/data/record/R = find_general_record("name", M.real_name) + if(R) // We found someone with a record. + var/recorded_rank = R.fields["real_rank"] + found_roles = role_name_to_department(recorded_rank) + . = found_roles[1] + if(. != ROLE_UNKNOWN) // We found the correct department, so we can stop now. + return + + // They have a custom title, aren't crew, or someone deleted their record, so we need a fallback method. + // Let's check the mind. + if(M.mind) + found_roles = role_name_to_department(M.mind.assigned_role) + . = found_roles[1] + if(. != ROLE_UNKNOWN) + return + + // At this point, they don't have a mind, or for some reason assigned_role didn't work. + found_roles = role_name_to_department(M.job) + . = found_roles[1] + if(. != ROLE_UNKNOWN) + return + + return ROLE_UNKNOWN // Welp. + +// Feed this proc the name of a job, and it will try to figure out what department they are apart of. +// Note that this returns a list, as some jobs are in more than one department, like Command. The 'primary' department is the first +// in the list, e.g. a HoS has Security as first, Command as second in the returned list. +/datum/metric/proc/role_name_to_department(var/role_name) + var/list/result = list() + + if(role_name in security_positions) + result += ROLE_SECURITY + + if(role_name in engineering_positions) + result += ROLE_ENGINEERING + + if(role_name in medical_positions) + result += ROLE_MEDICAL + + if(role_name in science_positions) + result += ROLE_RESEARCH + + if(role_name in cargo_positions) + result += ROLE_CARGO + + if(role_name in civilian_positions) + result += ROLE_CIVILIAN + + if(role_name in nonhuman_positions) + result += ROLE_SYNTHETIC + + if(role_name in command_positions) // We do Command last, since we consider command to only be a primary department for hop/admin. + result += ROLE_COMMAND + + if(!result.len) // No department was found. + result += ROLE_UNKNOWN + return result + +/datum/metric/proc/count_people_in_department(var/department) + if(!department) + return + for(var/mob/M in player_list) + if(guess_department(M) != department) // Ignore people outside the department we're counting. + continue + . += 1 \ No newline at end of file diff --git a/code/modules/metric/metric.dm b/code/modules/metric/metric.dm new file mode 100644 index 0000000000..1550b2c34e --- /dev/null +++ b/code/modules/metric/metric.dm @@ -0,0 +1,15 @@ +// This is a global datum used to retrieve certain information about the round, such as activity of a department or a specific +// player. + +/datum/metric + var/departments = list( + ROLE_COMMAND, + ROLE_SECURITY, + ROLE_ENGINEERING, + ROLE_MEDICAL, + ROLE_RESEARCH, + ROLE_CARGO, + ROLE_CIVILIAN, + ROLE_SYNTHETIC + ) + diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 8096e1f20e..b577439394 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -68,6 +68,7 @@ var/cell_type = /obj/item/weapon/cell/apc var/opened = 0 //0=closed, 1=opened, 2=cover removed var/shorted = 0 + var/grid_check = FALSE var/lighting = 3 var/equipment = 3 var/environ = 3 @@ -796,7 +797,7 @@ return "[area.name] : [equipment]/[lighting]/[environ] ([lastused_equip+lastused_light+lastused_environ]) : [cell? cell.percent() : "N/C"] ([charging])" /obj/machinery/power/apc/proc/update() - if(operating && !shorted) + if(operating && !shorted && !grid_check) area.power_light = (lighting > 1) area.power_equip = (equipment > 1) area.power_environ = (environ > 1) @@ -1001,7 +1002,7 @@ if(debug) log_debug("Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light] - Longterm: [longtermpower]") - if(cell && !shorted) + if(cell && !shorted && !grid_check) // draw power from cell as before to power the area var/cellused = min(cell.charge, CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell cell.use(cellused) @@ -1196,7 +1197,7 @@ obj/machinery/power/apc/proc/autoset(var/val, var/on) // overload the lights in this APC area /obj/machinery/power/apc/proc/overload_lighting(var/chance = 100) - if(/* !get_connection() || */ !operating || shorted) + if(/* !get_connection() || */ !operating || shorted || grid_check) return if( cell && cell.charge>=20) cell.use(20); @@ -1225,4 +1226,34 @@ obj/machinery/power/apc/proc/autoset(var/val, var/on) update_icon() return 1 +/obj/machinery/power/apc/overload(var/obj/machinery/power/source) + if(is_critical) + return + + if(prob(30)) // Nothing happens. + return + + if(prob(40)) // Lights blow. + overload_lighting() + + if(prob(40)) // Spooky flickers. + for(var/obj/machinery/light/L in area) + L.flicker(20) + + if(prob(25)) // Bluescreens. + emagged = 1 + locked = 0 + update_icon() + + if(prob(25)) // Cell gets damaged. + if(cell) + cell.corrupt() + + if(prob(10)) // Computers get broken. + for(var/obj/machinery/computer/comp in area) + comp.ex_act(3) + + if(prob(5)) // APC completely ruined. + set_broken() + #undef APC_UPDATE_ICON_COOLDOWN diff --git a/code/modules/power/generator.dm b/code/modules/power/generator.dm index 48798c1fb5..92d749df0f 100644 --- a/code/modules/power/generator.dm +++ b/code/modules/power/generator.dm @@ -234,3 +234,8 @@ return src.set_dir(turn(src.dir, -90)) + +/obj/machinery/power/generator/power_spike() + if(effective_gen >= max_power / 2 && powernet) // Don't make a spike if we're not making a whole lot of power. + ..() + diff --git a/code/modules/power/grid_checker.dm b/code/modules/power/grid_checker.dm new file mode 100644 index 0000000000..b52dc4a4d6 --- /dev/null +++ b/code/modules/power/grid_checker.dm @@ -0,0 +1,125 @@ +/obj/machinery/power/grid_checker + name = "grid checker" + desc = "A machine that reacts to unstable conditions in the powernet, by safely shutting everything down. Probably better \ + than the alternative." + icon_state = "gridchecker_on" + circuit = /obj/item/weapon/circuitboard/grid_checker + var/power_failing = FALSE // Turns to TRUE when the grid check event is fired by the Game Master, or perhaps a cheeky antag. + // Wire stuff below. + var/datum/wires/grid_checker/wires + var/wire_locked_out = FALSE + var/wire_allow_manual_1 = FALSE + var/wire_allow_manual_2 = FALSE + var/wire_allow_manual_3 = FALSE + var/opened = FALSE + +/obj/machinery/power/grid_checker/New() + ..() + connect_to_network() + update_icon() + wires = new(src) + component_parts = list() + component_parts += new /obj/item/weapon/stock_parts/capacitor(src) + component_parts += new /obj/item/weapon/stock_parts/capacitor(src) + component_parts += new /obj/item/weapon/stock_parts/capacitor(src) + component_parts += new /obj/item/stack/cable_coil(src, 10) + RefreshParts() + +/obj/machinery/power/grid_checker/Destroy() + qdel(wires) + wires = null + ..() + +/obj/machinery/power/grid_checker/update_icon() + if(power_failing) + icon_state = "gridchecker_off" + set_light(2, 2, "#F86060") + else + icon_state = "gridchecker_on" + set_light(2, 2, "#A8B0F8") + +/obj/machinery/power/grid_checker/attackby(obj/item/W, mob/user) + if(!user) + return + if(istype(W, /obj/item/weapon/screwdriver)) + default_deconstruction_screwdriver(user, W) + opened = !opened + else if(istype(W, /obj/item/weapon/crowbar)) + default_deconstruction_crowbar(user, W) + else if(istype(W, /obj/item/device/multitool) || istype(W, /obj/item/weapon/wirecutters) ) + attack_hand(user) + +/obj/machinery/power/grid_checker/attack_hand(mob/user) + if(!user) + return + add_fingerprint(user) + interact(user) + +/obj/machinery/power/grid_checker/interact(mob/user) + if(!user) + return + + if(opened) + wires.Interact(user) + + return ui_interact(user) + +/obj/machinery/power/grid_checker/proc/power_failure(var/announce = TRUE) + if(announce) + command_announcement.Announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, \ + the colony's power will be shut off for an indeterminate duration while the powernet monitor restarts automatically, or \ + when Engineering can manually resolve the issue.", + "Critical Power Failure", + new_sound = 'sound/AI/poweroff.ogg') + power_failing = TRUE + if(powernet) + for(var/obj/machinery/power/terminal/T in powernet.nodes) // SMESes that are "downstream" of the powernet. + + if(istype(T.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = T.master + if(A.is_critical) + continue + A.grid_check = TRUE + + for(var/obj/machinery/power/smes/smes in powernet.nodes) // These are "upstream" + smes.grid_check = TRUE +/* + smes.last_charge = smes.charge + smes.last_output_attempt = smes.output_attempt + smes.last_input_attempt = smes.input_attempt + smes.charge = 0 + smes.inputting(FALSE) + smes.outputting(FALSE) + smes.update_icon() + smes.power_change() +*/ + update_icon() + + spawn(rand(4 MINUTES, 10 MINUTES) ) + if(power_failing) // Check to see if engineering didn't beat us to it. + end_power_failure(TRUE) + +/obj/machinery/power/grid_checker/proc/end_power_failure(var/announce = TRUE) + if(announce) + command_announcement.Announce("Power has been restored to [station_name()]. We apologize for the inconvenience.", + "Power Systems Nominal", + new_sound = 'sound/AI/poweron.ogg') + power_failing = FALSE + update_icon() + + for(var/obj/machinery/power/terminal/T in powernet.nodes) + if(istype(T.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = T.master + if(A.is_critical) + continue + A.grid_check = FALSE + + for(var/obj/machinery/power/smes/smes in powernet.nodes) // These are "upstream" + smes.grid_check = FALSE + /* + smes.charge = smes.last_charge + smes.output_attempt = smes.last_output_attempt + smes.input_attempt = smes.last_input_attempt + smes.update_icon() + smes.power_change() + */ \ No newline at end of file diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index 4f34cd7eff..b8beea238c 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -138,6 +138,29 @@ ..() return +// Used for power spikes by the engine, has specific effects on different machines. +/obj/machinery/power/proc/overload(var/obj/machinery/power/source) + return + +/obj/machinery/power/proc/power_spike() + var/obj/machinery/power/grid_checker/G = locate() in powernet.nodes + if(G) // If we found a grid checker, then all is well. + G.power_failure(prob(30)) + else // Otherwise lets break some stuff. + spawn(1) + command_announcement.Announce("Dangerous power spike detected in the power network. Please check machinery \ + for electrical damage.", + "Critical Power Overload") + var/i = 0 + var/limit = rand(30, 50) + for(var/obj/machinery/power/P in powernet.nodes) + P.overload(src) + i++ + if(i % 5) + sleep(1) + if(i >= limit) + break + /////////////////////////////////////////// // Powernet handling helpers ////////////////////////////////////////// diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index 9fc0f74290..bf165f0916 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -44,6 +44,7 @@ var/building_terminal = 0 //Suggestions about how to avoid clickspam building several terminals accepted! var/obj/machinery/power/terminal/terminal = null var/should_be_mapped = 0 // If this is set to 0 it will send out warning on New() + var/grid_check = FALSE // If true, suspends all I/O. /obj/machinery/power/smes/drain_power(var/drain_check, var/surge, var/amount = 0) @@ -124,7 +125,7 @@ var/last_onln = outputting //inputting - if(input_attempt && (!input_pulsed && !input_cut)) + if(input_attempt && (!input_pulsed && !input_cut) && !grid_check) var/target_load = min((capacity-charge)/SMESRATE, input_level) // charge at set rate, limited to spare capacity var/actual_load = draw_power(target_load) // add the load to the terminal side network charge += actual_load * SMESRATE // increase the charge @@ -137,7 +138,7 @@ inputting = 0 //outputting - if(outputting && (!output_pulsed && !output_cut)) + if(outputting && (!output_pulsed && !output_cut) && !grid_check) output_used = min( charge/SMESRATE, output_level) //limit output to that stored charge -= output_used*SMESRATE // reduce the storage (may be recovered in /restore() if excessive) @@ -420,6 +421,11 @@ update_icon() ..() +/obj/machinery/power/smes/overload(var/obj/machinery/power/source) // This propagates the power spike down the powernet. + if(istype(source, /obj/machinery/power/smes)) // Prevent infinite loops if two SMESes are hooked up to each other. + return + power_spike() + /obj/machinery/power/smes/magical name = "magical power storage unit" diff --git a/code/modules/power/terminal.dm b/code/modules/power/terminal.dm index 3636c3acfa..a1f2fbf030 100644 --- a/code/modules/power/terminal.dm +++ b/code/modules/power/terminal.dm @@ -37,3 +37,7 @@ // Powernet rebuilds need this to work properly. /obj/machinery/power/terminal/process() return 1 + +/obj/machinery/power/terminal/overload(var/obj/machinery/power/source) + if(master) + master.overload(source) diff --git a/code/modules/research/designs.dm b/code/modules/research/designs.dm index bbb54d7f7e..a4807c2332 100644 --- a/code/modules/research/designs.dm +++ b/code/modules/research/designs.dm @@ -1038,6 +1038,14 @@ CIRCUITS BELOW build_path = /obj/item/weapon/circuitboard/smes sort_string = "JBABB" +/datum/design/circuit/grid_checker + name = "power grid checker" + desc = "Allows for the construction of circuit boards used to build a grid checker." + id = "grid_checker" + req_tech = list(TECH_POWER = 4, TECH_ENGINEERING = 3) + build_path = /obj/item/weapon/circuitboard/grid_checker + sort_string = "JBABC" + /datum/design/circuit/gas_heater name = "gas heating system" id = "gasheater" diff --git a/icons/obj/power.dmi b/icons/obj/power.dmi index 14878d4dd081a1d669bcff52f8ce4e5169c747c6..fe67ea02af8213d25a7fb1495334b0d2659cb951 100644 GIT binary patch delta 6896 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 delta 6306 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 a(78`p`)!N~CTjtV&)mckQFGoc=Dz?(EEy{R diff --git a/polaris.dme b/polaris.dme index 8205a0bf3d..ed8f7faa84 100644 --- a/polaris.dme +++ b/polaris.dme @@ -253,6 +253,7 @@ #include "code\datums\wires\autolathe.dm" #include "code\datums\wires\camera.dm" #include "code\datums\wires\explosive.dm" +#include "code\datums\wires\grid_checker.dm" #include "code\datums\wires\particle_accelerator.dm" #include "code\datums\wires\radio.dm" #include "code\datums\wires\robot.dm" @@ -1409,6 +1410,9 @@ #include "code\modules\materials\material_sheets.dm" #include "code\modules\materials\material_synth.dm" #include "code\modules\materials\materials.dm" +#include "code\modules\metric\activity.dm" +#include "code\modules\metric\department.dm" +#include "code\modules\metric\metric.dm" #include "code\modules\mining\abandonedcrates.dm" #include "code\modules\mining\alloys.dm" #include "code\modules\mining\coins.dm" @@ -1762,6 +1766,7 @@ #include "code\modules\power\generator.dm" #include "code\modules\power\generator_type2.dm" #include "code\modules\power\gravitygenerator.dm" +#include "code\modules\power\grid_checker.dm" #include "code\modules\power\lighting.dm" #include "code\modules\power\port_gen.dm" #include "code\modules\power\power.dm"