diff --git a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
index 0166047a33a..7f0e8665f7c 100644
--- a/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
+++ b/_maps/map_files/RandomRuins/SpaceRuins/syndie_space_base.dmm
@@ -1213,7 +1213,7 @@
},
/area/ruin/unpowered/syndicate_space_base/telecomms)
"mP" = (
-/obj/machinery/power/generator,
+/obj/machinery/power/teg,
/obj/structure/cable/yellow,
/turf/simulated/floor/plating,
/area/ruin/unpowered/syndicate_space_base/engineering)
diff --git a/code/__DEFINES/directions.dm b/code/__DEFINES/directions.dm
new file mode 100644
index 00000000000..086082c8e31
--- /dev/null
+++ b/code/__DEFINES/directions.dm
@@ -0,0 +1,30 @@
+//Directions (already defined on BYOND natively, purely here for reference)
+/// define purely for readability, cables especially need to use this as `NO_DIRECTION` represents a "node"
+#define NO_DIRECTION 0
+//#define NORTH 1
+//#define SOUTH 2
+//#define EAST 4
+//#define WEST 8
+//#define NORTHEAST 5
+//#define SOUTHEAST 6
+//#define NORTHWEST 9
+//#define SOUTHWEST 10
+
+/// Using the ^ operator or XOR, we can compared TRUE East and West bits against our direction,
+/// since XOR will only return TRUE if one bit is False and the other is True, if East is 0, that bit will return TRUE
+/// and if West is 1, then that bit will return 0
+/// hence EAST (0010) XOR EAST|WEST (0011) --> WEST (0001)
+
+///Flips a direction along the horizontal axis, will convert E -> W, W -> E, NE -> NW, SE -> SW, etc
+#define FLIP_DIR_HORIZONTALLY(dir) (dir ^ (EAST|WEST))
+///Flips a direction along the vertical axis, will convert N -> S, S -> N, NE -> SE, SW -> NW, etc
+#define FLIP_DIR_VERTICALLY(dir) (dir ^ (NORTH|SOUTH))
+
+/// for directions, each cardinal direction only has 1 TRUE bit, so `1000` or `0100` for example, so when you subtract 1
+/// from a cardinal direction it results in that directions initial TRUE bit always switching to FALSE, so if you & check it
+/// against its initial self, it will return false, indicating that the direction is straight and not diagonal
+
+/// returns TRUE if direction is diagonal and false if not
+#define IS_DIR_DIAGONAL(dir) (dir & (dir - 1))
+/// returns TRUE if direction is cardinal and false if not
+#define IS_DIR_CARDINAL(dir) (!IS_DIR_DIAGONAL(dir))
diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm
index 932dcead2f7..45402cba078 100644
--- a/code/__DEFINES/misc_defines.dm
+++ b/code/__DEFINES/misc_defines.dm
@@ -1,16 +1,6 @@
//Object specific defines
#define CANDLE_LUM 3 //For how bright candles are
-//Directions (already defined on BYOND natively, purely here for reference)
-//#define NORTH 1
-//#define SOUTH 2
-//#define EAST 4
-//#define WEST 8
-//#define NORTHEAST 5
-//#define SOUTHEAST 6
-//#define NORTHWEST 9
-//#define SOUTHWEST 10
-
//Security levels
#define SEC_LEVEL_GREEN 0
#define SEC_LEVEL_BLUE 1
diff --git a/code/_onclick/click_override.dm b/code/_onclick/click_override.dm
index 12b51dbe910..e1da5a15073 100644
--- a/code/_onclick/click_override.dm
+++ b/code/_onclick/click_override.dm
@@ -73,7 +73,7 @@
beam_from.Beam(target_atom, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 6)
if(isliving(target_atom))
var/mob/living/L = target_atom
- var/surplus_power = C.surplus()
+ var/surplus_power = C.get_surplus()
if(user.a_intent == INTENT_DISARM)
add_attack_logs(user, L, "shocked with power gloves.")
L.adjustStaminaLoss(60)
diff --git a/code/controllers/subsystem/machinery.dm b/code/controllers/subsystem/machinery.dm
index 20cf2036819..ace20099859 100644
--- a/code/controllers/subsystem/machinery.dm
+++ b/code/controllers/subsystem/machinery.dm
@@ -11,6 +11,7 @@ SUBSYSTEM_DEF(machines)
var/list/processing = list()
var/list/currentrun = list()
+ /// All regional powernets (/datum/regional_powernet) in the world
var/list/powernets = list()
var/list/deferred_powernet_rebuilds = list()
@@ -27,15 +28,15 @@ SUBSYSTEM_DEF(machines)
.["custom"] = cust
/datum/controller/subsystem/machines/proc/makepowernets()
- for(var/datum/powernet/PN in powernets)
+ for(var/datum/regional_powernet/PN in powernets)
qdel(PN)
powernets.Cut()
for(var/obj/structure/cable/PC in GLOB.cable_list)
if(!PC.powernet)
- var/datum/powernet/NewPN = new()
- NewPN.add_cable(PC)
- propagate_network(PC,PC.powernet)
+ var/datum/regional_powernet/new_pn = new()
+ new_pn.add_cable(PC)
+ propagate_network(PC, PC.powernet)
/datum/controller/subsystem/machines/get_stat_details()
return "Machines: [processing.len] | Powernets: [powernets.len] | Deferred: [deferred_powernet_rebuilds.len]"
@@ -49,7 +50,7 @@ SUBSYSTEM_DEF(machines)
var/obj/O = currentrun[currentrun.len]
currentrun.len--
if(O && !QDELETED(O))
- var/datum/powernet/newPN = new() // create a new powernet...
+ var/datum/regional_powernet/newPN = new() // create a new powernet...
propagate_network(O, newPN)//... and propagate it to the other side of the cable
deferred_powernet_rebuilds.Remove(O)
@@ -62,10 +63,10 @@ SUBSYSTEM_DEF(machines)
//cache for sanid speed (lists are references anyways)
var/list/currentrun = src.currentrun
while(currentrun.len)
- var/datum/powernet/P = currentrun[currentrun.len]
+ var/datum/regional_powernet/P = currentrun[currentrun.len]
currentrun.len--
if(P)
- P.reset() // reset the power state
+ P.process_power() // reset the power state
else
powernets.Remove(P)
if(MC_TICK_CHECK)
@@ -117,7 +118,7 @@ SUBSYSTEM_DEF(machines)
for(var/A in cables)
var/obj/structure/cable/PC = A
if(!PC.powernet)
- var/datum/powernet/NewPN = new()
+ var/datum/regional_powernet/NewPN = new()
NewPN.add_cable(PC)
propagate_network(PC,PC.powernet)
diff --git a/code/datums/cache/apc_cache.dm b/code/datums/cache/apc_cache.dm
index fe9f1eab21e..4162754e74b 100644
--- a/code/datums/cache/apc_cache.dm
+++ b/code/datums/cache/apc_cache.dm
@@ -1,6 +1,6 @@
GLOBAL_DATUM_INIT(apc_repository, /datum/repository/apc, new())
-/datum/repository/apc/proc/apc_data(datum/powernet/powernet)
+/datum/repository/apc/proc/apc_data(datum/regional_powernet/powernet)
var/apcData[0]
var/datum/cache_entry/cache_entry = cache_data
diff --git a/code/game/machinery/computer/power_monitor.dm b/code/game/machinery/computer/power_monitor.dm
index c711d2dc1b6..a348027cef6 100644
--- a/code/game/machinery/computer/power_monitor.dm
+++ b/code/game/machinery/computer/power_monitor.dm
@@ -8,7 +8,10 @@
active_power_consumption = 80
light_color = LIGHT_COLOR_ORANGE
circuit = /obj/item/circuitboard/powermonitor
- var/datum/powernet/powernet = null
+
+ /// The regional powernet this power monitor is hooked into
+ var/datum/regional_powernet/powernet = null
+ /// TGUI module this power monitor uses to produce a UI for the user
var/datum/ui_module/power_monitor/power_monitor
/// Will this monitor be hidden from viewers?
var/is_secret_monitor = FALSE
@@ -89,11 +92,11 @@
return
var/list/supply = history["supply"]
- supply += powernet.viewavail
+ supply += powernet.smoothed_available_power
if(length(supply) > record_size)
supply.Cut(1, 2)
var/list/demand = history["demand"]
- demand += powernet.viewload
+ demand += powernet.smoothed_demand
if(length(demand) > record_size)
demand.Cut(1, 2)
diff --git a/code/game/machinery/shieldgen.dm b/code/game/machinery/shieldgen.dm
index a0d29e3646a..dfbb24ab131 100644
--- a/code/game/machinery/shieldgen.dm
+++ b/code/game/machinery/shieldgen.dm
@@ -328,20 +328,20 @@
var/turf/T = loc
var/obj/structure/cable/C = T.get_cable_node()
- var/datum/powernet/PN = C?.powernet // find the powernet of the connected cable
+ var/datum/regional_powernet/PN = C?.powernet // find the powernet of the connected cable
if(!PN)
deactivate()
return FALSE
- var/surplus = max(PN.avail - PN.load, 0)
+ var/surplus = max(PN.available_power - PN.power_demand, 0)
var/shieldload = min(rand(50, 200), surplus)
if(!shieldload && stored_power <= 0) // no cable or no power, and no power stored
deactivate()
return FALSE
stored_power += min(shieldload, MAX_STORED_POWER - stored_power)
- PN.load += shieldload //uses powernet power.
+ PN.power_demand += shieldload //uses powernet power.
return TRUE
/obj/machinery/shieldwallgen/attack_hand(mob/user)
diff --git a/code/game/machinery/syndicatebeacon.dm b/code/game/machinery/syndicatebeacon.dm
index 64efac80b84..6c87c92559d 100644
--- a/code/game/machinery/syndicatebeacon.dm
+++ b/code/game/machinery/syndicatebeacon.dm
@@ -17,7 +17,7 @@
/obj/machinery/power/singularity_beacon/proc/Activate(mob/user = null)
- if(surplus() < 1500)
+ if(get_surplus() < 1500)
if(user)
to_chat(user, "The connected wire doesn't have enough current.")
return
@@ -42,11 +42,9 @@
if(user)
to_chat(user, "You deactivate the beacon.")
-
/obj/machinery/power/singularity_beacon/attack_ai(mob/user as mob)
return
-
/obj/machinery/power/singularity_beacon/attack_hand(mob/user as mob)
if(anchored)
return active ? Deactivate(user) : Activate(user)
@@ -54,7 +52,6 @@
to_chat(user, "You need to screw the beacon to the floor first!")
return
-
/obj/machinery/power/singularity_beacon/screwdriver_act(mob/user, obj/item/I)
. = TRUE
if(active)
@@ -84,8 +81,8 @@
if(!active)
return PROCESS_KILL
- if(surplus() >= 1500)
- add_load(1500)
+ if(get_surplus() >= 1500)
+ consume_direct_power(1500)
else
Deactivate()
diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm
index 7e2c2ddad5e..101891666c2 100644
--- a/code/game/mecha/equipment/tools/work_tools.dm
+++ b/code/game/mecha/equipment/tools/work_tools.dm
@@ -434,11 +434,11 @@
return reset()
var/obj/structure/cable/NC = new(new_turf)
NC.cable_color("red")
- NC.d1 = 0
+ NC.d1 = NO_DIRECTION
NC.d2 = fdirn
NC.update_icon()
- var/datum/powernet/PN
+ var/datum/regional_powernet/PN
if(last_piece && last_piece.d2 != Dir)
last_piece.d1 = min(last_piece.d2, Dir)
last_piece.d2 = max(last_piece.d2, Dir)
@@ -449,7 +449,7 @@
PN = new()
NC.powernet = PN
PN.cables += NC
- NC.mergeConnectedNetworks(NC.d2)
+ NC.merge_connected_networks(NC.d2)
//NC.mergeConnectedNetworksOnTurf()
last_piece = NC
diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm
index 5e94b9e02fe..06d1a2783b9 100644
--- a/code/game/objects/items/devices/powersink.dm
+++ b/code/game/objects/items/devices/powersink.dm
@@ -113,14 +113,14 @@
set_mode(DISCONNECTED)
return
- var/datum/powernet/PN = attached.powernet
+ var/datum/regional_powernet/PN = attached.powernet
if(PN)
set_light(5)
// found a powernet, so drain up to max power from it
- var/drained = min (drain_rate, attached.newavail())
- attached.add_delayedload(drained)
+ var/drained = min(drain_rate, attached.get_queued_surplus())
+ attached.add_queued_power_demand(drained)
power_drained += drained
// if tried to drain more than available on powernet
diff --git a/code/game/objects/structures/grille.dm b/code/game/objects/structures/grille.dm
index 2f5c5e17c11..14990b6d649 100644
--- a/code/game/objects/structures/grille.dm
+++ b/code/game/objects/structures/grille.dm
@@ -267,8 +267,8 @@
var/obj/structure/cable/C = T.get_cable_node()
if(C)
playsound(src, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
- tesla_zap(src, 3, C.newavail() * 0.01, ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_MOB_STUN | ZAP_ALLOW_DUPLICATES) //Zap for 1/100 of the amount of power. At a million watts in the grid, it will be as powerful as a tesla revolver shot.
- C.add_delayedload(C.newavail() * 0.0375) // you can gain up to 3.5 via the 4x upgrades power is halved by the pole so thats 2x then 1X then .5X for 3.5x the 3 bounces shock.
+ tesla_zap(src, 3, C.get_queued_available_power() * 0.01, ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_MOB_STUN | ZAP_ALLOW_DUPLICATES) //Zap for 1/100 of the amount of power. At a million watts in the grid, it will be as powerful as a tesla revolver shot.
+ C.add_queued_power_demand(C.get_queued_available_power() * 0.0375) // you can gain up to 3.5 via the 4x upgrades power is halved by the pole so thats 2x then 1X then .5X for 3.5x the 3 bounces shock.
return ..()
/obj/structure/grille/broken // Pre-broken grilles for map placement
diff --git a/code/game/turfs/simulated/floor/transparent.dm b/code/game/turfs/simulated/floor/transparent.dm
index 2c8cec1ed22..56ff787acf3 100644
--- a/code/game/turfs/simulated/floor/transparent.dm
+++ b/code/game/turfs/simulated/floor/transparent.dm
@@ -86,6 +86,9 @@
ChangeTurf(/turf/simulated/floor/plating)
+/turf/simulated/floor/transparent/glass/can_lay_cable()
+ return FALSE // this turf isn't "intact" but you also can't lay cable on it
+
/turf/simulated/floor/transparent/glass/reinforced
name = "reinforced glass floor"
desc = "Jump on it, it can cope. Promise..."
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 164b68afd36..09ca5d6419b 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -6,7 +6,8 @@
var/intact = TRUE
var/turf/baseturf = /turf/space
var/slowdown = 0 //negative for faster, positive for slower
- var/transparent_floor = FALSE //used to check if pipes should be visible under the turf or not
+ /// used to check if pipes should be visible under the turf or not
+ var/transparent_floor = FALSE
/// Set if the turf should appear on a different layer while in-game and map editing, otherwise use normal layer.
var/real_layer = TURF_LAYER
@@ -468,7 +469,31 @@
return TRUE
/turf/proc/can_lay_cable()
- return can_have_cabling() & !intact
+ return can_have_cabling() && !intact
+
+/*
+ * # power_list()
+ * returns a list power machinery on the turf and cables on the turf that have a direction equal to the one supplied in params and are currently connected to a powernet
+ *
+ * Arguments:
+ * source - the atom that is calling this proc
+ * direction - the direction that a cable must have in order to be returned in this proc i.e. d1 or d2 must equal direction
+ * cable_only - if TRUE, power_list will only return cables, if FALSE it will also return power machinery
+*/
+/turf/proc/power_list(atom/source, direction, cable_only = FALSE)
+ . = list()
+ for(var/obj/AM in src)
+ if(AM == source)
+ continue //we don't want to return source
+ if(istype(AM, /obj/structure/cable))
+
+ var/obj/structure/cable/C = AM
+ if(C.d1 == direction || C.d2 == direction)
+ . += C // one of the cables ends matches the supplied direction, add it to connnections
+ if(cable_only || direction)
+ continue
+ if(istype(AM, /obj/machinery/power) && !istype(AM, /obj/machinery/power/apc))
+ . += AM
/turf/proc/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
underlay_appearance.icon = icon
diff --git a/code/modules/admin/verbs/atmosdebug.dm b/code/modules/admin/verbs/atmosdebug.dm
index 3595fb9dfc5..96661c16e5d 100644
--- a/code/modules/admin/verbs/atmosdebug.dm
+++ b/code/modules/admin/verbs/atmosdebug.dm
@@ -46,7 +46,7 @@
return
SSblackbox.record_feedback("tally", "admin_verb", 1, "Check Power") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
- for(var/datum/powernet/PN in SSmachines.powernets)
+ for(var/datum/regional_powernet/PN in SSmachines.powernets)
if(!PN.nodes || !PN.nodes.len)
if(PN.cables && (PN.cables.len > 1))
var/obj/structure/cable/C = PN.cables[1]
diff --git a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
index 53c0fb052ea..24e843b62c5 100644
--- a/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
+++ b/code/modules/atmospherics/machinery/components/binary_devices/circulator.dm
@@ -3,17 +3,16 @@
desc = "A gas circulator pump and heat exchanger. Its input port is on the south side, and its output port is on the north side."
icon = 'icons/obj/atmospherics/circulator.dmi'
icon_state = "circ1-off"
+ anchored = TRUE
+ density = TRUE
+ can_unwrench = TRUE
- var/side = CIRC_LEFT
+ /// The Thermo-Electric Generator this circulator is connected to
+ var/obj/machinery/power/teg/generator
var/last_pressure_delta = 0
- var/obj/machinery/power/generator/generator
-
- anchored = TRUE
- density = TRUE
-
- can_unwrench = TRUE
+ var/side = CIRC_LEFT
var/side_inverted = FALSE
var/light_range_on = 1
@@ -33,10 +32,10 @@
. = ..(make_from = new /obj/machinery/atmospherics/binary/circulator(null))
/obj/machinery/atmospherics/binary/circulator/Destroy()
- if(generator && generator.cold_circ == src)
+ if(generator?.cold_circ == src)
generator.cold_circ = null
- else if(generator && generator.hot_circ == src)
+ else if(generator?.hot_circ == src)
generator.hot_circ = null
return ..()
@@ -51,12 +50,11 @@
//Need at least 10 KPa difference to overcome friction in the mechanism
last_pressure_delta = 0
update_icon()
- return null
+ return
//Calculate necessary moles to transfer using PV = nRT
if(inlet.temperature > 0)
var/pressure_delta = (input_starting_pressure - output_starting_pressure) / 2
-
var/transfer_moles = pressure_delta * outlet.volume/(inlet.temperature * R_IDEAL_GAS_EQUATION)
if(last_pressure_delta != pressure_delta)
@@ -68,40 +66,27 @@
//Actually transfer the gas
var/datum/gas_mixture/removed = inlet.remove(transfer_moles)
- parent1.update = 1
- parent2.update = 1
+ parent1.update = TRUE
+ parent2.update = TRUE
return removed
- else
- last_pressure_delta = 0
- update_icon()
+ last_pressure_delta = 0
+ update_icon()
/obj/machinery/atmospherics/binary/circulator/proc/get_inlet_air()
- if(side_inverted)
- return air1
- else
- return air2
+ return side_inverted ? air1 : air2
/obj/machinery/atmospherics/binary/circulator/proc/get_outlet_air()
- if(side_inverted)
- return air2
- else
- return air1
+ return side_inverted ? air2 : air1
/obj/machinery/atmospherics/binary/circulator/proc/get_inlet_side()
- if(dir==SOUTH||dir==NORTH)
- if(side_inverted)
- return "North"
- else
- return "South"
+ if(dir & (SOUTH|NORTH))
+ return side_inverted ? "North" : "South"
/obj/machinery/atmospherics/binary/circulator/proc/get_outlet_side()
- if(dir==SOUTH||dir==NORTH)
- if(side_inverted)
- return "South"
- else
- return "North"
+ if(dir & (SOUTH|NORTH))
+ return side_inverted ? "South" : "North"
/obj/machinery/atmospherics/binary/circulator/multitool_act(mob/user, obj/item/I)
. = TRUE
diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm
index 13d1906c27a..a66dfe14c4f 100644
--- a/code/modules/mob/living/simple_animal/friendly/mouse.dm
+++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm
@@ -48,7 +48,7 @@
if(istype(F) && !F.intact)
var/obj/structure/cable/C = locate() in F
if(C && prob(15))
- if(C.avail() && !HAS_TRAIT(src, TRAIT_SHOCKIMMUNE))
+ if(C.get_available_power() && !HAS_TRAIT(src, TRAIT_SHOCKIMMUNE))
visible_message("[src] chews through [C]. It's toast!")
playsound(src, 'sound/effects/sparks2.ogg', 100, 1)
toast() // mmmm toasty.
diff --git a/code/modules/power/apc/apc.dm b/code/modules/power/apc/apc.dm
index bdc6f46e9d0..20cd0fa213c 100644
--- a/code/modules/power/apc/apc.dm
+++ b/code/modules/power/apc/apc.dm
@@ -612,9 +612,9 @@
var/last_charging_state = charging
update_last_used() // get local powernet usage and clear it for next cycle
- var/excess = surplus()
- //Now we calculate the state of the external powernet
- if(!avail())
+ var/excess = get_power_balance()
+
+ if(!get_available_power())
main_status = APC_EXTERNAL_POWER_NOTCONNECTED
else if(excess < 0)
main_status = APC_EXTERNAL_POWER_NOENERGY // there's more demand than supply on powernet, there's not enough power
@@ -628,11 +628,11 @@
if(excess > last_used_total) // if power excess recharge the cell by the same amount just used
cell.give(cell_used)
- add_load(cell_used / GLOB.CELLRATE) // add the load used to recharge the cell
+ consume_direct_power(cell_used / GLOB.CELLRATE) // add the load used to recharge the cell
else // no excess, and not enough per-apc
if((cell.charge / GLOB.CELLRATE + excess) >= last_used_total) // can we draw enough from cell+grid to cover last usage?
cell.charge = min(cell.maxcharge, cell.charge + GLOB.CELLRATE * excess) //recharge with what we can
- add_load(excess) // so draw what we can from the grid
+ consume_direct_power(excess) // so draw what we can from the grid
charging = APC_NOT_CHARGING
else // not enough power available to run the last tick!
charging = APC_NOT_CHARGING
@@ -658,7 +658,7 @@
if(excess > 0) // check to make sure we have enough to charge
// Max charge is capped to % per second constant
var/ch = min(excess*GLOB.CELLRATE, cell.maxcharge*GLOB.CHARGELEVEL)
- add_load(ch/GLOB.CELLRATE) // Removes the power we're taking from the grid
+ consume_direct_power(ch / GLOB.CELLRATE) // Removes the power we're taking from the grid
cell.give(ch) // actually recharge the cell
else
@@ -777,21 +777,24 @@
/obj/machinery/power/apc/connect_to_network()
terminal?.connect_to_network() //The terminal is what the power computer looks for
-/obj/machinery/power/apc/surplus()
+/obj/machinery/power/apc/get_surplus()
if(terminal)
- return terminal.surplus()
+ return terminal.get_surplus()
else
- return 0 //not FALSE
+ return 0
-/obj/machinery/power/apc/add_load(amount)
+/obj/machinery/power/apc/get_power_balance()
+ if(terminal)
+ return terminal.get_power_balance()
+ else
+ return 0
+
+/obj/machinery/power/apc/consume_direct_power(amount)
if(terminal?.powernet)
- terminal.add_load(amount)
+ terminal.consume_direct_power(amount)
-/obj/machinery/power/apc/avail()
- if(terminal)
- return terminal.avail()
- else
- return 0 //not FALSE
+/obj/machinery/power/apc/get_available_power()
+ return terminal ? terminal.get_available_power() : 0
/obj/machinery/power/apc/proc/power_destroy() // Caused only by explosions and teslas, not for deconstruction
if(obj_integrity > integrity_failure || opened != APC_COVER_OFF)
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
deleted file mode 100644
index 10e209e051a..00000000000
--- a/code/modules/power/cable.dm
+++ /dev/null
@@ -1,896 +0,0 @@
-#define HEALPERCABLE 3
-#define MAXCABLEPERHEAL 8
-///////////////////////////////
-//CABLE STRUCTURE
-///////////////////////////////
-
-
-////////////////////////////////
-// Definitions
-////////////////////////////////
-
-/* Cable directions (d1 and d2)
-
-
-* 9 1 5
-* \ | /
-* 8 - 0 - 4
-* / | \
-* 10 2 6
-
-If d1 = 0 and d2 = 0, there's no cable
-If d1 = 0 and d2 = dir, it's a O-X cable, getting from the center of the tile to dir (knot cable)
-If d1 = dir1 and d2 = dir2, it's a full X-X cable, getting from dir1 to dir2
-By design, d1 is the smallest direction and d2 is the highest
-*/
-
-/obj/structure/cable
- level = 1
- anchored = TRUE
- on_blueprints = TRUE
- var/datum/powernet/powernet
- name = "power cable"
- desc = "A flexible superconducting cable for heavy-duty power transfer"
- icon = 'icons/obj/power_cond/power_cond_white.dmi'
- icon_state = "0-1"
- var/d1 = 0
- var/d2 = 1
- color = COLOR_RED
-
- //The following vars are set here for the benefit of mapping - they are reset when the cable is spawned
- alpha = 128 //is set to 255 when spawned
- plane = GAME_PLANE //is set to FLOOR_PLANE when spawned
- layer = LOW_OBJ_LAYER //isset to WIRE_LAYER when spawned
-
-/obj/structure/cable/yellow
- color = COLOR_YELLOW
-
-/obj/structure/cable/green
- color = COLOR_GREEN
-
-/obj/structure/cable/blue
- color = COLOR_BLUE
-
-/obj/structure/cable/pink
- color = COLOR_PINK
-
-/obj/structure/cable/orange
- color = COLOR_ORANGE
-
-/obj/structure/cable/cyan
- color = COLOR_CYAN
-
-/obj/structure/cable/white
- color = COLOR_WHITE
-
-/obj/structure/cable/Initialize(mapload)
- . = ..()
- plane = FLOOR_PLANE //move it down so ambient occlusion ignores it
- alpha = 255 //make it not semi-transparent
- layer = WIRE_LAYER //put it on the right level
-
- // ensure d1 & d2 reflect the icon_state for entering and exiting cable
- var/dash = findtext(icon_state, "-")
- d1 = text2num(copytext( icon_state, 1, dash ))
- d2 = text2num(copytext( icon_state, dash+1 ))
-
- var/turf/T = get_turf(src) // hide if turf is not intact
- LAZYADD(GLOB.cable_list, src) //add it to the global cable list
- if(T.transparent_floor)
- return
- if(level == 1)
- hide(T.intact)
-
-/obj/structure/cable/Destroy() // called when a cable is deleted
- if(powernet)
- cut_cable_from_powernet() // update the powernets
- LAZYREMOVE(GLOB.cable_list, src) //remove it from global cable list
- return ..() // then go ahead and delete the cable
-
-/obj/structure/cable/deconstruct(disassembled = TRUE)
- var/turf/T = get_turf(src)
- if(usr)
- investigate_log("was deconstructed by [key_name(usr, 1)] in [get_area(usr)]([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])","wires")
- if(!(flags & NODECONSTRUCT))
- if(d1) // 0-X cables are 1 unit, X-X cables are 2 units long
- new/obj/item/stack/cable_coil(T, 2, paramcolor = color)
- else
- new/obj/item/stack/cable_coil(T, 1, paramcolor = color)
- qdel(src)
-
-///////////////////////////////////
-// General procedures
-///////////////////////////////////
-
-//If underfloor, hide the cable
-/obj/structure/cable/hide(i)
-
- if(level == 1 && isturf(loc))
- invisibility = i ? INVISIBILITY_MAXIMUM : 0
- update_icon()
-
-/obj/structure/cable/update_icon_state()
- if(invisibility)
- icon_state = "[d1]-[d2]-f"
- else
- icon_state = "[d1]-[d2]"
-
-////////////////////////////////////////////
-// Power related
-///////////////////////////////////////////
-
-// All power generation handled in add_avail()
-// Machines should use add_load(), surplus(), avail()
-// Non-machines should use add_delayedload(), delayed_surplus(), newavail()
-
-/obj/structure/cable/proc/add_avail(amount)
- if(powernet)
- powernet.newavail += amount
-
-/obj/structure/cable/proc/add_load(amount)
- if(powernet)
- powernet.load += amount
-
-/obj/structure/cable/proc/surplus()
- if(powernet)
- return clamp(powernet.avail-powernet.load, 0, powernet.avail)
- else
- return 0
-
-/obj/structure/cable/proc/avail()
- if(powernet)
- return powernet.avail
- else
- return 0
-
-/obj/structure/cable/proc/add_delayedload(amount)
- if(powernet)
- powernet.delayedload += amount
-
-/obj/structure/cable/proc/delayed_surplus()
- if(powernet)
- return clamp(powernet.newavail - powernet.delayedload, 0, powernet.newavail)
- else
- return 0
-
-/obj/structure/cable/proc/newavail()
- if(powernet)
- return powernet.newavail
- else
- return 0
-
-//Telekinesis has no effect on a cable
-/obj/structure/cable/attack_tk(mob/user)
- return
-
-// Items usable on a cable :
-// - Wirecutters : cut it duh !
-// - Cable coil : merge cables
-// - Multitool : get the power currently passing through the cable
-//
-/obj/structure/cable/attackby(obj/item/W, mob/user)
- var/turf/T = get_turf(src)
- if(T.transparent_floor || T.intact)
- to_chat(user, "You can't interact with something that's under the floor!")
- return
-
- else if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/coil = W
- if(coil.get_amount() < 1)
- to_chat(user, "Not enough cable!")
- return
- coil.cable_join(src, user)
-
- else if(istype(W, /obj/item/twohanded/rcl))
- var/obj/item/twohanded/rcl/R = W
- if(R.loaded)
- R.loaded.cable_join(src, user)
- R.is_empty(user)
-
- else if(istype(W, /obj/item/toy/crayon))
- var/obj/item/toy/crayon/C = W
- cable_color(C.colourName)
-
- else
- if(W.flags & CONDUCT)
- shock(user, 50, 0.7)
-
- add_fingerprint(user)
-
-/obj/structure/cable/multitool_act(mob/user, obj/item/I)
- . = TRUE
- var/turf/T = get_turf(src)
- if(T.intact)
- return
- if(!I.use_tool(src, user, 0, volume = I.tool_volume))
- return
- if(powernet && (powernet.avail > 0)) // is it powered?
- to_chat(user, "Total power: [DisplayPower(powernet.avail)]\nLoad: [DisplayPower(powernet.load)]\nExcess power: [DisplayPower(surplus())]")
- else
- to_chat(user, "The cable is not powered.")
- shock(user, 5, 0.2)
-
-/obj/structure/cable/wirecutter_act(mob/user, obj/item/I)
- . = TRUE
- var/turf/T = get_turf(src)
- if(T.transparent_floor || T.intact)
- to_chat(user, "You can't interact with something that's under the floor!")
- return
- if(!I.use_tool(src, user, 0, volume = I.tool_volume))
- return
- if(shock(user, 50))
- return
- user.visible_message("[user] cuts the cable.", "You cut the cable.")
- investigate_log("was cut by [key_name(usr, 1)] in [get_area(user)]([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])","wires")
- deconstruct()
-
-// shock the user with probability prb
-/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1)
- if(!prob(prb))
- return FALSE
- if(electrocute_mob(user, powernet, src, siemens_coeff))
- do_sparks(5, 1, src)
- return TRUE
- else
- return FALSE
-
-/obj/structure/cable/singularity_pull(S, current_size)
- ..()
- if(current_size >= STAGE_FIVE)
- deconstruct()
-
-/obj/structure/cable/proc/cable_color(colorC)
- if(!colorC)
- color = COLOR_RED
- else if(colorC == "rainbow")
- color = color_rainbow()
- else if(colorC == "orange") //byond only knows 16 colors by name, and orange isn't one of them
- color = COLOR_ORANGE
- else
- color = colorC
-
-/obj/structure/cable/proc/color_rainbow()
- color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
- return color
-
-/////////////////////////////////////////////////
-// Cable laying helpers
-////////////////////////////////////////////////
-
-//handles merging diagonally matching cables
-//for info : direction^3 is flipping horizontally, direction^12 is flipping vertically
-/obj/structure/cable/proc/mergeDiagonalsNetworks(direction)
-
- //search for and merge diagonally matching cables from the first direction component (north/south)
- var/turf/T = get_step(src, direction&3)//go north/south
-
- for(var/obj/structure/cable/C in T)
-
- if(!C)
- continue
-
- if(src == C)
- continue
-
- if(C.d1 == (direction^3) || C.d2 == (direction^3)) //we've got a diagonally matching cable
- if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables)
- var/datum/powernet/newPN = new()
- newPN.add_cable(C)
-
- if(powernet) //if we already have a powernet, then merge the two powernets
- merge_powernets(powernet,C.powernet)
- else
- C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet
-
- //the same from the second direction component (east/west)
- T = get_step(src, direction&12)//go east/west
-
- for(var/obj/structure/cable/C in T)
-
- if(!C)
- continue
-
- if(src == C)
- continue
- if(C.d1 == (direction^12) || C.d2 == (direction^12)) //we've got a diagonally matching cable
- if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables)
- var/datum/powernet/newPN = new()
- newPN.add_cable(C)
-
- if(powernet) //if we already have a powernet, then merge the two powernets
- merge_powernets(powernet,C.powernet)
- else
- C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet
-
-// merge with the powernets of power objects in the given direction
-/obj/structure/cable/proc/mergeConnectedNetworks(direction)
-
- var/fdir = (!direction)? 0 : turn(direction, 180) //flip the direction, to match with the source position on its turf
-
- if(!(d1 == direction || d2 == direction)) //if the cable is not pointed in this direction, do nothing
- return
-
- var/turf/TB = get_step(src, direction)
-
- for(var/obj/structure/cable/C in TB)
-
- if(!C)
- continue
-
- if(src == C)
- continue
-
- if(C.d1 == fdir || C.d2 == fdir) //we've got a matching cable in the neighbor turf
- if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables)
- var/datum/powernet/newPN = new()
- newPN.add_cable(C)
-
- if(powernet) //if we already have a powernet, then merge the two powernets
- merge_powernets(powernet,C.powernet)
- else
- C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet
-
-// merge with the powernets of power objects in the source turf
-/obj/structure/cable/proc/mergeConnectedNetworksOnTurf()
- var/list/to_connect = list()
-
- if(!powernet) //if we somehow have no powernet, make one (should not happen for cables)
- var/datum/powernet/newPN = new()
- newPN.add_cable(src)
-
- //first let's add turf cables to our powernet
- //then we'll connect machines on turf with a node cable is present
- for(var/AM in loc)
- if(istype(AM, /obj/structure/cable))
- var/obj/structure/cable/C = AM
- if(C.d1 == d1 || C.d2 == d1 || C.d1 == d2 || C.d2 == d2) //only connected if they have a common direction
- if(C.powernet == powernet)
- continue
- if(C.powernet)
- merge_powernets(powernet, C.powernet)
- else
- powernet.add_cable(C) //the cable was powernetless, let's just add it to our powernet
-
- else if(istype(AM, /obj/machinery/power/apc))
- var/obj/machinery/power/apc/N = AM
- if(!N.terminal)
- continue // APC are connected through their terminal
-
- if(N.terminal.powernet == powernet)
- continue
-
- to_connect += N.terminal //we'll connect the machines after all cables are merged
-
- else if(istype(AM, /obj/machinery/power)) //other power machines
- var/obj/machinery/power/M = AM
-
- if(M.powernet == powernet)
- continue
-
- to_connect += M //we'll connect the machines after all cables are merged
-
- //now that cables are done, let's connect found machines
- for(var/obj/machinery/power/PM in to_connect)
- if(!PM.connect_to_network())
- PM.disconnect_from_network() //if we somehow can't connect the machine to the new powernet, remove it from the old nonetheless
-
-//////////////////////////////////////////////
-// Powernets handling helpers
-//////////////////////////////////////////////
-
-//if powernetless_only = 1, will only get connections without powernet
-/obj/structure/cable/proc/get_connections(powernetless_only = 0)
- . = list() // this will be a list of all connected power objects
- var/turf/T
-
- //get matching cables from the first direction
- if(d1) //if not a node cable
- T = get_step(src, d1)
- if(T)
- . += power_list(T, src, turn(d1, 180), powernetless_only) //get adjacents matching cables
-
- if(d1&(d1-1)) //diagonal direction, must check the 4 possibles adjacents tiles
- T = get_step(src,d1&3) // go north/south
- if(T)
- . += power_list(T, src, d1 ^ 3, powernetless_only) //get diagonally matching cables
- T = get_step(src,d1&12) // go east/west
- if(T)
- . += power_list(T, src, d1 ^ 12, powernetless_only) //get diagonally matching cables
-
- . += power_list(loc, src, d1, powernetless_only) //get on turf matching cables
-
- //do the same on the second direction (which can't be 0)
- T = get_step(src, d2)
- if(T)
- . += power_list(T, src, turn(d2, 180), powernetless_only) //get adjacents matching cables
-
- if(d2&(d2-1)) //diagonal direction, must check the 4 possibles adjacents tiles
- T = get_step(src,d2&3) // go north/south
- if(T)
- . += power_list(T, src, d2 ^ 3, powernetless_only) //get diagonally matching cables
- T = get_step(src,d2&12) // go east/west
- if(T)
- . += power_list(T, src, d2 ^ 12, powernetless_only) //get diagonally matching cables
- . += power_list(loc, src, d2, powernetless_only) //get on turf matching cables
-
- return .
-
-//should be called after placing a cable which extends another cable, creating a "smooth" cable that no longer terminates in the centre of a turf.
-//needed as this can, unlike other placements, disconnect cables
-/obj/structure/cable/proc/denode()
- var/turf/T1 = loc
- if(!T1)
- return
-
- var/list/powerlist = power_list(T1,src,0,0) //find the other cables that ended in the centre of the turf, with or without a powernet
- if(powerlist.len>0)
- var/datum/powernet/PN = new()
- propagate_network(powerlist[1],PN) //propagates the new powernet beginning at the source cable
-
- if(PN.is_empty()) //can happen with machines made nodeless when smoothing cables
- qdel(PN)
-
-/obj/structure/cable/proc/auto_propogate_cut_cable(obj/O)
- if(O && !QDELETED(O))
- var/datum/powernet/newPN = new()// creates a new powernet...
- propagate_network(O, newPN)//... and propagates it to the other side of the cable
-
-// cut the cable's powernet at this cable and updates the powergrid
-/obj/structure/cable/proc/cut_cable_from_powernet(remove=TRUE)
- var/turf/T1 = loc
- var/list/P_list
- if(!T1)
- return
- if(d1)
- T1 = get_step(T1, d1)
- P_list = power_list(T1, src, turn(d1,180),0,cable_only = 1) // what adjacently joins on to cut cable...
-
- P_list += power_list(loc, src, d1, 0, cable_only = 1)//... and on turf
-
-
- if(P_list.len == 0)//if nothing in both list, then the cable was a lone cable, just delete it and its powernet
- powernet.remove_cable(src)
-
- for(var/obj/machinery/power/P in T1)//check if it was powering a machine
- if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to
- P.disconnect_from_network() //remove from current network (and delete powernet)
- return
-
- var/obj/O = P_list[1]
- // remove the cut cable from its turf and powernet, so that it doesn't get count in propagate_network worklist
- if(remove)
- loc = null
- powernet.remove_cable(src) //remove the cut cable from its powernet
-
- // queue it to rebuild
- SSmachines.deferred_powernet_rebuilds += O
-// addtimer(CALLBACK(O, PROC_REF(auto_propogate_cut_cable), O), 0) //so we don't rebuild the network X times when singulo/explosion destroys a line of X cables
-
- // Disconnect machines connected to nodes
- if(d1 == 0) // if we cut a node (O-X) cable
- for(var/obj/machinery/power/P in T1)
- if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to
- P.disconnect_from_network() //remove from current network
-
-
-///////////////////////////////////////////////
-// The cable coil object, used for laying cable
-///////////////////////////////////////////////
-
-////////////////////////////////
-// Definitions
-////////////////////////////////
-
-GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe/cable_restraints("cable restraints", /obj/item/restraints/handcuffs/cable, 15)))
-
-/obj/item/stack/cable_coil
- name = "cable coil"
- singular_name = "cable"
- icon = 'icons/obj/power.dmi'
- icon_state = "coil"
- item_state = "coil_red"
- belt_icon = "cable_coil"
- amount = MAXCOIL
- max_amount = MAXCOIL
- merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves
- color = COLOR_RED
- throwforce = 10
- w_class = WEIGHT_CLASS_SMALL
- throw_speed = 2
- throw_range = 5
- materials = list(MAT_METAL = 15, MAT_GLASS = 10)
- flags = CONDUCT
- slot_flags = SLOT_BELT
- item_state = "coil"
- attack_verb = list("whipped", "lashed", "disciplined", "flogged")
- usesound = 'sound/items/deconstruct.ogg'
- toolspeed = 1
-
-/obj/item/stack/cable_coil/suicide_act(mob/user)
- if(locate(/obj/structure/chair/stool) in user.loc)
- user.visible_message("[user] is making a noose with [src]! It looks like [user.p_theyre()] trying to commit suicide.")
- else
- user.visible_message("[user] is strangling [user.p_themselves()] with [src]! It looks like [user.p_theyre()] trying to commit suicide.")
- return OXYLOSS
-
-/obj/item/stack/cable_coil/New(loc, length = MAXCOIL, paramcolor = null)
- ..()
- if(paramcolor)
- color = paramcolor
- pixel_x = rand(-2,2)
- pixel_y = rand(-2,2)
- update_icon()
- recipes = GLOB.cable_coil_recipes
- update_wclass()
-
-///////////////////////////////////
-// General procedures
-///////////////////////////////////
-//you can use wires to heal robotics
-/obj/item/stack/cable_coil/attack(mob/M, mob/user)
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- var/obj/item/organ/external/S = H.bodyparts_by_name[user.zone_selected]
-
- if(!S)
- return
- if(!S.is_robotic() || user.a_intent != INTENT_HELP || S.open == ORGAN_SYNTHETIC_OPEN)
- return ..()
-
- if(S.burn_dam > ROBOLIMB_SELF_REPAIR_CAP)
- to_chat(user, "The damage is far too severe to patch over externally.")
- return
-
- if(!S.burn_dam)
- to_chat(user, "Nothing to fix!")
- return
-
- if(H == user)
- if(!do_mob(user, H, 10))
- return 0
- var/cable_used = 0
- var/childlist
- if(!isnull(S.children))
- childlist = S.children.Copy()
- var/parenthealed = FALSE
- while(cable_used <= MAXCABLEPERHEAL && amount >= 1)
- var/obj/item/organ/external/E
- if(S.burn_dam)
- E = S
- else if(LAZYLEN(childlist))
- E = pick_n_take(childlist)
- if(!E.burn_dam || E.burn_dam >= ROBOLIMB_SELF_REPAIR_CAP || !E.is_robotic())
- continue
- else if(S.parent && !parenthealed)
- E = S.parent
- parenthealed = TRUE
- if(!E.burn_dam || E.burn_dam >= ROBOLIMB_SELF_REPAIR_CAP || !E.is_robotic())
- break
- else
- break
- while(cable_used <= MAXCABLEPERHEAL && E.burn_dam && amount >= 1)
- use(1)
- cable_used += 1
- E.heal_damage(0, HEALPERCABLE, 0, TRUE)
- H.UpdateDamageIcon()
- user.visible_message("[user] repairs some burn damage on [M]'s [E.name] with [src].")
- return 1
-
- else
- return ..()
-
-/obj/item/stack/cable_coil/split()
- var/obj/item/stack/cable_coil/C = ..()
- C.color = color
- return C
-
-/obj/item/stack/cable_coil/update_name()
- . = ..()
- if(amount > 2)
- name = "cable coil"
- else
- name = "cable piece"
-
-/obj/item/stack/cable_coil/update_icon_state()
- if(!color)
- color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_ORANGE, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
- if(amount == 1)
- icon_state = "coil1"
- else if(amount == 2)
- icon_state = "coil2"
- else
- icon_state = "coil"
-
-/obj/item/stack/cable_coil/proc/update_wclass()
- if(amount == 1)
- w_class = WEIGHT_CLASS_TINY
- else
- w_class = WEIGHT_CLASS_SMALL
-
-/obj/item/stack/cable_coil/examine(mob/user)
- . = ..()
- if(in_range(user, src) && !is_cyborg)
- if(get_amount() == 1)
- . += "A short piece of power cable."
- else if(get_amount() == 2)
- . += "A piece of power cable."
- else
- . += "A coil of power cables."
-
-// Items usable on a cable coil :
-// - Wirecutters : cut them duh !
-// - Cable coil : merge cables
-/obj/item/stack/cable_coil/attackby(obj/item/W, mob/user)
- ..()
- if(istype(W, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/C = W
- // Cable merging is handled by parent proc
- if(C.get_amount() >= MAXCOIL)
- to_chat(user, "The coil is as long as it will get.")
- return
- if((C.get_amount() + get_amount() <= MAXCOIL))
- to_chat(user, "You join the cable coils together.")
- return
- else
- to_chat(user, "You transfer [get_amount_transferred()] length\s of cable from one coil to the other.")
- return
-
- if(istype(W, /obj/item/toy/crayon))
- var/obj/item/toy/crayon/C = W
- cable_color(C.colourName)
-
-///////////////////////////////////////////////
-// Cable laying procedures
-//////////////////////////////////////////////
-
-/obj/item/stack/cable_coil/proc/get_new_cable(location)
- var/obj/structure/cable/C = new(location)
- C.cable_color(color)
-
- return C
-
-// called when cable_coil is clicked on a turf/simulated/floor
-/obj/item/stack/cable_coil/proc/place_turf(turf/T, mob/user, dirnew)
- if(!isturf(user.loc))
- return
-
- if(!isturf(T) || T.intact || !T.can_have_cabling())
- to_chat(user, "You can only lay cables on catwalks and plating!")
- return
-
- if(get_amount() < 1) // Out of cable
- to_chat(user, "There is no cable left!")
- return
-
- if(get_dist(T,user) > 1) // Too far
- to_chat(user, "You can't lay cable at a place that far away!")
- return
-
- var/dirn
- if(!dirnew) //If we weren't given a direction, come up with one! (Called as null from catwalk.dm and floor.dm)
- if(user.loc == T)
- dirn = user.dir //If laying on the tile we're on, lay in the direction we're facing
- else
- dirn = get_dir(T, user)
- else
- dirn = dirnew
-
- for(var/obj/structure/cable/LC in T)
- if(LC.d2 == dirn && LC.d1 == 0)
- to_chat(user, "There's already a cable at that position!")
- return
-
- var/obj/structure/cable/C = get_new_cable(T)
-
- //set up the new cable
- C.d1 = 0 //it's a O-X node cable
- C.d2 = dirn
- C.add_fingerprint(user)
- C.update_icon()
-
- //create a new powernet with the cable, if needed it will be merged later
- var/datum/powernet/PN = new()
- PN.add_cable(C)
-
- C.mergeConnectedNetworks(C.d2) //merge the powernet with adjacents powernets
- C.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets
-
- if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions
- C.mergeDiagonalsNetworks(C.d2)
-
- use(1)
-
- if(C.shock(user, 50))
- if(prob(50)) //fail
- new /obj/item/stack/cable_coil(get_turf(C), 1, paramcolor = C.color)
- C.deconstruct()
-
- return C
-
-// called when cable_coil is click on an installed obj/cable
-// or click on a turf that already contains a "node" cable
-/obj/item/stack/cable_coil/proc/cable_join(obj/structure/cable/C, mob/user)
- var/turf/U = user.loc
- if(!isturf(U))
- return
-
- var/turf/T = get_turf(C)
-
- if(!isturf(T) || T.intact || T.transparent_floor) // sanity checks, also stop use interacting with T-scanner revealed cable
- return
-
- if(get_dist(C, user) > 1) // make sure it's close enough
- to_chat(user, "You can't lay cable at a place that far away!")
- return
-
-
- if(U == T) //if clicked on the turf we're standing on, try to put a cable in the direction we're facing
- place_turf(T,user)
- return
-
- var/dirn = get_dir(C, user)
-
- // one end of the clicked cable is pointing towards us
- if(C.d1 == dirn || C.d2 == dirn)
- if(U.intact || U.transparent_floor) // can't place a cable if the floor is complete
- to_chat(user, "You can't lay cable there unless the floor tiles are removed!")
- return
- else
- // cable is pointing at us, we're standing on an open tile
- // so create a stub pointing at the clicked cable on our tile
-
- var/fdirn = turn(dirn, 180) // the opposite direction
-
- for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already
- if(LC.d1 == fdirn || LC.d2 == fdirn)
- to_chat(user, "There's already a cable at that position!")
- return
-
- var/obj/structure/cable/NC = get_new_cable (U)
-
- NC.d1 = 0
- NC.d2 = fdirn
- NC.add_fingerprint(user)
- NC.update_icon()
-
- //create a new powernet with the cable, if needed it will be merged later
- var/datum/powernet/newPN = new()
- newPN.add_cable(NC)
-
- NC.mergeConnectedNetworks(NC.d2) //merge the powernet with adjacents powernets
- NC.mergeConnectedNetworksOnTurf() //merge the powernet with on turf powernets
-
- if(NC.d2 & (NC.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions
- NC.mergeDiagonalsNetworks(NC.d2)
-
- use(1)
-
- if(NC.shock(user, 50))
- if(prob(50)) //fail
- NC.deconstruct()
- return
-
- // exisiting cable doesn't point at our position, so see if it's a stub
- else if(C.d1 == 0)
- // if so, make it a full cable pointing from it's old direction to our dirn
- var/nd1 = C.d2 // these will be the new directions
- var/nd2 = dirn
-
-
- if(nd1 > nd2) // swap directions to match icons/states
- nd1 = dirn
- nd2 = C.d2
-
-
- for(var/obj/structure/cable/LC in T) // check to make sure there's no matching cable
- if(LC == C) // skip the cable we're interacting with
- continue
- if((LC.d1 == nd1 && LC.d2 == nd2) || (LC.d1 == nd2 && LC.d2 == nd1) ) // make sure no cable matches either direction
- to_chat(user, "There's already a cable at that position!")
- return
-
-
- C.cable_color(color)
-
- C.d1 = nd1
- C.d2 = nd2
-
- C.add_fingerprint()
- C.update_icon()
-
-
- C.mergeConnectedNetworks(C.d1) //merge the powernets...
- C.mergeConnectedNetworks(C.d2) //...in the two new cable directions
- C.mergeConnectedNetworksOnTurf()
-
- if(C.d1 & (C.d1 - 1))// if the cable is layed diagonally, check the others 2 possible directions
- C.mergeDiagonalsNetworks(C.d1)
-
- if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions
- C.mergeDiagonalsNetworks(C.d2)
-
- use(1)
-
- if(C.shock(user, 50))
- if(prob(50)) //fail
- C.deconstruct()
- return
-
- C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets.
- return
-
-//////////////////////////////
-// Misc.
-/////////////////////////////
-
-/obj/item/stack/cable_coil/cut
- item_state = "coil2"
-
-/obj/item/stack/cable_coil/cut/Initialize(mapload)
- . = ..()
- src.amount = rand(1,2)
- pixel_x = rand(-2,2)
- pixel_y = rand(-2,2)
- update_appearance(UPDATE_NAME|UPDATE_ICON_STATE)
- update_wclass()
-
-
-/obj/item/stack/cable_coil/five
-
-// Passes '5' to the parent as `new_amount`, so 5 coils are created.
-/obj/item/stack/cable_coil/five/New(loc, new_amount = 5, merge = TRUE, paramcolor = null)
- ..()
-
-/obj/item/stack/cable_coil/yellow
- color = COLOR_YELLOW
-
-/obj/item/stack/cable_coil/blue
- color = COLOR_BLUE
-
-/obj/item/stack/cable_coil/green
- color = COLOR_GREEN
-
-/obj/item/stack/cable_coil/pink
- color = COLOR_PINK
-
-/obj/item/stack/cable_coil/orange
- color = COLOR_ORANGE
-
-/obj/item/stack/cable_coil/cyan
- color = COLOR_CYAN
-
-/obj/item/stack/cable_coil/white
- color = COLOR_WHITE
-
-/obj/item/stack/cable_coil/random/New()
- color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE)
- ..()
-
-/obj/item/stack/cable_coil/proc/cable_color(colorC)
- if(!colorC)
- color = COLOR_RED
- else if(colorC == "rainbow")
- color = color_rainbow()
- else if(colorC == "orange") //byond only knows 16 colors by name, and orange isn't one of them
- color = COLOR_ORANGE
- else
- color = colorC
-
-/obj/item/stack/cable_coil/proc/color_rainbow()
- color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
- return color
-
-/obj/item/stack/cable_coil/cyborg
- energy_type = /datum/robot_energy_storage/cable
- is_cyborg = TRUE
-
-/obj/item/stack/cable_coil/cyborg/update_icon_state()
- return // icon_state should always be a full cable
-
-/obj/item/stack/cable_coil/cyborg/attack_self(mob/user)
- var/cablecolor = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white")
- color = cablecolor
- update_icon()
-
-#undef MAXCABLEPERHEAL
-#undef HEALPERCABLE
diff --git a/code/modules/power/cable_logic.dm b/code/modules/power/cable_logic.dm
deleted file mode 100644
index 09d10320fd5..00000000000
--- a/code/modules/power/cable_logic.dm
+++ /dev/null
@@ -1,292 +0,0 @@
-#define LOGIC_HIGH 5
-
-//Indicators only have one input and no outputs
-/obj/machinery/logic/indicator
- //Input is searched from the 'dir' direction
- var/obj/structure/cable/input
-
-/obj/machinery/logic/indicator/process()
- if(input)
- return 1
-
-
- if(!input)
- var/turf/T = get_step(src, dir)
- if(T)
- var/inv_dir = turn(dir, 180)
- for(var/obj/structure/cable/C in T)
- if(C.d1 == inv_dir || C.d2 == inv_dir)
- input = C
- return 1
-
- return 0 //If it gets to here, it means no suitable wire to link to was found.
-
-/obj/machinery/logic/indicator/bulb
- icon = 'icons/obj/lighting.dmi'
- icon_state = "bulb0"
-
-/obj/machinery/logic/indicator/bulb/process()
- if(!..()) //Parent proc checks if input1 exists.
- return
-
- var/datum/powernet/pn_input = input.powernet
- if(!pn_input)
- return
-
- if(pn_input.avail >= LOGIC_HIGH)
- icon_state = "bulb1"
- else
- icon_state = "bulb0"
-
-
-
-
-//Sensors only have one output and no inputs
-/obj/machinery/logic/sensor
- //Output is searched from the 'dir' direction
- var/obj/structure/cable/output
-
-/obj/machinery/logic/sensor/process()
- if(output)
- return 1
-
- if(!output)
- var/turf/T = get_step(src, dir)
- if(T)
- var/inv_dir = turn(dir, 180)
- for(var/obj/structure/cable/C in T)
- if(C.d1 == inv_dir || C.d2 == inv_dir)
- output = C
- return 1
-
- return 0 //If it gets to here, it means no suitable wire to link to was found.
-
-//Constant high generator. This will continue to send a signal of LOGIC_HIGH as long as it exists.
-/obj/machinery/logic/sensor/constant_high
- icon = 'icons/obj/atmospherics/outlet_injector.dmi'
- icon_state = "off"
-
-/obj/machinery/logic/sensor/constant_high/process()
- if(!..()) //Parent proc checks if input1 exists.
- return
-
- var/datum/powernet/pn_output = output.powernet
- if(!pn_output)
- return
-
- pn_output.newavail = max(pn_output.avail, LOGIC_HIGH)
-
-
-
-
-//ONE INPUT logic elements have one input and one output
-/obj/machinery/logic/oneinput
- var/dir_input = 2
- var/dir_output = 1
- var/obj/structure/cable/input
- var/obj/structure/cable/output
- icon = 'icons/atmos/heat.dmi'
- icon_state = "intact"
-
-/obj/machinery/logic/oneinput/process()
- if(input && output)
- return 1
-
- if(!dir_input || !dir_output)
- return 0
-
- if(!input)
- var/turf/T = get_step(src, dir_input)
- if(T)
- var/inv_dir = turn(dir_input, 180)
- for(var/obj/structure/cable/C in T)
- if(C.d1 == inv_dir || C.d2 == inv_dir)
- input = C
-
- if(!output)
- var/turf/T = get_step(src, dir_output)
- if(T)
- var/inv_dir = turn(dir_output, 180)
- for(var/obj/structure/cable/C in T)
- if(C.d1 == inv_dir || C.d2 == inv_dir)
- output = C
-
- return 0 //On the process() call, where everything is still being searched for, it returns 0. It will return 1 on the next process() call.
-
-//NOT GATE
-/obj/machinery/logic/oneinput/not/process()
- if(!..()) //Parent proc checks if input1, input2 and output exist.
- return
-
- var/datum/powernet/pn_input = input.powernet
-
- if(!pn_input)
- return
-
- var/datum/powernet/pn_output = output.powernet
- if(!pn_output)
- return
-
- if( !(pn_input.avail >= LOGIC_HIGH))
- pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
- else
- pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5
-
-
-
-
-
-
-
-
-
-//TWO INPUT logic elements have two inputs and one output
-/obj/machinery/logic/twoinput
- var/dir_input1 = 2
- var/dir_input2 = 8
- var/dir_output = 1
- var/obj/structure/cable/input1
- var/obj/structure/cable/input2
- var/obj/structure/cable/output
- icon = 'icons/obj/atmospherics/mixer.dmi'
- icon_state = "intact_off"
-
-/obj/machinery/logic/twoinput/process()
- if(input1 && input2 && output)
- return 1
-
- if(!dir_input1 || !dir_input2 || !dir_output)
- return 0
-
- if(!input1)
- var/turf/T = get_step(src, dir_input1)
- if(T)
- var/inv_dir = turn(dir_input1, 180)
- for(var/obj/structure/cable/C in T)
- if(C.d1 == inv_dir || C.d2 == inv_dir)
- input1 = C
-
- if(!input2)
- var/turf/T = get_step(src, dir_input2)
- if(T)
- var/inv_dir = turn(dir_input2, 180)
- for(var/obj/structure/cable/C in T)
- if(C.d1 == inv_dir || C.d2 == inv_dir)
- input2 = C
-
- if(!output)
- var/turf/T = get_step(src, dir_output)
- if(T)
- var/inv_dir = turn(dir_output, 180)
- for(var/obj/structure/cable/C in T)
- if(C.d1 == inv_dir || C.d2 == inv_dir)
- output = C
-
- return 0 //On the process() call, where everything is still being searched for, it returns 0. It will return 1 on the next process() call.
-
-//AND GATE
-/obj/machinery/logic/twoinput/and/process()
- if(!..()) //Parent proc checks if input1, input2 and output exist.
- return
-
- var/datum/powernet/pn_input1 = input1.powernet
- var/datum/powernet/pn_input2 = input2.powernet
-
- if(!pn_input1 || !pn_input2)
- return
-
- var/datum/powernet/pn_output = output.powernet
- if(!pn_output)
- return
-
- if( (pn_input1.avail >= LOGIC_HIGH) && (pn_input2.avail >= LOGIC_HIGH) )
- pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
- else
- pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5
-
-//OR GATE
-/obj/machinery/logic/twoinput/or/process()
- if(!..()) //Parent proc checks if input1, input2 and output exist.
- return
-
- var/datum/powernet/pn_input1 = input1.powernet
- var/datum/powernet/pn_input2 = input2.powernet
-
- if(!pn_input1 || !pn_input2)
- return
-
- var/datum/powernet/pn_output = output.powernet
- if(!pn_output)
- return
-
- if( (pn_input1.avail >= LOGIC_HIGH) || (pn_input2.avail >= LOGIC_HIGH) )
- pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
- else
- pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5
-
-//XOR GATE
-/obj/machinery/logic/twoinput/xor/process()
- if(!..()) //Parent proc checks if input1, input2 and output exist.
- return
-
- var/datum/powernet/pn_input1 = input1.powernet
- var/datum/powernet/pn_input2 = input2.powernet
-
- if(!pn_input1 || !pn_input2)
- return
-
- var/datum/powernet/pn_output = output.powernet
- if(!pn_output)
- return
-
- if( (pn_input1.avail >= LOGIC_HIGH) != (pn_input2.avail >= LOGIC_HIGH) )
- pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
- else
- pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5
-
-//XNOR GATE (EQUIVALENCE)
-/obj/machinery/logic/twoinput/xnor/process()
- if(!..()) //Parent proc checks if input1, input2 and output exist.
- return
-
- var/datum/powernet/pn_input1 = input1.powernet
- var/datum/powernet/pn_input2 = input2.powernet
-
- if(!pn_input1 || !pn_input2)
- return
-
- var/datum/powernet/pn_output = output.powernet
- if(!pn_output)
- return
-
- if( (pn_input1.avail >= LOGIC_HIGH) == (pn_input2.avail >= LOGIC_HIGH) )
- pn_output.newavail = max(pn_output.avail, LOGIC_HIGH) //Set the output avilable power to 5 or whatever it was before.
- else
- pn_output.load += LOGIC_HIGH //Otherwise increase the load to 5
-
-#define RELAY_POWER_TRANSFER 2000 //How much power a relay transfers through.
-
-//RELAY - input1 governs the flow from input2 to output
-/obj/machinery/logic/twoinput/relay/process()
- if(!..()) //Parent proc checks if input1, input2 and output exist.
- return
-
- var/datum/powernet/pn_input1 = input1.powernet
-
- if(!pn_input1)
- return
-
- if( pn_input1.avail >= LOGIC_HIGH )
- var/datum/powernet/pn_input2 = input2.powernet
- var/datum/powernet/pn_output = output.powernet
-
- if(!pn_output)
- return
-
- if(pn_input2.avail >= RELAY_POWER_TRANSFER)
- pn_input2.load += RELAY_POWER_TRANSFER
- pn_output.newavail += RELAY_POWER_TRANSFER
-
-
-#undef RELAY_POWER_TRANSFER
-#undef LOGIC_HIGH
diff --git a/code/modules/power/cables/cable.dm b/code/modules/power/cables/cable.dm
new file mode 100644
index 00000000000..bf0bfcfb030
--- /dev/null
+++ b/code/modules/power/cables/cable.dm
@@ -0,0 +1,446 @@
+
+/*
+ * Cable directions (d1 and d2)
+ * 9 1 5
+ * \ | /
+ * 8 - 0 - 4
+ * / | \
+ * 10 2 6
+If d1 = 0 and d2 = 0, there's no cable
+If d1 = 0 and d2 = dir, it's a O-X cable, getting from the center of the tile to dir (knot cable)
+If d1 = dir1 and d2 = dir2, it's a full X-X cable, getting from dir1 to dir2
+By design, d1 is the smallest direction and d2 is the highest
+*/
+
+/*
+ * # /obj/structure/cable
+ *
+ * The red wire thingies you see on the ground all over the station in maintenance
+ * the d1 and d2 vars deal with the "directions" of the cables, since all instances of this cable structure are
+ * just lines, they have two endpoints (d1 and d2).
+*/
+/obj/structure/cable
+ name = "power cable"
+ desc = "A flexible superconducting cable for heavy-duty power transfer."
+ icon = 'icons/obj/power_cond/power_cond_white.dmi'
+ icon_state = "0-1"
+ level = 1
+ anchored = TRUE
+ on_blueprints = TRUE
+ color = COLOR_RED
+
+ //The following vars are set here for the benefit of mapping - they are reset when the cable is spawned
+ alpha = 128 //is set to 255 when spawned
+ plane = GAME_PLANE //is set to FLOOR_PLANE when spawned
+ layer = LOW_OBJ_LAYER //isset to WIRE_LAYER when spawned
+
+ /// The direction of endpoint one of this cable
+ var/d1 = 0
+ /// The direction of enpoint two of this cable
+ var/d2 = 1
+ /// The regional powernet this cable is registered to
+ var/datum/regional_powernet/powernet
+
+/obj/structure/cable/Initialize(mapload)
+ . = ..()
+ //we set vars in definition for mapping, now we revert it in init()
+ plane = FLOOR_PLANE //move it down so ambient occlusion ignores it
+ alpha = 255 //make it not semi-transparent
+ layer = WIRE_LAYER //put it on the right level
+
+ // ensure d1 & d2 reflect the icon_state for entering and exiting cable
+ var/dash = findtext(icon_state, "-")
+ d1 = text2num(copytext(icon_state, 1, dash))
+ d2 = text2num(copytext(icon_state, dash + 1))
+
+ var/turf/T = get_turf(src) // hide if turf is not intact
+ LAZYADD(GLOB.cable_list, src) //add it to the global cable list
+ if(T.transparent_floor)
+ return
+ if(level == 1)
+ hide(T.intact)
+
+/obj/structure/cable/Destroy()
+ if(powernet)
+ cut_cable_from_powernet() // update the powernets
+ LAZYREMOVE(GLOB.cable_list, src) //remove it from global cable list
+ return ..() // then go ahead and delete the cable
+
+/obj/structure/cable/update_icon_state()
+ if(invisibility)
+ icon_state = "[d1]-[d2]-f"
+ else
+ icon_state = "[d1]-[d2]"
+
+/// If underfloor, hide the cable
+/obj/structure/cable/hide(i)
+ if(level == 1 && isturf(loc))
+ invisibility = i ? INVISIBILITY_MAXIMUM : 0
+ update_icon()
+
+// Items usable on a cable :
+// - Wirecutters : cut it duh !
+// - Cable coil : merge cables
+// - Multitool : get the power currently passing through the cable
+//
+/obj/structure/cable/attackby(obj/item/W, mob/user)
+ var/turf/T = get_turf(src)
+ if(T.transparent_floor || T.intact)
+ to_chat(user, "You can't interact with something that's under the floor!")
+ return
+
+ else if(istype(W, /obj/item/stack/cable_coil))
+ var/obj/item/stack/cable_coil/coil = W
+ if(coil.get_amount() < 1)
+ to_chat(user, "Not enough cable!")
+ return
+ coil.cable_join(src, user)
+
+ else if(istype(W, /obj/item/twohanded/rcl))
+ var/obj/item/twohanded/rcl/R = W
+ if(R.loaded)
+ R.loaded.cable_join(src, user)
+ R.is_empty(user)
+
+ else if(istype(W, /obj/item/toy/crayon))
+ var/obj/item/toy/crayon/C = W
+ cable_color(C.colourName)
+
+ else
+ if(W.flags & CONDUCT)
+ shock(user, 50, 0.7)
+
+ add_fingerprint(user)
+
+/obj/structure/cable/multitool_act(mob/user, obj/item/I)
+ . = TRUE
+ var/turf/T = get_turf(src)
+ if(T.intact)
+ return
+ if(!I.use_tool(src, user, 0, volume = I.tool_volume))
+ return
+ if(powernet && (powernet.available_power > 0)) // is it powered?
+ to_chat(user, "Total power: [DisplayPower(powernet.available_power)]\nLoad: [DisplayPower(powernet.power_demand)]\nExcess power: [DisplayPower(get_surplus())]")
+ else
+ to_chat(user, "The cable is not powered.")
+ shock(user, 5, 0.2)
+
+/obj/structure/cable/wirecutter_act(mob/user, obj/item/I)
+ . = TRUE
+ var/turf/T = get_turf(src)
+ if(T.transparent_floor || T.intact)
+ to_chat(user, "You can't interact with something that's under the floor!")
+ return
+ if(!I.use_tool(src, user, 0, volume = I.tool_volume))
+ return
+ if(shock(user, 50))
+ return
+ user.visible_message("[user] cuts the cable.", "You cut the cable.")
+ investigate_log("was cut by [key_name(usr, 1)] in [get_area(user)]([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])","wires")
+ deconstruct()
+
+/obj/structure/cable/proc/cable_color(colorC)
+ if(!colorC)
+ color = COLOR_RED
+ else if(colorC == "rainbow")
+ color = color_rainbow()
+ else if(colorC == "orange") //byond only knows 16 colors by name, and orange isn't one of them
+ color = COLOR_ORANGE
+ else
+ color = colorC
+
+/obj/structure/cable/proc/color_rainbow()
+ color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
+ return color
+
+/obj/structure/cable/deconstruct(disassembled = TRUE)
+ var/turf/T = get_turf(src)
+ if(usr)
+ investigate_log("was deconstructed by [key_name(usr, 1)] in [get_area(usr)]([T.x], [T.y], [T.z] - [ADMIN_JMP(T)])","wires")
+ if(!(flags & NODECONSTRUCT))
+ if(d1) // 0-X cables are 1 unit, X-X cables are 2 units long
+ new/obj/item/stack/cable_coil(T, 2, paramcolor = color)
+ else
+ new/obj/item/stack/cable_coil(T, 1, paramcolor = color)
+ qdel(src)
+
+/* ===POWERNET PROCS=== */
+/// Adds power demand to the powernet, machines should use this
+/obj/structure/cable/proc/add_power_demand(amount)
+ powernet?.power_demand += amount
+
+/// Gets surplus power available on this cables powernet, machines should use this
+/obj/structure/cable/proc/get_surplus()
+ return powernet ? powernet.calculate_surplus() : 0
+
+/// Gets power available (NOT EXTRA) on this cables powernet, machines should use this, engines should add power to the net with this proc
+/obj/structure/cable/proc/get_available_power()
+ return powernet ? powernet.available_power : 0
+
+/// Adds queued power demand to be met next process cycle, non_machines should use this
+/obj/structure/cable/proc/add_queued_power_demand(amount)
+ powernet?.queued_power_demand += amount
+
+/// Gets surplus power queued for next process cycle on this cables powernet, non_machines should use this
+/obj/structure/cable/proc/get_queued_surplus()
+ return powernet ? powernet.calculate_queued_surplus() : 0
+
+/// Gets available (NOT EXTRA) power queued for next process cycle on this cables powernet, non_machines should use this
+/obj/structure/cable/proc/get_queued_available_power()
+ return powernet ? powernet.queued_power_production : 0
+
+
+/* ===CABLE LAYING HELPERS=== */
+/// merge_connected_networks(), merge_connected_networks_on_turf(), and merge_diagonal_networks() all deal with merging
+/// cables' powernets together
+/*
+ * # merge_connected_networks()
+ *
+ * Check the turf in the next step in that direction to see if our new cable lines up perfectly with
+ * another cable and then merge their associated regional powernets.
+ *
+ * In technical terms, Wires can be merged when they face eachother and have perfectly opposite directions, i.e east and west or north and south
+ * To check mergeability, we flip our original direction because in perfectly opposite directions, the inverse of one is equal to the other
+ * if the cable we find on the next turf has atleast one direction equal to the inverse of our new cables direction, we know it connects
+*/
+/obj/structure/cable/proc/merge_connected_networks(direction)
+ if(d1 != direction && d2 != direction)
+ return //if the cable is not pointed in this direction, do nothing
+
+ //flip the direction, so we can check it against cables in the next turf over
+ var/flipped_direction = turn(direction, 180)
+ for(var/obj/structure/cable/C in get_step(src, direction))
+ if(src == C) // skip ourself
+ continue
+ if(C.d1 != flipped_direction && C.d2 != flipped_direction)
+ continue //no match! Continue the search
+ //if the matching cable somehow got no powernet, make him one (should not happen for cables)
+ if(!C.powernet)
+ var/datum/regional_powernet/new_powernet = new()
+ new_powernet.add_cable(C)
+ if(powernet) //if we already have a powernet, then merge the two powernets
+ merge_powernets(powernet, C.powernet)
+ else
+ C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet
+
+/*
+ * # merge_connected_networks_on_turf()
+ *
+ * This proc merges powernets with power machines & cables that share a turf
+ * first it merges powernets of any cables that share an exact direction and then it will attempt
+ * to connect every power machine in the turf to the powernet
+*/
+/obj/structure/cable/proc/merge_connected_networks_on_turf()
+ var/list/to_connect = list()
+
+ for(var/obj/object in loc)
+ //first let's add turf cables to our powernet
+ if(istype(object, /obj/structure/cable))
+ var/obj/structure/cable/C = object
+ if(C.d1 == d1 || C.d2 == d1 || C.d1 == d2 || C.d2 == d2) //only connected if they have a common direction
+ if(C.powernet == powernet)
+ continue
+ if(C.powernet)
+ merge_powernets(powernet, C.powernet)
+ else
+ powernet.add_cable(C) //the cable was powernetless, let's just add it to our powernet
+ //Now we'll check for APCs
+ else if(istype(object, /obj/machinery/power/apc))
+ var/obj/machinery/power/apc/N = object
+ if(!N.terminal)
+ continue // APC are connected through their terminal
+ if(N.terminal.powernet == powernet)
+ continue
+ to_connect += N.terminal //we'll connect the machines after all cables are merged
+ //then we'll connect machines on turf with a node cable is present
+ else if(istype(object, /obj/machinery/power)) //other power machines
+ var/obj/machinery/power/M = object
+ if(M.powernet == powernet)
+ continue
+ to_connect += M //we'll connect the machines after all cables are merged
+
+ //now that cables are done, let's connect found machines
+ for(var/obj/machinery/power/PM as anything in to_connect)
+ if(!PM.connect_to_network())
+ PM.disconnect_from_network() //if we somehow can't connect the machine to the new powernet, remove it from the old nonetheless
+
+/*
+ * # merge_diagonal_networks()
+ *
+ * handles powernet merging diagonally matching cables, proc only takes diagonal directions as params
+*/
+/obj/structure/cable/proc/merge_diagonal_networks(direction)
+ //search for and merge diagonally matching cables from the first direction component (north/south)
+ for(var/obj/structure/cable/C in get_step(src, direction & (NORTH|SOUTH)))
+ if(src == C) // skip ourself
+ continue
+ //we've got a diagonally matching cable
+ if(C.d1 == FLIP_DIR_VERTICALLY(direction) || C.d2 == FLIP_DIR_VERTICALLY(direction))
+ if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables)
+ var/datum/regional_powernet/new_powernet = new()
+ new_powernet.add_cable(C)
+ if(powernet) //if we already have a powernet, then merge the two powernets
+ merge_powernets(powernet, C.powernet)
+ else
+ C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet
+
+ //the same from the second direction component (east/west)
+ for(var/obj/structure/cable/C in get_step(src, direction & (EAST|WEST)))
+ if(src == C)
+ continue
+ if(C.d1 == FLIP_DIR_HORIZONTALLY(direction) || C.d2 == FLIP_DIR_HORIZONTALLY(direction)) //we've got a diagonally matching cable
+ if(!C.powernet) //if the matching cable somehow got no powernet, make him one (should not happen for cables)
+ var/datum/regional_powernet/new_powernet = new()
+ new_powernet.add_cable(C)
+ if(powernet) //if we already have a powernet, then merge the two powernets
+ merge_powernets(powernet, C.powernet)
+ else
+ C.powernet.add_cable(src) //else, we simply connect to the matching cable powernet
+
+
+/* ===Powernets handling helpers=== */
+/*
+ * # get_connections()
+ *
+ * Builds a list of cables in neighboring procs that form a cable connection with src and returns it
+*/
+/obj/structure/cable/proc/get_connections()
+ . = list() // this will be a list of all connected power objects
+ var/turf/T
+
+ //get matching cables from the first direction
+ if(d1) //if not a node cable
+ T = get_step(src, d1)
+ if(T)
+ . += T.power_list(src, turn(d1, 180)) //get adjacents matching cables
+ if(IS_DIR_DIAGONAL(d1)) //diagonal direction, must check the 4 possibles adjacents tiles
+ T = get_step(src, d1 & (NORTH|SOUTH)) // go north/south
+ if(T)
+ . += T.power_list(src, FLIP_DIR_VERTICALLY(d1)) //get diagonally matching cables
+ T = get_step(src, d1 & (EAST|WEST)) // go east/west
+ if(T)
+ . += T.power_list(src, FLIP_DIR_HORIZONTALLY(d1)) //get diagonally matching cables
+ T = get_turf(src)
+ . += T.power_list(src, d1) //get on turf matching cables
+
+ //do the same on the second direction (which can't be 0)
+ T = get_step(src, d2)
+ if(T)
+ . += T.power_list(src, turn(d2, 180)) //get adjacents matching cables
+
+ if(d2&(d2-1)) //diagonal direction, must check the 4 possibles adjacents tiles
+ T = get_step(src, d2 & (NORTH|SOUTH)) // go north/south
+ if(T)
+ . += T.power_list(src, FLIP_DIR_VERTICALLY(d1)) //get diagonally matching cables
+ T = get_step(src, d2 & (EAST|WEST)) // go east/west
+ if(T)
+ . += T.power_list(src, FLIP_DIR_HORIZONTALLY(d1)) //get diagonally matching cables
+ T = get_turf(src)
+ . += T.power_list(src, d2) //get on turf matching cables
+
+ return .
+
+//should be called after placing a cable which extends another cable, creating a "smooth" cable that no longer terminates in the centre of a turf.
+//needed as this can, unlike other placements, disconnect cables
+/obj/structure/cable/proc/denode()
+ var/turf/T1 = loc
+ if(!T1)
+ return
+
+ var/list/powerlist = T1.power_list(src, 0) //find the other cables that ended in the centre of the turf, with or without a powernet
+ if(length(powerlist))
+ var/datum/regional_powernet/PN = new()
+ propagate_network(powerlist[1], PN) //propagates the new powernet beginning at the source cable
+ if(PN.is_empty()) //can happen with machines made nodeless when smoothing cables
+ qdel(PN)
+
+// cut the cable's powernet at this cable and updates the powergrid
+/obj/structure/cable/proc/cut_cable_from_powernet(remove = TRUE)
+ var/turf/T1 = get_turf(src)
+ var/list/P_list
+ if(!T1)
+ return
+ if(d1) //if d1 is not a node
+ T1 = get_step(T1, d1)
+ P_list = T1.power_list(src, turn(d1, 180), cable_only = TRUE) // what adjacently joins on to cut cable...
+ P_list += T1.power_list(loc, d1, cable_only = TRUE) //... and on turf
+ if(!length(P_list))//if nothing in both list, then the cable was a lone cable, just delete it and its powernet
+ powernet.remove_cable(src)
+
+ for(var/obj/machinery/power/P in T1)//check if it was powering a machine
+ if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to
+ P.disconnect_from_network() //remove from current network (and delete powernet)
+ return
+
+ var/obj/O = P_list[1]
+ // remove the cut cable from its turf and powernet, so that it doesn't get count in propagate_network worklist
+ if(remove)
+ loc = null
+ powernet.remove_cable(src) //remove the cut cable from its powernet
+ // queue it to rebuild
+ SSmachines.deferred_powernet_rebuilds += O
+
+ // Disconnect machines connected to nodes
+ if(d1 == 0) // if we cut a node (O-X) cable
+ for(var/obj/machinery/power/P in T1)
+ if(!P.connect_to_network()) //can't find a node cable on a the turf to connect to
+ P.disconnect_from_network() //remove from current network
+
+// shock the user with probability prb
+/obj/structure/cable/proc/shock(mob/user, prb, siemens_coeff = 1)
+ if(!prob(prb))
+ return FALSE
+ if(electrocute_mob(user, powernet, src, siemens_coeff))
+ do_sparks(5, 1, src)
+ return TRUE
+ else
+ return FALSE
+
+/obj/structure/cable/singularity_pull(S, current_size)
+ ..()
+ if(current_size >= STAGE_FIVE)
+ deconstruct()
+
+// override, so telekinesis has no effect on a cable
+/obj/structure/cable/attack_tk(mob/user)
+ return
+
+/obj/structure/cable/yellow
+ color = COLOR_YELLOW
+
+/obj/structure/cable/green
+ color = COLOR_GREEN
+
+/obj/structure/cable/blue
+ color = COLOR_BLUE
+
+/obj/structure/cable/pink
+ color = COLOR_PINK
+
+/obj/structure/cable/orange
+ color = COLOR_ORANGE
+
+/obj/structure/cable/cyan
+ color = COLOR_CYAN
+
+/obj/structure/cable/white
+ color = COLOR_WHITE
+
+//
+// This ASCII art represents my brain after looking at cable
+// code for too long, half of this was written before I was even
+// in 3rd grade
+// ~~Sirryan
+//
+// _.-^^---....,,--
+// _-- --_
+// < >)
+// | KABOOM |
+// \._ _./
+// ```--. . , ; .--'''
+// | | |
+// .-=|| | |=-.
+// `-=#$%&%$#=-'
+// | ; :|
+// _____.,-#%&$@%#~,._____
+//
diff --git a/code/modules/power/cables/cable_coil.dm b/code/modules/power/cables/cable_coil.dm
new file mode 100644
index 00000000000..de3d010a327
--- /dev/null
+++ b/code/modules/power/cables/cable_coil.dm
@@ -0,0 +1,402 @@
+///////////////////////////////////////////////
+// The cable coil object, used for laying cable
+///////////////////////////////////////////////
+
+#define HEALPERCABLE 3
+#define MAXCABLEPERHEAL 8
+GLOBAL_LIST_INIT(cable_coil_recipes, list (new/datum/stack_recipe/cable_restraints("cable restraints", /obj/item/restraints/handcuffs/cable, 15)))
+
+/obj/item/stack/cable_coil
+ name = "cable coil"
+ singular_name = "cable"
+ icon = 'icons/obj/power.dmi'
+ icon_state = "coil"
+ item_state = "coil_red"
+ belt_icon = "cable_coil"
+ amount = MAXCOIL
+ max_amount = MAXCOIL
+ merge_type = /obj/item/stack/cable_coil // This is here to let its children merge between themselves
+ color = COLOR_RED
+ throwforce = 10
+ w_class = WEIGHT_CLASS_SMALL
+ throw_speed = 2
+ throw_range = 5
+ materials = list(MAT_METAL = 15, MAT_GLASS = 10)
+ flags = CONDUCT
+ slot_flags = SLOT_BELT
+ item_state = "coil"
+ attack_verb = list("whipped", "lashed", "disciplined", "flogged")
+ usesound = 'sound/items/deconstruct.ogg'
+ toolspeed = 1
+
+/obj/item/stack/cable_coil/New(location, length = MAXCOIL, paramcolor = null)
+ . = ..()
+ if(paramcolor)
+ color = paramcolor
+
+/obj/item/stack/cable_coil/Initialize(mapload)
+ . = ..()
+ pixel_x = rand(-2,2)
+ pixel_y = rand(-2,2)
+ update_icon()
+ recipes = GLOB.cable_coil_recipes
+ update_wclass()
+
+/obj/item/stack/cable_coil/update_name()
+ . = ..()
+ if(amount > 2)
+ name = "cable coil"
+ else
+ name = "cable piece"
+
+/obj/item/stack/cable_coil/update_icon_state()
+ if(!color)
+ color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_ORANGE, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
+ if(amount == 1)
+ icon_state = "coil1"
+ else if(amount == 2)
+ icon_state = "coil2"
+ else
+ icon_state = "coil"
+
+/obj/item/stack/cable_coil/proc/update_wclass()
+ if(amount == 1)
+ w_class = WEIGHT_CLASS_TINY
+ else
+ w_class = WEIGHT_CLASS_SMALL
+
+/obj/item/stack/cable_coil/examine(mob/user)
+ . = ..()
+ if(!in_range(user, src) || is_cyborg)
+ return
+ if(get_amount() == 1)
+ . += "A short piece of power cable."
+ else if(get_amount() == 2)
+ . += "A piece of power cable."
+ else
+ . += "A coil of power cables."
+
+//you can use wires to heal robotics
+/obj/item/stack/cable_coil/attack(mob/M, mob/user)
+ if(!ishuman(M))
+ return ..()
+ var/mob/living/carbon/human/H = M
+ var/obj/item/organ/external/S = H.bodyparts_by_name[user.zone_selected]
+
+ if(!S?.is_robotic() || user.a_intent != INTENT_HELP || S.open == ORGAN_SYNTHETIC_OPEN)
+ return ..()
+ if(S.burn_dam > ROBOLIMB_SELF_REPAIR_CAP)
+ to_chat(user, "The damage is far too severe to patch over externally.")
+ return
+ if(!S.burn_dam)
+ to_chat(user, "Nothing to fix!")
+ return
+ if(H == user)
+ if(!do_mob(user, H, 10))
+ return FALSE
+ var/cable_used = 0
+ var/childlist
+ if(!isnull(S.children))
+ childlist = S.children.Copy()
+ var/parenthealed = FALSE
+ while(cable_used <= MAXCABLEPERHEAL && amount >= 1)
+ var/obj/item/organ/external/E
+ if(S.burn_dam)
+ E = S
+ else if(LAZYLEN(childlist))
+ E = pick_n_take(childlist)
+ if(!E.burn_dam || E.burn_dam >= ROBOLIMB_SELF_REPAIR_CAP || !E.is_robotic())
+ continue
+ else if(S.parent && !parenthealed)
+ E = S.parent
+ parenthealed = TRUE
+ if(!E.burn_dam || E.burn_dam >= ROBOLIMB_SELF_REPAIR_CAP || !E.is_robotic())
+ break
+ else
+ break
+ while(cable_used <= MAXCABLEPERHEAL && E.burn_dam && amount >= 1)
+ use(1)
+ cable_used += 1
+ E.heal_damage(0, HEALPERCABLE, 0, TRUE)
+ H.UpdateDamageIcon()
+ user.visible_message("[user] repairs some burn damage on [M]'s [E.name] with [src].")
+ return TRUE
+
+/obj/item/stack/cable_coil/split()
+ var/obj/item/stack/cable_coil/C = ..()
+ C.color = color
+ return C
+
+// Items usable on a cable coil :
+// - Wirecutters : cut them duh !
+// - Cable coil : merge cables
+/obj/item/stack/cable_coil/attackby(obj/item/W, mob/user)
+ . = ..()
+ if(istype(W, /obj/item/stack/cable_coil))
+ var/obj/item/stack/cable_coil/C = W
+ // Cable merging is handled by parent proc
+ if(C.get_amount() >= MAXCOIL)
+ to_chat(user, "The coil is as long as it will get.")
+ return
+ if((C.get_amount() + get_amount() <= MAXCOIL))
+ to_chat(user, "You join the cable coils together.")
+ return
+ else
+ to_chat(user, "You transfer [get_amount_transferred()] length\s of cable from one coil to the other.")
+ return
+
+ if(istype(W, /obj/item/toy/crayon))
+ var/obj/item/toy/crayon/C = W
+ cable_color(C.colourName)
+
+///////////////////////////////////////////////
+// Cable laying procedures
+//////////////////////////////////////////////
+
+/obj/item/stack/cable_coil/proc/get_new_cable(location)
+ var/obj/structure/cable/C = new(location)
+ C.cable_color(color)
+
+ return C
+
+/// called when cable_coil is clicked on a turf/simulated/floor
+/obj/item/stack/cable_coil/proc/place_turf(turf/T, mob/user, cable_direction)
+ if(!isturf(user.loc))
+ return
+ if(!isturf(T) || T.intact || !T.can_have_cabling())
+ to_chat(user, "You can only lay cables on catwalks and plating!")
+ return
+ if(get_amount() < 1) // Out of cable
+ to_chat(user, "There is no cable left!")
+ return
+ if(get_dist(T, user) > 1) // Too far
+ to_chat(user, "You can't lay cable at a place that far away!")
+ return
+
+ if(!cable_direction) //If we weren't given a direction, come up with one! (Called as null from catwalk.dm and floor.dm)
+ if(user.loc == T)
+ cable_direction = user.dir //If laying on the tile we're on, lay in the direction we're facing
+ else
+ cable_direction = get_dir(T, user) //otherwise get direction from us to the turf we've clicked
+
+ for(var/obj/structure/cable/LC in T)
+ if(LC.d2 == cable_direction && LC.d1 == NO_DIRECTION) //there's already a cable here that would be exactly what we just placed!
+ to_chat(user, "There's already a cable at that position!")
+ return
+
+ var/obj/structure/cable/C = get_new_cable(T)
+
+ //set up the new cable
+ C.d1 = NO_DIRECTION //it's a O-X node cable
+ C.d2 = cable_direction
+ C.add_fingerprint(user)
+ C.update_icon()
+
+ //create a new powernet with the cable, if needed it will be merged later
+ var/datum/regional_powernet/new_powernet = new()
+ new_powernet.add_cable(C)
+
+ C.merge_connected_networks(C.d2) //merge the powernet with adjacents powernets
+ C.merge_connected_networks_on_turf() //merge the powernet with on turf powernets
+
+ if(IS_DIR_DIAGONAL(C.d2))// if the cable is layed diagonally, check the others 2 possible directions
+ C.merge_diagonal_networks(C.d2)
+
+ use(1)
+
+ if(C.shock(user, 50))
+ if(prob(50)) //fail
+ new /obj/item/stack/cable_coil(get_turf(C), 1, paramcolor = C.color)
+ C.deconstruct()
+
+ return C
+
+/// called when cable_coil is click on an installed obj/cable or click on a turf that already contains a "node" cable
+/obj/item/stack/cable_coil/proc/cable_join(obj/structure/cable/C, mob/user)
+ var/turf/U = user.loc
+ if(!isturf(U))
+ return
+
+ var/turf/T = get_turf(C)
+ // sanity checks, also stop use interacting with T-scanner revealed cable
+ if(!isturf(T) || T.intact || T.transparent_floor)
+ return
+ // make sure it's close enough
+ if(get_dist(C, user) > 1)
+ to_chat(user, "You can't lay cable at a place that far away!")
+ return
+ //if clicked on the turf we're standing on, try to put a cable in the direction we're facing
+ if(U == T)
+ place_turf(T,user)
+ return
+
+ var/new_direction = get_dir(C, user)
+
+ // one end of the clicked cable is pointing towards us
+ if(C.d1 == new_direction || C.d2 == new_direction)
+ if(U.intact || U.transparent_floor) // can't place a cable if the floor is complete
+ to_chat(user, "You can't lay cable there unless the floor tiles are removed!")
+ return
+ else
+ // cable is pointing at us, we're standing on an open tile
+ // so create a stub pointing at the clicked cable on our tile
+
+ var/direction_flipped = turn(new_direction, 180) // the opposite direction
+
+ for(var/obj/structure/cable/LC in U) // check to make sure there's not a cable there already
+ if(LC.d1 == direction_flipped || LC.d2 == direction_flipped)
+ to_chat(user, "There's already a cable at that position!")
+ return
+
+ var/obj/structure/cable/NC = get_new_cable (U)
+
+ NC.d1 = 0
+ NC.d2 = direction_flipped
+ NC.add_fingerprint(user)
+ NC.update_icon()
+
+ //create a new powernet with the cable, if needed it will be merged later
+ var/datum/regional_powernet/newPN = new()
+ newPN.add_cable(NC)
+
+ NC.merge_connected_networks(NC.d2) //merge the powernet with adjacents powernets
+ NC.merge_connected_networks_on_turf() //merge the powernet with on turf powernets
+
+
+ if(IS_DIR_DIAGONAL(NC.d2)) // if the cable is layed diagonally, check the others 2 possible directions
+ NC.merge_diagonal_networks(NC.d2)
+
+ use(1)
+
+ if(NC.shock(user, 50))
+ if(prob(50)) //fail
+ NC.deconstruct()
+ return
+
+ // exisiting cable doesn't point at our position, so see if it's a stub
+ else if(C.d1 == 0)
+ // if so, make it a full cable pointing from it's old direction to our new_direction
+ var/nd1 = C.d2 // these will be the new directions
+ var/nd2 = new_direction
+
+
+ if(nd1 > nd2) // swap directions to match icons/states
+ nd1 = new_direction
+ nd2 = C.d2
+
+
+ for(var/obj/structure/cable/LC in T) // check to make sure there's no matching cable
+ if(LC == C) // skip the cable we're interacting with
+ continue
+ if((LC.d1 == nd1 && LC.d2 == nd2) || (LC.d1 == nd2 && LC.d2 == nd1) ) // make sure no cable matches either direction
+ to_chat(user, "There's already a cable at that position!")
+ return
+
+
+ C.cable_color(color)
+
+ C.d1 = nd1
+ C.d2 = nd2
+
+ C.add_fingerprint()
+ C.update_icon()
+
+
+ C.merge_connected_networks(C.d1) //merge the powernets...
+ C.merge_connected_networks(C.d2) //...in the two new cable directions
+ C.merge_connected_networks_on_turf()
+
+ if(C.d1 & (C.d1 - 1))// if the cable is layed diagonally, check the others 2 possible directions
+ C.merge_connected_networks(C.d1)
+
+ if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions
+ C.merge_connected_networks(C.d2)
+
+ use(1)
+
+ if(C.shock(user, 50))
+ if(prob(50)) //fail
+ C.deconstruct()
+ return
+
+ C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets.
+ return
+
+//////////////////////////////
+// Misc.
+/////////////////////////////
+
+/obj/item/stack/cable_coil/proc/cable_color(colorC)
+ if(!colorC)
+ color = COLOR_RED
+ else if(colorC == "rainbow")
+ color = color_rainbow()
+ else if(colorC == "orange") //byond only knows 16 colors by name, and orange isn't one of them
+ color = COLOR_ORANGE
+ else
+ color = colorC
+
+/obj/item/stack/cable_coil/suicide_act(mob/user)
+ if(locate(/obj/structure/chair/stool) in user.loc)
+ user.visible_message("[user] is making a noose with [src]! It looks like [user.p_theyre()] trying to commit suicide.")
+ else
+ user.visible_message("[user] is strangling [user.p_themselves()] with [src]! It looks like [user.p_theyre()] trying to commit suicide.")
+ return OXYLOSS
+
+/obj/item/stack/cable_coil/proc/color_rainbow()
+ color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN)
+ return color
+
+/obj/item/stack/cable_coil/five/New(loc, new_amount = 5, merge = TRUE, paramcolor = null)
+ ..()
+
+/obj/item/stack/cable_coil/yellow
+ color = COLOR_YELLOW
+
+/obj/item/stack/cable_coil/blue
+ color = COLOR_BLUE
+
+/obj/item/stack/cable_coil/green
+ color = COLOR_GREEN
+
+/obj/item/stack/cable_coil/pink
+ color = COLOR_PINK
+
+/obj/item/stack/cable_coil/orange
+ color = COLOR_ORANGE
+
+/obj/item/stack/cable_coil/cyan
+ color = COLOR_CYAN
+
+/obj/item/stack/cable_coil/white
+ color = COLOR_WHITE
+
+/obj/item/stack/cable_coil/random/New()
+ color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN, COLOR_ORANGE)
+ ..()
+
+/obj/item/stack/cable_coil/cut
+ item_state = "coil2"
+
+/obj/item/stack/cable_coil/cut/Initialize(mapload)
+ . = ..()
+ src.amount = rand(1,2)
+ pixel_x = rand(-2,2)
+ pixel_y = rand(-2,2)
+ update_appearance(UPDATE_NAME|UPDATE_ICON_STATE)
+ update_wclass()
+
+/obj/item/stack/cable_coil/cyborg
+ energy_type = /datum/robot_energy_storage/cable
+ is_cyborg = TRUE
+
+/obj/item/stack/cable_coil/cyborg/update_icon_state()
+ return // icon_state should always be a full cable
+
+/obj/item/stack/cable_coil/cyborg/attack_self(mob/user)
+ var/cablecolor = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white")
+ color = cablecolor
+ update_icon()
+
+#undef HEALPERCABLE
+#undef MAXCABLEPERHEAL
diff --git a/code/modules/power/terminal.dm b/code/modules/power/cables/terminal.dm
similarity index 100%
rename from code/modules/power/terminal.dm
rename to code/modules/power/cables/terminal.dm
diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/engines/singularity/collector.dm
similarity index 99%
rename from code/modules/power/singularity/collector.dm
rename to code/modules/power/engines/singularity/collector.dm
index f7a66dbba5e..3b7f8105cf8 100644
--- a/code/modules/power/singularity/collector.dm
+++ b/code/modules/power/engines/singularity/collector.dm
@@ -44,7 +44,7 @@ GLOBAL_LIST_EMPTY(rad_collectors)
loaded_tank.air_contents.toxins -= gasdrained
var/power_produced = RAD_COLLECTOR_OUTPUT
- add_avail(power_produced)
+ produce_direct_power(power_produced)
stored_energy -= power_produced
diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/engines/singularity/containment_field.dm
similarity index 100%
rename from code/modules/power/singularity/containment_field.dm
rename to code/modules/power/engines/singularity/containment_field.dm
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/engines/singularity/emitter.dm
similarity index 98%
rename from code/modules/power/singularity/emitter.dm
rename to code/modules/power/engines/singularity/emitter.dm
index 915cab25b51..eea4120f2b9 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/engines/singularity/emitter.dm
@@ -98,7 +98,7 @@
return ..()
/obj/machinery/power/emitter/update_icon_state()
- if(active && powernet && avail(active_power_consumption))
+ if(active && powernet && get_available_power())
icon_state = "emitter_+a"
else
icon_state = "emitter"
@@ -251,8 +251,8 @@
update_icon()
return
- if(!active_power_consumption || surplus() >= active_power_consumption)
- add_load(active_power_consumption)
+ if(!active_power_consumption || get_surplus() >= active_power_consumption)
+ consume_direct_power(active_power_consumption)
if(!powered)
powered = TRUE
update_icon()
diff --git a/code/modules/power/singularity/field_generator.dm b/code/modules/power/engines/singularity/field_generator.dm
similarity index 100%
rename from code/modules/power/singularity/field_generator.dm
rename to code/modules/power/engines/singularity/field_generator.dm
diff --git a/code/modules/power/singularity/investigate.dm b/code/modules/power/engines/singularity/investigate.dm
similarity index 100%
rename from code/modules/power/singularity/investigate.dm
rename to code/modules/power/engines/singularity/investigate.dm
diff --git a/code/modules/power/singularity/narsie.dm b/code/modules/power/engines/singularity/narsie.dm
similarity index 100%
rename from code/modules/power/singularity/narsie.dm
rename to code/modules/power/engines/singularity/narsie.dm
diff --git a/code/modules/power/singularity/particle_accelerator/particle.dm b/code/modules/power/engines/singularity/particle_accelerator/particle.dm
similarity index 100%
rename from code/modules/power/singularity/particle_accelerator/particle.dm
rename to code/modules/power/engines/singularity/particle_accelerator/particle.dm
diff --git a/code/modules/power/singularity/particle_accelerator/particle_accelerator.dm b/code/modules/power/engines/singularity/particle_accelerator/particle_accelerator.dm
similarity index 100%
rename from code/modules/power/singularity/particle_accelerator/particle_accelerator.dm
rename to code/modules/power/engines/singularity/particle_accelerator/particle_accelerator.dm
diff --git a/code/modules/power/singularity/particle_accelerator/particle_chamber.dm b/code/modules/power/engines/singularity/particle_accelerator/particle_chamber.dm
similarity index 100%
rename from code/modules/power/singularity/particle_accelerator/particle_chamber.dm
rename to code/modules/power/engines/singularity/particle_accelerator/particle_chamber.dm
diff --git a/code/modules/power/singularity/particle_accelerator/particle_control.dm b/code/modules/power/engines/singularity/particle_accelerator/particle_control.dm
similarity index 100%
rename from code/modules/power/singularity/particle_accelerator/particle_control.dm
rename to code/modules/power/engines/singularity/particle_accelerator/particle_control.dm
diff --git a/code/modules/power/singularity/particle_accelerator/particle_emitter.dm b/code/modules/power/engines/singularity/particle_accelerator/particle_emitter.dm
similarity index 100%
rename from code/modules/power/singularity/particle_accelerator/particle_emitter.dm
rename to code/modules/power/engines/singularity/particle_accelerator/particle_emitter.dm
diff --git a/code/modules/power/singularity/particle_accelerator/particle_power.dm b/code/modules/power/engines/singularity/particle_accelerator/particle_power.dm
similarity index 100%
rename from code/modules/power/singularity/particle_accelerator/particle_power.dm
rename to code/modules/power/engines/singularity/particle_accelerator/particle_power.dm
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/engines/singularity/singularity.dm
similarity index 100%
rename from code/modules/power/singularity/singularity.dm
rename to code/modules/power/engines/singularity/singularity.dm
diff --git a/code/modules/power/singularity/singulogen.dm b/code/modules/power/engines/singularity/singulogen.dm
similarity index 100%
rename from code/modules/power/singularity/singulogen.dm
rename to code/modules/power/engines/singularity/singulogen.dm
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/engines/supermatter/supermatter.dm
similarity index 100%
rename from code/modules/power/supermatter/supermatter.dm
rename to code/modules/power/engines/supermatter/supermatter.dm
diff --git a/code/modules/power/tesla/coil.dm b/code/modules/power/engines/tesla/coil.dm
similarity index 94%
rename from code/modules/power/tesla/coil.dm
rename to code/modules/power/engines/tesla/coil.dm
index 47127baeef1..2204e543f33 100644
--- a/code/modules/power/tesla/coil.dm
+++ b/code/modules/power/engines/tesla/coil.dm
@@ -97,7 +97,7 @@
if(zap_flags & ZAP_GENERATES_POWER) //I don't want no tesla revolver making 8GW you hear
return power / 2
var/power_produced = powernet ? power * input_power_multiplier : power
- add_avail(power_produced)
+ produce_direct_power(power_produced)
flick("coilhit", src)
playsound(loc, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
return power - power_produced //You get back the amount we didn't use
@@ -108,9 +108,9 @@
if((last_zap + zap_cooldown) > world.time || !powernet)
return FALSE
last_zap = world.time
- var/power = (powernet.avail) * 0.2 * input_power_multiplier //Always always always use more then you output for the love of god
- power = min(surplus(), power) //Take the smaller of the two
- add_load(power)
+ var/power = (powernet.available_power) * 0.2 * input_power_multiplier //Always always always use more then you output for the love of god
+ power = min(get_surplus(), power) //Take the smaller of the two
+ consume_direct_power(power)
playsound(loc, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
tesla_zap(src, 10, power, zap_flags)
zap_buckle_check(power)
diff --git a/code/modules/power/tesla/energy_ball.dm b/code/modules/power/engines/tesla/energy_ball.dm
similarity index 100%
rename from code/modules/power/tesla/energy_ball.dm
rename to code/modules/power/engines/tesla/energy_ball.dm
diff --git a/code/modules/power/tesla/teslagen.dm b/code/modules/power/engines/tesla/generator.dm
similarity index 100%
rename from code/modules/power/tesla/teslagen.dm
rename to code/modules/power/engines/tesla/generator.dm
diff --git a/code/modules/power/engines/tesla/teslagen.dm b/code/modules/power/engines/tesla/teslagen.dm
new file mode 100644
index 00000000000..260f4128bcf
--- /dev/null
+++ b/code/modules/power/engines/tesla/teslagen.dm
@@ -0,0 +1,10 @@
+/obj/machinery/the_singularitygen/tesla
+ name = "energy ball generator"
+ desc = "Makes the wardenclyffe look like a child's plaything when shot with a particle accelerator."
+ icon = 'icons/obj/tesla_engine/tesla_generator.dmi'
+ icon_state = "TheSingGen"
+ creation_type = /obj/singularity/energy_ball
+
+/obj/machinery/the_singularitygen/tesla/zap_act(power, zap_flags)
+ if(zap_flags & ZAP_MACHINE_EXPLOSIVE)
+ energy += power
diff --git a/code/modules/power/port_gen.dm b/code/modules/power/generators/portable generators/pacman.dm
similarity index 72%
rename from code/modules/power/port_gen.dm
rename to code/modules/power/generators/portable generators/pacman.dm
index 14c5d70f7c0..c790e6ef6bf 100644
--- a/code/modules/power/port_gen.dm
+++ b/code/modules/power/generators/portable generators/pacman.dm
@@ -1,89 +1,7 @@
#define SHEET_VOLUME 1000 //cm3
-
-//Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power).
-/obj/machinery/power/port_gen
- name = "Placeholder Generator" //seriously, don't use this. It can't be anchored without VV magic.
- desc = "A portable generator for emergency backup power"
- icon = 'icons/obj/power.dmi'
- icon_state = "portgen0_0"
- density = TRUE
- anchored = FALSE
-
- var/active = FALSE
- var/power_gen = 5000
- var/power_output = 1
- var/base_icon = "portgen0"
-
-/obj/machinery/power/port_gen/proc/IsBroken()
- return (stat & (BROKEN|EMPED))
-
-/obj/machinery/power/port_gen/proc/HasFuel() //Placeholder for fuel check.
- return 1
-
-/obj/machinery/power/port_gen/proc/UseFuel() //Placeholder for fuel use.
- return
-
-/obj/machinery/power/port_gen/proc/DropFuel()
- return
-
-/obj/machinery/power/port_gen/proc/handleInactive()
- return
-
-/obj/machinery/power/port_gen/update_icon_state()
- icon_state = "[base_icon]_[active]"
-
-/obj/machinery/power/port_gen/process()
- if(active && HasFuel() && !IsBroken() && anchored && powernet)
- add_avail(power_gen * power_output)
- UseFuel()
- else
- active = FALSE
- handleInactive()
- update_icon()
-
-/obj/machinery/power/has_power()
- return TRUE //doesn't require an external power source
-
-/obj/machinery/power/port_gen/attack_hand(mob/user as mob)
- if(..())
- return
- if(!anchored)
- return
-
-/obj/machinery/power/port_gen/examine(mob/user)
- . = ..()
- if(!in_range(user, src))
- if(active)
- . += "The generator is on."
- else
- . += "The generator is off."
-
-/obj/machinery/power/port_gen/emp_act(severity)
- var/duration = 6000 //ten minutes
- switch(severity)
- if(1)
- stat &= BROKEN
- if(prob(75)) explode()
- if(2)
- if(prob(25)) stat &= BROKEN
- if(prob(10)) explode()
- if(3)
- if(prob(10)) stat &= BROKEN
- duration = 300
-
- stat |= EMPED
- if(duration)
- spawn(duration)
- stat &= ~EMPED
-
-/obj/machinery/power/port_gen/proc/explode()
- explosion(src.loc, -1, 3, 5, -1)
- qdel(src)
-
#define TEMPERATURE_DIVISOR 40
#define TEMPERATURE_CHANGE_MAX 20
-//A power generator that runs on solid plasma sheets.
/obj/machinery/power/port_gen/pacman
name = "\improper P.A.C.M.A.N.-type Portable Generator"
desc = "A power generator that runs on solid plasma sheets. Rated for 80 kW max safe output."
@@ -98,18 +16,28 @@
temperature_gain and max_temperature are set so that the max safe power level is 4.
Setting to 5 or higher can only be done temporarily before the generator overheats.
*/
- power_gen = 20000 //Watts output per power_output level
- var/max_power_output = 5 //The maximum power setting without emagging.
- var/max_safe_output = 4 // For UI use, maximal output that won't cause overheat.
- var/time_per_sheet = 96 //fuel efficiency - how long 1 sheet lasts at power level 1
- var/max_sheets = 100 //max capacity of the hopper
- var/max_temperature = 300 //max temperature before overheating increases
- var/temperature_gain = 50 //how much the temperature increases per power output level, in degrees per level
+ power_gen = 20000 //Watts output per power_output level
+ ///The maximum power setting without emagging.
+ var/max_power_output = 5
+ /// For UI use, maximal output that won't cause overheat.
+ var/max_safe_output = 4
+ /// fuel efficiency - how long 1 sheet lasts at power level 1
+ var/time_per_sheet = 96
+ /// max capacity of the hopper
+ var/max_sheets = 100
+ /// max temperature before overheating increases
+ var/max_temperature = 300
+ /// how much the temperature increases per power output level, in degrees per level
+ var/temperature_gain = 50
- var/sheets = 0 //How many sheets of material are loaded in the generator
- var/sheet_left = 0 //How much is left of the current sheet
- var/temperature = 0 //The current temperature
- var/overheating = 0 //if this gets high enough the generator explodes
+ /// How many sheets of material are loaded in the generator
+ var/sheets = 0
+ /// How much is left of the current sheet
+ var/sheet_left = 0
+ /// The current temperature
+ var/temperature = 0
+ /// if this gets high enough the generator explodes
+ var/overheating = 0
/obj/machinery/power/port_gen/pacman/Initialize(mapload)
. = ..()
@@ -137,7 +65,7 @@
RefreshParts()
/obj/machinery/power/port_gen/pacman/Destroy()
- DropFuel()
+ drop_fuel()
return ..()
/obj/machinery/power/port_gen/pacman/RefreshParts()
@@ -154,31 +82,31 @@
. = ..()
. += "\The [src] appears to be producing [power_gen*power_output] W."
. += "There [sheets == 1 ? "is" : "are"] [sheets] sheet\s left in the hopper."
- if(IsBroken())
+ if(is_broken())
. += "\The [src] seems to have broken down."
if(overheating)
. += "\The [src] is overheating!"
-/obj/machinery/power/port_gen/pacman/HasFuel()
+/obj/machinery/power/port_gen/pacman/has_fuel()
var/needed_sheets = power_output / time_per_sheet
if(sheets >= needed_sheets - sheet_left)
- return 1
- return 0
+ return TRUE
+ return FALSE
//Removes one stack's worth of material from the generator.
-/obj/machinery/power/port_gen/pacman/DropFuel()
- if(sheets)
- var/obj/item/stack/sheet/mineral/S = new sheet_path(loc)
- var/amount = min(sheets, S.max_amount)
- S.amount = amount
- sheets -= amount
-
-/obj/machinery/power/port_gen/pacman/UseFuel()
+/obj/machinery/power/port_gen/pacman/drop_fuel()
+ if(!sheets)
+ return
+ var/obj/item/stack/sheet/mineral/S = new sheet_path(loc)
+ var/amount = min(sheets, S.max_amount)
+ S.amount = amount
+ sheets -= amount
+/obj/machinery/power/port_gen/pacman/use_fuel()
//how much material are we using this iteration?
var/needed_sheets = power_output / time_per_sheet
- //HasFuel() should guarantee us that there is enough fuel left, so no need to check that
+ //has_fuel() should guarantee us that there is enough fuel left, so no need to check that
//the only thing we need to worry about is if we are going to rollover to the next sheet
if(needed_sheets > sheet_left)
sheets--
@@ -199,19 +127,19 @@
*/
var/datum/gas_mixture/environment = loc.return_air()
if(environment)
- var/ratio = min(environment.return_pressure()/ONE_ATMOSPHERE, 1)
+ var/ratio = min(environment.return_pressure() / ONE_ATMOSPHERE, 1)
var/ambient = environment.temperature - T20C
- lower_limit += ambient*ratio
- upper_limit += ambient*ratio
+ lower_limit += ambient * ratio
+ upper_limit += ambient * ratio
- var/average = (upper_limit + lower_limit)/2
+ var/average = (upper_limit + lower_limit) / 2
//calculate the temperature increase
var/bias = 0
if(temperature < lower_limit)
- bias = min(round((average - temperature)/TEMPERATURE_DIVISOR, 1), TEMPERATURE_CHANGE_MAX)
+ bias = min(round((average - temperature) / TEMPERATURE_DIVISOR, 1), TEMPERATURE_CHANGE_MAX)
else if(temperature > upper_limit)
- bias = max(round((temperature - average)/TEMPERATURE_DIVISOR, 1), -TEMPERATURE_CHANGE_MAX)
+ bias = max(round((temperature - average) / TEMPERATURE_DIVISOR, 1), -TEMPERATURE_CHANGE_MAX)
//limit temperature increase so that it cannot raise temperature above upper_limit,
//or if it is already above upper_limit, limit the increase to 0.
@@ -224,7 +152,7 @@
else if(overheating > 0)
overheating--
-/obj/machinery/power/port_gen/pacman/handleInactive()
+/obj/machinery/power/port_gen/pacman/handle_inactive()
var/cooling_temperature = 20
var/datum/gas_mixture/environment = loc.return_air()
if(environment)
@@ -249,14 +177,6 @@
explode()
/obj/machinery/power/port_gen/pacman/explode()
- //Vapourize all the plasma
- //When ground up in a grinder, 1 sheet produces 20 u of plasma -- Chemistry-Machinery.dm
- //1 mol = 10 u? I dunno. 1 mol of carbon is definitely bigger than a pill
- /*var/plasma = (sheets+sheet_left)*20
- var/datum/gas_mixture/environment = loc.return_air()
- if(environment)
- environment.adjust_gas("plasma", plasma/10, temperature + T0C)*/
-
sheets = 0
sheet_left = 0
..()
@@ -267,7 +187,7 @@
if(!emagged)
emagged = TRUE
- return 1
+ return TRUE
/obj/machinery/power/port_gen/pacman/attackby(obj/item/O as obj, mob/user as mob)
if(istype(O, sheet_path))
@@ -343,7 +263,7 @@
data["is_ai"] = FALSE
data["anchored"] = anchored
- data["broken"] = IsBroken()
+ data["broken"] = is_broken()
data["output_set"] = power_output
data["output_max"] = max_power_output
data["output_safe"] = max_safe_output
@@ -355,7 +275,7 @@
data["fuel_cap"] = round(max_sheets * SHEET_VOLUME, 0.1)
data["fuel_usage"] = active ? round((power_output / time_per_sheet) * SHEET_VOLUME) : 0
data["fuel_type"] = sheet_name
- data["has_fuel"] = HasFuel()
+ data["has_fuel"] = has_fuel()
return data
@@ -374,7 +294,7 @@
active = !active
update_icon()
if("eject_fuel")
- DropFuel()
+ drop_fuel()
if("change_power")
var/newPower = text2num(params["change_power"])
if(newPower)
@@ -401,7 +321,7 @@
component_parts += new board_path(null)
RefreshParts()
-/obj/machinery/power/port_gen/pacman/super/UseFuel()
+/obj/machinery/power/port_gen/pacman/super/use_fuel()
//produces a tiny amount of radiation when in use
if(prob(2 * power_output))
radiation_pulse(get_turf(src), 50)
@@ -410,7 +330,7 @@
/obj/machinery/power/port_gen/pacman/super/explode()
//a nice burst of radiation
radiation_pulse(get_turf(src), 500, 2)
- explosion(src.loc, 3, 3, 5, 3)
+ explosion(loc, 3, 3, 5, 3)
qdel(src)
/obj/machinery/power/port_gen/pacman/mrs
@@ -421,7 +341,6 @@
sheet_path = /obj/item/stack/sheet/mineral/diamond
sheet_name = "Diamond Sheets"
- //I don't think tritium has any other use, so we might as well make this rewarding for players
//max safe power output (power level = 8) is 200 kW and lasts for 1 hour - 3 or 4 of these could power the station
power_gen = 25000 //watts
max_power_output = 10
@@ -444,5 +363,5 @@
/obj/machinery/power/port_gen/pacman/mrs/explode()
//no special effects, but the explosion is pretty big (same as a supermatter shard).
- explosion(src.loc, 3, 6, 12, 16, 1)
+ explosion(loc, 3, 6, 12, 16, 1)
qdel(src)
diff --git a/code/modules/power/generators/portable generators/port_gen.dm b/code/modules/power/generators/portable generators/port_gen.dm
new file mode 100644
index 00000000000..6c34b16da86
--- /dev/null
+++ b/code/modules/power/generators/portable generators/port_gen.dm
@@ -0,0 +1,80 @@
+
+
+//Baseline portable generator. Has all the default handling. Not intended to be used on it's own (since it generates unlimited power).
+/obj/machinery/power/port_gen
+ name = "Placeholder Generator" //seriously, don't use this. It can't be anchored without VV magic.
+ desc = "A portable generator for emergency backup power."
+ icon = 'icons/obj/power.dmi'
+ icon_state = "portgen0_0"
+ density = TRUE
+ anchored = FALSE
+
+ var/active = FALSE
+ var/power_gen = 5000
+ var/power_output = 1
+ var/base_icon = "portgen0"
+
+/obj/machinery/power/port_gen/examine(mob/user)
+ . = ..()
+ if(!in_range(user, src))
+ if(active)
+ . += "The generator is on."
+ else
+ . += "The generator is off."
+
+/obj/machinery/power/port_gen/process()
+ if(anchored && powernet && active && has_fuel() && !is_broken())
+ produce_direct_power(power_gen * power_output)
+ use_fuel()
+ return
+ active = FALSE
+ handle_inactive()
+ update_icon()
+
+/obj/machinery/power/port_gen/proc/is_broken()
+ return (stat & (BROKEN|EMPED))
+
+/obj/machinery/power/port_gen/proc/has_fuel() //Placeholder for fuel check.
+ return TRUE
+
+/obj/machinery/power/port_gen/proc/use_fuel() //Placeholder for fuel use.
+ return
+
+/obj/machinery/power/port_gen/proc/drop_fuel()
+ return
+
+/obj/machinery/power/port_gen/proc/handle_inactive()
+ return
+
+/obj/machinery/power/port_gen/update_icon_state()
+ icon_state = "[base_icon]_[active]"
+
+/obj/machinery/power/has_power()
+ return TRUE //doesn't require an external power source
+
+/obj/machinery/power/port_gen/emp_act(severity)
+ var/duration = 10 MINUTES
+ switch(severity)
+ if(1)
+ stat &= BROKEN
+ if(prob(75))
+ explode()
+ if(2)
+ if(prob(25))
+ stat &= BROKEN
+ if(prob(10))
+ explode()
+ if(3)
+ if(prob(10))
+ stat &= BROKEN
+ duration = 30 SECONDS
+ stat |= EMPED
+ addtimer(CALLBACK(src, PROC_REF(remove_emp)), duration)
+
+/// Callback proc for EMP status
+/obj/machinery/power/port_gen/proc/remove_emp()
+ stat &= ~EMPED
+
+/obj/machinery/power/port_gen/proc/explode()
+ explosion(loc, -1, 3, 5, -1)
+ qdel(src)
diff --git a/code/modules/power/solar.dm b/code/modules/power/generators/solar.dm
similarity index 99%
rename from code/modules/power/solar.dm
rename to code/modules/power/generators/solar.dm
index d1394edf5d2..d01c01dcef6 100644
--- a/code/modules/power/solar.dm
+++ b/code/modules/power/generators/solar.dm
@@ -8,6 +8,7 @@
density = TRUE
max_integrity = 150
integrity_failure = 50
+
var/obscured = FALSE
var/sunfrac = 0
var/adir = SOUTH // actual dir
@@ -27,10 +28,10 @@
//set the control of the panel to a given computer if closer than SOLAR_MAX_DIST
/obj/machinery/power/solar/proc/set_control(obj/machinery/power/solar_control/SC)
if(!SC || (get_dist(src, SC) > SOLAR_MAX_DIST))
- return 0
+ return FALSE
control = SC
SC.connected_panels |= src
- return 1
+ return TRUE
//set the control of the panel to null and removes it from the control list of the previous control computer if needed
/obj/machinery/power/solar/proc/unset_control()
@@ -86,8 +87,8 @@
S.give_glass(stat & BROKEN)
else
playsound(src, "shatter", 70, TRUE)
- new /obj/item/shard(src.loc)
- new /obj/item/shard(src.loc)
+ new /obj/item/shard(loc)
+ new /obj/item/shard(loc)
qdel(src)
/obj/machinery/power/solar/update_overlays()
@@ -128,7 +129,7 @@
if(obscured) //get no light from the sun, so don't generate power
return
var/sgen = SSsun.solar_gen_rate * sunfrac
- add_avail(sgen)
+ produce_direct_power(sgen)
control.gen += sgen
else //if we're no longer on the same powernet, remove from control computer
unset_control()
diff --git a/code/modules/power/teg.dm b/code/modules/power/generators/thermo_electric_generator.dm
similarity index 83%
rename from code/modules/power/teg.dm
rename to code/modules/power/generators/thermo_electric_generator.dm
index e3bfc19d839..9edacf5a153 100644
--- a/code/modules/power/teg.dm
+++ b/code/modules/power/generators/thermo_electric_generator.dm
@@ -1,4 +1,4 @@
-/obj/machinery/power/generator
+/obj/machinery/power/teg
name = "thermoelectric generator"
desc = "It's a high efficiency thermoelectric generator."
icon_state = "teg"
@@ -19,20 +19,20 @@
var/light_range_on = 1
var/light_power_on = 0.1 //just dont want it to be culled by byond.
-/obj/machinery/power/generator/Initialize(mapload)
+/obj/machinery/power/teg/Initialize(mapload)
. = ..()
update_appearance(UPDATE_DESC)
connect()
-/obj/machinery/power/generator/update_desc()
+/obj/machinery/power/teg/update_desc()
. = ..()
desc = initial(desc) + " Its cold circulator is located on the [dir2text(cold_dir)] side, and its heat circulator is located on the [dir2text(hot_dir)] side."
-/obj/machinery/power/generator/Destroy()
+/obj/machinery/power/teg/Destroy()
disconnect()
return ..()
-/obj/machinery/power/generator/proc/disconnect()
+/obj/machinery/power/teg/proc/disconnect()
if(cold_circ)
cold_circ.generator = null
if(hot_circ)
@@ -40,24 +40,23 @@
if(powernet)
disconnect_from_network()
-/obj/machinery/power/generator/Initialize()
+/obj/machinery/power/teg/Initialize()
. = ..()
connect()
-/obj/machinery/power/generator/proc/connect()
+/obj/machinery/power/teg/proc/connect()
connect_to_network()
- var/obj/machinery/atmospherics/binary/circulator/circpath = /obj/machinery/atmospherics/binary/circulator
- cold_circ = locate(circpath) in get_step(src, cold_dir)
- hot_circ = locate(circpath) in get_step(src, hot_dir)
+ cold_circ = locate(/obj/machinery/atmospherics/binary/circulator) in get_step(src, cold_dir)
+ hot_circ = locate(/obj/machinery/atmospherics/binary/circulator) in get_step(src, hot_dir)
- if(cold_circ && cold_circ.side == cold_dir)
+ if(cold_circ?.side == cold_dir)
cold_circ.generator = src
cold_circ.update_icon()
else
cold_circ = null
- if(hot_circ && hot_circ.side == hot_dir)
+ if(hot_circ?.side == hot_dir)
hot_circ.generator = src
hot_circ.update_icon()
else
@@ -67,7 +66,7 @@
update_icon()
updateDialog()
-/obj/machinery/power/generator/power_change()
+/obj/machinery/power/teg/power_change()
. = ..()
if(!anchored)
stat |= NOPOWER
@@ -77,7 +76,7 @@
set_light(light_range_on, light_power_on)
update_icon(UPDATE_OVERLAYS)
-/obj/machinery/power/generator/update_overlays()
+/obj/machinery/power/teg/update_overlays()
. = ..()
if(stat & (NOPOWER|BROKEN))
return
@@ -91,7 +90,7 @@
if(light)
. += emissive_appearance(icon, "teg-oc[lastcirc]")
-/obj/machinery/power/generator/process()
+/obj/machinery/power/teg/process()
if(stat & (NOPOWER|BROKEN))
return
@@ -136,7 +135,7 @@
//log_debug("POWER: [lastgen] W generated at [efficiency * 100]% efficiency and sinks sizes [cold_air_heat_capacity], [hot_air_heat_capacity]")
- add_avail(lastgen)
+ produce_direct_power(lastgen)
// update icon overlays only if displayed level has changed
if(hot_air)
@@ -156,21 +155,21 @@
updateDialog()
-/obj/machinery/power/generator/attack_ai(mob/user)
+/obj/machinery/power/teg/attack_ai(mob/user)
return attack_hand(user)
-/obj/machinery/power/generator/attack_ghost(mob/user)
+/obj/machinery/power/teg/attack_ghost(mob/user)
if(stat & (NOPOWER|BROKEN))
return
ui_interact(user)
-/obj/machinery/power/generator/attack_hand(mob/user)
+/obj/machinery/power/teg/attack_hand(mob/user)
if(..())
user << browse(null, "window=teg")
return
ui_interact(user)
-/obj/machinery/power/generator/multitool_act(mob/user, obj/item/I)
+/obj/machinery/power/teg/multitool_act(mob/user, obj/item/I)
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
@@ -190,7 +189,7 @@
to_chat(user, "You reverse the generator's circulator settings. The cold circulator is now on the [dir2text(cold_dir)] side, and the heat circulator is now on the [dir2text(hot_dir)] side.")
update_appearance(UPDATE_DESC)
-/obj/machinery/power/generator/wrench_act(mob/user, obj/item/I)
+/obj/machinery/power/teg/wrench_act(mob/user, obj/item/I)
. = TRUE
if(!I.use_tool(src, user, 0, volume = I.tool_volume))
return
@@ -202,13 +201,13 @@
connect()
to_chat(user, "You [anchored ? "secure" : "unsecure"] the bolts holding [src] to the floor.")
-/obj/machinery/power/generator/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
+/obj/machinery/power/teg/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = TRUE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "TEG", name, 500, 400, master_ui, state)
ui.open()
-/obj/machinery/power/generator/ui_data(mob/user)
+/obj/machinery/power/teg/ui_data(mob/user)
var/list/data = list()
if(!powernet)
data["error"] = "Unable to connect to the power network!"
@@ -238,7 +237,7 @@
data["error"] = "Unable to locate all parts!"
return data
-/obj/machinery/power/generator/ui_act(action, params)
+/obj/machinery/power/teg/ui_act(action, params)
if(..())
return
if(action == "check")
diff --git a/code/modules/power/tracker.dm b/code/modules/power/generators/tracker.dm
similarity index 100%
rename from code/modules/power/tracker.dm
rename to code/modules/power/generators/tracker.dm
diff --git a/code/modules/power/treadmill.dm b/code/modules/power/generators/treadmill.dm
similarity index 99%
rename from code/modules/power/treadmill.dm
rename to code/modules/power/generators/treadmill.dm
index fd41e56ac7b..f363c27f6ff 100644
--- a/code/modules/power/treadmill.dm
+++ b/code/modules/power/generators/treadmill.dm
@@ -81,7 +81,7 @@
var/output = get_power_output()
if(output)
- add_avail(output)
+ produce_direct_power(output)
update_icon()
/obj/machinery/power/treadmill/proc/get_power_output()
diff --git a/code/modules/power/turbine.dm b/code/modules/power/generators/turbine.dm
similarity index 99%
rename from code/modules/power/turbine.dm
rename to code/modules/power/generators/turbine.dm
index fc919819c0b..88524d3001e 100644
--- a/code/modules/power/turbine.dm
+++ b/code/modules/power/generators/turbine.dm
@@ -257,7 +257,7 @@
lastgen = ((compressor.rpm / TURBGENQ)**TURBGENG) * TURBGENQ * productivity
- add_avail(lastgen)
+ produce_direct_power(lastgen)
// Weird function but it works. Should be something else...
diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm
deleted file mode 100644
index 21bcb2af549..00000000000
--- a/code/modules/power/power.dm
+++ /dev/null
@@ -1,338 +0,0 @@
-//////////////////////////////
-// POWER MACHINERY BASE CLASS
-//////////////////////////////
-
-/////////////////////////////
-// Definitions
-/////////////////////////////
-
-/obj/machinery/power
- name = null
- icon = 'icons/obj/power.dmi'
- anchored = TRUE
- on_blueprints = TRUE
- power_state = NO_POWER_USE
-
- var/datum/powernet/powernet = null
-
-/obj/machinery/power/Destroy()
- disconnect_from_network()
- return ..()
-
-///////////////////////////////
-// General procedures
-//////////////////////////////
-
-// common helper procs for all power machines
-// All power generation handled in add_avail()
-// Machines should use add_load(), surplus(), avail()
-// Non-machines should use add_delayedload(), delayed_surplus(), newavail()
-
-/obj/machinery/power/proc/add_avail(amount)
- if(powernet)
- powernet.newavail += amount
- return TRUE
- else
- return FALSE
-
-/obj/machinery/power/proc/add_load(amount)
- if(powernet)
- powernet.load += amount
-
-/obj/machinery/power/proc/surplus()
- if(powernet)
- return clamp(powernet.avail - powernet.load, 0, powernet.avail)
- else
- return 0
-
-/obj/machinery/power/proc/avail()
- if(powernet)
- return powernet.avail
- else
- return 0
-
-/obj/machinery/power/proc/add_delayedload(amount)
- if(powernet)
- powernet.delayedload += amount
-
-/obj/machinery/power/proc/delayed_surplus()
- if(powernet)
- return clamp(powernet.newavail - powernet.delayedload, 0, powernet.newavail)
- else
- return 0
-
-/obj/machinery/power/proc/newavail()
- if(powernet)
- return powernet.newavail
- else
- return 0
-
-/obj/machinery/power/proc/disconnect_terminal() // 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
-/obj/machinery/power/proc/connect_to_network()
- var/turf/T = src.loc
- if(!T || !istype(T))
- return FALSE
-
- var/obj/structure/cable/C = T.get_cable_node() //check if we have a node cable on the machine turf, the first found is picked
- if(!C || !C.powernet)
- return FALSE
-
- C.powernet.add_machine(src)
- return TRUE
-
-// remove and disconnect the machine from its current powernet
-/obj/machinery/power/proc/disconnect_from_network()
- if(!powernet)
- return FALSE
- powernet.remove_machine(src)
- return TRUE
-
-// attach a wire to a power machine - leads from the turf you are standing on
-//almost never called, overwritten by all power machines but terminal and generator
-/obj/machinery/power/attackby(obj/item/I, mob/user, params)
- if(istype(I, /obj/item/stack/cable_coil))
- var/obj/item/stack/cable_coil/coil = I
- var/turf/T = user.loc
- if(T.intact || !isfloorturf(T))
- return
- if(get_dist(src, user) > 1)
- return
- coil.place_turf(T, user)
- else
- return ..()
-
-
-///////////////////////////////////////////
-// Powernet handling helpers
-//////////////////////////////////////////
-
-//returns all the cables WITHOUT a powernet in neighbors turfs,
-//pointing towards the turf the machine is located at
-/obj/machinery/power/proc/get_connections()
-
- . = list()
-
- var/cdir
- var/turf/T
-
- for(var/card in GLOB.cardinal)
- T = get_step(loc,card)
- cdir = get_dir(T,loc)
-
- for(var/obj/structure/cable/C in T)
- if(C.powernet)
- continue
- if(C.d1 == cdir || C.d2 == cdir)
- . += C
- return .
-
-//returns all the cables in neighbors turfs,
-//pointing towards the turf the machine is located at
-/obj/machinery/power/proc/get_marked_connections()
-
- . = list()
-
- var/cdir
- var/turf/T
-
- for(var/card in GLOB.cardinal)
- T = get_step(loc,card)
- cdir = get_dir(T,loc)
-
- for(var/obj/structure/cable/C in T)
- if(C.d1 == cdir || C.d2 == cdir)
- . += C
- return .
-
-//returns all the NODES (O-X) cables WITHOUT a powernet in the turf the machine is located at
-/obj/machinery/power/proc/get_indirect_connections()
- . = list()
- for(var/obj/structure/cable/C in loc)
- if(C.powernet)
- continue
- if(C.d1 == 0) // the cable is a node cable
- . += C
- return .
-
-///////////////////////////////////////////
-// GLOBAL PROCS for powernets handling
-//////////////////////////////////////////
-
-
-// returns a list of all power-related objects (nodes, cable, junctions) in turf,
-// excluding source, that match the direction d
-// if unmarked==1, only return those with no powernet
-/proc/power_list(turf/T, source, d, unmarked=0, cable_only = 0)
- . = list()
-
- for(var/AM in T)
- if(AM == source)
- continue //we don't want to return source
-
- if(!cable_only && istype(AM, /obj/machinery/power))
- var/obj/machinery/power/P = AM
- if(P.powernet == 0)
- continue // exclude APCs which have powernet=0
-
- if(!unmarked || !P.powernet) //if unmarked=1 we only return things with no powernet
- if(d == 0)
- . += P
-
- else if(istype(AM, /obj/structure/cable))
- var/obj/structure/cable/C = AM
-
- if(!unmarked || !C.powernet)
- if(C.d1 == d || C.d2 == d)
- . += C
- return .
-
-//remove the old powernet and replace it with a new one throughout the network.
-/proc/propagate_network(obj/O, datum/powernet/PN)
- var/list/worklist = list()
- var/list/found_machines = list()
- var/index = 1
- var/obj/P = null
-
- worklist+=O //start propagating from the passed object
-
- while(index<=worklist.len) //until we've exhausted all power objects
- P = worklist[index] //get the next power object found
- index++
-
- if(istype(P, /obj/structure/cable))
- var/obj/structure/cable/C = P
- if(C.powernet != PN) //add it to the powernet, if it isn't already there
- PN.add_cable(C)
- worklist |= C.get_connections() //get adjacents power objects, with or without a powernet
-
- else if(P.anchored && istype(P, /obj/machinery/power))
- var/obj/machinery/power/M = P
- found_machines |= M //we wait until the powernet is fully propagates to connect the machines
-
- else
- continue
-
- //now that the powernet is set, connect found machines to it
- for(var/obj/machinery/power/PM in found_machines)
- if(!PM.connect_to_network()) //couldn't find a node on its turf...
- PM.disconnect_from_network() //... so disconnect if already on a powernet
-
-
-//Merge two powernets, the bigger (in cable length term) absorbing the other
-/proc/merge_powernets(datum/powernet/net1, datum/powernet/net2)
- if(!net1 || !net2) //if one of the powernet doesn't exist, return
- return
-
- if(net1 == net2) //don't merge same powernets
- return
-
- //We assume net1 is larger. If net2 is in fact larger we are just going to make them switch places to reduce on code.
- if(net1.cables.len < net2.cables.len) //net2 is larger than net1. Let's switch them around
- var/temp = net1
- net1 = net2
- net2 = temp
-
- //merge net2 into net1
- for(var/obj/structure/cable/Cable in net2.cables) //merge cables
- net1.add_cable(Cable)
-
- for(var/obj/machinery/power/Node in net2.nodes) //merge power machines
- if(!Node.connect_to_network())
- Node.disconnect_from_network() //if somehow we can't connect the machine to the new powernet, disconnect it from the old nonetheless
-
- return net1
-
-//Determines how strong could be shock, deals damage to mob, uses power.
-//M is a mob who touched wire/whatever
-//power_source is a source of electricity, can be powercell, area, apc, cable, powernet or null
-//source is an object caused electrocuting (airlock, grille, etc)
-//No animations will be performed by this proc.
-/proc/electrocute_mob(mob/living/M, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE)
- if(!M || ismecha(M.loc))
- return FALSE //feckin mechs are dumb
- if(dist_check)
- if(!in_range(source, M))
- return FALSE
- if(ishuman(M))
- var/mob/living/carbon/human/H = M
- if(H.gloves)
- var/obj/item/clothing/gloves/G = H.gloves
- if(G.siemens_coefficient == 0)
- return FALSE //to avoid spamming with insulated glvoes on
-
- var/area/source_area
- if(isarea(power_source))
- source_area = power_source
- power_source = source_area.get_apc()
- if(istype(power_source, /obj/structure/cable))
- var/obj/structure/cable/Cable = power_source
- power_source = Cable.powernet
-
- var/datum/powernet/PN
- var/obj/item/stock_parts/cell/cell
-
- if(istype(power_source, /datum/powernet))
- PN = power_source
- else if(istype(power_source, /obj/item/stock_parts/cell))
- cell = power_source
- else if(istype(power_source, /obj/machinery/power/apc))
- var/obj/machinery/power/apc/apc = power_source
- cell = apc.cell
- if(apc.terminal)
- PN = apc.terminal.powernet
- else if(!power_source)
- return 0
- else
- log_admin("ERROR: /proc/electrocute_mob([M], [power_source], [source]): wrong power_source")
- return 0
- if(!cell && !PN)
- return 0
- var/PN_damage = 0
- var/cell_damage = 0
- if(PN)
- PN_damage = PN.get_electrocute_damage()
- if(cell)
- cell_damage = cell.get_electrocute_damage()
- var/shock_damage = 0
- if(PN_damage >= cell_damage)
- power_source = PN
- shock_damage = PN_damage
- else
- power_source = cell
- shock_damage = cell_damage
- var/drained_hp = M.electrocute_act(shock_damage, source, siemens_coeff) //zzzzzzap!
- var/drained_energy = drained_hp*20
-
- if(source_area)
- source_area.powernet.use_active_power(drained_energy / GLOB.CELLRATE)
- else if(istype(power_source, /datum/powernet))
- var/drained_power = drained_energy/GLOB.CELLRATE //convert from "joules" to "watts"
- PN.delayedload += (min(drained_power, max(PN.newavail - PN.delayedload, 0)))
- else if (istype(power_source, /obj/item/stock_parts/cell))
- cell.use(drained_energy)
- return drained_energy
-
-////////////////////////////////////////////////
-// Misc.
-///////////////////////////////////////////////
-
-
-// return a knot cable (O-X) if one is present in the turf
-// null if there's none
-/turf/proc/get_cable_node()
- if(!can_have_cabling())
- return null
- for(var/obj/structure/cable/C in src)
- if(C.d1 == 0)
- return C
- return null
-
-/area/proc/get_apc()
- for(var/thing in GLOB.apcs)
- var/obj/machinery/power/apc/APC = thing
- if(APC.apc_area == src)
- return APC
diff --git a/code/modules/power/power_machine.dm b/code/modules/power/power_machine.dm
new file mode 100644
index 00000000000..0c732508501
--- /dev/null
+++ b/code/modules/power/power_machine.dm
@@ -0,0 +1,109 @@
+//////////////////////////////
+// POWER MACHINERY BASE CLASS
+//////////////////////////////
+/obj/machinery/power
+ name = null
+ icon = 'icons/obj/power.dmi'
+ anchored = TRUE
+ on_blueprints = TRUE
+ power_state = NO_POWER_USE
+
+ var/datum/regional_powernet/powernet = null
+
+/obj/machinery/power/Destroy()
+ disconnect_from_network()
+ return ..()
+
+/obj/machinery/power/proc/produce_direct_power(amount)
+ if(powernet)
+ powernet.queued_power_production += amount
+ return TRUE
+ return FALSE
+
+/// Adds power demand to the powernet, machines should use this
+/obj/machinery/power/proc/consume_direct_power(amount)
+ powernet?.power_demand += amount
+
+/// Gets surplus power available on this machines powernet, machines should use this proc
+/obj/machinery/power/proc/get_surplus()
+ return powernet ? powernet.calculate_surplus() : 0
+
+/// Gets surplus power available on this machines powernet, machines should use this proc
+/obj/machinery/power/proc/get_power_balance()
+ return powernet ? powernet.calculate_power_balance() : 0
+
+/// Gets power available (NOT EXTRA) on this cables powernet, machines should use this
+/obj/machinery/power/proc/get_available_power()
+ return powernet ? powernet.available_power : 0
+
+/// Adds queued power demand to be met next process cycle
+/obj/machinery/power/proc/add_queued_power_demand(amount)
+ powernet?.queued_power_demand += amount
+
+/// Gets surplus power queued for next process cycle on this cables powernet
+/obj/machinery/power/proc/get_queued_surplus()
+ return powernet?.calculate_queued_surplus()
+
+/// Gets available (NOT EXTRA) power queued for next process cycle on this machines powernet
+/obj/machinery/power/proc/get_queued_available_power()
+ return powernet?.queued_power_production
+
+/obj/machinery/power/proc/disconnect_terminal() // 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
+/obj/machinery/power/proc/connect_to_network()
+ var/turf/T = loc
+ if(!istype(T))
+ return FALSE
+
+ var/obj/structure/cable/C = T.get_cable_node() //check if we have a node cable on the machine turf, the first found is picked
+
+ if(!C || !C.powernet)
+ return FALSE
+ C.powernet.add_machine(src)
+ return TRUE
+
+// remove and disconnect the machine from its current powernet
+/obj/machinery/power/proc/disconnect_from_network()
+ if(!powernet)
+ return FALSE
+ powernet.remove_machine(src)
+ return TRUE
+
+// attach a wire to a power machine - leads from the turf you are standing on
+//almost never called, overwritten by all power machines but terminal and generator
+/obj/machinery/power/attackby(obj/item/I, mob/user, params)
+ if(istype(I, /obj/item/stack/cable_coil))
+ var/obj/item/stack/cable_coil/coil = I
+ var/turf/T = user.loc
+ if(T.intact || !isfloorturf(T))
+ return
+ if(get_dist(src, user) > 1)
+ return
+ coil.place_turf(T, user)
+ else
+ return ..()
+
+
+////////////////////////////////////////////////
+// Misc.
+///////////////////////////////////////////////
+
+
+// return a knot cable (O-X) if one is present in the turf
+// null if there's none
+/turf/proc/get_cable_node()
+ if(!can_have_cabling())
+ return null
+ for(var/obj/structure/cable/C in src)
+ if(C.d1 == NO_DIRECTION)
+ return C
+ return null
+
+/area/proc/get_apc()
+ for(var/thing in GLOB.apcs)
+ var/obj/machinery/power/apc/APC = thing
+ if(APC.apc_area == src)
+ return APC
diff --git a/code/modules/power/powernet.dm b/code/modules/power/powernet.dm
deleted file mode 100644
index f73498cff2c..00000000000
--- a/code/modules/power/powernet.dm
+++ /dev/null
@@ -1,102 +0,0 @@
-////////////////////////////////////////////
-// POWERNET DATUM
-// each contiguous network of cables & nodes
-/////////////////////////////////////
-/datum/powernet
- var/number // unique id
- var/list/cables = list() // all cables & junctions
- var/list/nodes = list() // all connected machines
-
- var/load = 0 // the current load on the powernet, increased by each machine at processing
- var/newavail = 0 // what available power was gathered last tick, then becomes...
- var/avail = 0 //...the current available power in the powernet
- var/viewavail = 0 // the available power as it appears on the power console (gradually updated)
- var/viewload = 0 // the load as it appears on the power console (gradually updated)
- var/netexcess = 0 // excess power on the powernet (typically avail-load)///////
- var/delayedload = 0 // load applied to powernet between power ticks.
-
-/datum/powernet/New()
- SSmachines.powernets += src
- ..()
-
-/datum/powernet/Destroy()
- //Go away references, you suck!
- for(var/obj/structure/cable/C in cables)
- cables -= C
- C.powernet = null
- for(var/obj/machinery/power/M in nodes)
- nodes -= M
- M.powernet = null
-
- SSmachines.powernets -= src
- return ..()
-
-/datum/powernet/proc/is_empty()
- return !cables.len && !nodes.len
-
-//remove a cable from the current powernet
-//if the powernet is then empty, delete it
-//Warning : this proc DON'T check if the cable exists
-/datum/powernet/proc/remove_cable(obj/structure/cable/C)
- cables -= C
- C.powernet = null
- if(is_empty())//the powernet is now empty...
- qdel(src)///... delete it
-
-//add a cable to the current powernet
-//Warning : this proc DON'T check if the cable exists
-/datum/powernet/proc/add_cable(obj/structure/cable/C)
- if(C.powernet)// if C already has a powernet...
- if(C.powernet == src)
- return
- else
- C.powernet.remove_cable(C) //..remove it
- C.powernet = src
- cables +=C
-
-//remove a power machine from the current powernet
-//if the powernet is then empty, delete it
-//Warning : this proc DON'T check if the machine exists
-/datum/powernet/proc/remove_machine(obj/machinery/power/M)
- nodes -=M
- M.powernet = null
- if(is_empty())//the powernet is now empty...
- qdel(src)///... delete it
-
-
-//add a power machine to the current powernet
-//Warning : this proc DON'T check if the machine exists
-/datum/powernet/proc/add_machine(obj/machinery/power/M)
- if(M.powernet)// if M already has a powernet...
- if(M.powernet == src)
- return
- else
- M.disconnect_from_network()//..remove it
- M.powernet = src
- nodes[M] = M
-
-//handles the power changes in the powernet
-//called every ticks by the powernet controller
-/datum/powernet/proc/reset()
- //see if there's a surplus of power remaining in the powernet and stores unused power in the SMES
- netexcess = avail - load
-
- 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
-
- // update power consoles
- viewavail = round(0.8 * viewavail + 0.2 * avail)
- viewload = round(0.8 * viewload + 0.2 * load)
-
- // reset the powernet
- load = delayedload
- delayedload = 0
- avail = newavail
- newavail = 0
-
-/datum/powernet/proc/get_electrocute_damage()
- if(avail >= 1000)
- return clamp(20 + round(avail / 25000), 20, 195) + rand(-5, 5)
- else
- return 0
diff --git a/code/modules/power/powernets/README.md b/code/modules/power/powernets/README.md
new file mode 100644
index 00000000000..58acc36e4f4
--- /dev/null
+++ b/code/modules/power/powernets/README.md
@@ -0,0 +1,31 @@
+
+# Understanding Powernets
+Much like any other massive numbers system in SS13, the power (or powernet) system is complex and confusing to work with, only being trumped in complexity by atmospherics/LINDA. This README serves as a powernets 101 guide and breaks down how the system works.
+
+## Two Types of Powernets
+There are two types of powernets in our code
+1. Regional Powernets
+2. Local Powernets
+
+They are two completely different datum types from eachother and serve different completely different purposes. In a nutshell, regional powernets are dynamically sized and deal with physical machinery, cables, and generators whereas local powernets are statically locked into a single area each and work directly with APCs to handle individual machines interactions with the larger regional powernet.
+
+## Regional Powernet
+An inter-area datum which handles 1 continuous set of cables (`var/list/cables`) and all the connected machinery/nodes on that set of cable (`var/list/nodes`).
+
+On this datum you'll notice a lot of different vars handling power input, output, consumption, demand, etc
+
+### Regional Powernet Process Call Stack
+Starting in SSmachines,
+`/datum/controller/subsystem/machines/fire(resumed = 0)`
+the `fire()` proc will call process `process_powernets()`
+`/datum/controller/subsystem/machines/proc/process_powernets(resumed = 0)`
+This proc will then call `process_power()` on every single registered regional powernet
+
+### The Power Variables
+`var/available_power` - the currently available power in the powernet in watts THIS PROCESS CYCLE
+`var/power_demand` - the power being consumed from available power in watts THIS PROCESS CYCLE
+
+`var/queued_power_production` - the power in watts that will be available to be consumed in the NEXT PROCESS CYCLE
+--> All power producing generators dump their production into this variable
+`var/queued_power_demand` - the power in watts that will be guaranteed to be consumed in the NEXT PROCESS CYCLE
+--> Anything machine/item that needs to have priority consumption draws from the queue'd cycle first in order to ensure it gets priority power (electrocution, powersinks, etc)
diff --git a/code/modules/power/powernets/powernet_helpers.dm b/code/modules/power/powernets/powernet_helpers.dm
new file mode 100644
index 00000000000..59058d29098
--- /dev/null
+++ b/code/modules/power/powernets/powernet_helpers.dm
@@ -0,0 +1,126 @@
+///////////////////////////////////////////
+// GLOBAL PROCS for powernets handling
+//////////////////////////////////////////
+
+/// remove the old powernet and replace it with a new one throughout the network.
+/proc/propagate_network(obj/O, datum/regional_powernet/PN)
+ var/list/worklist = list()
+ var/list/found_machines = list()
+ var/index = 1
+ var/obj/P = null
+
+ worklist += O //start propagating from the passed object
+
+ while(index <= length(worklist)) //until we've exhausted all power objects
+ P = worklist[index] //get the next power object found
+ index++
+
+ if(istype(P, /obj/structure/cable))
+ var/obj/structure/cable/C = P
+ if(C.powernet != PN) //add it to the powernet, if it isn't already there
+ PN.add_cable(C)
+ worklist |= C.get_connections() //get adjacents power objects, with or without a powernet
+
+ else if(P.anchored && istype(P, /obj/machinery/power))
+ var/obj/machinery/power/M = P
+ found_machines |= M //we wait until the powernet is fully propagates to connect the machines
+
+ //now that the powernet is set, connect found machines to it
+ for(var/obj/machinery/power/PM in found_machines)
+ if(!PM.connect_to_network()) //couldn't find a node on its turf...
+ PM.disconnect_from_network() //... so disconnect if already on a powernet
+
+
+//Merge two powernets, the bigger (in cable length term) absorbing the other
+/proc/merge_powernets(datum/regional_powernet/net1, datum/regional_powernet/net2)
+ if(!net1 || !net2) //if one of the powernet doesn't exist, return
+ return
+
+ if(net1 == net2) //don't merge same powernets
+ return
+
+ //We assume net1 is larger. If net2 is in fact larger we are just going to make them switch places to reduce on code.
+ if(net1.cables.len < net2.cables.len) //net2 is larger than net1. Let's switch them around
+ var/temp = net1
+ net1 = net2
+ net2 = temp
+
+ //merge net2 into net1
+ for(var/obj/structure/cable/Cable in net2.cables) //merge cables
+ net1.add_cable(Cable)
+
+ for(var/obj/machinery/power/Node in net2.nodes) //merge power machines
+ if(!Node.connect_to_network())
+ Node.disconnect_from_network() //if somehow we can't connect the machine to the new powernet, disconnect it from the old nonetheless
+
+ return net1
+
+//Determines how strong could be shock, deals damage to mob, uses power.
+//M is a mob who touched wire/whatever
+//power_source is a source of electricity, can be powercell, area, apc, cable, powernet or null
+//source is an object caused electrocuting (airlock, grille, etc)
+//No animations will be performed by this proc.
+/proc/electrocute_mob(mob/living/M, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE)
+ if(!M || ismecha(M.loc))
+ return FALSE //feckin mechs are dumb
+ if(dist_check)
+ if(!in_range(source, M))
+ return FALSE
+ if(ishuman(M))
+ var/mob/living/carbon/human/H = M
+ if(H.gloves)
+ var/obj/item/clothing/gloves/G = H.gloves
+ if(G.siemens_coefficient == 0)
+ return FALSE //to avoid spamming with insulated glvoes on
+
+ var/area/source_area
+ if(isarea(power_source))
+ source_area = power_source
+ power_source = source_area.get_apc()
+ if(istype(power_source, /obj/structure/cable))
+ var/obj/structure/cable/Cable = power_source
+ power_source = Cable.powernet
+
+ var/datum/regional_powernet/PN
+ var/obj/item/stock_parts/cell/cell
+
+ if(istype(power_source, /datum/regional_powernet))
+ PN = power_source
+ else if(istype(power_source, /obj/item/stock_parts/cell))
+ cell = power_source
+ else if(istype(power_source, /obj/machinery/power/apc))
+ var/obj/machinery/power/apc/apc = power_source
+ cell = apc.cell
+ if(apc.terminal)
+ PN = apc.terminal.powernet
+ else if(!power_source)
+ return 0
+ else
+ log_admin("ERROR: /proc/electrocute_mob([M], [power_source], [source]): wrong power_source")
+ return 0
+ if(!cell && !PN)
+ return 0
+ var/PN_damage = 0
+ var/cell_damage = 0
+ if(PN)
+ PN_damage = PN.get_electrocute_damage()
+ if(cell)
+ cell_damage = cell.get_electrocute_damage()
+ var/shock_damage = 0
+ if(PN_damage >= cell_damage)
+ power_source = PN
+ shock_damage = PN_damage
+ else
+ power_source = cell
+ shock_damage = cell_damage
+ var/drained_hp = M.electrocute_act(shock_damage, source, siemens_coeff) //zzzzzzap!
+ var/drained_energy = drained_hp*20
+
+ if(source_area)
+ source_area.powernet.use_active_power(drained_energy / GLOB.CELLRATE)
+ else if(istype(power_source, /datum/regional_powernet))
+ var/drained_power = drained_energy/GLOB.CELLRATE //convert from "joules" to "watts"
+ PN.queued_power_demand += (min(drained_power, max(PN.queued_power_production - PN.queued_power_demand, 0)))
+ else if (istype(power_source, /obj/item/stock_parts/cell))
+ cell.use(drained_energy)
+ return drained_energy
diff --git a/code/modules/power/powernets/regional_powernet.dm b/code/modules/power/powernets/regional_powernet.dm
new file mode 100644
index 00000000000..67d03d660e1
--- /dev/null
+++ b/code/modules/power/powernets/regional_powernet.dm
@@ -0,0 +1,135 @@
+/*
+ * # /datum/regional_powernet
+ *
+ * each contiguous network of cables & nodes over a large area, unlike local powernets, these powernets
+ * don't concern areas and are instead attached to a single wirenet with power machine, engine, battery, and terminal nodes
+*/
+/datum/regional_powernet
+ /// The Powernet Unique ID Number
+ var/number
+ /// A list of All cables & junctions in this powernet
+ var/list/cables = list()
+ /// All Power Machines that are connected to this powernet
+ var/list/nodes = list()
+
+ /// the current available power in the powernet
+ var/available_power = 0
+ /// the current load on the powernet, increased by each machine at processing
+ var/power_demand = 0
+ /// what available power was gathered last tick, then becomes...
+ var/queued_power_production = 0
+ /// load applied to powernet between power ticks.
+ var/queued_power_demand = 0
+ /// excess power on the powernet (typically avail-load)
+ var/excess_power = 0
+
+ /// the available power as it appears on the power console (gradually updated)
+ var/smoothed_available_power = 0
+ /// the load as it appears on the power console (gradually updated)
+ var/smoothed_demand = 0
+
+/datum/regional_powernet/New()
+ . = ..()
+ SSmachines.powernets += src
+
+/datum/regional_powernet/Destroy()
+ //Go away references, you suck!
+ for(var/obj/structure/cable/C as anything in cables)
+ cables -= C
+ C.powernet = null
+ for(var/obj/machinery/power/M as anything in nodes)
+ nodes -= M
+ M.powernet = null
+
+ SSmachines.powernets -= src
+ return ..()
+
+/datum/regional_powernet/proc/is_empty()
+ return !length(cables) && !length(nodes)
+
+/// remove a cable from the current powernet, if the powernet is empty after, delete it
+/datum/regional_powernet/proc/remove_cable(obj/structure/cable/C)
+ cables -= C
+ C.powernet = null
+ if(is_empty())
+ qdel(src) //powernet datums are useless if they have no nodes/cables
+
+/// add a cable to the current powernet
+/datum/regional_powernet/proc/add_cable(obj/structure/cable/C)
+ if(C.powernet)
+ if(C.powernet != src)
+ C.powernet.remove_cable(C) //if C already has a powernet remove it
+ else
+ return //already connect to this powernet, return
+ C.powernet = src
+ cables += C
+
+/// remove a power machine from the current powernet, if the powernet is then empty, delete it
+/datum/regional_powernet/proc/remove_machine(obj/machinery/power/M)
+ nodes -= M
+ M.powernet = null
+ if(is_empty()) //the powernet is now empty so delete it
+ qdel(src)
+
+/// add a power machine to the current powernet
+/datum/regional_powernet/proc/add_machine(obj/machinery/power/M)
+ if(M.powernet)
+ if(M.powernet != src)
+ M.disconnect_from_network() // if M already has a powernet disconnect it from old powernet
+ else
+ return // already connected to this powernet, return
+ M.powernet = src
+ nodes[M] = M
+
+/// Returns the clamped difference between available power on the net and the demanded power, i.g. the surplus power available
+/datum/regional_powernet/proc/calculate_surplus()
+ return clamp(available_power - power_demand, 0, available_power)
+
+/// Returns the non-clamped difference between available power on the net and the demanded power, i.g. consumption vs. supply
+/datum/regional_powernet/proc/calculate_power_balance()
+ return (available_power - power_demand)
+
+/datum/regional_powernet/proc/calculate_queued_surplus()
+ return clamp(queued_power_production - queued_power_demand, 0, queued_power_production)
+/*
+ * # process_power()
+ *
+ * Bread and butter of the regional powernet datum, handles calculatting excess power in the net
+ * and returns that excess to connected batteries. Furthermore, will clear/apply the previous usage and take
+ * the new usage from the next tick after and apply it to the current power tracking vars
+ *
+ * called every tick by the powernet controller
+*/
+/datum/regional_powernet/proc/process_power()
+ //Calculate excess power in the net, so the difference between how much is used vs. how much is sent into the powernet
+ excess_power = calculate_surplus()
+
+ if(excess_power > 100 && length(nodes))
+ 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
+
+ // update power consoles, the reason we use 80% old value and 20% new value is to give the illusion of smoothness
+ smoothed_available_power = round(0.8 * smoothed_available_power + 0.2 * available_power)
+ smoothed_demand = round(0.8 * smoothed_demand + 0.2 * power_demand)
+
+ // reset the powernet
+ power_demand = queued_power_demand
+ queued_power_demand = 0
+ available_power = queued_power_production
+ queued_power_production = 0
+
+#define MINIMUM_PW_SHOCK 1000
+#define MINIMUM_SHOCK_DAMAGE 20
+#define MAXIMUM_SHOCK_DAMAGE 195
+#define WATT_TO_DAMAGE_RATIO 25000
+
+/datum/regional_powernet/proc/get_electrocute_damage()
+ if(available_power >= MINIMUM_PW_SHOCK)
+ return clamp(MINIMUM_SHOCK_DAMAGE + round(available_power / WATT_TO_DAMAGE_RATIO), MINIMUM_SHOCK_DAMAGE, MAXIMUM_SHOCK_DAMAGE) + rand(-5, 5)
+ else
+ return 0
+
+#undef MINIMUM_PW_SHOCK
+#undef MINIMUM_SHOCK_DAMAGE
+#undef MAXIMUM_SHOCK_DAMAGE
+#undef WATT_TO_DAMAGE_RATIO
diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm
index 81ac0a376b2..1cd6c54fe5c 100644
--- a/code/modules/power/smes.dm
+++ b/code/modules/power/smes.dm
@@ -254,7 +254,7 @@
//inputting
if(terminal && input_attempt)
- input_available = terminal.surplus()
+ input_available = terminal.get_power_balance()
if(inputting)
if(input_available > 0) // if there's power available, try to charge
@@ -263,7 +263,7 @@
charge += load * SMESRATE // increase the charge
- terminal.add_load(load) // add the load to the terminal side network
+ terminal.consume_direct_power(load) // add the load to the terminal side network
else // if not enough capcity
inputting = FALSE // stop inputting
@@ -278,8 +278,7 @@
if(output_attempt)
if(outputting)
output_used = min( charge/SMESRATE, output_level) //limit output to that stored
-
- if (add_avail(output_used)) // add output to powernet if it exists (smes side)
+ if(produce_direct_power(output_used)) // add output to powernet if it exists (smes side)
charge -= output_used*SMESRATE // reduce the storage (may be recovered in /restore() if excessive)
else
outputting = FALSE
@@ -310,7 +309,7 @@
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
+ var/excess = powernet.excess_power // 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
@@ -321,7 +320,7 @@
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
+ powernet.excess_power -= excess // remove the excess from the powernet, so later SMESes don't try to use it
output_used -= excess
diff --git a/code/modules/station_goals/bluespace_tap.dm b/code/modules/station_goals/bluespace_tap.dm
index a55b55724b5..9850b157e4a 100644
--- a/code/modules/station_goals/bluespace_tap.dm
+++ b/code/modules/station_goals/bluespace_tap.dm
@@ -313,16 +313,16 @@
/obj/machinery/power/bluespace_tap/process()
actual_power_usage = get_power_use(input_level)
- if(surplus() < actual_power_usage) //not enough power, so turn down a level
+ if(get_surplus() < actual_power_usage) //not enough power, so turn down a level
input_level--
return // and no mining gets done
if(actual_power_usage)
- add_load(actual_power_usage)
+ consume_direct_power(actual_power_usage)
var/points_to_add = (input_level + emagged) * base_points
points += points_to_add //point generation, emagging gets you 'free' points at the cost of higher anomaly chance
total_points += points_to_add
// actual input level changes slowly
- if(input_level < desired_level && (surplus() >= get_power_use(input_level + 1)))
+ if(input_level < desired_level && (get_surplus() >= get_power_use(input_level + 1)))
input_level++
else if(input_level > desired_level)
input_level--
@@ -345,7 +345,7 @@
data["points"] = points
data["totalPoints"] = total_points
data["powerUse"] = actual_power_usage
- data["availablePower"] = surplus()
+ data["availablePower"] = get_surplus()
data["maxLevel"] = max_level
data["emagged"] = emagged
data["safeLevels"] = safe_levels
diff --git a/code/modules/supply/supply_packs/pack_engineering.dm b/code/modules/supply/supply_packs/pack_engineering.dm
index 73ce2f9dbc6..44612090c04 100644
--- a/code/modules/supply/supply_packs/pack_engineering.dm
+++ b/code/modules/supply/supply_packs/pack_engineering.dm
@@ -212,7 +212,7 @@
/datum/supply_packs/engineering/engine/teg
name = "Thermo-Electric Generator Crate"
contains = list(
- /obj/machinery/power/generator,
+ /obj/machinery/power/teg,
/obj/item/pipe/circulator,
/obj/item/pipe/circulator)
cost = 250
diff --git a/code/modules/tgui/modules/power_monitor.dm b/code/modules/tgui/modules/power_monitor.dm
index 4e1255607c7..5453cdee480 100644
--- a/code/modules/tgui/modules/power_monitor.dm
+++ b/code/modules/tgui/modules/power_monitor.dm
@@ -34,8 +34,8 @@
powermonitor = null
return
if(powermonitor.powernet)
- data["poweravail"] = DisplayPower(powermonitor.powernet.viewavail)
- data["powerdemand"] = DisplayPower(powermonitor.powernet.viewload)
+ data["poweravail"] = DisplayPower(powermonitor.powernet.smoothed_available_power)
+ data["powerdemand"] = DisplayPower(powermonitor.powernet.smoothed_demand)
data["history"] = powermonitor.history
data["apcs"] = GLOB.apc_repository.apc_data(powermonitor.powernet)
data["no_powernet"] = FALSE
diff --git a/paradise.dme b/paradise.dme
index 8e1b71ab716..0e8f6ce81d3 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -44,6 +44,7 @@
#include "code\__DEFINES\criminal_status.dm"
#include "code\__DEFINES\cult_defines.dm"
#include "code\__DEFINES\departments.dm"
+#include "code\__DEFINES\directions.dm"
#include "code\__DEFINES\dna.dm"
#include "code\__DEFINES\economy_defines.dm"
#include "code\__DEFINES\emotes.dm"
@@ -2296,44 +2297,47 @@
#include "code\modules\pda\utilities.dm"
#include "code\modules\persistence\persistence.dm"
#include "code\modules\point\point.dm"
-#include "code\modules\power\cable.dm"
-#include "code\modules\power\cable_logic.dm"
#include "code\modules\power\cell.dm"
#include "code\modules\power\gravitygenerator.dm"
#include "code\modules\power\lights.dm"
-#include "code\modules\power\port_gen.dm"
-#include "code\modules\power\power.dm"
-#include "code\modules\power\powernet.dm"
+#include "code\modules\power\power_machine.dm"
#include "code\modules\power\smes.dm"
-#include "code\modules\power\solar.dm"
-#include "code\modules\power\teg.dm"
-#include "code\modules\power\terminal.dm"
-#include "code\modules\power\tracker.dm"
-#include "code\modules\power\treadmill.dm"
-#include "code\modules\power\turbine.dm"
#include "code\modules\power\apc\apc.dm"
#include "code\modules\power\apc\apc_construction.dm"
#include "code\modules\power\apc\apc_malfunction.dm"
#include "code\modules\power\apc\apc_overlay.dm"
+#include "code\modules\power\cables\cable.dm"
+#include "code\modules\power\cables\cable_coil.dm"
+#include "code\modules\power\cables\terminal.dm"
+#include "code\modules\power\engines\singularity\collector.dm"
+#include "code\modules\power\engines\singularity\containment_field.dm"
+#include "code\modules\power\engines\singularity\emitter.dm"
+#include "code\modules\power\engines\singularity\field_generator.dm"
+#include "code\modules\power\engines\singularity\investigate.dm"
+#include "code\modules\power\engines\singularity\narsie.dm"
+#include "code\modules\power\engines\singularity\singularity.dm"
+#include "code\modules\power\engines\singularity\singulogen.dm"
+#include "code\modules\power\engines\singularity\particle_accelerator\particle.dm"
+#include "code\modules\power\engines\singularity\particle_accelerator\particle_accelerator.dm"
+#include "code\modules\power\engines\singularity\particle_accelerator\particle_chamber.dm"
+#include "code\modules\power\engines\singularity\particle_accelerator\particle_control.dm"
+#include "code\modules\power\engines\singularity\particle_accelerator\particle_emitter.dm"
+#include "code\modules\power\engines\singularity\particle_accelerator\particle_power.dm"
+#include "code\modules\power\engines\supermatter\supermatter.dm"
+#include "code\modules\power\engines\tesla\coil.dm"
+#include "code\modules\power\engines\tesla\energy_ball.dm"
+#include "code\modules\power\engines\tesla\generator.dm"
+#include "code\modules\power\engines\tesla\teslagen.dm"
+#include "code\modules\power\generators\solar.dm"
+#include "code\modules\power\generators\thermo_electric_generator.dm"
+#include "code\modules\power\generators\tracker.dm"
+#include "code\modules\power\generators\treadmill.dm"
+#include "code\modules\power\generators\turbine.dm"
+#include "code\modules\power\generators\portable generators\pacman.dm"
+#include "code\modules\power\generators\portable generators\port_gen.dm"
#include "code\modules\power\powernets\local_powernet.dm"
-#include "code\modules\power\singularity\collector.dm"
-#include "code\modules\power\singularity\containment_field.dm"
-#include "code\modules\power\singularity\emitter.dm"
-#include "code\modules\power\singularity\field_generator.dm"
-#include "code\modules\power\singularity\investigate.dm"
-#include "code\modules\power\singularity\narsie.dm"
-#include "code\modules\power\singularity\singularity.dm"
-#include "code\modules\power\singularity\singulogen.dm"
-#include "code\modules\power\singularity\particle_accelerator\particle.dm"
-#include "code\modules\power\singularity\particle_accelerator\particle_accelerator.dm"
-#include "code\modules\power\singularity\particle_accelerator\particle_chamber.dm"
-#include "code\modules\power\singularity\particle_accelerator\particle_control.dm"
-#include "code\modules\power\singularity\particle_accelerator\particle_emitter.dm"
-#include "code\modules\power\singularity\particle_accelerator\particle_power.dm"
-#include "code\modules\power\supermatter\supermatter.dm"
-#include "code\modules\power\tesla\coil.dm"
-#include "code\modules\power\tesla\energy_ball.dm"
-#include "code\modules\power\tesla\teslagen.dm"
+#include "code\modules\power\powernets\powernet_helpers.dm"
+#include "code\modules\power\powernets\regional_powernet.dm"
#include "code\modules\procedural_mapping\mapGenerator.dm"
#include "code\modules\procedural_mapping\mapGeneratorModule.dm"
#include "code\modules\procedural_mapping\mapGeneratorReadme.dm"