Files
S.P.L.U.R.T-Station-13/code/game/machinery/_machinery.dm
Katherine Kiefer fd29f9c902 initial
2024-03-09 21:16:02 +11:00

674 lines
23 KiB
Plaintext

/*
Overview:
Used to create objects that need a per step proc call. Default definition of 'Initialize()'
stores a reference to src machine in global 'machines list'. Default definition
of 'Destroy' removes reference to src machine in global 'machines list'.
Class Variables:
use_power (num)
current state of auto power use.
Possible Values:
NO_POWER_USE -- no auto power use
IDLE_POWER_USE -- machine is using power at its idle power level
ACTIVE_POWER_USE -- machine is using power at its active power level
active_power_usage (num)
Value for the amount of power to use when in active power mode
idle_power_usage (num)
Value for the amount of power to use when in idle power mode
power_channel (num)
What channel to draw from when drawing power for power mode
Possible Values:
EQUIP:0 -- Equipment Channel
LIGHT:2 -- Lighting Channel
ENVIRON:3 -- Environment Channel
component_parts (list)
A list of component parts of machine used by frame based machines.
stat (bitflag)
Machine status bit flags.
Possible bit flags:
BROKEN -- Machine is broken
NOPOWER -- No power is being supplied to machine.
MAINT -- machine is currently under going maintenance.
EMPED -- temporary broken by EMP pulse
Class Procs:
Initialize() 'game/machinery/machine.dm'
Destroy() 'game/machinery/machine.dm'
auto_use_power() 'game/machinery/machine.dm'
This proc determines how power mode power is deducted by the machine.
'auto_use_power()' is called by the 'master_controller' game_controller every
tick.
Return Value:
return:1 -- if object is powered
return:0 -- if object is not powered.
Default definition uses 'use_power', 'power_channel', 'active_power_usage',
'idle_power_usage', 'powered()', and 'use_power()' implement behavior.
powered(chan = EQUIP) 'modules/power/power.dm'
Checks to see if area that contains the object has power available for power
channel given in 'chan'.
use_power(amount, chan=EQUIP) 'modules/power/power.dm'
Deducts 'amount' from the power channel 'chan' of the area that contains the object.
power_change() 'modules/power/power.dm'
Called by the area that contains the object when ever that area under goes a
power state change (area runs out of power, or area channel is turned off).
RefreshParts() 'game/machinery/machine.dm'
Called to refresh the variables in the machine that are contributed to by parts
contained in the component_parts list. (example: glass and material amounts for
the autolathe)
Default definition does nothing.
process() 'game/machinery/machine.dm'
Called by the 'machinery subsystem' once per machinery tick for each machine that is listed in its 'machines' list.
process_atmos()
Called by the 'air subsystem' once per atmos tick for each machine that is listed in its 'atmos_machines' list.
is_operational()
Returns 0 if the machine is unpowered, broken or undergoing maintenance, something else if not
Compiled by Aygar
*/
/obj/machinery
name = "machinery"
icon = 'icons/obj/stationobjs.dmi'
desc = "Some kind of machine."
verb_say = "beeps"
verb_yell = "blares"
pressure_resistance = 15
pass_flags_self = PASSMACHINE
max_integrity = 200
layer = BELOW_OBJ_LAYER //keeps shit coming out of the machine from ending up underneath it.
flags_1 = DEFAULT_RICOCHET_1
flags_ricochet = RICOCHET_HARD
ricochet_chance_mod = 0.3
explosion_flags = EXPLOSION_FLAG_DENSITY_DEPENDENT
wave_explosion_block = EXPLOSION_BLOCK_MACHINE
wave_explosion_multiply = EXPLOSION_DAMPEN_MACHINE
anchored = TRUE
interaction_flags_atom = INTERACT_ATOM_ATTACK_HAND | INTERACT_ATOM_UI_INTERACT
vocal_bark_id = "synth"
vocal_pitch = 0.6
vocal_volume = 40
var/stat = 0
var/use_power = IDLE_POWER_USE
//0 = dont run the auto
//1 = run auto, use idle
//2 = run auto, use active
var/idle_power_usage = 0
var/active_power_usage = 0
var/power_channel = EQUIP
//EQUIP,ENVIRON or LIGHT
var/list/component_parts = null //list of all the parts used to build it, if made from certain kinds of frames.
var/panel_open = FALSE
var/state_open = FALSE
var/critical_machine = FALSE //If this machine is critical to station operation and should have the area be excempted from power failures.
var/list/occupant_typecache //if set, turned into typecache in Initialize, other wise, defaults to mob/living typecache
var/atom/movable/occupant
var/new_occupant_dir = SOUTH //The direction the occupant will be set to look at when entering the machine.
var/speed_process = FALSE // Process as fast as possible?
var/obj/item/circuitboard/circuit // Circuit to be created and inserted when the machinery is created
var/wire_compatible = FALSE
// For storing and overriding ui id and dimensions
var/tgui_id // ID of TGUI interface
var/ui_style // ID of custom TGUI style (optional)
var/ui_x
var/ui_y
var/init_process = TRUE //Stop processing from starting on init
var/interaction_flags_machine = INTERACT_MACHINE_WIRES_IF_OPEN | INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_OPEN_SILICON | INTERACT_MACHINE_SET_MACHINE
var/fair_market_price = 69
var/market_verb = "Customer"
var/payment_department = ACCOUNT_ENG
///Boolean on whether this machines interact with atmos
var/atmos_processing = FALSE
/obj/machinery/Initialize(mapload)
if(!armor)
armor = list(MELEE = 25, BULLET = 10, LASER = 10, ENERGY = 0, BOMB = 0, BIO = 0, RAD = 0, FIRE = 50, ACID = 70)
. = ..()
GLOB.machines += src
if(ispath(circuit, /obj/item/circuitboard))
circuit = new circuit(src)
circuit.apply_default_parts(src)
if(init_process) // Required to prevent non-speed non-init machines from running
if(speed_process)
START_PROCESSING(SSfastprocess, src)
else
START_PROCESSING(SSmachines, src)
RegisterSignal(src, COMSIG_ENTER_AREA, PROC_REF(power_change))
if (occupant_typecache)
occupant_typecache = typecacheof(occupant_typecache)
return INITIALIZE_HINT_LATELOAD
/obj/machinery/LateInitialize()
. = ..()
power_change()
/obj/machinery/Destroy()
GLOB.machines.Remove(src)
if(!speed_process)
STOP_PROCESSING(SSmachines, src)
else
STOP_PROCESSING(SSfastprocess, src)
dropContents()
if(length(component_parts))
for(var/atom/A in component_parts)
qdel(A)
component_parts.Cut()
return ..()
/obj/machinery/proc/locate_machinery()
return
/obj/machinery/process()//If you dont use process or power why are you here
return PROCESS_KILL
/obj/machinery/proc/process_atmos()//If you dont use process why are you here
return PROCESS_KILL
///Called when we want to change the value of the stat variable. Holds bitflags.
/obj/machinery/proc/set_machine_stat(new_value)
if(new_value == stat)
return
. = stat
stat = new_value
on_machine_stat_update(stat)
/obj/machinery/proc/on_machine_stat_update(stat)
return
/obj/machinery/emp_act(severity)
. = ..()
if(use_power && !stat && !(. & EMP_PROTECT_SELF))
use_power(1000 + severity*65)
new /obj/effect/temp_visual/emp(loc)
/obj/machinery/proc/open_machine(drop = TRUE)
state_open = TRUE
density = FALSE
if(drop)
dropContents()
update_icon()
updateUsrDialog()
/obj/machinery/proc/dropContents(list/subset = null)
var/turf/T = get_turf(src)
for(var/atom/movable/A in contents)
if(subset && !(A in subset))
continue
A.forceMove(T)
if(isliving(A))
var/mob/living/L = A
L.update_mobility()
if(occupant)
SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant)
occupant = null
/obj/machinery/proc/can_be_occupant(atom/movable/am)
return occupant_typecache ? is_type_in_typecache(am, occupant_typecache) : isliving(am)
/obj/machinery/proc/close_machine(atom/movable/target = null)
state_open = FALSE
density = TRUE
if(!target)
for(var/am in loc)
if (!(can_be_occupant(am)))
continue
var/atom/movable/AM = am
if(AM.has_buckled_mobs())
continue
if(isliving(AM))
var/mob/living/L = am
if(L.buckled || L.mob_size >= MOB_SIZE_LARGE)
continue
target = am
var/mob/living/mobtarget = target
if(target && !target.has_buckled_mobs() && (!isliving(target) || !mobtarget.buckled))
occupant = target
target.forceMove(src)
target.setDir(new_occupant_dir)
updateUsrDialog()
update_icon()
/obj/machinery/proc/auto_use_power()
if(!powered(power_channel))
return FALSE
if(use_power == 1)
use_power(idle_power_usage,power_channel)
else if(use_power >= 2)
use_power(active_power_usage,power_channel)
return TRUE
/obj/machinery/proc/is_operational()
return !(stat & (NOPOWER|BROKEN|MAINT))
/obj/machinery/can_interact(mob/user)
if((stat & (NOPOWER|BROKEN)) && !(interaction_flags_machine & INTERACT_MACHINE_OFFLINE)) // Check if the machine is broken, and if we can still interact with it if so
return FALSE
if(IsAdminGhost(user))
return TRUE //if you're an admin, you probably know what you're doing (or at least have permission to do what you're doing)
if(!isliving(user))
return FALSE //no ghosts in the machine allowed, sorry
// if(SEND_SIGNAL(user, COMSIG_TRY_USE_MACHINE, src) & COMPONENT_CANT_USE_MACHINE_INTERACT)
// return FALSE
var/mob/living/living_user = user
var/is_dextrous = FALSE
if(isanimal(user))
var/mob/living/simple_animal/user_as_animal = user
if (user_as_animal.dextrous)
is_dextrous = TRUE
if(!issilicon(user) && !is_dextrous && !user.can_hold_items())
return FALSE //spiders gtfo
if(issilicon(user)) // If we are a silicon, make sure the machine allows silicons to interact with it
if(!(interaction_flags_machine & INTERACT_MACHINE_ALLOW_SILICON))
return FALSE
if(panel_open && !(interaction_flags_machine & INTERACT_MACHINE_OPEN) && !(interaction_flags_machine & INTERACT_MACHINE_OPEN_SILICON))
return FALSE
return TRUE //silicons don't care about petty mortal concerns like needing to be next to a machine to use it
if(living_user.incapacitated()) //idk why silicons aren't supposed to care about incapacitation when interacting with machines, but it was apparently like this before
return FALSE
// TODO: nerf blind people
// if((interaction_flags_machine & INTERACT_MACHINE_REQUIRES_SIGHT) && user.is_blind())
// to_chat(user, span_warning("This machine requires sight to use."))
// return FALSE
if(panel_open && !(interaction_flags_machine & INTERACT_MACHINE_OPEN))
return FALSE
if(interaction_flags_machine & INTERACT_MACHINE_REQUIRES_SILICON) //if the user was a silicon, we'd have returned out earlier, so the user must not be a silicon
return FALSE
if(!Adjacent(user)) // Next make sure we are next to the machine unless we have telekinesis
var/mob/living/carbon/carbon_user = living_user
if(!istype(carbon_user) || !carbon_user.has_dna() || !carbon_user.dna.check_mutation(TK))
return FALSE
return TRUE // If we passed all of those checks, woohoo! We can interact with this machine.
/obj/machinery/proc/can_transact(obj/item/card/id/thecard, allowdepartment, silent)
if(!istype(thecard))
if(!silent)
say("No card found.")
return FALSE
else if (!thecard.registered_account)
if(!silent)
say("No account found.")
return FALSE
else if(!allowdepartment && !thecard.registered_account.account_job)
if(!silent)
say("Departmental accounts have been blacklisted from personal expenses due to embezzlement.")
return FALSE
return TRUE
/obj/machinery/proc/attempt_transact(obj/item/card/id/thecard, transaction_cost)
if(!istype(thecard))
return FALSE
var/datum/bank_account/account = thecard.registered_account
if(!istype(account))
return FALSE
if(transaction_cost)
if(!account.adjust_money(-transaction_cost))
return FALSE
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
if(D)
D.adjust_money(transaction_cost)
return TRUE
/obj/machinery/proc/check_nap_violations()
if(!SSeconomy.full_ancap)
return TRUE
if(occupant && !state_open)
var/mob/living/L = occupant
var/obj/item/card/id/I = L.get_idcard(TRUE)
if(I)
var/datum/bank_account/insurance = I.registered_account
if(!insurance)
say("[market_verb] NAP Violation: No bank account found.")
nap_violation(L)
return FALSE
else
if(!insurance.adjust_money(-fair_market_price))
say("[market_verb] NAP Violation: Unable to pay.")
nap_violation(L)
return FALSE
var/datum/bank_account/D = SSeconomy.get_dep_account(payment_department)
if(D)
D.adjust_money(fair_market_price)
else
say("[market_verb] NAP Violation: No ID card found.")
nap_violation(L)
return FALSE
return TRUE
/obj/machinery/proc/nap_violation(mob/violator)
return
////////////////////////////////////////////////////////////////////////////////////////////
//Return a non FALSE value to interrupt attack_hand propagation to subtypes.
/obj/machinery/interact(mob/user, special_state)
if(interaction_flags_machine & INTERACT_MACHINE_SET_MACHINE)
user.set_machine(src)
. = ..()
/obj/machinery/ui_act(action, params)
add_fingerprint(usr)
return ..()
/obj/machinery/Topic(href, href_list)
..()
if(!can_interact(usr))
return TRUE
if(!usr.canUseTopic(src))
return TRUE
add_fingerprint(usr)
return FALSE
////////////////////////////////////////////////////////////////////////////////////////////
/obj/machinery/attack_paw(mob/living/user)
if(user.a_intent != INTENT_HARM)
return attack_hand(user)
else
user.DelayNextAction(CLICK_CD_MELEE)
user.do_attack_animation(src, ATTACK_EFFECT_PUNCH)
user.visible_message("<span class='danger'>[user.name] smashes against \the [src.name] with its paws.</span>", null, null, COMBAT_MESSAGE_RANGE)
take_damage(4, BRUTE, MELEE, 1)
/obj/machinery/attack_robot(mob/user)
if(!(interaction_flags_machine & INTERACT_MACHINE_ALLOW_SILICON) && !IsAdminGhost(user))
return FALSE
return _try_interact(user)
/obj/machinery/attack_ai(mob/user)
if(!(interaction_flags_machine & INTERACT_MACHINE_ALLOW_SILICON) && !IsAdminGhost(user))
return FALSE
if(iscyborg(user))// For some reason attack_robot doesn't work
return attack_robot(user)
else
return _try_interact(user)
/obj/machinery/_try_interact(mob/user)
if((interaction_flags_machine & INTERACT_MACHINE_WIRES_IF_OPEN) && panel_open && (attempt_wire_interaction(user) == WIRE_INTERACTION_BLOCK))
return TRUE
return ..()
/obj/machinery/CheckParts(list/parts_list)
..()
RefreshParts()
/obj/machinery/proc/RefreshParts() //Placeholder proc for machines that are built using frames.
return
/obj/machinery/proc/default_pry_open(obj/item/I)
. = !(state_open || panel_open || is_operational() || (flags_1 & NODECONSTRUCT_1)) && I.tool_behaviour == TOOL_CROWBAR
if(.)
I.play_tool_sound(src, 50)
visible_message("<span class='notice'>[usr] pries open \the [src].</span>", "<span class='notice'>You pry open \the [src].</span>")
open_machine()
/obj/machinery/proc/default_deconstruction_crowbar(obj/item/I, ignore_panel = 0)
. = (panel_open || ignore_panel) && !(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_CROWBAR
if(.)
I.play_tool_sound(src, 50)
deconstruct(TRUE)
/obj/machinery/deconstruct(disassembled = TRUE)
if(!(flags_1 & NODECONSTRUCT_1))
on_deconstruction()
if(LAZYLEN(component_parts))
spawn_frame(disassembled)
for(var/obj/item/I in component_parts)
I.forceMove(loc)
LAZYCLEARLIST(component_parts)
qdel(src)
/obj/machinery/proc/spawn_frame(disassembled)
var/obj/structure/frame/machine/M = new /obj/structure/frame/machine(loc)
. = M
M.setAnchored(anchored)
if(!disassembled)
M.obj_integrity = M.max_integrity * 0.5 //the frame is already half broken
transfer_fingerprints_to(M)
M.state = 2
M.icon_state = "box_1"
/obj/machinery/obj_break(damage_flag)
. = ..()
if(!(stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1))
stat |= BROKEN
SEND_SIGNAL(src, COMSIG_MACHINERY_BROKEN, damage_flag)
update_appearance()
return TRUE
/obj/machinery/contents_explosion(severity, target, origin)
if(occupant)
occupant.ex_act(severity, target, origin)
/obj/machinery/handle_atom_del(atom/A)
if(A == occupant)
occupant = null
update_icon()
updateUsrDialog()
/obj/machinery/CanAllowThrough(atom/movable/mover, turf/target)
. = ..()
if(mover.pass_flags & PASSMACHINE)
return TRUE
/obj/machinery/proc/default_deconstruction_screwdriver(mob/user, icon_state_open, icon_state_closed, obj/item/I)
if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_SCREWDRIVER)
I.play_tool_sound(src, 50)
if(!panel_open)
panel_open = TRUE
icon_state = icon_state_open
to_chat(user, "<span class='notice'>You open the maintenance hatch of [src].</span>")
else
panel_open = FALSE
icon_state = icon_state_closed
to_chat(user, "<span class='notice'>You close the maintenance hatch of [src].</span>")
return TRUE
return FALSE
/obj/machinery/proc/default_change_direction_wrench(mob/user, obj/item/I)
if(panel_open && I.tool_behaviour == TOOL_WRENCH)
I.play_tool_sound(src, 50)
setDir(turn(dir,-90))
to_chat(user, "<span class='notice'>You rotate [src].</span>")
return TRUE
return FALSE
/obj/proc/can_be_unfasten_wrench(mob/user, silent) //if we can unwrench this object; returns SUCCESSFUL_UNFASTEN and FAILED_UNFASTEN, which are both TRUE, or CANT_UNFASTEN, which isn't.
if(!(isfloorturf(loc) || istype(loc, /turf/open/indestructible)) && !anchored)
to_chat(user, "<span class='warning'>[src] needs to be on the floor to be secured!</span>")
return FAILED_UNFASTEN
return SUCCESSFUL_UNFASTEN
/obj/proc/default_unfasten_wrench(mob/user, obj/item/I, time = 20) //try to unwrench an object in a WONDERFUL DYNAMIC WAY
if(!(flags_1 & NODECONSTRUCT_1) && I.tool_behaviour == TOOL_WRENCH)
var/can_be_unfasten = can_be_unfasten_wrench(user)
if(!can_be_unfasten || can_be_unfasten == FAILED_UNFASTEN)
return can_be_unfasten
if(time)
to_chat(user, "<span class='notice'>You begin [anchored ? "un" : ""]securing [src]...</span>")
I.play_tool_sound(src, 50)
var/prev_anchored = anchored
//as long as we're the same anchored state and we're either on a floor or are anchored, toggle our anchored state
if(I.use_tool(src, user, time, extra_checks = CALLBACK(src, PROC_REF(unfasten_wrench_check), prev_anchored, user)))
to_chat(user, "<span class='notice'>You [anchored ? "un" : ""]secure [src].</span>")
setAnchored(!anchored)
playsound(src, 'sound/items/deconstruct.ogg', 50, 1)
SEND_SIGNAL(src, COMSIG_OBJ_DEFAULT_UNFASTEN_WRENCH, anchored)
return SUCCESSFUL_UNFASTEN
return FAILED_UNFASTEN
return CANT_UNFASTEN
/obj/proc/unfasten_wrench_check(prev_anchored, mob/user) //for the do_after, this checks if unfastening conditions are still valid
if(anchored != prev_anchored)
return FALSE
if(can_be_unfasten_wrench(user, TRUE) != SUCCESSFUL_UNFASTEN) //if we aren't explicitly successful, cancel the fuck out
return FALSE
return TRUE
/obj/machinery/proc/exchange_parts(mob/user, obj/item/storage/part_replacer/W)
if(!istype(W))
return FALSE
if((flags_1 & NODECONSTRUCT_1) && !W.works_from_distance)
return FALSE
var/shouldplaysound = 0
if(component_parts)
if(panel_open || W.works_from_distance)
var/obj/item/circuitboard/machine/CB = locate(/obj/item/circuitboard/machine) in component_parts
var/P
if(W.works_from_distance)
to_chat(user, display_parts(user))
for(var/obj/item/A in component_parts)
for(var/D in CB.req_components)
if(ispath(A.type, D))
P = D
break
for(var/obj/item/B in W.contents)
if(istype(B, P) && istype(A, P))
if(B.get_part_rating() > A.get_part_rating())
if(istype(B,/obj/item/stack)) //conveniently this will mean A is also a stack and I will kill the first person to prove me wrong
var/obj/item/stack/SA = A
var/obj/item/stack/SB = B
var/used_amt = SA.get_amount()
if(!SB.use(used_amt))
continue //if we don't have the exact amount to replace we don't
var/obj/item/stack/SN = new SB.merge_type(null,used_amt)
component_parts += SN
else
if(SEND_SIGNAL(W, COMSIG_TRY_STORAGE_TAKE, B, src))
component_parts += B
B.moveToNullspace()
SEND_SIGNAL(W, COMSIG_TRY_STORAGE_INSERT, A, null, null, TRUE)
component_parts -= A
to_chat(user, "<span class='notice'>[capitalize(A.name)] replaced with [B.name].</span>")
shouldplaysound = 1 //Only play the sound when parts are actually replaced!
break
RefreshParts()
else
to_chat(user, display_parts(user))
if(shouldplaysound)
W.play_rped_sound()
return TRUE
return FALSE
/obj/machinery/proc/display_parts(mob/user)
. = list()
. += "<span class='notice'>It contains the following parts:</span>"
for(var/obj/item/C in component_parts)
. += "<span class='notice'>[icon2html(C, user)] \A [C].</span>"
. = jointext(., "")
/obj/machinery/examine(mob/user)
. = ..()
if(stat & BROKEN)
. += "<span class='notice'>It looks broken and non-functional.</span>"
if(!(resistance_flags & INDESTRUCTIBLE))
if(resistance_flags & ON_FIRE)
. += "<span class='warning'>It's on fire!</span>"
var/healthpercent = (obj_integrity/max_integrity) * 100
switch(healthpercent)
if(50 to 99)
. += "It looks slightly damaged."
if(25 to 50)
. += "It appears heavily damaged."
if(0 to 25)
. += "<span class='warning'>It's falling apart!</span>"
if(user.research_scanner && component_parts)
. += display_parts(user, TRUE)
//called on machinery construction (i.e from frame to machinery) but not on initialization
/obj/machinery/proc/on_construction()
for(var/obj/I in contents)
I.moveToNullspace()
return
//called on deconstruction before the final deletion
/obj/machinery/proc/on_deconstruction()
return
/obj/machinery/proc/can_be_overridden()
. = 1
/obj/machinery/zap_act(power, zap_flags, shocked_objects)
. = ..()
if(prob(85) && (zap_flags & ZAP_MACHINE_EXPLOSIVE))
explosion(src, 1, 2, 4, flame_range = 2, adminlog = FALSE, smoke = FALSE)
else if(zap_flags & ZAP_OBJ_DAMAGE)
take_damage(power/2000, BURN, ENERGY)
if(prob(40))
emp_act(50)
/obj/machinery/Exited(atom/movable/AM, atom/newloc)
. = ..()
// if(AM == occupant)
// set_occupant(null)
if (AM == occupant)
SEND_SIGNAL(src, COMSIG_MACHINE_EJECT_OCCUPANT, occupant)
occupant = null
if(AM == circuit && circuit.loc != src)
component_parts -= AM //TODO: make the cmp part functions use lazyX
circuit = null
/obj/machinery/proc/adjust_item_drop_location(atom/movable/AM) // Adjust item drop location to a 3x3 grid inside the tile, returns slot id from 0 to 8
var/md5 = md5(AM.name) // Oh, and it's deterministic too. A specific item will always drop from the same slot.
for (var/i in 1 to 32)
. += hex2num(md5[i])
. = . % 9
AM.pixel_x = -8 + ((.%3)*8)
AM.pixel_y = -8 + (round( . / 3)*8)
/obj/machinery/rust_heretic_act()
take_damage(500, BRUTE, MELEE, 1)
/**
* Alerts the AI that a hack is in progress.
*
* Sends all AIs a message that a hack is occurring. Specifically used for space ninja tampering as this proc was originally in the ninja files.
* However, the proc may also be used elsewhere.
*/
/obj/machinery/proc/AI_notify_hack()
var/alertstr = "<span class='userdanger'>Network Alert: Hacking attempt detected[get_area(src)?" in [get_area_name(src, TRUE)]":". Unable to pinpoint location"].</span>"
for(var/mob/living/silicon/ai/AI in GLOB.player_list)
to_chat(AI, alertstr)