Ports over power-related enhancements and tweaks i coded for Bay a while back

- Please fasten your seatbelts, this will be a long one as it joins few older Bay PRs into one.

1. Ports https://github.com/Baystation12/Baystation12/pull/12626
- SMES units may now be damaged and destroyed. Charged SMES units fail quite violently. Damage can be fixed by welding tool.
- PSUs completely refactored, ghetto variant no longer exists.
- Cell rack PSUs now can be considered a hybrid between large battery charger and a SMES. They actually use the cells to store power (so you can hot-swap the cells to get more juice simply via the UI without deconstructing the whole machine), but in comparison to SMES have poor throughput and capacity in general (cells are simply too small). They are also somewhat limited in configuration options (lacks the precision electronics of a SMES). Better matter bin lets you put in more cells, up to 9.
- Cell rack PSU also has a mode that allows charge-balancing all inserted batteries (moves energy around so each battery has the same charge %)

2. Ports https://github.com/Baystation12/Baystation12/pull/11977
- SMES units now have full load balancing capability, getting rid of that annoying "One SMES charges at full, other SMES gets nothing" problem. If insufficient power is available on input, all inputting SMESes will now charge at same percentage. If more SMESes power a single output, they will all output equal percentage of their setting.
- This appears to have a pleasant side effect of fixing the issue where SMESes could starve APCs of energy. SMESes are ALWAYS last to input power on a powernet.
- This also appears to have fixed weird values displayed in SMES output/input load readings in the UI. By weird values i mostly mean inputs/outputs actually higher than the SMES is set to have.

3. Ports https://github.com/Baystation12/Baystation12/pull/18137
- SMES units (and subtypes, therefore effectively also PSUs from previous entry) can now have more than one terminal. This effectively allows a setup where two (or more) sources feed a single SMES, which then feeds the output. SMESes can not exceed input setting even with multiple terminals.
- Typical example of use in practice would be SMES that runs something important (for example an AI, telecomms, pick whatever you want). It could have one input from the power grid, and second input from a PACMAN generator set up nearby as a backup. Before the generator would have to be wired into main grid, therefore main grid would siphon off power from it. Now the generator can be separate and dedicated for whatever use you want.
This commit is contained in:
Atlantiscze
2020-04-17 08:26:29 +02:00
parent 08d463a797
commit 78925e2993
16 changed files with 576 additions and 372 deletions

View File

@@ -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

View File

@@ -1,234 +1,311 @@
//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
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, "<span class='warning'>Turn off the [src] before dismantling it.</span>")
else
to_chat(user, "<span class='warning'>Better let [src] discharge before dismantling it.</span>")
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, "<span class='notice'>You upgrade the [src] with [W.name].</span>")
else
to_chat(user, "<span class='warning'>Turn off the [src] before dismantling it.</span>")
else
to_chat(user, "<span class='warning'>Better let [src] discharge before putting your hand inside it.</span>")
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))
user << "You insert \the [W] into \the [src]."
else
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

View File

@@ -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, "<span class='warning'>[powernet.avail]W in power network.</span>")
to_chat(user, "<span class='warning'>[get_wattage()] in power network.</span>")
else
to_chat(user, "<span class='warning'>The cable is not powered.</span>")
return
@@ -154,6 +154,13 @@ var/list/possible_cable_coil_colours = list(
// General procedures
///////////////////////////////////
/obj/structure/cable/proc/get_wattage()
if(powernet.avail >= 1000000000)
return "[round(powernet.avail/1000000, 0.01)] MW"
if(powernet.avail >= 1000000)
return "[round(powernet.avail/1000, 0.01)] kW"
return "[round(powernet.avail)] W"
//If underfloor, hide the cable
/obj/structure/cable/hide(var/i)
if(istype(loc, /turf))
@@ -230,7 +237,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, "<span class='warning'>[powernet.avail]W in power network.</span>")
to_chat(user, "<span class='warning'>[get_wattage()]W in power network.</span>")
else
to_chat(user, "<span class='warning'>The cable is not powered.</span>")

View File

@@ -1,6 +1,7 @@
// the power cell
// charge from 0 to 100%
// fits in APC to provide backup power
var/cell_uid = 1 // Unique ID of this power cell. Used to reduce bunch of uglier code in nanoUI.
/obj/item/weapon/cell
name = "power cell"
@@ -14,6 +15,7 @@
throw_speed = 3
throw_range = 5
w_class = ITEMSIZE_NORMAL
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)

View File

@@ -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 1
return 0
/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
// returns true if the area has power on given channel (or doesn't require power).

View File

@@ -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,18 @@
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/smes/S in inputting)
S.input_power(smes_input_percentage)
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 +137,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, (avail / load) * 100, 100)
/datum/powernet/proc/get_electrocute_damage()
switch(avail)

View File

@@ -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,34 @@
/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(!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
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 +116,69 @@
/obj/machinery/power/smes/proc/chargedisplay()
return round(5.5*charge/(capacity ? capacity : 5e6))
/obj/machinery/power/smes/proc/input_power(var/percentage)
var/to_input = target_load * (percentage/100)
to_input = between(0, to_input, target_load)
input_available = 0
if(percentage == 100)
inputting = 2
else if(percentage)
inputting = 1
// else inputting = 0, as set in process()
for(var/obj/machinery/power/terminal/term in terminals)
var/inputted = term.powernet.draw_power(to_input)
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(src)
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 +186,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()
@@ -202,20 +223,36 @@
if(!tempLoc.is_plating())
to_chat(user, "<span class='warning'>You must remove the floor plating first.</span>")
return 1
if(check_terminal_exists(tempLoc, user, tempDir))
return 1
to_chat(user, "<span class='notice'>You start adding cable to the [src].</span>")
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, "<span class='notice'>There is already a terminal here.</span>")
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,23 +265,26 @@
/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, "<span class='notice'>You open the maintenance hatch of [src].</span>")
playsound(src, W.usesound, 50, 1)
return 0
else
open_hatch = 0
to_chat(user, "<span class='notice'>You close the maintenance hatch of [src].</span>")
playsound(src, W.usesound, 50, 1)
return 0
if(default_deconstruction_screwdriver(user, W))
return
if (!open_hatch)
if (!panel_open)
to_chat(user, "<span class='warning'>You need to open access hatch on [src] first!</span>")
return 0
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 0
if(!damage)
to_chat(user, "\The [src] is already fully repaired.")
return 0
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 0
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)
@@ -262,9 +302,17 @@
stat = 0
return 0
else if(W.is_wirecutter() && terminal && !building_terminal)
else if(W.is_wirecutter() && !building_terminal)
building_terminal = 1
var/turf/tempTDir = terminal.loc
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, "<span class='warning'>There is no terminal on this tile.</span>")
return 0
var/turf/tempTDir = get_turf(term)
if (istype(tempTDir))
if(!tempTDir.is_plating())
to_chat(user, "<span class='warning'>You must remove the floor plating first.</span>")
@@ -272,7 +320,7 @@
to_chat(user, "<span class='notice'>You begin to cut the cables...</span>")
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()
@@ -283,7 +331,8 @@
user.visible_message(\
"<span class='notice'>[user.name] cut the cables and dismantled the power terminal.</span>",\
"<span class='notice'>You cut the cables and dismantle the power terminal.</span>")
qdel(terminal)
terminals -= term
qdel(term)
building_terminal = 0
return 0
return 1
@@ -303,21 +352,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 +373,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 +384,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 +398,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,43 +408,11 @@
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
return 1
investigate_log("input/output; <font color='[input_level>output_level?"green":"red"][input_level]/[output_level]</font> | Output-mode: [output_attempt?"<font color='green'>on</font>":"<font color='red'>off</font>"] | Input-mode: [input_attempt?"<font color='green'>auto</font>":"<font color='red'>off</font>"] 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("<font color='red'>The [src.name] is making strange noises!</font>", 3, "<font color='red'>You hear sizzling electronics.</font>", 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()
/obj/machinery/power/smes/proc/inputting(var/do_input)
input_attempt = do_input
if(!input_attempt)
@@ -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("<span class='danger'>\The [src] explodes in large rain of sparks and smoke!</span>")
// 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)
..()
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, "<span class='danger'>It's casing is severely damaged, and sparking circuitry may be seen through the holes!</span>")
if(50 to 74)
to_chat(user, "<span class='notice'>It's casing is considerably damaged, and some of the internal circuits appear to be exposed!</span>")
if(25 to 49)
to_chat(user, "<span class='notice'>It's casing is quite seriously damaged.</span>")
if(0 to 24)
to_chat(user, "It's casing has some minor damage.")

View File

@@ -84,6 +84,9 @@
/obj/machinery/power/smes/buildable/Destroy()
qdel(wires)
wires = null
for(var/obj/machinery/power/terminal/T in terminals)
T.master = null
terminals = null
for(var/datum/nano_module/rcon/R in world)
R.FindDevices()
return ..()
@@ -113,7 +116,7 @@
to_chat(usr, "<span class='warning'>Connection error: Destination Unreachable.</span>")
// 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 +139,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 +332,7 @@
// Crowbar - Disassemble the SMES.
if(W.is_crowbar())
if (terminal)
if (terminals.len)
to_chat(user, "<span class='warning'>You have to disassemble the terminal first!</span>")
return

View File

@@ -22,7 +22,7 @@
/obj/machinery/power/terminal/Destroy()
if(master)
master.disconnect_terminal()
master.disconnect_terminal(src)
master = null
return ..()