From 69c935dbf1bdfa0f699ae31881ac5070b4f1eb45 Mon Sep 17 00:00:00 2001 From: Andrew Date: Wed, 17 Jul 2024 10:22:43 +0300 Subject: [PATCH] Emitters and Shieldgens work roundstart, APCs charge evenly (#84983) ## About The Pull Request Fixed the issue when the power priority was preferring random machinery around the station over the crucial Emitters and Shieldgens. Because of how the consumers are selected right now, PACMAN attached to a station network will give power for the machinery/lights/environment of a random APC instead of the emitters next to it. This also makes power sinks process before APCs. Also made APCs charge in cascades within a powernet: 1. APCs try to charge to 5% to enable environment channel. 2. When all are above 5%, then allow APCs to charge to 20% to enable lighting channel 2. When all are above 20%, then allow APCs to charge to 35% to enable equipment channel 3. When all are above 35%, then allow APC to charge to full battery As a result, a low-rate power source can provide enough power to at least make all airlocks working and have dim lights enabled. Charlie station APCs after you run PACMAN with 15 uranium sheets: ![image](https://github.com/user-attachments/assets/7bb282da-2189-4123-a31b-c5f294a092cb) ## Why It's Good For The Game Makes emitters actually work, instead of having people fire kisses at SM to raise the internal energy. Makes it possible to use PACMAN to kickstart SM. Gives a reason to set up solars. Makes the power distribution more fair and equal after the recent APC battery buff. ## Changelog :cl: fix: Fixed emitters, shield gens and other wired machinery having lower power consumption priority than APCs qol: Made APCs charge more evenly to ensure that environment and lights are working everywhere before giving the power for equipment /:cl: --- code/__DEFINES/subsystems.dm | 9 ++- code/controllers/subsystem/machines.dm | 56 +++++++++-------- code/modules/power/apc/apc_main.dm | 83 ++++++++++++++++++-------- 3 files changed, 94 insertions(+), 54 deletions(-) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 8d460c3aecb6f..5e9f2b35ef04d 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -298,9 +298,12 @@ #define SSEXPLOSIONS_THROWS 3 // Machines subsystem subtasks. -#define SSMACHINES_APCS_EARLY 1 -#define SSMACHINES_MACHINES 2 -#define SSMACHINES_APCS_LATE 3 +#define SSMACHINES_MACHINES 1 +#define SSMACHINES_APCS_EARLY 2 +#define SSMACHINES_APCS_ENVIRONMENT 3 +#define SSMACHINES_APCS_LIGHTS 4 +#define SSMACHINES_APCS_EQUIPMENT 5 +#define SSMACHINES_APCS_LATE 6 // Wardrobe subsystem tasks #define SSWARDROBE_STOCK 1 diff --git a/code/controllers/subsystem/machines.dm b/code/controllers/subsystem/machines.dm index 3e07eca8a2e87..25c2b3365fbe6 100644 --- a/code/controllers/subsystem/machines.dm +++ b/code/controllers/subsystem/machines.dm @@ -11,10 +11,17 @@ SUBSYSTEM_DEF(machines) VAR_PRIVATE/list/all_machines = list() var/list/processing = list() + var/list/processing_apcs = list() + var/list/currentrun = list() - var/list/apc_early_processing = list() - var/list/apc_late_processing = list() - var/current_part = SSMACHINES_APCS_EARLY + var/current_part = SSMACHINES_MACHINES + var/list/apc_steps = list( + SSMACHINES_APCS_ENVIRONMENT, + SSMACHINES_APCS_LIGHTS, + SSMACHINES_APCS_EQUIPMENT, + SSMACHINES_APCS_EARLY, + SSMACHINES_APCS_LATE + ) ///List of all powernets on the server. var/list/datum/powernet/powernets = list() @@ -82,25 +89,10 @@ SUBSYSTEM_DEF(machines) if (!resumed) for(var/datum/powernet/powernet as anything in powernets) powernet.reset() //reset the power state. - current_part = SSMACHINES_APCS_EARLY - src.currentrun = apc_early_processing.Copy() - - //APC early processing. Draws static power usages from their grids. - if(current_part == SSMACHINES_APCS_EARLY) - //cache for sanic speed (lists are references anyways) - var/list/currentrun = src.currentrun - while(currentrun.len) - var/obj/machinery/power/apc/apc = currentrun[currentrun.len] - currentrun.len-- - if(QDELETED(apc) || apc.early_process(wait * 0.1) == PROCESS_KILL) - apc_early_processing -= apc - apc.datum_flags &= ~DF_ISPROCESSING - if(MC_TICK_CHECK) - return current_part = SSMACHINES_MACHINES src.currentrun = processing.Copy() - //General machine processing. Their power usage can be dynamic and based on surplus power, so they come after static power usage have been applied. + //Processing all machines if(current_part == SSMACHINES_MACHINES) //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun @@ -112,22 +104,34 @@ SUBSYSTEM_DEF(machines) thing.datum_flags &= ~DF_ISPROCESSING if (MC_TICK_CHECK) return - current_part = SSMACHINES_APCS_LATE - src.currentrun = apc_late_processing.Copy() + current_part = apc_steps[1] + src.currentrun = processing_apcs.Copy() - //APC late processing. APCs will use the remaining power on the grid to charge their cells if needed. - //This is applied at the end so charging APCs don't cause others to discharge by taking all the power from the grid before machines use power. - if(current_part == SSMACHINES_APCS_LATE) + //Processing APCs + while(current_part in apc_steps) //cache for sanic speed (lists are references anyways) var/list/currentrun = src.currentrun while(currentrun.len) var/obj/machinery/power/apc/apc = currentrun[currentrun.len] currentrun.len-- - if(QDELETED(apc) || apc.late_process(wait * 0.1) == PROCESS_KILL) - apc_late_processing -= apc + if(QDELETED(apc)) + processing_apcs -= apc apc.datum_flags &= ~DF_ISPROCESSING + switch(current_part) + if(SSMACHINES_APCS_EARLY) + apc.early_process(wait * 0.1) + if(SSMACHINES_APCS_LATE) + apc.charge_channel(null, wait * 0.1) + apc.late_process(wait * 0.1) + else + apc.charge_channel(current_part, wait * 0.1) if(MC_TICK_CHECK) return + var/next_index = apc_steps.Find(current_part) + 1 + if (next_index > apc_steps.len) + return + current_part = apc_steps[next_index] + src.currentrun = processing_apcs.Copy() /datum/controller/subsystem/machines/proc/setup_template_powernets(list/cables) var/obj/structure/cable/PC diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm index 329f77ab7186d..ca765ac0514a4 100644 --- a/code/modules/power/apc/apc_main.dm +++ b/code/modules/power/apc/apc_main.dm @@ -7,6 +7,12 @@ ///Cap for how fast cells charge, as a percentage per second (.01 means cellcharge is capped to 1% per second) #define CHARGELEVEL 0.01 +///Charge percentage at which the lights channel stops working +#define APC_CHANNEL_LIGHT_TRESHOLD 15 +///Charge percentage at which the equipment channel stops working +#define APC_CHANNEL_EQUIP_TRESHOLD 30 +///Charge percentage at which the APC icon indicates discharging +#define APC_CHANNEL_ALARM_TRESHOLD 75 /obj/machinery/power/apc name = "area power controller" @@ -49,6 +55,8 @@ var/operating = TRUE ///State of the apc charging (not charging, charging, fully charged) var/charging = APC_NOT_CHARGING + ///Previous state of charging, to detect the change + var/last_charging ///Can the APC charge? var/chargemode = TRUE ///Is the apc interface locked? @@ -67,6 +75,8 @@ var/lastused_environ = 0 ///Total amount of power used by the three channels var/lastused_total = 0 + ///Total amount of power put into the battery + var/lastused_charge = 0 ///State of the apc external power (no power, low power, has power) var/main_status = APC_NO_POWER powernet = FALSE // set so that APCs aren't found as powernet nodes //Hackish, Horrible, was like this before I changed it :( @@ -146,8 +156,7 @@ //APCs get added to their own processing tasks for the machines subsystem. if (!(datum_flags & DF_ISPROCESSING)) datum_flags |= DF_ISPROCESSING - SSmachines.apc_early_processing += src - SSmachines.apc_late_processing += src + SSmachines.processing_apcs += src //Pixel offset its appearance based on its direction dir = ndir @@ -516,11 +525,13 @@ disconnect_remote_access() /** - * APC early processing. This gets processed before any other machine does. + * APC early processing. This gets processed after any other machine on the powernet does. * This adds up the total static power usage for the apc's area, then draw that power usage from the grid or APC cell. - * This is done early so machines that use dynamic power get a more truthful surplus when accessing available energy. */ /obj/machinery/power/apc/proc/early_process() + if(isnull(area)) + return + var/total_static_energy_usage = 0 total_static_energy_usage += APC_CHANNEL_IS_ON(lighting) * area.energy_usage[AREA_USAGE_STATIC_LIGHT] total_static_energy_usage += APC_CHANNEL_IS_ON(equipment) * area.energy_usage[AREA_USAGE_STATIC_EQUIP] @@ -551,15 +562,12 @@ lastused_environ = APC_CHANNEL_IS_ON(environ) ? area.energy_usage[AREA_USAGE_ENVIRON] + area.energy_usage[AREA_USAGE_STATIC_ENVIRON] : 0 area.clear_usage() - lastused_total = lastused_light + lastused_equip + lastused_environ - + lastused_total = lastused_light + lastused_equip + lastused_environ + lastused_charge //store states to update icon if any change var/last_lt = lighting var/last_eq = equipment var/last_en = environ - var/last_ch = charging - var/excess = surplus() if(!avail()) @@ -579,7 +587,7 @@ if(!nightshift_lights || (nightshift_lights && !low_power_nightshift_lights)) low_power_nightshift_lights = TRUE INVOKE_ASYNC(src, PROC_REF(set_nightshift), TRUE) - else if(cell.percent() < 15) // <15%, turn off lighting & equipment + else if(cell.percent() < APC_CHANNEL_LIGHT_TRESHOLD) // turn off lighting & equipment equipment = autoset(equipment, AUTOSET_OFF) lighting = autoset(lighting, AUTOSET_OFF) environ = autoset(environ, AUTOSET_ON) @@ -587,7 +595,7 @@ if(!nightshift_lights || (nightshift_lights && !low_power_nightshift_lights)) low_power_nightshift_lights = TRUE INVOKE_ASYNC(src, PROC_REF(set_nightshift), TRUE) - else if(cell.percent() < 30) // <30%, turn off equipment + else if(cell.percent() < APC_CHANNEL_EQUIP_TRESHOLD) // turn off equipment equipment = autoset(equipment, AUTOSET_OFF) lighting = autoset(lighting, AUTOSET_ON) environ = autoset(environ, AUTOSET_ON) @@ -603,21 +611,9 @@ low_power_nightshift_lights = FALSE if(!SSnightshift.nightshift_active) INVOKE_ASYNC(src, PROC_REF(set_nightshift), FALSE) - if(cell.percent() > 75) + if(cell.percent() > APC_CHANNEL_ALARM_TRESHOLD) alarm_manager.clear_alarm(ALARM_POWER) - charging = APC_NOT_CHARGING - // now trickle-charge the cell - if(chargemode && operating && excess && cell.used_charge()) - // Max charge is capped to % per second constant. - lastused_total += charge_cell(min(cell.chargerate, cell.maxcharge * CHARGELEVEL) * seconds_per_tick, cell = cell, grid_only = TRUE, channel = AREA_USAGE_APC_CHARGE) - charging = APC_CHARGING - - // show cell as fully charged if so - if(cell.charge >= cell.maxcharge) - cell.charge = cell.maxcharge - charging = APC_FULLY_CHARGED - else // no cell, switch everything off charging = APC_NOT_CHARGING equipment = autoset(equipment, AUTOSET_FORCE_OFF) @@ -626,13 +622,47 @@ alarm_manager.send_alarm(ALARM_POWER) // update icon & area power if anything changed - if(last_lt != lighting || last_eq != equipment || last_en != environ || force_update) force_update = FALSE queue_icon_update() update() - else if(last_ch != charging) + if(charging != last_charging) queue_icon_update() + // show cell as fully charged if so + if(cell.charge >= cell.maxcharge) + cell.charge = cell.maxcharge + charging = APC_FULLY_CHARGED + +// charge until the battery is full or to the treshold of the provided channel +/obj/machinery/power/apc/proc/charge_channel(channel = null, seconds_per_tick) + if(channel == SSMACHINES_APCS_ENVIRONMENT) + lastused_charge = 0 + last_charging = charging + charging = APC_NOT_CHARGING + + if(!cell || shorted || !operating || !chargemode || !surplus() || !cell.used_charge()) + return + + // no overcharge past the next treshold + var/need_charge_for_channel + switch(channel) + if(SSMACHINES_APCS_ENVIRONMENT) + need_charge_for_channel = (cell.maxcharge * 0.05) - cell.charge + if(SSMACHINES_APCS_LIGHTS) + need_charge_for_channel = (cell.maxcharge * (APC_CHANNEL_LIGHT_TRESHOLD + 5) * 0.01) - cell.charge + if(SSMACHINES_APCS_EQUIPMENT) + need_charge_for_channel = (cell.maxcharge * (APC_CHANNEL_EQUIP_TRESHOLD + 5) * 0.01) - cell.charge + else + need_charge_for_channel = cell.used_charge() + + var/remaining_charge_rate = min(cell.chargerate, cell.maxcharge * CHARGELEVEL) - lastused_charge + var/need_charge = min(need_charge_for_channel, remaining_charge_rate) * seconds_per_tick + //check if we can charge the battery + if(need_charge < 0) + return + + lastused_charge += charge_cell(need_charge, cell = cell, grid_only = TRUE, channel = AREA_USAGE_APC_CHARGE) + charging = APC_CHARGING /obj/machinery/power/apc/proc/reset(wire) switch(wire) @@ -752,3 +782,6 @@ return null #undef CHARGELEVEL +#undef APC_CHANNEL_LIGHT_TRESHOLD +#undef APC_CHANNEL_EQUIP_TRESHOLD +#undef APC_CHANNEL_ALARM_TRESHOLD