diff --git a/code/game/objects/items/circuitboards/machinery/power.dm b/code/game/objects/items/circuitboards/machinery/power.dm
index b580ee6ff641..d8cdc9faab6c 100644
--- a/code/game/objects/items/circuitboards/machinery/power.dm
+++ b/code/game/objects/items/circuitboards/machinery/power.dm
@@ -1,27 +1,3 @@
-/obj/item/stock_parts/circuitboard/smes
- name = "circuitboard (superconductive magnetic energy storage)"
- build_path = /obj/machinery/power/smes/buildable
- board_type = "machine"
- origin_tech = @'{"powerstorage":6,"engineering":4}'
- req_components = list(/obj/item/stock_parts/smes_coil = 1, /obj/item/stack/cable_coil = 30)
- additional_spawn_components = list(
- /obj/item/stock_parts/console_screen = 1,
- /obj/item/stock_parts/keyboard = 1,
- /obj/item/stock_parts/shielding/electric = 1
- )
-
-/obj/item/stock_parts/circuitboard/batteryrack
- name = "circuitboard (battery rack PSU)"
- build_path = /obj/machinery/power/smes/batteryrack
- board_type = "machine"
- origin_tech = @'{"powerstorage":3,"engineering":2}'
- req_components = list(/obj/item/stock_parts/capacitor/ = 3, /obj/item/stock_parts/matter_bin/ = 1)
- additional_spawn_components = list(
- /obj/item/stock_parts/console_screen = 1,
- /obj/item/stock_parts/keyboard = 1,
- /obj/item/stock_parts/power/apc/buildable = 1
- )
-
/obj/item/stock_parts/circuitboard/recharger
name = "circuitboard (recharger)"
build_path = /obj/machinery/recharger
@@ -51,83 +27,6 @@
/obj/item/stock_parts/capacitor = 6
) // The apc part is to supply upkeep power, so it charges the battery instead of draining it. Capacitors make things go faster.
-/obj/item/stock_parts/circuitboard/turbine
- name = "circuitboard (small turbine)"
- build_path = /obj/machinery/atmospherics/pipeturbine
- board_type = "machine"
- origin_tech = @'{"powerstorage":4,"engineering":4}'
- req_components = list(
- /obj/item/stock_parts/manipulator = 2,
- /obj/item/stock_parts/matter_bin = 2
- )
- additional_spawn_components = list()
-
-/obj/item/stock_parts/circuitboard/turbine/motor
- name = "circuitboard (small turbine motor)"
- build_path = /obj/machinery/turbinemotor
- board_type = "machine"
- origin_tech = @'{"powerstorage":4,"engineering":4}'
- req_components = list(
- /obj/item/stock_parts/manipulator = 2,
- /obj/item/stock_parts/capacitor = 4
- )
-
-/obj/item/stock_parts/circuitboard/big_turbine
- name = "circuitboard (large turbine compressor)"
- build_path = /obj/machinery/compressor
- board_type = "machine"
- origin_tech = @'{"powerstorage":4,"engineering":4}'
- req_components = list(
- /obj/item/stock_parts/manipulator = 3,
- /obj/item/stock_parts/matter_bin = 3
- )
- additional_spawn_components = list(
- /obj/item/stock_parts/power/apc/buildable = 1
- )
-
-/obj/item/stock_parts/circuitboard/big_turbine/center
- name = "circuitboard (large turbine motor)"
- build_path = /obj/machinery/turbine
- board_type = "machine"
- origin_tech = @'{"powerstorage":4,"engineering":4}'
- req_components = list(
- /obj/item/stock_parts/manipulator = 2,
- /obj/item/stock_parts/capacitor = 4
- )
- additional_spawn_components = list(
- /obj/item/stock_parts/console_screen = 1,
- /obj/item/stock_parts/keyboard = 1,
- /obj/item/stock_parts/power/apc/buildable = 1
- )
-
-/obj/item/stock_parts/circuitboard/teg_turbine
- name = "circuitboard (thermoelectric generator turbine)"
- build_path = /obj/machinery/atmospherics/binary/circulator
- board_type = "machine"
- origin_tech = @'{"powerstorage":4,"engineering":4}'
- req_components = list(
- /obj/item/stock_parts/manipulator = 3,
- /obj/item/stock_parts/matter_bin = 3
- )
- additional_spawn_components = list(
- /obj/item/stock_parts/power/apc/buildable = 1
- )
-
-/obj/item/stock_parts/circuitboard/teg_turbine/motor
- name = "circuitboard (thermoelectric generator motor)"
- build_path = /obj/machinery/generator
- board_type = "machine"
- origin_tech = @'{"powerstorage":4,"engineering":4}'
- req_components = list(
- /obj/item/stock_parts/manipulator = 2,
- /obj/item/stock_parts/capacitor = 4
- )
- additional_spawn_components = list(
- /obj/item/stock_parts/console_screen = 1,
- /obj/item/stock_parts/keyboard = 1,
- /obj/item/stock_parts/power/apc/buildable = 1
- )
-
/obj/item/stock_parts/circuitboard/breaker
name = "circuitboard (breaker box)"
build_path = /obj/machinery/power/breakerbox
diff --git a/code/modules/power/stirling.dm b/code/modules/atmospherics/components/binary_devices/stirling.dm
similarity index 100%
rename from code/modules/power/stirling.dm
rename to code/modules/atmospherics/components/binary_devices/stirling.dm
diff --git a/code/modules/codex/entries/machinery.dm b/code/modules/codex/entries/machinery.dm
index 62f34de3efeb..0d60980ef1e0 100644
--- a/code/modules/codex/entries/machinery.dm
+++ b/code/modules/codex/entries/machinery.dm
@@ -123,12 +123,6 @@
disambiguator = "machine"
available_to_map_tech_level = MAP_TECH_LEVEL_SPACE
-/datum/codex_entry/smes
- associated_paths = list(/obj/machinery/power/smes)
- mechanics_text = "It's a big battery. An important part of the power network that ensures that you still have power when your generators eventually explode. Maximum input and output settings are available.
The lights on the front show the status of the SMES: The column on the left shows stored power, a blinking red light at top right shows that it's on but receiving no power, blinking yellow shows that the SMES is charging, and the little light on the middle right shows whether it's outputting power or not.
A floor terminal puts power into the SMES, and power is output to a wire underneath."
- disambiguator = "machine"
- available_to_map_tech_level = MAP_TECH_LEVEL_SPACE
-
/datum/codex_entry/vending
associated_paths = list(/obj/machinery/vending)
associated_strings = list("vending machine", "vendor")
diff --git a/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm b/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm
index 48070e2e7e7a..33d206212bba 100644
--- a/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm
+++ b/code/modules/fabrication/designs/imprinter/designs_misc_circuits.dm
@@ -188,12 +188,6 @@
/datum/fabricator_recipe/imprinter/circuit/pacmanpotato
path = /obj/item/stock_parts/circuitboard/pacman/super/potato
-/datum/fabricator_recipe/imprinter/circuit/batteryrack
- path = /obj/item/stock_parts/circuitboard/batteryrack
-
-/datum/fabricator_recipe/imprinter/circuit/smes_cell
- path = /obj/item/stock_parts/circuitboard/smes
-
/datum/fabricator_recipe/imprinter/circuit/alerts
path = /obj/item/stock_parts/circuitboard/stationalert
diff --git a/code/modules/power/fission/core.dm b/code/modules/fission/core.dm
similarity index 100%
rename from code/modules/power/fission/core.dm
rename to code/modules/fission/core.dm
diff --git a/code/modules/power/fission/core_control.dm b/code/modules/fission/core_control.dm
similarity index 100%
rename from code/modules/power/fission/core_control.dm
rename to code/modules/fission/core_control.dm
diff --git a/code/modules/power/fission/fission_circuits.dm b/code/modules/fission/fission_circuits.dm
similarity index 100%
rename from code/modules/power/fission/fission_circuits.dm
rename to code/modules/fission/fission_circuits.dm
diff --git a/code/modules/power/fuel_assembly/fuel_assembly.dm b/code/modules/fuel_assembly/fuel_assembly.dm
similarity index 100%
rename from code/modules/power/fuel_assembly/fuel_assembly.dm
rename to code/modules/fuel_assembly/fuel_assembly.dm
diff --git a/code/modules/power/fuel_assembly/fuel_compressor.dm b/code/modules/fuel_assembly/fuel_compressor.dm
similarity index 100%
rename from code/modules/power/fuel_assembly/fuel_compressor.dm
rename to code/modules/fuel_assembly/fuel_compressor.dm
diff --git a/code/modules/power/fusion/_setup.dm b/code/modules/fusion/_setup.dm
similarity index 100%
rename from code/modules/power/fusion/_setup.dm
rename to code/modules/fusion/_setup.dm
diff --git a/code/modules/power/fusion/consoles/_consoles.dm b/code/modules/fusion/consoles/_consoles.dm
similarity index 100%
rename from code/modules/power/fusion/consoles/_consoles.dm
rename to code/modules/fusion/consoles/_consoles.dm
diff --git a/code/modules/power/fusion/consoles/core_control.dm b/code/modules/fusion/consoles/core_control.dm
similarity index 100%
rename from code/modules/power/fusion/consoles/core_control.dm
rename to code/modules/fusion/consoles/core_control.dm
diff --git a/code/modules/power/fusion/consoles/gyrotron_control.dm b/code/modules/fusion/consoles/gyrotron_control.dm
similarity index 100%
rename from code/modules/power/fusion/consoles/gyrotron_control.dm
rename to code/modules/fusion/consoles/gyrotron_control.dm
diff --git a/code/modules/power/fusion/consoles/injector_control.dm b/code/modules/fusion/consoles/injector_control.dm
similarity index 100%
rename from code/modules/power/fusion/consoles/injector_control.dm
rename to code/modules/fusion/consoles/injector_control.dm
diff --git a/code/modules/power/fusion/core/_core.dm b/code/modules/fusion/core/_core.dm
similarity index 100%
rename from code/modules/power/fusion/core/_core.dm
rename to code/modules/fusion/core/_core.dm
diff --git a/code/modules/power/fusion/core/core_field.dm b/code/modules/fusion/core/core_field.dm
similarity index 100%
rename from code/modules/power/fusion/core/core_field.dm
rename to code/modules/fusion/core/core_field.dm
diff --git a/code/modules/power/fusion/fuel_injector/fuel_injector.dm b/code/modules/fusion/fuel_injector/fuel_injector.dm
similarity index 100%
rename from code/modules/power/fusion/fuel_injector/fuel_injector.dm
rename to code/modules/fusion/fuel_injector/fuel_injector.dm
diff --git a/code/modules/power/fusion/fusion_circuits.dm b/code/modules/fusion/fusion_circuits.dm
similarity index 100%
rename from code/modules/power/fusion/fusion_circuits.dm
rename to code/modules/fusion/fusion_circuits.dm
diff --git a/code/modules/power/fusion/fusion_particle_catcher.dm b/code/modules/fusion/fusion_particle_catcher.dm
similarity index 100%
rename from code/modules/power/fusion/fusion_particle_catcher.dm
rename to code/modules/fusion/fusion_particle_catcher.dm
diff --git a/code/modules/power/fusion/fusion_reactions.dm b/code/modules/fusion/fusion_reactions.dm
similarity index 100%
rename from code/modules/power/fusion/fusion_reactions.dm
rename to code/modules/fusion/fusion_reactions.dm
diff --git a/code/modules/power/fusion/gyrotron/gyrotron.dm b/code/modules/fusion/gyrotron/gyrotron.dm
similarity index 100%
rename from code/modules/power/fusion/gyrotron/gyrotron.dm
rename to code/modules/fusion/gyrotron/gyrotron.dm
diff --git a/code/modules/power/fusion/kinetic_harvester.dm b/code/modules/fusion/kinetic_harvester.dm
similarity index 100%
rename from code/modules/power/fusion/kinetic_harvester.dm
rename to code/modules/fusion/kinetic_harvester.dm
diff --git a/code/modules/power/geothermal/_geothermal.dm b/code/modules/geothermal/_geothermal.dm
similarity index 64%
rename from code/modules/power/geothermal/_geothermal.dm
rename to code/modules/geothermal/_geothermal.dm
index 138a38cd1cf4..632dc84e1e4b 100644
--- a/code/modules/power/geothermal/_geothermal.dm
+++ b/code/modules/geothermal/_geothermal.dm
@@ -1,103 +1,3 @@
-var/global/const/GEOTHERMAL_EFFICIENCY_MOD = 0.2
-var/global/const/GEOTHERMAL_PRESSURE_LOSS = 0.3
-var/global/const/GEOTHERMAL_PRESSURE_CONSUMED_PER_TICK = 0.05
-var/global/const/GEOTHERMAL_PRESSURE_TO_POWER = 800
-var/global/const/MAX_GEOTHERMAL_PRESSURE = 12000
-
-//////////////////////////////////////////////////////////////////////
-// Geyser Steam Particle Emitter
-//////////////////////////////////////////////////////////////////////
-
-///Particle emitter that emits a ~64 pixels by ~192 pixels high column of steam while active.
-/particles/geyser_steam
- icon_state = "smallsmoke"
- icon = 'icons/effects/effects.dmi'
- width = WORLD_ICON_SIZE * 2 //Particles expand a bit as they climb, so need a bit of space on the width
- height = WORLD_ICON_SIZE * 6 //Needs to be really tall, because particles stop being drawn outside of the canvas.
- count = 64
- spawning = 5
- lifespan = generator("num", 1 SECOND, 2.5 SECONDS, LINEAR_RAND)
- fade = 3 SECONDS
- fadein = 0.25 SECONDS
- grow = 0.1
- velocity = generator("vector", list(0, 0), list(0, 0.2))
- position = generator("circle", -6, 6, NORMAL_RAND)
- gravity = list(0, 0.40)
- scale = generator("vector", list(0.3, 0.3), list(1,1), NORMAL_RAND)
- rotation = generator("num", -45, 45)
- spin = generator("num", -20, 20)
-
-//////////////////////////////////////////////////////////////////////
-// Geyser Object
-//////////////////////////////////////////////////////////////////////
-
-/// A prop that periodically emit steam spouts and can have a geothermal generator placed on top to generate power.
-/obj/effect/geyser
- name = "geothermal vent"
- desc = "A vent leading to an underground geothermally heated reservoir, which periodically spews superheated liquid."
- icon = 'icons/effects/geyser.dmi'
- icon_state = "geyser"
- anchored = TRUE
- layer = TURF_LAYER + 0.01
- level = LEVEL_BELOW_PLATING // Goes under floor/plating
- /// The particle emitter that will generate the steam column effect for this geyser
- var/particles/geyser_steam/steamfx
-
-/obj/effect/geyser/Initialize(ml)
- . = ..()
- if(prob(50))
- var/matrix/M = matrix()
- M.Scale(-1, 1)
- transform = M
- set_extension(src, /datum/extension/geothermal_vent)
- steamfx = new //Prepare our FX
-
-///Async proc that enables the particle emitter for the steam column for a few seconds
-/obj/effect/geyser/proc/do_spout()
- set waitfor = FALSE
- particles = steamfx
- particles.spawning = initial(particles.spawning)
- sleep(6 SECONDS)
- particles.spawning = 0
-
-/obj/effect/geyser/explosion_act(severity)
- . = ..()
- if(!QDELETED(src) && prob(100 - (25 * severity)))
- physically_destroyed()
-
-/obj/effect/geyser/hide(hide)
- var/datum/extension/geothermal_vent/E = get_extension(src, /datum/extension/geothermal_vent)
- if(istype(E))
- E.set_obstructed(hide)
- . = ..()
- update_icon()
-
-//////////////////////////////////////////////////////////////////////
-// Underwater Geyser Variant
-//////////////////////////////////////////////////////////////////////
-
-/obj/effect/geyser/underwater
- desc = "A crack in the ocean floor that occasionally vents gouts of superheated water and steam."
-
-/obj/effect/geyser/underwater/Initialize(ml)
- . = ..()
- if(!loc)
- return INITIALIZE_HINT_QDEL
- for(var/turf/floor/seafloor/T in RANGE_TURFS(loc, 5))
- var/dist = get_dist(loc, T)-1
- if(prob(100 - (dist * 20)))
- if(prob(25))
- T = T.ChangeTurf(/turf/floor/clay)
- else
- T = T.ChangeTurf(/turf/floor/mud)
- if(prob(50 - (dist * 10)))
- new /obj/random/seaweed(T)
-
-/obj/effect/geyser/underwater/do_spout()
- set waitfor = FALSE
- var/turf/T = get_turf(src)
- T.show_bubbles()
-
//////////////////////////////////////////////////////////////////////
// Geothermal Generator
//////////////////////////////////////////////////////////////////////
@@ -131,6 +31,12 @@ var/global/const/MAX_GEOTHERMAL_PRESSURE = 12000
var/tmp/list/neighbor_connectors
var/tmp/list/neighbor_connectors_glow
+ var/const/GEOTHERMAL_EFFICIENCY_MOD = 0.2
+ var/const/GEOTHERMAL_PRESSURE_LOSS = 0.3
+ var/const/GEOTHERMAL_PRESSURE_CONSUMED_PER_TICK = 0.05
+ var/const/GEOTHERMAL_PRESSURE_TO_POWER = 800
+ var/const/MAX_GEOTHERMAL_PRESSURE = 12000
+
//#TODO: Maybe should cache neighbors and put listeners on them?
/obj/machinery/geothermal/Initialize(mapload, d, populate_parts = TRUE)
diff --git a/code/modules/geothermal/_geothermal_geysers.dm b/code/modules/geothermal/_geothermal_geysers.dm
new file mode 100644
index 000000000000..54c4bc09e7ee
--- /dev/null
+++ b/code/modules/geothermal/_geothermal_geysers.dm
@@ -0,0 +1,70 @@
+//////////////////////////////////////////////////////////////////////
+// Geyser Object
+//////////////////////////////////////////////////////////////////////
+
+/// A prop that periodically emit steam spouts and can have a geothermal generator placed on top to generate power.
+/obj/effect/geyser
+ name = "geothermal vent"
+ desc = "A vent leading to an underground geothermally heated reservoir, which periodically spews superheated liquid."
+ icon = 'icons/effects/geyser.dmi'
+ icon_state = "geyser"
+ anchored = TRUE
+ layer = TURF_LAYER + 0.01
+ level = LEVEL_BELOW_PLATING // Goes under floor/plating
+ /// The particle emitter that will generate the steam column effect for this geyser
+ var/particles/geyser_steam/steamfx
+
+/obj/effect/geyser/Initialize(ml)
+ . = ..()
+ if(prob(50))
+ var/matrix/M = matrix()
+ M.Scale(-1, 1)
+ transform = M
+ set_extension(src, /datum/extension/geothermal_vent)
+ steamfx = new //Prepare our FX
+
+///Async proc that enables the particle emitter for the steam column for a few seconds
+/obj/effect/geyser/proc/do_spout()
+ set waitfor = FALSE
+ particles = steamfx
+ particles.spawning = initial(particles.spawning)
+ sleep(6 SECONDS)
+ particles.spawning = 0
+
+/obj/effect/geyser/explosion_act(severity)
+ . = ..()
+ if(!QDELETED(src) && prob(100 - (25 * severity)))
+ physically_destroyed()
+
+/obj/effect/geyser/hide(hide)
+ var/datum/extension/geothermal_vent/E = get_extension(src, /datum/extension/geothermal_vent)
+ if(istype(E))
+ E.set_obstructed(hide)
+ . = ..()
+ update_icon()
+
+//////////////////////////////////////////////////////////////////////
+// Underwater Geyser Variant
+//////////////////////////////////////////////////////////////////////
+
+/obj/effect/geyser/underwater
+ desc = "A crack in the ocean floor that occasionally vents gouts of superheated water and steam."
+
+/obj/effect/geyser/underwater/Initialize(ml)
+ . = ..()
+ if(!loc)
+ return INITIALIZE_HINT_QDEL
+ for(var/turf/floor/seafloor/T in RANGE_TURFS(loc, 5))
+ var/dist = get_dist(loc, T)-1
+ if(prob(100 - (dist * 20)))
+ if(prob(25))
+ T = T.ChangeTurf(/turf/floor/clay)
+ else
+ T = T.ChangeTurf(/turf/floor/mud)
+ if(prob(50 - (dist * 10)))
+ new /obj/random/seaweed(T)
+
+/obj/effect/geyser/underwater/do_spout()
+ set waitfor = FALSE
+ var/turf/T = get_turf(src)
+ T.show_bubbles()
diff --git a/code/modules/geothermal/_geothermal_particles.dm b/code/modules/geothermal/_geothermal_particles.dm
new file mode 100644
index 000000000000..d1b8423f02c2
--- /dev/null
+++ b/code/modules/geothermal/_geothermal_particles.dm
@@ -0,0 +1,22 @@
+//////////////////////////////////////////////////////////////////////
+// Geyser Steam Particle Emitter
+//////////////////////////////////////////////////////////////////////
+
+///Particle emitter that emits a ~64 pixels by ~192 pixels high column of steam while active.
+/particles/geyser_steam
+ icon_state = "smallsmoke"
+ icon = 'icons/effects/effects.dmi'
+ width = WORLD_ICON_SIZE * 2 //Particles expand a bit as they climb, so need a bit of space on the width
+ height = WORLD_ICON_SIZE * 6 //Needs to be really tall, because particles stop being drawn outside of the canvas.
+ count = 64
+ spawning = 5
+ lifespan = generator("num", 1 SECOND, 2.5 SECONDS, LINEAR_RAND)
+ fade = 3 SECONDS
+ fadein = 0.25 SECONDS
+ grow = 0.1
+ velocity = generator("vector", list(0, 0), list(0, 0.2))
+ position = generator("circle", -6, 6, NORMAL_RAND)
+ gravity = list(0, 0.40)
+ scale = generator("vector", list(0.3, 0.3), list(1,1), NORMAL_RAND)
+ rotation = generator("num", -45, 45)
+ spin = generator("num", -20, 20)
\ No newline at end of file
diff --git a/code/modules/power/geothermal/geothermal_circuit.dm b/code/modules/geothermal/geothermal_circuit.dm
similarity index 100%
rename from code/modules/power/geothermal/geothermal_circuit.dm
rename to code/modules/geothermal/geothermal_circuit.dm
diff --git a/code/modules/power/geothermal/geothermal_extension.dm b/code/modules/geothermal/geothermal_extension.dm
similarity index 100%
rename from code/modules/power/geothermal/geothermal_extension.dm
rename to code/modules/geothermal/geothermal_extension.dm
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable/cable.dm
similarity index 55%
rename from code/modules/power/cable.dm
rename to code/modules/power/cable/cable.dm
index b18e5bd16812..f62fb73de132 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable/cable.dm
@@ -493,424 +493,4 @@ var/global/list/obj/structure/cable/all_cables = list()
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
- powernet = null // And finally null the powernet var.
-
-///////////////////////////////////////////////
-// The cable coil object, used for laying cable
-///////////////////////////////////////////////
-
-////////////////////////////////
-// Definitions
-////////////////////////////////
-
-#define MAXCOIL 30
-
-/obj/item/stack/cable_coil
- name = "multipurpose cable coil"
- icon = 'icons/obj/items/cable_coil.dmi'
- icon_state = ICON_STATE_WORLD
- randpixel = 2
- amount = MAXCOIL
- max_amount = MAXCOIL
- color = COLOR_MAROON
- paint_color = COLOR_MAROON
- desc = "A coil of wiring, suitable for both delicate electronics and heavy-duty power supply."
- singular_name = "length"
- w_class = ITEM_SIZE_NORMAL
- throw_speed = 2
- throw_range = 5
- material = /decl/material/solid/metal/copper
- matter = list(
- /decl/material/solid/fiberglass = MATTER_AMOUNT_REINFORCEMENT,
- /decl/material/solid/organic/plastic = MATTER_AMOUNT_TRACE
- )
- obj_flags = OBJ_FLAG_CONDUCTIBLE
- slot_flags = SLOT_LOWER_BODY
- item_state = "coil"
- attack_verb = list("whipped", "lashed", "disciplined", "flogged")
- stack_merge_type = /obj/item/stack/cable_coil
- matter_multiplier = 0.15
- /// Whether or not this cable coil can even have a color in the first place.
- var/can_have_color = TRUE
- /// The type of cable structure produced when laying down this cable.
- /// src.cable_type::cable_type should equal stack_merge_type, ideally
- var/cable_type = /obj/structure/cable
-
-/obj/item/stack/cable_coil/single
- amount = 1
-
-/obj/item/stack/cable_coil/cyborg
- name = "cable coil synthesizer"
- desc = "A device that makes cable."
- gender = NEUTER
- matter = null
- uses_charge = 1
- charge_costs = list(1)
- max_health = ITEM_HEALTH_NO_DAMAGE
- is_spawnable_type = FALSE
-
-/obj/item/stack/cable_coil/Initialize(mapload, c_length, var/param_color = null)
- . = ..(mapload, c_length)
- set_extension(src, /datum/extension/tool/variable/simple, list(
- TOOL_CABLECOIL = TOOL_QUALITY_DEFAULT,
- TOOL_SUTURES = TOOL_QUALITY_MEDIOCRE
- ))
- if (can_have_color && param_color) // It should be red by default, so only recolor it if parameter was specified.
- set_color(param_color)
- update_icon()
- update_wclass()
-
-///////////////////////////////////
-// General procedures
-///////////////////////////////////
-
-//you can use wires to heal robotics
-/obj/item/stack/cable_coil/use_on_mob(mob/living/target, mob/living/user, animate = TRUE)
- var/obj/item/organ/external/affecting = istype(target) && GET_EXTERNAL_ORGAN(target, user?.get_target_zone())
- if(affecting && user.check_intent(I_FLAG_HELP))
- if(!affecting.is_robotic())
- to_chat(user, SPAN_WARNING("\The [target]'s [affecting.name] is not robotic. \The [src] cannot repair it."))
- else if(BP_IS_BRITTLE(affecting))
- to_chat(user, SPAN_WARNING("\The [target]'s [affecting.name] is hard and brittle. \The [src] cannot repair it."))
- else
- var/use_amt = min(src.amount, ceil(affecting.burn_dam/3), 5)
- if(can_use(use_amt) && affecting.robo_repair(3*use_amt, BURN, "some damaged wiring", src, user))
- use(use_amt)
- return TRUE
- return ..()
-
-/obj/item/stack/cable_coil/on_update_icon()
- . = ..()
- if (!paint_color && can_have_color)
- var/list/possible_cable_colours = get_global_cable_colors()
- set_color(possible_cable_colours[pick(possible_cable_colours)])
- if(amount == 1)
- icon_state = "coil1"
- SetName("cable piece")
- else if(amount == 2)
- icon_state = "coil2"
- SetName("cable piece")
- else if(amount > 2 && amount != max_amount)
- icon_state = "coil"
- SetName(initial(name))
- else
- icon_state = "coil-max"
- SetName(initial(name))
-
-/obj/item/stack/cable_coil/proc/set_cable_color(var/selected_color, var/user)
- if(!selected_color || !can_have_color)
- return
-
- var/list/possible_cable_colours = get_global_cable_colors()
- var/final_color = possible_cable_colours[selected_color]
- if(!final_color)
- selected_color = "Red"
- final_color = possible_cable_colours[selected_color]
- set_color(final_color)
- to_chat(user, SPAN_NOTICE("You change \the [src]'s color to [lowertext(selected_color)]."))
-
-/obj/item/stack/cable_coil/proc/update_wclass()
- if(amount == 1)
- w_class = ITEM_SIZE_TINY
- else
- w_class = ITEM_SIZE_SMALL
-
-/obj/item/stack/cable_coil/get_examine_strings(mob/user, distance, infix, suffix)
- . = ..()
- if(distance > 1)
- return
- if(get_amount() == 1)
- . += "\A [singular_name] of cable."
- else if(get_amount() == 2)
- . += "Two [plural_name] of cable."
- else
- . += "A coil of power cable. There are [get_amount()] [plural_name] of cable in the coil."
-
-/obj/item/stack/cable_coil/verb/make_restraint()
- set name = "Make Cable Restraints"
- set category = "Object"
- var/mob/M = usr
-
- if(ishuman(M) && !M.incapacitated())
- if(!isturf(usr.loc)) return
- if(!src.use(15))
- to_chat(usr, SPAN_WARNING("You need at least 15 [plural_name] of cable to make restraints!"))
- return
- var/obj/item/handcuffs/cable/B = new /obj/item/handcuffs/cable(usr.loc)
- B.set_color(color)
- to_chat(usr, SPAN_NOTICE("You wind some [plural_name] of cable together to make some restraints."))
- else
- to_chat(usr, SPAN_NOTICE("You cannot do that."))
-
-/obj/item/stack/cable_coil/cyborg/verb/set_colour()
- set name = "Change Colour"
- set category = "Object"
-
- var/selected_type = input("Pick new colour.", "Cable Colour", null, null) as null|anything in get_global_cable_colors()
- set_cable_color(selected_type, usr)
-
-// Items usable on a cable coil :
-// - Wirecutters : cut them duh !
-// - Cable coil : merge cables
-/obj/item/stack/cable_coil/can_merge_stacks(var/obj/item/stack/other)
- return !other || (istype(other) && other.color == color)
-
-/obj/item/stack/cable_coil/cyborg/can_merge_stacks(var/obj/item/stack/other)
- return TRUE
-
-/obj/item/stack/cable_coil/transfer_to(obj/item/stack/cable_coil/coil)
- if(!istype(coil))
- return 0
- if(!(can_merge_stacks(coil) || coil.can_merge_stacks(src)))
- return 0
-
- return ..()
-
-///////////////////////////////////////////////
-// Cable laying procedures
-//////////////////////////////////////////////
-
-// called when cable_coil is clicked on a turf
-/obj/item/stack/cable_coil/proc/turf_place(turf/F, mob/user)
- if(!isturf(user.loc))
- return
-
- if(get_amount() < 1) // Out of cable
- to_chat(user, SPAN_WARNING("There is no [plural_name] of cable left."))
- return
-
- if(get_dist(F,user) > 1) // Too far
- to_chat(user, SPAN_WARNING("You can't lay cable at a place that far away."))
- return
-
- if(!F.is_plating()) // Ff floor is intact, complain
- to_chat(user, SPAN_WARNING("You can't lay cable there unless the floor tiles are removed."))
- return
-
- var/dirn
- if(user.loc == F)
- dirn = user.dir // if laying on the tile we're on, lay in the direction we're facing
- else
- dirn = get_dir(F, user)
-
- var/end_dir = 0
- if(istype(F) && F.is_open())
- if(!can_use(2))
- to_chat(user, SPAN_WARNING("You don't have enough [plural_name] of cable to do this!"))
- return
- end_dir = DOWN
-
- for(var/obj/structure/cable/LC in F)
- if((LC.d1 == dirn && LC.d2 == end_dir ) || ( LC.d2 == dirn && LC.d1 == end_dir))
- to_chat(user, SPAN_WARNING("There's already a cable at that position."))
- return
-
- put_cable(F, user, end_dir, dirn)
- if(end_dir == DOWN)
- put_cable(GetBelow(F), user, UP, 0)
- return TRUE
-
-// 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 = C.loc
-
- if(!isturf(T) || !T.is_plating()) // 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, SPAN_WARNING("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
- return turf_place(T,user)
-
- 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.is_plating()) // can't place a cable if the floor is complete
- to_chat(user, SPAN_WARNING("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, SPAN_WARNING("There's already a cable at that position."))
- return
- put_cable(U,user,0,fdirn)
- return TRUE
-
- // 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, SPAN_WARNING("There's already a cable at that position."))
- return
-
-
- C.cableColor(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
- new/obj/item/stack/cable_coil(C.loc, 2, C.color)
- qdel(C)
- return
-
- C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets.
- return TRUE
-
- else if(C.d1 == UP) //Special cases for zcables, since they behave weirdly
- . = turf_place(T, user)
- if(.)
- to_chat(user, SPAN_NOTICE("You connect the cable hanging from the ceiling."))
- return .
-
-/obj/item/stack/cable_coil/proc/put_cable(turf/F, mob/user, d1, d2)
- if(!istype(F))
- return FALSE
-
- var/obj/structure/cable/C = new cable_type(F)
- C.cableColor(color)
- C.d1 = d1
- C.d2 = d2
- 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.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
- new/obj/item/stack/cable_coil(C.loc, 1, C.color)
- qdel(C)
- return FALSE
-
-//////////////////////////////
-// Misc.
-/////////////////////////////
-
-/obj/item/stack/cable_coil/cut
- item_state = "coil2"
-
-/obj/item/stack/cable_coil/cut/Initialize()
- . = ..()
- src.amount = rand(1,2)
- update_icon()
- update_wclass()
-
-/obj/item/stack/cable_coil/yellow
- color = COLOR_AMBER
- paint_color = COLOR_AMBER
-
-/obj/item/stack/cable_coil/blue
- color = COLOR_CYAN_BLUE
- paint_color = COLOR_CYAN_BLUE
-
-/obj/item/stack/cable_coil/green
- color = COLOR_GREEN
- paint_color = COLOR_GREEN
-
-/obj/item/stack/cable_coil/pink
- color = COLOR_PURPLE
- paint_color = COLOR_PURPLE
-
-/obj/item/stack/cable_coil/orange
- color = COLOR_ORANGE
- paint_color = COLOR_ORANGE
-
-/obj/item/stack/cable_coil/cyan
- color = COLOR_SKY_BLUE
- paint_color = COLOR_SKY_BLUE
-
-/obj/item/stack/cable_coil/white
- color = COLOR_SILVER
- paint_color = COLOR_SILVER
-
-/obj/item/stack/cable_coil/lime
- color = COLOR_LIME
- paint_color = COLOR_LIME
-
-/obj/item/stack/cable_coil/random/Initialize(mapload, c_length, param_color)
- var/list/possible_cable_colours = get_global_cable_colors()
- set_color(possible_cable_colours[pick(possible_cable_colours)])
- . = ..()
-
-// Produces cable coil from a rig power cell.
-/obj/item/stack/cable_coil/fabricator
- name = "cable fabricator"
- var/cost_per_cable = 10
-
-/obj/item/stack/cable_coil/fabricator/split(var/tamount, var/force=FALSE)
- return
-
-/obj/item/stack/cable_coil/fabricator/get_cell()
- if(istype(loc, /obj/item/rig_module))
- var/obj/item/rig_module/module = loc
- return module.get_cell()
- if(isrobot(loc))
- var/mob/living/silicon/robot/robot = loc
- return robot.get_cell()
-
-/obj/item/stack/cable_coil/fabricator/use(var/used)
- var/obj/item/cell/cell = get_cell()
- return cell?.use(used * cost_per_cable)
-
-/obj/item/stack/cable_coil/fabricator/get_amount()
- var/obj/item/cell/cell = get_cell()
- . = (cell ? floor(cell.charge / cost_per_cable) : 0)
-
-/obj/item/stack/cable_coil/fabricator/get_max_amount()
- var/obj/item/cell/cell = get_cell()
- . = (cell ? floor(cell.maxcharge / cost_per_cable) : 0)
+ powernet = null // And finally null the powernet var.
\ No newline at end of file
diff --git a/code/modules/power/cable/cable_coil.dm b/code/modules/power/cable/cable_coil.dm
new file mode 100644
index 000000000000..9cfdfd3e6107
--- /dev/null
+++ b/code/modules/power/cable/cable_coil.dm
@@ -0,0 +1,419 @@
+///////////////////////////////////////////////
+// The cable coil object, used for laying cable
+///////////////////////////////////////////////
+
+////////////////////////////////
+// Definitions
+////////////////////////////////
+
+#define MAXCOIL 30
+
+/obj/item/stack/cable_coil
+ name = "multipurpose cable coil"
+ icon = 'icons/obj/items/cable_coil.dmi'
+ icon_state = ICON_STATE_WORLD
+ randpixel = 2
+ amount = MAXCOIL
+ max_amount = MAXCOIL
+ color = COLOR_MAROON
+ paint_color = COLOR_MAROON
+ desc = "A coil of wiring, suitable for both delicate electronics and heavy-duty power supply."
+ singular_name = "length"
+ w_class = ITEM_SIZE_NORMAL
+ throw_speed = 2
+ throw_range = 5
+ material = /decl/material/solid/metal/copper
+ matter = list(
+ /decl/material/solid/fiberglass = MATTER_AMOUNT_REINFORCEMENT,
+ /decl/material/solid/organic/plastic = MATTER_AMOUNT_TRACE
+ )
+ obj_flags = OBJ_FLAG_CONDUCTIBLE
+ slot_flags = SLOT_LOWER_BODY
+ item_state = "coil"
+ attack_verb = list("whipped", "lashed", "disciplined", "flogged")
+ stack_merge_type = /obj/item/stack/cable_coil
+ matter_multiplier = 0.15
+ /// Whether or not this cable coil can even have a color in the first place.
+ var/can_have_color = TRUE
+ /// The type of cable structure produced when laying down this cable.
+ /// src.cable_type::cable_type should equal stack_merge_type, ideally
+ var/cable_type = /obj/structure/cable
+
+/obj/item/stack/cable_coil/single
+ amount = 1
+
+/obj/item/stack/cable_coil/cyborg
+ name = "cable coil synthesizer"
+ desc = "A device that makes cable."
+ gender = NEUTER
+ matter = null
+ uses_charge = 1
+ charge_costs = list(1)
+ max_health = ITEM_HEALTH_NO_DAMAGE
+ is_spawnable_type = FALSE
+
+/obj/item/stack/cable_coil/Initialize(mapload, c_length, var/param_color = null)
+ . = ..(mapload, c_length)
+ set_extension(src, /datum/extension/tool/variable/simple, list(
+ TOOL_CABLECOIL = TOOL_QUALITY_DEFAULT,
+ TOOL_SUTURES = TOOL_QUALITY_MEDIOCRE
+ ))
+ if (can_have_color && param_color) // It should be red by default, so only recolor it if parameter was specified.
+ set_color(param_color)
+ update_icon()
+ update_wclass()
+
+///////////////////////////////////
+// General procedures
+///////////////////////////////////
+
+//you can use wires to heal robotics
+/obj/item/stack/cable_coil/use_on_mob(mob/living/target, mob/living/user, animate = TRUE)
+ var/obj/item/organ/external/affecting = istype(target) && GET_EXTERNAL_ORGAN(target, user?.get_target_zone())
+ if(affecting && user.check_intent(I_FLAG_HELP))
+ if(!affecting.is_robotic())
+ to_chat(user, SPAN_WARNING("\The [target]'s [affecting.name] is not robotic. \The [src] cannot repair it."))
+ else if(BP_IS_BRITTLE(affecting))
+ to_chat(user, SPAN_WARNING("\The [target]'s [affecting.name] is hard and brittle. \The [src] cannot repair it."))
+ else
+ var/use_amt = min(src.amount, ceil(affecting.burn_dam/3), 5)
+ if(can_use(use_amt) && affecting.robo_repair(3*use_amt, BURN, "some damaged wiring", src, user))
+ use(use_amt)
+ return TRUE
+ return ..()
+
+/obj/item/stack/cable_coil/on_update_icon()
+ . = ..()
+ if (!paint_color && can_have_color)
+ var/list/possible_cable_colours = get_global_cable_colors()
+ set_color(possible_cable_colours[pick(possible_cable_colours)])
+ if(amount == 1)
+ icon_state = "coil1"
+ SetName("cable piece")
+ else if(amount == 2)
+ icon_state = "coil2"
+ SetName("cable piece")
+ else if(amount > 2 && amount != max_amount)
+ icon_state = "coil"
+ SetName(initial(name))
+ else
+ icon_state = "coil-max"
+ SetName(initial(name))
+
+/obj/item/stack/cable_coil/proc/set_cable_color(var/selected_color, var/user)
+ if(!selected_color || !can_have_color)
+ return
+
+ var/list/possible_cable_colours = get_global_cable_colors()
+ var/final_color = possible_cable_colours[selected_color]
+ if(!final_color)
+ selected_color = "Red"
+ final_color = possible_cable_colours[selected_color]
+ set_color(final_color)
+ to_chat(user, SPAN_NOTICE("You change \the [src]'s color to [lowertext(selected_color)]."))
+
+/obj/item/stack/cable_coil/proc/update_wclass()
+ if(amount == 1)
+ w_class = ITEM_SIZE_TINY
+ else
+ w_class = ITEM_SIZE_SMALL
+
+/obj/item/stack/cable_coil/get_examine_strings(mob/user, distance, infix, suffix)
+ . = ..()
+ if(distance > 1)
+ return
+ if(get_amount() == 1)
+ . += "\A [singular_name] of cable."
+ else if(get_amount() == 2)
+ . += "Two [plural_name] of cable."
+ else
+ . += "A coil of power cable. There are [get_amount()] [plural_name] of cable in the coil."
+
+/obj/item/stack/cable_coil/verb/make_restraint()
+ set name = "Make Cable Restraints"
+ set category = "Object"
+ var/mob/M = usr
+
+ if(ishuman(M) && !M.incapacitated())
+ if(!isturf(usr.loc)) return
+ if(!src.use(15))
+ to_chat(usr, SPAN_WARNING("You need at least 15 [plural_name] of cable to make restraints!"))
+ return
+ var/obj/item/handcuffs/cable/B = new /obj/item/handcuffs/cable(usr.loc)
+ B.set_color(color)
+ to_chat(usr, SPAN_NOTICE("You wind some [plural_name] of cable together to make some restraints."))
+ else
+ to_chat(usr, SPAN_NOTICE("You cannot do that."))
+
+/obj/item/stack/cable_coil/cyborg/verb/set_colour()
+ set name = "Change Colour"
+ set category = "Object"
+
+ var/selected_type = input("Pick new colour.", "Cable Colour", null, null) as null|anything in get_global_cable_colors()
+ set_cable_color(selected_type, usr)
+
+// Items usable on a cable coil :
+// - Wirecutters : cut them duh !
+// - Cable coil : merge cables
+/obj/item/stack/cable_coil/can_merge_stacks(var/obj/item/stack/other)
+ return !other || (istype(other) && other.color == color)
+
+/obj/item/stack/cable_coil/cyborg/can_merge_stacks(var/obj/item/stack/other)
+ return TRUE
+
+/obj/item/stack/cable_coil/transfer_to(obj/item/stack/cable_coil/coil)
+ if(!istype(coil))
+ return 0
+ if(!(can_merge_stacks(coil) || coil.can_merge_stacks(src)))
+ return 0
+
+ return ..()
+
+///////////////////////////////////////////////
+// Cable laying procedures
+//////////////////////////////////////////////
+
+// called when cable_coil is clicked on a turf
+/obj/item/stack/cable_coil/proc/turf_place(turf/F, mob/user)
+ if(!isturf(user.loc))
+ return
+
+ if(get_amount() < 1) // Out of cable
+ to_chat(user, SPAN_WARNING("There is no [plural_name] of cable left."))
+ return
+
+ if(get_dist(F,user) > 1) // Too far
+ to_chat(user, SPAN_WARNING("You can't lay cable at a place that far away."))
+ return
+
+ if(!F.is_plating()) // Ff floor is intact, complain
+ to_chat(user, SPAN_WARNING("You can't lay cable there unless the floor tiles are removed."))
+ return
+
+ var/dirn
+ if(user.loc == F)
+ dirn = user.dir // if laying on the tile we're on, lay in the direction we're facing
+ else
+ dirn = get_dir(F, user)
+
+ var/end_dir = 0
+ if(istype(F) && F.is_open())
+ if(!can_use(2))
+ to_chat(user, SPAN_WARNING("You don't have enough [plural_name] of cable to do this!"))
+ return
+ end_dir = DOWN
+
+ for(var/obj/structure/cable/LC in F)
+ if((LC.d1 == dirn && LC.d2 == end_dir ) || ( LC.d2 == dirn && LC.d1 == end_dir))
+ to_chat(user, SPAN_WARNING("There's already a cable at that position."))
+ return
+
+ put_cable(F, user, end_dir, dirn)
+ if(end_dir == DOWN)
+ put_cable(GetBelow(F), user, UP, 0)
+ return TRUE
+
+// 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 = C.loc
+
+ if(!isturf(T) || !T.is_plating()) // 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, SPAN_WARNING("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
+ return turf_place(T,user)
+
+ 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.is_plating()) // can't place a cable if the floor is complete
+ to_chat(user, SPAN_WARNING("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, SPAN_WARNING("There's already a cable at that position."))
+ return
+ put_cable(U,user,0,fdirn)
+ return TRUE
+
+ // 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, SPAN_WARNING("There's already a cable at that position."))
+ return
+
+
+ C.cableColor(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
+ new/obj/item/stack/cable_coil(C.loc, 2, C.color)
+ qdel(C)
+ return
+
+ C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets.
+ return TRUE
+
+ else if(C.d1 == UP) //Special cases for zcables, since they behave weirdly
+ . = turf_place(T, user)
+ if(.)
+ to_chat(user, SPAN_NOTICE("You connect the cable hanging from the ceiling."))
+ return .
+
+/obj/item/stack/cable_coil/proc/put_cable(turf/F, mob/user, d1, d2)
+ if(!istype(F))
+ return FALSE
+
+ var/obj/structure/cable/C = new cable_type(F)
+ C.cableColor(color)
+ C.d1 = d1
+ C.d2 = d2
+ 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.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
+ new/obj/item/stack/cable_coil(C.loc, 1, C.color)
+ qdel(C)
+ return FALSE
+
+//////////////////////////////
+// Misc.
+/////////////////////////////
+
+/obj/item/stack/cable_coil/cut
+ item_state = "coil2"
+
+/obj/item/stack/cable_coil/cut/Initialize()
+ . = ..()
+ src.amount = rand(1,2)
+ update_icon()
+ update_wclass()
+
+/obj/item/stack/cable_coil/yellow
+ color = COLOR_AMBER
+ paint_color = COLOR_AMBER
+
+/obj/item/stack/cable_coil/blue
+ color = COLOR_CYAN_BLUE
+ paint_color = COLOR_CYAN_BLUE
+
+/obj/item/stack/cable_coil/green
+ color = COLOR_GREEN
+ paint_color = COLOR_GREEN
+
+/obj/item/stack/cable_coil/pink
+ color = COLOR_PURPLE
+ paint_color = COLOR_PURPLE
+
+/obj/item/stack/cable_coil/orange
+ color = COLOR_ORANGE
+ paint_color = COLOR_ORANGE
+
+/obj/item/stack/cable_coil/cyan
+ color = COLOR_SKY_BLUE
+ paint_color = COLOR_SKY_BLUE
+
+/obj/item/stack/cable_coil/white
+ color = COLOR_SILVER
+ paint_color = COLOR_SILVER
+
+/obj/item/stack/cable_coil/lime
+ color = COLOR_LIME
+ paint_color = COLOR_LIME
+
+/obj/item/stack/cable_coil/random/Initialize(mapload, c_length, param_color)
+ var/list/possible_cable_colours = get_global_cable_colors()
+ set_color(possible_cable_colours[pick(possible_cable_colours)])
+ . = ..()
+
+// Produces cable coil from a rig power cell.
+/obj/item/stack/cable_coil/fabricator
+ name = "cable fabricator"
+ var/cost_per_cable = 10
+
+/obj/item/stack/cable_coil/fabricator/split(var/tamount, var/force=FALSE)
+ return
+
+/obj/item/stack/cable_coil/fabricator/get_cell()
+ if(istype(loc, /obj/item/rig_module))
+ var/obj/item/rig_module/module = loc
+ return module.get_cell()
+ if(isrobot(loc))
+ var/mob/living/silicon/robot/robot = loc
+ return robot.get_cell()
+
+/obj/item/stack/cable_coil/fabricator/use(var/used)
+ var/obj/item/cell/cell = get_cell()
+ return cell?.use(used * cost_per_cable)
+
+/obj/item/stack/cable_coil/fabricator/get_amount()
+ var/obj/item/cell/cell = get_cell()
+ . = (cell ? floor(cell.charge / cost_per_cable) : 0)
+
+/obj/item/stack/cable_coil/fabricator/get_max_amount()
+ var/obj/item/cell/cell = get_cell()
+ . = (cell ? floor(cell.maxcharge / cost_per_cable) : 0)
diff --git a/code/modules/power/heavycable.dm b/code/modules/power/cable/heavycable.dm
similarity index 100%
rename from code/modules/power/heavycable.dm
rename to code/modules/power/cable/heavycable.dm
diff --git a/code/modules/power/cell/_cell.dm b/code/modules/power/cell/_cell.dm
new file mode 100644
index 000000000000..b51e7a6258af
--- /dev/null
+++ b/code/modules/power/cell/_cell.dm
@@ -0,0 +1,117 @@
+// Power Cells
+/obj/item/cell
+ name = "power cell"
+ desc = "A rechargeable electrochemical power cell."
+ icon = 'icons/obj/power.dmi'
+ icon_state = "cell"
+ item_state = "cell"
+ origin_tech = @'{"powerstorage":1}'
+ throw_speed = 3
+ throw_range = 5
+ w_class = ITEM_SIZE_NORMAL
+ material = /decl/material/solid/metal/steel
+ matter = list(
+ /decl/material/solid/fiberglass = MATTER_AMOUNT_REINFORCEMENT,
+ /decl/material/solid/organic/plastic = MATTER_AMOUNT_TRACE
+ )
+ var/charge // Current charge
+ var/maxcharge = 1000 // Capacity in Wh
+
+/obj/item/cell/Initialize()
+ . = ..()
+ if(isnull(charge))
+ charge = maxcharge
+ update_icon()
+
+/obj/item/cell/drain_power(var/drain_check, var/surge, var/power = 0)
+
+ if(drain_check)
+ return 1
+
+ if(charge <= 0)
+ return 0
+
+ var/cell_amt = power * CELLRATE
+
+ return use(cell_amt) / CELLRATE
+
+/obj/item/cell/on_update_icon()
+ . = ..()
+ var/overlay_state = null
+ switch(percent())
+ if(95 to 100)
+ overlay_state = "cell-o2"
+ if(25 to 95)
+ overlay_state = "cell-o1"
+ if(0.05 to 25)
+ overlay_state = "cell-o0"
+ if(overlay_state)
+ add_overlay(overlay_state)
+
+/obj/item/cell/proc/percent() // return % charge of cell
+ return maxcharge && (100.0*charge/maxcharge)
+
+/obj/item/cell/proc/fully_charged()
+ return (charge == maxcharge)
+
+// checks if the power cell is able to provide the specified amount of charge
+/obj/item/cell/proc/check_charge(var/amount)
+ return (charge >= amount)
+
+// use power from a cell, returns the amount actually used
+/obj/item/cell/proc/use(var/amount)
+ var/used = min(charge, amount)
+ charge -= used
+ update_icon()
+ return used
+
+// Checks if the specified amount can be provided. If it can, it removes the amount
+// from the cell and returns 1. Otherwise does nothing and returns 0.
+/obj/item/cell/proc/checked_use(var/amount)
+ if(!check_charge(amount))
+ return 0
+ use(amount)
+ return 1
+
+/obj/item/cell/proc/give(var/amount)
+ var/amount_used = min(maxcharge-charge,amount)
+ charge += amount_used
+ update_icon()
+ return amount_used
+
+/obj/item/cell/get_examine_strings(mob/user, distance, infix, suffix)
+ . = ..()
+ . += "The label states it's capacity is [maxcharge] Wh."
+ . += "The charge meter reads [round(src.percent(), 0.1)]%."
+
+/obj/item/cell/emp_act(severity)
+ // remove this if EMPs are ever rebalanced so that they don't instantly drain borg cells
+ // todo: containers (partially) shielding contents?
+ if(isrobot(loc))
+ var/mob/living/silicon/robot/robot = loc
+ severity *= robot.cell_emp_mult
+
+ // Lose 1/2, 1/4, 1/6 of the current charge per hit or 1/4, 1/8, 1/12 of the max charge per hit, whichever is highest
+ charge -= max(charge / (2 * severity), maxcharge/(4 * severity))
+ if (charge < 0)
+ charge = 0
+ ..()
+
+
+/obj/item/cell/proc/get_electrocute_damage()
+ switch (charge)
+ if (1000000 to INFINITY)
+ return min(rand(50,160),rand(50,160))
+ if (200000 to 1000000-1)
+ return min(rand(25,80),rand(25,80))
+ if (100000 to 200000-1)//Ave powernet
+ return min(rand(20,60),rand(20,60))
+ if (50000 to 100000-1)
+ return min(rand(15,40),rand(15,40))
+ if (1000 to 50000-1)
+ return min(rand(10,20),rand(10,20))
+ else
+ return 0
+
+/obj/item/cell/get_cell()
+ return src //no shit Sherlock
\ No newline at end of file
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell/cell_types.dm
similarity index 68%
rename from code/modules/power/cell.dm
rename to code/modules/power/cell/cell_types.dm
index dc9234516221..541a1fc5a91a 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell/cell_types.dm
@@ -1,123 +1,3 @@
-// Power Cells
-/obj/item/cell
- name = "power cell"
- desc = "A rechargeable electrochemical power cell."
- icon = 'icons/obj/power.dmi'
- icon_state = "cell"
- item_state = "cell"
- origin_tech = @'{"powerstorage":1}'
- throw_speed = 3
- throw_range = 5
- w_class = ITEM_SIZE_NORMAL
- material = /decl/material/solid/metal/steel
- matter = list(
- /decl/material/solid/fiberglass = MATTER_AMOUNT_REINFORCEMENT,
- /decl/material/solid/organic/plastic = MATTER_AMOUNT_TRACE
- )
- var/charge // Current charge
- var/maxcharge = 1000 // Capacity in Wh
-
-/obj/item/cell/Initialize()
- . = ..()
- if(isnull(charge))
- charge = maxcharge
- update_icon()
-
-/obj/item/cell/drain_power(var/drain_check, var/surge, var/power = 0)
-
- if(drain_check)
- return 1
-
- if(charge <= 0)
- return 0
-
- var/cell_amt = power * CELLRATE
-
- return use(cell_amt) / CELLRATE
-
-/obj/item/cell/on_update_icon()
- . = ..()
- var/overlay_state = null
- switch(percent())
- if(95 to 100)
- overlay_state = "cell-o2"
- if(25 to 95)
- overlay_state = "cell-o1"
- if(0.05 to 25)
- overlay_state = "cell-o0"
- if(overlay_state)
- add_overlay(overlay_state)
-
-/obj/item/cell/proc/percent() // return % charge of cell
- return maxcharge && (100.0*charge/maxcharge)
-
-/obj/item/cell/proc/fully_charged()
- return (charge == maxcharge)
-
-// checks if the power cell is able to provide the specified amount of charge
-/obj/item/cell/proc/check_charge(var/amount)
- return (charge >= amount)
-
-// use power from a cell, returns the amount actually used
-/obj/item/cell/proc/use(var/amount)
- var/used = min(charge, amount)
- charge -= used
- update_icon()
- return used
-
-// Checks if the specified amount can be provided. If it can, it removes the amount
-// from the cell and returns 1. Otherwise does nothing and returns 0.
-/obj/item/cell/proc/checked_use(var/amount)
- if(!check_charge(amount))
- return 0
- use(amount)
- return 1
-
-/obj/item/cell/proc/give(var/amount)
- var/amount_used = min(maxcharge-charge,amount)
- charge += amount_used
- update_icon()
- return amount_used
-
-/obj/item/cell/get_examine_strings(mob/user, distance, infix, suffix)
- . = ..()
- . += "The label states it's capacity is [maxcharge] Wh."
- . += "The charge meter reads [round(src.percent(), 0.1)]%."
-
-/obj/item/cell/emp_act(severity)
- // remove this if EMPs are ever rebalanced so that they don't instantly drain borg cells
- // todo: containers (partially) shielding contents?
- if(isrobot(loc))
- var/mob/living/silicon/robot/robot = loc
- severity *= robot.cell_emp_mult
-
- // Lose 1/2, 1/4, 1/6 of the current charge per hit or 1/4, 1/8, 1/12 of the max charge per hit, whichever is highest
- charge -= max(charge / (2 * severity), maxcharge/(4 * severity))
- if (charge < 0)
- charge = 0
- ..()
-
-
-/obj/item/cell/proc/get_electrocute_damage()
- switch (charge)
- if (1000000 to INFINITY)
- return min(rand(50,160),rand(50,160))
- if (200000 to 1000000-1)
- return min(rand(25,80),rand(25,80))
- if (100000 to 200000-1)//Ave powernet
- return min(rand(20,60),rand(20,60))
- if (50000 to 100000-1)
- return min(rand(15,40),rand(15,40))
- if (1000 to 50000-1)
- return min(rand(10,20),rand(10,20))
- else
- return 0
-
-/obj/item/cell/get_cell()
- return src //no shit Sherlock
-
-// SUBTYPES BELOW
-
// Smaller variant, used by energy guns and similar small devices.
/obj/item/cell/device
name = "device power cell"
diff --git a/code/modules/power/solar/solar_control.dm b/code/modules/power/solar/solar_control.dm
new file mode 100644
index 000000000000..865534212a95
--- /dev/null
+++ b/code/modules/power/solar/solar_control.dm
@@ -0,0 +1,215 @@
+//
+// Solar Control Computer
+//
+
+/obj/machinery/power/solar_control
+ name = "solar panel control"
+ desc = "A controller for solar panel arrays."
+ icon = 'icons/obj/computer.dmi'
+ icon_state = "solar"
+ anchored = TRUE
+ density = TRUE
+ use_power = POWER_USE_IDLE
+ idle_power_usage = 250
+ construct_state = /decl/machine_construction/default/panel_closed/computer
+ base_type = /obj/machinery/power/solar_control
+ frame_type = /obj/machinery/constructable_frame/computerframe/deconstruct
+ var/cdir = 0
+ var/targetdir = 0 // target angle in manual tracking (since it updates every game minute)
+ var/gen = 0
+ var/lastgen = 0
+ var/track = 0 // 0= off 1=timed 2=auto (tracker)
+ var/trackrate = 600 // 300-900 seconds
+ var/nexttime = 0 // time for a panel to rotate of 1° in manual tracking
+ var/obj/machinery/power/tracker/connected_tracker = null
+ var/list/connected_panels = list()
+
+/obj/machinery/power/solar_control/drain_power()
+ return -1
+
+/obj/machinery/power/solar_control/Destroy()
+ for(var/obj/machinery/power/solar/M in connected_panels)
+ M.unset_control()
+ if(connected_tracker)
+ connected_tracker.unset_control()
+ return ..()
+
+/obj/machinery/power/solar_control/disconnect_from_network()
+ ..()
+ solars_list.Remove(src)
+
+/obj/machinery/power/solar_control/connect_to_network()
+ var/to_return = ..()
+ if(powernet) //if connected and not already in solar_list...
+ solars_list |= src //... add it
+ return to_return
+
+//search for unconnected panels and trackers in the computer powernet and connect them
+/obj/machinery/power/solar_control/proc/search_for_connected()
+ if(powernet)
+ for(var/obj/machinery/power/M in powernet.nodes)
+ if(istype(M, /obj/machinery/power/solar))
+ var/obj/machinery/power/solar/S = M
+ if(!S.control) //i.e unconnected
+ if(S.set_control(src))
+ connected_panels |= S
+ else if(istype(M, /obj/machinery/power/tracker))
+ if(!connected_tracker) //if there's already a tracker connected to the computer don't add another
+ var/obj/machinery/power/tracker/T = M
+ if(!T.control) //i.e unconnected
+ if(T.set_control(src))
+ connected_tracker = T
+
+//called by the sun controller, update the facing angle (either manually or via tracking) and rotates the panels accordingly
+/obj/machinery/power/solar_control/proc/update()
+ if(stat & (NOPOWER | BROKEN))
+ return
+
+ switch(track)
+ if(1)
+ if(trackrate) //we're manual tracking. If we set a rotation speed...
+ cdir = targetdir //...the current direction is the targetted one (and rotates panels to it)
+ if(2) // auto-tracking
+ var/datum/sun/sun = get_best_sun()
+ if(connected_tracker && sun)
+ connected_tracker.set_angle(sun.angle)
+
+ set_panels(cdir)
+ updateDialog()
+
+/obj/machinery/power/solar_control/Initialize()
+ . = ..()
+ if(!connect_to_network()) return
+ set_panels(cdir)
+
+/obj/machinery/power/solar_control/on_update_icon()
+ if(stat & BROKEN)
+ icon_state = "broken"
+ return
+ if(stat & NOPOWER)
+ icon_state = "c_unpowered"
+ return
+ icon_state = "solar"
+
+/obj/machinery/power/solar_control/interface_interact(mob/user)
+ interact(user)
+ return TRUE
+
+/obj/machinery/power/solar_control/interact(mob/user)
+
+ var/datum/sun/sun = get_best_sun()
+ var/t = "Generated power : [round(lastgen)] W
"
+ t += "Star Orientation: [sun?.angle || 0]° ([angle2text(sun?.angle || 0)])
"
+ t += "Array Orientation: [rate_control(src,"cdir","[cdir]°",1,15)] ([angle2text(cdir)])
"
+ t += "Tracking: