/* Overview: Used to create objects that need a per step proc call. Default definition of 'New()' stores a reference to src machine in global 'machines list'. Default definition of 'Del' removes reference to src machine in global 'machines list'. Class Variables: use_power (num) current state of auto power use. Possible Values: 0 -- no auto power use 1 -- machine is using power at its idle power level 2 -- 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: AREA_USAGE_EQUIP:0 -- Equipment Channel AREA_USAGE_LIGHT:2 -- Lighting Channel AREA_USAGE_ENVIRON:3 -- Environment Channel component_parts (list) A list of component parts of machine used by frame based machines. panel_open (num) Whether the panel is open uid (num) Unique id of machine across all machines. gl_uid (global num) Next uid value in sequence stat (bitflag) Machine status bit flags. Possible bit flags: BROKEN:1 -- Machine is broken NOPOWER:2 -- No power is being supplied to machine. POWEROFF:4 -- tbd MAINT:8 -- machine is currently under going maintenance. EMPED:16 -- temporary broken by EMP pulse Class Procs: New() 'game/machinery/machine.dm' Destroy() 'game/machinery/machine.dm' powered(chan = AREA_USAGE_EQUIP) 'modules/power/power_usage.dm' Checks to see if area that contains the object has power available for power channel given in 'chan'. use_power_oneoff(amount, chan=AREA_USAGE_EQUIP, autocalled) 'modules/power/power_usage.dm' Deducts 'amount' from the power channel 'chan' of the area that contains the object. This is not a continuous draw, but rather will be cleared after one APC update. power_change() 'modules/power/power_usage.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 handles power usage only (all parts contribute based on energy_rating). assign_uid() 'game/machinery/machine.dm' Called by machine to assign a value to the uid variable. process() 'game/machinery/machine.dm' Called by the 'master_controller' once per game tick for each machine that is listed in the 'machines' list. Compiled by Aygar */ /obj/structure/machinery name = "machinery" icon = 'icons/obj/stationobjs.dmi' init_flags = INIT_MACHINERY_PROCESS_SELF pass_flags_self = PASSMACHINE | LETPASSCLICKS destroy_sound = 'sound/effects/meteorimpact.ogg' hitsound = 'sound/effects/metalhit.ogg' /** * 'stat' = 'state'. Controlled by a bitflag, differentiates between a few different possible states including the machine being broken or unpowered. * These definitions are copypasted from 'code/__DEFINES/machinery.dm' so they can be easily referenced in code. * SO THAT MEANS IF THEY'VE BEEN UPDATED THERE, MAKE SURE THEY'RE UPDATED HERE! * * #define BROKEN 0x1 * #define NOPOWER 0x2 * #define POWEROFF 0x4 // TBD. * #define MAINT 0x8 // Under maintenance. * #define EMPED 0x10 // Temporary broken by EMP pulse. * * #define INOPERABLE(machine) (machine.stat & (BROKEN|NOPOWER|MAINT|EMPED)) * #define OPERABLE(machine) !INOPERABLE(machine) */ var/stat = 0 /// Is this machine emagged? var/emagged = 0 /// In what power state is this machine? Possible states include being off, idle, or active - see code/__defines/machinery.dm. /// You should not be modifying this directly! Use the procs in power_usage.dm. var/use_power = POWER_USE_IDLE var/internal = FALSE /// How much power should this be drawing in the idle power state? var/idle_power_usage = 0 /// How much power should this be drawing in the active power state? var/active_power_usage = 0 var/power_init_complete = FALSE /// What power channel does this fall under in APCs? Possible channels include: AREA_USAGE_EQUIP, AREA_USAGE_ENVIRON or AREA_USAGE_LIGHT var/power_channel = AREA_USAGE_EQUIP /* List of types that should be spawned as component_parts for this machine. Structure: type -> num_objects num_objects is optional, and will be treated as 1 if omitted. example: component_types = list( /obj/foo/bar, /obj/baz = 2 ) */ var/list/component_types /// List of all the parts used to build it, if made from certain kinds of frames. var/list/component_parts = null /// Use the generic power rating mechanics for parts, or bespoke. var/parts_power_mgmt = TRUE /// The total power rating of all parts serves as a power usage multiplier. var/parts_power_usage = 0 var/uid var/panel_open = 0 var/global/gl_uid = 1 /// Can the machine be interacted with while de-powered? var/interact_offline = FALSE /// Is this machine currently printing anything? var/printing = FALSE /// Component parts queued for processing by the machine. Expected type: `/obj/item/stock_parts` Unused currently. var/list/processing_parts /// Bitflag. What is being processed. One of `MACHINERY_PROCESS_*`. var/processing_flags /// Played sound on usage. var/clicksound /// Volume. var/clickvol = 40 /// Signaller attached to the machine var/obj/item/assembly/signaler/signaler /// Overmap sector the machine is linked to var/obj/effect/overmap/visitable/linked /** * Manufacturer of this machine. Used for TGUI themes, when you have a base type and subtypes with different themes (like the coffee machine). * Pass the manufacturer in ui_data and then use it in the UI. */ var/manufacturer = null ///Do we want to hook into on_enter_area and on_exit_area? ///Disables some optimizations var/always_area_sensitive = FALSE /obj/structure/machinery/feedback_hints(mob/user, distance, is_adjacent) . = list() if(signaler && is_adjacent) . += SPAN_WARNING("\The [src] has a hidden signaler attached to it. You might or might not notice this.") // Still needs some work- must be able to be distinguish between thinobjectsgs that are anchored that can be casually // unanchored (i.e. vending machines) vs. objects that are anchored but require other steps to unanchor (i.e. airlocks). /* if(anchored) . += SPAN_NOTICE("\The [src] is anchored to the floor by a couple of bolts.") */ /obj/structure/machinery/Initialize(mapload, d = 0, populate_components = TRUE, is_internal = FALSE) //Stupid macro used in power usage CAN_BE_REDEFINED(TRUE) . = ..() if(d) set_dir(d) if(init_flags & INIT_MACHINERY_PROCESS_ALL) START_PROCESSING_MACHINE(src, init_flags & INIT_MACHINERY_PROCESS_ALL) SSmachinery.machinery += src // All machines should be in machinery. if (populate_components && component_types) component_parts = list() for (var/type in component_types) var/count = component_types[type] if(ispath(type, /obj/item/stack)) if(isnull(count)) count = 1 component_parts += new type(src, count) else if(count > 1) for (var/i in 1 to count) component_parts += new type(src) else component_parts += new type(src) if(component_parts.len) RefreshParts() return INITIALIZE_HINT_LATELOAD /obj/structure/machinery/Destroy() //Stupid macro used in power usage CAN_BE_REDEFINED(TRUE) STOP_PROCESSING_MACHINE(src, MACHINERY_PROCESS_ALL) SSmachinery.machinery -= src //Clear the component parts //If the components are inside the machine, delete them, otherwise we assume they were dropped to the ground during deconstruction, //and were not removed from the component_parts list by deconstruction code if(component_parts) for(var/atom/A in component_parts) if(A.loc == src) qdel(A) component_parts = null return ..() /obj/structure/machinery/on_death(damage, damage_flags, damage_type, armor_penetration, obj/weapon) var/turf/current_turf = get_turf(src) spark(current_turf, 3, GLOB.alldirs) if(component_parts) for(var/obj/item/stock_parts/part in component_parts) if(prob(25)) part.forceMove(current_turf) component_parts -= part var/obj/item/circuitboard/board = locate() in component_parts if(istype(board)) if(prob(25)) board.forceMove(current_turf) component_parts -= board else new /obj/item/trash/broken_electronics(current_turf) var/metal_to_spawn = 0 for(var/i = 1 to 2) if(prob(50)) metal_to_spawn++ else new /obj/item/material/shard(current_turf, DEFAULT_WALL_MATERIAL) if(metal_to_spawn) new /obj/item/stack/material/steel(get_turf(src), metal_to_spawn) if(!should_use_health) return FALSE qdel(src) /obj/structure/machinery/examine_descriptor(mob/user) return "machine" // /obj/structure/machinery/proc/process_all() // /* Uncomment this if/when you need component processing // if(processing_flags & MACHINERY_PROCESS_COMPONENTS) // for(var/thing in processing_parts) // var/obj/item/stock_parts/part = thing // if(part.machine_process(src) == PROCESS_KILL) // part.stop_processing() */ // if((processing_flags & MACHINERY_PROCESS_SELF)) // . = process() // if(. == PROCESS_KILL) // STOP_PROCESSING_MACHINE(src, MACHINERY_PROCESS_SELF) /obj/structure/machinery/process(seconds_per_tick) return PROCESS_KILL /obj/structure/machinery/emp_act(severity) . = ..() if(use_power && stat == 0) use_power_oneoff(7500/severity) var/obj/effect/overlay/pulse2 = new(src.loc) pulse2.icon = 'icons/effects/effects.dmi' pulse2.icon_state = "empdisable" pulse2.name = "emp sparks" pulse2.anchored = 1 pulse2.set_dir(pick(GLOB.cardinals)) QDEL_IN(pulse2, 10) /** * Check to see if the machine is operable * * * `additional_flags` - Additional flags to check for, that could have been added to the `stat` variable * * Returns `TRUE` if the machine is operable, `FALSE` otherwise */ /obj/structure/machinery/proc/operable(additional_flags = 0) SHOULD_NOT_SLEEP(TRUE) SHOULD_BE_PURE(TRUE) if(stat & (NOPOWER|BROKEN|additional_flags)) return FALSE else return TRUE /obj/structure/machinery/proc/toggle_power(power_set = -1, additional_flags = 0) if(power_set >= 0) update_use_power(power_set) else if (use_power || !operable(additional_flags)) update_use_power(POWER_USE_OFF) else update_use_power(initial(use_power)) update_icon() /obj/structure/machinery/CanUseTopic(mob/user) if(stat & BROKEN) return STATUS_CLOSE if(!interact_offline && (stat & NOPOWER)) return STATUS_CLOSE return ..() /obj/structure/machinery/CouldUseTopic(mob/user) ..() if(clicksound && iscarbon(user)) playsound(src, clicksound, clickvol) user.set_machine(src) /obj/structure/machinery/CouldNotUseTopic(mob/user) user.unset_machine() //////////////////////////////////////////////////////////////////////////////////////////// /obj/structure/machinery/attack_ai(mob/user) if(!ai_can_interact(user)) return if(isrobot(user)) // For some reason attack_robot doesn't work // This is to stop robots from using cameras to remotely control machines. if(user.client && user.client.eye == user) return src.attack_hand(user) else return src.attack_hand(user) /obj/structure/machinery/attack_hand(mob/user) if(!operable(MAINT)) return TRUE if(user.lying || user.stat) return TRUE if (!istype(usr, /mob/living/carbon/human) && !istype(usr, /mob/living/silicon)) to_chat(usr, SPAN_WARNING("You don't have the dexterity to do this!")) return TRUE src.add_fingerprint(user) return ..() /obj/structure/machinery/attack_ranged(mob/user, params) . = ..() if(isipc(user)) var/mob/living/carbon/human/robot = user var/obj/item/organ/internal/machine/wireless_access/wireless_access_point = robot.internal_organs_by_name[BP_WIRELESS_ACCESS] if(wireless_access_point?.access_terminal(src)) attack_hand(user) /obj/structure/machinery/attackby(obj/item/attacking_item, mob/user) if(obj_flags & OBJ_FLAG_SIGNALER) if(issignaler(attacking_item)) if(signaler) to_chat(user, SPAN_WARNING("\The [src] already has a signaler attached.")) return TRUE var/obj/item/assembly/signaler/S = attacking_item user.drop_from_inventory(attacking_item, src) signaler = S S.machine = src user.visible_message("[user] attaches \the [S] to \the [src].", SPAN_NOTICE("You attach \the [S] to \the [src]."), range = 3) log_and_message_admins("has attached a signaler to \the [src].", user, get_turf(src)) return TRUE else if(attacking_item.tool_behaviour == TOOL_WIRECUTTER && signaler) user.visible_message("[user] removes \the [signaler] from \the [src].", SPAN_NOTICE("You remove \the [signaler] from \the [src]."), range = 3) user.put_in_hands(detach_signaler()) return TRUE return ..() /obj/structure/machinery/proc/detach_signaler(var/turf/detach_turf) if(!signaler) return if(!detach_turf) detach_turf = get_turf(src) if(!detach_turf) LOG_DEBUG("[src] tried to drop a signaler, but it had no turf ([src.x]-[src.y]-[src.z])") return var/obj/item/assembly/signaler/S = signaler signaler.forceMove(detach_turf) signaler.machine = null signaler = null return S /obj/structure/machinery/proc/RefreshParts() /* if(parts_power_mgmt) var/new_idle_power var/new_active_power if(!component_parts || !component_parts.len) return var/parts_energy_rating = 0 for(var/obj/item/stock_parts/part in component_parts) parts_energy_rating += part.energy_rating() new_idle_power = initial(idle_power_usage) * (1 + parts_energy_rating) new_active_power = initial(active_power_usage) * (1 + parts_energy_rating) change_power_consumption(new_idle_power) change_power_consumption(new_active_power, POWER_USE_ACTIVE) */ /obj/structure/machinery/proc/assign_uid() uid = gl_uid gl_uid++ /obj/structure/machinery/proc/state(var/msg) for(var/mob/O in hearers(src, null)) O.show_message("[icon2html(src, O)] [msg]", 2) /obj/structure/machinery/proc/ping(text=null) if (!text) text = "\The [src] pings." state(text, "blue") playsound(src.loc, 'sound/machines/ping.ogg', 50, 0) /obj/structure/machinery/proc/pingx3(text=null) if (!text) text = "\The [src] pings." state(text, "blue") playsound(src.loc, 'sound/machines/pingx3.ogg', 50, 0) /obj/structure/machinery/proc/buzz(text=null) if (!text) text = "\The [src] buzzes." state(text, "blue") playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0) //TODO: Check if that one is the correct sound /obj/structure/machinery/proc/shock(mob/user, prb) if(!operable()) return 0 if(!prob(prb)) return 0 spark(src, 5, GLOB.alldirs) if (electrocute_mob(user, get_area(src), src, 0.7)) var/area/temp_area = get_area(src) if(temp_area) var/obj/structure/machinery/power/apc/temp_apc = temp_area.get_apc() if(temp_apc && temp_apc.terminal && temp_apc.terminal.powernet) temp_apc.terminal.powernet.trigger_warning() if(user.stunned) return 1 return 0 /obj/structure/machinery/proc/default_deconstruction_crowbar(var/mob/user, var/obj/item/C) if(!istype(C) || C.tool_behaviour != TOOL_CROWBAR) return 0 if(!panel_open) return 0 . = dismantle() /obj/structure/machinery/proc/default_deconstruction_screwdriver(var/mob/user, var/obj/item/S) if(!istype(S) || S.tool_behaviour != TOOL_SCREWDRIVER) return FALSE S.play_tool_sound(get_turf(src), 50) panel_open = !panel_open to_chat(user, SPAN_NOTICE("You [panel_open ? "open" : "close"] the maintenance hatch of [src].")) update_icon() return TRUE /obj/structure/machinery/proc/default_part_replacement(var/mob/user, var/obj/item/storage/part_replacer/R) if(!LAZYLEN(component_parts)) return FALSE else if(istype(R)) var/parts_replaced = FALSE if(panel_open) var/obj/item/circuitboard/CB = locate(/obj/item/circuitboard) in component_parts var/P for(var/obj/item/reagent_containers/glass/G in component_parts) for(var/D in CB.req_components) var/T = text2path(D) if(ispath(G.type, T)) P = T break for(var/obj/item/reagent_containers/glass/B in R.contents) if(B.reagents && B.reagents.total_volume > 0) continue if(istype(B, P) && istype(G, P)) if(B.volume > G.volume) R.remove_from_storage(B, src) R.handle_item_insertion(G, 1) component_parts -= G component_parts += B B.forceMove(src) to_chat(user, SPAN_NOTICE("[G.name] replaced with [B.name].")) break for(var/obj/item/stock_parts/A in component_parts) for(var/D in CB.req_components) var/T = text2path(D) if(ispath(A.type, T)) P = T break for(var/obj/item/stock_parts/B in R.contents) if(istype(B, P) && istype(A, P)) if(B.rating > A.rating) R.remove_from_storage(B, src) R.handle_item_insertion(A, 1) component_parts -= A component_parts += B B.forceMove(src) to_chat(user, SPAN_NOTICE("[A.name] replaced with [B.name].")) parts_replaced = TRUE break RefreshParts() update_icon() if(parts_replaced) //only play sound when RPED actually replaces parts playsound(src, 'sound/items/rped.ogg', 40, TRUE) return TRUE else to_chat(user, SPAN_NOTICE("The following parts have been detected in \the [src]:")) to_chat(user, counting_english_list(component_parts)) else return FALSE /obj/structure/machinery/dismantle() playsound(loc, SFX_CROWBAR, 50, 1) var/obj/structure/machinery/constructable_frame/machine_frame/M = new /obj/structure/machinery/constructable_frame/machine_frame(loc) M.set_dir(src.dir) M.state = 3 M.icon_state = "blueprint_1" for(var/obj/I in component_parts) I.forceMove(loc) component_parts -= I qdel(src) return TRUE /obj/structure/machinery/proc/print(var/obj/paper, var/play_sound = 1, var/print_sfx = SFX_PRINT, var/print_delay = 10, var/message, var/mob/user) if( printing ) return FALSE printing = TRUE if (play_sound) playsound(src.loc, print_sfx, 50, 1) if(!message) message = "\The [src] rattles to life and spits out a paper titled [paper]." visible_message(SPAN_NOTICE(message)) addtimer(CALLBACK(src, PROC_REF(print_move_paper), paper, user), print_delay) return TRUE /obj/structure/machinery/proc/print_move_paper(obj/paper, mob/user) if(user && ishuman(user) && user.Adjacent(src)) user.put_in_hands(paper) else paper.forceMove(loc) printing = FALSE /obj/structure/machinery/proc/do_hair_pull(mob/living/carbon/human/H) if(stat & (NOPOWER|BROKEN)) return if(!istype(H)) return //for whatever reason, skrell's tentacles have a really long length //horns would not get caught in the machine //vaurca have fine control of their antennae if(isskrell(H) || isunathi(H) || isvaurca(H)) return var/datum/sprite_accessory/hair/hair_style = GLOB.hair_styles_list[H.h_style] for(var/obj/item/protection in list(H.head)) if(protection && (protection.flags_inv & BLOCKHAIR|BLOCKHEADHAIR)) return if(hair_style.length >= 4 && prob(25)) H.apply_damage(30, DAMAGE_BRUTE, BP_HEAD) H.visible_message(SPAN_DANGER("\The [H]'s hair catches in \the [src]!"), SPAN_DANGER("Your hair gets caught in \the [src]!")) if(H.can_feel_pain()) H.emote("scream") H.apply_damage(45, DAMAGE_PAIN) // A late init operation called in SSshuttle for ship computers and holopads, used to attach the thing to the right ship. /obj/structure/machinery/proc/attempt_hook_up(var/obj/effect/overmap/visitable/sector) SHOULD_CALL_PARENT(TRUE) if(!istype(sector)) return FALSE if(sector.check_ownership(src)) linked = sector return TRUE return FALSE /obj/structure/machinery/proc/sync_linked() var/obj/effect/overmap/visitable/sector = GLOB.map_sectors["[z]"] if(!sector) return return attempt_hook_up_recursive(sector) /obj/structure/machinery/proc/attempt_hook_up_recursive(var/obj/effect/overmap/visitable/sector) if(attempt_hook_up(sector)) return sector for(var/obj/effect/overmap/visitable/candidate in sector) if((. = .(candidate))) return /obj/proc/on_user_login(mob/M) return /obj/structure/machinery/proc/set_emergency_state(var/new_security_level) return /obj/structure/machinery/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) . = ..() if(isliving(hitting_atom)) var/mob/living/M = hitting_atom M.turf_collision(src, throwingdatum.speed) return else visible_message(SPAN_DANGER("\The [src] was hit by \the [hitting_atom].")) /obj/structure/machinery/ui_status(mob/user, datum/ui_state/state) . = ..() if(. < UI_INTERACTIVE) if(user?.machine) user.unset_machine()