// main_status var #define APC_EXTERNAL_POWER_NOTCONNECTED 0 #define APC_EXTERNAL_POWER_NOENERGY 1 #define APC_EXTERNAL_POWER_GOOD 2 //opened #define APC_CLOSED 0 #define APC_OPENED 1 #define APC_COVER_OFF 2 #define APC_AUTOFLAG_ALL_OFF 0 #define APC_AUTOFLAG_ENVIRO_ONLY 1 #define APC_AUTOFLAG_EQUIPMENT_OFF 2 #define APC_AUTOFLAG_ALL_ON 3 //electronics_state #define APC_ELECTRONICS_NONE 0 #define APC_ELECTRONICS_INSTALLED 1 #define APC_ELECTRONICS_SECURED 2 /// Power channel is off, anything connected to it is not powered, cannot be set manually by players #define CHANNEL_SETTING_OFF 0 /// APC power channel Setting Off, if set while apc is "on" set apc to "off" otherwise set to "auto-off" #define CHANNEL_SETTING_AUTO_OFF 1 /// APC power channel setting on, #define CHANNEL_SETTING_ON 2 //on // APC user setting, #define CHANNEL_SETTING_AUTO_ON 3 //auto // the Area Power Controller (APC), formerly Power Distribution Unit (PDU) // one per area, needs wire conection to power network through a terminal // controls power to devices in that area // may be opened to change power cell // three different channels (lighting/equipment/environ) - may each be set to on, off, or auto /obj/machinery/power/apc name = "area power controller" desc = "A control terminal for the area electrical systems." icon_state = "apc0" max_integrity = 200 integrity_failure = 50 resistance_flags = FIRE_PROOF req_access = list(ACCESS_ENGINE_EQUIP) siemens_strength = 1 damage_deflection = 10 move_resist = INFINITY // set so that APCs aren't found as powernet nodes //Hackish, Horrible, was like this before I changed it :( powernet = 0 /*** APC Area/Powernet vars ***/ /// the area that this APC is in var/area/apc_area /// the set string name of the area, used in naming the APC var/areastring = null /// The power terminal connected to this APC var/obj/machinery/power/terminal/terminal = null /// The status of the terminals powernet that this APC is connected to: not connected, no power, or recieving power var/main_status = APC_EXTERNAL_POWER_NOTCONNECTED /// amount of power used in the last cycle for lighting channel var/last_used_lighting = 0 /// amount of power used in the last cycle for equipment channel var/last_used_equipment = 0 /// amount of power used in the last cycle for environment channel var/last_used_environment = 0 /// amount of power used in the last cycle in total var/last_used_total = 0 /*** APC Cell Vars ***/ /// the cell type stored in this APC var/obj/item/stock_parts/cell/cell /// the percentage charge the internal battery will start with var/start_charge = 90 /// Base cell has 2500 capacity. Enter the path of a different cell you want to use. cell determines charge rates, max capacity, ect. These can also be changed with other APC vars, but isn't recommended to minimize the risk of accidental usage of dirty editted APCs var/cell_type = 2500 /*** APC Status Vars ***/ /// The wire panel associated with this APC var/datum/wires/apc/wires = null /// Can the APC recieve/transmit power? Determined by the condition of the 2 Main Power Wires var/shorted = FALSE /// Is the APC on and transmitting power to enabled breakers? Think of this var as the master breaker for the APC var/operating = TRUE /// The current charging mode of the APC: not charging, charging, fully charged var/charging = APC_NOT_CHARGING /// A counter var used to help determine if the APC has not been charging long enough to justify not performing certain auto setting such as turning all channels back on var/longtermpower = 10 /// State of the APC Cover - Closed, Open, or Off var/opened = APC_CLOSED /// Can silicons access this APC? var/aidisabled = FALSE var/electronics_state = APC_ELECTRONICS_NONE /*** APC Settings Vars ***/ /// The current setting for the lighting channel var/lighting_channel = CHANNEL_SETTING_AUTO_ON /// The current setting for the equipment channel var/equipment_channel = CHANNEL_SETTING_AUTO_ON /// The current setting for the environment channel var/environment_channel = CHANNEL_SETTING_AUTO_ON /// Is the APC cover locked? i.e cannot be opened? var/coverlocked = TRUE /// Is the APC User Interface locked (prevents interaction)? Will not prevent silicons or admin observers from interacting var/locked = TRUE /// If TRUE, the APC will automatically draw power from connect terminal, if FALSE it will not charge var/chargemode = TRUE /// Counter var, ticks up when the APC recieves power from terminal and resets to 0 when not charging, used for the `var/charging` var var/chargecount = 0 var/report_power_alarm = TRUE var/shock_proof = FALSE //if set to FALSE, this APC will not arc bolts of electricity if it's overloaded. // Nightshift var/nightshift_lights = FALSE var/last_nightshift_switch = 0 /// Used to determine if emergency lights should be on or off var/emergency_power = TRUE var/emergency_power_timer var/emergency_lights = FALSE /// settings variable for having the APC auto use certain power channel settings var/autoflag = APC_AUTOFLAG_ALL_OFF // 0 = off, 1= eqp and lights off, 2 = eqp off, 3 = all on. /// Being hijacked by a pulse demon? var/being_hijacked = FALSE /*** APC Malf AI Vars ****/ var/malfhack = FALSE //New var for my changes to AI malf. --NeoFite var/mob/living/silicon/ai/malfai = null //See above --NeoFite var/mob/living/silicon/ai/occupier = null /// Was this APC built instead of already existing? Used for malfhack to keep borgs from building apcs in space var/constructed = FALSE var/overload = 1 //used for the Blackout malf module /*** APC Overlay Vars ***/ var/update_state = -1 var/update_overlay = -1 var/global/status_overlays = FALSE var/updating_icon = FALSE var/global/list/status_overlays_lock var/global/list/status_overlays_charging var/global/list/status_overlays_equipment var/global/list/status_overlays_lighting var/global/list/status_overlays_environ var/keep_preset_name = FALSE /obj/machinery/power/apc/New(turf/loc, direction, building = 0) if(!armor) armor = list(MELEE = 20, BULLET = 20, LASER = 10, ENERGY = 100, BOMB = 30, RAD = 100, FIRE = 90, ACID = 50) ..() GLOB.apcs += src GLOB.apcs = sortAtom(GLOB.apcs) wires = new(src) if(building) // Offset 24 pixels in direction of dir. This allows the APC to be embedded in a wall, yet still inside an area setDir(direction) // This is only used for pixel offsets, and later terminal placement. APC dir doesn't affect its sprite since it only has one orientation. set_pixel_offsets_from_dir(24, -24, 24, -24) apc_area = get_area(src) apc_area.apc += src opened = APC_OPENED operating = FALSE name = "[apc_area.name] APC" stat |= MAINT constructed = TRUE update_icon() addtimer(CALLBACK(src, PROC_REF(update)), 5) /obj/machinery/power/apc/Destroy() SStgui.close_uis(wires) GLOB.apcs -= src machine_powernet.set_power_channel(PW_CHANNEL_LIGHTING, FALSE) machine_powernet.set_power_channel(PW_CHANNEL_EQUIPMENT, FALSE) machine_powernet.set_power_channel(PW_CHANNEL_ENVIRONMENT, FALSE) machine_powernet.power_change() if(occupier) malfvacate(1) QDEL_NULL(wires) QDEL_NULL(cell) if(terminal) disconnect_terminal() machine_powernet.powernet_apc = null apc_area.apc -= src return ..() /obj/machinery/power/apc/Initialize(mapload) . = ..() if(!mapload) return electronics_state = APC_ELECTRONICS_SECURED // is starting with a power cell installed, create it and set its charge level if(cell_type) cell = new /obj/item/stock_parts/cell/upgraded(src) cell.maxcharge = cell_type // cell_type is maximum charge (old default was 1000 or 2500 (values one and two respectively) cell.charge = start_charge * cell.maxcharge / 100 // (convert percentage to actual value) var/area/A = get_area(src) //if area isn't specified use current if(keep_preset_name) if(isarea(A)) apc_area = A // no-op, keep the name else if(isarea(A) && !areastring) apc_area = A name = "\improper [apc_area.name] APC" else name = "\improper [get_area_name(apc_area, TRUE)] APC" apc_area.apc |= src update_icon() make_terminal() set_light(1, LIGHTING_MINIMUM_POWER) addtimer(CALLBACK(src, PROC_REF(update)), 5) /obj/machinery/power/apc/examine(mob/user) . = ..() if(in_range(user, src)) if(stat & BROKEN) return if(opened) if(has_electronics() && terminal) . += "The cover is [opened == APC_OPENED ? "removed" : "open"] and the power cell is [ cell ? "installed" : "missing"]." else if(!has_electronics() && terminal) . += "There are some wires but no electronics." else if(has_electronics() && !terminal) . += "Electronics installed but not wired." else /* if(!has_electronics() && !terminal) */ . += "There are no electronics nor connected wires." else if(stat & MAINT) . += "The cover is closed. Something wrong with it: it doesn't work." else if(malfhack) . += "The cover is broken. It may be hard to force it open." else . += "The cover is closed." . += "This powerful, yet small, device powers the entire room in which it is located. From lighting, airlocks, and equipment, an APC is able to power it all! You can unlock an APC by using an ID with the required access on it, or by a local synthetic." . += "The enviroment setting controls the gas and airlock power." . += "The lighting setting controls the power of all the lighting of the room." . += "The equipment setting controls the power of all machines and computers in the room." . += "You can crowbar an unlocked APC to open the cover of the APC." if(isAntag(user)) . += "An APC can be emagged to unlock it, this will keep it in it's refresh state, making very obvious something is wrong." //attack with an item - open/close cover, insert cell, or (un)lock interface /obj/machinery/power/apc/attackby(obj/item/W, mob/living/user, params) if(issilicon(user) && get_dist(src, user) > 1) return attack_hand(user) else if(istype(W, /obj/item/stock_parts/cell) && opened) // trying to put a cell inside if(cell) to_chat(user, "There is a power cell already installed!") return else if(stat & MAINT) to_chat(user, "There is no connector for your power cell!") return if(!user.drop_item()) return W.forceMove(src) cell = W for(var/mob/living/simple_animal/demon/pulse_demon/demon in cell) demon.forceMove(src) demon.current_power = src if(!being_hijacked) // first come first serve demon.try_hijack_apc(src) if(being_hijacked) cell.rigged = FALSE // don't blow the demon up user.visible_message(\ "[user.name] has inserted the power cell to [name]!",\ "You insert the power cell.") chargecount = 0 update_icon() else if(W.GetID()) // trying to unlock the interface with an ID card togglelock(user) else if(istype(W, /obj/item/stack/cable_coil) && opened) var/turf/host_turf = get_turf(src) if(!host_turf) throw EXCEPTION("attackby on APC when it's not on a turf") return if(host_turf.intact) to_chat(user, "You must remove the floor plating in front of the APC first!") return else if(terminal) // it already have terminal to_chat(user, "This APC is already wired!") return else if(!has_electronics()) to_chat(user, "There is nothing to wire!") return var/obj/item/stack/cable_coil/C = W if(C.get_amount() < 10) to_chat(user, "You need ten lengths of cable for APC!") return user.visible_message("[user.name] adds cables to the APC frame.", \ "You start adding cables to the APC frame...") playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) if(do_after(user, 20, target = src)) if(C.get_amount() < 10 || !C) return if(C.get_amount() >= 10 && !terminal && opened && has_electronics()) var/turf/T = get_turf(src) var/obj/structure/cable/N = T.get_cable_node() if(prob(50) && electrocute_mob(usr, N, N, 1, TRUE)) do_sparks(5, TRUE, src) return C.use(10) to_chat(user, "You add cables to the APC frame.") make_terminal() terminal.connect_to_network() else if(istype(W, /obj/item/apc_electronics) && opened) if(has_electronics()) // there are already electronicks inside to_chat(user, "You cannot put the board inside, there already is one!") return else if(stat & BROKEN) to_chat(user, "You cannot put the board inside, the frame is damaged!") return user.visible_message("[user.name] inserts [W] into [src].", \ "You start to insert [W] into the frame...") playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE) if(do_after(user, 10, target = src)) if(!has_electronics()) electronics_state = APC_ELECTRONICS_INSTALLED locked = FALSE to_chat(user, "You place [W] inside the frame.") qdel(W) else if(istype(W, /obj/item/mounted/frame/apc_frame) && opened) if(!(stat & BROKEN || opened == APC_COVER_OFF || obj_integrity < max_integrity)) // There is nothing to repair to_chat(user, "You found no reason for repairing this APC.") return if(!(stat & BROKEN) && opened == APC_COVER_OFF) // Cover is the only thing broken, we do not need to remove elctronicks to replace cover user.visible_message("[user.name] replaces missing APC's cover.",\ "You begin to replace APC's cover...") if(do_after(user, 20, target = src)) // replacing cover is quicker than replacing whole frame to_chat(user, "You replace missing APC's cover.") qdel(W) opened = APC_OPENED update_icon() return if(has_electronics()) to_chat(user, "You cannot repair this APC until you remove the electronics still inside!") return user.visible_message("[user.name] replaces the damaged APC frame with a new one.",\ "You begin to replace the damaged APC frame...") if(do_after(user, 50, target = src)) to_chat(user, "You replace the damaged APC frame with a new one.") qdel(W) stat &= ~BROKEN obj_integrity = max_integrity if(opened == APC_COVER_OFF) opened = APC_OPENED update_icon() return else return ..() /obj/machinery/power/apc/AltClick(mob/user) if(Adjacent(user)) togglelock(user) /obj/machinery/power/apc/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) if(stat & BROKEN) return damage_amount . = ..() /obj/machinery/power/apc/obj_break(damage_flag) if(!(flags & NODECONSTRUCT)) set_broken() // attack with hand - remove cell (if cover open) or interact with the APC /obj/machinery/power/apc/attack_hand(mob/user) if(!user) return add_fingerprint(user) if(usr == user && opened && !issilicon(user)) if(cell) user.visible_message("[user.name] removes [cell] from [src]!", "You remove [cell].") user.put_in_hands(cell) cell.add_fingerprint(user) cell.update_icon(UPDATE_OVERLAYS) cell = null charging = APC_NOT_CHARGING update_icon() return if(stat & (BROKEN|MAINT)) return interact(user) /obj/machinery/power/apc/attack_ghost(mob/user) if(panel_open) wires.Interact(user) return ui_interact(user) /obj/machinery/power/apc/interact(mob/user) if(!user) return if(panel_open) wires.Interact(user) return ui_interact(user) /obj/machinery/power/apc/ui_state(mob/user) return GLOB.default_state /obj/machinery/power/apc/ui_interact(mob/user, datum/tgui/ui = null) ui = SStgui.try_update_ui(user, src, ui) if(!ui) ui = new(user, src, "APC", name) ui.open() /obj/machinery/power/apc/ui_data(mob/user) var/list/data = list() data["locked"] = is_locked(user) data["normallyLocked"] = locked data["isOperating"] = operating data["externalPower"] = main_status data["powerCellStatus"] = cell ? cell.percent() : null data["chargeMode"] = chargemode data["chargingStatus"] = charging data["totalLoad"] = round(last_used_equipment + last_used_lighting + last_used_environment) data["coverLocked"] = coverlocked data["siliconUser"] = issilicon(user) data["siliconLock"] = locked data["malfStatus"] = get_malf_status(user) data["nightshiftLights"] = nightshift_lights data["emergencyLights"] = !emergency_lights var/list/power_channels = list() power_channels += list(list( "title" = "Equipment", "powerLoad" = round(last_used_equipment), "status" = equipment_channel, "topicParams" = list( "auto" = list("eqp" = 3), "on" = list("eqp" = 2), "off" = list("eqp" = 1) ) )) power_channels += list(list( "title" = "Lighting", "powerLoad" = round(last_used_lighting), "status" = lighting_channel, "topicParams" = list( "auto" = list("lgt" = 3), "on" = list("lgt" = 2), "off" = list("lgt" = 1) ) )) power_channels += list(list( "title" = "Environment", "powerLoad" = round(last_used_environment), "status" = environment_channel, "topicParams" = list( "auto" = list("env" = 3), "on" = list("env" = 2), "off" = list("env" = 1) ) )) data["powerChannels"] = power_channels return data /obj/machinery/power/apc/proc/update() if(operating && !shorted) machine_powernet.set_power_channel(PW_CHANNEL_LIGHTING, (lighting_channel > CHANNEL_SETTING_AUTO_OFF)) machine_powernet.set_power_channel(PW_CHANNEL_EQUIPMENT, (equipment_channel > CHANNEL_SETTING_AUTO_OFF)) machine_powernet.set_power_channel(PW_CHANNEL_ENVIRONMENT, (environment_channel > CHANNEL_SETTING_AUTO_OFF)) if(lighting_channel) emergency_power = TRUE if(emergency_power_timer) deltimer(emergency_power_timer) emergency_power_timer = null else emergency_power_timer = addtimer(CALLBACK(src, PROC_REF(turn_emergency_power_off)), 2 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE) else machine_powernet.set_power_channel(PW_CHANNEL_LIGHTING, FALSE) machine_powernet.set_power_channel(PW_CHANNEL_EQUIPMENT, FALSE) machine_powernet.set_power_channel(PW_CHANNEL_ENVIRONMENT, FALSE) emergency_power_timer = addtimer(CALLBACK(src, PROC_REF(turn_emergency_power_off)), 2 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE) machine_powernet.power_change() /obj/machinery/power/apc/proc/can_use(mob/user, loud = 0) //used by attack_hand() and Topic() if(user.can_admin_interact()) return TRUE autoflag = 5 //why the hell is this being set to 5, fucking malf code -sirryan if(issilicon(user)) var/mob/living/silicon/ai/AI = user var/mob/living/silicon/robot/robot = user if(aidisabled || (malfhack && istype(malfai) && ((istype(AI) && (malfai != AI && malfai != AI.parent))) || (istype(robot) && malfai && !(robot in malfai.connected_robots)))) if(!loud) to_chat(user, "\The [src] has AI control disabled!") return FALSE else if((!in_range(src, user) || !isturf(loc))) return FALSE var/mob/living/carbon/human/H = user if(istype(H)) if(H.getBrainLoss() >= 60) for(var/mob/M in viewers(src, null)) to_chat(M, "[H] stares cluelessly at [src].") return FALSE else if(prob(H.getBrainLoss())) to_chat(user, "You momentarily forget how to use [src].") return FALSE return TRUE /obj/machinery/power/apc/proc/is_authenticated(mob/user as mob) if(user.can_admin_interact()) return TRUE if(isAI(user) || isrobot(user) || user.has_unlimited_silicon_privilege) return TRUE else return !locked /obj/machinery/power/apc/proc/is_locked(mob/user as mob) if(user.can_admin_interact()) return FALSE if(isAI(user) || isrobot(user) || user.has_unlimited_silicon_privilege) return FALSE else return locked /obj/machinery/power/apc/ui_act(action, params, datum/tgui/ui) var/mob/user = ui.user if(..() || !can_use(user, TRUE) || (is_locked(user) && (action != "toggle_nightshift"))) return . = TRUE switch(action) if("lock") if(user.has_unlimited_silicon_privilege) if(emagged || stat & BROKEN) to_chat(user, "The APC does not respond to the command!") return FALSE else locked = !locked update_icon() else to_chat(user, "Access Denied!") return FALSE if("cover") coverlocked = !coverlocked if("breaker") toggle_breaker(user) if("toggle_nightshift") if(last_nightshift_switch > world.time + 100) // don't spam... to_chat(user, "[src]'s night lighting circuit breaker is still cycling!") return FALSE last_nightshift_switch = world.time set_nightshift(!nightshift_lights) if("charge") chargemode = !chargemode if("channel") if(params["eqp"]) equipment_channel = setsubsystem(text2num(params["eqp"])) update_icon() update() else if(params["lgt"]) lighting_channel = setsubsystem(text2num(params["lgt"])) update_icon() update() else if(params["env"]) environment_channel = setsubsystem(text2num(params["env"])) update_icon() update() if("overload") if(user.has_unlimited_silicon_privilege) INVOKE_ASYNC(src, PROC_REF(overload_lighting)) if("hack") if(get_malf_status(user)) malfhack(user) if("occupy") if(get_malf_status(user)) malfoccupy(usr) if("deoccupy") if(get_malf_status(user)) malfvacate() if("emergency_lighting") emergency_lights = !emergency_lights for(var/obj/machinery/light/L in apc_area) INVOKE_ASYNC(L, TYPE_PROC_REF(/obj/machinery/light, update), FALSE) CHECK_TICK /obj/machinery/power/apc/proc/update_last_used() last_used_lighting = machine_powernet.get_channel_usage(PW_CHANNEL_LIGHTING) last_used_equipment = machine_powernet.get_channel_usage(PW_CHANNEL_EQUIPMENT) last_used_environment = machine_powernet.get_channel_usage(PW_CHANNEL_ENVIRONMENT) last_used_total = machine_powernet.get_total_usage() machine_powernet.clear_usage() /// What the APC will do every process interval, updates power settings and power changes depending on powernet state /obj/machinery/power/apc/process() if(stat & (BROKEN|MAINT)) // if the APC is broken, don't even bother return if(!apc_area.requires_power) // if the area doesn't use power, don't even bother return //We store the initial power channel states so we can check if they've changed at the end of the proc so we can update overlays efficiently var/last_lighting_state = lighting_channel var/last_equipment_state = equipment_channel var/last_environment_state = environment_channel var/last_charging_state = charging update_last_used() // get local powernet usage and clear it for next cycle var/excess = get_power_balance() if(!get_available_power()) main_status = APC_EXTERNAL_POWER_NOTCONNECTED else if(excess < 0) main_status = APC_EXTERNAL_POWER_NOENERGY // there's more demand than supply on powernet, there's not enough power else main_status = APC_EXTERNAL_POWER_GOOD // there's some excess power on the powernet! if(cell && !shorted) // draw power from cell as before to power the area var/cell_used = min(cell.charge, GLOB.CELLRATE * last_used_total) // clamp deduction to a max, amount left in cell cell.use(cell_used) if(excess > last_used_total) // if power excess recharge the cell by the same amount just used cell.give(cell_used) consume_direct_power(cell_used / GLOB.CELLRATE) // add the load used to recharge the cell else // no excess, and not enough per-apc if((cell.charge / GLOB.CELLRATE + excess) >= last_used_total) // can we draw enough from cell+grid to cover last usage? cell.charge = min(cell.maxcharge, cell.charge + GLOB.CELLRATE * excess) //recharge with what we can consume_direct_power(excess) // so draw what we can from the grid charging = APC_NOT_CHARGING else // not enough power available to run the last tick! charging = APC_NOT_CHARGING 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. equipment_channel = autoset(equipment_channel, CHANNEL_SETTING_OFF) lighting_channel = autoset(lighting_channel, CHANNEL_SETTING_OFF) environment_channel = autoset(environment_channel, CHANNEL_SETTING_OFF) autoflag = APC_AUTOFLAG_ALL_OFF // Set channels depending on how much charge we have left // Allow the APC to operate as normal if the cell can charge if(charging != APC_NOT_CHARGING && longtermpower < 10) longtermpower += 1 else if(longtermpower > -10) longtermpower -= 2 handle_autoflag() // now trickle-charge the cell if(chargemode && charging == APC_IS_CHARGING && operating) if(excess > 0) // check to make sure we have enough to charge // Max charge is capped to % per second constant var/ch = min(excess*GLOB.CELLRATE, cell.maxcharge*GLOB.CHARGELEVEL) consume_direct_power(ch / GLOB.CELLRATE) // Removes the power we're taking from the grid cell.give(ch) // actually recharge the cell else charging = APC_NOT_CHARGING // stop charging chargecount = 0 // show cell as fully charged if so if(cell.charge >= cell.maxcharge) cell.charge = cell.maxcharge charging = APC_FULLY_CHARGED if(chargemode) if(charging == APC_NOT_CHARGING) if(excess > cell.maxcharge * GLOB.CHARGELEVEL) chargecount++ else chargecount = 0 if(chargecount == 10) chargecount = 0 charging = APC_IS_CHARGING else // chargemode off charging = APC_NOT_CHARGING chargecount = 0 if(!shock_proof) handle_shock_chance(excess) else // no cell, switch everything off charging = APC_NOT_CHARGING chargecount = 0 equipment_channel = autoset(equipment_channel, CHANNEL_SETTING_OFF) lighting_channel = autoset(lighting_channel, CHANNEL_SETTING_OFF) environment_channel = autoset(environment_channel, CHANNEL_SETTING_OFF) if(report_power_alarm) apc_area.poweralert(FALSE, src) autoflag = APC_AUTOFLAG_ALL_OFF // update icon & area power if anything changed if(last_lighting_state != lighting_channel || last_equipment_state != equipment_channel || last_environment_state != environment_channel) queue_icon_update() update() else if(last_charging_state != charging) queue_icon_update() machine_powernet.handle_flicker() // try and flicker machines/lights in the area randomly /obj/machinery/power/apc/proc/handle_autoflag() // Put most likely at the top so we don't check it last, effeciency 101 <--- old coders can't spell if(cell.charge >= 1250 || longtermpower > 0) if(autoflag != APC_AUTOFLAG_ALL_ON) equipment_channel = autoset(equipment_channel, CHANNEL_SETTING_AUTO_OFF) lighting_channel = autoset(lighting_channel, CHANNEL_SETTING_AUTO_OFF) environment_channel = autoset(environment_channel, CHANNEL_SETTING_AUTO_OFF) autoflag = APC_AUTOFLAG_ALL_ON if(report_power_alarm) apc_area.poweralert(TRUE, src) return if(cell.charge < 1250 && cell.charge > 750 && longtermpower < 0) // <30%, turn off equipment if(autoflag != APC_AUTOFLAG_EQUIPMENT_OFF) equipment_channel = autoset(equipment_channel, CHANNEL_SETTING_ON) lighting_channel = autoset(lighting_channel, CHANNEL_SETTING_AUTO_OFF) environment_channel = autoset(environment_channel, CHANNEL_SETTING_AUTO_OFF) if(report_power_alarm) apc_area.poweralert(FALSE, src) autoflag = APC_AUTOFLAG_EQUIPMENT_OFF else if(cell.charge < 750 && cell.charge > 10) // <15%, turn off lighting & equipment if(autoflag > APC_AUTOFLAG_ENVIRO_ONLY) equipment_channel = autoset(equipment_channel, CHANNEL_SETTING_ON) lighting_channel = autoset(lighting_channel, CHANNEL_SETTING_ON) environment_channel = autoset(environment_channel, CHANNEL_SETTING_AUTO_OFF) if(report_power_alarm) apc_area.poweralert(FALSE, src) autoflag = APC_AUTOFLAG_ENVIRO_ONLY else if(cell.charge <= 0) // zero charge, turn all off if(autoflag != APC_AUTOFLAG_ALL_OFF) equipment_channel = autoset(equipment_channel, CHANNEL_SETTING_OFF) lighting_channel = autoset(lighting_channel, CHANNEL_SETTING_OFF) environment_channel = autoset(environment_channel, CHANNEL_SETTING_OFF) if(report_power_alarm) apc_area.poweralert(FALSE, src) autoflag = APC_AUTOFLAG_ALL_OFF /// Handles APC arc'ing every APC process interval /obj/machinery/power/apc/proc/handle_shock_chance(excess = 0) if(excess < 2500000) return var/shock_chance = 5 // 5% if(excess >= 7500000) shock_chance = 15 else if(excess >= 5000000) shock_chance = 10 if(prob(shock_chance)) var/list/shock_mobs = list() for(var/C in view(get_turf(src), 5)) //We only want to shock a single random mob in range, not every one. if(isliving(C)) shock_mobs += C if(length(shock_mobs)) var/mob/living/L = pick(shock_mobs) L.electrocute_act(rand(5, 25), "electrical arc") playsound(get_turf(L), 'sound/effects/eleczap.ogg', 75, TRUE) Beam(L, icon_state = "lightning[rand(1, 12)]", icon = 'icons/effects/effects.dmi', time = 5) /// ************* /// APC Power Procs /// ************* /obj/machinery/power/apc/get_cell() return cell /// Override because the APC does not directly connect to the network; it goes through a terminal. /obj/machinery/power/apc/connect_to_network() terminal?.connect_to_network() //The terminal is what the power computer looks for /obj/machinery/power/apc/get_surplus() if(terminal) return terminal.get_surplus() else return 0 /obj/machinery/power/apc/get_power_balance() if(terminal) return terminal.get_power_balance() else return 0 /obj/machinery/power/apc/consume_direct_power(amount) if(terminal?.powernet) terminal.consume_direct_power(amount) /obj/machinery/power/apc/get_available_power() return terminal ? terminal.get_available_power() : 0 /obj/machinery/power/apc/proc/power_destroy() // Caused only by explosions and teslas, not for deconstruction if(obj_integrity > integrity_failure || opened != APC_COVER_OFF) return var/drop_loc = drop_location() new /obj/item/stack/sheet/metal(drop_loc, 3) // Metal from the frame new /obj/item/stack/cable_coil(drop_loc, 10) // wiring from the terminal and the APC, some lost due to explosion QDEL_NULL(terminal) // We don't want floating terminals qdel(src) /obj/machinery/power/apc/proc/make_terminal() // create a terminal object at the same position as original turf loc // wires will attach to this terminal = new/obj/machinery/power/terminal(get_turf(src)) terminal.setDir(dir) terminal.master = src /obj/machinery/power/apc/disconnect_terminal() if(terminal) terminal.master = null terminal = null /// ************* /// APC Settings /// ************* /obj/machinery/power/apc/proc/togglelock(mob/living/user) if(emagged) to_chat(user, "The interface is broken!") else if(opened) to_chat(user, "You must close the cover to swipe an ID card!") else if(panel_open) to_chat(user, "You must close the panel!") else if(stat & (BROKEN|MAINT)) to_chat(user, "Nothing happens!") else if(allowed(user) && !wires.is_cut(WIRE_IDSCAN) && !malfhack) locked = !locked to_chat(user, "You [locked ? "lock" : "unlock"] the APC interface.") update_icon() else to_chat(user, "Access denied.") /obj/machinery/power/apc/proc/toggle_breaker() operating = !operating update() update_icon() /** * # autoset * * this proc is rather confusing at first glance, it handles apc power channel settings when trying to * auto set values to it, good luck understanding how this works but I sure as hell don't */ /obj/machinery/power/apc/proc/autoset(current_setting, new_setting) switch(new_setting) if(CHANNEL_SETTING_OFF) if(current_setting == CHANNEL_SETTING_ON) // if on, return off return CHANNEL_SETTING_OFF if(current_setting == CHANNEL_SETTING_AUTO_ON) // if auto-on, return auto-off return CHANNEL_SETTING_AUTO_OFF if(CHANNEL_SETTING_AUTO_OFF) if(current_setting == CHANNEL_SETTING_AUTO_OFF) // if auto-off, return auto-on return CHANNEL_SETTING_AUTO_ON if(CHANNEL_SETTING_ON) if(current_setting == CHANNEL_SETTING_AUTO_ON) // if auto-on, return auto-off return CHANNEL_SETTING_AUTO_OFF return current_setting //if setting is not changed, just keep current setting /obj/machinery/power/apc/proc/setsubsystem(new_setting) if(cell?.charge > 1) //if there's a charge if(new_setting == CHANNEL_SETTING_AUTO_OFF) //and apc now set to off, return off return CHANNEL_SETTING_OFF return new_setting //else return the new setting if(new_setting == CHANNEL_SETTING_AUTO_ON) //if no charge and set to auto, set to auto off return CHANNEL_SETTING_AUTO_OFF return CHANNEL_SETTING_OFF //if set to on and no charge, set to off /// ************* --- /// APC Helper Procs --- Antag Abilities/Fun Stuff /// ************* --- /// set APC stat as broken and set it to be non operational, kick out an AI if there's a malf one in it /obj/machinery/power/apc/proc/set_broken() stat |= BROKEN operating = FALSE if(occupier) malfvacate(1) update_icon() update() /// overload all the lights in this APC area /obj/machinery/power/apc/proc/overload_lighting(chance = 100) if(!operating || shorted) return if(cell && cell.charge >= 20) cell.use(20) for(var/obj/machinery/light/L in apc_area) if(prob(chance)) L.break_light_tube(0, 1) stoplag() /// turns off emergency power and sets each light to update /obj/machinery/power/apc/proc/turn_emergency_power_off() emergency_power = FALSE for(var/obj/machinery/light/L in apc_area) INVOKE_ASYNC(L, TYPE_PROC_REF(/obj/machinery/light, update), FALSE) /// sets nightshift mode on for the APC /obj/machinery/power/apc/proc/set_nightshift(on) set waitfor = FALSE nightshift_lights = on for(var/obj/machinery/light/L in apc_area) if(L.nightshift_allowed) L.nightshift_enabled = nightshift_lights L.update(FALSE, play_sound = FALSE) CHECK_TICK /obj/machinery/power/apc/proc/relock_callback() locked = TRUE updateDialog() /// Updates APC to not be shorted if both main power wires are mended /obj/machinery/power/apc/proc/check_main_power_callback() if(!wires.is_cut(WIRE_MAIN_POWER1) && !wires.is_cut(WIRE_MAIN_POWER2)) shorted = FALSE updateDialog() /// Updates APC to AI accesible if AI Wire is mended /obj/machinery/power/apc/proc/check_ai_control_callback() if(!wires.is_cut(WIRE_AI_CONTROL)) aidisabled = FALSE updateDialog() /// Repairs all wires, enables all breakers, and unshorts the APC /obj/machinery/power/apc/proc/repair_apc() if(wires) wires.repair() if(!operating) toggle_breaker() if(shorted) shorted = FALSE /// If the APC has a cell, recharge it /obj/machinery/power/apc/proc/recharge_apc() var/obj/item/stock_parts/cell/C = get_cell() if(C) C.charge = C.maxcharge /obj/machinery/power/apc/proc/emp_callback() equipment_channel = 3 environment_channel = 3 update_icon() update() /// ************* /// External Acts on the APCS /// ************* /obj/machinery/power/apc/emp_act(severity) if(cell) cell.emp_act(severity) if(occupier) occupier.emp_act(severity) lighting_channel = 0 equipment_channel = 0 environment_channel = 0 update_icon() update() addtimer(CALLBACK(src, PROC_REF(emp_callback)), 60 SECONDS) ..() /obj/machinery/power/apc/blob_act(obj/structure/blob/B) set_broken() /obj/machinery/power/apc/ex_act(severity) ..() if(severity < EXPLODE_LIGHT) power_destroy() /obj/machinery/power/apc/zap_act(power, zap_flags) . = ..() power_destroy() /obj/machinery/power/apc/proc/ion_act() //intended to be exactly the same as an AI malf attack if(!malfhack && is_station_level(z)) if(prob(3)) locked = TRUE if(cell.charge > 0) cell.charge = 0 cell.corrupt() malfhack = TRUE update_icon() var/datum/effect_system/smoke_spread/smoke = new smoke.set_up(3, FALSE, loc) smoke.attach(src) smoke.start() do_sparks(3, 1, src) for(var/mob/M in viewers(src)) M.show_message("[src] suddenly lets out a blast of smoke and some sparks!", 3, "You hear sizzling electronics.", 2) /obj/machinery/power/apc/emag_act(user as mob) if(!(emagged || malfhack)) // trying to unlock with an emag card if(opened) to_chat(user, "You must close the cover to swipe an ID card.") else if(panel_open) to_chat(user, "You must close the panel first.") else if(stat & (BROKEN|MAINT)) to_chat(user, "Nothing happens.") else flick("apc-spark", src) emagged = TRUE locked = FALSE to_chat(user, "You emag the APC interface.") update_icon() return TRUE /obj/machinery/power/apc/proc/apc_short() // if it has internal wires, cut the power wires if(wires) if(!wires.is_cut(WIRE_MAIN_POWER1)) wires.cut(WIRE_MAIN_POWER1) if(!wires.is_cut(WIRE_MAIN_POWER2)) wires.cut(WIRE_MAIN_POWER2) // if it was operating, toggle off the breaker if(operating) toggle_breaker() // no matter what, ensure the area knows something happened to the power apc_area.powernet.power_change() /// ************* /// APC subtypes /// ************* /obj/machinery/power/apc/worn_out name = "\improper Worn out APC" keep_preset_name = TRUE locked = FALSE environment_channel = 0 equipment_channel = 0 lighting_channel = 0 operating = FALSE emergency_power = FALSE /// APC type used for when you don't want the power alarm on the APC to show up on AI reports /obj/machinery/power/apc/off_station report_power_alarm = FALSE /// APCs used for ruins, this version also starts devoid of a charge /obj/machinery/power/apc/off_station/empty_charge start_charge = 0 /obj/machinery/power/apc/syndicate //general syndicate access name = "Main branch, do not use" req_access = list(ACCESS_SYNDICATE) report_power_alarm = FALSE /obj/machinery/power/apc/syndicate/north name = "north bump" pixel_y = 24 /obj/machinery/power/apc/syndicate/south name = "south bump" pixel_y = -24 /obj/machinery/power/apc/syndicate/east name = "east bump" pixel_x = 24 /obj/machinery/power/apc/syndicate/west name = "west bump" pixel_x = -24 /obj/machinery/power/apc/syndicate/off name = "APC off" environment_channel = 0 equipment_channel = 0 lighting_channel = 0 operating = FALSE /obj/machinery/power/apc/syndicate/off/Initialize(mapload) . = ..() cell.charge = 0 /obj/item/apc_electronics name = "APC electronics" desc = "Heavy-duty switching circuits for power control." icon = 'icons/obj/module.dmi' icon_state = "power_mod" w_class = WEIGHT_CLASS_SMALL origin_tech = "engineering=2;programming=1" item_state = "electronic" flags = CONDUCT usesound = 'sound/items/deconstruct.ogg' toolspeed = 1 #undef APC_EXTERNAL_POWER_NOTCONNECTED #undef APC_EXTERNAL_POWER_NOENERGY #undef APC_EXTERNAL_POWER_GOOD