From 51035f931e6d3fc03e572ecfe6860df237acd5c2 Mon Sep 17 00:00:00 2001 From: Markolie Date: Fri, 31 Jul 2015 23:59:41 +0200 Subject: [PATCH] Powernet rework --- .../components/trinary_devices/filter.dm | 6 - .../components/unary/vent_pump.dm | 6 +- .../components/unary/vent_scrubber.dm | 4 +- code/__DEFINES/game.dm | 4 + code/__DEFINES/misc.dm | 3 + code/_globalvars/lists/objects.dm | 2 +- code/controllers/Processes/machinery.dm | 12 +- code/controllers/master_controller.dm | 9 +- code/datums/mixed.dm | 15 - code/datums/wires/smartfridge.dm | 51 ++ code/game/area/Space Station 13 areas.dm | 1 - code/game/area/areas.dm | 6 +- code/game/atoms_movable.dm | 8 +- code/game/gamemodes/events/power_failure.dm | 24 +- code/game/gamemodes/gameticker.dm | 6 - code/game/machinery/alarm.dm | 16 +- code/game/machinery/atmo_control.dm | 10 +- code/game/machinery/autolathe.dm | 13 - code/game/machinery/camera/camera.dm | 5 - code/game/machinery/computer/computer.dm | 5 - code/game/machinery/doors/airlock.dm | 16 +- code/game/machinery/floodlight.dm | 3 + code/game/machinery/kitchen/smartfridge.dm | 152 +++- code/game/machinery/machinery.dm | 17 +- code/game/machinery/suit_storage_unit.dm | 12 - code/game/machinery/syndicatebeacon.dm | 2 +- code/game/machinery/vending.dm | 14 - .../mecha/equipment/tools/medical_tools.dm | 4 +- code/game/objects/items.dm | 5 +- code/game/objects/items/devices/PDA/cart.dm | 83 +- code/game/objects/items/devices/PDA/radio.dm | 7 +- code/game/objects/items/devices/powersink.dm | 203 ++--- code/game/objects/items/stacks/stack.dm | 3 + code/game/objects/objs.dm | 3 - code/modules/admin/verbs/debug.dm | 2 +- code/modules/assembly/signaler.dm | 2 +- code/modules/power/apc.dm | 376 +++++---- code/modules/power/cable.dm | 783 ++++++++++++------ code/modules/power/power.dm | 477 ++++------- code/modules/power/powernet.dm | 158 ++++ code/modules/power/singularity/collector.dm | 1 - .../power/singularity/containment_field.dm | 42 +- code/modules/power/singularity/emitter.dm | 14 +- code/modules/power/smes.dm | 330 ++++---- code/modules/power/solar.dm | 33 +- code/modules/power/terminal.dm | 6 +- code/modules/power/tracker.dm | 10 +- code/modules/power/turbine.dm | 1 - code/modules/research/rdmachines.dm | 14 - code/world.dm | 2 +- icons/obj/power.dmi | Bin 22790 -> 24398 bytes nano/templates/apc.tmpl | 8 +- nano/templates/pda.tmpl | 73 +- nano/templates/smartfridge.tmpl | 39 - nano/templates/smes.tmpl | 18 +- nano/templates/solar_control.tmpl | 20 +- paradise.dme | 3 + 57 files changed, 1711 insertions(+), 1431 deletions(-) create mode 100644 code/__DEFINES/game.dm create mode 100644 code/datums/wires/smartfridge.dm create mode 100644 code/modules/power/powernet.dm diff --git a/code/ATMOSPHERICS/components/trinary_devices/filter.dm b/code/ATMOSPHERICS/components/trinary_devices/filter.dm index 5e1fabcebd3..09aecb64393 100755 --- a/code/ATMOSPHERICS/components/trinary_devices/filter.dm +++ b/code/ATMOSPHERICS/components/trinary_devices/filter.dm @@ -30,12 +30,6 @@ Filter types: if(frequency) radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) -/* I don't know why this is here, but it's ugly, so I'm disabling it -/obj/machinery/atmospherics/trinary/filter/New() - ..() - if(radio_controller) - initialize() -*/ /obj/machinery/atmospherics/trinary/filter/update_icon() if(istype(src, /obj/machinery/atmospherics/trinary/filter/m_filter)) icon_state = "m" diff --git a/code/ATMOSPHERICS/components/unary/vent_pump.dm b/code/ATMOSPHERICS/components/unary/vent_pump.dm index 307b98f0ad7..d61f22485cf 100644 --- a/code/ATMOSPHERICS/components/unary/vent_pump.dm +++ b/code/ATMOSPHERICS/components/unary/vent_pump.dm @@ -64,9 +64,6 @@ if (!id_tag) assign_uid() id_tag = num2text(uid) - if(ticker && ticker.current_state == 3)//if the game is running - src.initialize() - src.broadcast_status() ..() /obj/machinery/atmospherics/unary/vent_pump/high_volume @@ -227,11 +224,12 @@ /obj/machinery/atmospherics/unary/vent_pump/initialize() ..() - //some vents work his own spesial way + //some vents work his own special way radio_filter_in = frequency==1439?(RADIO_FROM_AIRALARM):null radio_filter_out = frequency==1439?(RADIO_TO_AIRALARM):null if(frequency) set_frequency(frequency) + src.broadcast_status() /obj/machinery/atmospherics/unary/vent_pump/receive_signal(datum/signal/signal) if(stat & (NOPOWER|BROKEN)) diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm index 5a6220a48ee..b1bb5ffece0 100644 --- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm +++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm @@ -42,9 +42,6 @@ if (!id_tag) assign_uid() id_tag = num2text(uid) - if(ticker && ticker.current_state == 3)//if the game is running - src.initialize() - src.broadcast_status() ..() /obj/machinery/atmospherics/unary/vent_scrubber/update_icon(var/safety = 0) @@ -131,6 +128,7 @@ radio_filter_out = frequency==initial(frequency)?(RADIO_TO_AIRALARM):null if(frequency) set_frequency(frequency) + src.broadcast_status() /obj/machinery/atmospherics/unary/vent_scrubber/process() ..() diff --git a/code/__DEFINES/game.dm b/code/__DEFINES/game.dm new file mode 100644 index 00000000000..68518e80d6c --- /dev/null +++ b/code/__DEFINES/game.dm @@ -0,0 +1,4 @@ +#define GAME_STATE_PREGAME 1 +#define GAME_STATE_SETTING_UP 2 +#define GAME_STATE_PLAYING 3 +#define GAME_STATE_FINISHED 4 \ No newline at end of file diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 6d24ef3099b..a2071b28385 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -50,6 +50,9 @@ #define INFECTION_LEVEL_TWO 500 #define INFECTION_LEVEL_THREE 1000 +// Damage above this value must be repaired with surgery. +#define ROBOLIMB_SELF_REPAIR_CAP 30 + //metal, glass, rod stacks #define MAX_STACK_AMOUNT_METAL 50 #define MAX_STACK_AMOUNT_GLASS 50 diff --git a/code/_globalvars/lists/objects.dm b/code/_globalvars/lists/objects.dm index 510be54c75c..8c419cc35fb 100644 --- a/code/_globalvars/lists/objects.dm +++ b/code/_globalvars/lists/objects.dm @@ -15,8 +15,8 @@ var/global/list/singularities = list() //list of all singularities var/global/list/aibots = list() // AI controlled bots var/global/list/table_recipes = list() //list of all table craft recipes -var/global/list/active_areas = list() var/global/list/all_areas = list() var/global/list/machines = list() +var/global/list/processing_power_items = list() //items that ask to be called every cycle diff --git a/code/controllers/Processes/machinery.dm b/code/controllers/Processes/machinery.dm index 2d8986ff2ae..1e8fb197dde 100644 --- a/code/controllers/Processes/machinery.dm +++ b/code/controllers/Processes/machinery.dm @@ -4,6 +4,7 @@ /datum/controller/process/machinery/doWork() process_power() + process_power_drain() process_machines() /datum/controller/process/machinery/proc/process_machines() @@ -40,5 +41,12 @@ powerNetwork.reset() scheck() continue - else - powernets -= powerNetwork + + powernets.Remove(powerNetwork) + +/datum/controller/process/machinery/proc/process_power_drain() + // Currently only used by powersinks. These items get priority processed before machinery + for(var/obj/item/I in processing_power_items) + if(!I.pwr_drain()) // 0 = Process Kill, remove from processing list. + processing_power_items.Remove(I) + scheck() diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index 15890ce870a..a2434e3ead7 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -318,13 +318,8 @@ datum/controller/game_controller/proc/process() /datum/controller/game_controller/proc/processPowernets() last_thing_processed = /datum/powernet - - for (var/datum/powernet/Powernet in powernets) - if (Powernet) - Powernet.reset() - continue - - powernets -= Powernet + for(var/datum/powernet/Powernet in powernets) + Powernet.reset() /datum/controller/game_controller/proc/processNano() last_thing_processed = /datum/nanoui diff --git a/code/datums/mixed.dm b/code/datums/mixed.dm index a47137b9f1d..df066541633 100644 --- a/code/datums/mixed.dm +++ b/code/datums/mixed.dm @@ -28,20 +28,5 @@ name = "text" var/data = null - - -/datum/powernet - 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/viewload = 0 // the load as it appears on the power console (gradually updated) - var/number = 0 // Unused //TODEL - var/netexcess = 0 // excess power on the powernet (typically avail-load) - - - /datum/debug var/list/debuglist diff --git a/code/datums/wires/smartfridge.dm b/code/datums/wires/smartfridge.dm new file mode 100644 index 00000000000..b611497e796 --- /dev/null +++ b/code/datums/wires/smartfridge.dm @@ -0,0 +1,51 @@ +/datum/wires/smartfridge + holder_type = /obj/machinery/smartfridge + wire_count = 3 + +/datum/wires/smartfridge/secure + random = 1 + wire_count = 4 + +var/const/SMARTFRIDGE_WIRE_ELECTRIFY = 1 +var/const/SMARTFRIDGE_WIRE_THROW = 2 +var/const/SMARTFRIDGE_WIRE_IDSCAN = 4 + +/datum/wires/smartfridge/CanUse(var/mob/living/L) + var/obj/machinery/smartfridge/S = holder + if(!istype(L, /mob/living/silicon)) + if(S.seconds_electrified) + if(S.shock(L, 100)) + return 0 + if(S.panel_open) + return 1 + return 0 + +/datum/wires/smartfridge/GetInteractWindow() + var/obj/machinery/smartfridge/S = holder + . += ..() + . += "
The orange light is [S.seconds_electrified ? "off" : "on"].
" + . += "The red light is [S.shoot_inventory ? "off" : "blinking"].
" + . += "A [S.scan_id ? "purple" : "yellow"] light is on.
" + +/datum/wires/smartfridge/UpdatePulsed(var/index) + var/obj/machinery/smartfridge/S = holder + switch(index) + if(SMARTFRIDGE_WIRE_THROW) + S.shoot_inventory = !S.shoot_inventory + if(SMARTFRIDGE_WIRE_ELECTRIFY) + S.seconds_electrified = 30 + if(SMARTFRIDGE_WIRE_IDSCAN) + S.scan_id = !S.scan_id + +/datum/wires/smartfridge/UpdateCut(var/index, var/mended) + var/obj/machinery/smartfridge/S = holder + switch(index) + if(SMARTFRIDGE_WIRE_THROW) + S.shoot_inventory = !mended + if(SMARTFRIDGE_WIRE_ELECTRIFY) + if(mended) + S.seconds_electrified = 0 + else + S.seconds_electrified = -1 + if(SMARTFRIDGE_WIRE_IDSCAN) + S.scan_id = 1 diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm index d6ff953e598..5ed3ac97e67 100644 --- a/code/game/area/Space Station 13 areas.dm +++ b/code/game/area/Space Station 13 areas.dm @@ -35,7 +35,6 @@ NOTE: there are two lists of areas in the end of this file: centcom and station var/eject = null var/debug = 0 - var/powerupdate = 10 //We give everything 10 ticks to settle out it's power usage. var/requires_power = 1 var/always_unpowered = 0 //this gets overriden to 1 for space in area/New() diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index b0914724fa6..d457ae4e3df 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -16,7 +16,6 @@ icon_state = "" layer = 10 uid = ++global_uid - active_areas += src all_areas += src if(type == /area) // override defaults for space. TODO: make space areas of type /area/space rather than /area @@ -245,7 +244,7 @@ if(radalert) // always show the radiation alert, regardless of power icon_state = "radiation" blend_mode = BLEND_MULTIPLY - else if ((fire || eject || party) && ((!requires_power)?(!requires_power):power_environ))//If it doesn't require power, can still activate this proc. + else if ((fire || eject || party) && (!requires_power||power_environ))//If it doesn't require power, can still activate this proc. if(fire && !radalert && !eject && !party) icon_state = "red" blend_mode = BLEND_MULTIPLY @@ -297,9 +296,8 @@ // called when power status changes /area/proc/power_change() - powerupdate = 2 for(var/obj/machinery/M in src) // for each machine in the area - M.power_change() // reverify power status (to update icons etc.) + M.power_change() // reverify power status (to update icons etc.) if (fire || eject || party) updateicon() diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index ae10059f504..c8c6af0161d 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -16,11 +16,14 @@ var/mob/pulledby = null var/area/areaMaster + + var/auto_init = 1 /atom/movable/New() . = ..() areaMaster = get_area_master(src) - + if(auto_init && ticker && ticker.current_state == GAME_STATE_PLAYING) + initialize() /atom/movable/Destroy() for(var/atom/movable/AM in contents) @@ -32,6 +35,9 @@ pulledby = null ..() return QDEL_HINT_QUEUE + +/atom/movable/proc/initialize() + return // Used in shuttle movement and AI eye stuff. // Primarily used to notify objects being moved by a shuttle/bluespace fuckup. diff --git a/code/game/gamemodes/events/power_failure.dm b/code/game/gamemodes/events/power_failure.dm index ed1c7fbded2..70c3619494d 100644 --- a/code/game/gamemodes/events/power_failure.dm +++ b/code/game/gamemodes/events/power_failure.dm @@ -10,10 +10,13 @@ var/area/current_area = get_area(S) if(current_area.type in skipped_areas || !(S.z in config.station_levels)) continue + S.last_charge = S.charge + S.last_output_attempt = S.output_attempt + S.last_input_attempt = S.input_attempt S.charge = 0 - S.output = 0 - S.online = 0 - S.updateicon() + S.inputting(0) + S.outputting(0) + S.update_icon() S.power_change() @@ -40,10 +43,10 @@ var/area/current_area = get_area(S) if(current_area.type in skipped_areas || !(S.z in config.station_levels)) continue - S.charge = S.capacity - S.output = 200000 - S.online = 1 - S.updateicon() + S.charge = S.last_charge + S.output_attempt = S.last_output_attempt + S.input_attempt = S.last_input_attempt + S.update_icon() S.power_change() /proc/power_restore_quick(var/announce = 1) @@ -54,7 +57,8 @@ if(S.z != 1) continue S.charge = S.capacity - S.output = 200000 - S.online = 1 - S.updateicon() + S.output_level = S.output_level_max + S.output_attempt = 1 + S.input_attempt = 1 + S.update_icon() S.power_change() diff --git a/code/game/gamemodes/gameticker.dm b/code/game/gamemodes/gameticker.dm index 5880c2bee59..3d816ee1715 100644 --- a/code/game/gamemodes/gameticker.dm +++ b/code/game/gamemodes/gameticker.dm @@ -1,11 +1,5 @@ var/global/datum/controller/gameticker/ticker -#define GAME_STATE_PREGAME 1 -#define GAME_STATE_SETTING_UP 2 -#define GAME_STATE_PLAYING 3 -#define GAME_STATE_FINISHED 4 - - /datum/controller/gameticker var/const/restart_timeout = 600 var/current_state = GAME_STATE_PREGAME diff --git a/code/game/machinery/alarm.dm b/code/game/machinery/alarm.dm index 753d53e9e3a..6643776eaa8 100644 --- a/code/game/machinery/alarm.dm +++ b/code/game/machinery/alarm.dm @@ -141,8 +141,6 @@ pixel_x = (dir & 3)? 0 : (dir == 4 ? -24 : 24) pixel_y = (dir & 3)? (dir ==1 ? -24 : 24) : 0 update_icon() - if(ticker && ticker.current_state == GAME_STATE_PLAYING) - initialize() return first_run() @@ -528,19 +526,6 @@ E.update_icon()*/ - -/obj/machinery/alarm/proc/shock(mob/user, prb) - if((stat & (NOPOWER))) // unpowered, no shock - return 0 - if(!prob(prb)) - return 0 //you lucked out, no shock for you - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() //sparks always. - if (electrocute_mob(user, get_area(src), src)) - return 1 - else - return 0 /////////////// //END HACKING// /////////////// @@ -626,6 +611,7 @@ /obj/machinery/alarm/proc/get_nano_data(mob/user, fromAtmosConsole=0, AtmosConsoleEmagged=0) var/data[0] + data["name"] = sanitize(name) data["air"]=ui_air_status() data["alarmActivated"]=alarmActivated || local_danger_level==2 data["sensors"]=TLV diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm index 5e0e3c5689f..ea13b442df4 100644 --- a/code/game/machinery/atmo_control.dm +++ b/code/game/machinery/atmo_control.dm @@ -87,6 +87,8 @@ obj/machinery/air_sensor process() if(on) + if(!radio_connection) + return var/datum/signal/signal = new signal.transmission_method = 1 //radio signal signal.data["tag"] = id_tag @@ -128,12 +130,6 @@ obj/machinery/air_sensor initialize() set_frequency(frequency) - New() - ..() - - if(radio_controller) - set_frequency(frequency) - Destroy() if(radio_controller) radio_controller.remove_object(src,frequency) @@ -533,6 +529,8 @@ legend { send_signal(list("tag"=device, "status")) proc/send_signal(var/list/data) + if(!radio_connection) + return var/datum/signal/signal = new signal.transmission_method = 1 //radio signal signal.source = src diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index 4f8d02e0411..5df74641bcc 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -500,19 +500,6 @@ dat += "[D.materials["$glass"] / coeff] glass" return dat -/obj/machinery/autolathe/proc/shock(mob/user, prb) - if(stat & (BROKEN|NOPOWER)) // unpowered, no shock - return 0 - if(!prob(prb)) - return 0 - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - if (electrocute_mob(user, get_area(src), src, 0.7)) - return 1 - else - return 0 - /obj/machinery/autolathe/proc/adjust_hacked(var/hack) hacked = hack diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 289a67ca432..dcecc4aa34d 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -113,11 +113,6 @@ src.view_range = num cameranet.updateVisibility(src, 0) -/obj/machinery/camera/proc/shock(var/mob/living/user) - if(!istype(user)) - return - user.electrocute_act(10, src) - /obj/machinery/camera/attackby(W as obj, mob/living/user as mob, params) invalidateCameraCache() var/msg = "You attach [W] into the assembly inner circuits." diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm index 35197a7c7d1..c7bd2c86388 100644 --- a/code/game/machinery/computer/computer.dm +++ b/code/game/machinery/computer/computer.dm @@ -14,11 +14,6 @@ var/light_range_on = 2 var/light_power_on = 1 -/obj/machinery/computer/New() - ..() - if(ticker) - initialize() - /obj/machinery/computer/initialize() power_change() update_icon() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 48d4050ff51..6eaa50bc1f1 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -450,25 +450,19 @@ About the new airlock wires panel: // shock user with probability prb (if all connections & power are working) // returns 1 if shocked, 0 otherwise // The preceding comment was borrowed from the grille's shock script -/obj/machinery/door/airlock/proc/shock(mob/user, prb) - if((stat & (NOPOWER)) || !src.arePowerSystemsOn()) // unpowered, no shock +/obj/machinery/door/airlock/shock(mob/user, prb) + if(!arePowerSystemsOn()) return 0 if(hasShocked) return 0 //Already shocked someone recently? - if(!prob(prb)) - return 0 //you lucked out, no shock for you - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() //sparks always. - if(electrocute_mob(user, get_area(src), src)) + if(..()) hasShocked = 1 - spawn(10) - hasShocked = 0 + sleep(10) + hasShocked = 0 return 1 else return 0 - /obj/machinery/door/airlock/update_icon() if(overlays) overlays.Cut() overlays = list() diff --git a/code/game/machinery/floodlight.dm b/code/game/machinery/floodlight.dm index 37a892f940b..1bba12b0113 100644 --- a/code/game/machinery/floodlight.dm +++ b/code/game/machinery/floodlight.dm @@ -31,6 +31,9 @@ set_light(0) src.visible_message("[src] shuts down due to lack of power!") return + +/obj/machinery/floodlight/attack_ai(mob/user as mob) + return /obj/machinery/floodlight/attack_hand(mob/user as mob) if(open && cell) diff --git a/code/game/machinery/kitchen/smartfridge.dm b/code/game/machinery/kitchen/smartfridge.dm index d1354d17c9d..81ae816fb42 100644 --- a/code/game/machinery/kitchen/smartfridge.dm +++ b/code/game/machinery/kitchen/smartfridge.dm @@ -14,9 +14,28 @@ var/global/max_n_of_items = 999 // Sorry but the BYOND infinite loop detector doesn't look things over 1000. var/icon_on = "smartfridge" var/icon_off = "smartfridge-off" + var/icon_panel = "smartfridge-panel" var/item_quants = list() - var/ispowered = 1 //starts powered - var/isbroken = 0 + var/seconds_electrified = 0; + var/shoot_inventory = 0 + var/locked = 0 + var/scan_id = 1 + var/is_secure = 0 + var/datum/wires/smartfridge/wires = null + +/obj/machinery/smartfridge/secure + is_secure = 1 + +/obj/machinery/smartfridge/New() + ..() + if(is_secure) + wires = new/datum/wires/smartfridge/secure(src) + else + wires = new/datum/wires/smartfridge(src) + +/obj/machinery/smartfridge/Destroy() + qdel(wires) + ..() /obj/machinery/smartfridge/proc/accept_check(var/obj/item/O as obj) if(istype(O,/obj/item/weapon/reagent_containers/food/snacks/grown/) || istype(O,/obj/item/seeds/)) @@ -58,27 +77,47 @@ return 1 return 0 +/obj/machinery/smartfridge/process() + if(stat & (BROKEN|NOPOWER)) + return + if(src.seconds_electrified > 0) + src.seconds_electrified-- + if(src.shoot_inventory && prob(2)) + src.throw_item() /obj/machinery/smartfridge/power_change() - if( powered() ) - src.ispowered = 1 - stat &= ~NOPOWER - if(!isbroken) - icon_state = icon_on + var/old_stat = stat + ..() + if(old_stat != stat) + update_icon() + +/obj/machinery/smartfridge/update_icon() + if(stat & (BROKEN|NOPOWER)) + icon_state = icon_off else - spawn(rand(0, 15)) - src.ispowered = 0 - stat |= NOPOWER - if(!isbroken) - icon_state = icon_off + icon_state = icon_on /******************* * Item Adding ********************/ -/obj/machinery/smartfridge/attackby(var/obj/item/O as obj, var/mob/user as mob, params) - if(!src.ispowered) +/obj/machinery/smartfridge/attackby(var/obj/item/O as obj, var/mob/user as mob) + if(istype(O, /obj/item/weapon/screwdriver)) + panel_open = !panel_open + user.visible_message("[user] [panel_open ? "opens" : "closes"] the maintenance panel of \the [src].", "You [panel_open ? "open" : "close"] the maintenance panel of \the [src].") + overlays.Cut() + if(panel_open) + overlays += image(icon, icon_panel) + nanomanager.update_uis(src) + return + + if(istype(O, /obj/item/device/multitool)||istype(O, /obj/item/weapon/wirecutters)) + if(panel_open) + attack_hand(user) + return + + if(stat & NOPOWER) user << "\The [src] is unpowered and useless." return @@ -87,21 +126,18 @@ user << "\The [src] is full." return 1 else - if(!user.unEquip(O)) - usr << "\the [O] is stuck to your hand, you cannot put it in \the [src]" - return + user.remove_from_mob(O) O.loc = src if(item_quants[O.name]) item_quants[O.name]++ else item_quants[O.name] = 1 - user.visible_message("[user] has added \the [O] to \the [src].", \ - "You add \the [O] to \the [src].") + user.visible_message("[user] has added \the [O] to \the [src].", "You add \the [O] to \the [src].") nanomanager.update_uis(src) - else if(istype(O, /obj/item/weapon/storage/bag/plants) || istype(O, /obj/item/weapon/storage/belt/medical)) - var/obj/item/weapon/storage/bag/plants/P = O + else if(istype(O, /obj/item/weapon/storage/bag)) + var/obj/item/weapon/storage/bag/P = O var/plants_loaded = 0 for(var/obj/G in P.contents) if(accept_check(G)) @@ -117,27 +153,23 @@ plants_loaded++ if(plants_loaded) - user.visible_message( \ - "[user] loads \the [src] with \the [P].", \ - "You load \the [src] with \the [P].") + user.visible_message("[user] loads \the [src] with \the [P].", "You load \the [src] with \the [P].") if(P.contents.len > 0) user << "Some items are refused." nanomanager.update_uis(src) - else user << "\The [src] smartly refuses [O]." return 1 - updateUsrDialog() - - /obj/machinery/smartfridge/attack_ai(mob/user as mob) - return 0 + attack_hand(user) /obj/machinery/smartfridge/attack_hand(mob/user as mob) - + if(stat & (NOPOWER|BROKEN)) + return + wires.Interact(user) ui_interact(user) //Drag pill bottle to fridge to empty it into the fridge @@ -145,7 +177,7 @@ if(!istype(over_object, /obj/item/weapon/storage/pill_bottle)) //Only pill bottles, please return - if(!src.ispowered) + if(stat & NOPOWER) user << "\The [src] is unpowered and useless." return @@ -170,8 +202,6 @@ if(P.contents.len > 0) user << "Some items are refused." nanomanager.update_uis(src) - updateUsrDialog() - /******************* * SmartFridge Menu @@ -182,38 +212,41 @@ var/data[0] data["contents"] = null + data["electrified"] = seconds_electrified > 0 + data["shoot_inventory"] = shoot_inventory + data["locked"] = locked + data["secure"] = is_secure var/list/items[0] for (var/i=1 to length(item_quants)) var/K = item_quants[i] var/count = item_quants[K] - if (count > 0) + if(count > 0) items.Add(list(list("display_name" = html_encode(capitalize(K)), "vend" = i, "quantity" = count))) - if (items.len > 0) + if(items.len > 0) data["contents"] = items - ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) + if(!ui) ui = new(user, src, ui_key, "smartfridge.tmpl", src.name, 400, 500) ui.set_initial_data(data) ui.open() /obj/machinery/smartfridge/Topic(href, href_list) - if (..()) return 0 + if(..()) return 0 var/mob/user = usr var/datum/nanoui/ui = nanomanager.get_open_ui(user, src, "main") src.add_fingerprint(user) - if (href_list["close"]) + if(href_list["close"]) user.unset_machine() ui.close() return 0 - if (href_list["vend"]) + if(href_list["vend"]) var/index = text2num(href_list["vend"]) var/amount = text2num(href_list["amount"]) var/K = item_quants[index] @@ -225,14 +258,47 @@ var/i = amount for(var/obj/O in contents) - if (O.name == K) + if(O.name == K) O.loc = loc i-- - if (i <= 0) + if(i <= 0) return 1 return 1 - return 0 +/obj/machinery/smartfridge/proc/throw_item() + var/obj/throw_item = null + var/mob/living/target = locate() in view(7,src) + if(!target) + return 0 + for (var/O in item_quants) + if(item_quants[O] <= 0) //Try to use a record that actually has something to dump. + continue + + item_quants[O]-- + for(var/obj/T in contents) + if(T.name == O) + T.loc = src.loc + throw_item = T + break + break + if(!throw_item) + return 0 + spawn(0) + throw_item.throw_at(target,16,3,src) + src.visible_message("[src] launches [throw_item.name] at [target.name]!") + return 1 + +/************************ +* Secure SmartFridges +*************************/ + +/obj/machinery/smartfridge/secure/Topic(href, href_list) + if(stat & (NOPOWER|BROKEN)) return 0 + if(usr.contents.Find(src) || (in_range(src, usr) && istype(loc, /turf))) + if(!allowed(usr) && !emagged && locked != -1 && href_list["vend"]) + usr << "Access denied." + return 0 + return ..() \ No newline at end of file diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 4b054a58f42..266111cfe2b 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -353,9 +353,6 @@ Class Procs: src.add_fingerprint(user) - var/area/A = get_area(src) - A.powerupdate = 1 - return 0 /obj/machinery/CheckParts() @@ -524,3 +521,17 @@ Class Procs: threatcount += 4 return threatcount + + +/obj/machinery/proc/shock(mob/user, prb) + if(inoperable()) + return 0 + if(!prob(prb)) + return 0 + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + if (electrocute_mob(user, get_area(src), src, 0.7)) + if(user.stunned) + return 1 + return 0 diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index efbef9c5670..a841267faa3 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -1016,18 +1016,6 @@ if (WIRE_SCANID) locked = !locked */ -/obj/machinery/suit_cycler/proc/shock(mob/user, prb) - if(stat & (BROKEN|NOPOWER)) // unpowered, no shock - return 0 - if(!prob(prb)) - return 0 - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - if (electrocute_mob(user, get_area(src), src, 0.7)) - return 1 - else - return 0 //There HAS to be a less bloated way to do this. TODO: some kind of table/icon name coding? ~Z /obj/machinery/suit_cycler/proc/apply_paintjob() diff --git a/code/game/machinery/syndicatebeacon.dm b/code/game/machinery/syndicatebeacon.dm index 667ebfddde8..915def6bdda 100644 --- a/code/game/machinery/syndicatebeacon.dm +++ b/code/game/machinery/syndicatebeacon.dm @@ -192,7 +192,7 @@ return PROCESS_KILL else if(surplus() > 1500) - add_load(1500) + draw_power(1500) else Deactivate() diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 98ef44fdfc6..bf33153a3a8 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -677,20 +677,6 @@ src.visible_message("\red [src] launches [throw_item.name] at [target.name]!") return 1 - -/obj/machinery/vending/proc/shock(mob/user, prb) - if(stat & (BROKEN|NOPOWER)) // unpowered, no shock - return 0 - if(!prob(prb)) - return 0 - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - if(electrocute_mob(user, get_area(src), src, 0.7)) - return 1 - else - return 0 - /* * Vending machine types */ diff --git a/code/game/mecha/equipment/tools/medical_tools.dm b/code/game/mecha/equipment/tools/medical_tools.dm index 4d5f134662d..685a65ad15e 100644 --- a/code/game/mecha/equipment/tools/medical_tools.dm +++ b/code/game/mecha/equipment/tools/medical_tools.dm @@ -372,9 +372,7 @@ if(!PN) PN = new() - powernets += PN - NC.powernet = PN - PN.cables += NC + PN.add_cable(NC) NC.mergeConnectedNetworks(NC.d2) //NC.mergeConnectedNetworksOnTurf() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 160843152c6..ab0454b9c61 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -701,4 +701,7 @@ spawn(0) //this is needed or multiple items will be thrown sequentially and not simultaneously if(current_size >= STAGE_FOUR) throw_at(S,14,3) - else ..() \ No newline at end of file + else ..() + +/obj/item/proc/pwr_drain() + return 0 // Process Kill \ No newline at end of file diff --git a/code/game/objects/items/devices/PDA/cart.dm b/code/game/objects/items/devices/PDA/cart.dm index 4add16544fc..4e6c9c3f2ef 100644 --- a/code/game/objects/items/devices/PDA/cart.dm +++ b/code/game/objects/items/devices/PDA/cart.dm @@ -59,10 +59,9 @@ icon_state = "cart-s" access_security = 1 -/obj/item/weapon/cartridge/security/New() +/obj/item/weapon/cartridge/security/initialize() + radio = new /obj/item/radio/integrated/beepsky(src) ..() - spawn(5) - radio = new /obj/item/radio/integrated/beepsky(src) /obj/item/weapon/cartridge/detective name = "D.E.T.E.C.T. Cartridge" @@ -111,12 +110,9 @@ access_reagent_scanner = 1 access_atmos = 1 -/obj/item/weapon/cartridge/signal/New() +/obj/item/weapon/cartridge/signal/initialize() + radio = new /obj/item/radio/integrated/signal(src) ..() - spawn(5) - radio = new /obj/item/radio/integrated/signal(src) - - /obj/item/weapon/cartridge/quartermaster name = "Space Parts & Space Vendors Cartridge" @@ -124,10 +120,9 @@ icon_state = "cart-q" access_quartermaster = 1 -/obj/item/weapon/cartridge/quartermaster/New() +/obj/item/weapon/cartridge/quartermaster/initialize() + radio = new /obj/item/radio/integrated/mule(src) ..() - spawn(5) - radio = new /obj/item/radio/integrated/mule(src) /obj/item/weapon/cartridge/head name = "Easy-Record DELUXE" @@ -142,10 +137,9 @@ access_janitor = 1 access_security = 1 -/obj/item/weapon/cartridge/hop/New() +/obj/item/weapon/cartridge/hop/initialize() + radio = new /obj/item/radio/integrated/mule(src) ..() - spawn(5) - radio = new /obj/item/radio/integrated/mule(src) /obj/item/weapon/cartridge/hos name = "R.O.B.U.S.T. DELUXE" @@ -153,10 +147,9 @@ access_status_display = 1 access_security = 1 -/obj/item/weapon/cartridge/hos/New() +/obj/item/weapon/cartridge/hos/initialize() + radio = new /obj/item/radio/integrated/beepsky(src) ..() - spawn(5) - radio = new /obj/item/radio/integrated/beepsky(src) /obj/item/weapon/cartridge/ce name = "Power-On DELUXE" @@ -179,10 +172,9 @@ access_reagent_scanner = 1 access_atmos = 1 -/obj/item/weapon/cartridge/rd/New() - ..() - spawn(5) - radio = new /obj/item/radio/integrated/signal(src) +/obj/item/weapon/cartridge/rd/initialize() + radio = new /obj/item/radio/integrated/signal(src) + ..() /obj/item/weapon/cartridge/captain name = "Value-PAK Cartridge" @@ -197,10 +189,9 @@ access_status_display = 1 access_atmos = 1 -/obj/item/weapon/cartridge/captain/New() - ..() - spawn(5) - radio = new /obj/item/radio/integrated/beepsky(src) +/obj/item/weapon/cartridge/captain/initialize() + radio = new /obj/item/radio/integrated/beepsky(src) + ..() /obj/item/weapon/cartridge/supervisor name = "Easy-Record DELUXE" @@ -221,10 +212,9 @@ access_status_display = 1 access_atmos = 1 -/obj/item/weapon/cartridge/centcom/New() +/obj/item/weapon/cartridge/centcom/initialize() + radio = new /obj/item/radio/integrated/beepsky(src) ..() - spawn(5) - radio = new /obj/item/radio/integrated/beepsky(src) /obj/item/weapon/cartridge/syndicate name = "Detomatix Cartridge" @@ -294,29 +284,32 @@ /* Power Monitor (Mode: 43 / 433) */ if(mode==43 || mode==433) var/pMonData[0] + var/apcData[0] for(var/obj/machinery/computer/monitor/pMon in world) if(!(pMon.stat & (NOPOWER|BROKEN)) ) pMonData[++pMonData.len] = list ("Name" = pMon.name, "ref" = "\ref[pMon]") - if(isnull(powmonitor)) powmonitor = pMon - + if(isnull(powmonitor)) powmonitor = pMon + values["powermonitors"] = pMonData - values["poweravail"] = powmonitor.powernet.avail - values["powerload"] = num2text(powmonitor.powernet.viewload,10) + if (!isnull(powmonitor.powernet)) + values["powerconnected"] = 1 + values["poweravail"] = powmonitor.powernet.avail + values["powerload"] = num2text(powmonitor.powernet.viewload,10) + var/list/L = list() + for(var/obj/machinery/power/terminal/term in powmonitor.powernet.nodes) + if(istype(term.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = term.master + L += A - var/list/L = list() - for(var/obj/machinery/power/terminal/term in powmonitor.powernet.nodes) - if(istype(term.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = term.master - L += A - - var/list/Status = list(0,0,1,1) // Status: off, auto-off, on, auto-on - var/list/chg = list(0,1,1) // Charging: nope, charging, full - var/apcData[0] - for(var/obj/machinery/power/apc/A in L) - apcData[++apcData.len] = list("Name" = html_encode(A.area.name), "Equipment" = Status[A.equipment+1], "Lights" = Status[A.lighting+1], "Environment" = Status[A.environ+1], "CellPct" = A.cell ? round(A.cell.percent(),1) : -1, "CellStatus" = A.cell ? chg[A.charging+1] : 0) - - values["apcs"] = apcData + var/list/Status = list(0,0,1,1) // Status: off, auto-off, on, auto-on + var/list/chg = list(0,1,1) // Charging: nope, charging, full + for(var/obj/machinery/power/apc/A in L) + apcData[++apcData.len] = list("Name" = html_encode(A.area.name), "Equipment" = Status[A.equipment+1], "Lights" = Status[A.lighting+1], "Environment" = Status[A.environ+1], "CellPct" = A.cell ? round(A.cell.percent(),1) : -1, "CellStatus" = A.cell ? chg[A.charging+1] : 0) + + values["apcs"] = apcData + else + values["powerconnected"] = 0 diff --git a/code/game/objects/items/devices/PDA/radio.dm b/code/game/objects/items/devices/PDA/radio.dm index bc631aece49..aa5e34eba90 100644 --- a/code/game/objects/items/devices/PDA/radio.dm +++ b/code/game/objects/items/devices/PDA/radio.dm @@ -229,17 +229,14 @@ var/last_transmission var/datum/radio_frequency/radio_connection -/obj/item/radio/integrated/signal/New() - ..() - if(radio_controller) - initialize() - /obj/item/radio/integrated/signal/Destroy() if(radio_controller) radio_controller.remove_object(src, frequency) return ..() /obj/item/radio/integrated/signal/initialize() + if(!radio_controller) + return if (src.frequency < 1441 || src.frequency > 1489) src.frequency = sanitize_frequency(src.frequency) diff --git a/code/game/objects/items/devices/powersink.dm b/code/game/objects/items/devices/powersink.dm index 7b0ea895654..336ac002671 100644 --- a/code/game/objects/items/devices/powersink.dm +++ b/code/game/objects/items/devices/powersink.dm @@ -1,8 +1,8 @@ // Powersink - used to drain station power /obj/item/device/powersink - desc = "A nulling power sink which drains energy from electrical systems." name = "power sink" + desc = "A nulling power sink which drains energy from electrical systems." icon_state = "powersink0" item_state = "electronic" w_class = 4.0 @@ -13,114 +13,119 @@ m_amt = 750 w_amt = 750 origin_tech = "powerstorage=3;syndicate=5" - var/drain_rate = 600000 // amount of power to drain per tick - var/power_drained = 0 // has drained this much power - var/max_power = 1e8 // maximum power that can be drained before exploding - var/mode = 0 // 0 = off, 1=clamped (off), 2=operating - + var/drain_rate = 1500000 // amount of power to drain per tick + var/apc_drain_rate = 5000 // Max. amount drained from single APC. In Watts. + var/dissipation_rate = 20000 // Passive dissipation of drained power. In Watts. + var/power_drained = 0 // Amount of power drained. + var/max_power = 5e9 // Detonation point. + var/mode = 0 // 0 = off, 1=clamped (off), 2=operating + var/drained_this_tick = 0 // This is unfortunately necessary to ensure we process powersinks BEFORE other machinery such as APCs. + var/datum/powernet/PN // Our powernet var/obj/structure/cable/attached // the attached cable - attackby(var/obj/item/I, var/mob/user, params) - if(istype(I, /obj/item/weapon/screwdriver)) - if(mode == 0) - var/turf/T = loc - if(isturf(T) && !T.intact) - attached = locate() in T - if(!attached) - user << "No exposed cable here to attach to." - return - else -// attached.attached = src - anchored = 1 - mode = 1 - user << "You attach the device to the cable." - for(var/mob/M in viewers(user)) - if(M == user) continue - M << "[user] attaches the power sink to the cable." - return +/obj/item/device/powersink/Destroy() + processing_objects.Remove(src) + processing_power_items.Remove(src) + ..() + +/obj/item/device/powersink/attackby(var/obj/item/I, var/mob/user) + if(istype(I, /obj/item/weapon/screwdriver)) + if(mode == 0) + var/turf/T = loc + if(isturf(T) && !T.intact) + attached = locate() in T + if(!attached) + user << "No exposed cable here to attach to." + return else - user << "Device must be placed over an exposed cable to attach to it." + anchored = 1 + mode = 1 + src.visible_message("[user] attaches [src] to the cable!") return else - if (mode == 2) - processing_objects.Remove(src) // Now the power sink actually stops draining the station's power if you unhook it. --NeoFite - anchored = 0 - mode = 0 - user << "You detach the device from the cable." -// attached.attached = null - attached = null - for(var/mob/M in viewers(user)) - if(M == user) continue - M << "[user] detaches the power sink from the cable." - set_light(0) - icon_state = "powersink0" - + user << "Device must be placed over an exposed cable to attach to it." return else + if (mode == 2) + processing_objects.Remove(src) // Now the power sink actually stops draining the station's power if you unhook it. --NeoFite + processing_power_items.Remove(src) + anchored = 0 + mode = 0 + src.visible_message("[user] detaches [src] from the cable!") + set_light(0) + icon_state = "powersink0" + + return + else + ..() + +/obj/item/device/powersink/attack_ai() + return + +/obj/item/device/powersink/attack_hand(var/mob/user) + switch(mode) + if(0) ..() + if(1) + src.visible_message("[user] activates [src]!") + mode = 2 + icon_state = "powersink1" + processing_objects.Add(src) + processing_power_items.Add(src) + if(2) //This switch option wasn't originally included. It exists now. --NeoFite + src.visible_message("[user] deactivates [src]!") + mode = 1 + set_light(0) + icon_state = "powersink0" + processing_objects.Remove(src) + processing_power_items.Remove(src) - Destroy() - set_light(0) - processing_objects.Remove(src) -// attached.attached = null - attached = null - return ..() +/obj/item/device/powersink/pwr_drain() + if(!attached) + return 0 + + if(drained_this_tick) + return 1 + drained_this_tick = 1 + + var/drained = 0 + + if(!PN) + return 1 + + set_light(12) + PN.trigger_warning() + // found a powernet, so drain up to max power from it + drained = PN.draw_power(drain_rate) + // if tried to drain more than available on powernet + // now look for APCs and drain their cells + if(drained < drain_rate) + for(var/obj/machinery/power/terminal/T in PN.nodes) + // Enough power drained this tick, no need to torture more APCs + if(drained >= drain_rate) + break + if(istype(T.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = T.master + if(A.operating && A.cell) + var/cur_charge = A.cell.charge / CELLRATE + var/drain_val = min(apc_drain_rate, cur_charge) + A.cell.use(drain_val * CELLRATE) + drained += drain_val + power_drained += drained + return 1 - attack_ai() +/obj/item/device/powersink/process() + drained_this_tick = 0 + power_drained -= min(dissipation_rate, power_drained) + if(power_drained > max_power * 0.95) + playsound(src, 'sound/effects/screech.ogg', 100, 1, 1) + if(power_drained >= max_power) + explosion(src.loc, 3,6,9,12) + qdel(src) return - - attack_hand(var/mob/user) - switch(mode) - if(0) - ..() - - if(1) - user << "You activate the device!" - for(var/mob/M in viewers(user)) - if(M == user) continue - M << "[user] activates the power sink!" - mode = 2 - icon_state = "powersink1" - processing_objects.Add(src) - - if(2) //This switch option wasn't originally included. It exists now. --NeoFite - user << "You deactivate the device!" - for(var/mob/M in viewers(user)) - if(M == user) continue - M << "[user] deactivates the power sink!" - mode = 1 - set_light(0) - icon_state = "powersink0" - processing_objects.Remove(src) - - process() - if(attached) - var/datum/powernet/PN = attached.powernet - if(PN) - set_light(12) - - // found a powernet, so drain up to max power from it - - var/drained = min ( drain_rate, PN.avail ) - PN.load += drained - power_drained += drained - - // if tried to drain more than available on powernet - // now look for APCs and drain their cells - if(drained < drain_rate) - for(var/obj/machinery/power/terminal/T in PN.nodes) - if(istype(T.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = T.master - if(A.operating && A.cell) - A.cell.charge = max(0, A.cell.charge - 50) - power_drained += 50 - - - if(power_drained > max_power * 0.95) - playsound(src, 'sound/effects/screech.ogg', 100, 1, 1) - if(power_drained >= max_power) - processing_objects.Remove(src) - explosion(src.loc, 3,6,9,12) - qdel(src) + if(attached && attached.powernet) + PN = attached.powernet + else + PN = null diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 2736ccd4260..08e4b53d212 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -191,6 +191,9 @@ /obj/item/stack/proc/get_amount() return amount + +/obj/item/stack/proc/get_max_amount() + return max_amount /obj/item/stack/attack_hand(mob/user as mob) if (user.get_inactive_hand() == src) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 7000b0c01a6..595bd5cad2d 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -83,9 +83,6 @@ else return null -/atom/movable/proc/initialize() - return - /obj/proc/updateUsrDialog() if(in_use) var/is_in_use = 0 diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 93dc0fba5b9..6c82fc63185 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -1139,7 +1139,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that for(var/obj/machinery/power/smes/SMES in world) if(SMES.anchored) - SMES.chargemode = 1 + SMES.input_attempt = 1 /client/proc/cmd_debug_mob_lists() set category = "Debug" diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index 869720b1d43..57b23b30813 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -18,7 +18,7 @@ var/delay = 0 var/datum/radio_frequency/radio_connection var/airlock_wire = null - + New() ..() if(!radio_controller) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index d0bbbadb3fc..03dc1c4eac9 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -54,7 +54,7 @@ var/areastring = null var/obj/item/weapon/stock_parts/cell/cell var/start_charge = 90 // initial cell charge % - var/cell_type = 2500 // 0=no cell, 1=regular, 2=high-cap (x5) <- old, now it's just 0=no cell, otherwise dictate cellcapacity by changing this value. 1 used to be 1000, 2 was 2500 + var/cell_type = 2500 var/opened = 0 //0=closed, 1=opened, 2=cover removed var/shorted = 0 var/lighting = 3 @@ -84,12 +84,13 @@ var/has_electronics = 0 // 0 - none, 1 - plugged in, 2 - secured by screwdriver var/overload = 1 //used for the Blackout malf module var/beenhit = 0 // used for counting how many times it has been hit, used for Aliens at the moment - var/mob/living/silicon/ai/occupant = null + var/mob/living/silicon/ai/occupier = null var/longtermpower = 10 var/update_state = -1 var/update_overlay = -1 var/global/status_overlays = 0 var/updating_icon = 0 + var/standard_max_charge var/datum/wires/apc/wires = null //var/debug = 0 var/global/list/status_overlays_lock @@ -99,14 +100,21 @@ var/global/list/status_overlays_environ var/indestructible = 0 // If set, prevents aliens from destroying it -/obj/machinery/power/apc/updateDialog() - if (stat & (BROKEN|MAINT)) - return - ..() +/obj/machinery/power/apc/connect_to_network() + //Override because the APC does not directly connect to the network; it goes through a terminal. + //The terminal is what the power computer looks for anyway. + if(!terminal) + make_terminal() + if(terminal) + terminal.connect_to_network() /obj/machinery/power/apc/New(turf/loc, var/ndir, var/building=0) ..() wires = new(src) + var/tmp/obj/item/weapon/stock_parts/cell/tmp_cell = new + standard_max_charge = tmp_cell.maxcharge + qdel(tmp_cell) + // offset 24 pixels in direction of dir // this allows the APC to be embedded in a wall, yet still inside an area if (building) @@ -128,8 +136,24 @@ src.update_icon() spawn(5) src.update() - - + +/obj/machinery/power/apc/Destroy() + if(malfai && operating) + if (ticker.mode.config_tag == "malfunction") + if (src.z == 1) //if (is_type_in_list(get_area(src), the_station_areas)) + ticker.mode:apcs-- + area.power_light = 0 + area.power_equip = 0 + area.power_environ = 0 + area.power_change() + if(occupier) + malfvacate(1) + qdel(wires) + if(cell) + qdel(cell) // qdel + if(terminal) + disconnect_terminal() + ..() /obj/machinery/power/apc/proc/make_terminal() // create a terminal object at the same position as original turf loc @@ -269,7 +293,7 @@ if(update & 2) if(overlays.len) - overlays = 0 + overlays.len = 0 if(!(stat & (BROKEN|MAINT)) && update_state & UPSTATE_ALLGOOD) overlays += status_overlays_lock[locked+1] @@ -342,10 +366,11 @@ return 0 if(last_update_state != update_state) results += 1 - if(last_update_overlay != update_overlay && update_overlay != 0) + if(last_update_overlay != update_overlay) results += 2 return results +// Used in process so it doesn't update the icon too much /obj/machinery/power/apc/proc/queue_icon_update() if(!updating_icon) @@ -372,29 +397,30 @@ if (istype(W, /obj/item/weapon/crowbar) && opened) if (has_electronics==1) if (terminal) - user << "\red Disconnect wires first." + user << "Disconnect wires first." return playsound(src.loc, 'sound/items/Crowbar.ogg', 50, 1) user << "You are trying to remove the power control board..." //lpeters - fixed grammar issues if(do_after(user, 50)) - has_electronics = 0 - if ((stat & BROKEN) || malfhack) - user.visible_message(\ - "\red [user.name] has broken the power control board inside [src.name]!",\ - "You broke the charred power control board and remove the remains.", - "You hear a crack!") - //ticker.mode:apcs-- //XSI said no and I agreed. -rastaf0 - else - user.visible_message(\ - "\red [user.name] has removed the power control board from [src.name]!",\ - "You remove the power control board.") - new /obj/item/weapon/module/power_control(loc) + if (has_electronics==1) + has_electronics = 0 + if ((stat & BROKEN) || malfhack) + user.visible_message(\ + "[user.name] has broken the power control board inside [src.name]!",\ + "You broke the charred power control board and remove the remains.", + "You hear a crack!") + //ticker.mode:apcs-- //XSI said no and I agreed. -rastaf0 + else + user.visible_message(\ + "[user.name] has removed the power control board from [src.name]!",\ + "You remove the power control board.") + new /obj/item/weapon/module/power_control(loc) else if (opened!=2) //cover isn't removed opened = 0 update_icon() else if (istype(W, /obj/item/weapon/crowbar) && !((stat & BROKEN) || malfhack) ) if(coverlocked && !(stat & MAINT)) - user << "\red The cover is locked and cannot be opened." + user << "The cover is locked and cannot be opened." return else opened = 1 @@ -405,20 +431,20 @@ return else if (stat & MAINT) - user << "\red There is no connector for your power cell." + user << "There is no connector for your power cell." return user.drop_item() W.loc = src cell = W user.visible_message(\ - "\red [user.name] has inserted the power cell to [src.name]!",\ + "[user.name] has inserted the power cell to [src.name]!",\ "You insert the power cell.") chargecount = 0 update_icon() else if (istype(W, /obj/item/weapon/screwdriver)) // haxing if(opened) if (cell) - user << "\red Close the APC first." //Less hints more mystery! + user << "Close the APC first." //Less hints more mystery! return else if (has_electronics==1 && terminal) @@ -432,7 +458,7 @@ playsound(src.loc, 'sound/items/Screwdriver.ogg', 50, 1) user << "You unfasten the electronics." else /* has_electronics==0 */ - user << "\red There is nothing to secure." + user << "There is nothing to secure." return update_icon() else if(emagged) @@ -457,87 +483,90 @@ user << "You [ locked ? "lock" : "unlock"] the APC interface." update_icon() else - user << "\red Access denied." + user << "Access denied." else if (istype(W, /obj/item/stack/cable_coil) && !terminal && opened && has_electronics!=2) if (src.loc:intact) - user << "\red You must remove the floor plating in front of the APC first." + user << "You must remove the floor plating in front of the APC first." return var/obj/item/stack/cable_coil/C = W if(C.amount < 10) - user << "\red You need more wires." + user << "You need more wires." return user << "You start adding cables to the APC frame..." playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) - if(do_after(user, 20) && C.amount >= 10) - var/turf/T = get_turf(src) - var/obj/structure/cable/N = T.get_cable_node() - if (prob(50) && electrocute_mob(usr, N, N)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - return - C.use(10) - user.visible_message(\ - "\red [user.name] has added cables to the APC frame!",\ - "You add cables to the APC frame.") - make_terminal() - terminal.connect_to_network() + if(do_after(user, 20)) + if (C.amount >= 10 && !terminal && opened && has_electronics != 2) + var/turf/T = get_turf(src) + var/obj/structure/cable/N = T.get_cable_node() + if (prob(50) && electrocute_mob(usr, N, N)) + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + return + C.use(10) + user.visible_message(\ + "[user.name] has added cables to the APC frame!",\ + "You add cables to the APC frame.") + make_terminal() + terminal.connect_to_network() else if (istype(W, /obj/item/weapon/wirecutters) && terminal && opened && has_electronics!=2) if (src.loc:intact) - user << "\red You must remove the floor plating in front of the APC first." + user << "You must remove the floor plating in front of the APC first." return user << "You begin to cut the cables..." playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) if(do_after(user, 50)) - if (prob(50) && electrocute_mob(usr, terminal.powernet, terminal)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - return - new /obj/item/stack/cable_coil(loc,10) - user.visible_message(\ - "\red [user.name] cut the cables and dismantled the power terminal.",\ - "You cut the cables and dismantle the power terminal.") - qdel(terminal) + if(terminal && opened && has_electronics!=2) + if (prob(50) && electrocute_mob(usr, terminal.powernet, terminal)) + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + return + new /obj/item/stack/cable_coil(loc,10) + user << "You cut the cables and dismantle the power terminal." + qdel(terminal) // qdel else if (istype(W, /obj/item/weapon/module/power_control) && opened && has_electronics==0 && !((stat & BROKEN) || malfhack)) user << "You trying to insert the power control board into the frame..." playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) if(do_after(user, 10)) - has_electronics = 1 - user << "You place the power control board inside the frame." - qdel(W) + if(has_electronics==0) + has_electronics = 1 + user << "You place the power control board inside the frame." + qdel(W) // qdel else if (istype(W, /obj/item/weapon/module/power_control) && opened && has_electronics==0 && ((stat & BROKEN) || malfhack)) - user << "\red You cannot put the board inside, the frame is damaged." + user << "You cannot put the board inside, the frame is damaged." return else if (istype(W, /obj/item/weapon/weldingtool) && opened && has_electronics==0 && !terminal) var/obj/item/weapon/weldingtool/WT = W if (WT.get_fuel() < 3) - user << "\blue You need more welding fuel to complete this task." + user << "You need more welding fuel to complete this task." return - user << "You start welding the APC frame..." + user.visible_message("[user.name] welds [src].", \ + "You start welding the APC frame...", \ + "You hear welding.") playsound(src.loc, 'sound/items/Welder.ogg', 50, 1) if(do_after(user, 50)) if(!src || !WT.remove_fuel(3, user)) return if (emagged || malfhack || (stat & BROKEN) || opened==2) new /obj/item/stack/sheet/metal(loc) user.visible_message(\ - "\red [src] has been cut apart by [user.name] with the weldingtool.",\ - "You disassembled the broken APC frame.",\ - "\red You hear welding.") + "[src] has been cut apart by [user.name] with the weldingtool.",\ + "You disassembled the broken APC frame.",\ + "You hear welding.") else new /obj/item/mounted/frame/apc_frame(loc) user.visible_message(\ - "\red [src] has been cut from the wall by [user.name] with the weldingtool.",\ - "You cut the APC frame from the wall.",\ - "\red You hear welding.") - qdel(src) + "[src] has been cut from the wall by [user.name] with the weldingtool.",\ + "You cut the APC frame from the wall.",\ + "You hear welding.") + qdel(src) // qdel return else if (istype(W, /obj/item/mounted/frame/apc_frame) && opened && emagged) emagged = 0 if (opened==2) opened = 1 user.visible_message(\ - "\red [user.name] has replaced the damaged APC frontal panel with a new one.",\ + "[user.name] has replaced the damaged APC frontal panel with a new one.",\ "You replace the damaged APC frontal panel with a new one.") qdel(W) update_icon() @@ -548,7 +577,7 @@ user << "You begin to replace the damaged APC frame..." if(do_after(user, 50)) user.visible_message(\ - "\red [user.name] has replaced the damaged APC frame with new one.",\ + "[user.name] has replaced the damaged APC frame with new one.",\ "You replace the damaged APC frame with new one.") qdel(W) stat &= ~BROKEN @@ -566,8 +595,8 @@ ) \ && prob(20) ) opened = 2 - user.visible_message("\red The APC cover was knocked down with the [W.name] by [user.name]!", \ - "\red You knock down the APC cover with your [W.name]!", \ + user.visible_message("The APC cover was knocked down with the [W.name] by [user.name]!", \ + "You knock down the APC cover with your [W.name]!", \ "You hear bang") update_icon() else @@ -578,8 +607,8 @@ istype(W, /obj/item/weapon/wirecutters) || istype(W, /obj/item/device/assembly/signaler))) return src.attack_hand(user) user.do_attack_animation(src) - user.visible_message("\red The [src.name] has been hit with the [W.name] by [user.name]!", \ - "\red You hit the [src.name] with your [W.name]!", \ + user.visible_message("The [src.name] has been hit with the [W.name] by [user.name]!", \ + "You hit the [src.name] with your [W.name]!", \ "You hear bang") /obj/machinery/power/apc/emag_act(user as mob) @@ -612,7 +641,7 @@ var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread s.set_up(3, 1, src) s.start() - H << "\red The APC power currents surge eratically, damaging your chassis!" + H << "The APC power currents surge eratically, damaging your chassis!" H.adjustFireLoss(10,0) else if(src.cell && src.cell.charge > 0) if(H.nutrition < 450) @@ -624,13 +653,13 @@ H.nutrition += src.cell.charge/10 src.cell.charge = 0 - user << "\blue You slot your fingers into the APC interface and siphon off some of the stored charge for your own use." + user << "You slot your fingers into the APC interface and siphon off some of the stored charge for your own use." if(src.cell.charge < 0) src.cell.charge = 0 if(H.nutrition > 500) H.nutrition = 500 src.charging = 1 else - user << "\blue You are already fully charged." + user << "You are already fully charged." else user << "There is no charge to draw from that APC." return @@ -645,7 +674,7 @@ cell.updateicon() src.cell = null - user.visible_message("\red [user.name] removes the power cell from [src.name]!", "You remove the power cell.") + user.visible_message("[user.name] removes the power cell from [src.name]!", "You remove the power cell.") //user << "You remove the power cell." charging = 0 src.update_icon() @@ -664,7 +693,7 @@ return user.changeNext_move(CLICK_CD_MELEE) user.do_attack_animation(src) - user.visible_message("\red [user.name] slashes at the [src.name]!", "\blue You slash at the [src.name]!") + user.visible_message("[user.name] slashes at the [src.name]!", "You slash at the [src.name]!") playsound(src.loc, 'sound/weapons/slash.ogg', 100, 1) var/allcut = wires.IsAllCut() @@ -672,12 +701,12 @@ if(beenhit >= pick(3, 4) && wiresexposed != 1) wiresexposed = 1 src.update_icon() - src.visible_message("\red The [src.name]'s cover flies open, exposing the wires!") + src.visible_message("The [src.name]'s cover flies open, exposing the wires!") else if(wiresexposed == 1 && allcut == 0) wires.CutAll() src.update_icon() - src.visible_message("\red The [src.name]'s wires are shredded!") + src.visible_message("The [src.name]'s wires are shredded!") else beenhit += 1 return @@ -700,7 +729,7 @@ /obj/machinery/power/apc/proc/get_malf_status(mob/user) if (ticker && ticker.mode && (user.mind in ticker.mode.malf_ai) && istype(user, /mob/living/silicon/ai)) if (src.malfai == (user:parent ? user:parent : user)) - if (src.occupant == user) + if (src.occupier == user) return 3 // 3 = User is shunted in this APC else if (istype(user.loc, /obj/machinery/power/apc)) return 4 // 4 = User is shunted in another APC @@ -801,7 +830,7 @@ /obj/machinery/power/apc/proc/can_use(mob/user as mob, var/loud = 0) //used by attack_hand() and Topic() if (user.stat && !isobserver(user)) - user << "\red You must be conscious to use this [src]!" + user << "You must be conscious to use this [src]!" return 0 if(stat & (NOPOWER|BROKEN)) return 0 @@ -811,13 +840,13 @@ return 0 if ( ! (istype(user, /mob/living/carbon/human) || \ istype(user, /mob/living/silicon))) - user << "\red You don't have the dexterity to use this [src]!" + user << "You don't have the dexterity to use this [src]!" return 0 if(user.restrained()) - user << "\red You must have free hands to use this [src]" + user << "You must have free hands to use this [src]" return 0 if(user.lying) - user << "\red You must stand to use this [src]!" + user << "You must stand to use this [src]!" return 0 autoflag = 5 if (istype(user, /mob/living/silicon)) @@ -832,7 +861,7 @@ ) \ ) if(!loud) - user << "\red \The [src] has AI control disabled!" + user << "\The [src] has AI control disabled!" user << browse(null, "window=apc") user.unset_machine() return 0 @@ -844,10 +873,10 @@ if (istype(H)) if(H.getBrainLoss() >= 60) for(var/mob/M in viewers(src, null)) - M << "\red [H] stares cluelessly at [src] and drools." + M << "[H] stares cluelessly at [src] and drools." return 0 else if(prob(H.getBrainLoss())) - user << "\red You momentarily forget how to use [src]." + user << "You momentarily forget how to use [src]." return 0 return 1 @@ -989,34 +1018,34 @@ return if(!(src.z in config.station_levels)) return - src.occupant = new /mob/living/silicon/ai(src,malf.laws,null,1) - src.occupant.adjustOxyLoss(malf.getOxyLoss()) - if(!findtext(src.occupant.name,"APC Copy")) - src.occupant.name = "[malf.name] APC Copy" + src.occupier = new /mob/living/silicon/ai(src,malf.laws,null,1) + src.occupier.adjustOxyLoss(malf.getOxyLoss()) + if(!findtext(src.occupier.name,"APC Copy")) + src.occupier.name = "[malf.name] APC Copy" if(malf.parent) - src.occupant.parent = malf.parent + src.occupier.parent = malf.parent else - src.occupant.parent = malf - malf.mind.transfer_to(src.occupant) - src.occupant.eyeobj.name = "[src.occupant.name] (AI Eye)" + src.occupier.parent = malf + malf.mind.transfer_to(src.occupier) + src.occupier.eyeobj.name = "[src.occupier.name] (AI Eye)" if(malf.parent) qdel(malf) - src.occupant.verbs += /mob/living/silicon/ai/proc/corereturn - src.occupant.verbs += /datum/game_mode/malfunction/proc/takeover - src.occupant.cancel_camera() + src.occupier.verbs += /mob/living/silicon/ai/proc/corereturn + src.occupier.verbs += /datum/game_mode/malfunction/proc/takeover + src.occupier.cancel_camera() if (seclevel2num(get_security_level()) == SEC_LEVEL_DELTA) for(var/obj/item/weapon/pinpointer/point in world) point.the_disk = src //the pinpointer will detect the shunted AI /obj/machinery/power/apc/proc/malfvacate(var/forced) - if(!src.occupant) + if(!src.occupier) return - if(src.occupant.parent && src.occupant.parent.stat != 2) - src.occupant.mind.transfer_to(src.occupant.parent) - src.occupant.parent.adjustOxyLoss(src.occupant.getOxyLoss()) - src.occupant.parent.cancel_camera() - qdel(src.occupant) + if(src.occupier.parent && src.occupier.parent.stat != 2) + src.occupier.mind.transfer_to(src.occupier.parent) + src.occupier.parent.adjustOxyLoss(src.occupier.getOxyLoss()) + src.occupier.parent.cancel_camera() + del(src.occupier) // qdel if (seclevel2num(get_security_level()) == SEC_LEVEL_DELTA) for(var/obj/item/weapon/pinpointer/point in world) for(var/datum/mind/AI_mind in ticker.mode.malf_ai) @@ -1025,11 +1054,11 @@ point.the_disk = A //The pinpointer tracks the AI back into its core. else - src.occupant << "\red Primary core damaged, unable to return core processes." + src.occupier << "Primary core damaged, unable to return core processes." if(forced) - src.occupant.loc = src.loc - src.occupant.death() - src.occupant.gib() + src.occupier.loc = src.loc + src.occupier.death() + src.occupier.gib() for(var/obj/item/weapon/pinpointer/point in world) point.the_disk = null //the pinpointer will go back to pointing at the nuke disc. @@ -1040,7 +1069,7 @@ if(prob(3)) src.locked = 1 if (src.cell.charge > 0) -// world << "\red blew APC in [src.loc.loc]" +// world << "blew APC in [src.loc.loc]" src.cell.charge = 0 cell.corrupt() src.malfhack = 1 @@ -1053,7 +1082,7 @@ s.set_up(3, 1, src) s.start() for(var/mob/M in viewers(src)) - M.show_message("\red The [src.name] suddenly lets out a blast of smoke and some sparks!", 3, "\red You hear sizzling electronics.", 2) + M.show_message("The [src.name] suddenly lets out a blast of smoke and some sparks!", 3, "You hear sizzling electronics.", 2) /obj/machinery/power/apc/surplus() @@ -1062,9 +1091,14 @@ else return 0 -/obj/machinery/power/apc/add_load(var/amount) +//Returns 1 if the APC should attempt to charge +/obj/machinery/power/apc/proc/attempt_charging() + return (chargemode && charging == 1 && operating) + +/obj/machinery/power/apc/draw_power(var/amount) if(terminal && terminal.powernet) - terminal.powernet.load += amount + return terminal.powernet.draw_power(amount) + return 0 /obj/machinery/power/apc/avail() if(terminal) @@ -1082,8 +1116,7 @@ lastused_light = area.usage(LIGHT) lastused_equip = area.usage(EQUIP) lastused_environ = area.usage(ENVIRON) - if(area.powerupdate) - area.clear_usage() + area.clear_usage() lastused_total = lastused_light + lastused_equip + lastused_environ @@ -1102,33 +1135,24 @@ else main_status = 2 - //if(debug) - //world << "Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light]" + if(debug) + log_debug("Status: [main_status] - Excess: [excess] - Last Equip: [lastused_equip] - Last Light: [lastused_light] - Longterm: [longtermpower]") if(cell && !shorted) - var/cell_charge = cell.charge - var/cell_maxcharge = cell.maxcharge - // draw power from cell as before to power the area - - var/cellused = min(cell_charge, CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell + var/cellused = min(cell.charge, CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell cell.use(cellused) if(excess > lastused_total) // if power excess recharge the cell - // by the same amount just used - cell.give(cellused) - add_load(cellused/CELLRATE) // add the load used to recharge the cell - - + // by the same amount just used + var/draw = draw_power(cellused/CELLRATE) // draw the power needed to charge this cell + cell.give(draw * CELLRATE) else // no excess, and not enough per-apc - - if( (cell_charge/CELLRATE + excess) >= lastused_total) // can we draw enough from cell+grid to cover last usage? - - cell_charge = min(cell_maxcharge, cell_charge + CELLRATE * excess) //recharge with what we can - add_load(excess) // so draw what we can from the grid + if( (cell.charge/CELLRATE + excess) >= lastused_total) // can we draw enough from cell+grid to cover last usage? + var/draw = draw_power(excess) + cell.charge = min(cell.maxcharge, cell.charge + CELLRATE * draw) //recharge with what we can charging = 0 - - else if (autoflag != 0) // not enough power available to run the last tick! + else // not enough power available to run the last tick! charging = 0 chargecount = 0 // This turns everything off in the case that there is still a charge left on the battery, just not enough to run the room. @@ -1138,7 +1162,7 @@ autoflag = 0 - // set channels depending on how much charge we have left + // Set channels depending on how much charge we have left // Allow the APC to operate as normal if the cell can charge if(charging && longtermpower < 10) @@ -1146,31 +1170,30 @@ else if(longtermpower > -10) longtermpower -= 2 - - if(cell_charge >= 1250 || longtermpower > 0) // Put most likely at the top so we don't check it last, effeciency 101 + if(cell.charge >= 1250 || longtermpower > 0) // Put most likely at the top so we don't check it last, effeciency 101 if(autoflag != 3) equipment = autoset(equipment, 1) lighting = autoset(lighting, 1) environ = autoset(environ, 1) autoflag = 3 area.poweralert(1, src) - if(cell_charge >= 4000) + if(cell.charge >= 4000) area.poweralert(1, src) - else if(cell_charge < 1250 && cell_charge > 750 && longtermpower < 0) // <30%, turn off equipment + else if(cell.charge < 1250 && cell.charge > 750 && longtermpower < 0) // <30%, turn off equipment if(autoflag != 2) equipment = autoset(equipment, 2) lighting = autoset(lighting, 1) environ = autoset(environ, 1) area.poweralert(0, src) autoflag = 2 - else if(cell_charge < 750 && cell_charge > 10 && longtermpower < 0) // <15%, turn off lighting & equipment - if(autoflag != 1) + else if(cell.charge < 750 && cell.charge > 10) // <15%, turn off lighting & equipment + if((autoflag > 1 && longtermpower < 0) || (autoflag > 1 && longtermpower >= 0)) equipment = autoset(equipment, 2) lighting = autoset(lighting, 2) environ = autoset(environ, 1) area.poweralert(0, src) autoflag = 1 - else if(cell_charge <= 0) // zero charge, turn all off + else if(cell.charge <= 0) // zero charge, turn all off if(autoflag != 0) equipment = autoset(equipment, 0) lighting = autoset(lighting, 0) @@ -1180,32 +1203,31 @@ // now trickle-charge the cell - if(chargemode && charging == 1 && operating) + if(src.attempt_charging()) if(excess > 0) // check to make sure we have enough to charge // Max charge is capped to % per second constant var/ch = min(excess*CELLRATE, cell.maxcharge*CHARGELEVEL) - add_load(ch/CELLRATE) // Removes the power we're taking from the grid - cell.give(ch) // actually recharge the cell + + ch = draw_power(ch/CELLRATE) // Removes the power we're taking from the grid + cell.give(ch*CELLRATE) // actually recharge the cell else charging = 0 // stop charging chargecount = 0 // show cell as fully charged if so - if(cell.charge >= cell.maxcharge) cell.charge = cell.maxcharge charging = 2 if(chargemode) if(!charging) - if(excess > cell_maxcharge*CHARGELEVEL) + if(excess > cell.maxcharge*CHARGELEVEL) chargecount++ else chargecount = 0 - charging = 0 - if(chargecount == 10) + if(chargecount >= 10) chargecount = 0 charging = 1 @@ -1224,18 +1246,15 @@ area.poweralert(0, src) autoflag = 0 - // update icon & area power if anything changed + if(last_lt != lighting || last_eq != equipment || last_en != environ) queue_icon_update() update() else if (last_ch != charging) queue_icon_update() - -// val 0=off, 1=off(auto) 2=on 3=on(auto) -// on 0=off, 1=on, 2=autooff - -obj/machinery/power/apc/proc/autoset(var/val, var/on) + +/obj/machinery/power/apc/proc/autoset(var/val, var/on) if(on==0) if(val==2) // if on, return off return 0 @@ -1251,14 +1270,14 @@ obj/machinery/power/apc/proc/autoset(var/val, var/on) return 1 return val - + // damage and destruction acts /obj/machinery/power/apc/emp_act(severity) if(cell) cell.emp_act(severity) - if(occupant) - occupant.emp_act(severity) + if(occupier) + occupier.emp_act(severity) lighting = 0 equipment = 0 environ = 0 @@ -1293,6 +1312,12 @@ obj/machinery/power/apc/proc/autoset(var/val, var/on) set_broken() if (cell && prob(5)) cell.blob_act() + + +/obj/machinery/power/apc/disconnect_terminal() + if(terminal) + terminal.master = null + terminal = null /obj/machinery/power/apc/proc/set_broken() if(malfai && operating) @@ -1301,7 +1326,7 @@ obj/machinery/power/apc/proc/autoset(var/val, var/on) ticker.mode:apcs-- stat |= BROKEN operating = 0 - if(occupant) + if(occupier) malfvacate(1) update_icon() update() @@ -1320,35 +1345,6 @@ obj/machinery/power/apc/proc/autoset(var/val, var/on) L.broken() sleep(1) -/obj/machinery/power/apc/Destroy() - if(malfai && operating) - if (ticker.mode.config_tag == "malfunction") - if ((src.z in config.station_levels)) //if (is_type_in_list(get_area(src), the_station_areas)) - ticker.mode:apcs-- - area.power_light = 0 - area.power_equip = 0 - area.power_environ = 0 - area.power_change() - if(occupant) - malfvacate(1) - qdel(wires) - if(cell) - qdel(cell) - return ..() - -/obj/machinery/power/apc/proc/shock(mob/user, prb) - if(!prob(prb)) - return 0 - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - if(isalien(user)) - return 0 - if (electrocute_mob(user, src, src)) - return 1 - else - return 0 - /obj/machinery/power/apc/proc/setsubsystem(val) if(cell && cell.charge > 0) return (val==1) ? 0 : val diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm index 550d0ada3fe..33f4a475e86 100644 --- a/code/modules/power/cable.dm +++ b/code/modules/power/cable.dm @@ -1,27 +1,26 @@ -// attach a wire to a power machine - leads from the turf you are standing on +/////////////////////////////// +//CABLE STRUCTURE +/////////////////////////////// -/obj/machinery/power/attackby(obj/item/weapon/W, mob/user, params) - if(istype(W, /obj/item/stack/cable_coil)) +//////////////////////////////// +// Definitions +//////////////////////////////// - var/obj/item/stack/cable_coil/coil = W +/* Cable directions (d1 and d2) - var/turf/T = user.loc - if(T.intact || !istype(T, /turf/simulated/floor)) - return + 9 1 5 + \ | / + 8 - 0 - 4 + / | \ + 10 2 6 - if(get_dist(src, user) > 1) - return - - if(!directwired) // only for attaching to directwired machines - return - - coil.turf_place(T, user) - return - else - ..() - return +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 @@ -29,43 +28,33 @@ var/datum/powernet/powernet name = "power cable" desc = "A flexible superconducting cable for heavy-duty power transfer" - icon = 'icons/obj/power_cond/power_cond_red.dmi' + icon = 'icons/obj/power_cond/power_cond_white.dmi' icon_state = "0-1" var/d1 = 0 var/d2 = 1 layer = 2.44 //Just below unary stuff, which is at 2.45 and above pipes, which are at 2.4 - var/cable_color = "red" - var/obj/structure/powerswitch/power_switch + color = COLOR_RED /obj/structure/cable/yellow - cable_color = "yellow" - icon = 'icons/obj/power_cond/power_cond_yellow.dmi' + color = COLOR_YELLOW /obj/structure/cable/green - cable_color = "green" - icon = 'icons/obj/power_cond/power_cond_green.dmi' + color = COLOR_GREEN /obj/structure/cable/blue - cable_color = "blue" - icon = 'icons/obj/power_cond/power_cond_blue.dmi' + color = COLOR_BLUE /obj/structure/cable/pink - cable_color = "pink" - icon = 'icons/obj/power_cond/power_cond_pink.dmi' + color = COLOR_PINK /obj/structure/cable/orange - cable_color = "orange" - icon = 'icons/obj/power_cond/power_cond_orange.dmi' + color = COLOR_ORANGE /obj/structure/cable/cyan - cable_color = "cyan" - icon = 'icons/obj/power_cond/power_cond_cyan.dmi' + color = COLOR_CYAN /obj/structure/cable/white - cable_color = "white" - icon = 'icons/obj/power_cond/power_cond_white.dmi' - -// the power cable object + color = COLOR_WHITE /obj/structure/cable/New() ..() @@ -82,15 +71,20 @@ var/turf/T = src.loc // hide if turf is not intact if(level==1) hide(T.intact) - cable_list += src + cable_list += src //add it to the global cable list -/obj/structure/cable/Destroy() // called when a cable is deleted +/obj/structure/cable/Destroy() // called when a cable is deleted if(powernet) - powernet.cut_cable(src) // update the powernets - cable_list -= src - return ..() // then go ahead and delete the cable + cut_cable_from_powernet() // update the powernets + cable_list -= src //remove it from global cable list + ..() // then go ahead and delete the cable +/////////////////////////////////// +// General procedures +/////////////////////////////////// + +//If underfloor, hide the cable /obj/structure/cable/hide(var/i) if(level == 1 && istype(loc, /turf)) @@ -108,51 +102,72 @@ /obj/structure/cable/proc/get_powernet() //TODO: remove this as it is obsolete return powernet +//Telekinesis has no effect on a cable /obj/structure/cable/attack_tk(mob/user) return -/obj/structure/cable/attackby(obj/item/W, mob/user, params) +// 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 = src.loc if(T.intact) return if(istype(W, /obj/item/weapon/wirecutters)) - -// if(power_switch) -// user << "\red This piece of cable is tied to a power switch. Flip the switch to remove it." -// return +///// Z-Level Stuff + /* if(src.d1 == 12 || src.d2 == 12) + user << "You must cut this cable from above." + return */ +///// Z-Level Stuff + /* if(breaker_box) + user << "\red This cable is connected to nearby breaker box. Use breaker box to interact with it." + return */ if (shock(user, 50)) return if(src.d1) // 0-X cables are 1 unit, X-X cables are 2 units long - new/obj/item/stack/cable_coil(T, 2, cable_color) + new/obj/item/stack/cable_coil(T, 2, color) else - new/obj/item/stack/cable_coil(T, 1, cable_color) + new/obj/item/stack/cable_coil(T, 1, color) for(var/mob/O in viewers(src, null)) - O.show_message("\red [user] cuts the cable.", 1) + O.show_message("[user] cuts the cable.", 1) +///// Z-Level Stuff + /*if(src.d1 == 11 || src.d2 == 11) + var/turf/controllerlocation = locate(1, 1, z) + for(var/obj/effect/landmark/zcontroller/controller in controllerlocation) + if(controller.down) + var/turf/below = locate(src.x, src.y, controller.down_target) + for(var/obj/structure/cable/c in below) + if(c.d1 == 12 || c.d2 == 12) + c.qdel()*/ +///// Z-Level Stuff investigate_log("was cut by [key_name(usr, usr.client)] in [user.loc.loc]","wires") - qdel(src) + qdel(src) // qdel return else if(istype(W, /obj/item/stack/cable_coil)) var/obj/item/stack/cable_coil/coil = W + if (coil.get_amount() < 1) + user << "Not enough cable" + return coil.cable_join(src, user) else if(istype(W, /obj/item/device/multitool)) - var/datum/powernet/PN = get_powernet() // find the powernet - - if(PN && (PN.avail > 0)) // is it powered? - user << "\red [PN.avail]W in power network." + if(powernet && (powernet.avail > 0)) // is it powered? + user << "[powernet.avail]W in power network." else - user << "\red The cable is not powered." + user << "The cable is not powered." shock(user, 5, 0.2) @@ -163,7 +178,6 @@ src.add_fingerprint(user) // shock the user with probability prb - /obj/structure/cable/proc/shock(mob/user, prb, var/siemens_coeff = 1.0) if(!prob(prb)) return 0 @@ -175,35 +189,279 @@ else return 0 +//explosion handling /obj/structure/cable/ex_act(severity) switch(severity) if(1.0) - qdel(src) + qdel(src) // qdel if(2.0) if (prob(50)) - new/obj/item/stack/cable_coil(src.loc, src.d1 ? 2 : 1, cable_color) - qdel(src) + new/obj/item/stack/cable_coil(src.loc, src.d1 ? 2 : 1, color) + qdel(src) // qdel if(3.0) if (prob(25)) - new/obj/item/stack/cable_coil(src.loc, src.d1 ? 2 : 1, cable_color) - qdel(src) + new/obj/item/stack/cable_coil(src.loc, src.d1 ? 2 : 1, color) + qdel(src) // qdel return -/obj/structure/cable/singularity_pull(S, current_size) - if(current_size >= STAGE_FIVE) - qdel(src) +obj/structure/cable/proc/cableColor(var/colorC) + var/color_n = "#DD0000" + if(colorC) + color_n = colorC + color = color_n -// the cable coil object, used for laying cable +///////////////////////////////////////////////// +// 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(var/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(var/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(var/powernetless_only = 0) + . = list() // this will be a list of all connected power objects + var/turf/T + +///// Z-Level Stuff + /* if (d1 == 11 || d1 == 12) + var/turf/controllerlocation = locate(1, 1, z) + for(var/obj/effect/landmark/zcontroller/controller in controllerlocation) + if(controller.up && d1 == 12) + T = locate(src.x, src.y, controller.up_target) + if(T) + . += power_list(T, src, 11, 1) + if(controller.down && d1 == 11) + T = locate(src.x, src.y, controller.down_target) + if(T) + . += power_list(T, src, 12, 1) */ +///// Z-Level Stuff + //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 + +///// Z-Level Stuff + /*if(d2 == 11 || d2 == 12) + var/turf/controllerlocation = locate(1, 1, z) + for(var/obj/effect/landmark/zcontroller/controller in controllerlocation) + if(controller.up && d2 == 12) + T = locate(src.x, src.y, controller.up_target) + if(T) + . += power_list(T, src, 11, 1) + if(controller.down && d2 == 11) + T = locate(src.x, src.y, controller.down_target) + if(T) + . += power_list(T, src, 12, 1) +///// Z-Level Stuff + else */ + //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) // qdel + +// cut the cable's powernet at this cable and updates the powergrid +/obj/structure/cable/proc/cut_cable_from_powernet() + 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 + + // remove the cut cable from its turf and powernet, so that it doesn't get count in propagate_network worklist + loc = null + powernet.remove_cable(src) //remove the cut cable from its powernet + + var/datum/powernet/newPN = new()// creates a new powernet... + propagate_network(P_list[1], newPN)//... and propagates it to the other side of the cable + + // 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 +//////////////////////////////// #define MAXCOIL 30 + /obj/item/stack/cable_coil name = "cable coil" icon = 'icons/obj/power.dmi' - icon_state = "coil_red" - item_state = "coil_red" + icon_state = "coil" amount = MAXCOIL - _color = "red" + max_amount = MAXCOIL + color = COLOR_RED desc = "A coil of power cable." throwforce = 10 w_class = 2.0 @@ -213,47 +471,86 @@ g_amt = 20 flags = CONDUCT slot_flags = SLOT_BELT + item_state = "coil" attack_verb = list("whipped", "lashed", "disciplined", "flogged") - suicide_act(mob/user) - if(locate(/obj/structure/stool) in user.loc) - viewers(user) << "[user] is making a noose with the [src.name]! It looks like \he's trying to commit suicide." - else - viewers(user) << "[user] is strangling \himself with the [src.name]! It looks like \he's trying to commit suicide." - return(OXYLOSS) +/obj/item/stack/cable_coil/suicide_act(mob/user) + if(locate(/obj/structure/stool) in user.loc) + user.visible_message("[user] is making a noose with the [src.name]! It looks like \he's trying to commit suicide.") + else + user.visible_message("[user] is strangling \himself with the [src.name]! It looks like \he's trying to commit suicide.") + return(OXYLOSS) - -/obj/item/stack/cable_coil/New(loc, length = MAXCOIL, var/param_color = null) +/obj/item/stack/cable_coil/New(loc, length = MAXCOIL, var/paramcolor = null) ..() src.amount = length - if (param_color) - _color = param_color + if (paramcolor) + color = paramcolor + else + color = color pixel_x = rand(-2,2) pixel_y = rand(-2,2) update_icon() + update_wclass() + +/////////////////////////////////// +// General procedures +/////////////////////////////////// + +//you can use wires to heal robotics +/obj/item/stack/cable_coil/attack(mob/M as mob, mob/user as mob) + if(istype(M,/mob/living/carbon/human)) + var/mob/living/carbon/human/H = M + var/obj/item/organ/external/S = H.get_organ(user.zone_sel.selecting) + if(!(S.status & ORGAN_ROBOT) || user.a_intent != "help") + return ..() + + if(H.species.flags & IS_SYNTHETIC) + if(M == user) + user << "\red You can't repair damage to your own body - it's against OH&S." + return + + if(S.burn_dam > 0 && use(1)) + S.heal_damage(0,15,0,1) + user.visible_message("\red \The [user] repairs some burn damage on \the [M]'s [S.name] with \the [src].") + return + else + user << "Nothing to fix!" + + else + return ..() + /obj/item/stack/cable_coil/update_icon() - if (!_color) - _color = pick("red", "yellow", "blue", "green") + if (!color) + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_ORANGE, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) + color = color if(amount == 1) - icon_state = "coil_[_color]1" + icon_state = "coil1" name = "cable piece" else if(amount == 2) - icon_state = "coil_[_color]2" + icon_state = "coil2" name = "cable piece" else - icon_state = "coil_[_color]" + icon_state = "coil" name = "cable coil" +/obj/item/stack/cable_coil/proc/update_wclass() + if(amount == 1) + w_class = 1.0 + else + w_class = 2.0 + /obj/item/stack/cable_coil/examine() set src in view(1) - if(amount == 1) + if(get_amount() == 1) usr << "A short piece of power cable." - else if(amount == 2) + else if(get_amount() == 2) usr << "A piece of power cable." else - usr << "A coil of power cable. There are [amount] lengths of cable in the coil." + usr << "A coil of power cable. There are [get_amount()] lengths of cable in the coil." + /obj/item/stack/cable_coil/verb/make_restraint() set name = "Make Cable Restraints" @@ -266,23 +563,25 @@ usr << "\red You need at least 15 lengths to make restraints!" return var/obj/item/weapon/restraints/handcuffs/cable/B = new /obj/item/weapon/restraints/handcuffs/cable(usr.loc) - B.icon_state = "cuff_[_color]" + B.icon_state = "cuff_[color]" usr << "\blue You wind some cable together to make some restraints." src.use(15) else usr << "\blue You cannot do that." ..() -/obj/item/stack/cable_coil/attackby(obj/item/weapon/W, mob/user, params) +// Items usable on a cable coil : +// - Wirecutters : cut them duh ! +// - Cable coil : merge cables +/obj/item/stack/cable_coil/attackby(obj/item/weapon/W, mob/user) ..() if( istype(W, /obj/item/weapon/wirecutters) && src.amount > 1) src.amount-- - new/obj/item/stack/cable_coil(user.loc, 1,_color) + new/obj/item/stack/cable_coil(user.loc, 1,color) user << "You cut a piece off the cable coil." src.update_icon() return - - else if( istype(W, /obj/item/stack/cable_coil) ) + else if(istype(W, /obj/item/stack/cable_coil)) var/obj/item/stack/cable_coil/C = W if(C.amount >= MAXCOIL) user << "The coil is too long, you cannot add any more cable to it." @@ -301,20 +600,28 @@ src.use(amt) return +//remove cables from the stack +/* This is probably reduntant /obj/item/stack/cable_coil/use(var/used) if(src.amount < used) return 0 else if (src.amount == used) - //handle mob icon update - if(ismob(loc)) + if(ismob(loc)) //handle mob icon update var/mob/M = loc M.unEquip(src) - qdel(src) + qqdel(src) + return 1 else amount -= used update_icon() return 1 +*/ +/obj/item/stack/cable_coil/use(var/used) + . = ..() + update_icon() + return +//add cables to the stack /obj/item/stack/cable_coil/proc/give(var/extra) if(amount + extra > MAXCOIL) amount = MAXCOIL @@ -322,18 +629,24 @@ amount += extra update_icon() +/////////////////////////////////////////////// +// Cable laying procedures +////////////////////////////////////////////// + // called when cable_coil is clicked on a turf/simulated/floor - /obj/item/stack/cable_coil/proc/turf_place(turf/simulated/floor/F, mob/user) - if(!isturf(user.loc)) return - if(get_dist(F,user) > 1) + if(get_amount() < 1) // Out of cable + user << "There is no cable left." + return + + if(get_dist(F,user) > 1) // Too far user << "You can't lay cable at a place that far away." return - if(F.intact) // if floor is intact, complain + if(F.intact) // Ff floor is intact, complain user << "You can't lay cable there unless the floor tiles are removed." return @@ -345,6 +658,48 @@ else dirn = get_dir(F, user) + for(var/obj/structure/cable/LC in F) + if((LC.d1 == dirn && LC.d2 == 0 ) || ( LC.d2 == dirn && LC.d1 == 0)) + user << "There's already a cable at that position." + return +///// Z-Level Stuff + // check if the target is open space + /* if(istype(F, /turf/simulated/floor/open)) + for(var/obj/structure/cable/LC in F) + if((LC.d1 == dirn && LC.d2 == 11 ) || ( LC.d2 == dirn && LC.d1 == 11)) + user << "There's already a cable at that position." + return + + var/turf/simulated/floor/open/temp = F + var/obj/structure/cable/C = new(F) + var/obj/structure/cable/D = new(temp.floorbelow) + + C.cableColor(color) + + C.d1 = 11 + C.d2 = dirn + C.add_fingerprint(user) + C.updateicon() + + var/datum/powernet/PN = new() + PN.add_cable(C) + + C.mergeConnectedNetworks(C.d2) + C.mergeConnectedNetworksOnTurf() + + D.cableColor(color) + + D.d1 = 12 + D.d2 = 0 + D.add_fingerprint(user) + D.updateicon() + + PN.add_cable(D) + D.mergeConnectedNetworksOnTurf() + + // do the normal stuff + else */ +///// Z-Level Stuff for(var/obj/structure/cable/LC in F) if((LC.d1 == dirn && LC.d2 == 0 ) || ( LC.d2 == dirn && LC.d1 == 0)) user << "There's already a cable at that position." @@ -352,34 +707,34 @@ var/obj/structure/cable/C = new(F) - C.cableColor(_color) + C.cableColor(color) - C.d1 = 0 + //set up the new cable + C.d1 = 0 //it's a O-X node cable C.d2 = dirn C.add_fingerprint(user) C.updateicon() - C.powernet = new() - powernets += C.powernet - C.powernet.cables += C + //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) - C.mergeConnectedNetworksOnTurf() + 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(C.loc, 1, C.cable_color) - qdel(C) - //src.laying = 1 - //last = C - + new/obj/item/stack/cable_coil(C.loc, 1, C.color) + qdel(C) // qdel // 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 @@ -394,12 +749,14 @@ return - if(U == T) // do nothing if we clicked a cable we're standing on - return // may change later if can think of something logical to do + if(U == T) //if clicked on the turf we're standing on, try to put a cable in the direction we're facing + turf_place(T,user) + return var/dirn = get_dir(C, user) - if(C.d1 == dirn || C.d2 == dirn) // one end of the clicked cable is pointing towards us + // one end of the clicked cable is pointing towards us + if(C.d1 == dirn || C.d2 == dirn) if(U.intact) // can't place a cable if the floor is complete user << "You can't lay cable there unless the floor tiles are removed." return @@ -415,26 +772,34 @@ return var/obj/structure/cable/NC = new(U) - NC.cableColor(_color) + NC.cableColor(color) NC.d1 = 0 NC.d2 = fdirn NC.add_fingerprint() NC.updateicon() - if(C.powernet) - NC.powernet = C.powernet - NC.powernet.cables += NC - NC.mergeConnectedNetworks(NC.d2) - NC.mergeConnectedNetworksOnTurf() + //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 - new/obj/item/stack/cable_coil(NC.loc, 1, NC.cable_color) - qdel(NC) + new/obj/item/stack/cable_coil(NC.loc, 1, NC.color) + qdel(NC) // qdel return - else if(C.d1 == 0) // exisiting cable doesn't point at our position, so see if it's a stub + + // 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 @@ -453,7 +818,7 @@ return - C.cableColor(_color) + C.cableColor(color) C.d1 = nd1 C.d2 = nd2 @@ -462,110 +827,33 @@ C.updateicon() - C.mergeConnectedNetworks(C.d1) - C.mergeConnectedNetworks(C.d2) + C.mergeConnectedNetworks(C.d1) //merge the powernets... + C.mergeConnectedNetworks(C.d2) //...in the two new cable directions C.mergeConnectedNetworksOnTurf() + if(C.d1 & (C.d1 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d1) + + if(C.d2 & (C.d2 - 1))// if the cable is layed diagonally, check the others 2 possible directions + C.mergeDiagonalsNetworks(C.d2) + use(1) + if (C.shock(user, 50)) if (prob(50)) //fail - new/obj/item/stack/cable_coil(C.loc, 2, C.cable_color) - qdel(C) + new/obj/item/stack/cable_coil(C.loc, 2, C.color) + qdel(C) // qdel + return + C.denode()// this call may have disconnected some cables that terminated on the centre of the turf, if so split the powernets. return -/obj/structure/cable/proc/mergeConnectedNetworks(var/direction) - var/turf/TB - if(!(d1 == direction || d2 == direction)) - return - TB = get_step(src, direction) - - for(var/obj/structure/cable/TC in TB) - - if(!TC) - continue - - if(src == TC) - continue - - var/fdir = (!direction)? 0 : turn(direction, 180) - - if(TC.d1 == fdir || TC.d2 == fdir) - - if(!TC.powernet) - TC.powernet = new() - powernets += TC.powernet - TC.powernet.cables += TC - - if(powernet) - merge_powernets(powernet,TC.powernet) - else - powernet = TC.powernet - powernet.cables += src - - - - -/obj/structure/cable/proc/mergeConnectedNetworksOnTurf() - if(!powernet) - powernet = new() - powernets += powernet - powernet.cables += src - - for(var/AM in loc) - if(istype(AM,/obj/structure/cable)) - var/obj/structure/cable/C = AM - if(C.powernet == powernet) continue - if(C.powernet) - merge_powernets(powernet, C.powernet) - else - C.powernet = powernet - powernet.cables += C - - else if(istype(AM,/obj/machinery/power/apc)) - var/obj/machinery/power/apc/N = AM - if(!N.terminal) continue - if(N.terminal.powernet) - merge_powernets(powernet, N.terminal.powernet) - else - N.terminal.powernet = powernet - powernet.nodes[N.terminal] = N.terminal - - else if(istype(AM,/obj/machinery/power)) - var/obj/machinery/power/M = AM - if(M.powernet == powernet) continue - if(M.powernet) - merge_powernets(powernet, M.powernet) - else - M.powernet = powernet - powernet.nodes[M] = M - - -obj/structure/cable/proc/cableColor(var/colorC) - var/color_n = "red" - if(colorC) - color_n = colorC - cable_color = color_n - switch(colorC) - if("red") - icon = 'icons/obj/power_cond/power_cond_red.dmi' - if("yellow") - icon = 'icons/obj/power_cond/power_cond_yellow.dmi' - if("green") - icon = 'icons/obj/power_cond/power_cond_green.dmi' - if("blue") - icon = 'icons/obj/power_cond/power_cond_blue.dmi' - if("pink") - icon = 'icons/obj/power_cond/power_cond_pink.dmi' - if("orange") - icon = 'icons/obj/power_cond/power_cond_orange.dmi' - if("cyan") - icon = 'icons/obj/power_cond/power_cond_cyan.dmi' - if("white") - icon = 'icons/obj/power_cond/power_cond_white.dmi' +////////////////////////////// +// Misc. +///////////////////////////// /obj/item/stack/cable_coil/cut - item_state = "coil_red2" + item_state = "coil2" /obj/item/stack/cable_coil/cut/New(loc) ..() @@ -573,68 +861,37 @@ obj/structure/cable/proc/cableColor(var/colorC) pixel_x = rand(-2,2) pixel_y = rand(-2,2) update_icon() + update_wclass() /obj/item/stack/cable_coil/yellow - _color = "yellow" - icon_state = "coil_yellow" + color = COLOR_YELLOW /obj/item/stack/cable_coil/blue - _color = "blue" - icon_state = "coil_blue" - item_state = "coil_blue" + color = COLOR_BLUE /obj/item/stack/cable_coil/green - _color = "green" - icon_state = "coil_green" + color = COLOR_GREEN /obj/item/stack/cable_coil/pink - _color = "pink" - icon_state = "coil_pink" + color = COLOR_PINK /obj/item/stack/cable_coil/orange - _color = "orange" - icon_state = "coil_orange" + color = COLOR_ORANGE /obj/item/stack/cable_coil/cyan - _color = "cyan" - icon_state = "coil_cyan" + color = COLOR_CYAN /obj/item/stack/cable_coil/white - _color = "white" - icon_state = "coil_white" + color = COLOR_WHITE /obj/item/stack/cable_coil/random/New() - _color = pick("red","yellow","green","blue","pink") - icon_state = "coil_[_color]" + color = pick(COLOR_RED, COLOR_BLUE, COLOR_GREEN, COLOR_WHITE, COLOR_PINK, COLOR_YELLOW, COLOR_CYAN) ..() -/obj/item/stack/cable_coil/attack(mob/M as mob, mob/user as mob) - if(istype(M,/mob/living/carbon/human)) - var/mob/living/carbon/human/H = M - var/obj/item/organ/external/S = H.get_organ(user.zone_sel.selecting) - if(!(S.status & ORGAN_ROBOT) || user.a_intent != "help") - return ..() - - if(H.species.flags & IS_SYNTHETIC) - if(M == user) - user << "\red You can't repair damage to your own body - it's against OH&S." - return - - if(S.burn_dam > 0 && use(1)) - S.heal_damage(0,15,0,1) - user.visible_message("\red \The [user] repairs some burn damage on \the [M]'s [S.name] with \the [src].") - H.updatehealth() - return - else - user << "Nothing to fix!" - - else - return ..() - /obj/item/stack/cable_coil/cyborg name = "cyborg cable coil" /obj/item/stack/cable_coil/cyborg/attack_self(mob/user) - var/cable_color = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") - _color = cable_color - update_icon() \ No newline at end of file + var/cablecolor = input(user,"Pick a cable color.","Cable Color") in list("red","yellow","green","blue","pink","orange","cyan","white") + color = cablecolor + update_icon() \ No newline at end of file diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index ff952f7c07e..5e7e935e1f7 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -1,26 +1,37 @@ +////////////////////////////// +// POWER MACHINERY BASE CLASS +////////////////////////////// + +///////////////////////////// +// Definitions +///////////////////////////// + /obj/machinery/power name = null icon = 'icons/obj/power.dmi' anchored = 1.0 var/datum/powernet/powernet = null - var/directwired = 1 // by default, power machines are connected by a cable in a neighbouring turf - // if set to 0, requires a 0-X cable on this turf use_power = 0 idle_power_usage = 0 active_power_usage = 0 -/obj/machinery/power/Destroy() +/obj/machinery/power/Del() disconnect_from_network() - return ..() + ..() + +/////////////////////////////// +// General procedures +////////////////////////////// // common helper procs for all power machines /obj/machinery/power/proc/add_avail(var/amount) if(powernet) powernet.newavail += amount -/obj/machinery/power/proc/add_load(var/amount) +/obj/machinery/power/proc/draw_power(var/amount) if(powernet) - powernet.load += amount + return powernet.draw_power(amount) + return 0 /obj/machinery/power/proc/surplus() if(powernet) @@ -34,17 +45,16 @@ else return 0 +/obj/machinery/power/proc/disconnect_terminal() // machines without a terminal will just return, no harm no fowl. + return + // returns true if the area has power on given channel (or doesn't require power). // defaults to power_channel - -/obj/machinery/proc/powered(var/chan = -1) +/obj/machinery/proc/powered(var/chan = -1) // defaults to power_channel if(!src.loc) return 0 - if(!use_power) - return 1 - var/area/A = src.loc.loc // make sure it's in an area if(!A || !isarea(A)) return 0 // if not, then not powered @@ -71,94 +81,63 @@ stat |= NOPOWER 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 0 -// the powernet datum -// each contiguous network of cables & nodes - - -// rebuild all power networks from scratch - -/hook/startup/proc/buildPowernets() - return makepowernets() - -/proc/makepowernets() - for(var/datum/powernet/PN in powernets) - qdel(PN) - powernets.Cut() - - for(var/obj/structure/cable/PC in cable_list) - if(!PC.powernet) - PC.powernet = new() - powernets += PC.powernet -// if(Debug) world.log << "Starting mpn at [PC.x],[PC.y] ([PC.d1]/[PC.d2])" - powernet_nextlink(PC,PC.powernet) - -// if(Debug) world.log << "[powernets.len] powernets found" - - for(var/obj/structure/cable/C in cable_list) - if(!C.powernet) continue - C.powernet.cables += C - - for(var/obj/machinery/power/M in machines) - if(!M.powernet) continue // APCs have powernet=0 so they don't count as network nodes directly - M.powernet.nodes[M] = M + 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 0 + C.powernet.add_machine(src) return 1 +// remove and disconnect the machine from its current powernet +/obj/machinery/power/proc/disconnect_from_network() + if(!powernet) + return 0 + powernet.remove_machine(src) + return 1 -// 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(var/turf/T, var/source, var/d, var/unmarked=0) - . = list() - var/fdir = (!d)? 0 : turn(d, 180) // the opposite direction to d (or 0 if d==0) -// world.log << "d=[d] fdir=[fdir]" - for(var/AM in T) - if(AM == source) continue //we don't want to return source +// 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/weapon/W, mob/user) - if(istype(AM,/obj/machinery/power)) - var/obj/machinery/power/P = AM - if(P.powernet == 0) continue // exclude APCs which have powernet=0 + if(istype(W, /obj/item/stack/cable_coil)) - if(!unmarked || !P.powernet) //if unmarked=1 we only return things with no powernet - if(P.directwired || (d == 0)) - . += P + var/obj/item/stack/cable_coil/coil = W - else if(istype(AM,/obj/structure/cable)) - var/obj/structure/cable/C = AM + var/turf/T = user.loc - if(!unmarked || !C.powernet) - if(C.d1 == fdir || C.d2 == fdir) - . += C - else if(C.d1 == turn(C.d2, 180)) - . += C - return . + if(T.intact || !istype(T, /turf/simulated/floor)) + return + if(get_dist(src, user) > 1) + return -/obj/structure/cable/proc/get_connections() - . = list() // this will be a list of all connected power objects - var/turf/T = loc - - if(d1) T = get_step(src, d1) - if(T) . += power_list(T, src, d1, 1) - - T = get_step(src, d2) - if(T) . += power_list(T, src, d2, 1) - - return . + coil.turf_place(T, user) + return + 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() - if(!directwired) - return get_indirect_connections() - var/cdir + var/turf/T for(var/card in cardinal) - var/turf/T = get_step(loc,card) + T = get_step(loc,card) cdir = get_dir(T,loc) for(var/obj/structure/cable/C in T) @@ -167,193 +146,132 @@ . += 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 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) + if(C.d1 == 0) // the cable is a node cable . += C return . +/////////////////////////////////////////// +// GLOBAL PROCS for powernets handling +////////////////////////////////////////// -/proc/powernet_nextlink(var/obj/O, var/datum/powernet/PN) - var/list/P - while(1) - if( istype(O,/obj/structure/cable) ) - var/obj/structure/cable/C = O - C.powernet = PN - P = C.get_connections() +// 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(var/turf/T, var/source, var/d, var/unmarked=0, var/cable_only = 0) + . = list() + var/fdir = (!d)? 0 : turn(d, 180) // the opposite direction to d (or 0 if d==0) +///// Z-Level Stuff + var/Zdir + if(d==11) + Zdir = 11 + else if (d==12) + Zdir = 12 + else + Zdir = 999 +///// Z-Level Stuff + for(var/AM in T) + if(AM == source) continue //we don't want to return source - else if(O.anchored && istype(O,/obj/machinery/power)) - var/obj/machinery/power/M = O - M.powernet = PN - P = M.get_connections() + 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) +///// Z-Level Stuff + if(C.d1 == fdir || C.d2 == fdir || C.d1 == Zdir || C.d2 == Zdir) +///// Z-Level Stuff + . += C + else if(C.d1 == d || C.d2 == d) + . += C + return . + +/hook/startup/proc/buildPowernets() + return makepowernets() + +// rebuild all power networks from scratch - only called at world creation or by the admin verb +/proc/makepowernets() + for(var/datum/powernet/PN in powernets) + del(PN) + powernets.Cut() + + for(var/obj/structure/cable/PC in cable_list) + if(!PC.powernet) + var/datum/powernet/NewPN = new() + NewPN.add_cable(PC) + propagate_network(PC,PC.powernet) + return 1 + +//remove the old powernet and replace it with a new one throughout the network. +/proc/propagate_network(var/obj/O, var/datum/powernet/PN) + //world.log << "propagating new network" + 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 - return + continue - if(P.len == 0) return - - O = P[1] - - for(var/L = 2 to P.len) - powernet_nextlink(P[L], PN) + //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 -// cut a powernet at this cable object -/datum/powernet/proc/cut_cable(var/obj/structure/cable/C) - var/turf/T1 = C.loc - if(!T1) return - var/node = 0 - if(C.d1 == 0) - node = 1 - - var/turf/T2 - if(C.d2) T2 = get_step(T1, C.d2) - if(C.d1) T1 = get_step(T1, C.d1) - - - var/list/P1 = power_list(T1, C, C.d1) // what joins on to cut cable in dir1 - var/list/P2 = power_list(T2, C, C.d2) // what joins on to cut cable in dir2 - -// if(Debug) -// for(var/obj/O in P1) -// world.log << "P1: [O] at [O.x] [O.y] : [istype(O, /obj/structure/cable) ? "[O:d1]/[O:d2]" : null] " -// for(var/obj/O in P2) -// world.log << "P2: [O] at [O.x] [O.y] : [istype(O, /obj/structure/cable) ? "[O:d1]/[O:d2]" : null] " - - - if(P1.len == 0 || P2.len == 0)//if nothing in either list, then the cable was an endpoint no need to rebuild the powernet, - cables -= C //just remove cut cable from the list -// if(Debug) world.log << "Was end of cable" +//Merge two powernets, the bigger (in cable length term) absorbing the other +/proc/merge_powernets(var/datum/powernet/net1, var/datum/powernet/net2) + if(!net1 || !net2) //if one of the powernet doesn't exist, return return - //null the powernet reference of all cables & nodes in this powernet - var/i=1 - while(i<=cables.len) - var/obj/structure/cable/Cable = cables[i] - if(Cable) - Cable.powernet = null - if(Cable == C) - cables.Cut(i,i+1) - continue - i++ - i=1 - while(i<=nodes.len) - var/obj/machinery/power/Node = nodes[i] - if(Node) - Node.powernet = null - i++ - - // remove the cut cable from the network -// C.netnum = -1 - - C.loc = null - - powernet_nextlink(P1[1], src) // propagate network from 1st side of cable, using current netnum //TODO? - - // now test to see if propagation reached to the other side - // if so, then there's a loop in the network - var/notlooped = 0 - for(var/O in P2) - if( istype(O, /obj/machinery/power) ) - var/obj/machinery/power/Machine = O - if(Machine.powernet != src) - notlooped = 1 - break - else if( istype(O, /obj/structure/cable) ) - var/obj/structure/cable/Cable = O - if(Cable.powernet != src) - notlooped = 1 - break - - if(notlooped) - // not looped, so make a new powernet - var/datum/powernet/PN = new() - powernets += PN - -// if(Debug) world.log << "Was not looped: spliting PN#[number] ([cables.len];[nodes.len])" - - i=1 - while(i<=cables.len) - var/obj/structure/cable/Cable = cables[i] - if(Cable && !Cable.powernet) // non-connected cables will have powernet=null, since they weren't reached by propagation - Cable.powernet = PN - cables.Cut(i,i+1) // remove from old network & add to new one - PN.cables += Cable - continue - i++ - - i=1 - while(i<=nodes.len) - var/obj/machinery/power/Node = nodes[i] - if(Node && !Node.powernet) - Node.powernet = PN - nodes.Cut(i,i+1) - PN.nodes[Node] = Node - continue - i++ - - // Disconnect machines connected to nodes - if(node) - for(var/obj/machinery/power/P in T1) - if(P.powernet && !P.powernet.nodes[src]) - P.disconnect_from_network() -// if(Debug) -// world.log << "Old PN#[number] : ([cables.len];[nodes.len])" -// world.log << "New PN#[PN.number] : ([PN.cables.len];[PN.nodes.len])" -// -// else -// if(Debug) -// world.log << "Was looped." -// //there is a loop, so nothing to be done -// return - - - -/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 - - //updates the viewed load (as seen on power computers) - viewload = 0.8*viewload + 0.2*load - viewload = round(viewload) - - //reset the powernet - load = 0 - avail = newavail - newavail = 0 - -/datum/powernet/proc/get_electrocute_damage() - switch(avail) - if (5000000 to INFINITY) - return min(rand(200,300),rand(200,300)) - if (4000000 to 5000000) - return min(rand(80,180),rand(80,180)) - if (1000000 to 4000000) - return min(rand(50,160),rand(50,160)) - if (200000 to 1000000) - return min(rand(25,80),rand(25,80)) - if (100000 to 200000)//Ave powernet - return min(rand(20,60),rand(20,60)) - if (50000 to 100000) - return min(rand(15,40),rand(15,40)) - if (1000 to 50000) - return min(rand(10,20),rand(10,20)) - else - return 0 - -//The powernet that calls this proc will consume the other powernet - Rockdtben -//TODO: rewrite so the larger net absorbs the smaller net -/proc/merge_powernets(var/datum/powernet/net1, var/datum/powernet/net2) - if(!net1 || !net2) return - if(net1 == net2) 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 @@ -361,57 +279,18 @@ net1 = net2 net2 = temp - for(var/i=1,i<=net2.nodes.len,i++) //merge net2 into net1 - var/obj/machinery/power/Node = net2.nodes[i] - if(Node) - Node.powernet = net1 - net1.nodes[Node] = Node + //merge net2 into net1 + for(var/obj/structure/cable/Cable in net2.cables) //merge cables + net1.add_cable(Cable) + + if(!net2) return net1 - for(var/i=1,i<=net2.cables.len,i++) - var/obj/structure/cable/Cable = net2.cables[i] - if(Cable) - Cable.powernet = net1 - net1.cables += 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 - qdel(net2) return net1 - -/obj/machinery/power/proc/connect_to_network() - var/turf/T = src.loc - var/obj/structure/cable/C = T.get_cable_node() - if(!C || !C.powernet) return 0 -// makepowernets() //TODO: find fast way //EWWWW what are you doing!? - powernet = C.powernet - powernet.nodes[src] = src - return 1 - -/obj/machinery/power/proc/disconnect_from_network() - if(!powernet) - //world << " no powernet" - return 0 - powernet.nodes -= src - powernet = null - //world << "powernet null" - return 1 - -/turf/proc/get_cable_node() - if(!istype(src, /turf/simulated/floor)) - return null - for(var/obj/structure/cable/C in src) - if(C.d1 == 0) - return C - return null - -/area/proc/get_apc() - // This was a simple locate() in src, but an unresolved BYOND bug causes trying to locate() in an - // area to take an inconceivably long time, especially for large areas. - // See: http://www.byond.com/forum/?post=1860571 - var/obj/machinery/power/apc/FINDME - for(FINDME in src) - return FINDME - - //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 @@ -419,9 +298,6 @@ //No animations will be performed by this proc. /proc/electrocute_mob(mob/living/carbon/M as mob, var/power_source, var/obj/source, var/siemens_coeff = 1.0) if(istype(M.loc,/obj/mecha)) return 0 //feckin mechs are dumb - - //This is for performance optimization only. - //DO NOT modify siemens_coeff here. That is checked in human/electrocute_act() if(istype(M,/mob/living/carbon/human)) var/mob/living/carbon/human/H = M if(H.gloves) @@ -475,8 +351,7 @@ source_area.use_power(drained_energy/CELLRATE) else if (istype(power_source,/datum/powernet)) var/drained_power = drained_energy/CELLRATE //convert from "joules" to "watts" - PN.load+=drained_power + drained_power = PN.draw_power(drained_power) else if (istype(power_source, /obj/item/weapon/stock_parts/cell)) cell.use(drained_energy) return drained_energy - diff --git a/code/modules/power/powernet.dm b/code/modules/power/powernet.dm new file mode 100644 index 00000000000..e80113b3057 --- /dev/null +++ b/code/modules/power/powernet.dm @@ -0,0 +1,158 @@ +/datum/powernet + 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/viewload = 0 // the load as it appears on the power console (gradually updated) + var/number = 0 // Unused //TODEL + + var/perapc = 0 // per-apc avilability + var/perapc_excess = 0 + var/netexcess = 0 // excess power on the powernet (typically avail-load) + + var/problem = 0 // If either of these is set to 1 there is some sort of issue at the powernet. + + +/datum/powernet/New() + powernets += src + ..() + +/datum/powernet/Destroy() + powernets -= src + ..() + +//Returns the amount of excess power (before refunding to SMESs) from last tick. +//This is for machines that might adjust their power consumption using this data. +/datum/powernet/proc/last_surplus() + return max(avail - load, 0) + +/datum/powernet/proc/draw_power(var/amount) + var/draw = between(0, amount, avail - load) + load += draw + return draw + +/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(var/obj/structure/cable/C) + cables -= C + C.powernet = null + if(is_empty())//the powernet is now empty... + del(src)///... delete it - qdel + +//add a cable to the current powernet +//Warning : this proc DON'T check if the cable exists +/datum/powernet/proc/add_cable(var/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(var/obj/machinery/power/M) + nodes -=M + M.powernet = null + if(is_empty())//the powernet is now empty... + del(src)///... delete it - qdel + + +//add a power machine to the current powernet +//Warning : this proc DON'T check if the machine exists +/datum/powernet/proc/add_machine(var/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 + +// Triggers warning for certain amount of ticks +/datum/powernet/proc/trigger_warning(var/duration_ticks = 20) + problem = max(duration_ticks, problem) + +//handles the power changes in the powernet +//called every ticks by the powernet controller +/datum/powernet/proc/reset() + var/numapc = 0 + + if(problem > 0) + problem = max(problem - 1, 0) + + if(nodes && nodes.len) // Added to fix a bad list bug -- TLE + for(var/obj/machinery/power/terminal/term in nodes) + if( istype( term.master, /obj/machinery/power/apc ) ) + numapc++ + + netexcess = avail - load + + if(numapc) + //very simple load balancing. If there was a net excess this tick then it must have been that some APCs used less than perapc, since perapc*numapc = avail + //Therefore we can raise the amount of power rationed out to APCs on the assumption that those APCs that used less than perapc will continue to do so. + //If that assumption fails, then some APCs will miss out on power next tick, however it will be rebalanced for the tick after. + if (netexcess >= 0) + perapc_excess += min(netexcess/numapc, (avail - perapc) - perapc_excess) + else + perapc_excess = 0 + + perapc = avail/numapc + perapc_excess + + if(netexcess > 100 && nodes && nodes.len) // if there was excess power last cycle + for(var/obj/machinery/power/smes/S in nodes) // find the SMESes in the network + S.restore() // and restore some of the power that was used + + //updates the viewed load (as seen on power computers) + viewload = round(load) + + //reset the powernet + load = 0 + avail = newavail + newavail = 0 + +/datum/powernet/proc/get_electrocute_damage() + switch(avail) + if (1000000 to INFINITY) + return min(rand(50,160),rand(50,160)) + if (200000 to 1000000) + return min(rand(25,80),rand(25,80)) + if (100000 to 200000)//Ave powernet + return min(rand(20,60),rand(20,60)) + if (50000 to 100000) + return min(rand(15,40),rand(15,40)) + if (1000 to 50000) + return min(rand(10,20),rand(10,20)) + else + return 0 + +//////////////////////////////////////////////// +// 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(!istype(src, /turf/simulated/floor)) + return null + for(var/obj/structure/cable/C in src) + if(C.d1 == 0) + return C + return null + +/area/proc/get_apc() + // This was a simple locate() in src, but an unresolved BYOND bug causes trying to locate() in an + // area to take an inconceivably long time, especially for large areas. + // See: http://www.byond.com/forum/?post=1860571 + var/obj/machinery/power/apc/FINDME + for(FINDME in src) + return FINDME \ No newline at end of file diff --git a/code/modules/power/singularity/collector.dm b/code/modules/power/singularity/collector.dm index c7e96dd4737..3e94274cf2f 100644 --- a/code/modules/power/singularity/collector.dm +++ b/code/modules/power/singularity/collector.dm @@ -8,7 +8,6 @@ var/global/list/rad_collectors = list() icon_state = "ca" anchored = 0 density = 1 - directwired = 1 req_access = list(access_engine_equip) // use_power = 0 var/obj/item/weapon/tank/plasma/P = null diff --git a/code/modules/power/singularity/containment_field.dm b/code/modules/power/singularity/containment_field.dm index b940014b517..2b32af83526 100644 --- a/code/modules/power/singularity/containment_field.dm +++ b/code/modules/power/singularity/containment_field.dm @@ -46,57 +46,23 @@ return 1 return 0 - - -/obj/machinery/containment_field/proc/shock(mob/living/user as mob) +/obj/machinery/containment_field/shock(mob/living/user as mob) if(hasShocked) return 0 if(!FG1 || !FG2) qdel(src) return 0 - if(iscarbon(user)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, user.loc) - s.start() - + if(isliving(user)) hasShocked = 1 var/shock_damage = min(rand(30,40),rand(30,40)) - user.burn_skin(shock_damage) - user.updatehealth() - user.visible_message("\red [user.name] was shocked by the [src.name]!", \ - "\red You feel a powerful shock course through your body sending you flying!", \ - "\red You hear a heavy electrical crack") + user.electrocute_act(shock_damage, src) - var/stun = min(shock_damage, 15) - user.Stun(stun) - user.Weaken(10) - - user.updatehealth() var/atom/target = get_edge_target_turf(user, get_dir(src, get_step_away(user, src))) user.throw_at(target, 200, 4) sleep(20) + hasShocked = 0 - return - - else if(issilicon(user)) - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, user.loc) - s.start() - - hasShocked = 1 - var/shock_damage = rand(15,30) - user.take_overall_damage(0,shock_damage) - user.visible_message("\red [user.name] was shocked by the [src.name]!", \ - "\red Energy pulse detected, system damaged!", \ - "\red You hear an electrical crack") - if(prob(20)) - user.Stun(2) - - sleep(20) - hasShocked = 0 - return - return /obj/machinery/containment_field/proc/set_master(var/master1,var/master2) diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 687cbd7dcb5..a30d2de451e 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -48,7 +48,6 @@ ..() if(state == 2 && anchored) connect_to_network() - src.directwired = 1 if(frequency) set_frequency(frequency) /obj/machinery/power/emitter/multitool_menu(var/mob/user,var/obj/item/device/multitool/P) @@ -145,8 +144,8 @@ return if(((src.last_shot + src.fire_delay) <= world.time) && (src.active == 1)) - if(!active_power_usage || avail(active_power_usage)) - add_load(active_power_usage) + var/actual_load = draw_power(active_power_usage) + if(actual_load >= active_power_usage) //does the laser have enough power to shoot? if(!powered) powered = 1 update_icon() @@ -238,7 +237,6 @@ state = 2 user << "You weld the [src] to the floor." connect_to_network() - src.directwired = 1 else user << "\red You need more welding fuel to complete this task." if(2) @@ -252,7 +250,6 @@ state = 1 user << "You cut the [src] free from the floor." disconnect_from_network() - src.directwired = 0 else user << "\red You need more welding fuel to complete this task." return @@ -275,11 +272,8 @@ ..() return -/obj/machinery/power/emitter/emag_act(user as mob) +/obj/machinery/power/emitter/emag_act(var/mob/living/user as mob) if(!emagged) - if(!ishuman(user)) - return - var/mob/living/carbon/human/H = user locked = 0 emagged = 1 - H.visible_message("[H.name] emags the [src.name].","\red You short out the lock.") + user.visible_message("[user.name] emags the [src.name].","\red You short out the lock.") diff --git a/code/modules/power/smes.dm b/code/modules/power/smes.dm index ba38cf9ea73..7fb7aee723b 100644 --- a/code/modules/power/smes.dm +++ b/code/modules/power/smes.dm @@ -13,22 +13,32 @@ icon_state = "smes" density = 1 anchored = 1 - use_power = 0 - var/output = 50000 - var/lastout = 0 - var/loaddemand = 0 - var/capacity = 5e6 - var/charge = 0 - var/charging = 0 - var/chargemode = 0 - var/chargecount = 0 - var/chargelevel = 50000 - var/input_level_max = 200000 // cap on input_level - var/output_level_max = 200000 // cap on output_level - var/online = 1 + + var/capacity = 5e6 // maximum charge + var/charge = 1e6 // actual charge + + var/input_attempt = 0 // 1 = attempting to charge, 0 = not attempting to charge + var/inputting = 0 // 1 = actually inputting, 0 = not inputting + var/input_level = 50000 // amount of power the SMES attempts to charge by + var/input_level_max = 200000 // cap on input_level + var/input_available = 0 // amount of charge available from input last tick + + var/output_attempt = 1 // 1 = attempting to output, 0 = not attempting to output + var/outputting = 1 // 1 = actually outputting, 0 = not outputting + var/output_level = 50000 // amount of power the SMES attempts to output + var/output_level_max = 200000 // cap on output_level + var/output_used = 0 // amount of power actually outputted. may be less than output_level if the powernet returns excess power + + //Holders for powerout event. + var/last_output_attempt = 0 + var/last_input_attempt = 0 + var/last_charge = 0 + + var/open_hatch = 0 var/name_tag = null + var/building_terminal = 0 //Suggestions about how to avoid clickspam building several terminals accepted! var/obj/machinery/power/terminal/terminal = null - var/list/overlay_images + /obj/machinery/power/smes/New() ..() @@ -42,6 +52,7 @@ component_parts += new /obj/item/weapon/stock_parts/capacitor(null) component_parts += new /obj/item/stack/cable_coil(null, 5) RefreshParts() + spawn(5) if(!powernet) connect_to_network() @@ -59,9 +70,22 @@ terminal.master = src if(!terminal.powernet) terminal.connect_to_network() - updateicon() + update_icon() return +/obj/machinery/power/smes/upgraded/New() + ..() + component_parts = list() + component_parts += new /obj/item/weapon/circuitboard/smes(null) + component_parts += new /obj/item/weapon/stock_parts/cell/high(null) + component_parts += new /obj/item/weapon/stock_parts/cell/high(null) + component_parts += new /obj/item/weapon/stock_parts/cell/high(null) + component_parts += new /obj/item/weapon/stock_parts/cell/high(null) + component_parts += new /obj/item/weapon/stock_parts/cell/high(null) + component_parts += new /obj/item/weapon/stock_parts/capacitor(null) + component_parts += new /obj/item/stack/cable_coil(null, 5) + RefreshParts() + /obj/machinery/power/smes/RefreshParts() var/IO = 0 var/C = 0 @@ -73,41 +97,25 @@ C += PC.maxcharge capacity = C / (15000) * 1e6 -/obj/machinery/power/smes/proc/updateicon() +/obj/machinery/power/smes/update_icon() overlays.Cut() if(stat & BROKEN) return - if(isnull(src.overlay_images)) - src.overlay_images = new - src.overlay_images.len = 3 + overlays += image('icons/obj/power.dmi', "smes-op[outputting]") - src.overlay_images[1] = image('icons/obj/power.dmi', "smes-op[online]") - src.overlay_images[2] = image('icons/obj/power.dmi', "smes-oc0") - src.overlay_images[3] = image('icons/obj/power.dmi', "smes-og1") - - var/image/buffer - - buffer = src.overlay_images[1] - buffer.icon_state = "smes-op[online]" - overlays += src.overlay_images[1] - - if(!charging && chargemode) - buffer = src.overlay_images[2] - buffer.icon_state = "smes-oc0" - overlays += src.overlay_images[2] + if(inputting == 2) + overlays += image('icons/obj/power.dmi', "smes-oc2") + else if (inputting == 1) + overlays += image('icons/obj/power.dmi', "smes-oc1") else - buffer = src.overlay_images[2] - buffer.icon_state = "smes-oc1" - overlays += src.overlay_images[2] + if(input_attempt) + overlays += image('icons/obj/power.dmi', "smes-oc0") var/clevel = chargedisplay() - if(clevel > 0) - buffer = src.overlay_images[3] - buffer.icon_state = "smes-og[clevel]" - overlays += src.overlay_images[3] + if(clevel>0) + overlays += image('icons/obj/power.dmi', "smes-og[clevel]") return - /obj/machinery/power/smes/attackby(obj/item/I, mob/user, params) //opening using screwdriver if(default_deconstruction_screwdriver(user, "[initial(icon_state)]-o", initial(icon_state), I)) @@ -203,7 +211,7 @@ user.visible_message(\ "[user.name] cuts the cables and dismantles the power terminal.",\ "You cut the cables and dismantle the power terminal.") - charging = 0 //stop inputting, since we have don't have a terminal anymore + inputting = 0 //stop inputting, since we have don't have a terminal anymore qdel(terminal) return @@ -221,92 +229,70 @@ disconnect_terminal() return ..() -// create a terminal object pointing towards the SMES -// wires will attach to this -/obj/machinery/power/smes/proc/make_terminal(var/turf/T) - terminal = new/obj/machinery/power/terminal(T) - terminal.dir = get_dir(T,src) - terminal.master = src - -/obj/machinery/power/smes/proc/disconnect_terminal() - if(terminal) - terminal.master = null - terminal = null + return round(5.5*charge/(capacity ? capacity : 5e6)) /obj/machinery/power/smes/proc/chargedisplay() - return round(5.5*charge/capacity) - + return round(5.5*charge/(capacity ? capacity : 5e6)) +#define SMESRATE 0.05 /obj/machinery/power/smes/process() - if(stat & BROKEN) - return + + if(stat & BROKEN) return //store machine state to see if we need to update the icon overlays var/last_disp = chargedisplay() - var/last_chrg = charging - var/last_onln = online + var/last_chrg = inputting + var/last_onln = outputting - if(terminal) - var/excess = terminal.surplus() + //inputting + if(input_attempt) + var/target_load = min((capacity-charge)/SMESRATE, input_level) // charge at set rate, limited to spare capacity + var/actual_load = draw_power(target_load) // add the load to the terminal side network + charge += actual_load * SMESRATE // increase the charge - if(charging) - if(excess >= 0) // if there's power available, try to charge + if (actual_load >= target_load) // Did we charge at full rate? + inputting = 2 + else if (actual_load) // If not, did we charge at least partially? + inputting = 1 + else // Or not at all? + inputting = 0 - var/load = min((capacity-charge)/SMESRATE, chargelevel) // charge at set rate, limited to spare capacity + //outputting + if(outputting) + output_used = min( charge/SMESRATE, output_level) //limit output to that stored - charge += load * SMESRATE // increase the charge + charge -= output_used*SMESRATE // reduce the storage (may be recovered in /restore() if excessive) - add_load(load) // add the load to the terminal side network + add_avail(output_used) // add output to powernet (smes side) - else // if not enough capcity - charging = 0 // stop charging - chargecount = 0 - - else - if(chargemode) - if(chargecount > rand(3,6)) - charging = 1 - chargecount = 0 - - if(excess > chargelevel) - chargecount++ - else - chargecount = 0 - else - chargecount = 0 - - if(online) // if outputting - lastout = min( charge/SMESRATE, output) //limit output to that stored - - charge -= lastout*SMESRATE // reduce the storage (may be recovered in /restore() if excessive) - - add_avail(lastout) // add output to powernet (smes side) - - if(charge < 0.0001) - online = 0 // stop output if charge falls to zero + if(output_used < 0.0001) // either from no charge or set to 0 + outputting = 0 + investigate_log("lost power and turned off","singulo") + else if(output_attempt && charge > output_level && output_level > 0) + outputting = 1 + else + output_used = 0 // only update icon if state changed - if(last_disp != chargedisplay() || last_chrg != charging || last_onln != online) - updateicon() + if(last_disp != chargedisplay() || last_chrg != inputting || last_onln != outputting) + update_icon() + - return // called after all power processes are finished // restores charge level to smes if there was excess this ptick - - /obj/machinery/power/smes/proc/restore() if(stat & BROKEN) return - if(!online) - loaddemand = 0 + if(!outputting) + output_used = 0 return var/excess = powernet.netexcess // this was how much wasn't used on the network last ptick, minus any removed by other SMESes - excess = min(lastout, excess) // clamp it to how much was actually output by this SMES last ptick + excess = min(output_used, excess) // clamp it to how much was actually output by this SMES last ptick excess = min((capacity-charge)/SMESRATE, excess) // for safety, also limit recharge by space capacity of SMES (shouldn't happen) @@ -314,23 +300,51 @@ var/clev = chargedisplay() - charge += excess * SMESRATE + charge += excess * SMESRATE // restore unused power powernet.netexcess -= excess // remove the excess from the powernet, so later SMESes don't try to use it - loaddemand = lastout-excess + output_used -= excess - if(clev != chargedisplay() ) - updateicon() + if(clev != chargedisplay() ) //if needed updates the icons overlay + update_icon() return +//Will return 1 on failure +/obj/machinery/power/smes/proc/make_terminal(const/mob/user) + if (user.loc == loc) + user << "You must not be on the same tile as the [src]." + return 1 -/obj/machinery/power/smes/add_load(var/amount) + //Direction the terminal will face to + var/tempDir = get_dir(user, src) + switch(tempDir) + if (NORTHEAST, SOUTHEAST) + tempDir = EAST + if (NORTHWEST, SOUTHWEST) + tempDir = WEST + var/turf/tempLoc = get_step(src, reverse_direction(tempDir)) + if (istype(tempLoc, /turf/space)) + user << "You can't build a terminal on space." + return 1 + else if (istype(tempLoc)) + if(tempLoc.intact) + user << "You must remove the floor plating first." + return 1 + user << "You start adding cable to the [src]." + if(do_after(user, 50)) + terminal = new /obj/machinery/power/terminal(tempLoc) + terminal.dir = tempDir + terminal.master = src + return 0 + return 1 + +/obj/machinery/power/smes/draw_power(var/amount) if(terminal && terminal.powernet) - terminal.powernet.load += amount - + return terminal.powernet.draw_power(amount) + return 0 /obj/machinery/power/smes/attack_ai(mob/user) - add_fingerprint(user) + add_hiddenprint(user) ui_interact(user) /obj/machinery/power/smes/attack_ghost(mob/user) @@ -344,7 +358,6 @@ return /obj/machinery/power/smes/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) - if(stat & BROKEN) return @@ -352,14 +365,21 @@ var/data[0] data["nameTag"] = name_tag data["storedCapacity"] = round(100.0*charge/capacity, 0.1) - data["charging"] = charging - data["chargeMode"] = chargemode - data["chargeLevel"] = chargelevel + data["charging"] = inputting + data["chargeMode"] = input_attempt + data["chargeLevel"] = input_level data["chargeMax"] = input_level_max - data["outputOnline"] = online - data["outputLevel"] = output + data["outputOnline"] = output_attempt + data["outputLevel"] = output_level data["outputMax"] = output_level_max - data["outputLoad"] = round(loaddemand) + data["outputLoad"] = round(output_used) + + if(outputting) + data["outputting"] = 2 // smes is outputting + else if(!outputting && output_attempt) + data["outputting"] = 1 // smes is online but not outputting because it's charge level is too low + else + data["outputting"] = 0 // smes is not outputting // update the ui if it exists, returns null if no ui is passed/found ui = nanomanager.try_update_ui(user, src, ui_key, ui, data, force_open) @@ -374,7 +394,6 @@ // auto update every Master Controller tick ui.set_auto_update(1) - /obj/machinery/power/smes/Topic(href, href_list) ..() @@ -391,43 +410,39 @@ return 0 // Do not update ui if( href_list["cmode"] ) - chargemode = !chargemode - if(!chargemode) - charging = 0 - updateicon() + inputting(!input_attempt) + update_icon() else if( href_list["online"] ) - online = !online - updateicon() + outputting(!output_attempt) + update_icon() else if( href_list["input"] ) switch( href_list["input"] ) if("min") - chargelevel = 0 + input_level = 0 if("max") - chargelevel = input_level_max //30000 + input_level = input_level_max if("set") - chargelevel = input(usr, "Enter new input level (0-[input_level_max])", "SMES Input Power Control", chargelevel) as num - chargelevel = max(0, min(input_level_max, chargelevel)) // clamp to range + input_level = input(usr, "Enter new input level (0-[input_level_max])", "SMES Input Power Control", input_level) as num + input_level = max(0, min(input_level_max, input_level)) // clamp to range else if( href_list["output"] ) switch( href_list["output"] ) if("min") - output = 0 + output_level = 0 if("max") - output = output_level_max //30000 + output_level = output_level_max if("set") - output = input(usr, "Enter new output level (0-[output_level_max])", "SMES Output Power Control", output) as num - output = max(0, min(output_level_max, output)) // clamp to range + output_level = input(usr, "Enter new output level (0-[output_level_max])", "SMES Output Power Control", output_level) as num + output_level = max(0, min(output_level_max, output_level)) // clamp to range - investigate_log("input/output; [chargelevel>output?"":""][chargelevel]/[output] | Output-mode: [online?"on":"off"] | Input-mode: [chargemode?"auto":"off"] by [usr.key]","singulo") + investigate_log("input/output; [input_level>output_level?"":""][input_level]/[output_level] | Output-mode: [output_attempt?"on":"off"] | Input-mode: [input_attempt?"auto":"off"] by [usr.key]","singulo") return 1 - /obj/machinery/power/smes/proc/ion_act() - if((src.z in config.station_levels)) + if(src.z in config.station_levels) if(prob(1)) //explosion - world << "\red SMES explosion in [src.loc.loc]" for(var/mob/M in viewers(src)) M.show_message("\red The [src.name] is making strange noises!", 3, "\red You hear sizzling electronics.", 2) sleep(10*pick(4,5,6,7,10,14)) @@ -435,11 +450,10 @@ smoke.set_up(3, 0, src.loc) smoke.attach(src) smoke.start() - explosion(src.loc, -1, 0, 1, 3, 0) + explosion(src.loc, -1, 0, 1, 3, 1, 0) qdel(src) return if(prob(15)) //Power drain - world << "\red SMES power drain in [src.loc.loc]" var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread s.set_up(3, 1, src) s.start() @@ -448,45 +462,43 @@ else emp_act(2) if(prob(5)) //smoke only - world << "\red SMES smoke in [src.loc.loc]" var/datum/effect/effect/system/harmless_smoke_spread/smoke = new /datum/effect/effect/system/harmless_smoke_spread() smoke.set_up(3, 0, src.loc) smoke.attach(src) smoke.start() +/obj/machinery/power/smes/proc/inputting(var/do_input) + input_attempt = do_input + if(!input_attempt) + inputting = 0 + +/obj/machinery/power/smes/proc/outputting(var/do_output) + output_attempt = do_output + if(!output_attempt) + outputting = 0 /obj/machinery/power/smes/emp_act(severity) - online = 0 - charging = 0 - output = 0 + inputting(rand(0,1)) + outputting(rand(0,1)) + output_level = rand(0, output_level_max) + input_level = rand(0, input_level_max) charge -= 1e6/severity if (charge < 0) charge = 0 - spawn(100) - output = initial(output) - charging = initial(charging) - online = initial(online) + update_icon() ..() - - /obj/machinery/power/smes/engineering - charge = 1e6 // Engineering starts with some charge for singulo + charge = 1e6 // Engineering starts with some charge for singulo /obj/machinery/power/smes/magical name = "magical power storage unit" desc = "A high-capacity superconducting magnetic energy storage (SMES) unit. Magically produces power." - process() - capacity = INFINITY - charge = INFINITY - ..() + capacity = 9000000 + output_level = 250000 -/proc/rate_control(var/S, var/V, var/C, var/Min=1, var/Max=5, var/Limit=null) - var/href = "-[href]=-[Min]'>- [(C?C : 0)] [href]=[Min]'>+[href]=[Max]'>+" - if(Limit) return "[href]=-[Limit]'>-"+rate+"[href]=[Limit]'>+" - return rate +/obj/machinery/power/smes/magical/process() + charge = 5000000 + ..() - - -#undef SMESRATE +#undef SMESRATE \ No newline at end of file diff --git a/code/modules/power/solar.dm b/code/modules/power/solar.dm index 3eb5b0dc8e3..33b4dddea2e 100644 --- a/code/modules/power/solar.dm +++ b/code/modules/power/solar.dm @@ -289,15 +289,11 @@ var/lastgen = 0 var/track = 0 // 0= off 1=timed 2=auto (tracker) var/trackrate = 600 // 300-900 seconds - var/nexttime = 0 // time for a panel to rotate of 1° in manual tracking + var/nexttime = 0 // time for a panel to rotate of 1? in manual tracking var/obj/machinery/power/tracker/connected_tracker = null var/list/connected_panels = list() - -/obj/machinery/power/solar_control/New() - ..() - if(ticker) - initialize() +/obj/machinery/power/solar_control/initialize() connect_to_network() /obj/machinery/power/solar_control/Destroy() @@ -309,11 +305,12 @@ /obj/machinery/power/solar_control/disconnect_from_network() ..() - sun.solars.Remove(src) + if(sun) + sun.solars.Remove(src) /obj/machinery/power/solar_control/connect_to_network() var/to_return = ..() - if(powernet) //if connected and not already in solar list... + if(powernet && sun) //if connected and not already in solar list... sun.solars |= src //... add it return to_return @@ -366,9 +363,21 @@ if(cdir > -1) overlays += image('icons/obj/computer.dmi', "solcon-o", FLY_LAYER, angle2dir(cdir)) +/obj/machinery/power/solar_control/attack_ai(mob/user as mob) + src.add_hiddenprint(user) + ui_interact(user) + +/obj/machinery/power/solar_control/attack_ghost(mob/user as mob) + ui_interact(user) + /obj/machinery/power/solar_control/attack_hand(mob/user) - if(!..()) - ui_interact(user) + if(..(user)) + return 1 + + if(stat & BROKEN) + return + + ui_interact(user) /obj/machinery/power/solar_control/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) @@ -438,9 +447,9 @@ connected_tracker.unset_control() if(track==1 && trackrate) //manual tracking and set a rotation speed - if(nexttime <= world.time) //every time we need to increase/decrease the angle by 1°... + if(nexttime <= world.time) //every time we need to increase/decrease the angle by 1?... targetdir = (targetdir + trackrate/abs(trackrate) + 360) % 360 //... do it - nexttime += 36000/abs(trackrate) //reset the counter for the next 1° + nexttime += 36000/abs(trackrate) //reset the counter for the next 1? /obj/machinery/power/solar_control/Topic(href, href_list) if(..()) diff --git a/code/modules/power/terminal.dm b/code/modules/power/terminal.dm index f739ba93fda..194e96f6dba 100644 --- a/code/modules/power/terminal.dm +++ b/code/modules/power/terminal.dm @@ -11,7 +11,6 @@ layer = TURF_LAYER var/obj/machinery/power/master = null anchored = 1 - directwired = 0 // must have a cable on same turf connecting to terminal layer = 2.6 // a bit above wires @@ -20,6 +19,11 @@ var/turf/T = src.loc if(level==1) hide(T.intact) return + +/obj/machinery/power/terminal/Destroy() + if(master) + master.disconnect_terminal() + return ..() /obj/machinery/power/terminal/hide(var/i) diff --git a/code/modules/power/tracker.dm b/code/modules/power/tracker.dm index d429d876769..9fc6765e6fa 100644 --- a/code/modules/power/tracker.dm +++ b/code/modules/power/tracker.dm @@ -23,11 +23,11 @@ /obj/machinery/power/tracker/Destroy() unset_control() //remove from control computer - return ..() + ..() //set the control of the tracker to a given computer if closer than SOLAR_MAX_DIST /obj/machinery/power/tracker/proc/set_control(var/obj/machinery/power/solar_control/SC) - if(!SC || (get_dist(src, SC) > SOLAR_MAX_DIST)) + if(SC && (get_dist(src, SC) > SOLAR_MAX_DIST)) return 0 control = SC SC.connected_tracker = src @@ -58,7 +58,7 @@ if(powernet && (powernet == control.powernet)) //update if we're still in the same powernet control.cdir = angle -/obj/machinery/power/tracker/attackby(var/obj/item/weapon/W, var/mob/user, params) +/obj/machinery/power/tracker/attackby(var/obj/item/weapon/W, var/mob/user) if(istype(W, /obj/item/weapon/crowbar)) playsound(src.loc, 'sound/machines/click.ogg', 50, 1) @@ -70,7 +70,7 @@ S.give_glass() playsound(src.loc, 'sound/items/Deconstruct.ogg', 50, 1) user.visible_message("[user] takes the glass off the tracker.") - qdel(src) + del(src) // qdel return ..() @@ -81,4 +81,4 @@ name = "tracker electronics" icon = 'icons/obj/doors/door_assembly.dmi' icon_state = "door_electronics" - w_class = 2.0 \ No newline at end of file + w_class = 2.0 diff --git a/code/modules/power/turbine.dm b/code/modules/power/turbine.dm index 01e52edfee9..583de36d245 100644 --- a/code/modules/power/turbine.dm +++ b/code/modules/power/turbine.dm @@ -22,7 +22,6 @@ anchored = 1 density = 1 var/obj/machinery/compressor/compressor - directwired = 1 var/turf/simulated/outturf var/lastgen diff --git a/code/modules/research/rdmachines.dm b/code/modules/research/rdmachines.dm index ee922a055a6..4f0b81e5315 100644 --- a/code/modules/research/rdmachines.dm +++ b/code/modules/research/rdmachines.dm @@ -35,20 +35,6 @@ src.disable_wire = pick(w) w -= src.disable_wire -/obj/machinery/r_n_d/proc/ - shock(mob/user, prb) - if(stat & (BROKEN|NOPOWER)) // unpowered, no shock - return 0 - if(!prob(prb)) - return 0 - var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread - s.set_up(5, 1, src) - s.start() - if (electrocute_mob(user, get_area(src), src, 0.7)) - return 1 - else - return 0 - /obj/machinery/r_n_d/attack_hand(mob/user as mob) if (shocked) shock(user,50) diff --git a/code/world.dm b/code/world.dm index e1e99b18d2f..2086f563590 100644 --- a/code/world.dm +++ b/code/world.dm @@ -18,7 +18,7 @@ var/global/datum/global_init/init = new () cache_lifespan = 0 //stops player uploaded stuff from being kept in the rsc past the current session -#define RECOMMENDED_VERSION 501 +#define RECOMMENDED_VERSION 508 /world/New() //logs diff --git a/icons/obj/power.dmi b/icons/obj/power.dmi index f0628147b4d5ff586fb68ce168d85a925897c93a..ea42ac5b39daa095c8d980df70860c1f5d3297a4 100644 GIT binary patch literal 24398 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 aDOi 1}} -
APC Hacked
+ {{else malfStatus == 2}} + {{:helper.link('Shunt Core Processes', 'arrowreturn-1-s', {'occupyapc' : 1})}} + {{else malfStatus == 3}} + {{:helper.link('Return to Main Core', 'arrowreturn-1-w', {'deoccupyapc' : 1})}} + {{else malfStatus == 4}} + {{:helper.link('Shunt Core Processes', 'arrowreturn-1-s', {'occupyapc' : 1}, 'linkOff')}} {{/if}} {{/if}} diff --git a/nano/templates/pda.tmpl b/nano/templates/pda.tmpl index 684c52afc5c..27658721f94 100644 --- a/nano/templates/pda.tmpl +++ b/nano/templates/pda.tmpl @@ -519,9 +519,9 @@ Used In File(s): \code\game\objects\items\devices\PDA\PDA.dm {{else data.mode == 43}} -

Station Powermonitors

+

Station Power Monitors

- Select A power monitor: + Select a power monitor:
{{for data.records.powermonitors}}
@@ -532,38 +532,43 @@ Used In File(s): \code\game\objects\items\devices\PDA\PDA.dm {{else data.mode == 433}}

Powernet Status

-
-
- Current Load: -
-
- {{:data.records.powerload}} W -
-
-
-
- Total Power: -
-
- {{:data.records.poweravail}} W -
-
-
-
- - {{for data.records.apcs}} - {{if index % 20 === 0}} - - {{/if}} - - {{:helper.string('', value.Equipment==1 ? '#4f7529' : '#8f1414')}} - {{:helper.string('', value.Lights==1 ? '#4f7529' : '#8f1414')}} - {{:helper.string('', value.Environment==1 ? '#4f7529' : '#8f1414')}} - {{:helper.string('', value.CellStatus==1 ? '#4f7529' : '#8f1414', value.CellStatus==-1 ? 'No Cell' : CellPct + '%')}} - - {{/for}} -
 Area  Eqp.  Lgt.  Env  Cell 
{{:value.Name}}   {1}
-
+ {{if data.records.powerconnected == 1}} +
+
+ Current Load: +
+
+ {{:data.records.powerload}} W +
+
+
+
+ Total Power: +
+
+ {{:data.records.poweravail}} W +
+
+
+
+ + {{for data.records.apcs}} + {{if index % 20 === 0}} + + {{/if}} + + {{:helper.string('', value.Equipment==1 ? '#4f7529' : '#8f1414')}} + {{:helper.string('', value.Lights==1 ? '#4f7529' : '#8f1414')}} + {{:helper.string('', value.Environment==1 ? '#4f7529' : '#8f1414')}} + {{:helper.string('', value.CellStatus==1 ? '#4f7529' : '#8f1414', value.CellStatus==-1 ? 'No Cell' : value.CellPct + '%')}} + + {{/for}} +
 Area  Eqp.  Lgt.  Env  Cell 
{{:value.Name}}   {1}
+
+ {{else}} + Power monitor not connected to net + {{/if}} + {{else data.mode == 44}} diff --git a/nano/templates/smartfridge.tmpl b/nano/templates/smartfridge.tmpl index b371baaef32..cc25d760ce0 100644 --- a/nano/templates/smartfridge.tmpl +++ b/nano/templates/smartfridge.tmpl @@ -34,42 +34,3 @@ No products loaded. {{/if}}
- -{{if data.panel_open}} -
-

Access Panel

-
-
-
- Wires: -
-
- {{for data.wires}} -
-
- {{:value.wire}} wire: -
-
- {{if value.cut}} - {{:helper.link('Mend', 'plus', {'cutwire' : value.index})}} - {{else}} - {{:helper.link('Cut', 'minus', {'cutwire' : value.index})}} - {{:helper.link('Pulse', 'signal-diag', {'pulsewire' : value.index})}} - {{/if}} -
-
- {{/for}} -
-
- -
- The orange light is {{:data.electrified ? "on" : "off"}}.
- The red light is {{:data.shoot_inventory ? "on" : "off"}}.
- {{if data.secure}} - The green light is - - {{:data.locked == 1 ? "off" : data.locked == -1 ? "blinking" : "on"}} - .
- {{/if}} -
-{{/if}} diff --git a/nano/templates/smes.tmpl b/nano/templates/smes.tmpl index 04b93b41480..799ac3911d7 100644 --- a/nano/templates/smes.tmpl +++ b/nano/templates/smes.tmpl @@ -18,10 +18,12 @@
{{:helper.link('Auto', 'refresh', {'cmode' : 1}, data.chargeMode ? 'selected' : null)}}{{:helper.link('Off', 'close', {'cmode' : 1}, data.chargeMode ? null : 'selected')}}   - {{if data.charging}} + {{if data.charging == 2}} [Charging] + {{else data.charging == 1}} + [Partially Charging] {{else}} - [Not Charging] + [Not Charging] {{/if}}
@@ -36,7 +38,7 @@ {{:helper.link('MIN', null, {'input' : 'min'}, (data.chargeLevel > 0) ? null : 'disabled')}} {{:helper.link('SET', null, {'input' : 'set'}, null)}} {{:helper.link('MAX', null, {'input' : 'max'}, (data.chargeLevel < data.chargeMax) ? null : 'disabled')}} -
 {{:data.chargeLevel}} W 
+
 {{:data.chargeLevel}} W 
@@ -48,6 +50,14 @@
{{:helper.link('Online', 'power', {'online' : 1}, data.outputOnline ? 'selected' : null)}}{{:helper.link('Offline', 'close', {'online' : 1}, data.outputOnline ? null : 'selected')}} +   + {{if data.outputting == 2}} + [Outputting] + {{else data.outputting == 1}} + [Stored energy too low] + {{else}} + [Not Outputting] + {{/if}}
@@ -61,7 +71,7 @@ {{:helper.link('MIN', null, {'output' : 'min'}, (data.outputLevel > 0) ? null : 'disabled')}} {{:helper.link('SET', null, {'output' : 'set'}, null)}} {{:helper.link('MAX', null, {'output' : 'max'}, (data.outputLevel < data.outputMax) ? null : 'disabled')}} -
 {{:data.outputLevel}} W 
+
 {{:data.outputLevel}} W 
diff --git a/nano/templates/solar_control.tmpl b/nano/templates/solar_control.tmpl index 17c3566bece..263764679a2 100644 --- a/nano/templates/solar_control.tmpl +++ b/nano/templates/solar_control.tmpl @@ -3,7 +3,7 @@ Title: Solar Control Computer Used In File(s): \code\modules\power\solar.dm --> -

Status:

+

Status

@@ -30,11 +30,11 @@ Used In File(s): \code\modules\power\solar.dm
-

Tracking:

+

Tracking

-
- Tracker status:  +
+ Tracker status:
{{:helper.link('Off', 'close', {'track' : '0'}, (data.tracking_state == 0) ? 'selected' : '')}} @@ -47,7 +47,7 @@ Used In File(s): \code\modules\power\solar.dm
-
+
Tracking rate:
@@ -66,10 +66,10 @@ Used In File(s): \code\modules\power\solar.dm
-

Connected devices :

+

Connected Devices

-
+
Solars panels:
@@ -78,15 +78,15 @@ Used In File(s): \code\modules\power\solar.dm
-
+
Solar tracker:
{{if data.connected_tracker}} - Found  + Found {{else}} - Not found  + Not Found {{/if}}
diff --git a/paradise.dme b/paradise.dme index 131d5139d79..f0ae528ffd2 100644 --- a/paradise.dme +++ b/paradise.dme @@ -21,6 +21,7 @@ #include "code\__DEFINES\clothing.dm" #include "code\__DEFINES\combat.dm" #include "code\__DEFINES\flags.dm" +#include "code\__DEFINES\game.dm" #include "code\__DEFINES\genetics.dm" #include "code\__DEFINES\hud.dm" #include "code\__DEFINES\language.dm" @@ -222,6 +223,7 @@ #include "code\datums\wires\particle_accelerator.dm" #include "code\datums\wires\radio.dm" #include "code\datums\wires\robot.dm" +#include "code\datums\wires\smartfridge.dm" #include "code\datums\wires\syndicatebomb.dm" #include "code\datums\wires\taperecorder.dm" #include "code\datums\wires\vending.dm" @@ -1515,6 +1517,7 @@ #include "code\modules\power\lighting.dm" #include "code\modules\power\port_gen.dm" #include "code\modules\power\power.dm" +#include "code\modules\power\powernet.dm" #include "code\modules\power\smes.dm" #include "code\modules\power\solar.dm" #include "code\modules\power\switch.dm"