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"