/* 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/machine_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 == machine_stat) return . = machine_stat machine_stat = new_value on_machine_stat_update(machine_stat) /obj/machinery/proc/on_machine_stat_update(stat) return /obj/machinery/emp_act(severity) . = ..() if(use_power && !machine_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 !(machine_stat & (NOPOWER|BROKEN|MAINT)) /obj/machinery/can_interact(mob/user) if((machine_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("[user.name] smashes against \the [src.name] with its paws.", 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("[usr] pries open \the [src].", "You pry open \the [src].") 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(!(machine_stat & BROKEN) && !(flags_1 & NODECONSTRUCT_1)) machine_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, "You open the maintenance hatch of [src].") else panel_open = FALSE icon_state = icon_state_closed to_chat(user, "You close the maintenance hatch of [src].") 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, "You rotate [src].") 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, "[src] needs to be on the floor to be secured!") 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, "You begin [anchored ? "un" : ""]securing [src]...") 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, "You [anchored ? "un" : ""]secure [src].") 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, "[capitalize(A.name)] replaced with [B.name].") 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() . += "It contains the following parts:" for(var/obj/item/C in component_parts) . += "[icon2html(C, user)] \A [C]." . = jointext(., "") /obj/machinery/examine(mob/user) . = ..() if(machine_stat & BROKEN) . += "It looks broken and non-functional." if(!(resistance_flags & INDESTRUCTIBLE)) if(resistance_flags & ON_FIRE) . += "It's on fire!" 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) . += "It's falling apart!" 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 = "Network Alert: Hacking attempt detected[get_area(src)?" in [get_area_name(src, TRUE)]":". Unable to pinpoint location"]." for(var/mob/living/silicon/ai/AI in GLOB.player_list) to_chat(AI, alertstr)