Files
Paradise/code/modules/power/apc/apc.dm
2025-10-17 17:19:08 +00:00

1183 lines
42 KiB
Plaintext

// 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"
integrity_failure = 50
resistance_flags = FIRE_PROOF
req_access = list(ACCESS_ENGINE_EQUIP)
siemens_strength = 1
damage_deflection = 10
move_resist = INFINITY
/*** APC construction vars***/
// These exist here and not as defines in `apc_defines.dm` so they can be modified for `test_apc_construction.dm`.
var/apc_frame_welding_time = 2 SECONDS
var/apc_terminal_wiring_time = 2 SECONDS
var/frame_type = /obj/item/mounted/frame/apc_frame
var/sheet_type = /obj/item/stack/sheet/metal
// 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 (Watts)
var/last_used_lighting = 0
/// amount of power used in the last cycle for equipment channel (Watts)
var/last_used_equipment = 0
/// amount of power used in the last cycle for environment channel (Watts)
var/last_used_environment = 0
/// amount of power used in the last cycle in total (Watts)
var/last_used_total = 0
/*** APC Cell Vars ***/
/// the cell type stored in this APC. APC uses cell energy capacity (kilojoules)
var/obj/item/stock_parts/cell/cell
/// the percentage charge the internal battery will start with
var/start_charge = 90
/// Base cell has a 2500 kJ 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 edited APCs
var/cell_type = 2500
/*** APC Status Vars ***/
/// The wire panel associated with this APC
var/datum/wires/apc/wires = null
/// Can the APC receive/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 = APC_CHANNEL_SETTING_AUTO_ON
/// The current setting for the equipment channel
var/equipment_channel = APC_CHANNEL_SETTING_AUTO_ON
/// The current setting for the environment channel
var/environment_channel = APC_CHANNEL_SETTING_AUTO_ON
/// Is the APC cover locked? i.e cannot be opened?
var/cover_locked = 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 receives 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
/// Are we immune to EMPS?
var/emp_proof = 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
/// Are we hacked by a ruins malf AI? If so, it will act like a malf AI hacked APC, but silicons and malf AI's will not be able to interface with it.
var/hacked_by_ruin_AI = FALSE
/*** 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/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, direction, building = FALSE)
. = ..()
if(!armor)
armor = list(MELEE = 20, BULLET = 20, LASER = 10, ENERGY = 100, BOMB = 30, RAD = 100, FIRE = 90, ACID = 50)
GLOB.apcs += src
wires = new(src)
var/area/A = get_area(src)
if(A.powernet && !A.powernet.powernet_apc)
A.powernet.powernet_apc = src
if(!mapload)
GLOB.apcs = sortAtom(GLOB.apcs)
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"
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)
opened = APC_OPENED
operating = FALSE
stat |= MAINT
constructed = TRUE
else
electronics_state = APC_ELECTRONICS_INSTALLED
// is starting with a power cell installed, create it and set its charge level
if(cell_type)
cell = new /obj/item/stock_parts/cell(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)
make_terminal()
set_light(1, LIGHTING_MINIMUM_POWER)
//if area isn't specified use current
apc_area.apc |= src
update_icon()
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."
. += shock_proof ? "There is additional plastic insulation" : "There is a place to put in some plastic insulation"
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."
. += "<span class='notice'>This powerful, yet small, device powers the entire room in which it is located. Of 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 (shortcut: <b>Alt-click</b>), or ask a local synthetic.</span>"
. += "<span class='notice'>The enviroment setting controls the gas and airlock power.</span>"
. += "<span class='notice'>The lighting setting controls the power of all the lighting of the room.</span>"
. += "<span class='notice'>The equipment setting controls the power of all machines and computers in the room.</span>"
. += "<span class='notice'>You can crowbar an unlocked APC to open the cover of the APC.</span>"
if(isAntag(user))
. += "<span class='warning'>An APC can be emagged to unlock it, this will keep it in it's refresh state, making very obvious something is wrong.</span>"
/obj/machinery/power/apc/item_interaction(mob/living/user, obj/item/used, list/modifiers)
if(issilicon(user) && get_dist(src, user) > 1)
attack_hand(user)
return ITEM_INTERACT_COMPLETE
// Adding power cell.
if(istype(used, /obj/item/stock_parts/cell) && opened)
if(cell)
to_chat(user, "<span class='warning'>[src] already has a power cell!</span>")
return ITEM_INTERACT_COMPLETE
if(stat & MAINT)
to_chat(user, "<span class='warning'>[src] has no electronics inside!</span>")
return ITEM_INTERACT_COMPLETE
if(!user.drop_item())
return ITEM_INTERACT_COMPLETE
used.forceMove(src)
cell = used
user.visible_message(
"<span class='notice'>[user] inserts [used] into [src].</span>",
"<span class='notice'>You insert [used] into [src].</span>"
)
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 // Do not explode the demon.
chargecount = 0
update_icon()
return ITEM_INTERACT_COMPLETE
// Swiping ID card.
if(used.GetID())
togglelock(user)
return ITEM_INTERACT_COMPLETE
// Adding cables.
if(istype(used, /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 ITEM_INTERACT_COMPLETE
if(host_turf.intact)
to_chat(user, "<span class='warning'>You must expose the floor plating in front of [src] first!</span>")
return ITEM_INTERACT_COMPLETE
if(terminal)
to_chat(user, "<span class='warning'>[src] is already wired!</span>")
return ITEM_INTERACT_COMPLETE
if(!has_electronics())
to_chat(user, "<span class='warning'>[src] has no electronics inside to wire!</span>")
return ITEM_INTERACT_COMPLETE
var/obj/item/stack/cable_coil/C = used
if(C.get_amount() < 10)
to_chat(user, "<span class='warning'>You need ten lengths of cable to wire [src]!</span>")
return ITEM_INTERACT_COMPLETE
user.visible_message(
"<span class='notice'>[user] starts adding cables to [src]...</span>",
"<span class='notice'>You start adding cables to [src]...</span>"
)
playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
if(do_after(user, apc_terminal_wiring_time, target = src))
if(C.get_amount() < 10 || !C)
return ITEM_INTERACT_COMPLETE
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 ITEM_INTERACT_COMPLETE
C.use(10)
to_chat(user, "<span class='notice'>You add cables to [src].</span>")
make_terminal()
terminal.connect_to_network()
return ITEM_INTERACT_COMPLETE
// Adding APC electronics.
if(istype(used, /obj/item/apc_electronics) && opened)
if(has_electronics())
if(user.mind && HAS_TRAIT(user.mind, TRAIT_ELECTRICAL_SPECIALIST))
if(stat & BROKEN)
to_chat(user, "<span class='warning'>[src] is damaged! You must repair the frame before you can install [used]!</span>")
return ITEM_INTERACT_COMPLETE
if(malfhack)
malfai = null
malfhack = FALSE
user.visible_message(\
"<span class='notice'>[name] has discarded the strangely programmed APC electronics from [src]!</span>",
"<span class='notice'>You discarded the strangely programmed board.</span>",
"<span class='warning'>You hear metallic levering.</span>"
)
else
user.visible_message(
"<span class='notice'>[user] exchanges out broken the APC electronics inside [src]!</span>",
"<span class='notice'>You carefully remove the charred electronics, replacing it with a functional board.</span>",
"<span class='warning'>You hear metallic levering and a crack, followed by a gentle click.</span>")
playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
qdel(used)
electronics_state = APC_ELECTRONICS_INSTALLED
locked = FALSE
stat &= ~MAINT
update_icon()
return ITEM_INTERACT_COMPLETE
else
to_chat(user, "<span class='warning'>[src] already contains APC electronics!</span>")
return ITEM_INTERACT_COMPLETE
if(stat & BROKEN)
to_chat(user, "<span class='warning'>[src] is damaged! You must repair the frame before you can install [used]!</span>")
return ITEM_INTERACT_COMPLETE
if(!has_electronics())
user.visible_message(
"<span class='notice'>[user] installs [used] into [src].</span>",
"<span class='notice'>You install [used] into [src].</span>"
)
playsound(loc, 'sound/items/deconstruct.ogg', 50, TRUE)
electronics_state = APC_ELECTRONICS_INSTALLED
locked = FALSE
stat &= ~MAINT
update_icon()
qdel(used)
return ITEM_INTERACT_COMPLETE
// APC frame repair. Instant, but you consume 2 metal instead of doing it for free.
if(istype(used, frame_type) && opened)
if(!(stat & BROKEN || opened == APC_COVER_OFF || obj_integrity < max_integrity))
to_chat(user, "<span class='warning'>[src] has no damage to fix!</span>")
return ITEM_INTERACT_COMPLETE
// Only cover is broken, no need to remove any components.
if(!(stat & BROKEN) && opened == APC_COVER_OFF)
user.visible_message(
"<span class='notice'>[user] replaces the missing cover of [src].</span>",
"<span class='notice'>You replace the missing cover of [src].</span>"
)
qdel(used)
opened = APC_OPENED
update_icon()
return ITEM_INTERACT_COMPLETE
if(has_electronics() && user.mind && !HAS_TRAIT(user.mind, TRAIT_ELECTRICAL_SPECIALIST))
to_chat(user, "<span class='warning'>You cannot repair [src] until you remove the electronics!</span>")
return ITEM_INTERACT_COMPLETE
user.visible_message(
"<span class='notice'>[user] replaces the damaged frame of [src].</span>",
"<span class='notice'>You replace the damaged frame of [src].</span>"
)
qdel(used)
stat &= ~BROKEN
obj_integrity = max_integrity
if(opened == APC_COVER_OFF)
opened = APC_OPENED
update_icon()
return ITEM_INTERACT_COMPLETE
if(istype(used, /obj/item/stack/sheet/plastic))
var/obj/item/stack/sheet/plastic/plastic_stack = used
if(!opened)
to_chat(user, "<span class='warning'>You can't add insulation with the cover closed!</span>")
return ITEM_INTERACT_COMPLETE
if(shock_proof)
to_chat(user, "<span class='warning'>[src] already has extra insulation installed!</span>")
return ITEM_INTERACT_COMPLETE
if(plastic_stack.get_amount() < 10)
to_chat(user, "<span class='warning'>You need ten sheets of plastic to add insulation to [src]!</span>")
return ITEM_INTERACT_COMPLETE
plastic_stack.use(10)
to_chat(user, "<span class='notice'>You add extra insulation to [src].</span>")
shock_proof = TRUE
return ITEM_INTERACT_COMPLETE
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(opened && !issilicon(user))
if(cell)
user.visible_message(
"<span class='notice'>[user] removes [cell] from [src].",
"<span class='notice'>You remove [cell].</span>"
)
user.put_in_hands(cell)
cell.add_fingerprint(user)
cell.update_icon(UPDATE_OVERLAYS)
cell = null
charging = APC_NOT_CHARGING
update_icon()
else if(shock_proof)
to_chat(user, "<span class='info'>You remove the insulation from [src]</span>")
var/obj/item/stack/sheet/plastic/plastic_stack = new(loc, 10)
user.put_in_hands(plastic_stack)
shock_proof = FALSE
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"] = cover_locked
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 > APC_CHANNEL_SETTING_AUTO_OFF))
machine_powernet.set_power_channel(PW_CHANNEL_EQUIPMENT, (equipment_channel > APC_CHANNEL_SETTING_AUTO_OFF))
machine_powernet.set_power_channel(PW_CHANNEL_ENVIRONMENT, (environment_channel > APC_CHANNEL_SETTING_AUTO_OFF))
if(lighting_channel > APC_CHANNEL_SETTING_AUTO_OFF)
emergency_power = TRUE
if(emergency_power_timer)
deltimer(emergency_power_timer)
emergency_power_timer = null
else
if(!emergency_power_timer)
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)
if(!emergency_power_timer)
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) || ispulsedemon(user))
if(hacked_by_ruin_AI)
to_chat(user, "<span class='danger'>The APC interface program has been completely corrupted, you are unable to interface with it!</span>")
return FALSE
var/mob/living/L = user
if(!L.can_remote_apc_interface(src))
if(!loud)
to_chat(user, "<span class='danger'>[src] has AI control disabled!</span>")
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, "<span class='danger'>[H] stares cluelessly at [src].</span>")
return FALSE
else if(prob(H.getBrainLoss()))
to_chat(user, "<span class='danger'>You momentarily forget how to use [src].</span>")
return FALSE
return TRUE
/obj/machinery/power/apc/proc/is_authenticated(mob/user as mob)
if(user.can_admin_interact())
return TRUE
if(is_ai(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(is_ai(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, "<span class='warning'>The APC does not respond to the command!</span>")
return FALSE
else
locked = !locked
update_icon()
else
to_chat(user, "<span class='warning'>Access Denied!</span>")
return FALSE
if("cover")
cover_locked = !cover_locked
if("breaker")
toggle_breaker(user)
if("toggle_nightshift")
if(last_nightshift_switch > world.time + 100) // don't spam...
to_chat(user, "<span class='warning'>[src]'s night lighting circuit breaker is still cycling!</span>")
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, APC_CHANNEL_SETTING_OFF)
lighting_channel = autoset(lighting_channel, APC_CHANNEL_SETTING_OFF)
environment_channel = autoset(environment_channel, APC_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, APC_CHANNEL_SETTING_OFF)
lighting_channel = autoset(lighting_channel, APC_CHANNEL_SETTING_OFF)
environment_channel = autoset(environment_channel, APC_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, APC_CHANNEL_SETTING_AUTO_OFF)
lighting_channel = autoset(lighting_channel, APC_CHANNEL_SETTING_AUTO_OFF)
environment_channel = autoset(environment_channel, APC_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, APC_CHANNEL_SETTING_ON)
lighting_channel = autoset(lighting_channel, APC_CHANNEL_SETTING_AUTO_OFF)
environment_channel = autoset(environment_channel, APC_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, APC_CHANNEL_SETTING_ON)
lighting_channel = autoset(lighting_channel, APC_CHANNEL_SETTING_ON)
environment_channel = autoset(environment_channel, APC_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, APC_CHANNEL_SETTING_OFF)
lighting_channel = autoset(lighting_channel, APC_CHANNEL_SETTING_OFF)
environment_channel = autoset(environment_channel, APC_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 sheet_type(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
if(shock_proof)
new /obj/item/stack/sheet/plastic(drop_loc, 10)
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, "<span class='warning'>The interface is broken!</span>")
else if(opened)
to_chat(user, "<span class='warning'>You must close the cover to swipe an ID card!</span>")
else if(panel_open)
to_chat(user, "<span class='warning'>You must close the panel!</span>")
else if(stat & (BROKEN|MAINT))
to_chat(user, "<span class='warning'>Nothing happens!</span>")
else
if(allowed(user) && !wires.is_cut(WIRE_IDSCAN) && !malfhack)
locked = !locked
to_chat(user, "<span class='notice'>You [locked ? "lock" : "unlock"] the APC interface.</span>")
update_icon()
else
to_chat(user, "<span class='warning'>Access denied.</span>")
/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(APC_CHANNEL_SETTING_OFF)
if(current_setting == APC_CHANNEL_SETTING_ON) // if on, return off
return APC_CHANNEL_SETTING_OFF
if(current_setting == APC_CHANNEL_SETTING_AUTO_ON) // if auto-on, return auto-off
return APC_CHANNEL_SETTING_AUTO_OFF
if(APC_CHANNEL_SETTING_AUTO_OFF)
if(current_setting == APC_CHANNEL_SETTING_AUTO_OFF) // if auto-off, return auto-on
return APC_CHANNEL_SETTING_AUTO_ON
if(APC_CHANNEL_SETTING_ON)
if(current_setting == APC_CHANNEL_SETTING_AUTO_ON) // if auto-on, return auto-off
return APC_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 == APC_CHANNEL_SETTING_AUTO_OFF) //and apc now set to off, return off
return APC_CHANNEL_SETTING_OFF
return new_setting //else return the new setting
if(new_setting == APC_CHANNEL_SETTING_AUTO_ON) //if no charge and set to auto, set to auto off
return APC_CHANNEL_SETTING_AUTO_OFF
return APC_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)
/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(emp_proof)
return
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("<span class='danger'>[src] suddenly lets out a blast of smoke and some sparks!", 3, "<span class='danger'>You hear sizzling electronics.</span>", 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
/// general syndicate access
/obj/machinery/power/apc/syndicate
name = "Main branch, do not use"
req_access = list(ACCESS_SYNDICATE)
report_power_alarm = FALSE
/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/machinery/power/apc/important
cell_type = 10000
/obj/machinery/power/apc/critical
cell_type = 25000
/// Can handle any amount of power. Made with plasteel frames and is found in maints and other high power areas.
/obj/machinery/power/apc/reinforced
shock_proof = TRUE
/obj/machinery/power/apc/reinforced/important
cell_type = 10000
/obj/machinery/power/apc/reinforced/critical
cell_type = 25000
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/syndicate, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/syndicate/off, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/important, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/critical, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/off_station, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/off_station/empty_charge, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/worn_out, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/reinforced, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/reinforced/important, 24, 24)
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/power/apc/reinforced/critical, 24, 24)
/obj/item/apc_electronics
name = "APC electronics"
desc = "Heavy-duty switching circuits for power control."
icon = 'icons/obj/module.dmi'
icon_state = "power_mod"
inhand_icon_state = "electronic"
w_class = WEIGHT_CLASS_SMALL
origin_tech = "engineering=2;programming=1"
flags = CONDUCT
usesound = 'sound/items/deconstruct.ogg'