From 77fef987894fac8b46f7a3cd318ed342866991fa Mon Sep 17 00:00:00 2001 From: Atermonera Date: Sun, 3 May 2020 15:44:25 -0700 Subject: [PATCH] Merge pull request #7010 from atlantiscze/2020_04_17_LoadBalancing Ports over power-related enhancements and tweaks i coded for Bay a while back --- code/__defines/machinery.dm | 1 + code/datums/wires/smes.dm | 2 +- code/defines/obj/weapon.dm | 7 - .../weapons/circuitboards/machinery/power.dm | 9 +- code/game/objects/structures/loot_piles.dm | 2 +- code/modules/power/apc.dm | 2 +- code/modules/power/batteryrack.dm | 479 ++++++++++-------- code/modules/power/cable.dm | 11 +- code/modules/power/cell.dm | 3 + code/modules/power/debug_items.dm | 65 +++ code/modules/power/fractal_reactor.dm | 29 -- code/modules/power/power.dm | 6 +- code/modules/power/powernet.dm | 37 +- code/modules/power/smes.dm | 374 ++++++++------ code/modules/power/smes_construction.dm | 6 +- code/modules/power/terminal.dm | 2 +- html/changelogs/atlantiscze-power.yml | 10 + icons/obj/cellrack.dmi | Bin 0 -> 819 bytes maps/example/example-2.dmm | 2 +- nano/templates/psu.tmpl | 58 +++ vorestation.dme | 2 +- 21 files changed, 679 insertions(+), 428 deletions(-) create mode 100644 code/modules/power/debug_items.dm delete mode 100644 code/modules/power/fractal_reactor.dm create mode 100644 html/changelogs/atlantiscze-power.yml create mode 100644 icons/obj/cellrack.dmi create mode 100644 nano/templates/psu.tmpl diff --git a/code/__defines/machinery.dm b/code/__defines/machinery.dm index 718a94118d3..53e47d9acfe 100644 --- a/code/__defines/machinery.dm +++ b/code/__defines/machinery.dm @@ -2,6 +2,7 @@ var/global/defer_powernet_rebuild = 0 // True if net rebuild will be called #define CELLRATE 0.002 // Multiplier for watts per tick <> cell storage (e.g., 0.02 means if there is a load of 1000 watts, 20 units will be taken from a cell per second) // It's a conversion constant. power_used*CELLRATE = charge_provided, or charge_used/CELLRATE = power_provided +#define SMESRATE 0.03333 // Same for SMESes. A different number for some reason. #define KILOWATTS *1000 #define MEGAWATTS *1000000 diff --git a/code/datums/wires/smes.dm b/code/datums/wires/smes.dm index 82d93b9fa83..1751bb67602 100644 --- a/code/datums/wires/smes.dm +++ b/code/datums/wires/smes.dm @@ -11,7 +11,7 @@ var/const/SMES_WIRE_FAILSAFES = 16 // Cut to disable failsafes, mend to reenable /datum/wires/smes/CanUse(var/mob/living/L) var/obj/machinery/power/smes/buildable/S = holder - if(S.open_hatch) + if(S.panel_open) return 1 return 0 diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm index 4521ac08c85..99c0f753c8a 100644 --- a/code/defines/obj/weapon.dm +++ b/code/defines/obj/weapon.dm @@ -230,13 +230,6 @@ desc = "Heavy-duty switching circuits for power control." matter = list(DEFAULT_WALL_MATERIAL = 50, "glass" = 50) -/obj/item/weapon/module/power_control/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) - if (istype(W, /obj/item/device/multitool)) - var/obj/item/weapon/circuitboard/ghettosmes/newcircuit = new/obj/item/weapon/circuitboard/ghettosmes(user.loc) - qdel(src) - user.put_in_hands(newcircuit) - - /obj/item/weapon/module/id_auth name = "\improper ID authentication module" icon_state = "id_mod" diff --git a/code/game/objects/items/weapons/circuitboards/machinery/power.dm b/code/game/objects/items/weapons/circuitboards/machinery/power.dm index 9bd5f98bad4..e874d04d491 100644 --- a/code/game/objects/items/weapons/circuitboards/machinery/power.dm +++ b/code/game/objects/items/weapons/circuitboards/machinery/power.dm @@ -18,14 +18,7 @@ build_path = /obj/machinery/power/smes/batteryrack board_type = new /datum/frame/frame_types/machine origin_tech = list(TECH_POWER = 3, TECH_ENGINEERING = 2) - req_components = list(/obj/item/weapon/cell = 3) - -/obj/item/weapon/circuitboard/ghettosmes - name = T_BOARD("makeshift PSU") - desc = "An APC circuit repurposed into some power storage device controller" - build_path = /obj/machinery/power/smes/batteryrack/makeshift - board_type = new /datum/frame/frame_types/machine - req_components = list(/obj/item/weapon/cell = 3) + req_components = list(/obj/item/weapon/stock_parts/capacitor/ = 3, /obj/item/weapon/stock_parts/matter_bin/ = 1) /obj/item/weapon/circuitboard/grid_checker name = T_BOARD("power grid checker") diff --git a/code/game/objects/structures/loot_piles.dm b/code/game/objects/structures/loot_piles.dm index e14c9ba9e38..053d1293a94 100644 --- a/code/game/objects/structures/loot_piles.dm +++ b/code/game/objects/structures/loot_piles.dm @@ -351,8 +351,8 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh /obj/item/weapon/circuitboard/atmos_alert, /obj/item/weapon/circuitboard/airalarm, /obj/item/weapon/circuitboard/fax, - /obj/item/weapon/circuitboard/ghettosmes, /obj/item/weapon/circuitboard/jukebox, + /obj/item/weapon/circuitboard/batteryrack, /obj/item/weapon/circuitboard/message_monitor, /obj/item/weapon/circuitboard/rcon_console, /obj/item/weapon/smes_coil, diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index 0541d2f1f0c..a3493dc9792 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -1255,7 +1255,7 @@ obj/machinery/power/apc/proc/autoset(var/cur_state, var/on) cell.ex_act(3) return -/obj/machinery/power/apc/disconnect_terminal() +/obj/machinery/power/apc/disconnect_terminal(var/obj/machinery/power/terminal/term) if(terminal) terminal.master = null terminal = null diff --git a/code/modules/power/batteryrack.dm b/code/modules/power/batteryrack.dm index d897bb827a1..d6fe46fa2b3 100644 --- a/code/modules/power/batteryrack.dm +++ b/code/modules/power/batteryrack.dm @@ -1,234 +1,313 @@ -//The one that works safely. +// Cell rack PSU, similar to SMES, but uses power cells to store power. +// Lacks detailed control of input/output values, and has generally much worse capacity. +#define PSU_OFFLINE 0 +#define PSU_OUTPUT 1 +#define PSU_INPUT 2 +#define PSU_AUTO 3 + +#define PSU_MAXCELLS 9 // Capped to 9 cells due to sprite limitation + /obj/machinery/power/smes/batteryrack name = "power cell rack PSU" desc = "A rack of power cells working as a PSU." - charge = 0 //you dont really want to make a potato PSU which already is overloaded - output_attempt = 0 - input_level = 0 - output_level = 0 - input_level_max = 0 - output_level_max = 0 - icon_state = "gsmes" - circuit = /obj/item/weapon/circuitboard/batteryrack - var/cells_amount = 0 - var/capacitors_amount = 0 - var/global/list/br_cache = null + icon = 'icons/obj/cellrack.dmi' + icon_state = "rack" + capacity = 0 + charge = 0 + output_attempt = FALSE + input_attempt = FALSE + + var/max_transfer_rate = 0 // Maximal input/output rate. Determined by used capacitors when building the device. + var/mode = PSU_OFFLINE // Current inputting/outputting mode + var/list/internal_cells = list() // Cells stored in this PSU + var/max_cells = 3 // Maximal amount of stored cells at once. Capped at 9. + var/previous_charge = 0 // Charge previous tick. + var/equalise = 0 // If true try to equalise charge between cells + var/icon_update = 0 // Timer in ticks for icon update. + var/ui_tick = 0 + /obj/machinery/power/smes/batteryrack/New() ..() + add_parts() RefreshParts() - return -//Maybe this should be moved up to obj/machinery /obj/machinery/power/smes/batteryrack/proc/add_parts() component_parts = list() - component_parts += new /obj/item/weapon/cell/high - component_parts += new /obj/item/weapon/cell/high - component_parts += new /obj/item/weapon/cell/high - return + component_parts += new /obj/item/weapon/circuitboard/batteryrack + component_parts += new /obj/item/weapon/stock_parts/capacitor/ // Capacitors: Maximal I/O + component_parts += new /obj/item/weapon/stock_parts/capacitor/ + component_parts += new /obj/item/weapon/stock_parts/capacitor/ + component_parts += new /obj/item/weapon/stock_parts/matter_bin/ // Matter Bin: Max. amount of cells. /obj/machinery/power/smes/batteryrack/RefreshParts() - capacitors_amount = 0 - cells_amount = 0 - var/max_level = 0 //for both input and output + var/capacitor_efficiency = 0 + var/maxcells = 0 for(var/obj/item/weapon/stock_parts/capacitor/CP in component_parts) - max_level += CP.rating - capacitors_amount++ - input_level_max = 50000 + max_level * 20000 - output_level_max = 50000 + max_level * 20000 + capacitor_efficiency += CP.rating - var/C = 0 - for(var/obj/item/weapon/cell/PC in component_parts) - C += PC.maxcharge - cells_amount++ - capacity = C * 40 //Basic cells are such crap. Hyper cells needed to get on normal SMES levels. + for(var/obj/item/weapon/stock_parts/matter_bin/MB in component_parts) + maxcells += MB.rating * 3 + max_transfer_rate = 10000 * capacitor_efficiency // 30kw - 90kw depending on used capacitors. + max_cells = min(PSU_MAXCELLS, maxcells) + input_level = max_transfer_rate + output_level = max_transfer_rate + +/obj/machinery/power/smes/batteryrack/Destroy() + for(var/obj/item/weapon/cell/C in internal_cells) + qdel(C) + internal_cells = null + return ..() /obj/machinery/power/smes/batteryrack/update_icon() overlays.Cut() - if(stat & BROKEN) return + icon_update = 0 - if(!br_cache) - br_cache = list() - br_cache.len = 7 - br_cache[1] = image('icons/obj/power.dmi', "gsmes_outputting") - br_cache[2] = image('icons/obj/power.dmi', "gsmes_charging") - br_cache[3] = image('icons/obj/power.dmi', "gsmes_overcharge") - br_cache[4] = image('icons/obj/power.dmi', "gsmes_og1") - br_cache[5] = image('icons/obj/power.dmi', "gsmes_og2") - br_cache[6] = image('icons/obj/power.dmi', "gsmes_og3") - br_cache[7] = image('icons/obj/power.dmi', "gsmes_og4") - - if (output_attempt) - overlays += br_cache[1] - if(inputting) - overlays += br_cache[2] - - var/clevel = chargedisplay() - if(clevel>0) - overlays += br_cache[3+clevel] - return + var/cellcount = 0 + var/charge_level = between(0, round(Percentage() / 12), 7) -/obj/machinery/power/smes/batteryrack/chargedisplay() - return round(4 * charge/(capacity ? capacity : 5e6)) + overlays += "charge[charge_level]" + + for(var/obj/item/weapon/cell/C in internal_cells) + cellcount++ + overlays += "cell[cellcount]" + if(C.fully_charged()) + overlays += "cell[cellcount]f" + else if(!C.charge) + overlays += "cell[cellcount]e" + +// Recalculate maxcharge and similar variables. +/obj/machinery/power/smes/batteryrack/proc/update_maxcharge() + var/newmaxcharge = 0 + for(var/obj/item/weapon/cell/C in internal_cells) + newmaxcharge += C.maxcharge + + newmaxcharge /= CELLRATE // Convert to Joules + newmaxcharge *= SMESRATE // And to SMES charge units (which are for some reason different than CELLRATE) + capacity = newmaxcharge + charge = between(0, charge, newmaxcharge) -/obj/machinery/power/smes/batteryrack/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) //these can only be moved by being reconstructed, solves having to remake the powernet. - ..() //SMES attackby for now handles screwdriver, cable coils and wirecutters, no need to repeat that here - if(open_hatch) - if(W.is_crowbar()) - if (charge < (capacity / 100)) - if (!output_attempt && !input_attempt) - playsound(src, W.usesound, 50, 1) - var/obj/structure/frame/M = new /obj/structure/frame(src.loc) - M.frame_type = "machine" - M.state = 2 - M.icon_state = "machine_1" - for(var/obj/I in component_parts) - I.loc = src.loc - qdel(src) - return 1 - else - to_chat(user, "Turn off the [src] before dismantling it.") - else - to_chat(user, "Better let [src] discharge before dismantling it.") - else if ((istype(W, /obj/item/weapon/stock_parts/capacitor) && (capacitors_amount < 5)) || (istype(W, /obj/item/weapon/cell) && (cells_amount < 5))) - if (charge < (capacity / 100)) - if (!output_attempt && !input_attempt) - user.drop_item() - component_parts += W - W.loc = src - RefreshParts() - to_chat(user, "You upgrade the [src] with [W.name].") - else - to_chat(user, "Turn off the [src] before dismantling it.") - else - to_chat(user, "Better let [src] discharge before putting your hand inside it.") - else - user.set_machine(src) - interact(user) - return 1 - return +// Sets input/output depending on our "mode" var. +/obj/machinery/power/smes/batteryrack/proc/update_io(var/newmode) + mode = newmode + switch(mode) + if(PSU_OFFLINE) + input_attempt = 0 + output_attempt = 0 + if(PSU_INPUT) + input_attempt = 1 + output_attempt = 0 + if(PSU_OUTPUT) + input_attempt = 0 + output_attempt = 1 + if(PSU_AUTO) + input_attempt = 1 + output_attempt = 1 + +// Store charge in the power cells, instead of using the charge var. Amount is in joules. +/obj/machinery/power/smes/batteryrack/add_charge(var/amount) + amount *= CELLRATE // Convert to CELLRATE first. + if(equalise) + // Now try to get least charged cell and use the power from it. + var/obj/item/weapon/cell/CL = get_least_charged_cell() + amount -= CL.give(amount) + if(!amount) + return + // We're still here, so it means the least charged cell was full OR we don't care about equalising the charge. Give power to other cells instead. + for(var/obj/item/weapon/cell/C in internal_cells) + amount -= C.give(amount) + // No more power to input so return. + if(!amount) + return -//The shitty one that will blow up. -/obj/machinery/power/smes/batteryrack/makeshift - name = "makeshift PSU" - desc = "A rack of batteries connected by a mess of wires posing as a PSU." - circuit = /obj/item/weapon/circuitboard/ghettosmes - var/overcharge_percent = 0 +/obj/machinery/power/smes/batteryrack/remove_charge(var/amount) + amount *= CELLRATE // Convert to CELLRATE first. + if(equalise) + // Now try to get most charged cell and use the power from it. + var/obj/item/weapon/cell/CL = get_most_charged_cell() + amount -= CL.use(amount) + if(!amount) + return + // We're still here, so it means the most charged cell didn't have enough power OR we don't care about equalising the charge. Use power from other cells instead. + for(var/obj/item/weapon/cell/C in internal_cells) + amount -= C.use(amount) + // No more power to output so return. + if(!amount) + return + +// Helper procs to get most/least charged cells. +/obj/machinery/power/smes/batteryrack/proc/get_most_charged_cell() + var/obj/item/weapon/cell/CL = null + for(var/obj/item/weapon/cell/C in internal_cells) + if(CL == null) + CL = C + else if(CL.percent() < C.percent()) + CL = C + return CL +/obj/machinery/power/smes/batteryrack/proc/get_least_charged_cell() + var/obj/item/weapon/cell/CL = null + for(var/obj/item/weapon/cell/C in internal_cells) + if(CL == null) + CL = C + else if(CL.percent() > C.percent()) + CL = C + return CL + +/obj/machinery/power/smes/batteryrack/proc/insert_cell(var/obj/item/weapon/cell/C, var/mob/user) + if(!istype(C)) + return 0 + + if(internal_cells.len >= max_cells) + return 0 + + internal_cells.Add(C) + if(user) + user.drop_from_inventory(C) + C.forceMove(src) + RefreshParts() + update_maxcharge() + update_icon() + return 1 -/obj/machinery/power/smes/batteryrack/makeshift/add_parts() - component_parts = list() - component_parts += new /obj/item/weapon/cell/high - component_parts += new /obj/item/weapon/cell/high - component_parts += new /obj/item/weapon/cell/high - return +/obj/machinery/power/smes/batteryrack/process() + charge = 0 + for(var/obj/item/weapon/cell/C in internal_cells) + charge += C.charge + charge /= CELLRATE // Convert to Joules + charge *= SMESRATE // And to SMES charge units (which are for some reason different than CELLRATE) -/obj/machinery/power/smes/batteryrack/makeshift/update_icon() - overlays.Cut() - if(stat & BROKEN) return + ..() + ui_tick = !ui_tick + icon_update++ - if (output_attempt) - overlays += br_cache[1] - if(inputting) - overlays += br_cache[2] - if (overcharge_percent > 100) - overlays += br_cache[3] - else - var/clevel = chargedisplay() - if(clevel>0) - overlays += br_cache[3+clevel] - return - -//This mess of if-elses and magic numbers handles what happens if the engies don't pay attention and let it eat too much charge -//What happens depends on how much capacity has the ghetto smes and how much it is overcharged. -//Under 1.2M: 5% of ion_act() per process() tick from 125% and higher overcharges. 1.2M is achieved with 3 high cells. -//[1.2M-2.4M]: 6% ion_act from 120%. 1% of EMP from 140%. -//(2.4M-3.6M] :7% ion_act from 115%. 1% of EMP from 130%. 1% of non-hull-breaching explosion at 150%. -//(3.6M-INFI): 8% ion_act from 115%. 2% of EMP from 125%. 1% of Hull-breaching explosion from 140%. -/obj/machinery/power/smes/batteryrack/makeshift/proc/overcharge_consequences() - switch (capacity) - if (0 to (1.2e6-1)) - if (overcharge_percent >= 125) - if (prob(5)) - ion_act() - if (1.2e6 to 2.4e6) - if (overcharge_percent >= 120) - if (prob(6)) - ion_act() - else - return - if (overcharge_percent >= 140) - if (prob(1)) - empulse(src.loc, 2, 3, 6, 8, 1) - if ((2.4e6+1) to 3.6e6) - if (overcharge_percent >= 115) - if (prob(7)) - ion_act() - else - return - if (overcharge_percent >= 130) - if (prob(1)) - empulse(src.loc, 2, 3, 6, 8, 1) - if (overcharge_percent >= 150) - if (prob(1)) - explosion(src.loc, 0, 1, 3, 5) - if ((3.6e6+1) to INFINITY) - if (overcharge_percent >= 115) - if (prob(8)) - ion_act() - else - return - if (overcharge_percent >= 125) - if (prob(2)) - empulse(src.loc, 2, 4, 7, 10, 1) - if (overcharge_percent >= 140) - if (prob(1)) - explosion(src.loc, 1, 3, 5, 8) - else //how the hell was this proc called for negative charge - charge = 0 - - -#define SMESRATE 0.05 // rate of internal charge to external power -/obj/machinery/power/smes/batteryrack/makeshift/process() - if(stat & BROKEN) return - - //store machine state to see if we need to update the icon overlays - var/last_disp = chargedisplay() - var/last_chrg = inputting - var/last_onln = output_attempt - var/last_overcharge = overcharge_percent - - if(terminal) - if(input_attempt) - 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 - - if (actual_load >= target_load) // did the powernet have enough power available for us? - inputting = 1 - else - inputting = 0 - - if(output_attempt) // if outputting - 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) - add_avail(output_used) // add output to powernet (smes side) - if(charge < 0.0001) - outputting(0) // stop output if charge falls to zero - - overcharge_percent = round((charge / capacity) * 100) - if (overcharge_percent > 115) //115% is the minimum overcharge for anything to happen - overcharge_consequences() - - // only update icon if state changed - if(last_disp != chargedisplay() || last_chrg != inputting || last_onln != output_attempt || ((overcharge_percent > 100) ^ (last_overcharge > 100))) + // Don't update icon too much, prevents unnecessary processing. + if(icon_update >= 10) update_icon() + // Try to balance charge between stored cells. Capped at max_transfer_rate per tick. + // Take power from most charged cell, and give it to least charged cell. + if(equalise) + var/obj/item/weapon/cell/least = get_least_charged_cell() + var/obj/item/weapon/cell/most = get_most_charged_cell() + // Don't bother equalising charge between two same cells. Also ensure we don't get NULLs or wrong types. Don't bother equalising when difference between charges is tiny. + if(least == most || !istype(least) || !istype(most) || least.percent() == most.percent()) + return + var/percentdiff = (most.percent() - least.percent()) / 2 // Transfer only 50% of power. The reason is that it could lead to situations where least and most charged cells would "swap places" (45->50% and 50%->45%) + var/celldiff + // Take amount of power to transfer from the cell with smaller maxcharge + if(most.maxcharge > least.maxcharge) + celldiff = (least.maxcharge / 100) * percentdiff + else + celldiff = (most.maxcharge / 100) * percentdiff + celldiff = between(0, celldiff, max_transfer_rate * CELLRATE) + // Ensure we don't transfer more energy than the most charged cell has, and that the least charged cell can input. + celldiff = min(min(celldiff, most.charge), least.maxcharge - least.charge) + least.give(most.use(celldiff)) + +/obj/machinery/power/smes/batteryrack/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + var/data[0] + + data["mode"] = mode + data["transfer_max"] = max_transfer_rate + data["output_load"] = round(output_used) + data["input_load"] = round(input_available) + data["equalise"] = equalise + data["blink_tick"] = ui_tick + data["cells_max"] = max_cells + data["cells_cur"] = internal_cells.len + var/list/cells = list() + var/cell_index = 0 + for(var/obj/item/weapon/cell/C in internal_cells) + var/list/cell[0] + cell["slot"] = cell_index + 1 + cell["used"] = 1 + cell["percentage"] = round(C.percent(), 0.01) + cell["id"] = C.c_uid + cell_index++ + cells += list(cell) + while(cell_index < PSU_MAXCELLS) + var/list/cell[0] + cell["slot"] = cell_index + 1 + cell["used"] = 0 + cell_index++ + cells += list(cell) + data["cells_list"] = cells + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "psu.tmpl", "Cell Rack PSU", 500, 430) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/power/smes/batteryrack/dismantle() + for(var/obj/item/weapon/cell/C in internal_cells) + C.forceMove(get_turf(src)) + internal_cells -= C + return ..() + +/obj/machinery/power/smes/batteryrack/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) + if(!..()) + return 0 + if(default_deconstruction_crowbar(user, W)) + return + if(default_part_replacement(user, W)) + return + if(istype(W, /obj/item/weapon/cell)) // ID Card, try to insert it. + if(insert_cell(W, user)) + to_chat(user, "You insert \the [W] into \the [src].") + else + to_chat(user, "\The [src] has no empty slot for \the [W]") + +/obj/machinery/power/smes/batteryrack/attack_hand(var/mob/user) + ui_interact(user) + +/obj/machinery/power/smes/batteryrack/inputting() return -#undef SMESRATE +/obj/machinery/power/smes/batteryrack/outputting() + return + +/obj/machinery/power/smes/batteryrack/Topic(href, href_list) + // ..() would respond to those topic calls, but we don't want to use them at all. + // Calls to these shouldn't occur anyway, due to usage of different nanoUI, but + // it's here in case someone decides to try hrefhacking/modified templates. + if(href_list["input"] || href_list["output"]) + return 1 + + if(..()) + return 1 + if( href_list["disable"] ) + update_io(0) + return 1 + else if( href_list["enable"] ) + update_io(between(1, text2num(href_list["enable"]), 3)) + return 1 + else if( href_list["equaliseon"] ) + equalise = 1 + return 1 + else if( href_list["equaliseoff"] ) + equalise = 0 + return 1 + else if( href_list["ejectcell"] ) + var/obj/item/weapon/cell/C + for(var/obj/item/weapon/cell/CL in internal_cells) + if(CL.c_uid == text2num(href_list["ejectcell"])) + C = CL + break + + if(!istype(C)) + return 1 + + C.forceMove(get_turf(src)) + internal_cells -= C + update_icon() + RefreshParts() + update_maxcharge() + return 1 \ No newline at end of file diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index acf90fcadf6..1ab2d6a5ea9 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -116,7 +116,7 @@ var/list/possible_cable_coil_colours = list( user.examinate(src) // following code taken from attackby (multitool) if(powernet && (powernet.avail > 0)) - to_chat(user, "[powernet.avail]W in power network.") + to_chat(user, "[DisplayPower(powernet.avail)] in power network.") else to_chat(user, "The cable is not powered.") return @@ -230,7 +230,7 @@ var/list/possible_cable_coil_colours = list( else if(istype(W, /obj/item/device/multitool)) if(powernet && (powernet.avail > 0)) // is it powered? - to_chat(user, "[powernet.avail]W in power network.") + to_chat(user, "[DisplayPower(powernet.avail)] in power network.") else to_chat(user, "The cable is not powered.") @@ -617,6 +617,13 @@ obj/structure/cable/proc/cableColor(var/colorC) else if(Adjacent(user)) . += "There are [get_amount()] lengths of cable in the coil." +/obj/item/stack/cable_coil/attackby(obj/item/W, mob/user) + if(istype(W, /obj/item/device/multitool)) + var/selected_type = input("Pick new colour.", "Cable Colour", null, null) as null|anything in possible_cable_coil_colours + set_cable_color(selected_type, usr) + return + return ..() + /obj/item/stack/cable_coil/verb/make_restraint() set name = "Make Cable Restraints" set category = "Object" diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm index ec5800deb65..ecfbf00a5fc 100644 --- a/code/modules/power/cell.dm +++ b/code/modules/power/cell.dm @@ -14,6 +14,8 @@ throw_speed = 3 throw_range = 5 w_class = ITEMSIZE_NORMAL + var/static/cell_uid = 1 // Unique ID of this power cell. Used to reduce bunch of uglier code in nanoUI. + var/c_uid var/charge = 0 // note %age conveted to actual charge in New var/maxcharge = 1000 var/rigged = 0 // true if rigged to explode @@ -31,6 +33,7 @@ /obj/item/weapon/cell/New() ..() + c_uid = cell_uid++ charge = maxcharge update_icon() if(self_recharge) diff --git a/code/modules/power/debug_items.dm b/code/modules/power/debug_items.dm new file mode 100644 index 00000000000..6fee9cce223 --- /dev/null +++ b/code/modules/power/debug_items.dm @@ -0,0 +1,65 @@ +/obj/machinery/power/debug_items/ + icon = 'icons/obj/power.dmi' + icon_state = "tracker" + anchored = 1 + density = 1 + var/show_extended_information = 1 // Set to 0 to disable extra information on examining (for example, when used on admin events) + +/obj/machinery/power/debug_items/examine(mob/user) + . = ..() + if(show_extended_information) + show_info(user) + +/obj/machinery/power/debug_items/proc/show_info(var/mob/user) + if(!powernet) + to_chat(user, "This device is not connected to a powernet") + return + + to_chat(user, "Connected to powernet: [powernet]") + to_chat(user, "Available power: [num2text(powernet.avail, 20)] W") + to_chat(user, "Load: [num2text(powernet.viewload, 20)] W") + to_chat(user, "Has alert: [powernet.problem ? "YES" : "NO"]") + to_chat(user, "Cables: [powernet.cables.len]") + to_chat(user, "Nodes: [powernet.nodes.len]") + + +// An infinite power generator. Adds energy to connected cable. +/obj/machinery/power/debug_items/infinite_generator + name = "fractal energy reactor" + desc = "An experimental power generator capable of generating massive amounts of energy from subspace vacuum." + var/power_generation_rate = 1000000 + +/obj/machinery/power/debug_items/infinite_generator/process() + add_avail(power_generation_rate) + +/obj/machinery/power/debug_items/infinite_generator/show_info(var/mob/user) + ..() + to_chat(user, "Generator is providing [num2text(power_generation_rate, 20)] W") + + +// A cable powersink, without the explosion/network alarms normal powersink causes. +/obj/machinery/power/debug_items/infinite_cable_powersink + name = "null point core" + desc = "An experimental device that disperses energy, used for grid stress-testing purposes." + var/power_usage_rate = 0 + var/last_used = 0 + +/obj/machinery/power/debug_items/infinite_cable_powersink/process() + last_used = draw_power(power_usage_rate) + +/obj/machinery/power/debug_items/infinite_cable_powersink/show_info(var/mob/user) + ..() + to_chat(user, "Power sink is demanding [num2text(power_usage_rate, 20)] W") + to_chat(user, "[num2text(last_used, 20)] W was actually used last tick") + + +/obj/machinery/power/debug_items/infinite_apc_powersink + name = "APC dummy load" + desc = "A dummy load that connects to an APC, used for load testing purposes." + use_power = USE_POWER_ACTIVE + active_power_usage = 0 + +/obj/machinery/power/debug_items/infinite_apc_powersink/show_info(var/mob/user) + ..() + to_chat(user, "Dummy load is using [num2text(active_power_usage, 20)] W") + to_chat(user, "Powered: [powered() ? "YES" : "NO"]") \ No newline at end of file diff --git a/code/modules/power/fractal_reactor.dm b/code/modules/power/fractal_reactor.dm deleted file mode 100644 index 59b6196d30f..00000000000 --- a/code/modules/power/fractal_reactor.dm +++ /dev/null @@ -1,29 +0,0 @@ -// ############################################################################### -// # ITEM: FRACTAL ENERGY REACTOR # -// # FUNCTION: Generate infinite electricity. Used for map testing. # -// ############################################################################### - -/obj/machinery/power/fractal_reactor - name = "Fractal Energy Reactor" - desc = "This thing drains power from fractal-subspace." // (DEBUG ITEM: INFINITE POWERSOURCE FOR MAP TESTING. CONTACT DEVELOPERS IF FOUND.)" - icon = 'icons/obj/power.dmi' - icon_state = "tracker" //ICON stolen from solar tracker. There is no need to make new texture for debug item - anchored = 1 - density = 1 - var/power_generation_rate = 1000000 //Defaults to 1MW of power. - var/powernet_connection_failed = 0 - var/mapped_in = 0 //Do not announce creation when it's mapped in. - - // This should be only used on Dev for testing purposes. -/obj/machinery/power/fractal_reactor/New() - ..() - if(!mapped_in) - to_world("WARNING: Map testing power source activated at: X:[src.loc.x] Y:[src.loc.y] Z:[src.loc.z]") - -/obj/machinery/power/fractal_reactor/process() - if(!powernet && !powernet_connection_failed) - if(!connect_to_network()) - powernet_connection_failed = 1 - spawn(150) // Error! Check again in 15 seconds. - powernet_connection_failed = 0 - add_avail(power_generation_rate) \ No newline at end of file diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index 25720561bc0..5c8ba5a8a41 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -17,8 +17,6 @@ /obj/machinery/power/Destroy() disconnect_from_network() - disconnect_terminal() - return ..() /////////////////////////////// @@ -37,6 +35,8 @@ /obj/machinery/power/proc/add_avail(var/amount) if(powernet) powernet.newavail += amount + return TRUE + return FALSE /obj/machinery/power/proc/draw_power(var/amount) if(powernet) @@ -61,7 +61,7 @@ else return 0 -/obj/machinery/power/proc/disconnect_terminal() // machines without a terminal will just return, no harm no fowl. +/obj/machinery/power/proc/disconnect_terminal(var/obj/machinery/power/terminal/term) // machines without a terminal will just return, no harm no fowl. return // connect the machine to a powernet if a node cable is present on the turf diff --git a/code/modules/power/powernet.dm b/code/modules/power/powernet.dm index 2710d697888..b08586a4496 100644 --- a/code/modules/power/powernet.dm +++ b/code/modules/power/powernet.dm @@ -8,6 +8,11 @@ var/viewload = 0 // the load as it appears on the power console (gradually updated) var/number = 0 // Unused //TODEL + var/smes_demand = 0 // Amount of power demanded by all SMESs from this network. Needed for load balancing. + var/list/inputting = list() // List of SMESs that are demanding power from this network. Needed for load balancing. + var/smes_avail = 0 // Amount of power (avail) from SMESes. Used by SMES load balancing + var/smes_newavail = 0 // As above, just for newavail + var/perapc = 0 // per-apc avilability var/perapc_excess = 0 var/netexcess = 0 // excess power on the powernet (typically avail-load) @@ -113,9 +118,20 @@ perapc = avail/numapc + perapc_excess - if(netexcess > 100 && nodes && nodes.len) // if there was excess power last cycle - for(var/obj/machinery/power/smes/S in nodes) // find the SMESes in the network - S.restore() // and restore some of the power that was used + + // At this point, all other machines have finished using power. Anything left over may be used up to charge SMESs. + if(inputting.len && smes_demand) + var/smes_input_percentage = between(0, (netexcess / smes_demand) * 100, 100) + for(var/obj/machinery/power/terminal/T in inputting) + var/obj/machinery/power/smes/S = T.master + if(istype(S)) + S.input_power(smes_input_percentage, T) + + netexcess = avail - load + if(netexcess) + var/perc = get_percent_load(1) + for(var/obj/machinery/power/smes/S in nodes) + S.restore(perc) //updates the viewed load (as seen on power computers) viewload = round(load) @@ -123,7 +139,22 @@ //reset the powernet load = 0 avail = newavail + smes_avail = smes_newavail + inputting.Cut() + smes_demand = 0 newavail = 0 + smes_newavail = 0 + +/datum/powernet/proc/get_percent_load(var/smes_only = 0) + if(smes_only) + var/smes_used = load - (avail - smes_avail) // SMESs are always last to provide power + if(!smes_used || smes_used < 0 || !smes_avail) // SMES power isn't available or being used at all, SMES load is therefore 0% + return 0 + return between(0, (smes_used / smes_avail) * 100, 100) // Otherwise return percentage load of SMESs. + else + if(!load) + return 0 + return between(0, (load / avail) * 100, 100) /datum/powernet/proc/get_electrocute_damage() switch(avail) diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index a3ee1a2033e..e6555705455 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -1,7 +1,6 @@ // the SMES // stores power -#define SMESRATE 0.03333 //translates Watt into Kilowattminutes with respect to machinery schedule_interval ~(2s*1W*1min/60s) #define SMESMAXCHARGELEVEL 250000 #define SMESMAXOUTPUT 250000 @@ -25,7 +24,7 @@ var/input_available = 0 // amount of charge available from input last tick var/output_attempt = 1 // 1 = attempting to output, 0 = not attempting to output - var/outputting = 1 // 1 = actually outputting, 0 = not outputting + var/outputting = 0 // 1 = actually outputting, 0 = not outputting var/output_level = 50000 // amount of power the SMES attempts to output var/output_level_max = 200000 // cap on output_level var/output_used = 0 // amount of power actually outputted. may be less than output_level if the powernet returns excess power @@ -35,15 +34,23 @@ var/last_input_attempt = 0 var/last_charge = 0 + //For icon overlay updates + var/last_disp + var/last_chrg + var/last_onln + + var/damage = 0 + var/maxdamage = 500 // Relatively resilient, given how expensive it is, but once destroyed produces small explosion. + var/input_cut = 0 var/input_pulsed = 0 var/output_cut = 0 var/output_pulsed = 0 + var/target_load = 0 - var/open_hatch = 0 var/name_tag = null var/building_terminal = 0 //Suggestions about how to avoid clickspam building several terminals accepted! - var/obj/machinery/power/terminal/terminal = null + var/list/terminals = list() 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. @@ -58,38 +65,38 @@ /obj/machinery/power/smes/Initialize() . = ..() - if(!powernet) - connect_to_network() - - dir_loop: - for(var/d in cardinal) - var/turf/T = get_step(src, d) - for(var/obj/machinery/power/terminal/term in T) - if(term && term.dir == turn(d, 180)) - terminal = term - break dir_loop - if(!terminal) + for(var/d in GLOB.cardinal) + var/turf/T = get_step(src, d) + for(var/obj/machinery/power/terminal/term in T) + if(term && term.dir == turn(d, 180) && !term.master) + terminals |= term + term.master = src + term.connect_to_network() + if(!terminals.len) stat |= BROKEN return - terminal.master = src - if(!terminal.powernet) - terminal.connect_to_network() update_icon() - + if(!powernet) + connect_to_network() if(!should_be_mapped) warning("Non-buildable or Non-magical SMES at [src.x]X [src.y]Y [src.z]Z") /obj/machinery/power/smes/Destroy() - terminal = null + for(var/obj/machinery/power/terminal/T in terminals) + T.master = null + terminals = null return ..() -/obj/machinery/power/smes/disconnect_terminal() - if(terminal) - terminal.master = null - terminal = null +/obj/machinery/power/smes/add_avail(var/amount) + if(..(amount)) + powernet.smes_newavail += amount return 1 return 0 +/obj/machinery/power/smes/disconnect_terminal(var/obj/machinery/power/terminal/term) + terminals -= term + term.master = null + /obj/machinery/power/smes/update_icon() cut_overlays() if(stat & BROKEN) return @@ -113,48 +120,67 @@ /obj/machinery/power/smes/proc/chargedisplay() return round(5.5*charge/(capacity ? capacity : 5e6)) +/obj/machinery/power/smes/proc/input_power(var/percentage, var/obj/machinery/power/terminal/term) + var/to_input = target_load * (percentage/100) + to_input = between(0, to_input, target_load) + if(percentage == 100) + inputting = 2 + else if(percentage) + inputting = 1 + // else inputting = 0, as set in process() + + var/inputted = term.powernet.draw_power(min(to_input, input_level - input_available)) + add_charge(inputted) + input_available += inputted + +// Mostly in place due to child types that may store power in other way (PSUs) +/obj/machinery/power/smes/proc/add_charge(var/amount) + charge += amount*SMESRATE + +/obj/machinery/power/smes/proc/remove_charge(var/amount) + charge -= amount*SMESRATE + /obj/machinery/power/smes/process() if(stat & BROKEN) return - //store machine state to see if we need to update the icon overlays - var/last_disp = chargedisplay() - var/last_chrg = inputting - var/last_onln = outputting - - //inputting - 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 - - if (actual_load >= target_load) // Did we charge at full rate? - inputting = 2 - else if (actual_load) // If not, did we charge at least partially? - inputting = 1 - else // Or not at all? - inputting = 0 - - //outputting - 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) - - add_avail(output_used) // add output to powernet (smes side) - else if(output_attempt && output_level > 0) - outputting = 1 - else - output_used = 0 - // only update icon if state changed if(last_disp != chargedisplay() || last_chrg != inputting || last_onln != outputting) update_icon() + //store machine state to see if we need to update the icon overlays + last_disp = chargedisplay() + last_chrg = inputting + last_onln = outputting + input_available = 0 + //inputting + if(input_attempt && (!input_pulsed && !input_cut) && !grid_check) + target_load = min((capacity-charge)/SMESRATE, input_level) // Amount we will request from the powernet. + var/input_available = FALSE + for(var/obj/machinery/power/terminal/term in terminals) + if(!term.powernet) + continue + input_available = TRUE + term.powernet.smes_demand += target_load + term.powernet.inputting.Add(term) + if(!input_available) + target_load = 0 // We won't input any power without powernet connection. + inputting = 0 + output_used = 0 + //outputting + if(output_attempt && (!output_pulsed && !output_cut) && powernet && charge && !grid_check) + output_used = min( charge/SMESRATE, output_level) //limit output to that stored + remove_charge(output_used) // reduce the storage (may be recovered in /restore() if excessive) + add_avail(output_used) // add output to powernet (smes side) + outputting = 2 + else if(!powernet || !charge) + outputting = 1 + else + output_used = 0 // called after all power processes are finished // restores charge level to smes if there was excess this ptick -/obj/machinery/power/smes/proc/restore() +/obj/machinery/power/smes/proc/restore(var/percent_load) if(stat & BROKEN) return @@ -162,20 +188,17 @@ output_used = 0 return - var/excess = powernet.netexcess // this was how much wasn't used on the network last ptick, minus any removed by other SMESes - - excess = min(output_used, excess) // clamp it to how much was actually output by this SMES last ptick - - excess = min((capacity-charge)/SMESRATE, excess) // for safety, also limit recharge by space capacity of SMES (shouldn't happen) + var/total_restore = output_used * (percent_load / 100) // First calculate amount of power used from our output + total_restore = between(0, total_restore, output_used) // Now clamp the value between 0 and actual output, just for clarity. + total_restore = output_used - total_restore // And, at last, subtract used power from outputted power, to get amount of power we will give back to the SMES. // now recharge this amount - var/clev = chargedisplay() - charge += excess * SMESRATE // restore unused power - powernet.netexcess -= excess // remove the excess from the powernet, so later SMESes don't try to use it + add_charge(total_restore) // restore unused power + powernet.netexcess -= total_restore // remove the excess from the powernet, so later SMESes don't try to use it - output_used -= excess + output_used -= total_restore if(clev != chargedisplay() ) //if needed updates the icons overlay update_icon() @@ -184,7 +207,7 @@ //Will return 1 on failure /obj/machinery/power/smes/proc/make_terminal(const/mob/user) if (user.loc == loc) - to_chat(user, "You must not be on the same tile as the [src].") + to_chat(user, "You must not be on the same tile as the [src].") return 1 //Direction the terminal will face to @@ -196,26 +219,42 @@ tempDir = WEST var/turf/tempLoc = get_step(src, reverse_direction(tempDir)) if (istype(tempLoc, /turf/space)) - to_chat(user, "You can't build a terminal on space.") + to_chat(user, "You can't build a terminal on space.") return 1 else if (istype(tempLoc)) if(!tempLoc.is_plating()) - to_chat(user, "You must remove the floor plating first.") + to_chat(user, "You must remove the floor plating first.") return 1 - to_chat(user, "You start adding cable to the [src].") + if(check_terminal_exists(tempLoc, user, tempDir)) + return 1 + to_chat(user, "You start adding cable to the [src].") if(do_after(user, 50)) - terminal = new /obj/machinery/power/terminal(tempLoc) - terminal.set_dir(tempDir) - terminal.master = src - terminal.connect_to_network() + if(check_terminal_exists(tempLoc, user, tempDir)) + return 1 + var/obj/machinery/power/terminal/term = new/obj/machinery/power/terminal(tempLoc) + term.set_dir(tempDir) + term.master = src + term.connect_to_network() + terminals |= term return 0 return 1 +/obj/machinery/power/smes/proc/check_terminal_exists(var/turf/location, var/mob/user, var/direction) + for(var/obj/machinery/power/terminal/term in location) + if(term.dir == direction) + to_chat(user, "There is already a terminal here.") + return 1 + return 0 /obj/machinery/power/smes/draw_power(var/amount) - if(terminal && terminal.powernet) - return terminal.powernet.draw_power(amount) - return 0 + var/drained = 0 + for(var/obj/machinery/power/terminal/term in terminals) + if(!term.powernet) + continue + if((amount - drained) <= 0) + return 0 + drained += term.powernet.draw_power(amount - drained) + return drained /obj/machinery/power/smes/attack_ai(mob/user) @@ -228,65 +267,78 @@ /obj/machinery/power/smes/attackby(var/obj/item/weapon/W as obj, var/mob/user as mob) - if(W.is_screwdriver()) - if(!open_hatch) - open_hatch = 1 - to_chat(user, "You open the maintenance hatch of [src].") - playsound(src, W.usesound, 50, 1) - return 0 - else - open_hatch = 0 - to_chat(user, "You close the maintenance hatch of [src].") - playsound(src, W.usesound, 50, 1) - return 0 + if(default_deconstruction_screwdriver(user, W)) + return FALSE - if (!open_hatch) - to_chat(user, "You need to open access hatch on [src] first!") - return 0 + if (!panel_open) + to_chat(user, "You need to open access hatch on [src] first!") + return FALSE - if(istype(W, /obj/item/stack/cable_coil) && !terminal && !building_terminal) + if(istype(W, /obj/item/weapon/weldingtool)) + var/obj/item/weapon/weldingtool/WT = W + if(!WT.isOn()) + to_chat(user, "Turn on \the [WT] first!") + return FALSE + if(!damage) + to_chat(user, "\The [src] is already fully repaired.") + return FALSE + if(WT.remove_fuel(0,user) && do_after(user, damage, src)) + to_chat(user, "You repair all structural damage to \the [src]") + damage = 0 + return FALSE + else if(istype(W, /obj/item/stack/cable_coil) && !building_terminal) building_terminal = 1 var/obj/item/stack/cable_coil/CC = W if (CC.get_amount() < 10) - to_chat(user, "You need more cables.") + to_chat(user, "You need more cables.") building_terminal = 0 - return 0 + return FALSE if (make_terminal(user)) building_terminal = 0 - return 0 + return FALSE building_terminal = 0 CC.use(10) user.visible_message(\ - "[user.name] has added cables to the [src].",\ - "You added cables to the [src].") + "[user.name] has added cables to the [src].",\ + "You added cables to the [src].") stat = 0 - return 0 + return FALSE - else if(W.is_wirecutter() && terminal && !building_terminal) - building_terminal = 1 - var/turf/tempTDir = terminal.loc + else if(W.is_wirecutter() && !building_terminal) + building_terminal = TRUE + var/obj/machinery/power/terminal/term + for(var/obj/machinery/power/terminal/T in get_turf(user)) + if(T.master == src) + term = T + break + if(!term) + to_chat(user, "There is no terminal on this tile.") + building_terminal = FALSE + return FALSE + var/turf/tempTDir = get_turf(term) if (istype(tempTDir)) if(!tempTDir.is_plating()) - to_chat(user, "You must remove the floor plating first.") + to_chat(user, "You must remove the floor plating first.") else - to_chat(user, "You begin to cut the cables...") + to_chat(user, "You begin to cut the cables...") playsound(get_turf(src), 'sound/items/Deconstruct.ogg', 50, 1) if(do_after(user, 50 * W.toolspeed)) - if (prob(50) && electrocute_mob(usr, terminal.powernet, terminal)) + if (prob(50) && electrocute_mob(usr, term.powernet, term)) var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread s.set_up(5, 1, src) s.start() - building_terminal = 0 + building_terminal = FALSE if(usr.stunned) - return 0 + return FALSE new /obj/item/stack/cable_coil(loc,10) user.visible_message(\ - "[user.name] cut the cables and dismantled the power terminal.",\ - "You cut the cables and dismantle the power terminal.") - qdel(terminal) - building_terminal = 0 - return 0 - return 1 + "[user.name] cut the cables and dismantled the power terminal.",\ + "You cut the cables and dismantle the power terminal.") + terminals -= term + qdel(term) + building_terminal = FALSE + return FALSE + return TRUE /obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) @@ -303,21 +355,12 @@ data["chargeMode"] = input_attempt data["chargeLevel"] = round(input_level/1000, 0.1) data["chargeMax"] = round(input_level_max/1000) - if (terminal && terminal.powernet) - data["chargeLoad"] = round(terminal.powernet.avail/1000, 0.1) - else - data["chargeLoad"] = 0 + data["chargeLoad"] = round(input_available/1000, 0.1) data["outputOnline"] = output_attempt data["outputLevel"] = round(output_level/1000, 0.1) data["outputMax"] = round(output_level_max/1000) data["outputLoad"] = round(output_used/1000, 0.1) - - if(outputting) - data["outputting"] = 2 // smes is outputting - else if(!outputting && output_attempt) - data["outputting"] = 1 // smes is online but not outputting because it's charge level is too low - else - data["outputting"] = 0 // smes is not outputting + data["outputting"] = outputting // update the ui if it exists, returns null if no ui is passed/found ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) @@ -333,6 +376,8 @@ ui.set_auto_update(1) /obj/machinery/power/smes/proc/Percentage() + if(!capacity) + return 0 return round(100.0*charge/capacity, 0.1) /obj/machinery/power/smes/Topic(href, href_list) @@ -342,10 +387,11 @@ if( href_list["cmode"] ) inputting(!input_attempt) update_icon() - + return 1 else if( href_list["online"] ) outputting(!output_attempt) update_icon() + return 1 else if( href_list["input"] ) switch( href_list["input"] ) if("min") @@ -355,7 +401,7 @@ if("set") input_level = (input(usr, "Enter new input level (0-[input_level_max/1000] kW)", "SMES Input Power Control", input_level/1000) as num) * 1000 input_level = max(0, min(input_level_max, input_level)) // clamp to range - + return 1 else if( href_list["output"] ) switch( href_list["output"] ) if("min") @@ -365,42 +411,7 @@ if("set") output_level = (input(usr, "Enter new output level (0-[output_level_max/1000] kW)", "SMES Output Power Control", output_level/1000) as num) * 1000 output_level = max(0, min(output_level_max, output_level)) // clamp to range - - investigate_log("input/output; on":"off"] | Input-mode: [input_attempt?"auto":"off"] by [usr.key]","singulo") - log_game("SMES([x],[y],[z]) [key_name(usr)] changed settings: I:[input_level]([input_attempt]), O:[output_level]([output_attempt])") - return 1 - - -/obj/machinery/power/smes/proc/ion_act() - if(src.z in using_map.station_levels) - if(prob(1)) //explosion - for(var/mob/M in viewers(src)) - M.show_message("The [src.name] is making strange noises!", 3, "You hear sizzling electronics.", 2) - sleep(10*pick(4,5,6,7,10,14)) - var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread() - smoke.set_up(3, 0, src.loc) - smoke.attach(src) - smoke.start() - explosion(src.loc, -1, 0, 1, 3, 1, 0) - qdel(src) - return - if(prob(15)) //Power drain - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(3, 1, src) - s.start() - if(prob(25)) - emp_act(1) - else if(prob(25)) - emp_act(2) - else if(prob(25)) - emp_act(3) - else - emp_act(4) - if(prob(5)) //smoke only - var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread() - smoke.set_up(3, 0, src.loc) - smoke.attach(src) - smoke.start() + return 1 /obj/machinery/power/smes/proc/inputting(var/do_input) input_attempt = do_input @@ -412,6 +423,21 @@ if(!output_attempt) outputting = 0 +/obj/machinery/power/smes/take_damage(var/amount) + amount = max(0, round(amount)) + damage += amount + if(damage > maxdamage) + visible_message("\The [src] explodes in large shower of sparks and smoke!") + // Depending on stored charge percentage cause damage. + switch(Percentage()) + if(75 to INFINITY) + explosion(get_turf(src), 1, 2, 4) + if(40 to 74) + explosion(get_turf(src), 0, 2, 3) + if(5 to 39) + explosion(get_turf(src), 0, 1, 2) + qdel(src) // Either way we want to ensure the SMES is deleted. + /obj/machinery/power/smes/emp_act(severity) inputting(rand(0,1)) outputting(rand(0,1)) @@ -423,13 +449,27 @@ update_icon() ..() -/obj/machinery/power/smes/magical - name = "magical power storage unit" - desc = "A high-capacity superconducting magnetic energy storage (SMES) unit. Magically produces power." - capacity = 9000000 - output_level = 250000 - should_be_mapped = 1 +/obj/machinery/power/smes/bullet_act(var/obj/item/projectile/Proj) + if(Proj.damage_type == BRUTE || Proj.damage_type == BURN) + take_damage(Proj.damage) -/obj/machinery/power/smes/magical/process() - charge = 5000000 +/obj/machinery/power/smes/ex_act(var/severity) + // Two strong explosions will destroy a SMES. + // Given the SMES creates another explosion on it's destruction it sounds fairly reasonable. + take_damage(250 / severity) + +/obj/machinery/power/smes/examine(var/mob/user) ..() + to_chat(user, "The service hatch is [panel_open ? "open" : "closed"].") + if(!damage) + return + var/damage_percentage = round((damage / maxdamage) * 100) + switch(damage_percentage) + if(75 to INFINITY) + to_chat(user, "It's casing is severely damaged, and sparking circuitry may be seen through the holes!") + if(50 to 74) + to_chat(user, "It's casing is considerably damaged, and some of the internal circuits appear to be exposed!") + if(25 to 49) + to_chat(user, "It's casing is quite seriously damaged.") + if(0 to 24) + to_chat(user, "It's casing has some minor damage.") \ No newline at end of file diff --git a/code/modules/power/smes_construction.dm b/code/modules/power/smes_construction.dm index 6a9e6bfaff8..aadcc41ec4a 100644 --- a/code/modules/power/smes_construction.dm +++ b/code/modules/power/smes_construction.dm @@ -113,7 +113,7 @@ to_chat(usr, "Connection error: Destination Unreachable.") // Cyborgs standing next to the SMES can play with the wiring. - if(istype(usr, /mob/living/silicon/robot) && Adjacent(usr) && open_hatch) + if(istype(usr, /mob/living/silicon/robot) && Adjacent(usr) && panel_open) wires.Interact(usr) // Proc: New() @@ -136,7 +136,7 @@ // Description: Opens the UI as usual, and if cover is removed opens the wiring panel. /obj/machinery/power/smes/buildable/attack_hand() ..() - if(open_hatch) + if(panel_open) wires.Interact(usr) // Proc: recalc_coils() @@ -329,7 +329,7 @@ // Crowbar - Disassemble the SMES. if(W.is_crowbar()) - if (terminal) + if (terminals.len) to_chat(user, "You have to disassemble the terminal first!") return diff --git a/code/modules/power/terminal.dm b/code/modules/power/terminal.dm index 1ce14059d4c..7846ff6ff16 100644 --- a/code/modules/power/terminal.dm +++ b/code/modules/power/terminal.dm @@ -22,7 +22,7 @@ /obj/machinery/power/terminal/Destroy() if(master) - master.disconnect_terminal() + master.disconnect_terminal(src) master = null return ..() diff --git a/html/changelogs/atlantiscze-power.yml b/html/changelogs/atlantiscze-power.yml new file mode 100644 index 00000000000..31e354cc556 --- /dev/null +++ b/html/changelogs/atlantiscze-power.yml @@ -0,0 +1,10 @@ +author: atlantiscze +delete-after: True + +changes: + - rscadd: "SMES units now have automatic load balancing both on inputs and outputs" + - rscadd: "SMES units (and derived objects, such as PSUs) can now have more than one input terminal. This allows for input from more otherwise isolated power networks." + - tweak: "Completely redesigned cell rack PSUs, removed ghetto variant. Cell rack PSUs now support multiple power cells and hot-swapping of cells during operation. This allows for either charging multiple cells at once in one device (directly from power cable) or for powering up various machinery with only a power cell. They are still inferior to SMESes in pretty much all aspects." + - tweak: "Multitool on a cable now shows nicer results with large currents (uses kW or MW accordingly)" + - tweak: "Multitool may be now used to change colour of cable coils." + \ No newline at end of file diff --git a/icons/obj/cellrack.dmi b/icons/obj/cellrack.dmi new file mode 100644 index 0000000000000000000000000000000000000000..41347de3f72ea876f41d73e68c7add453f25fd9b GIT binary patch literal 819 zcmV-31I+x1P)1AqyPW_I5;?PaBwOrDpXWd|Nj9000000{{a60z`(%Z49txH z0004WQchCV=-0C=2@)Vm78KoCIDI{Ot%Y(?^Z z6gCo(6y_fmv%0~kunCHPZ`^=|5V+k8oN1=GE4kYr>PA+_T0FHKy2o8^WwHr`_v+fw zOiLUI&*<)A#(5b=HR@^ftvX$O;kBI|rZp7a*{YkK=N~%hz7LU#kcyE?kV=utkjjxN zkSf_%HG3;#F=Gj1DPtL9Ib#K5C1c^4y_d0=v4pXdv5c{tv4XJ@Fr(~62hc6lW2#cC zd-eW~>*)Uo#Kbv}6X!rsoC8U54n)Pd#jLF4zNuWcKii3z3${vU+5i9n!AV3xRCt{2 z-Rq8mFc5}eS`I4;a{qVh6n9sRr6r+I7~bcHEGet~oVE#v#G*I}W2`mi_x0`1-2F<# zn6j+I^R@chk%+l_mc(Sdx>gZT#-dkUH;s;fruHzlX|x0^eJTm4WDfq%!1qm6Sv9NZ zR0SoTmLWPWGZN}Yly3Qy;>i$|X{aQiy|nG+na?G3dsYHkH5Ihi+wJ-a==Q9t3N{|9 zD%g(dkBzDdwr*%jviHqL1zkyJyi?l4l9>$ns$ki5<&1Yqdss4OsG#~e=ntr96^+fo zwXHh^X3W90t-FYwp@Qn?;M6Lr<%HKiL=~Oz`UA@Ou9_k0Uu5)LbefohdII`R2VFoN zQ*gYn{dLKyGFC~vq8RVAc;xOE000000K3=s84d%0R9JR!aL0ZQW`<)w$uxud3IYHC z00000U+(vmhXKfihX4TV6HhV$000000O0Jn%?4m&X&(Y$5d*L>7BK(-0079z{SHIU z_Ab`mb;0&Q#6Cat2}|4iKHTp5zogye3w}fK4)zNJhW|f7z{n5)0000000000Kti$m xYq9pd8-{R#So`k9ZXg~$AOHXW0002C@c@1_8q_X}&G-NS002ovPDHLkV1gV|bn5^B literal 0 HcmV?d00001 diff --git a/maps/example/example-2.dmm b/maps/example/example-2.dmm index 79a40529e04..d911c24d252 100644 --- a/maps/example/example-2.dmm +++ b/maps/example/example-2.dmm @@ -24,7 +24,7 @@ "x" = (/obj/machinery/alarm{dir = 4; icon_state = "alarm0"; pixel_x = -22},/obj/machinery/light{icon_state = "tube1"; dir = 8},/obj/machinery/disposal,/obj/structure/disposalpipe/trunk{ icon_state = "pipe-t"; dir = 1},/turf/simulated/floor/tiled/dark,/area/aisat_interior) "y" = (/obj/machinery/light{icon_state = "tube1"; dir = 4},/turf/simulated/floor/tiled/dark,/area/aisat_interior) "z" = (/obj/machinery/light_switch,/turf/simulated/wall,/area/aisat_interior) -"A" = (/obj/machinery/power/fractal_reactor,/obj/structure/cable/yellow{d2 = 4; icon_state = "0-4"},/obj/machinery/light{icon_state = "tube1"; dir = 8},/turf/simulated/floor/tiled/dark,/area/aisat_interior) +"A" = (/obj/machinery/power/debug_items/infinite_generator,/obj/structure/cable/yellow{d2 = 4; icon_state = "0-4"},/obj/machinery/light{icon_state = "tube1"; dir = 8},/turf/simulated/floor/tiled/dark,/area/aisat_interior) "B" = (/obj/structure/cable/yellow{d1 = 4; d2 = 8; icon_state = "4-8"},/turf/simulated/floor/tiled/dark,/area/aisat_interior) "C" = (/obj/machinery/power/apc{dir = 4; name = "east bump"; pixel_x = 24},/obj/structure/cable/yellow{d2 = 8; icon_state = "0-8"},/obj/structure/cable/yellow{d1 = 2; d2 = 8; icon_state = "2-8"},/obj/machinery/light{icon_state = "tube1"; dir = 4},/turf/simulated/floor/tiled/dark,/area/aisat_interior) "D" = (/obj/structure/cable/yellow{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/effect/floor_decal/corner/lime/diagonal,/turf/simulated/floor/tiled/dark,/area/aisat_interior) diff --git a/nano/templates/psu.tmpl b/nano/templates/psu.tmpl new file mode 100644 index 00000000000..397c3cfa84e --- /dev/null +++ b/nano/templates/psu.tmpl @@ -0,0 +1,58 @@ +
+
+ Current mode: +
+
+ {{if data.mode == 1}} + OUTPUT ONLY + {{else data.mode == 2}} + INPUT ONLY + {{else data.mode == 3}} + INPUT AND OUTPUT + {{else}} + OFFLINE + {{/if}} +
+
+ Input status: +
+
+ {{:data.input_load}} / {{:data.transfer_max}} W +
+
+ Output status: +
+
+ {{:data.output_load}} / {{:data.transfer_max}} W +
+
+ Control panel: +
+
+ {{:helper.link('OFF', null, { "disable" : 1 }, (data.mode == 0) ? 'disabled' : null)}} + {{:helper.link('OUT', null, { "enable" : 1 }, (data.mode == 1) ? 'disabled' : null)}} + {{:helper.link('IN', null, { "enable" : 2 }, (data.mode == 2) ? 'disabled' : null)}} + {{:helper.link('IN/OUT', null, { "enable" : 3 }, (data.mode == 3) ? 'disabled' : null)}} + {{if data.equalise}} + {{:helper.link('EQ', null, { "equaliseoff" : 1 }, null, data.blink_tick ? 'yellowButton' : 'redButton')}} + {{else}} + {{:helper.link('EQ', null, { "equaliseon" : 1 })}} + {{/if}} +
+
+
+ + {{for data.cells_list}} + +
Cell {{:value.slot}} + {{if value.used}} + {{:helper.displayBar(value.percentage, 0, 100, 'good')}} + {{:value.percentage}} % + {{:helper.link('E', null, { "ejectcell" : value.id })}} + {{else}} + {{:helper.displayBar(100, 0, 100, 'bad')}} + N/C + {{:helper.link('E', null, {}, 'disabled')}} + {{/if}} + {{/for}} +
\ No newline at end of file diff --git a/vorestation.dme b/vorestation.dme index d50e6f16c1c..739d40c1d4b 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -2983,7 +2983,7 @@ #include "code\modules\power\cable_ender.dm" #include "code\modules\power\cable_heavyduty.dm" #include "code\modules\power\cell.dm" -#include "code\modules\power\fractal_reactor.dm" +#include "code\modules\power\debug_items.dm" #include "code\modules\power\generator.dm" #include "code\modules\power\generator_type2.dm" #include "code\modules\power\gravitygenerator_vr.dm"