From b0779fe2d7b4e06165359235f7e31a9a3cbd8f07 Mon Sep 17 00:00:00 2001 From: Lin Date: Tue, 10 Dec 2019 23:52:31 +0000 Subject: [PATCH] Merge pull request #9983 from Ghommie/Ghommie-cit408 refactoring altclick interaction to allow parent calls without forcing the turf contents stat menu open. --- code/_onclick/ai.dm | 11 +- code/_onclick/click.dm | 1038 ++++---- code/_onclick/cyborg.dm | 350 ++- code/_onclick/hud/action_button.dm | 1 + code/_onclick/observer.dm | 166 +- code/datums/components/rotation.dm | 1 + code/datums/components/storage/storage.dm | 1557 +++++------ code/game/machinery/Sleeper.dm | 2 + code/game/machinery/aug_manipulator.dm | 4 +- .../game/machinery/computer/buildandrepair.dm | 287 +- code/game/machinery/computer/card.dm | 1258 ++++----- .../machinery/computer/prisoner/_prisoner.dm | 5 +- code/game/machinery/defibrillator_mount.dm | 2 + code/game/machinery/deployable.dm | 2 + code/game/machinery/dish_drive.dm | 2 + code/game/machinery/dna_scanner.dm | 7 + code/game/machinery/harvester.dm | 2 + code/game/machinery/washing_machine.dm | 566 ++-- code/game/mecha/mecha_actions.dm | 2 + code/game/objects/items/cards_ids.dm | 1123 ++++---- .../circuitboards/machine_circuitboards.dm | 2 + code/game/objects/items/devices/PDA/PDA.dm | 2346 ++++++++--------- .../objects/items/devices/desynchronizer.dm | 91 + .../objects/items/devices/geiger_counter.dm | 6 +- code/game/objects/items/devices/gps.dm | 450 ++-- .../objects/items/devices/quantum_keycard.dm | 2 + .../objects/items/devices/radio/headset.dm | 668 ++--- code/game/objects/items/devices/scanners.dm | 4 +- code/game/objects/items/flamethrower.dm | 2 + code/game/objects/items/pet_carrier.dm | 2 + code/game/objects/items/stacks/stack.dm | 4 +- code/game/objects/items/storage/belt.dm | 12 +- code/game/objects/items/storage/fancy.dm | 779 +++--- code/game/objects/items/storage/lockbox.dm | 397 +-- code/game/objects/items/twohanded.dm | 2 + code/game/objects/objs.dm | 505 ++-- .../objects/structures/beds_chairs/chair.dm | 5 + .../structures/crates_lockers/closets.dm | 1233 ++++----- code/game/objects/structures/extinguisher.dm | 310 +-- code/game/objects/structures/morgue.dm | 889 +++---- code/game/objects/structures/reflector.dm | 2 + code/modules/admin/sound_emitter.dm | 2 + .../clockcult/clock_items/clockwork_slab.dm | 2 + .../revenant/revenant_abilities.dm | 2 +- .../atmospherics/machinery/airalarm.dm | 6 +- .../atmospherics/machinery/atmosmachinery.dm | 681 +++-- .../components/binary_devices/pump.dm | 2 + .../components/trinary_devices/mixer.dm | 2 + .../components/unary_devices/cryo.dm | 876 +++--- .../components/unary_devices/thermomachine.dm | 6 +- .../portable/portable_atmospherics.dm | 310 +-- code/modules/cargo/supplypod_beacon.dm | 2 + code/modules/clothing/clothing.dm | 12 +- code/modules/clothing/glasses/_glasses.dm | 884 ++++--- code/modules/clothing/head/hardhat.dm | 318 +-- code/modules/clothing/head/jobs.dm | 594 ++--- code/modules/clothing/head/soft_caps.dm | 284 +- code/modules/clothing/masks/breath.dm | 84 +- code/modules/clothing/spacesuits/hardsuit.dm | 123 + code/modules/clothing/suits/toggles.dm | 6 +- code/modules/clothing/under/accessories.dm | 2 + code/modules/detectivework/scanner.dm | 436 +-- .../kitchen_machinery/microwave.dm | 2 + code/modules/games/cas.dm | 2 + code/modules/mining/abandoned_crates.dm | 7 +- .../mining/equipment/marker_beacons.dm | 4 +- .../simple_animal/guardian/types/explosive.dm | 1 + .../simple_animal/guardian/types/support.dm | 3 +- .../simple_animal/hostile/megafauna/drake.dm | 1 + .../hostile/megafauna/hierophant.dm | 2 +- code/modules/mob/living/ventcrawling.dm | 1 + .../computers/item/computer.dm | 4 +- .../computers/item/laptop.dm | 4 +- .../computers/machinery/modular_computer.dm | 3 +- code/modules/paperwork/paperplane.dm | 2 + code/modules/photography/camera/camera.dm | 429 +-- code/modules/power/apc.dm | 6 +- .../projectiles/guns/ballistic/shotgun.dm | 5 +- .../chemistry/machinery/chem_dispenser.dm | 3 +- .../chemistry/machinery/chem_heater.dm | 3 +- .../reagents/chemistry/machinery/pandemic.dm | 3 +- .../reagents/reagent_containers/hypospray.dm | 2 + .../reagents/reagent_containers/rags.dm | 28 +- .../research/nanites/nanite_hijacker.dm | 1 + .../modules/research/nanites/nanite_remote.dm | 1 + .../ruins/spaceruin_code/hilbertshotel.dm | 17 +- code/modules/vehicles/ridden.dm | 157 +- code/modules/vehicles/scooter.dm | 2 + .../code/modules/arousal/toys/dildos.dm | 10 +- .../code/modules/clothing/clothing.dm | 3 +- .../clothing/spacesuits/cydonian_armor.dm | 176 -- .../living/silicon/robot/dogborg_equipment.dm | 765 +++--- .../projectiles/guns/ballistic/rifles.dm | 3 + .../projectiles/guns/energy/energy_gun.dm | 2 + .../modules/projectiles/guns/pumpenergy.dm | 12 +- tgstation.dme | 1 - 96 files changed, 10279 insertions(+), 10117 deletions(-) create mode 100644 code/game/objects/items/devices/desynchronizer.dm delete mode 100644 modular_citadel/code/modules/clothing/spacesuits/cydonian_armor.dm diff --git a/code/_onclick/ai.dm b/code/_onclick/ai.dm index 7a495b95..2a828969 100644 --- a/code/_onclick/ai.dm +++ b/code/_onclick/ai.dm @@ -114,7 +114,8 @@ /mob/living/silicon/ai/CtrlClickOn(var/atom/A) A.AICtrlClick(src) /mob/living/silicon/ai/AltClickOn(var/atom/A) - A.AIAltClick(src) + if(!A.AIAltClick(src)) + altclick_listed_turf(A) /* The following criminally helpful code is just the previous code cleaned up; @@ -125,9 +126,10 @@ /* Atom Procs */ /atom/proc/AICtrlClick() return + /atom/proc/AIAltClick(mob/living/silicon/ai/user) - AltClick(user) - return + return AltClick(user) + /atom/proc/AIShiftClick() return /atom/proc/AICtrlShiftClick() @@ -151,6 +153,7 @@ shock_perm(usr) else shock_restore(usr) + return TRUE /obj/machinery/door/airlock/AIShiftClick() // Opens and closes doors! if(obj_flags & EMAGGED) @@ -185,10 +188,12 @@ return toggle_on() add_fingerprint(usr) + return TRUE /* Holopads */ /obj/machinery/holopad/AIAltClick(mob/living/silicon/ai/user) hangup_all_calls() + return TRUE // // Override TurfAdjacent for AltClicking diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index d384c76e..46ae8e39 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -1,520 +1,518 @@ -/* - Click code cleanup - ~Sayu -*/ - -// 1 decisecond click delay (above and beyond mob/next_move) -//This is mainly modified by click code, to modify click delays elsewhere, use next_move and changeNext_move() -/mob/var/next_click = 0 - -// THESE DO NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK -/mob/var/next_move_adjust = 0 //Amount to adjust action/click delays by, + or - -/mob/var/next_move_modifier = 1 //Value to multiply action/click delays by - - -//Delays the mob's next click/action by num deciseconds -// eg: 10-3 = 7 deciseconds of delay -// eg: 10*0.5 = 5 deciseconds of delay -// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK - -/mob/proc/changeNext_move(num) - next_move = world.time + ((num+next_move_adjust)*next_move_modifier) - -/mob/living/changeNext_move(num) - var/mod = next_move_modifier - var/adj = next_move_adjust - for(var/i in status_effects) - var/datum/status_effect/S = i - mod *= S.nextmove_modifier() - adj += S.nextmove_adjust() - next_move = world.time + ((num + adj)*mod) - -/* - Before anything else, defer these calls to a per-mobtype handler. This allows us to - remove istype() spaghetti code, but requires the addition of other handler procs to simplify it. - - Alternately, you could hardcode every mob's variation in a flat ClickOn() proc; however, - that's a lot of code duplication and is hard to maintain. - - Note that this proc can be overridden, and is in the case of screen objects. -*/ -/atom/Click(location,control,params) - if(flags_1 & INITIALIZED_1) - SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr) - usr.ClickOn(src, params) - -/atom/DblClick(location,control,params) - if(flags_1 & INITIALIZED_1) - usr.DblClickOn(src,params) - -/atom/MouseWheel(delta_x,delta_y,location,control,params) - if(flags_1 & INITIALIZED_1) - usr.MouseWheelOn(src, delta_x, delta_y, params) - -/* - Standard mob ClickOn() - Handles exceptions: Buildmode, middle click, modified clicks, mech actions - - After that, mostly just check your state, check whether you're holding an item, - check whether you're adjacent to the target, then pass off the click to whoever - is receiving it. - The most common are: - * mob/UnarmedAttack(atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves - * atom/attackby(item,user) - used only when adjacent - * item/afterattack(atom,user,adjacent,params) - used both ranged and adjacent - * mob/RangedAttack(atom,params) - used only ranged, only used for tk and laser eyes but could be changed -*/ -/mob/proc/ClickOn( atom/A, params ) - if(world.time <= next_click) - return - next_click = world.time + 1 - - if(check_click_intercept(params,A)) - return - - if(notransform) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"] && (client && client.show_popup_menus || modifiers["right"])) //CIT CHANGE - makes shift-click examine use right click instead of left click in combat mode - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(modifiers["right"]) //CIT CHANGE - allows right clicking to perform actions - RightClickOn(A,params) //CIT CHANGE - ditto - return //CIT CHANGE - ditto - - if(incapacitated(ignore_restraints = 1)) - return - - face_atom(A) - - if(next_move > world.time) // in the year 2000... - return - - if(!modifiers["catcher"] && A.IsObscured()) - return - - if(ismecha(loc)) - var/obj/mecha/M = loc - return M.click_action(A,src,params) - - if(restrained()) - changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow - RestrainedClickOn(A) - return - - if(in_throw_mode) - throw_item(A) - return - - var/obj/item/W = get_active_held_item() - - if(W == A) - W.attack_self(src) - update_inv_hands() - return - - //These are always reachable. - //User itself, current loc, and user inventory - if(A in DirectAccess()) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A) - return - - //Can't reach anything else in lockers or other weirdness - if(!loc.AllowClick()) - return - - //Standard reach turf to turf or reaching inside storage - if(CanReach(A,W)) - if(W) - W.melee_attack_chain(src, A, params) - else - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - UnarmedAttack(A,1) - else - if(W) - W.afterattack(A,src,0,params) - else - RangedAttack(A,params) - -//Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it -/atom/proc/IsObscured() - if(!isturf(loc)) //This only makes sense for things directly on turfs for now - return FALSE - var/turf/T = get_turf_pixel(src) - if(!T) - return FALSE - for(var/atom/movable/AM in T) - if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density && AM.layer > layer) - return TRUE - return FALSE - -/turf/IsObscured() - for(var/atom/movable/AM in src) - if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density) - return TRUE - return FALSE - -/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) - // A backwards depth-limited breadth-first-search to see if the target is - // logically "in" anything adjacent to us. - var/list/direct_access = DirectAccess() - var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH) - - var/list/closed = list() - var/list/checking = list(ultimate_target) - while (checking.len && depth > 0) - var/list/next = list() - --depth - - for(var/atom/target in checking) // will filter out nulls - if(closed[target] || isarea(target)) // avoid infinity situations - continue - closed[target] = TRUE - if(isturf(target) || isturf(target.loc) || (target in direct_access)) //Directly accessible atoms - if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks - return TRUE - - if (!target.loc) - continue - - if(!(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_BLOCK_REACH)) - next += target.loc - - checking = next - return FALSE - -/atom/movable/proc/DirectAccess() - return list(src, loc) - -/mob/DirectAccess(atom/target) - return ..() + contents - -/mob/living/DirectAccess(atom/target) - return ..() + GetAllContents() - -/atom/proc/AllowClick() - return FALSE - -/turf/AllowClick() - return TRUE - -/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) - if(!here || !there) - return - switch(reach) - if(0) - return FALSE - if(1) - return FALSE //here.Adjacent(there) - if(2 to INFINITY) - var/obj/dummy = new(get_turf(here)) - dummy.pass_flags |= PASSTABLE - dummy.invisibility = INVISIBILITY_ABSTRACT - for(var/i in 1 to reach) //Limit it to that many tries - var/turf/T = get_step(dummy, get_dir(dummy, there)) - if(dummy.CanReach(there)) - qdel(dummy) - return TRUE - if(!dummy.Move(T)) //we're blocked! - qdel(dummy) - return - qdel(dummy) - -// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) -/mob/proc/DblClickOn(atom/A, params) - return - - -/* - Translates into attack_hand, etc. - - Note: proximity_flag here is used to distinguish between normal usage (flag=1), - and usage when clicking on things telekinetically (flag=0). This proc will - not be called at ranged except with telekinesis. - - proximity_flag is not currently passed to attack_hand, and is instead used - in human click code to allow glove touches only at melee range. -*/ -/mob/proc/UnarmedAttack(atom/A, proximity_flag) - if(ismob(A)) - changeNext_move(CLICK_CD_MELEE) - return - -/* - Ranged unarmed attack: - - This currently is just a default for all mobs, involving - laser eyes and telekinesis. You could easily add exceptions - for things like ranged glove touches, spitting alien acid/neurotoxin, - animals lunging, etc. -*/ -/mob/proc/RangedAttack(atom/A, params) - SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) -/* - Restrained ClickOn - - Used when you are handcuffed and click things. - Not currently used by anything but could easily be. -*/ -/mob/proc/RestrainedClickOn(atom/A) - return - -/* - Middle click - Only used for swapping hands -*/ -/mob/proc/MiddleClickOn(atom/A) - return - -/mob/living/carbon/MiddleClickOn(atom/A) - if(!stat && mind && iscarbon(A) && A != src) - var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) - if(C && C.chosen_sting) - C.chosen_sting.try_to_sting(src,A) - next_click = world.time + 5 - return - swap_hand() - -/mob/living/simple_animal/drone/MiddleClickOn(atom/A) - swap_hand() - -// In case of use break glass -/* -/atom/proc/MiddleClick(mob/M as mob) - return -*/ - -/* - Shift click - For most mobs, examine. - This is overridden in ai.dm -*/ -/mob/proc/ShiftClickOn(atom/A) - A.ShiftClick(src) - return - -/atom/proc/ShiftClick(mob/user) - var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) - if(!(flags & COMPONENT_DENY_EXAMINATE) && user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE)) - user.examinate(src) - return - -/* - Ctrl click - For most objects, pull -*/ - -/mob/proc/CtrlClickOn(atom/A) - A.CtrlClick(src) - return - -/atom/proc/CtrlClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) - var/mob/living/ML = user - if(istype(ML)) - ML.pulled(src) - -/mob/living/carbon/human/CtrlClick(mob/user) - if(ishuman(user) && Adjacent(user) && !user.incapacitated()) - if(world.time < user.next_move) - return FALSE - var/mob/living/carbon/human/H = user - H.dna.species.grab(H, src, H.mind.martial_art) - H.changeNext_move(CLICK_CD_MELEE) - else - ..() -/* - Alt click - Unused except for AI -*/ -/mob/proc/AltClickOn(atom/A) - A.AltClick(src) - return - -/mob/living/carbon/AltClickOn(atom/A) - if(!stat && mind && iscarbon(A) && A != src) - var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) - if(C && C.chosen_sting) - C.chosen_sting.try_to_sting(src,A) - next_click = world.time + 5 - return - ..() - -/atom/proc/AltClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) - var/turf/T = get_turf(src) - if(T && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -// Use this instead of /mob/proc/AltClickOn(atom/A) where you only want turf content listing without additional atom alt-click interaction -/atom/proc/AltClickNoInteract(mob/user, atom/A) - var/turf/T = get_turf(A) - if(T && user.TurfAdjacent(T)) - user.listed_turf = T - user.client.statpanel = T.name - -/mob/proc/TurfAdjacent(turf/T) - return T.Adjacent(src) - -/* - Control+Shift click - Unused except for AI -*/ -/mob/proc/CtrlShiftClickOn(atom/A) - A.CtrlShiftClick(src) - return - -/mob/proc/ShiftMiddleClickOn(atom/A) - src.pointed(A) - return - -/atom/proc/CtrlShiftClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) - return - -/* - Misc helpers - - Laser Eyes: as the name implies, handles this since nothing else does currently - face_atom: turns the mob towards what you clicked on -*/ -/mob/proc/LaserEyes(atom/A, params) - return - -/mob/living/LaserEyes(atom/A, params) - changeNext_move(CLICK_CD_RANGE) - - var/obj/item/projectile/beam/LE = new /obj/item/projectile/beam(loc) - LE.icon = 'icons/effects/genetics.dmi' - LE.icon_state = "eyelasers" - playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1) - - LE.firer = src - LE.def_zone = get_organ_target() - LE.preparePixelProjectile(A, src, params) - LE.fire() - -// Simple helper to face what you clicked on, in case it should be needed in more than one place -/mob/proc/face_atom(atom/A) - if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y ) - return - var/dx = A.x - x - var/dy = A.y - y - if(!dx && !dy) // Wall items are graphically shifted but on the floor - if(A.pixel_y > 16) - setDir(NORTH) - else if(A.pixel_y < -16) - setDir(SOUTH) - else if(A.pixel_x > 16) - setDir(EAST) - else if(A.pixel_x < -16) - setDir(WEST) - return - - if(abs(dx) < abs(dy)) - if(dy > 0) - setDir(NORTH) - else - setDir(SOUTH) - else - if(dx > 0) - setDir(EAST) - else - setDir(WEST) - -//debug -/obj/screen/proc/scale_to(x1,y1) - if(!y1) - y1 = x1 - var/matrix/M = new - M.Scale(x1,y1) - transform = M - -/obj/screen/click_catcher - icon = 'icons/mob/screen_gen.dmi' - icon_state = "catcher" - plane = CLICKCATCHER_PLANE - mouse_opacity = MOUSE_OPACITY_OPAQUE - screen_loc = "CENTER" - -#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size) -#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose. - -/obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15) - var/icon/newicon = icon('icons/mob/screen_gen.dmi', "catcher") - var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x) - var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y) - var/px = view_size_x * world.icon_size - var/py = view_size_y * world.icon_size - var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px) - var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py) - newicon.Scale(sx, sy) - icon = newicon - screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]" - var/matrix/M = new - M.Scale(px/sx, py/sy) - transform = M - -/obj/screen/click_catcher/Click(location, control, params) - var/list/modifiers = params2list(params) - if(modifiers["middle"] && iscarbon(usr)) - var/mob/living/carbon/C = usr - C.swap_hand() - else - var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client) - params += "&catcher=1" - if(T) - T.Click(location, control, params) - . = 1 - -/* MouseWheelOn */ - -/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) - return - -/mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params) - var/list/modifier = params2list(params) - if(modifier["shift"]) - var/view = 0 - if(delta_y > 0) - view = -1 - else - view = 1 - add_view_range(view) - -/mob/proc/check_click_intercept(params,A) - //Client level intercept - if(client && client.click_intercept) - if(call(client.click_intercept, "InterceptClickOn")(src, params, A)) - return TRUE - - //Mob level intercept - if(click_intercept) - if(call(click_intercept, "InterceptClickOn")(src, params, A)) - return TRUE - - return FALSE +/* + Click code cleanup + ~Sayu +*/ + +// 1 decisecond click delay (above and beyond mob/next_move) +//This is mainly modified by click code, to modify click delays elsewhere, use next_move and changeNext_move() +/mob/var/next_click = 0 + +// THESE DO NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK +/mob/var/next_move_adjust = 0 //Amount to adjust action/click delays by, + or - +/mob/var/next_move_modifier = 1 //Value to multiply action/click delays by + + +//Delays the mob's next click/action by num deciseconds +// eg: 10-3 = 7 deciseconds of delay +// eg: 10*0.5 = 5 deciseconds of delay +// DOES NOT EFFECT THE BASE 1 DECISECOND DELAY OF NEXT_CLICK + +/mob/proc/changeNext_move(num) + next_move = world.time + ((num+next_move_adjust)*next_move_modifier) + +/mob/living/changeNext_move(num) + var/mod = next_move_modifier + var/adj = next_move_adjust + for(var/i in status_effects) + var/datum/status_effect/S = i + mod *= S.nextmove_modifier() + adj += S.nextmove_adjust() + next_move = world.time + ((num + adj)*mod) + +/* + Before anything else, defer these calls to a per-mobtype handler. This allows us to + remove istype() spaghetti code, but requires the addition of other handler procs to simplify it. + + Alternately, you could hardcode every mob's variation in a flat ClickOn() proc; however, + that's a lot of code duplication and is hard to maintain. + + Note that this proc can be overridden, and is in the case of screen objects. +*/ +/atom/Click(location,control,params) + if(flags_1 & INITIALIZED_1) + SEND_SIGNAL(src, COMSIG_CLICK, location, control, params, usr) + usr.ClickOn(src, params) + +/atom/DblClick(location,control,params) + if(flags_1 & INITIALIZED_1) + usr.DblClickOn(src,params) + +/atom/MouseWheel(delta_x,delta_y,location,control,params) + if(flags_1 & INITIALIZED_1) + usr.MouseWheelOn(src, delta_x, delta_y, params) + +/* + Standard mob ClickOn() + Handles exceptions: Buildmode, middle click, modified clicks, mech actions + + After that, mostly just check your state, check whether you're holding an item, + check whether you're adjacent to the target, then pass off the click to whoever + is receiving it. + The most common are: + * mob/UnarmedAttack(atom,adjacent) - used here only when adjacent, with no item in hand; in the case of humans, checks gloves + * atom/attackby(item,user) - used only when adjacent + * item/afterattack(atom,user,adjacent,params) - used both ranged and adjacent + * mob/RangedAttack(atom,params) - used only ranged, only used for tk and laser eyes but could be changed +*/ +/mob/proc/ClickOn( atom/A, params ) + if(world.time <= next_click) + return + next_click = world.time + 1 + + if(check_click_intercept(params,A)) + return + + if(notransform) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"] && (client && client.show_popup_menus || modifiers["right"])) //CIT CHANGE - makes shift-click examine use right click instead of left click in combat mode + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(modifiers["right"]) //CIT CHANGE - allows right clicking to perform actions + RightClickOn(A,params) //CIT CHANGE - ditto + return //CIT CHANGE - ditto + + if(incapacitated(ignore_restraints = 1)) + return + + face_atom(A) + + if(next_move > world.time) // in the year 2000... + return + + if(!modifiers["catcher"] && A.IsObscured()) + return + + if(ismecha(loc)) + var/obj/mecha/M = loc + return M.click_action(A,src,params) + + if(restrained()) + changeNext_move(CLICK_CD_HANDCUFFED) //Doing shit in cuffs shall be vey slow + RestrainedClickOn(A) + return + + if(in_throw_mode) + throw_item(A) + return + + var/obj/item/W = get_active_held_item() + + if(W == A) + W.attack_self(src) + update_inv_hands() + return + + //These are always reachable. + //User itself, current loc, and user inventory + if(A in DirectAccess()) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A) + return + + //Can't reach anything else in lockers or other weirdness + if(!loc.AllowClick()) + return + + //Standard reach turf to turf or reaching inside storage + if(CanReach(A,W)) + if(W) + W.melee_attack_chain(src, A, params) + else + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + UnarmedAttack(A,1) + else + if(W) + W.afterattack(A,src,0,params) + else + RangedAttack(A,params) + +//Is the atom obscured by a PREVENT_CLICK_UNDER_1 object above it +/atom/proc/IsObscured() + if(!isturf(loc)) //This only makes sense for things directly on turfs for now + return FALSE + var/turf/T = get_turf_pixel(src) + if(!T) + return FALSE + for(var/atom/movable/AM in T) + if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density && AM.layer > layer) + return TRUE + return FALSE + +/turf/IsObscured() + for(var/atom/movable/AM in src) + if(AM.flags_1 & PREVENT_CLICK_UNDER_1 && AM.density) + return TRUE + return FALSE + +/atom/movable/proc/CanReach(atom/ultimate_target, obj/item/tool, view_only = FALSE) + // A backwards depth-limited breadth-first-search to see if the target is + // logically "in" anything adjacent to us. + var/list/direct_access = DirectAccess() + var/depth = 1 + (view_only ? STORAGE_VIEW_DEPTH : INVENTORY_DEPTH) + + var/list/closed = list() + var/list/checking = list(ultimate_target) + while (checking.len && depth > 0) + var/list/next = list() + --depth + + for(var/atom/target in checking) // will filter out nulls + if(closed[target] || isarea(target)) // avoid infinity situations + continue + closed[target] = TRUE + if(isturf(target) || isturf(target.loc) || (target in direct_access)) //Directly accessible atoms + if(Adjacent(target) || (tool && CheckToolReach(src, target, tool.reach))) //Adjacent or reaching attacks + return TRUE + + if (!target.loc) + continue + + if(!(SEND_SIGNAL(target.loc, COMSIG_ATOM_CANREACH, next) & COMPONENT_BLOCK_REACH)) + next += target.loc + + checking = next + return FALSE + +/atom/movable/proc/DirectAccess() + return list(src, loc) + +/mob/DirectAccess(atom/target) + return ..() + contents + +/mob/living/DirectAccess(atom/target) + return ..() + GetAllContents() + +/atom/proc/AllowClick() + return FALSE + +/turf/AllowClick() + return TRUE + +/proc/CheckToolReach(atom/movable/here, atom/movable/there, reach) + if(!here || !there) + return + switch(reach) + if(0) + return FALSE + if(1) + return FALSE //here.Adjacent(there) + if(2 to INFINITY) + var/obj/dummy = new(get_turf(here)) + dummy.pass_flags |= PASSTABLE + dummy.invisibility = INVISIBILITY_ABSTRACT + for(var/i in 1 to reach) //Limit it to that many tries + var/turf/T = get_step(dummy, get_dir(dummy, there)) + if(dummy.CanReach(there)) + qdel(dummy) + return TRUE + if(!dummy.Move(T)) //we're blocked! + qdel(dummy) + return + qdel(dummy) + +// Default behavior: ignore double clicks (the second click that makes the doubleclick call already calls for a normal click) +/mob/proc/DblClickOn(atom/A, params) + return + + +/* + Translates into attack_hand, etc. + + Note: proximity_flag here is used to distinguish between normal usage (flag=1), + and usage when clicking on things telekinetically (flag=0). This proc will + not be called at ranged except with telekinesis. + + proximity_flag is not currently passed to attack_hand, and is instead used + in human click code to allow glove touches only at melee range. +*/ +/mob/proc/UnarmedAttack(atom/A, proximity_flag) + if(ismob(A)) + changeNext_move(CLICK_CD_MELEE) + return + +/* + Ranged unarmed attack: + + This currently is just a default for all mobs, involving + laser eyes and telekinesis. You could easily add exceptions + for things like ranged glove touches, spitting alien acid/neurotoxin, + animals lunging, etc. +*/ +/mob/proc/RangedAttack(atom/A, params) + SEND_SIGNAL(src, COMSIG_MOB_ATTACK_RANGED, A, params) +/* + Restrained ClickOn + + Used when you are handcuffed and click things. + Not currently used by anything but could easily be. +*/ +/mob/proc/RestrainedClickOn(atom/A) + return + +/* + Middle click + Only used for swapping hands +*/ +/mob/proc/MiddleClickOn(atom/A) + return + +/mob/living/carbon/MiddleClickOn(atom/A) + if(!stat && mind && iscarbon(A) && A != src) + var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) + if(C && C.chosen_sting) + C.chosen_sting.try_to_sting(src,A) + next_click = world.time + 5 + return + swap_hand() + +/mob/living/simple_animal/drone/MiddleClickOn(atom/A) + swap_hand() + +// In case of use break glass +/* +/atom/proc/MiddleClick(mob/M as mob) + return +*/ + +/* + Shift click + For most mobs, examine. + This is overridden in ai.dm +*/ +/mob/proc/ShiftClickOn(atom/A) + A.ShiftClick(src) + return + +/atom/proc/ShiftClick(mob/user) + var/flags = SEND_SIGNAL(src, COMSIG_CLICK_SHIFT, user) + if(!(flags & COMPONENT_DENY_EXAMINATE) && user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE)) + user.examinate(src) + return + +/* + Ctrl click + For most objects, pull +*/ + +/mob/proc/CtrlClickOn(atom/A) + A.CtrlClick(src) + return + +/atom/proc/CtrlClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_CTRL, user) + var/mob/living/ML = user + if(istype(ML)) + ML.pulled(src) + +/mob/living/carbon/human/CtrlClick(mob/user) + if(ishuman(user) && Adjacent(user) && !user.incapacitated()) + if(world.time < user.next_move) + return FALSE + var/mob/living/carbon/human/H = user + H.dna.species.grab(H, src, H.mind.martial_art) + H.changeNext_move(CLICK_CD_MELEE) + else + ..() +/* + Alt click + Unused except for AI +*/ +/mob/proc/AltClickOn(atom/A) + if(!A.AltClick(src)) + altclick_listed_turf(A) + +/mob/proc/altclick_listed_turf(atom/A) + var/turf/T = get_turf(A) + if(T == A.loc || T == A) + if(T == listed_turf) + listed_turf = null + else if(TurfAdjacent(T)) + listed_turf = T + client.statpanel = T.name + +/mob/living/carbon/AltClickOn(atom/A) + if(!stat && mind && iscarbon(A) && A != src) + var/datum/antagonist/changeling/C = mind.has_antag_datum(/datum/antagonist/changeling) + if(C && C.chosen_sting) + C.chosen_sting.try_to_sting(src,A) + next_click = world.time + 5 + return + ..() + +/atom/proc/AltClick(mob/user) + . = SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) + +/mob/proc/TurfAdjacent(turf/T) + return T.Adjacent(src) + +/* + Control+Shift click + Unused except for AI +*/ +/mob/proc/CtrlShiftClickOn(atom/A) + A.CtrlShiftClick(src) + return + +/mob/proc/ShiftMiddleClickOn(atom/A) + src.pointed(A) + return + +/atom/proc/CtrlShiftClick(mob/user) + SEND_SIGNAL(src, COMSIG_CLICK_CTRL_SHIFT) + return + +/* + Misc helpers + + Laser Eyes: as the name implies, handles this since nothing else does currently + face_atom: turns the mob towards what you clicked on +*/ +/mob/proc/LaserEyes(atom/A, params) + return + +/mob/living/LaserEyes(atom/A, params) + changeNext_move(CLICK_CD_RANGE) + + var/obj/item/projectile/beam/LE = new /obj/item/projectile/beam(loc) + LE.icon = 'icons/effects/genetics.dmi' + LE.icon_state = "eyelasers" + playsound(usr.loc, 'sound/weapons/taser2.ogg', 75, 1) + + LE.firer = src + LE.def_zone = get_organ_target() + LE.preparePixelProjectile(A, src, params) + LE.fire() + +// Simple helper to face what you clicked on, in case it should be needed in more than one place +/mob/proc/face_atom(atom/A) + if( buckled || stat != CONSCIOUS || !A || !x || !y || !A.x || !A.y ) + return + var/dx = A.x - x + var/dy = A.y - y + if(!dx && !dy) // Wall items are graphically shifted but on the floor + if(A.pixel_y > 16) + setDir(NORTH) + else if(A.pixel_y < -16) + setDir(SOUTH) + else if(A.pixel_x > 16) + setDir(EAST) + else if(A.pixel_x < -16) + setDir(WEST) + return + + if(abs(dx) < abs(dy)) + if(dy > 0) + setDir(NORTH) + else + setDir(SOUTH) + else + if(dx > 0) + setDir(EAST) + else + setDir(WEST) + +//debug +/obj/screen/proc/scale_to(x1,y1) + if(!y1) + y1 = x1 + var/matrix/M = new + M.Scale(x1,y1) + transform = M + +/obj/screen/click_catcher + icon = 'icons/mob/screen_gen.dmi' + icon_state = "catcher" + plane = CLICKCATCHER_PLANE + mouse_opacity = MOUSE_OPACITY_OPAQUE + screen_loc = "CENTER" + +#define MAX_SAFE_BYOND_ICON_SCALE_TILES (MAX_SAFE_BYOND_ICON_SCALE_PX / world.icon_size) +#define MAX_SAFE_BYOND_ICON_SCALE_PX (33 * 32) //Not using world.icon_size on purpose. + +/obj/screen/click_catcher/proc/UpdateGreed(view_size_x = 15, view_size_y = 15) + var/icon/newicon = icon('icons/mob/screen_gen.dmi', "catcher") + var/ox = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_x) + var/oy = min(MAX_SAFE_BYOND_ICON_SCALE_TILES, view_size_y) + var/px = view_size_x * world.icon_size + var/py = view_size_y * world.icon_size + var/sx = min(MAX_SAFE_BYOND_ICON_SCALE_PX, px) + var/sy = min(MAX_SAFE_BYOND_ICON_SCALE_PX, py) + newicon.Scale(sx, sy) + icon = newicon + screen_loc = "CENTER-[(ox-1)*0.5],CENTER-[(oy-1)*0.5]" + var/matrix/M = new + M.Scale(px/sx, py/sy) + transform = M + +/obj/screen/click_catcher/Click(location, control, params) + var/list/modifiers = params2list(params) + if(modifiers["middle"] && iscarbon(usr)) + var/mob/living/carbon/C = usr + C.swap_hand() + else + var/turf/T = params2turf(modifiers["screen-loc"], get_turf(usr.client ? usr.client.eye : usr), usr.client) + params += "&catcher=1" + if(T) + T.Click(location, control, params) + . = 1 + +/* MouseWheelOn */ + +/mob/proc/MouseWheelOn(atom/A, delta_x, delta_y, params) + return + +/mob/dead/observer/MouseWheelOn(atom/A, delta_x, delta_y, params) + var/list/modifier = params2list(params) + if(modifier["shift"]) + var/view = 0 + if(delta_y > 0) + view = -1 + else + view = 1 + add_view_range(view) + +/mob/proc/check_click_intercept(params,A) + //Client level intercept + if(client && client.click_intercept) + if(call(client.click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + //Mob level intercept + if(click_intercept) + if(call(click_intercept, "InterceptClickOn")(src, params, A)) + return TRUE + + return FALSE diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index 4087a864..b0191d89 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -1,176 +1,174 @@ -/* - Cyborg ClickOn() - - Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. - However, they do have a range restriction on item use, so they cannot do without the - adjacency code. -*/ - -/mob/living/silicon/robot/ClickOn(var/atom/A, var/params) - if(world.time <= next_click) - return - next_click = world.time + 1 - - if(check_click_intercept(params,A)) - return - - if(stat || lockcharge || IsKnockdown() || IsStun() || IsUnconscious()) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) // alt and alt-gr (rightalt) - AltClickOn(A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(next_move >= world.time) - return - - face_atom(A) // change direction to face what you clicked on - - /* - cyborg restrained() currently does nothing - if(restrained()) - RestrainedClickOn(A) - return - */ - if(aicamera.in_camera_mode) //Cyborg picture taking - aicamera.camera_mode_off() - aicamera.captureimage(A, usr) - return - - var/obj/item/W = get_active_held_item() - - if(!W && get_dist(src,A) <= interaction_range) - A.attack_robot(src) - return - - if(W) - // buckled cannot prevent machine interlinking but stops arm movement - if( buckled || incapacitated()) - return - - if(W == A) - W.attack_self(src) - return - - // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents) - if(A == loc || (A in loc) || (A in contents)) - W.melee_attack_chain(src, A, params) - return - - if(!isturf(loc)) - return - - // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc && isturf(A.loc.loc)) - if(isturf(A) || isturf(A.loc)) - if(A.Adjacent(src)) // see adjacent.dm - W.melee_attack_chain(src, A, params) - return - else - W.afterattack(A, src, 0, params) - return - -//Middle click cycles through selected modules. -/mob/living/silicon/robot/MiddleClickOn(atom/A) - cycle_modules() - return - -//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks -// for non-doors/apcs -/mob/living/silicon/robot/CtrlShiftClickOn(atom/A) - A.BorgCtrlShiftClick(src) -/mob/living/silicon/robot/ShiftClickOn(atom/A) - A.BorgShiftClick(src) -/mob/living/silicon/robot/CtrlClickOn(atom/A) - A.BorgCtrlClick(src) -/mob/living/silicon/robot/AltClickOn(atom/A) - A.BorgAltClick(src) - -/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlShiftClick(user) - -/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlShiftClick() - else - ..() - - -/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden - ShiftClick(user) - -/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIShiftClick() - else - ..() - - -/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden - CtrlClick(user) - -/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AICtrlClick() - else - ..() - -/atom/proc/BorgAltClick(mob/living/silicon/robot/user) - AltClick(user) - return - -/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. - if(get_dist(src,user) <= user.interaction_range) - AIAltClick() - else - ..() - -/* - As with AI, these are not used in click code, - because the code for robots is specific, not generic. - - If you would like to add advanced features to robot - clicks, you can do so here, but you will have to - change attack_robot() above to the proper function -*/ -/mob/living/silicon/robot/UnarmedAttack(atom/A) - A.attack_robot(src) -/mob/living/silicon/robot/RangedAttack(atom/A) - A.attack_robot(src) - -/atom/proc/attack_robot(mob/user) - attack_ai(user) - return +/* + Cyborg ClickOn() + + Cyborgs have no range restriction on attack_robot(), because it is basically an AI click. + However, they do have a range restriction on item use, so they cannot do without the + adjacency code. +*/ + +/mob/living/silicon/robot/ClickOn(var/atom/A, var/params) + if(world.time <= next_click) + return + next_click = world.time + 1 + + if(check_click_intercept(params,A)) + return + + if(stat || lockcharge || IsKnockdown() || IsStun() || IsUnconscious()) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) // alt and alt-gr (rightalt) + AltClickOn(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(next_move >= world.time) + return + + face_atom(A) // change direction to face what you clicked on + + /* + cyborg restrained() currently does nothing + if(restrained()) + RestrainedClickOn(A) + return + */ + if(aicamera.in_camera_mode) //Cyborg picture taking + aicamera.camera_mode_off() + aicamera.captureimage(A, usr) + return + + var/obj/item/W = get_active_held_item() + + if(!W && get_dist(src,A) <= interaction_range) + A.attack_robot(src) + return + + if(W) + // buckled cannot prevent machine interlinking but stops arm movement + if( buckled || incapacitated()) + return + + if(W == A) + W.attack_self(src) + return + + // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc in contents) + if(A == loc || (A in loc) || (A in contents)) + W.melee_attack_chain(src, A, params) + return + + if(!isturf(loc)) + return + + // cyborgs are prohibited from using storage items so we can I think safely remove (A.loc && isturf(A.loc.loc)) + if(isturf(A) || isturf(A.loc)) + if(A.Adjacent(src)) // see adjacent.dm + W.melee_attack_chain(src, A, params) + return + else + W.afterattack(A, src, 0, params) + return + +//Middle click cycles through selected modules. +/mob/living/silicon/robot/MiddleClickOn(atom/A) + cycle_modules() + return + +//Give cyborgs hotkey clicks without breaking existing uses of hotkey clicks +// for non-doors/apcs +/mob/living/silicon/robot/CtrlShiftClickOn(atom/A) + A.BorgCtrlShiftClick(src) +/mob/living/silicon/robot/ShiftClickOn(atom/A) + A.BorgShiftClick(src) +/mob/living/silicon/robot/CtrlClickOn(atom/A) + A.BorgCtrlClick(src) +/mob/living/silicon/robot/AltClickOn(atom/A) + if(!A.BorgAltClick(src)) + altclick_listed_turf(A) + +/atom/proc/BorgCtrlShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlShiftClick(user) + +/obj/machinery/door/airlock/BorgCtrlShiftClick(mob/living/silicon/robot/user) // Sets/Unsets Emergency Access Override Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlShiftClick() + else + ..() + + +/atom/proc/BorgShiftClick(mob/living/silicon/robot/user) //forward to human click if not overridden + ShiftClick(user) + +/obj/machinery/door/airlock/BorgShiftClick(mob/living/silicon/robot/user) // Opens and closes doors! Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AIShiftClick() + else + ..() + + +/atom/proc/BorgCtrlClick(mob/living/silicon/robot/user) //forward to human click if not overridden + CtrlClick(user) + +/obj/machinery/door/airlock/BorgCtrlClick(mob/living/silicon/robot/user) // Bolts doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/power/apc/BorgCtrlClick(mob/living/silicon/robot/user) // turns off/on APCs. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/obj/machinery/turretid/BorgCtrlClick(mob/living/silicon/robot/user) //turret control on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + AICtrlClick() + else + ..() + +/atom/proc/BorgAltClick(mob/living/silicon/robot/user) + return AltClick(user) + +/obj/machinery/door/airlock/BorgAltClick(mob/living/silicon/robot/user) // Eletrifies doors. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + return AIAltClick() + return ..() + +/obj/machinery/turretid/BorgAltClick(mob/living/silicon/robot/user) //turret lethal on/off. Forwards to AI code. + if(get_dist(src,user) <= user.interaction_range) + return AIAltClick() + return ..() + +/* + As with AI, these are not used in click code, + because the code for robots is specific, not generic. + + If you would like to add advanced features to robot + clicks, you can do so here, but you will have to + change attack_robot() above to the proper function +*/ +/mob/living/silicon/robot/UnarmedAttack(atom/A) + A.attack_robot(src) +/mob/living/silicon/robot/RangedAttack(atom/A) + A.attack_robot(src) + +/atom/proc/attack_robot(mob/user) + attack_ai(user) + return diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm index 2b5af9dd..d2508b3e 100644 --- a/code/_onclick/hud/action_button.dm +++ b/code/_onclick/hud/action_button.dm @@ -125,6 +125,7 @@ moved = FALSE user.update_action_buttons(TRUE) to_chat(user, "Action button positions have been reset.") + return TRUE /obj/screen/movable/action_button/hide_toggle/proc/InitialiseIcon(datum/hud/owner_hud) diff --git a/code/_onclick/observer.dm b/code/_onclick/observer.dm index 7dff8a8e..f76a745f 100644 --- a/code/_onclick/observer.dm +++ b/code/_onclick/observer.dm @@ -1,83 +1,83 @@ -/mob/dead/observer/DblClickOn(var/atom/A, var/params) - if(can_reenter_corpse && mind && mind.current) - if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it - reenter_corpse() // (cloning scanner, body bag, closet, mech, etc) - return // seems legit. - - // Things you might plausibly want to follow - if(ismovableatom(A)) - ManualFollow(A) - - // Otherwise jump - else if(A.loc) - forceMove(get_turf(A)) - update_parallax_contents() - -/mob/dead/observer/ClickOn(var/atom/A, var/params) - if(check_click_intercept(params,A)) - return - - var/list/modifiers = params2list(params) - if(modifiers["shift"] && modifiers["middle"]) - ShiftMiddleClickOn(A) - return - if(modifiers["shift"] && modifiers["ctrl"]) - CtrlShiftClickOn(A) - return - if(modifiers["middle"]) - MiddleClickOn(A) - return - if(modifiers["shift"]) - ShiftClickOn(A) - return - if(modifiers["alt"]) - AltClickNoInteract(src, A) - return - if(modifiers["ctrl"]) - CtrlClickOn(A) - return - - if(world.time <= next_move) - return - // You are responsible for checking config.ghost_interaction when you override this function - // Not all of them require checking, see below - A.attack_ghost(src) - -// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you -/atom/proc/attack_ghost(mob/dead/observer/user) - if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) - return TRUE - if(user.client) - if(IsAdminGhost(user)) - attack_ai(user) - else if(user.client.prefs.inquisitive_ghost) - user.examinate(src) - return FALSE - -/mob/living/attack_ghost(mob/dead/observer/user) - if(user.client && user.health_scan) - healthscan(user, src, 1, TRUE) - return ..() - -// --------------------------------------- -// And here are some good things for free: -// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu - -/obj/machinery/gateway/centerstation/attack_ghost(mob/user) - if(awaygate) - user.forceMove(awaygate.loc) - else - to_chat(user, "[src] has no destination.") - return ..() - -/obj/machinery/gateway/centeraway/attack_ghost(mob/user) - if(stationgate) - user.forceMove(stationgate.loc) - else - to_chat(user, "[src] has no destination.") - return ..() - -/obj/machinery/teleport/hub/attack_ghost(mob/user) - if(power_station && power_station.engaged && power_station.teleporter_console && power_station.teleporter_console.target) - user.forceMove(get_turf(power_station.teleporter_console.target)) - return ..() +/mob/dead/observer/DblClickOn(var/atom/A, var/params) + if(can_reenter_corpse && mind && mind.current) + if(A == mind.current || (mind.current in A)) // double click your corpse or whatever holds it + reenter_corpse() // (cloning scanner, body bag, closet, mech, etc) + return // seems legit. + + // Things you might plausibly want to follow + if(ismovableatom(A)) + ManualFollow(A) + + // Otherwise jump + else if(A.loc) + forceMove(get_turf(A)) + update_parallax_contents() + +/mob/dead/observer/ClickOn(var/atom/A, var/params) + if(check_click_intercept(params,A)) + return + + var/list/modifiers = params2list(params) + if(modifiers["shift"] && modifiers["middle"]) + ShiftMiddleClickOn(A) + return + if(modifiers["shift"] && modifiers["ctrl"]) + CtrlShiftClickOn(A) + return + if(modifiers["middle"]) + MiddleClickOn(A) + return + if(modifiers["shift"]) + ShiftClickOn(A) + return + if(modifiers["alt"]) + altclick_listed_turf(A) + return + if(modifiers["ctrl"]) + CtrlClickOn(A) + return + + if(world.time <= next_move) + return + // You are responsible for checking config.ghost_interaction when you override this function + // Not all of them require checking, see below + A.attack_ghost(src) + +// Oh by the way this didn't work with old click code which is why clicking shit didn't spam you +/atom/proc/attack_ghost(mob/dead/observer/user) + if(SEND_SIGNAL(src, COMSIG_ATOM_ATTACK_GHOST, user) & COMPONENT_NO_ATTACK_HAND) + return TRUE + if(user.client) + if(IsAdminGhost(user)) + attack_ai(user) + else if(user.client.prefs.inquisitive_ghost) + user.examinate(src) + return FALSE + +/mob/living/attack_ghost(mob/dead/observer/user) + if(user.client && user.health_scan) + healthscan(user, src, 1, TRUE) + return ..() + +// --------------------------------------- +// And here are some good things for free: +// Now you can click through portals, wormholes, gateways, and teleporters while observing. -Sayu + +/obj/machinery/gateway/centerstation/attack_ghost(mob/user) + if(awaygate) + user.forceMove(awaygate.loc) + else + to_chat(user, "[src] has no destination.") + return ..() + +/obj/machinery/gateway/centeraway/attack_ghost(mob/user) + if(stationgate) + user.forceMove(stationgate.loc) + else + to_chat(user, "[src] has no destination.") + return ..() + +/obj/machinery/teleport/hub/attack_ghost(mob/user) + if(power_station && power_station.engaged && power_station.teleporter_console && power_station.teleporter_console.target) + user.forceMove(get_turf(power_station.teleporter_console.target)) + return ..() diff --git a/code/datums/components/rotation.dm b/code/datums/components/rotation.dm index 4194d6a7..c4ed0031 100644 --- a/code/datums/components/rotation.dm +++ b/code/datums/components/rotation.dm @@ -106,6 +106,7 @@ if(!can_be_rotated.Invoke(user, rotation) || !can_user_rotate.Invoke(user, rotation)) return BaseRot(user, rotation) + return TRUE /datum/component/simple_rotation/proc/WrenchRot(datum/source, obj/item/I, mob/living/user) if(!can_be_rotated.Invoke(user,default_rotation_direction) || !can_user_rotate.Invoke(user,default_rotation_direction)) diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 8a58f112..8a47af52 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -1,778 +1,779 @@ -#define COLLECT_ONE 0 -#define COLLECT_EVERYTHING 1 -#define COLLECT_SAME 2 - -#define DROP_NOTHING 0 -#define DROP_AT_PARENT 1 -#define DROP_AT_LOCATION 2 - -// External storage-related logic: -// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages -// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement - -/datum/component/storage - dupe_mode = COMPONENT_DUPE_UNIQUE - var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. - - var/list/can_hold //if this is set, only things in this typecache will fit. - var/list/cant_hold //if this is set, anything in this typecache will not be able to fit. - - var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. - - var/locked = FALSE //when locked nothing can see inside or use it. - - var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. - var/max_combined_w_class = 14 //max combined sizes of objects that will fit. - var/max_items = 7 //max number of objects that will fit. - - var/emp_shielded = FALSE - - var/silent = FALSE //whether this makes a message when things are put in. - var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. - var/rustle_sound = TRUE //play rustle sound on interact. - var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. - var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. - - var/collection_mode = COLLECT_EVERYTHING - - var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. - - var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. - - var/obj/screen/storage/boxes //storage display object - var/obj/screen/close/closer //close button object - - var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. - - var/attack_hand_interact = TRUE //interact on attack hand. - var/quickdraw = FALSE //altclick interact - - var/datum/action/item_action/storage_gather_mode/modeswitch_action - - //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. - var/screen_max_columns = 7 //These two determine maximum screen sizes. - var/screen_max_rows = INFINITY - var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer - var/screen_pixel_y = 16 - var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. - var/screen_start_y = 2 - //End - -/datum/component/storage/Initialize(datum/component/storage/concrete/master) - if(!isatom(parent)) - return COMPONENT_INCOMPATIBLE - if(master) - change_master(master) - boxes = new(null, src) - closer = new(null, src) - orient2hud() - - RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) - RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) - RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) - RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) - RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) - RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) - RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) - - RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) - - RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) - RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) - RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) - RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) - RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) - RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) - - RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) - RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) - RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) - - RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) - - RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) - RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) - RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) - - update_actions() - -/datum/component/storage/Destroy() - close_all() - QDEL_NULL(boxes) - QDEL_NULL(closer) - LAZYCLEARLIST(is_using) - return ..() - -/datum/component/storage/PreTransfer() - update_actions() - -/datum/component/storage/proc/update_actions() - QDEL_NULL(modeswitch_action) - if(!isitem(parent) || !allow_quick_gather) - return - var/obj/item/I = parent - modeswitch_action = new(I) - RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) - if(I.obj_flags & IN_INVENTORY) - var/mob/M = I.loc - if(!istype(M)) - return - modeswitch_action.Grant(M) - -/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) - if(new_master == src || (!isnull(new_master) && !istype(new_master))) - return FALSE - if(master) - master.on_slave_unlink(src) - master = new_master - if(master) - master.on_slave_link(src) - return TRUE - -/datum/component/storage/proc/master() - if(master == src) - return //infinite loops yo. - return master - -/datum/component/storage/proc/real_location() - var/datum/component/storage/concrete/master = master() - return master? master.real_location() : null - -/datum/component/storage/proc/canreach_react(datum/source, list/next) - var/datum/component/storage/concrete/master = master() - if(!master) - return - . = COMPONENT_BLOCK_REACH - next += master.parent - for(var/i in master.slaves) - var/datum/component/storage/slave = i - next += slave.parent - -/datum/component/storage/proc/attack_self(datum/source, mob/M) - if(check_locked(source, M, TRUE)) - return FALSE - if((M.get_active_held_item() == parent) && allow_quick_empty) - quick_empty(M) - -/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) - if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) - return FALSE - . = COMPONENT_NO_ATTACK - if(check_locked(source, M, TRUE)) - return FALSE - var/atom/A = parent - var/obj/item/I = O - if(collection_mode == COLLECT_ONE) - if(can_be_inserted(I, null, M)) - handle_item_insertion(I, null, M) - A.do_squish() - return - if(!isturf(I.loc)) - return - var/list/things = I.loc.contents.Copy() - if(collection_mode == COLLECT_SAME) - things = typecache_filter_list(things, typecacheof(I.type)) - var/len = length(things) - if(!len) - to_chat(M, "You failed to pick up anything with [parent].") - return - var/datum/progressbar/progress = new(M, len, I.loc) - var/list/rejections = list() - while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) - stoplag(1) - qdel(progress) - to_chat(M, "You put everything you could [insert_preposition] [parent].") - A.do_squish(1.4, 0.4) - -/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) - var/atom/source_real_location = src_object.real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != source_real_location) - continue - if(user.active_storage != src_object) - if(I.on_found(user)) - break - if(can_be_inserted(I,FALSE,user)) - handle_item_insertion(I, TRUE, user) - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != thing_loc) - continue - if(I.type in rejections) // To limit bag spamming: any given type only complains once - continue - if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no - if(real_location.contents.len >= max_items) - break - rejections += I.type // therefore full bags are still a little spammy - continue - - handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. - - if (TICK_CHECK) - progress.update(progress.goal - things.len) - return TRUE - - progress.update(progress.goal - things.len) - return FALSE - -/datum/component/storage/proc/quick_empty(mob/M) - var/atom/A = parent - if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) - return - if(check_locked(null, M, TRUE)) - return FALSE - A.add_fingerprint(M) - to_chat(M, "You start dumping out [parent].") - var/turf/T = get_turf(A) - var/list/things = contents() - var/datum/progressbar/progress = new(M, length(things), T) - while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) - stoplag(1) - qdel(progress) - A.do_squish(0.8, 1.2) - -/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) - var/atom/real_location = real_location() - for(var/obj/item/I in things) - things -= I - if(I.loc != real_location) - continue - remove_from_storage(I, target) - if(trigger_on_found && I.on_found()) - return FALSE - if(TICK_CHECK) - progress.update(progress.goal - length(things)) - return TRUE - progress.update(progress.goal - length(things)) - return FALSE - -/datum/component/storage/proc/do_quick_empty(atom/_target) - if(!_target) - _target = get_turf(parent) - if(usr) - hide_from(usr) - var/list/contents = contents() - var/atom/real_location = real_location() - for(var/obj/item/I in contents) - if(I.loc != real_location) - continue - remove_from_storage(I, _target) - return TRUE - -/datum/component/storage/proc/set_locked(datum/source, new_state) - locked = new_state - if(check_locked()) - close_all() - -/datum/component/storage/proc/_process_numerical_display() - . = list() - var/atom/real_location = real_location() - for(var/obj/item/I in real_location.contents) - if(QDELETED(I)) - continue - if(!.[I.type]) - .[I.type] = new /datum/numbered_display(I, 1) - else - var/datum/numbered_display/ND = .[I.type] - ND.number++ - -//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. -/datum/component/storage/proc/orient2hud(mob/user, maxcolumns) - var/atom/real_location = real_location() - var/adjusted_contents = real_location.contents.len - - //Numbered contents display - var/list/datum/numbered_display/numbered_contents - if(display_numerical_stacking) - numbered_contents = _process_numerical_display() - adjusted_contents = numbered_contents.len - - var/columns = CLAMP(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns) - var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) - standard_orient_objs(rows, columns, numbered_contents) - -//This proc draws out the inventory and places the items on it. It uses the standard position. -/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) - boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" - var/cx = screen_start_x - var/cy = screen_start_y - if(islist(numerical_display_contents)) - for(var/type in numerical_display_contents) - var/datum/numbered_display/ND = numerical_display_contents[type] - ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE - ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" - ND.sample_object.layer = ABOVE_HUD_LAYER - ND.sample_object.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - else - var/atom/real_location = real_location() - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" - O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" - O.maptext = "" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx - screen_start_x >= cols) - cx = screen_start_x - cy++ - if(cy - screen_start_y >= rows) - break - closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" - -/datum/component/storage/proc/show_to(mob/M) - if(!M.client) - return FALSE - var/list/cview = getviewsize(M.client.view) - var/maxallowedscreensize = cview[1]-8 - var/atom/real_location = real_location() - if(M.active_storage != src && (M.stat == CONSCIOUS)) - for(var/obj/item/I in real_location) - if(I.on_found(M)) - return FALSE - if(M.active_storage) - M.active_storage.hide_from(M) - orient2hud(M, (isliving(M) ? maxallowedscreensize : 7)) - M.client.screen |= boxes - M.client.screen |= closer - M.client.screen |= real_location.contents - M.active_storage = src - LAZYOR(is_using, M) - return TRUE - -/datum/component/storage/proc/hide_from(mob/M) - if(!M.client) - return TRUE - var/atom/real_location = real_location() - M.client.screen -= boxes - M.client.screen -= closer - M.client.screen -= real_location.contents - if(M.active_storage == src) - M.active_storage = null - LAZYREMOVE(is_using, M) - return TRUE - -/datum/component/storage/proc/close(mob/M) - hide_from(M) - -/datum/component/storage/proc/close_all() - . = FALSE - for(var/mob/M in can_see_contents()) - close(M) - . = TRUE //returns TRUE if any mobs actually got a close(M) call - -/datum/component/storage/proc/emp_act(datum/source, severity) - if(emp_shielded) - return - var/datum/component/storage/concrete/master = master() - master.emp_act(source, severity) - -//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. -//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. -/datum/component/storage/proc/orient_objs(tx, ty, mx, my) - var/atom/real_location = real_location() - var/cx = tx - var/cy = ty - boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" - for(var/obj/O in real_location) - if(QDELETED(O)) - continue - O.screen_loc = "[cx],[cy]" - O.layer = ABOVE_HUD_LAYER - O.plane = ABOVE_HUD_PLANE - cx++ - if(cx > mx) - cx = tx - cy-- - closer.screen_loc = "[mx+1],[my]" - -//Resets something that is being removed from storage. -/datum/component/storage/proc/_removal_reset(atom/movable/thing) - if(!istype(thing)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master._removal_reset(thing) - -/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) - _removal_reset(thing) - refresh_mob_views() - -//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted -/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) - if(!istype(AM)) - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.remove_from_storage(AM, new_location) - -/datum/component/storage/proc/refresh_mob_views() - var/list/seeing = can_see_contents() - for(var/i in seeing) - show_to(i) - return TRUE - -/datum/component/storage/proc/can_see_contents() - var/list/cansee = list() - for(var/mob/M in is_using) - if(M.active_storage == src && M.client) - cansee |= M - else - LAZYREMOVE(is_using, M) - return cansee - -//Tries to dump content -/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) - var/atom/A = parent - var/atom/dump_destination = dest_object.get_dumping_location() - if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) - if(check_locked(null, M, TRUE)) - return FALSE - if(dump_destination.storage_contents_dump_act(src, M)) - playsound(A, "rustle", 50, 1, -5) - A.do_squish(0.8, 1.2) - return TRUE - return FALSE - -//This proc is called when you want to place an item into the storage item. -/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) - if(istype(I, /obj/item/hand_labeler)) - var/obj/item/hand_labeler/labeler = I - if(labeler.mode) - return FALSE - . = TRUE //no afterattack - if(iscyborg(M)) - return - if(!can_be_inserted(I, FALSE, M)) - var/atom/real_location = real_location() - if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit - return TRUE - return FALSE - handle_item_insertion(I, FALSE, M) - var/atom/A = parent - A.do_squish() - -/datum/component/storage/proc/return_inv(recursive) - var/list/ret = list() - ret |= contents() - if(recursive) - for(var/i in ret.Copy()) - var/atom/A = i - SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) - return ret - -/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! - var/atom/real_location = real_location() - return real_location.contents.Copy() - -//Abuses the fact that lists are just references, or something like that. -/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) - if(!islist(interface)) - return FALSE - interface |= return_inv(recursive) - return TRUE - -/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) - set waitfor = FALSE - . = COMPONENT_NO_MOUSEDROP - var/atom/A = parent - if(ismob(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked - A.add_fingerprint(M) - if(!over_object) - return FALSE - if(ismecha(M.loc)) // stops inventory actions in a mech - return FALSE - // this must come before the screen objects only block, dunno why it wasn't before - if(over_object == M) - user_show_to_mob(M) - if(!M.incapacitated()) - if(!istype(over_object, /obj/screen)) - dump_content_at(over_object, M) - return - if(A.loc != M) - return - playsound(A, "rustle", 50, 1, -5) - A.do_jiggle() - if(istype(over_object, /obj/screen/inventory/hand)) - var/obj/screen/inventory/hand/H = over_object - M.putItemFromInventoryInHandIfPossible(A, H.held_index) - return - A.add_fingerprint(M) - -/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) - var/atom/A = parent - if(!istype(M)) - return FALSE - A.add_fingerprint(M) - if(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE))) - return FALSE - show_to(M) - -/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) - if(isitem(O)) - var/obj/item/I = O - if(iscarbon(M) || isdrone(M)) - var/mob/living/L = M - if(!L.incapacitated() && I == L.get_active_held_item()) - if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. - handle_item_insertion(I, FALSE, L) - var/atom/A = parent - A.do_squish() - -//This proc return 1 if the item can be picked up and 0 if it can't. -//Set the stop_messages to stop it from printing messages -/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) - if(!istype(I) || (I.item_flags & ABSTRACT)) - return FALSE //Not an item - if(I == parent) - return FALSE //no paradoxes for you - var/atom/real_location = real_location() - var/atom/host = parent - if(real_location == I.loc) - return FALSE //Means the item is already in the storage item - if(check_locked(null, M, !stop_messages)) - if(M && !stop_messages) - host.add_fingerprint(M) - return FALSE - if(real_location.contents.len >= max_items) - if(!stop_messages) - to_chat(M, "[host] is full, make some space!") - return FALSE //Storage item is full - if(length(can_hold)) - if(!is_type_in_typecache(I, can_hold)) - if(!stop_messages) - to_chat(M, "[host] cannot hold [I]!") - return FALSE - if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold. - if(!stop_messages) - to_chat(M, "[host] cannot hold [I]!") - return FALSE - if(I.w_class > max_w_class) - if(!stop_messages) - to_chat(M, "[I] is too big for [host]!") - return FALSE - var/sum_w_class = I.w_class - for(var/obj/item/_I in real_location) - sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. - if(sum_w_class > max_combined_w_class) - if(!stop_messages) - to_chat(M, "[I] won't fit in [host], make some space!") - return FALSE - if(isitem(host)) - var/obj/item/IP = host - var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage) - if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting) - if(!stop_messages) - to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!") - return FALSE //To prevent the stacking of same sized storage items. - if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry. - to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") - return FALSE - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - return master.slave_can_insert_object(src, I, stop_messages, M) - -/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) - return FALSE - -//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() -//The stop_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, -//such as when picking up all the items on a tile with one click. -/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) - var/atom/parent = src.parent - var/datum/component/storage/concrete/master = master() - if(!istype(master)) - return FALSE - if(silent) - prevent_warning = TRUE - if(M) - parent.add_fingerprint(M) - . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) - -/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) - if(silent && !override) - return - if(rustle_sound) - playsound(parent, "rustle", 50, 1, -5) - for(var/mob/viewing in viewers(user, null)) - if(M == viewing) - to_chat(usr, "You put [I] [insert_preposition]to [parent].") - else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) - else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... - viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) - -/datum/component/storage/proc/update_icon() - if(isobj(parent)) - var/obj/O = parent - O.update_icon() - -/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) - if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) - return FALSE - return handle_item_insertion(I, silent, M) - -/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) - return can_be_inserted(I, silent, M) - -/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) - return user_show_to_mob(M, TRUE) - -/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) - return user_show_to_mob(showto, force) - -/datum/component/storage/proc/on_check() - return TRUE - -/datum/component/storage/proc/check_locked(datum/source, mob/user, message = FALSE) - . = locked - if(message && . && user) - to_chat(user, "[parent] seems to be locked!") - -/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) - if(!force) - if(check_adjacent) - if(!user || !user.CanReach(destination) || !user.CanReach(parent)) - return FALSE - var/list/taking = typecache_filter_list(contents(), typecacheof(type)) - if(taking.len > amount) - taking.len = amount - if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. - for(var/i in taking) - if(remove_from_storage(i, destination)) - inserted |= i - else - for(var/i in taking) - remove_from_storage(i, destination) - return TRUE - -/datum/component/storage/proc/remaining_space_items() - var/atom/real_location = real_location() - return max(0, max_items - real_location.contents.len) - -/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) - var/atom/real_location = real_location() - if(!force) - amount = min(remaining_space_items(), amount) - for(var/i in 1 to amount) - handle_item_insertion(new type(real_location), TRUE) - CHECK_TICK - return TRUE - -/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) - var/atom/A = parent - if(!attack_hand_interact) - return - if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item - user.active_storage.close(user) - close(user) - . = COMPONENT_NO_ATTACK_HAND - return - - if(rustle_sound) - playsound(A, "rustle", 50, 1, -5) - - if(ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.l_store = null - return - if(H.r_store == A && !H.get_active_held_item()) - . = COMPONENT_NO_ATTACK_HAND - H.put_in_hands(A) - H.r_store = null - return - - if(A.loc == user) - . = COMPONENT_NO_ATTACK_HAND - if(!check_locked(source, user, TRUE)) - show_to(user) - A.do_jiggle() - -/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) - var/atom/A = parent - update_actions() - for(var/mob/M in range(1, A)) - if(M.active_storage == src) - close(M) - -/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) - if(!(AM in real_location())) - return FALSE - return remove_from_storage(AM, new_loc) - -/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) - return do_quick_empty(loctarget) - -/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) - return hide_from(target) - -/datum/component/storage/proc/on_alt_click(datum/source, mob/user) - if(!isliving(user) || !user.CanReach(parent)) - return - if(check_locked(source, user, TRUE)) - return - - var/atom/A = parent - if(!quickdraw) - A.add_fingerprint(user) - user_show_to_mob(user) - if(rustle_sound) - playsound(A, "rustle", 50, 1, -5) - return - - if(!user.incapacitated()) - var/obj/item/I = locate() in real_location() - if(!I) - return - A.add_fingerprint(user) - remove_from_storage(I, get_turf(user)) - if(!user.put_in_hands(I)) - to_chat(user, "You fumble for [I] and it falls on the floor.") - return - user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") - return - -/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source) - gather_mode_switch(source.owner) - return COMPONENT_ACTION_BLOCK_TRIGGER - -/datum/component/storage/proc/gather_mode_switch(mob/user) - collection_mode = (collection_mode+1)%3 - switch(collection_mode) - if(COLLECT_SAME) - to_chat(user, "[parent] now picks up all items of a single type at once.") - if(COLLECT_EVERYTHING) - to_chat(user, "[parent] now picks up all items in a tile at once.") - if(COLLECT_ONE) - to_chat(user, "[parent] now picks up one item at a time.") +#define COLLECT_ONE 0 +#define COLLECT_EVERYTHING 1 +#define COLLECT_SAME 2 + +#define DROP_NOTHING 0 +#define DROP_AT_PARENT 1 +#define DROP_AT_LOCATION 2 + +// External storage-related logic: +// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages +// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement + +/datum/component/storage + dupe_mode = COMPONENT_DUPE_UNIQUE + var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point. + + var/list/can_hold //if this is set, only things in this typecache will fit. + var/list/cant_hold //if this is set, anything in this typecache will not be able to fit. + + var/list/mob/is_using //lazy list of mobs looking at the contents of this storage. + + var/locked = FALSE //when locked nothing can see inside or use it. + + var/max_w_class = WEIGHT_CLASS_SMALL //max size of objects that will fit. + var/max_combined_w_class = 14 //max combined sizes of objects that will fit. + var/max_items = 7 //max number of objects that will fit. + + var/emp_shielded = FALSE + + var/silent = FALSE //whether this makes a message when things are put in. + var/click_gather = FALSE //whether this can be clicked on items to pick it up rather than the other way around. + var/rustle_sound = TRUE //play rustle sound on interact. + var/allow_quick_empty = FALSE //allow empty verb which allows dumping on the floor of everything inside quickly. + var/allow_quick_gather = FALSE //allow toggle mob verb which toggles collecting all items from a tile. + + var/collection_mode = COLLECT_EVERYTHING + + var/insert_preposition = "in" //you put things "in" a bag, but "on" a tray. + + var/display_numerical_stacking = FALSE //stack things of the same type and show as a single object with a number. + + var/obj/screen/storage/boxes //storage display object + var/obj/screen/close/closer //close button object + + var/allow_big_nesting = FALSE //allow storage objects of the same or greater size. + + var/attack_hand_interact = TRUE //interact on attack hand. + var/quickdraw = FALSE //altclick interact + + var/datum/action/item_action/storage_gather_mode/modeswitch_action + + //Screen variables: Do not mess with these vars unless you know what you're doing. They're not defines so storage that isn't in the same location can be supported in the future. + var/screen_max_columns = 7 //These two determine maximum screen sizes. + var/screen_max_rows = INFINITY + var/screen_pixel_x = 16 //These two are pixel values for screen loc of boxes and closer + var/screen_pixel_y = 16 + var/screen_start_x = 4 //These two are where the storage starts being rendered, screen_loc wise. + var/screen_start_y = 2 + //End + +/datum/component/storage/Initialize(datum/component/storage/concrete/master) + if(!isatom(parent)) + return COMPONENT_INCOMPATIBLE + if(master) + change_master(master) + boxes = new(null, src) + closer = new(null, src) + orient2hud() + + RegisterSignal(parent, COMSIG_CONTAINS_STORAGE, .proc/on_check) + RegisterSignal(parent, COMSIG_IS_STORAGE_LOCKED, .proc/check_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SHOW, .proc/signal_show_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/signal_insertion_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_CAN_INSERT, .proc/signal_can_insert) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE_TYPE, .proc/signal_take_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_FILL_TYPE, .proc/signal_fill_type) + RegisterSignal(parent, COMSIG_TRY_STORAGE_SET_LOCKSTATE, .proc/set_locked) + RegisterSignal(parent, COMSIG_TRY_STORAGE_TAKE, .proc/signal_take_obj) + RegisterSignal(parent, COMSIG_TRY_STORAGE_QUICK_EMPTY, .proc/signal_quick_empty) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_FROM, .proc/signal_hide_attempt) + RegisterSignal(parent, COMSIG_TRY_STORAGE_HIDE_ALL, .proc/close_all) + RegisterSignal(parent, COMSIG_TRY_STORAGE_RETURN_INVENTORY, .proc/signal_return_inv) + + RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/attackby) + + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_PAW, .proc/on_attack_hand) + RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/emp_act) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_GHOST, .proc/show_to_ghost) + RegisterSignal(parent, COMSIG_ATOM_ENTERED, .proc/refresh_mob_views) + RegisterSignal(parent, COMSIG_ATOM_EXITED, .proc/_remove_and_refresh) + RegisterSignal(parent, COMSIG_ATOM_CANREACH, .proc/canreach_react) + + RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, .proc/preattack_intercept) + RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/attack_self) + RegisterSignal(parent, COMSIG_ITEM_PICKUP, .proc/signal_on_pickup) + + RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/close_all) + + RegisterSignal(parent, COMSIG_CLICK_ALT, .proc/on_alt_click) + RegisterSignal(parent, COMSIG_MOUSEDROP_ONTO, .proc/mousedrop_onto) + RegisterSignal(parent, COMSIG_MOUSEDROPPED_ONTO, .proc/mousedrop_receive) + + update_actions() + +/datum/component/storage/Destroy() + close_all() + QDEL_NULL(boxes) + QDEL_NULL(closer) + LAZYCLEARLIST(is_using) + return ..() + +/datum/component/storage/PreTransfer() + update_actions() + +/datum/component/storage/proc/update_actions() + QDEL_NULL(modeswitch_action) + if(!isitem(parent) || !allow_quick_gather) + return + var/obj/item/I = parent + modeswitch_action = new(I) + RegisterSignal(modeswitch_action, COMSIG_ACTION_TRIGGER, .proc/action_trigger) + if(I.obj_flags & IN_INVENTORY) + var/mob/M = I.loc + if(!istype(M)) + return + modeswitch_action.Grant(M) + +/datum/component/storage/proc/change_master(datum/component/storage/concrete/new_master) + if(new_master == src || (!isnull(new_master) && !istype(new_master))) + return FALSE + if(master) + master.on_slave_unlink(src) + master = new_master + if(master) + master.on_slave_link(src) + return TRUE + +/datum/component/storage/proc/master() + if(master == src) + return //infinite loops yo. + return master + +/datum/component/storage/proc/real_location() + var/datum/component/storage/concrete/master = master() + return master? master.real_location() : null + +/datum/component/storage/proc/canreach_react(datum/source, list/next) + var/datum/component/storage/concrete/master = master() + if(!master) + return + . = COMPONENT_BLOCK_REACH + next += master.parent + for(var/i in master.slaves) + var/datum/component/storage/slave = i + next += slave.parent + +/datum/component/storage/proc/attack_self(datum/source, mob/M) + if(check_locked(source, M, TRUE)) + return FALSE + if((M.get_active_held_item() == parent) && allow_quick_empty) + quick_empty(M) + +/datum/component/storage/proc/preattack_intercept(datum/source, obj/O, mob/M, params) + if(!isitem(O) || !click_gather || SEND_SIGNAL(O, COMSIG_CONTAINS_STORAGE)) + return FALSE + . = COMPONENT_NO_ATTACK + if(check_locked(source, M, TRUE)) + return FALSE + var/atom/A = parent + var/obj/item/I = O + if(collection_mode == COLLECT_ONE) + if(can_be_inserted(I, null, M)) + handle_item_insertion(I, null, M) + A.do_squish() + return + if(!isturf(I.loc)) + return + var/list/things = I.loc.contents.Copy() + if(collection_mode == COLLECT_SAME) + things = typecache_filter_list(things, typecacheof(I.type)) + var/len = length(things) + if(!len) + to_chat(M, "You failed to pick up anything with [parent].") + return + var/datum/progressbar/progress = new(M, len, I.loc) + var/list/rejections = list() + while(do_after(M, 10, TRUE, parent, FALSE, CALLBACK(src, .proc/handle_mass_pickup, things, I.loc, rejections, progress))) + stoplag(1) + qdel(progress) + to_chat(M, "You put everything you could [insert_preposition] [parent].") + A.do_squish(1.4, 0.4) + +/datum/component/storage/proc/handle_mass_item_insertion(list/things, datum/component/storage/src_object, mob/user, datum/progressbar/progress) + var/atom/source_real_location = src_object.real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != source_real_location) + continue + if(user.active_storage != src_object) + if(I.on_found(user)) + break + if(can_be_inserted(I,FALSE,user)) + handle_item_insertion(I, TRUE, user) + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/handle_mass_pickup(list/things, atom/thing_loc, list/rejections, datum/progressbar/progress) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != thing_loc) + continue + if(I.type in rejections) // To limit bag spamming: any given type only complains once + continue + if(!can_be_inserted(I, stop_messages = TRUE)) // Note can_be_inserted still makes noise when the answer is no + if(real_location.contents.len >= max_items) + break + rejections += I.type // therefore full bags are still a little spammy + continue + + handle_item_insertion(I, TRUE) //The TRUE stops the "You put the [parent] into [S]" insertion message from being displayed. + + if (TICK_CHECK) + progress.update(progress.goal - things.len) + return TRUE + + progress.update(progress.goal - things.len) + return FALSE + +/datum/component/storage/proc/quick_empty(mob/M) + var/atom/A = parent + if(!M.canUseStorage() || !A.Adjacent(M) || M.incapacitated()) + return + if(check_locked(null, M, TRUE)) + return FALSE + A.add_fingerprint(M) + to_chat(M, "You start dumping out [parent].") + var/turf/T = get_turf(A) + var/list/things = contents() + var/datum/progressbar/progress = new(M, length(things), T) + while (do_after(M, 10, TRUE, T, FALSE, CALLBACK(src, .proc/mass_remove_from_storage, T, things, progress))) + stoplag(1) + qdel(progress) + A.do_squish(0.8, 1.2) + +/datum/component/storage/proc/mass_remove_from_storage(atom/target, list/things, datum/progressbar/progress, trigger_on_found = TRUE) + var/atom/real_location = real_location() + for(var/obj/item/I in things) + things -= I + if(I.loc != real_location) + continue + remove_from_storage(I, target) + if(trigger_on_found && I.on_found()) + return FALSE + if(TICK_CHECK) + progress.update(progress.goal - length(things)) + return TRUE + progress.update(progress.goal - length(things)) + return FALSE + +/datum/component/storage/proc/do_quick_empty(atom/_target) + if(!_target) + _target = get_turf(parent) + if(usr) + hide_from(usr) + var/list/contents = contents() + var/atom/real_location = real_location() + for(var/obj/item/I in contents) + if(I.loc != real_location) + continue + remove_from_storage(I, _target) + return TRUE + +/datum/component/storage/proc/set_locked(datum/source, new_state) + locked = new_state + if(check_locked()) + close_all() + +/datum/component/storage/proc/_process_numerical_display() + . = list() + var/atom/real_location = real_location() + for(var/obj/item/I in real_location.contents) + if(QDELETED(I)) + continue + if(!.[I.type]) + .[I.type] = new /datum/numbered_display(I, 1) + else + var/datum/numbered_display/ND = .[I.type] + ND.number++ + +//This proc determines the size of the inventory to be displayed. Please touch it only if you know what you're doing. +/datum/component/storage/proc/orient2hud(mob/user, maxcolumns) + var/atom/real_location = real_location() + var/adjusted_contents = real_location.contents.len + + //Numbered contents display + var/list/datum/numbered_display/numbered_contents + if(display_numerical_stacking) + numbered_contents = _process_numerical_display() + adjusted_contents = numbered_contents.len + + var/columns = CLAMP(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns) + var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows) + standard_orient_objs(rows, columns, numbered_contents) + +//This proc draws out the inventory and places the items on it. It uses the standard position. +/datum/component/storage/proc/standard_orient_objs(rows, cols, list/obj/item/numerical_display_contents) + boxes.screen_loc = "[screen_start_x]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y] to [screen_start_x+cols-1]:[screen_pixel_x],[screen_start_y+rows-1]:[screen_pixel_y]" + var/cx = screen_start_x + var/cy = screen_start_y + if(islist(numerical_display_contents)) + for(var/type in numerical_display_contents) + var/datum/numbered_display/ND = numerical_display_contents[type] + ND.sample_object.mouse_opacity = MOUSE_OPACITY_OPAQUE + ND.sample_object.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + ND.sample_object.maptext = "[(ND.number > 1)? "[ND.number]" : ""]" + ND.sample_object.layer = ABOVE_HUD_LAYER + ND.sample_object.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + else + var/atom/real_location = real_location() + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.mouse_opacity = MOUSE_OPACITY_OPAQUE //This is here so storage items that spawn with contents correctly have the "click around item to equip" + O.screen_loc = "[cx]:[screen_pixel_x],[cy]:[screen_pixel_y]" + O.maptext = "" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx - screen_start_x >= cols) + cx = screen_start_x + cy++ + if(cy - screen_start_y >= rows) + break + closer.screen_loc = "[screen_start_x + cols]:[screen_pixel_x],[screen_start_y]:[screen_pixel_y]" + +/datum/component/storage/proc/show_to(mob/M) + if(!M.client) + return FALSE + var/list/cview = getviewsize(M.client.view) + var/maxallowedscreensize = cview[1]-8 + var/atom/real_location = real_location() + if(M.active_storage != src && (M.stat == CONSCIOUS)) + for(var/obj/item/I in real_location) + if(I.on_found(M)) + return FALSE + if(M.active_storage) + M.active_storage.hide_from(M) + orient2hud(M, (isliving(M) ? maxallowedscreensize : 7)) + M.client.screen |= boxes + M.client.screen |= closer + M.client.screen |= real_location.contents + M.active_storage = src + LAZYOR(is_using, M) + return TRUE + +/datum/component/storage/proc/hide_from(mob/M) + if(!M.client) + return TRUE + var/atom/real_location = real_location() + M.client.screen -= boxes + M.client.screen -= closer + M.client.screen -= real_location.contents + if(M.active_storage == src) + M.active_storage = null + LAZYREMOVE(is_using, M) + return TRUE + +/datum/component/storage/proc/close(mob/M) + hide_from(M) + +/datum/component/storage/proc/close_all() + . = FALSE + for(var/mob/M in can_see_contents()) + close(M) + . = TRUE //returns TRUE if any mobs actually got a close(M) call + +/datum/component/storage/proc/emp_act(datum/source, severity) + if(emp_shielded) + return + var/datum/component/storage/concrete/master = master() + master.emp_act(source, severity) + +//This proc draws out the inventory and places the items on it. tx and ty are the upper left tile and mx, my are the bottm right. +//The numbers are calculated from the bottom-left The bottom-left slot being 1,1. +/datum/component/storage/proc/orient_objs(tx, ty, mx, my) + var/atom/real_location = real_location() + var/cx = tx + var/cy = ty + boxes.screen_loc = "[tx]:,[ty] to [mx],[my]" + for(var/obj/O in real_location) + if(QDELETED(O)) + continue + O.screen_loc = "[cx],[cy]" + O.layer = ABOVE_HUD_LAYER + O.plane = ABOVE_HUD_PLANE + cx++ + if(cx > mx) + cx = tx + cy-- + closer.screen_loc = "[mx+1],[my]" + +//Resets something that is being removed from storage. +/datum/component/storage/proc/_removal_reset(atom/movable/thing) + if(!istype(thing)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master._removal_reset(thing) + +/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing) + _removal_reset(thing) + refresh_mob_views() + +//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted +/datum/component/storage/proc/remove_from_storage(atom/movable/AM, atom/new_location) + if(!istype(AM)) + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.remove_from_storage(AM, new_location) + +/datum/component/storage/proc/refresh_mob_views() + var/list/seeing = can_see_contents() + for(var/i in seeing) + show_to(i) + return TRUE + +/datum/component/storage/proc/can_see_contents() + var/list/cansee = list() + for(var/mob/M in is_using) + if(M.active_storage == src && M.client) + cansee |= M + else + LAZYREMOVE(is_using, M) + return cansee + +//Tries to dump content +/datum/component/storage/proc/dump_content_at(atom/dest_object, mob/M) + var/atom/A = parent + var/atom/dump_destination = dest_object.get_dumping_location() + if(A.Adjacent(M) && dump_destination && M.Adjacent(dump_destination)) + if(check_locked(null, M, TRUE)) + return FALSE + if(dump_destination.storage_contents_dump_act(src, M)) + playsound(A, "rustle", 50, 1, -5) + A.do_squish(0.8, 1.2) + return TRUE + return FALSE + +//This proc is called when you want to place an item into the storage item. +/datum/component/storage/proc/attackby(datum/source, obj/item/I, mob/M, params) + if(istype(I, /obj/item/hand_labeler)) + var/obj/item/hand_labeler/labeler = I + if(labeler.mode) + return FALSE + . = TRUE //no afterattack + if(iscyborg(M)) + return + if(!can_be_inserted(I, FALSE, M)) + var/atom/real_location = real_location() + if(real_location.contents.len >= max_items) //don't use items on the backpack if they don't fit + return TRUE + return FALSE + handle_item_insertion(I, FALSE, M) + var/atom/A = parent + A.do_squish() + +/datum/component/storage/proc/return_inv(recursive) + var/list/ret = list() + ret |= contents() + if(recursive) + for(var/i in ret.Copy()) + var/atom/A = i + SEND_SIGNAL(A, COMSIG_TRY_STORAGE_RETURN_INVENTORY, ret, TRUE) + return ret + +/datum/component/storage/proc/contents() //ONLY USE IF YOU NEED TO COPY CONTENTS OF REAL LOCATION, COPYING IS NOT AS FAST AS DIRECT ACCESS! + var/atom/real_location = real_location() + return real_location.contents.Copy() + +//Abuses the fact that lists are just references, or something like that. +/datum/component/storage/proc/signal_return_inv(datum/source, list/interface, recursive = TRUE) + if(!islist(interface)) + return FALSE + interface |= return_inv(recursive) + return TRUE + +/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M) + set waitfor = FALSE + . = COMPONENT_NO_MOUSEDROP + var/atom/A = parent + if(ismob(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked + A.add_fingerprint(M) + if(!over_object) + return FALSE + if(ismecha(M.loc)) // stops inventory actions in a mech + return FALSE + // this must come before the screen objects only block, dunno why it wasn't before + if(over_object == M) + user_show_to_mob(M) + if(!M.incapacitated()) + if(!istype(over_object, /obj/screen)) + dump_content_at(over_object, M) + return + if(A.loc != M) + return + playsound(A, "rustle", 50, 1, -5) + A.do_jiggle() + if(istype(over_object, /obj/screen/inventory/hand)) + var/obj/screen/inventory/hand/H = over_object + M.putItemFromInventoryInHandIfPossible(A, H.held_index) + return + A.add_fingerprint(M) + +/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE) + var/atom/A = parent + if(!istype(M)) + return FALSE + A.add_fingerprint(M) + if(!force && (check_locked(null, M) || !M.CanReach(parent, view_only = TRUE))) + return FALSE + show_to(M) + +/datum/component/storage/proc/mousedrop_receive(datum/source, atom/movable/O, mob/M) + if(isitem(O)) + var/obj/item/I = O + if(iscarbon(M) || isdrone(M)) + var/mob/living/L = M + if(!L.incapacitated() && I == L.get_active_held_item()) + if(!SEND_SIGNAL(I, COMSIG_CONTAINS_STORAGE) && can_be_inserted(I, FALSE)) //If it has storage it should be trying to dump, not insert. + handle_item_insertion(I, FALSE, L) + var/atom/A = parent + A.do_squish() + +//This proc return 1 if the item can be picked up and 0 if it can't. +//Set the stop_messages to stop it from printing messages +/datum/component/storage/proc/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M) + if(!istype(I) || (I.item_flags & ABSTRACT)) + return FALSE //Not an item + if(I == parent) + return FALSE //no paradoxes for you + var/atom/real_location = real_location() + var/atom/host = parent + if(real_location == I.loc) + return FALSE //Means the item is already in the storage item + if(check_locked(null, M, !stop_messages)) + if(M && !stop_messages) + host.add_fingerprint(M) + return FALSE + if(real_location.contents.len >= max_items) + if(!stop_messages) + to_chat(M, "[host] is full, make some space!") + return FALSE //Storage item is full + if(length(can_hold)) + if(!is_type_in_typecache(I, can_hold)) + if(!stop_messages) + to_chat(M, "[host] cannot hold [I]!") + return FALSE + if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold. + if(!stop_messages) + to_chat(M, "[host] cannot hold [I]!") + return FALSE + if(I.w_class > max_w_class) + if(!stop_messages) + to_chat(M, "[I] is too big for [host]!") + return FALSE + var/sum_w_class = I.w_class + for(var/obj/item/_I in real_location) + sum_w_class += _I.w_class //Adds up the combined w_classes which will be in the storage item if the item is added to it. + if(sum_w_class > max_combined_w_class) + if(!stop_messages) + to_chat(M, "[I] won't fit in [host], make some space!") + return FALSE + if(isitem(host)) + var/obj/item/IP = host + var/datum/component/storage/STR_I = I.GetComponent(/datum/component/storage) + if((I.w_class >= IP.w_class) && STR_I && !allow_big_nesting) + if(!stop_messages) + to_chat(M, "[IP] cannot hold [I] as it's a storage item of the same size!") + return FALSE //To prevent the stacking of same sized storage items. + if(HAS_TRAIT(I, TRAIT_NODROP)) //SHOULD be handled in unEquip, but better safe than sorry. + to_chat(M, "\the [I] is stuck to your hand, you can't put it in \the [host]!") + return FALSE + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + return master.slave_can_insert_object(src, I, stop_messages, M) + +/datum/component/storage/proc/_insert_physical_item(obj/item/I, override = FALSE) + return FALSE + +//This proc handles items being inserted. It does not perform any checks of whether an item can or can't be inserted. That's done by can_be_inserted() +//The stop_warning parameter will stop the insertion message from being displayed. It is intended for cases where you are inserting multiple items at once, +//such as when picking up all the items on a tile with one click. +/datum/component/storage/proc/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) + var/atom/parent = src.parent + var/datum/component/storage/concrete/master = master() + if(!istype(master)) + return FALSE + if(silent) + prevent_warning = TRUE + if(M) + parent.add_fingerprint(M) + . = master.handle_item_insertion_from_slave(src, I, prevent_warning, M) + +/datum/component/storage/proc/mob_item_insertion_feedback(mob/user, mob/M, obj/item/I, override = FALSE) + if(silent && !override) + return + if(rustle_sound) + playsound(parent, "rustle", 50, 1, -5) + for(var/mob/viewing in viewers(user, null)) + if(M == viewing) + to_chat(usr, "You put [I] [insert_preposition]to [parent].") + else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) + else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance... + viewing.show_message("[M] puts [I] [insert_preposition]to [parent].", 1) + +/datum/component/storage/proc/update_icon() + if(isobj(parent)) + var/obj/O = parent + O.update_icon() + +/datum/component/storage/proc/signal_insertion_attempt(datum/source, obj/item/I, mob/M, silent = FALSE, force = FALSE) + if((!force && !can_be_inserted(I, TRUE, M)) || (I == parent)) + return FALSE + return handle_item_insertion(I, silent, M) + +/datum/component/storage/proc/signal_can_insert(datum/source, obj/item/I, mob/M, silent = FALSE) + return can_be_inserted(I, silent, M) + +/datum/component/storage/proc/show_to_ghost(datum/source, mob/dead/observer/M) + return user_show_to_mob(M, TRUE) + +/datum/component/storage/proc/signal_show_attempt(datum/source, mob/showto, force = FALSE) + return user_show_to_mob(showto, force) + +/datum/component/storage/proc/on_check() + return TRUE + +/datum/component/storage/proc/check_locked(datum/source, mob/user, message = FALSE) + . = locked + if(message && . && user) + to_chat(user, "[parent] seems to be locked!") + +/datum/component/storage/proc/signal_take_type(datum/source, type, atom/destination, amount = INFINITY, check_adjacent = FALSE, force = FALSE, mob/user, list/inserted) + if(!force) + if(check_adjacent) + if(!user || !user.CanReach(destination) || !user.CanReach(parent)) + return FALSE + var/list/taking = typecache_filter_list(contents(), typecacheof(type)) + if(taking.len > amount) + taking.len = amount + if(inserted) //duplicated code for performance, don't bother checking retval/checking for list every item. + for(var/i in taking) + if(remove_from_storage(i, destination)) + inserted |= i + else + for(var/i in taking) + remove_from_storage(i, destination) + return TRUE + +/datum/component/storage/proc/remaining_space_items() + var/atom/real_location = real_location() + return max(0, max_items - real_location.contents.len) + +/datum/component/storage/proc/signal_fill_type(datum/source, type, amount = 20, force = FALSE) + var/atom/real_location = real_location() + if(!force) + amount = min(remaining_space_items(), amount) + for(var/i in 1 to amount) + handle_item_insertion(new type(real_location), TRUE) + CHECK_TICK + return TRUE + +/datum/component/storage/proc/on_attack_hand(datum/source, mob/user) + var/atom/A = parent + if(!attack_hand_interact) + return + if(user.active_storage == src && A.loc == user) //if you're already looking inside the storage item + user.active_storage.close(user) + close(user) + . = COMPONENT_NO_ATTACK_HAND + return + + if(rustle_sound) + playsound(A, "rustle", 50, 1, -5) + + if(ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.l_store == A && !H.get_active_held_item()) //Prevents opening if it's in a pocket. + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.l_store = null + return + if(H.r_store == A && !H.get_active_held_item()) + . = COMPONENT_NO_ATTACK_HAND + H.put_in_hands(A) + H.r_store = null + return + + if(A.loc == user) + . = COMPONENT_NO_ATTACK_HAND + if(!check_locked(source, user, TRUE)) + show_to(user) + A.do_jiggle() + +/datum/component/storage/proc/signal_on_pickup(datum/source, mob/user) + var/atom/A = parent + update_actions() + for(var/mob/M in range(1, A)) + if(M.active_storage == src) + close(M) + +/datum/component/storage/proc/signal_take_obj(datum/source, atom/movable/AM, new_loc, force = FALSE) + if(!(AM in real_location())) + return FALSE + return remove_from_storage(AM, new_loc) + +/datum/component/storage/proc/signal_quick_empty(datum/source, atom/loctarget) + return do_quick_empty(loctarget) + +/datum/component/storage/proc/signal_hide_attempt(datum/source, mob/target) + return hide_from(target) + +/datum/component/storage/proc/on_alt_click(datum/source, mob/user) + if(!isliving(user) || !user.CanReach(parent)) + return + if(check_locked(source, user, TRUE)) + return TRUE + + var/atom/A = parent + if(!quickdraw) + A.add_fingerprint(user) + user_show_to_mob(user) + if(rustle_sound) + playsound(A, "rustle", 50, 1, -5) + return TRUE + + if(user.can_hold_items() && !user.incapacitated()) + var/obj/item/I = locate() in real_location() + if(!I) + return + A.add_fingerprint(user) + remove_from_storage(I, get_turf(user)) + if(!user.put_in_hands(I)) + user.visible_message("[user] fumbles with the [parent], letting [I] fall on the floor.", \ + "You fumble with [parent], letting [I] fall on the floor.") + return TRUE + user.visible_message("[user] draws [I] from [parent]!", "You draw [I] from [parent].") + return TRUE + +/datum/component/storage/proc/action_trigger(datum/signal_source, datum/action/source) + gather_mode_switch(source.owner) + return COMPONENT_ACTION_BLOCK_TRIGGER + +/datum/component/storage/proc/gather_mode_switch(mob/user) + collection_mode = (collection_mode+1)%3 + switch(collection_mode) + if(COLLECT_SAME) + to_chat(user, "[parent] now picks up all items of a single type at once.") + if(COLLECT_EVERYTHING) + to_chat(user, "[parent] now picks up all items in a tile at once.") + if(COLLECT_ONE) + to_chat(user, "[parent] now picks up one item at a time.") diff --git a/code/game/machinery/Sleeper.dm b/code/game/machinery/Sleeper.dm index 18306ff6..03f580a9 100644 --- a/code/game/machinery/Sleeper.dm +++ b/code/game/machinery/Sleeper.dm @@ -181,12 +181,14 @@ open_machine() /obj/machinery/sleeper/AltClick(mob/user) + . = ..() if(!user.canUseTopic(src, !issilicon(user))) return if(state_open) close_machine() else open_machine() + return TRUE /obj/machinery/sleeper/examine(mob/user) . = ..() diff --git a/code/game/machinery/aug_manipulator.dm b/code/game/machinery/aug_manipulator.dm index 0bbb79e6..72655cde 100644 --- a/code/game/machinery/aug_manipulator.dm +++ b/code/game/machinery/aug_manipulator.dm @@ -132,8 +132,8 @@ ..() if(!user.canUseTopic(src)) return - else - eject_part(user) + eject_part(user) + return TRUE /obj/machinery/aug_manipulator/power_change() ..() diff --git a/code/game/machinery/computer/buildandrepair.dm b/code/game/machinery/computer/buildandrepair.dm index 5bad1b49..46aec8c1 100644 --- a/code/game/machinery/computer/buildandrepair.dm +++ b/code/game/machinery/computer/buildandrepair.dm @@ -1,143 +1,144 @@ -/obj/structure/frame/computer - name = "computer frame" - icon_state = "0" - state = 0 - -/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) - add_fingerprint(user) - switch(state) - if(0) - if(istype(P, /obj/item/wrench)) - to_chat(user, "You start wrenching the frame into place...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You wrench the frame into place.") - setAnchored(TRUE) - state = 1 - return - if(istype(P, /obj/item/weldingtool)) - if(!P.tool_start_check(user, amount=0)) - return - - to_chat(user, "You start deconstructing the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You deconstruct the frame.") - var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) - M.add_fingerprint(user) - qdel(src) - return - if(1) - if(istype(P, /obj/item/wrench)) - to_chat(user, "You start to unfasten the frame...") - if(P.use_tool(src, user, 20, volume=50)) - to_chat(user, "You unfasten the frame.") - setAnchored(FALSE) - state = 0 - return - if(istype(P, /obj/item/circuitboard/computer) && !circuit) - if(!user.transferItemToLoc(P, src)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You place [P] inside the frame.") - icon_state = "1" - circuit = P - circuit.add_fingerprint(user) - return - - else if(istype(P, /obj/item/circuitboard) && !circuit) - to_chat(user, "This frame does not accept circuit boards of this type!") - return - if(istype(P, /obj/item/screwdriver) && circuit) - P.play_tool_sound(src) - to_chat(user, "You screw [circuit] into place.") - state = 2 - icon_state = "2" - return - if(istype(P, /obj/item/crowbar) && circuit) - P.play_tool_sound(src) - to_chat(user, "You remove [circuit].") - state = 1 - icon_state = "0" - circuit.forceMove(drop_location()) - circuit.add_fingerprint(user) - circuit = null - return - if(2) - if(istype(P, /obj/item/screwdriver) && circuit) - P.play_tool_sound(src) - to_chat(user, "You unfasten the circuit board.") - state = 1 - icon_state = "1" - return - if(istype(P, /obj/item/stack/cable_coil)) - if(!P.tool_start_check(user, amount=5)) - return - to_chat(user, "You start adding cables to the frame...") - if(P.use_tool(src, user, 20, volume=50, amount=5)) - if(state != 2) - return - to_chat(user, "You add cables to the frame.") - state = 3 - icon_state = "3" - return - if(3) - if(istype(P, /obj/item/wirecutters)) - P.play_tool_sound(src) - to_chat(user, "You remove the cables.") - state = 2 - icon_state = "2" - var/obj/item/stack/cable_coil/A = new (drop_location(), 5) - A.add_fingerprint(user) - return - - if(istype(P, /obj/item/stack/sheet/glass)) - if(!P.tool_start_check(user, amount=2)) - return - playsound(src, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You start to put in the glass panel...") - if(P.use_tool(src, user, 20, amount=2)) - if(state != 3) - return - to_chat(user, "You put in the glass panel.") - state = 4 - src.icon_state = "4" - return - if(4) - if(istype(P, /obj/item/crowbar)) - P.play_tool_sound(src) - to_chat(user, "You remove the glass panel.") - state = 3 - icon_state = "3" - var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) - G.add_fingerprint(user) - return - if(istype(P, /obj/item/screwdriver)) - P.play_tool_sound(src) - to_chat(user, "You connect the monitor.") - var/obj/B = new circuit.build_path (loc, circuit) - B.setDir(dir) - transfer_fingerprints_to(B) - qdel(src) - return - if(user.a_intent == INTENT_HARM) - return ..() - - -/obj/structure/frame/computer/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(state == 4) - new /obj/item/shard(drop_location()) - new /obj/item/shard(drop_location()) - if(state >= 3) - new /obj/item/stack/cable_coil(drop_location(), 5) - ..() - -/obj/structure/frame/computer/AltClick(mob/user) - ..() - if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - - if(anchored) - to_chat(usr, "You must unwrench [src] before rotating it!") - return - - setDir(turn(dir, -90)) +/obj/structure/frame/computer + name = "computer frame" + icon_state = "0" + state = 0 + +/obj/structure/frame/computer/attackby(obj/item/P, mob/user, params) + add_fingerprint(user) + switch(state) + if(0) + if(istype(P, /obj/item/wrench)) + to_chat(user, "You start wrenching the frame into place...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You wrench the frame into place.") + setAnchored(TRUE) + state = 1 + return + if(istype(P, /obj/item/weldingtool)) + if(!P.tool_start_check(user, amount=0)) + return + + to_chat(user, "You start deconstructing the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You deconstruct the frame.") + var/obj/item/stack/sheet/metal/M = new (drop_location(), 5) + M.add_fingerprint(user) + qdel(src) + return + if(1) + if(istype(P, /obj/item/wrench)) + to_chat(user, "You start to unfasten the frame...") + if(P.use_tool(src, user, 20, volume=50)) + to_chat(user, "You unfasten the frame.") + setAnchored(FALSE) + state = 0 + return + if(istype(P, /obj/item/circuitboard/computer) && !circuit) + if(!user.transferItemToLoc(P, src)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You place [P] inside the frame.") + icon_state = "1" + circuit = P + circuit.add_fingerprint(user) + return + + else if(istype(P, /obj/item/circuitboard) && !circuit) + to_chat(user, "This frame does not accept circuit boards of this type!") + return + if(istype(P, /obj/item/screwdriver) && circuit) + P.play_tool_sound(src) + to_chat(user, "You screw [circuit] into place.") + state = 2 + icon_state = "2" + return + if(istype(P, /obj/item/crowbar) && circuit) + P.play_tool_sound(src) + to_chat(user, "You remove [circuit].") + state = 1 + icon_state = "0" + circuit.forceMove(drop_location()) + circuit.add_fingerprint(user) + circuit = null + return + if(2) + if(istype(P, /obj/item/screwdriver) && circuit) + P.play_tool_sound(src) + to_chat(user, "You unfasten the circuit board.") + state = 1 + icon_state = "1" + return + if(istype(P, /obj/item/stack/cable_coil)) + if(!P.tool_start_check(user, amount=5)) + return + to_chat(user, "You start adding cables to the frame...") + if(P.use_tool(src, user, 20, volume=50, amount=5)) + if(state != 2) + return + to_chat(user, "You add cables to the frame.") + state = 3 + icon_state = "3" + return + if(3) + if(istype(P, /obj/item/wirecutters)) + P.play_tool_sound(src) + to_chat(user, "You remove the cables.") + state = 2 + icon_state = "2" + var/obj/item/stack/cable_coil/A = new (drop_location(), 5) + A.add_fingerprint(user) + return + + if(istype(P, /obj/item/stack/sheet/glass)) + if(!P.tool_start_check(user, amount=2)) + return + playsound(src, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You start to put in the glass panel...") + if(P.use_tool(src, user, 20, amount=2)) + if(state != 3) + return + to_chat(user, "You put in the glass panel.") + state = 4 + src.icon_state = "4" + return + if(4) + if(istype(P, /obj/item/crowbar)) + P.play_tool_sound(src) + to_chat(user, "You remove the glass panel.") + state = 3 + icon_state = "3" + var/obj/item/stack/sheet/glass/G = new(drop_location(), 2) + G.add_fingerprint(user) + return + if(istype(P, /obj/item/screwdriver)) + P.play_tool_sound(src) + to_chat(user, "You connect the monitor.") + var/obj/B = new circuit.build_path (loc, circuit) + B.setDir(dir) + transfer_fingerprints_to(B) + qdel(src) + return + if(user.a_intent == INTENT_HARM) + return ..() + + +/obj/structure/frame/computer/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(state == 4) + new /obj/item/shard(drop_location()) + new /obj/item/shard(drop_location()) + if(state >= 3) + new /obj/item/stack/cable_coil(drop_location(), 5) + ..() + +/obj/structure/frame/computer/AltClick(mob/user) + . = ..() + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + + if(anchored) + to_chat(usr, "You must unwrench [src] before rotating it!") + return TRUE + + setDir(turn(dir, -90)) + return TRUE diff --git a/code/game/machinery/computer/card.dm b/code/game/machinery/computer/card.dm index eaecc9a9..7f771959 100644 --- a/code/game/machinery/computer/card.dm +++ b/code/game/machinery/computer/card.dm @@ -1,629 +1,629 @@ - - -//Keeps track of the time for the ID console. Having it as a global variable prevents people from dismantling/reassembling it to -//increase the slots of many jobs. -GLOBAL_VAR_INIT(time_last_changed_position, 0) - -#define JOB_ALLOWED 1 -#define JOB_COOLDOWN -2 -#define JOB_MAX_POSITIONS -1 // Trying to reduce the number of slots below that of current holders of that job, or trying to open more slots than allowed -#define JOB_DENIED 0 - -/obj/machinery/computer/card - name = "identification console" - desc = "You can use this to manage jobs and ID access." - icon_screen = "id" - icon_keyboard = "id_key" - req_one_access = list(ACCESS_HEADS, ACCESS_CHANGE_IDS) - circuit = /obj/item/circuitboard/computer/card - var/mode = 0 - var/printing = null - var/target_dept = 0 //Which department this computer has access to. 0=all departments - - //Cooldown for closing positions in seconds - //if set to -1: No cooldown... probably a bad idea - //if set to 0: Not able to close "original" positions. You can only close positions that you have opened before - var/change_position_cooldown = 30 - //Jobs you cannot open new positions for - var/list/blacklisted = list( - "AI", - "Assistant", - "Cyborg", - "Captain", - "Head of Personnel", - "Head of Security", - "Chief Engineer", - "Research Director", - "Chief Medical Officer", - "Quartermaster") - - //The scaling factor of max total positions in relation to the total amount of people on board the station in % - var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players - - //This is used to keep track of opened positions for jobs to allow instant closing - //Assoc array: "JobName" = (int) - var/list/opened_positions = list() - var/obj/item/card/id/inserted_scan_id - var/obj/item/card/id/inserted_modify_id - var/list/region_access = null - var/list/head_subordinates = null - - light_color = LIGHT_COLOR_BLUE - -/obj/machinery/computer/card/proc/get_jobs() - return get_all_jobs() - -/obj/machinery/computer/card/centcom/get_jobs() - return get_all_centcom_jobs() - -/obj/machinery/computer/card/Initialize() - . = ..() - change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) - -/obj/machinery/computer/card/examine(mob/user) - . = ..() - if(inserted_scan_id || inserted_modify_id) - . += "Alt-click to eject the ID card." - -/obj/machinery/computer/card/attackby(obj/I, mob/user, params) - if(isidcard(I)) - if(check_access(I) && !inserted_scan_id) - if(id_insert(user, I, inserted_scan_id)) - inserted_scan_id = I - updateUsrDialog() - else if(id_insert(user, I, inserted_modify_id)) - inserted_modify_id = I - updateUsrDialog() - else - return ..() - -/obj/machinery/computer/card/Destroy() - if(inserted_scan_id) - qdel(inserted_scan_id) - inserted_scan_id = null - if(inserted_modify_id) - qdel(inserted_modify_id) - inserted_modify_id = null - return ..() - -/obj/machinery/computer/card/handle_atom_del(atom/A) - ..() - if(A == inserted_scan_id) - inserted_scan_id = null - updateUsrDialog() - if(A == inserted_modify_id) - inserted_modify_id = null - updateUsrDialog() - -/obj/machinery/computer/card/on_deconstruction() - if(inserted_scan_id) - inserted_scan_id.forceMove(drop_location()) - inserted_scan_id = null - if(inserted_modify_id) - inserted_modify_id.forceMove(drop_location()) - inserted_modify_id = null - -//Check if you can't open a new position for a certain job -/obj/machinery/computer/card/proc/job_blacklisted(jobtitle) - return (jobtitle in blacklisted) - - -//Logic check for Topic() if you can open the job -/obj/machinery/computer/card/proc/can_open_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100))) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) - return JOB_ALLOWED - return JOB_COOLDOWN - return JOB_MAX_POSITIONS - return JOB_DENIED - -//Logic check for Topic() if you can close the job -/obj/machinery/computer/card/proc/can_close_job(datum/job/job) - if(job) - if(!job_blacklisted(job.title)) - if(job.total_positions > job.current_positions) - var/delta = (world.time / 10) - GLOB.time_last_changed_position - if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) - return JOB_ALLOWED - return JOB_COOLDOWN - return JOB_MAX_POSITIONS - return JOB_DENIED - -/obj/machinery/computer/card/proc/id_insert(mob/user, obj/item/inserting_item, obj/item/target) - var/obj/item/card/id/card_to_insert = inserting_item - var/holder_item = FALSE - - if(!isidcard(card_to_insert)) - card_to_insert = inserting_item.RemoveID() - holder_item = TRUE - - if(!card_to_insert || !user.transferItemToLoc(card_to_insert, src)) - return FALSE - - if(target) - if(holder_item && inserting_item.InsertID(target)) - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - else - id_eject(user, target) - - user.visible_message("[user] inserts \the [card_to_insert] into \the [src].", - "You insert \the [card_to_insert] into \the [src].") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - updateUsrDialog() - return TRUE - -/obj/machinery/computer/card/proc/id_eject(mob/user, obj/target) - if(!target) - to_chat(user, "That slot is empty!") - return FALSE - else - target.forceMove(drop_location()) - if(!issilicon(user) && Adjacent(user)) - user.put_in_hands(target) - user.visible_message("[user] gets \the [target] from \the [src].", \ - "You get \the [target] from \the [src].") - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - updateUsrDialog() - return TRUE - -/obj/machinery/computer/card/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) - return - if(inserted_modify_id) - if(id_eject(user, inserted_modify_id)) - inserted_modify_id = null - updateUsrDialog() - return - if(inserted_scan_id) - if(id_eject(user, inserted_scan_id)) - inserted_scan_id = null - updateUsrDialog() - return - -/obj/machinery/computer/card/ui_interact(mob/user) - . = ..() - var/list/dat = list() - if (mode == 1) // accessing crew manifest - dat += "Crew Manifest:
Please use security record computer to modify entries.

" - for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - dat += {"[t.fields["name"]] - [t.fields["rank"]]
"} - dat += "Print

Access ID modification console.
" - - else if(mode == 2) - // JOB MANAGEMENT - dat += {"Return - - "} - for(var/datum/job/job in SSjob.occupations) - dat += "" - if(job.title in blacklisted) - continue - dat += {" - - " - dat += "
JobSlotsOpen jobClose jobPrioritize
[job.title][job.current_positions]/[job.total_positions]"} - switch(can_open_job(job)) - if(JOB_ALLOWED) - if(authenticated == 2) - dat += "Open Position
" - else - dat += "Open Position" - if(JOB_COOLDOWN) - var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) - var/mins = round(time_to_wait / 60) - var/seconds = time_to_wait - (60*mins) - dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" - else - dat += "Denied" - dat += "
" - switch(can_close_job(job)) - if(JOB_ALLOWED) - if(authenticated == 2) - dat += "Close Position" - else - dat += "Close Position" - if(JOB_COOLDOWN) - var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) - var/mins = round(time_to_wait / 60) - var/seconds = time_to_wait - (60*mins) - dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" - else - dat += "Denied" - dat += "" - switch(job.total_positions) - if(0) - dat += "Denied" - else - if(authenticated == 2) - if(job in SSjob.prioritized_jobs) - dat += "Deprioritize" - else - if(SSjob.prioritized_jobs.len < 5) - dat += "Prioritize" - else - dat += "Denied" - else - dat += "Prioritize" - - dat += "
" - else - var/list/header = list() - var/scan_name = inserted_scan_id ? html_encode(inserted_scan_id.name) : "--------" - var/target_name = inserted_modify_id ? html_encode(inserted_modify_id.name) : "--------" - var/target_owner = (inserted_modify_id && inserted_modify_id.registered_name) ? html_encode(inserted_modify_id.registered_name) : "--------" - var/target_rank = (inserted_modify_id && inserted_modify_id.assignment) ? html_encode(inserted_modify_id.assignment) : "Unassigned" - - if(!authenticated) - header += {"
Please insert the cards into the slots
- Target: [target_name]
- Confirm Identity: [scan_name]
"} - else - header += {"

- Target: Remove [target_name] || - Confirm Identity: Remove [scan_name]
- Access Crew Manifest
- [!target_dept ? "Job Management
" : ""] - Log Out
"} - - header += "
" - - var/body - - if (authenticated && inserted_modify_id) - var/list/carddesc = list() - var/list/jobs = list() - if (authenticated == 2) - var/list/jobs_all = list() - for(var/job in (list("Unassigned") + get_jobs() + "Custom")) - jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job - carddesc += {""} - carddesc += {"
- - - registered name: - -
- Assignment: "} - - jobs += "[target_rank]" //CHECK THIS - - else - carddesc += "registered_name: [target_owner]" - jobs += "Assignment: [target_rank] (Demote)" - - var/list/accesses = list() - if(istype(src, /obj/machinery/computer/card/centcom)) //REE - accesses += "
Central Command:
" - for(var/A in get_all_centcom_access()) - if(A in inserted_modify_id.access) - accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " - else - accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " - else - accesses += {"
Access
- - "} - for(var/i = 1; i <= 7; i++) - if(authenticated == 1 && !(i in region_access)) - continue - accesses += "" - accesses += "" - for(var/i = 1; i <= 7; i++) - if(authenticated == 1 && !(i in region_access)) - continue - accesses += "" - accesses += "
[get_region_accesses_name(i)]:
" - for(var/A in get_region_accesses(i)) - if(A in inserted_modify_id.access) - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - else - accesses += "[replacetext(get_access_desc(A), " ", " ")] " - accesses += "
" - accesses += "
" - body = "[carddesc.Join()]
[jobs.Join()]

[accesses.Join()]
" //CHECK THIS - - else if (!authenticated) - body = {"Log In

- Access Crew Manifest

"} - if(!target_dept) - body += "Job Management
" - - dat = list("", header.Join(), body, "
") - var/datum/browser/popup = new(user, "id_com", src.name, 900, 620) - popup.set_content(dat.Join()) - popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) - popup.open() - -/obj/machinery/computer/card/Topic(href, href_list) - if(..()) - return - - if(!usr.canUseTopic(src, !issilicon(usr)) || !is_operational()) - usr.unset_machine() - usr << browse(null, "window=id_com") - return - - usr.set_machine(src) - switch(href_list["choice"]) - if ("inserted_modify_id") - if(inserted_modify_id && !usr.get_active_held_item()) - if(id_eject(usr, inserted_modify_id)) - inserted_modify_id = null - updateUsrDialog() - return - if(usr.get_id_in_hand()) - var/obj/item/held_item = usr.get_active_held_item() - var/obj/item/card/id/id_to_insert = held_item.GetID() - if(id_insert(usr, held_item, inserted_modify_id)) - inserted_modify_id = id_to_insert - updateUsrDialog() - if ("inserted_scan_id") - if(inserted_scan_id && !usr.get_active_held_item()) - if(id_eject(usr, inserted_scan_id)) - inserted_scan_id = null - updateUsrDialog() - return - if(usr.get_id_in_hand()) - var/obj/item/held_item = usr.get_active_held_item() - var/obj/item/card/id/id_to_insert = held_item.GetID() - if(id_insert(usr, held_item, inserted_scan_id)) - inserted_scan_id = id_to_insert - updateUsrDialog() - if ("auth") - if ((!( authenticated ) && (inserted_scan_id || issilicon(usr)) || mode)) - if (check_access(inserted_scan_id)) - region_access = list() - head_subordinates = list() - if(ACCESS_CHANGE_IDS in inserted_scan_id.access) - if(target_dept) - head_subordinates = get_all_jobs() - region_access |= target_dept - authenticated = 1 - else - authenticated = 2 - playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) - - else - if((ACCESS_HOP in inserted_scan_id.access) && ((target_dept==1) || !target_dept)) - region_access |= 1 - get_subordinates("Head of Personnel") - if((ACCESS_HOS in inserted_scan_id.access) && ((target_dept==2) || !target_dept)) - region_access |= 2 - get_subordinates("Head of Security") - if((ACCESS_CMO in inserted_scan_id.access) && ((target_dept==3) || !target_dept)) - region_access |= 3 - get_subordinates("Chief Medical Officer") - if((ACCESS_RD in inserted_scan_id.access) && ((target_dept==4) || !target_dept)) - region_access |= 4 - get_subordinates("Research Director") - if((ACCESS_CE in inserted_scan_id.access) && ((target_dept==5) || !target_dept)) - region_access |= 5 - get_subordinates("Chief Engineer") - if((ACCESS_QM in inserted_scan_id.access) && ((target_dept==6) || !target_dept)) - region_access |= 6 - get_subordinates("Quartermaster") - if(region_access) - authenticated = 1 - else if ((!( authenticated ) && issilicon(usr)) && (!inserted_modify_id)) - to_chat(usr, "You can't modify an ID without an ID inserted to modify! Once one is in the modify slot on the computer, you can log in.") - if ("logout") - region_access = null - head_subordinates = null - authenticated = 0 - playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) - - if("access") - if(href_list["allowed"]) - if(authenticated) - var/access_type = text2num(href_list["access_target"]) - var/access_allowed = text2num(href_list["allowed"]) - if(access_type in (istype(src, /obj/machinery/computer/card/centcom)?get_all_centcom_access() : get_all_accesses())) - inserted_modify_id.access -= access_type - if(access_allowed == 1) - inserted_modify_id.access += access_type - playsound(src, "terminal_type", 50, FALSE) - if ("assign") - if (authenticated == 2) - var/t1 = href_list["assign_target"] - if(t1 == "Custom") - var/newJob = reject_bad_text(input("Enter a custom job assignment.", "Assignment", inserted_modify_id ? inserted_modify_id.assignment : "Unassigned"), MAX_NAME_LEN) - if(newJob) - t1 = newJob - - else if(t1 == "Unassigned") - inserted_modify_id.access -= get_all_accesses() - - else - var/datum/job/jobdatum - for(var/jobtype in typesof(/datum/job)) - var/datum/job/J = new jobtype - if(ckey(J.title) == ckey(t1)) - jobdatum = J - updateUsrDialog() - break - if(!jobdatum) - to_chat(usr, "No log exists for this job.") - updateUsrDialog() - return - - inserted_modify_id.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) - if (inserted_modify_id) - inserted_modify_id.assignment = t1 - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - if ("demote") - if(inserted_modify_id.assignment in head_subordinates || inserted_modify_id.assignment == "Assistant") - inserted_modify_id.assignment = "Unassigned" - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else - to_chat(usr, "You are not authorized to demote this position.") - if ("reg") - if (authenticated) - var/t2 = inserted_modify_id - if ((authenticated && inserted_modify_id == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) - var/newName = reject_bad_name(href_list["reg"]) - if(newName) - inserted_modify_id.registered_name = newName - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - else - to_chat(usr, "Invalid name entered.") - updateUsrDialog() - return - if ("mode") - mode = text2num(href_list["mode_target"]) - - if("return") - //DISPLAY MAIN MENU - mode = 3; - playsound(src, "terminal_type", 25, FALSE) - - if("make_job_available") - // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS - if(authenticated && !target_dept) - var/edit_job_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(edit_job_target) - if(!j) - updateUsrDialog() - return 0 - if(can_open_job(j) != 1) - updateUsrDialog() - return 0 - if(opened_positions[edit_job_target] >= 0) - GLOB.time_last_changed_position = world.time / 10 - j.total_positions++ - opened_positions[edit_job_target]++ - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - - if("make_job_unavailable") - // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS - if(authenticated && !target_dept) - var/edit_job_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(edit_job_target) - if(!j) - updateUsrDialog() - return 0 - if(can_close_job(j) != 1) - updateUsrDialog() - return 0 - //Allow instant closing without cooldown if a position has been opened before - if(opened_positions[edit_job_target] <= 0) - GLOB.time_last_changed_position = world.time / 10 - j.total_positions-- - opened_positions[edit_job_target]-- - playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) - - if ("prioritize_job") - // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY - if(authenticated && !target_dept) - var/priority_target = href_list["job"] - var/datum/job/j = SSjob.GetJob(priority_target) - if(!j) - updateUsrDialog() - return 0 - var/priority = TRUE - if(j in SSjob.prioritized_jobs) - SSjob.prioritized_jobs -= j - priority = FALSE - else if(j.total_positions <= j.current_positions) - to_chat(usr, "[j.title] has had all positions filled. Open up more slots before prioritizing it.") - updateUsrDialog() - return - else - SSjob.prioritized_jobs += j - to_chat(usr, "[j.title] has been successfully [priority ? "prioritized" : "unprioritized"]. Potential employees will notice your request.") - playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) - - if ("print") - if (!( printing )) - printing = 1 - sleep(50) - var/obj/item/paper/P = new /obj/item/paper( loc ) - var/t1 = "Crew Manifest:
" - for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) - t1 += t.fields["name"] + " - " + t.fields["rank"] + "
" - P.info = t1 - P.name = "paper- 'Crew Manifest'" - printing = null - playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) - if (inserted_modify_id) - inserted_modify_id.update_label() - updateUsrDialog() - -/obj/machinery/computer/card/proc/get_subordinates(rank) - for(var/datum/job/job in SSjob.occupations) - if(rank in job.department_head) - head_subordinates += job.title - -/obj/machinery/computer/card/centcom - name = "\improper CentCom identification console" - circuit = /obj/item/circuitboard/computer/card/centcom - req_access = list(ACCESS_CENT_CAPTAIN) - -/obj/machinery/computer/card/minor - name = "department management console" - desc = "You can use this to change ID's for specific departments." - icon_screen = "idminor" - circuit = /obj/item/circuitboard/computer/card/minor - -/obj/machinery/computer/card/minor/Initialize() - . = ..() - var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit - if(target_dept) - typed_circuit.target_dept = target_dept - else - target_dept = typed_circuit.target_dept - var/list/dept_list = list("civilian","security","medical","science","engineering","cargo") - name = "[dept_list[target_dept]] department console" - -/obj/machinery/computer/card/minor/hos - target_dept = 2 - icon_screen = "idhos" - - light_color = LIGHT_COLOR_RED - -/obj/machinery/computer/card/minor/cmo - target_dept = 3 - icon_screen = "idcmo" - -/obj/machinery/computer/card/minor/rd - target_dept = 4 - icon_screen = "idrd" - - light_color = LIGHT_COLOR_PINK - -/obj/machinery/computer/card/minor/ce - target_dept = 5 - icon_screen = "idce" - - light_color = LIGHT_COLOR_YELLOW - -/obj/machinery/computer/card/minor/qm - target_dept = 6 - icon_screen = "idqm" - - light_color = LIGHT_COLOR_ORANGE - -#undef JOB_ALLOWED -#undef JOB_COOLDOWN -#undef JOB_MAX_POSITIONS -#undef JOB_DENIED + + +//Keeps track of the time for the ID console. Having it as a global variable prevents people from dismantling/reassembling it to +//increase the slots of many jobs. +GLOBAL_VAR_INIT(time_last_changed_position, 0) + +#define JOB_ALLOWED 1 +#define JOB_COOLDOWN -2 +#define JOB_MAX_POSITIONS -1 // Trying to reduce the number of slots below that of current holders of that job, or trying to open more slots than allowed +#define JOB_DENIED 0 + +/obj/machinery/computer/card + name = "identification console" + desc = "You can use this to manage jobs and ID access." + icon_screen = "id" + icon_keyboard = "id_key" + req_one_access = list(ACCESS_HEADS, ACCESS_CHANGE_IDS) + circuit = /obj/item/circuitboard/computer/card + var/mode = 0 + var/printing = null + var/target_dept = 0 //Which department this computer has access to. 0=all departments + + //Cooldown for closing positions in seconds + //if set to -1: No cooldown... probably a bad idea + //if set to 0: Not able to close "original" positions. You can only close positions that you have opened before + var/change_position_cooldown = 30 + //Jobs you cannot open new positions for + var/list/blacklisted = list( + "AI", + "Assistant", + "Cyborg", + "Captain", + "Head of Personnel", + "Head of Security", + "Chief Engineer", + "Research Director", + "Chief Medical Officer", + "Quartermaster") + + //The scaling factor of max total positions in relation to the total amount of people on board the station in % + var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players + + //This is used to keep track of opened positions for jobs to allow instant closing + //Assoc array: "JobName" = (int) + var/list/opened_positions = list() + var/obj/item/card/id/inserted_scan_id + var/obj/item/card/id/inserted_modify_id + var/list/region_access = null + var/list/head_subordinates = null + + light_color = LIGHT_COLOR_BLUE + +/obj/machinery/computer/card/proc/get_jobs() + return get_all_jobs() + +/obj/machinery/computer/card/centcom/get_jobs() + return get_all_centcom_jobs() + +/obj/machinery/computer/card/Initialize() + . = ..() + change_position_cooldown = CONFIG_GET(number/id_console_jobslot_delay) + +/obj/machinery/computer/card/examine(mob/user) + . = ..() + if(inserted_scan_id || inserted_modify_id) + . += "Alt-click to eject the ID card." + +/obj/machinery/computer/card/attackby(obj/I, mob/user, params) + if(isidcard(I)) + if(check_access(I) && !inserted_scan_id) + if(id_insert(user, I, inserted_scan_id)) + inserted_scan_id = I + updateUsrDialog() + else if(id_insert(user, I, inserted_modify_id)) + inserted_modify_id = I + updateUsrDialog() + else + return ..() + +/obj/machinery/computer/card/Destroy() + if(inserted_scan_id) + qdel(inserted_scan_id) + inserted_scan_id = null + if(inserted_modify_id) + qdel(inserted_modify_id) + inserted_modify_id = null + return ..() + +/obj/machinery/computer/card/handle_atom_del(atom/A) + ..() + if(A == inserted_scan_id) + inserted_scan_id = null + updateUsrDialog() + if(A == inserted_modify_id) + inserted_modify_id = null + updateUsrDialog() + +/obj/machinery/computer/card/on_deconstruction() + if(inserted_scan_id) + inserted_scan_id.forceMove(drop_location()) + inserted_scan_id = null + if(inserted_modify_id) + inserted_modify_id.forceMove(drop_location()) + inserted_modify_id = null + +//Check if you can't open a new position for a certain job +/obj/machinery/computer/card/proc/job_blacklisted(jobtitle) + return (jobtitle in blacklisted) + + +//Logic check for Topic() if you can open the job +/obj/machinery/computer/card/proc/can_open_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if((job.total_positions <= GLOB.player_list.len * (max_relative_positions / 100))) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] < 0)) + return JOB_ALLOWED + return JOB_COOLDOWN + return JOB_MAX_POSITIONS + return JOB_DENIED + +//Logic check for Topic() if you can close the job +/obj/machinery/computer/card/proc/can_close_job(datum/job/job) + if(job) + if(!job_blacklisted(job.title)) + if(job.total_positions > job.current_positions) + var/delta = (world.time / 10) - GLOB.time_last_changed_position + if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) + return JOB_ALLOWED + return JOB_COOLDOWN + return JOB_MAX_POSITIONS + return JOB_DENIED + +/obj/machinery/computer/card/proc/id_insert(mob/user, obj/item/inserting_item, obj/item/target) + var/obj/item/card/id/card_to_insert = inserting_item + var/holder_item = FALSE + + if(!isidcard(card_to_insert)) + card_to_insert = inserting_item.RemoveID() + holder_item = TRUE + + if(!card_to_insert || !user.transferItemToLoc(card_to_insert, src)) + return FALSE + + if(target) + if(holder_item && inserting_item.InsertID(target)) + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + else + id_eject(user, target) + + user.visible_message("[user] inserts \the [card_to_insert] into \the [src].", + "You insert \the [card_to_insert] into \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + updateUsrDialog() + return TRUE + +/obj/machinery/computer/card/proc/id_eject(mob/user, obj/target) + if(!target) + to_chat(user, "That slot is empty!") + return FALSE + else + target.forceMove(drop_location()) + if(!issilicon(user) && Adjacent(user)) + user.put_in_hands(target) + user.visible_message("[user] gets \the [target] from \the [src].", \ + "You get \the [target] from \the [src].") + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + updateUsrDialog() + return TRUE + +/obj/machinery/computer/card/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, !issilicon(user)) || !is_operational()) + return + if(inserted_modify_id) + if(id_eject(user, inserted_modify_id)) + inserted_modify_id = null + updateUsrDialog() + return TRUE + if(inserted_scan_id) + if(id_eject(user, inserted_scan_id)) + inserted_scan_id = null + updateUsrDialog() + return TRUE + +/obj/machinery/computer/card/ui_interact(mob/user) + . = ..() + var/list/dat = list() + if (mode == 1) // accessing crew manifest + dat += "Crew Manifest:
Please use security record computer to modify entries.

" + for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + dat += {"[t.fields["name"]] - [t.fields["rank"]]
"} + dat += "Print

Access ID modification console.
" + + else if(mode == 2) + // JOB MANAGEMENT + dat += {"Return + + "} + for(var/datum/job/job in SSjob.occupations) + dat += "" + if(job.title in blacklisted) + continue + dat += {" + + " + dat += "
JobSlotsOpen jobClose jobPrioritize
[job.title][job.current_positions]/[job.total_positions]"} + switch(can_open_job(job)) + if(JOB_ALLOWED) + if(authenticated == 2) + dat += "Open Position
" + else + dat += "Open Position" + if(JOB_COOLDOWN) + var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) + var/mins = round(time_to_wait / 60) + var/seconds = time_to_wait - (60*mins) + dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" + else + dat += "Denied" + dat += "
" + switch(can_close_job(job)) + if(JOB_ALLOWED) + if(authenticated == 2) + dat += "Close Position" + else + dat += "Close Position" + if(JOB_COOLDOWN) + var/time_to_wait = round(change_position_cooldown - ((world.time / 10) - GLOB.time_last_changed_position), 1) + var/mins = round(time_to_wait / 60) + var/seconds = time_to_wait - (60*mins) + dat += "Cooldown ongoing: [mins]:[(seconds < 10) ? "0[seconds]" : "[seconds]"]" + else + dat += "Denied" + dat += "" + switch(job.total_positions) + if(0) + dat += "Denied" + else + if(authenticated == 2) + if(job in SSjob.prioritized_jobs) + dat += "Deprioritize" + else + if(SSjob.prioritized_jobs.len < 5) + dat += "Prioritize" + else + dat += "Denied" + else + dat += "Prioritize" + + dat += "
" + else + var/list/header = list() + var/scan_name = inserted_scan_id ? html_encode(inserted_scan_id.name) : "--------" + var/target_name = inserted_modify_id ? html_encode(inserted_modify_id.name) : "--------" + var/target_owner = (inserted_modify_id && inserted_modify_id.registered_name) ? html_encode(inserted_modify_id.registered_name) : "--------" + var/target_rank = (inserted_modify_id && inserted_modify_id.assignment) ? html_encode(inserted_modify_id.assignment) : "Unassigned" + + if(!authenticated) + header += {"
Please insert the cards into the slots
+ Target: [target_name]
+ Confirm Identity: [scan_name]
"} + else + header += {"

+ Target: Remove [target_name] || + Confirm Identity: Remove [scan_name]
+ Access Crew Manifest
+ [!target_dept ? "Job Management
" : ""] + Log Out
"} + + header += "
" + + var/body + + if (authenticated && inserted_modify_id) + var/list/carddesc = list() + var/list/jobs = list() + if (authenticated == 2) + var/list/jobs_all = list() + for(var/job in (list("Unassigned") + get_jobs() + "Custom")) + jobs_all += "[replacetext(job, " ", " ")] " //make sure there isn't a line break in the middle of a job + carddesc += {""} + carddesc += {"
+ + + registered name: + +
+ Assignment: "} + + jobs += "[target_rank]" //CHECK THIS + + else + carddesc += "registered_name: [target_owner]" + jobs += "Assignment: [target_rank] (Demote)" + + var/list/accesses = list() + if(istype(src, /obj/machinery/computer/card/centcom)) //REE + accesses += "
Central Command:
" + for(var/A in get_all_centcom_access()) + if(A in inserted_modify_id.access) + accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " + else + accesses += "[replacetext(get_centcom_access_desc(A), " ", " ")] " + else + accesses += {"
Access
+ + "} + for(var/i = 1; i <= 7; i++) + if(authenticated == 1 && !(i in region_access)) + continue + accesses += "" + accesses += "" + for(var/i = 1; i <= 7; i++) + if(authenticated == 1 && !(i in region_access)) + continue + accesses += "" + accesses += "
[get_region_accesses_name(i)]:
" + for(var/A in get_region_accesses(i)) + if(A in inserted_modify_id.access) + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + else + accesses += "[replacetext(get_access_desc(A), " ", " ")] " + accesses += "
" + accesses += "
" + body = "[carddesc.Join()]
[jobs.Join()]

[accesses.Join()]
" //CHECK THIS + + else if (!authenticated) + body = {"Log In

+ Access Crew Manifest

"} + if(!target_dept) + body += "Job Management
" + + dat = list("", header.Join(), body, "
") + var/datum/browser/popup = new(user, "id_com", src.name, 900, 620) + popup.set_content(dat.Join()) + popup.set_title_image(user.browse_rsc_icon(src.icon, src.icon_state)) + popup.open() + +/obj/machinery/computer/card/Topic(href, href_list) + if(..()) + return + + if(!usr.canUseTopic(src, !issilicon(usr)) || !is_operational()) + usr.unset_machine() + usr << browse(null, "window=id_com") + return + + usr.set_machine(src) + switch(href_list["choice"]) + if ("inserted_modify_id") + if(inserted_modify_id && !usr.get_active_held_item()) + if(id_eject(usr, inserted_modify_id)) + inserted_modify_id = null + updateUsrDialog() + return + if(usr.get_id_in_hand()) + var/obj/item/held_item = usr.get_active_held_item() + var/obj/item/card/id/id_to_insert = held_item.GetID() + if(id_insert(usr, held_item, inserted_modify_id)) + inserted_modify_id = id_to_insert + updateUsrDialog() + if ("inserted_scan_id") + if(inserted_scan_id && !usr.get_active_held_item()) + if(id_eject(usr, inserted_scan_id)) + inserted_scan_id = null + updateUsrDialog() + return + if(usr.get_id_in_hand()) + var/obj/item/held_item = usr.get_active_held_item() + var/obj/item/card/id/id_to_insert = held_item.GetID() + if(id_insert(usr, held_item, inserted_scan_id)) + inserted_scan_id = id_to_insert + updateUsrDialog() + if ("auth") + if ((!( authenticated ) && (inserted_scan_id || issilicon(usr)) || mode)) + if (check_access(inserted_scan_id)) + region_access = list() + head_subordinates = list() + if(ACCESS_CHANGE_IDS in inserted_scan_id.access) + if(target_dept) + head_subordinates = get_all_jobs() + region_access |= target_dept + authenticated = 1 + else + authenticated = 2 + playsound(src, 'sound/machines/terminal_on.ogg', 50, FALSE) + + else + if((ACCESS_HOP in inserted_scan_id.access) && ((target_dept==1) || !target_dept)) + region_access |= 1 + get_subordinates("Head of Personnel") + if((ACCESS_HOS in inserted_scan_id.access) && ((target_dept==2) || !target_dept)) + region_access |= 2 + get_subordinates("Head of Security") + if((ACCESS_CMO in inserted_scan_id.access) && ((target_dept==3) || !target_dept)) + region_access |= 3 + get_subordinates("Chief Medical Officer") + if((ACCESS_RD in inserted_scan_id.access) && ((target_dept==4) || !target_dept)) + region_access |= 4 + get_subordinates("Research Director") + if((ACCESS_CE in inserted_scan_id.access) && ((target_dept==5) || !target_dept)) + region_access |= 5 + get_subordinates("Chief Engineer") + if((ACCESS_QM in inserted_scan_id.access) && ((target_dept==6) || !target_dept)) + region_access |= 6 + get_subordinates("Quartermaster") + if(region_access) + authenticated = 1 + else if ((!( authenticated ) && issilicon(usr)) && (!inserted_modify_id)) + to_chat(usr, "You can't modify an ID without an ID inserted to modify! Once one is in the modify slot on the computer, you can log in.") + if ("logout") + region_access = null + head_subordinates = null + authenticated = 0 + playsound(src, 'sound/machines/terminal_off.ogg', 50, FALSE) + + if("access") + if(href_list["allowed"]) + if(authenticated) + var/access_type = text2num(href_list["access_target"]) + var/access_allowed = text2num(href_list["allowed"]) + if(access_type in (istype(src, /obj/machinery/computer/card/centcom)?get_all_centcom_access() : get_all_accesses())) + inserted_modify_id.access -= access_type + if(access_allowed == 1) + inserted_modify_id.access += access_type + playsound(src, "terminal_type", 50, FALSE) + if ("assign") + if (authenticated == 2) + var/t1 = href_list["assign_target"] + if(t1 == "Custom") + var/newJob = reject_bad_text(input("Enter a custom job assignment.", "Assignment", inserted_modify_id ? inserted_modify_id.assignment : "Unassigned"), MAX_NAME_LEN) + if(newJob) + t1 = newJob + + else if(t1 == "Unassigned") + inserted_modify_id.access -= get_all_accesses() + + else + var/datum/job/jobdatum + for(var/jobtype in typesof(/datum/job)) + var/datum/job/J = new jobtype + if(ckey(J.title) == ckey(t1)) + jobdatum = J + updateUsrDialog() + break + if(!jobdatum) + to_chat(usr, "No log exists for this job.") + updateUsrDialog() + return + + inserted_modify_id.access = ( istype(src, /obj/machinery/computer/card/centcom) ? get_centcom_access(t1) : jobdatum.get_access() ) + if (inserted_modify_id) + inserted_modify_id.assignment = t1 + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + if ("demote") + if(inserted_modify_id.assignment in head_subordinates || inserted_modify_id.assignment == "Assistant") + inserted_modify_id.assignment = "Unassigned" + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else + to_chat(usr, "You are not authorized to demote this position.") + if ("reg") + if (authenticated) + var/t2 = inserted_modify_id + if ((authenticated && inserted_modify_id == t2 && (in_range(src, usr) || issilicon(usr)) && isturf(loc))) + var/newName = reject_bad_name(href_list["reg"]) + if(newName) + inserted_modify_id.registered_name = newName + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + else + to_chat(usr, "Invalid name entered.") + updateUsrDialog() + return + if ("mode") + mode = text2num(href_list["mode_target"]) + + if("return") + //DISPLAY MAIN MENU + mode = 3; + playsound(src, "terminal_type", 25, FALSE) + + if("make_job_available") + // MAKE ANOTHER JOB POSITION AVAILABLE FOR LATE JOINERS + if(authenticated && !target_dept) + var/edit_job_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + updateUsrDialog() + return 0 + if(can_open_job(j) != 1) + updateUsrDialog() + return 0 + if(opened_positions[edit_job_target] >= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions++ + opened_positions[edit_job_target]++ + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + if("make_job_unavailable") + // MAKE JOB POSITION UNAVAILABLE FOR LATE JOINERS + if(authenticated && !target_dept) + var/edit_job_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(edit_job_target) + if(!j) + updateUsrDialog() + return 0 + if(can_close_job(j) != 1) + updateUsrDialog() + return 0 + //Allow instant closing without cooldown if a position has been opened before + if(opened_positions[edit_job_target] <= 0) + GLOB.time_last_changed_position = world.time / 10 + j.total_positions-- + opened_positions[edit_job_target]-- + playsound(src, 'sound/machines/terminal_prompt_deny.ogg', 50, FALSE) + + if ("prioritize_job") + // TOGGLE WHETHER JOB APPEARS AS PRIORITIZED IN THE LOBBY + if(authenticated && !target_dept) + var/priority_target = href_list["job"] + var/datum/job/j = SSjob.GetJob(priority_target) + if(!j) + updateUsrDialog() + return 0 + var/priority = TRUE + if(j in SSjob.prioritized_jobs) + SSjob.prioritized_jobs -= j + priority = FALSE + else if(j.total_positions <= j.current_positions) + to_chat(usr, "[j.title] has had all positions filled. Open up more slots before prioritizing it.") + updateUsrDialog() + return + else + SSjob.prioritized_jobs += j + to_chat(usr, "[j.title] has been successfully [priority ? "prioritized" : "unprioritized"]. Potential employees will notice your request.") + playsound(src, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) + + if ("print") + if (!( printing )) + printing = 1 + sleep(50) + var/obj/item/paper/P = new /obj/item/paper( loc ) + var/t1 = "Crew Manifest:
" + for(var/datum/data/record/t in sortRecord(GLOB.data_core.general)) + t1 += t.fields["name"] + " - " + t.fields["rank"] + "
" + P.info = t1 + P.name = "paper- 'Crew Manifest'" + printing = null + playsound(src, 'sound/machines/terminal_insert_disc.ogg', 50, FALSE) + if (inserted_modify_id) + inserted_modify_id.update_label() + updateUsrDialog() + +/obj/machinery/computer/card/proc/get_subordinates(rank) + for(var/datum/job/job in SSjob.occupations) + if(rank in job.department_head) + head_subordinates += job.title + +/obj/machinery/computer/card/centcom + name = "\improper CentCom identification console" + circuit = /obj/item/circuitboard/computer/card/centcom + req_access = list(ACCESS_CENT_CAPTAIN) + +/obj/machinery/computer/card/minor + name = "department management console" + desc = "You can use this to change ID's for specific departments." + icon_screen = "idminor" + circuit = /obj/item/circuitboard/computer/card/minor + +/obj/machinery/computer/card/minor/Initialize() + . = ..() + var/obj/item/circuitboard/computer/card/minor/typed_circuit = circuit + if(target_dept) + typed_circuit.target_dept = target_dept + else + target_dept = typed_circuit.target_dept + var/list/dept_list = list("civilian","security","medical","science","engineering","cargo") + name = "[dept_list[target_dept]] department console" + +/obj/machinery/computer/card/minor/hos + target_dept = 2 + icon_screen = "idhos" + + light_color = LIGHT_COLOR_RED + +/obj/machinery/computer/card/minor/cmo + target_dept = 3 + icon_screen = "idcmo" + +/obj/machinery/computer/card/minor/rd + target_dept = 4 + icon_screen = "idrd" + + light_color = LIGHT_COLOR_PINK + +/obj/machinery/computer/card/minor/ce + target_dept = 5 + icon_screen = "idce" + + light_color = LIGHT_COLOR_YELLOW + +/obj/machinery/computer/card/minor/qm + target_dept = 6 + icon_screen = "idqm" + + light_color = LIGHT_COLOR_ORANGE + +#undef JOB_ALLOWED +#undef JOB_COOLDOWN +#undef JOB_MAX_POSITIONS +#undef JOB_DENIED diff --git a/code/game/machinery/computer/prisoner/_prisoner.dm b/code/game/machinery/computer/prisoner/_prisoner.dm index d07c351a..dfadb73a 100644 --- a/code/game/machinery/computer/prisoner/_prisoner.dm +++ b/code/game/machinery/computer/prisoner/_prisoner.dm @@ -5,7 +5,7 @@ if(contained_id) contained_id.forceMove(get_turf(src)) return ..() - + /obj/machinery/computer/prisoner/examine(mob/user) . = ..() @@ -15,8 +15,9 @@ /obj/machinery/computer/prisoner/AltClick(mob/user) + ..() id_eject(user) - return ..() + return TRUE /obj/machinery/computer/prisoner/proc/id_insert(mob/user, obj/item/card/id/prisoner/P) if(istype(P)) diff --git a/code/game/machinery/defibrillator_mount.dm b/code/game/machinery/defibrillator_mount.dm index 97cc0f57..40ccc61b 100644 --- a/code/game/machinery/defibrillator_mount.dm +++ b/code/game/machinery/defibrillator_mount.dm @@ -115,8 +115,10 @@ return TRUE /obj/machinery/defibrillator_mount/AltClick(mob/living/carbon/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return + . = TRUE if(!defib) to_chat(user, "It'd be hard to remove a defib unit from a mount that has none.") return diff --git a/code/game/machinery/deployable.dm b/code/game/machinery/deployable.dm index f07cf38a..c0399b4b 100644 --- a/code/game/machinery/deployable.dm +++ b/code/game/machinery/deployable.dm @@ -152,9 +152,11 @@ . += "Alt-click to toggle modes." /obj/item/grenade/barrier/AltClick(mob/living/carbon/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return toggle_mode(user) + return TRUE /obj/item/grenade/barrier/proc/toggle_mode(mob/user) switch(mode) diff --git a/code/game/machinery/dish_drive.dm b/code/game/machinery/dish_drive.dm index 3aad8d87..7f21d1c1 100644 --- a/code/game/machinery/dish_drive.dm +++ b/code/game/machinery/dish_drive.dm @@ -97,8 +97,10 @@ do_the_dishes(TRUE) /obj/machinery/dish_drive/AltClick(mob/living/user) + . = ..() if(user.canUseTopic(src, !issilicon(user))) do_the_dishes(TRUE) + return TRUE /obj/machinery/dish_drive/proc/do_the_dishes(manual) if(!contents.len) diff --git a/code/game/machinery/dna_scanner.dm b/code/game/machinery/dna_scanner.dm index 7895fb8c..6a5cfab7 100644 --- a/code/game/machinery/dna_scanner.dm +++ b/code/game/machinery/dna_scanner.dm @@ -143,6 +143,13 @@ /obj/machinery/dna_scannernew/interact(mob/user) toggle_open(user) +/obj/machinery/dna_scannernew/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, !issilicon(user))) + return + interact(user) + return TRUE + /obj/machinery/dna_scannernew/MouseDrop_T(mob/target, mob/user) if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) return diff --git a/code/game/machinery/harvester.dm b/code/game/machinery/harvester.dm index 63321baa..7280ae8c 100644 --- a/code/game/machinery/harvester.dm +++ b/code/game/machinery/harvester.dm @@ -51,10 +51,12 @@ open_machine() /obj/machinery/harvester/AltClick(mob/user) + . = ..() if(harvesting || !user || !isliving(user) || state_open) return if(can_harvest()) start_harvest() + return TRUE /obj/machinery/harvester/proc/can_harvest() if(!powered(EQUIP) || state_open || !occupant || !iscarbon(occupant)) diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 5ea822f5..95f8e25b 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -1,282 +1,284 @@ -/obj/machinery/washing_machine - name = "washing machine" - desc = "Gets rid of those pesky bloodstains, or your money back!" - icon = 'icons/obj/machines/washing_machine.dmi' - icon_state = "wm_1_0" - density = TRUE - state_open = TRUE - var/busy = FALSE - var/bloody_mess = 0 - var/has_corgi = 0 - var/obj/item/color_source - var/max_wash_capacity = 5 - -/obj/machinery/washing_machine/examine(mob/user) - . = ..() - . += "Alt-click it to start a wash cycle." - -/obj/machinery/washing_machine/AltClick(mob/user) - if(!user.canUseTopic(src)) - return - - if(busy) - return - - if(state_open) - to_chat(user, "Close the door first") - return - - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first.") - return - - if(has_corgi) - bloody_mess = 1 - - busy = TRUE - update_icon() - addtimer(CALLBACK(src, .proc/wash_cycle), 200) - START_PROCESSING(SSfastprocess, src) - -/obj/machinery/washing_machine/process() - if (!busy) - animate(src, transform=matrix(), time=2) - return PROCESS_KILL - if (anchored) - if (prob(5)) - var/matrix/M = new - M.Translate(rand(-1, 1), rand(0, 1)) - animate(src, transform=M, time=1) - animate(transform=matrix(), time=1) - else - if (prob(1)) - step(src, pick(GLOB.cardinals)) - var/matrix/M = new - M.Translate(rand(-3, 3), rand(-1, 3)) - animate(src, transform=M, time=2) - -/obj/machinery/washing_machine/clean_blood() - ..() - if(!busy) - bloody_mess = FALSE - update_icon() - -/obj/machinery/washing_machine/proc/wash_cycle() - for(var/X in contents) - var/atom/movable/AM = X - SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) - AM.clean_blood() - AM.machine_wash(src) - - busy = FALSE - if(color_source) - qdel(color_source) - color_source = null - update_icon() - - -//what happens to this object when washed inside a washing machine -/atom/movable/proc/machine_wash(obj/machinery/washing_machine/WM) - return - -/obj/item/stack/sheet/hairlesshide/machine_wash(obj/machinery/washing_machine/WM) - new /obj/item/stack/sheet/wetleather(drop_location(), amount) - qdel(src) - -/obj/item/clothing/suit/hooded/ian_costume/machine_wash(obj/machinery/washing_machine/WM) - new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(loc) - qdel(src) - -/obj/item/paper/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - if(istype(WM.color_source, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/CR = WM.color_source - add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) - -/obj/item/reagents_containers/rag/towel/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - if(istype(WM.color_source, /obj/item/toy/crayon)) - var/obj/item/toy/crayon/CR = WM.color_source - add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) - -/mob/living/simple_animal/pet/dog/corgi/machine_wash(obj/machinery/washing_machine/WM) - gib() - -/obj/item/clothing/under/color/machine_wash(obj/machinery/washing_machine/WM) - jumpsuit_wash(WM) - -/obj/item/clothing/under/rank/machine_wash(obj/machinery/washing_machine/WM) - jumpsuit_wash(WM) - -/obj/item/clothing/under/proc/jumpsuit_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - var/obj/item/clothing/under/U - for(var/T in typesof(/obj/item/clothing/under/color)) - var/obj/item/clothing/under/color/J = T - if(wash_color == initial(J.item_color)) - U = J - break - if(!U) - for(var/T in typesof(/obj/item/clothing/under/rank)) - var/obj/item/clothing/under/rank/R = T - if(wash_color == initial(R.item_color)) - U = R - break - if(U) - item_state = initial(U.item_state) - icon_state = initial(U.icon_state) - item_color = wash_color - name = initial(U.name) - desc = "The colors are a bit dodgy." - can_adjust = initial(U.can_adjust) - if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable - toggle_jumpsuit_adjust() - -/obj/item/clothing/gloves/color/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/clothing/gloves/color)) - var/obj/item/clothing/gloves/color/G = T - if(wash_color == initial(G.item_color)) - item_state = initial(G.item_state) - icon_state = initial(G.icon_state) - item_color = wash_color - name = initial(G.name) - desc = "The colors are a bit dodgy." - break - -/obj/item/clothing/shoes/sneakers/machine_wash(obj/machinery/washing_machine/WM) - if(chained) - chained = 0 - slowdown = SHOES_SLOWDOWN - new /obj/item/restraints/handcuffs(loc) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/clothing/shoes/sneakers)) - var/obj/item/clothing/shoes/sneakers/S = T - if(wash_color == initial(S.item_color)) - icon_state = initial(S.icon_state) - item_color = wash_color - name = initial(S.name) - desc = "The colors are a bit dodgy." - break - -/obj/item/bedsheet/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/bedsheet)) - var/obj/item/bedsheet/B = T - if(wash_color == initial(B.item_color)) - icon_state = initial(B.icon_state) - item_color = wash_color - name = initial(B.name) - desc = "The colors are a bit dodgy." - break - -/obj/item/clothing/head/soft/machine_wash(obj/machinery/washing_machine/WM) - if(WM.color_source) - var/wash_color = WM.color_source.item_color - for(var/T in typesof(/obj/item/clothing/head/soft)) - var/obj/item/clothing/head/soft/H = T - if(wash_color == initial(H.item_color)) - icon_state = initial(H.icon_state) - item_color = wash_color - name = initial(H.name) - desc = "The colors are a bit dodgy." - break - - -/obj/machinery/washing_machine/relaymove(mob/user) - container_resist(user) - -/obj/machinery/washing_machine/container_resist(mob/living/user) - if(!busy) - add_fingerprint(user) - open_machine() - - - -/obj/machinery/washing_machine/update_icon() - cut_overlays() - if(busy) - icon_state = "wm_running_[bloody_mess]" - else if(bloody_mess) - icon_state = "wm_[state_open]_blood" - else - var/full = contents.len ? 1 : 0 - icon_state = "wm_[state_open]_[full]" - if(panel_open) - add_overlay("wm_panel") - -/obj/machinery/washing_machine/attackby(obj/item/W, mob/user, params) - if(panel_open && !busy && default_unfasten_wrench(user, W)) - return - - if(default_deconstruction_screwdriver(user, null, null, W)) - update_icon() - return - - if(istype(W, /obj/item/clothing/head/mob_holder)) - to_chat(user, "It's too unweildy to put in this way.") - return 1 - - else if(user.a_intent != INTENT_HARM) - - if (!state_open) - to_chat(user, "Open the door first!") - return 1 - - if(bloody_mess) - to_chat(user, "[src] must be cleaned up first.") - return 1 - - if(contents.len >= max_wash_capacity) - to_chat(user, "The washing machine is full!") - return 1 - - if(!user.transferItemToLoc(W, src)) - to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") - return 1 - - if(istype(W, /obj/item/toy/crayon) || istype(W, /obj/item/stamp)) - color_source = W - update_icon() - - else - return ..() - -/obj/machinery/washing_machine/attack_hand(mob/user) - . = ..() - if(.) - return - if(busy) - to_chat(user, "[src] is busy.") - return - - if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) - var/mob/living/L = user.pulling - if(L.buckled || L.has_buckled_mobs()) - return - if(state_open) - if(iscorgi(L)) - has_corgi = 1 - L.forceMove(src) - update_icon() - return - - if(!state_open) - open_machine() - else - state_open = FALSE //close the door - update_icon() - -/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/machinery/washing_machine/open_machine(drop = 1) - ..() - density = TRUE //because machinery/open_machine() sets it to 0 - color_source = null - has_corgi = 0 +/obj/machinery/washing_machine + name = "washing machine" + desc = "Gets rid of those pesky bloodstains, or your money back!" + icon = 'icons/obj/machines/washing_machine.dmi' + icon_state = "wm_1_0" + density = TRUE + state_open = TRUE + var/busy = FALSE + var/bloody_mess = 0 + var/has_corgi = 0 + var/obj/item/color_source + var/max_wash_capacity = 5 + +/obj/machinery/washing_machine/examine(mob/user) + . = ..() + . += "Alt-click it to start a wash cycle." + +/obj/machinery/washing_machine/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src)) + return + + if(busy) + return + + if(state_open) + to_chat(user, "Close the door first") + return TRUE + + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first.") + return TRUE + + if(has_corgi) + bloody_mess = 1 + + busy = TRUE + update_icon() + addtimer(CALLBACK(src, .proc/wash_cycle), 200) + START_PROCESSING(SSfastprocess, src) + return TRUE + +/obj/machinery/washing_machine/process() + if (!busy) + animate(src, transform=matrix(), time=2) + return PROCESS_KILL + if (anchored) + if (prob(5)) + var/matrix/M = new + M.Translate(rand(-1, 1), rand(0, 1)) + animate(src, transform=M, time=1) + animate(transform=matrix(), time=1) + else + if (prob(1)) + step(src, pick(GLOB.cardinals)) + var/matrix/M = new + M.Translate(rand(-3, 3), rand(-1, 3)) + animate(src, transform=M, time=2) + +/obj/machinery/washing_machine/clean_blood() + ..() + if(!busy) + bloody_mess = FALSE + update_icon() + +/obj/machinery/washing_machine/proc/wash_cycle() + for(var/X in contents) + var/atom/movable/AM = X + SEND_SIGNAL(AM, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_WEAK) + AM.clean_blood() + AM.machine_wash(src) + + busy = FALSE + if(color_source) + qdel(color_source) + color_source = null + update_icon() + + +//what happens to this object when washed inside a washing machine +/atom/movable/proc/machine_wash(obj/machinery/washing_machine/WM) + return + +/obj/item/stack/sheet/hairlesshide/machine_wash(obj/machinery/washing_machine/WM) + new /obj/item/stack/sheet/wetleather(drop_location(), amount) + qdel(src) + +/obj/item/clothing/suit/hooded/ian_costume/machine_wash(obj/machinery/washing_machine/WM) + new /obj/item/reagent_containers/food/snacks/meat/slab/corgi(loc) + qdel(src) + +/obj/item/paper/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + if(istype(WM.color_source, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/CR = WM.color_source + add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) + +/obj/item/reagents_containers/rag/towel/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + if(istype(WM.color_source, /obj/item/toy/crayon)) + var/obj/item/toy/crayon/CR = WM.color_source + add_atom_colour(CR.paint_color, WASHABLE_COLOUR_PRIORITY) + +/mob/living/simple_animal/pet/dog/corgi/machine_wash(obj/machinery/washing_machine/WM) + gib() + +/obj/item/clothing/under/color/machine_wash(obj/machinery/washing_machine/WM) + jumpsuit_wash(WM) + +/obj/item/clothing/under/rank/machine_wash(obj/machinery/washing_machine/WM) + jumpsuit_wash(WM) + +/obj/item/clothing/under/proc/jumpsuit_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + var/obj/item/clothing/under/U + for(var/T in typesof(/obj/item/clothing/under/color)) + var/obj/item/clothing/under/color/J = T + if(wash_color == initial(J.item_color)) + U = J + break + if(!U) + for(var/T in typesof(/obj/item/clothing/under/rank)) + var/obj/item/clothing/under/rank/R = T + if(wash_color == initial(R.item_color)) + U = R + break + if(U) + item_state = initial(U.item_state) + icon_state = initial(U.icon_state) + item_color = wash_color + name = initial(U.name) + desc = "The colors are a bit dodgy." + can_adjust = initial(U.can_adjust) + if(!can_adjust && adjusted) //we deadjust the uniform if it's now unadjustable + toggle_jumpsuit_adjust() + +/obj/item/clothing/gloves/color/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/clothing/gloves/color)) + var/obj/item/clothing/gloves/color/G = T + if(wash_color == initial(G.item_color)) + item_state = initial(G.item_state) + icon_state = initial(G.icon_state) + item_color = wash_color + name = initial(G.name) + desc = "The colors are a bit dodgy." + break + +/obj/item/clothing/shoes/sneakers/machine_wash(obj/machinery/washing_machine/WM) + if(chained) + chained = 0 + slowdown = SHOES_SLOWDOWN + new /obj/item/restraints/handcuffs(loc) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/clothing/shoes/sneakers)) + var/obj/item/clothing/shoes/sneakers/S = T + if(wash_color == initial(S.item_color)) + icon_state = initial(S.icon_state) + item_color = wash_color + name = initial(S.name) + desc = "The colors are a bit dodgy." + break + +/obj/item/bedsheet/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/bedsheet)) + var/obj/item/bedsheet/B = T + if(wash_color == initial(B.item_color)) + icon_state = initial(B.icon_state) + item_color = wash_color + name = initial(B.name) + desc = "The colors are a bit dodgy." + break + +/obj/item/clothing/head/soft/machine_wash(obj/machinery/washing_machine/WM) + if(WM.color_source) + var/wash_color = WM.color_source.item_color + for(var/T in typesof(/obj/item/clothing/head/soft)) + var/obj/item/clothing/head/soft/H = T + if(wash_color == initial(H.item_color)) + icon_state = initial(H.icon_state) + item_color = wash_color + name = initial(H.name) + desc = "The colors are a bit dodgy." + break + + +/obj/machinery/washing_machine/relaymove(mob/user) + container_resist(user) + +/obj/machinery/washing_machine/container_resist(mob/living/user) + if(!busy) + add_fingerprint(user) + open_machine() + + + +/obj/machinery/washing_machine/update_icon() + cut_overlays() + if(busy) + icon_state = "wm_running_[bloody_mess]" + else if(bloody_mess) + icon_state = "wm_[state_open]_blood" + else + var/full = contents.len ? 1 : 0 + icon_state = "wm_[state_open]_[full]" + if(panel_open) + add_overlay("wm_panel") + +/obj/machinery/washing_machine/attackby(obj/item/W, mob/user, params) + if(panel_open && !busy && default_unfasten_wrench(user, W)) + return + + if(default_deconstruction_screwdriver(user, null, null, W)) + update_icon() + return + + if(istype(W, /obj/item/clothing/head/mob_holder)) + to_chat(user, "It's too unweildy to put in this way.") + return 1 + + else if(user.a_intent != INTENT_HARM) + + if (!state_open) + to_chat(user, "Open the door first!") + return 1 + + if(bloody_mess) + to_chat(user, "[src] must be cleaned up first.") + return 1 + + if(contents.len >= max_wash_capacity) + to_chat(user, "The washing machine is full!") + return 1 + + if(!user.transferItemToLoc(W, src)) + to_chat(user, "\The [W] is stuck to your hand, you cannot put it in the washing machine!") + return 1 + + if(istype(W, /obj/item/toy/crayon) || istype(W, /obj/item/stamp)) + color_source = W + update_icon() + + else + return ..() + +/obj/machinery/washing_machine/attack_hand(mob/user) + . = ..() + if(.) + return + if(busy) + to_chat(user, "[src] is busy.") + return + + if(user.pulling && user.a_intent == INTENT_GRAB && isliving(user.pulling)) + var/mob/living/L = user.pulling + if(L.buckled || L.has_buckled_mobs()) + return + if(state_open) + if(iscorgi(L)) + has_corgi = 1 + L.forceMove(src) + update_icon() + return + + if(!state_open) + open_machine() + else + state_open = FALSE //close the door + update_icon() + +/obj/machinery/washing_machine/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/machinery/washing_machine/open_machine(drop = 1) + ..() + density = TRUE //because machinery/open_machine() sets it to 0 + color_source = null + has_corgi = 0 diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm index 307e8d1b..83af041a 100644 --- a/code/game/mecha/mecha_actions.dm +++ b/code/game/mecha/mecha_actions.dm @@ -139,8 +139,10 @@ chassis.toggle_strafe() /obj/mecha/AltClick(mob/living/user) + . = ..() if((user == occupant) && user.canUseTopic(src)) toggle_strafe() + return TRUE /obj/mecha/proc/toggle_strafe() strafe = !strafe diff --git a/code/game/objects/items/cards_ids.dm b/code/game/objects/items/cards_ids.dm index 9422a979..b8cdb224 100644 --- a/code/game/objects/items/cards_ids.dm +++ b/code/game/objects/items/cards_ids.dm @@ -1,561 +1,562 @@ -/* Cards - * Contains: - * DATA CARD - * ID CARD - * FINGERPRINT CARD HOLDER - * FINGERPRINT CARD - */ - - - -/* - * DATA CARDS - Used for the IC data card reader - */ -/obj/item/card - name = "card" - desc = "Does card things." - icon = 'icons/obj/card.dmi' - w_class = WEIGHT_CLASS_TINY - - var/list/files = list() - -/obj/item/card/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins to swipe [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/card/data - name = "data card" - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has a stripe running down the middle." - icon_state = "data_1" - obj_flags = UNIQUE_RENAME - var/function = "storage" - var/data = "null" - var/special = null - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - var/detail_color = COLOR_ASSEMBLY_ORANGE - -/obj/item/card/data/Initialize() - .=..() - update_icon() - -/obj/item/card/data/update_icon() - cut_overlays() - if(detail_color == COLOR_FLOORTILE_GRAY) - return - var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/card.dmi', "[icon_state]-color") - detail_overlay.color = detail_color - add_overlay(detail_overlay) - -/obj/item/card/data/attackby(obj/item/I, mob/living/user) - if(istype(I, /obj/item/integrated_electronics/detailer)) - var/obj/item/integrated_electronics/detailer/D = I - detail_color = D.detail_color - update_icon() - return ..() - -/obj/item/proc/GetCard() - -/obj/item/card/data/GetCard() - return src - -/obj/item/card/data/full_color - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." - icon_state = "data_2" - -/obj/item/card/data/disk - desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one inexplicibly looks like a floppy disk." - icon_state = "data_3" - -/* - * ID CARDS - */ -/obj/item/card/emag - desc = "It's a card with a magnetic strip attached to some circuitry." - name = "cryptographic sequencer" - icon_state = "emag" - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - item_flags = NO_MAT_REDEMPTION | NOBLUDGEON - var/prox_check = TRUE //If the emag requires you to be in range - var/uses = 10 - -/obj/item/card/emag/bluespace - name = "bluespace cryptographic sequencer" - desc = "It's a blue card with a magnetic strip attached to some circuitry. It appears to have some sort of transmitter attached to it." - color = rgb(40, 130, 255) - prox_check = FALSE - -/obj/item/card/emag/attack() - return - -/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) - . = ..() - var/atom/A = target - if(!proximity && prox_check || !(isobj(A) || issilicon(A) || isbot(A) || isdrone(A))) - return - if(istype(A, /obj/item/storage) && !(istype(A, /obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod))) - return - if(!uses) - user.visible_message("[src] emits a weak spark. It's burnt out!") - playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) - return - else if(uses <= 3) - playsound(src, 'sound/effects/light_flicker.ogg', 30, 1) //Tiiiiiiny warning sound to let ya know your emag's almost dead - if(!A.emag_act(user)) - return - uses = max(uses - 1, 0) - if(!uses) - user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.") - playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) - -/obj/item/card/emag/examine(mob/user) - . = ..() - . += "It has [uses ? uses : "no"] charges left." - -/obj/item/card/emag/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/emagrecharge)) - var/obj/item/emagrecharge/ER = W - if(ER.uses) - uses += ER.uses - to_chat(user, "You have added [ER.uses] charges to [src]. It now has [uses] charges.") - playsound(src, "sparks", 100, 1) - ER.uses = 0 - else - to_chat(user, "[ER] has no charges left.") - return - . = ..() - -/obj/item/emagrecharge - name = "electromagnet charging device" - desc = "A small cell with two prongs lazily jabbed into it. It looks like it's made for charging the small batteries found in electromagnetic devices, sadly this can't be recharged like a normal cell." - icon = 'icons/obj/module.dmi' - icon_state = "cell_mini" - item_flags = NOBLUDGEON - var/uses = 5 //Dictates how many charges the device adds to compatible items - -/obj/item/emagrecharge/examine(mob/user) - . = ..() - if(uses) - . += "It can add up to [uses] charges to compatible devices" - else - . += "It has a small, red, blinking light coming from inside of it. It's spent." - -/obj/item/card/emagfake - desc = "It's a card with a magnetic strip attached to some circuitry. Closer inspection shows that this card is a poorly made replica, with a \"DonkCo\" logo stamped on the back." - name = "cryptographic sequencer" - icon_state = "emag" - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/emagfake/afterattack() - . = ..() - playsound(src, 'sound/items/bikehorn.ogg', 50, 1) - -/obj/item/card/id - name = "identification card" - desc = "A card used to provide ID and determine access across the station." - icon_state = "id" - item_state = "card-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - slot_flags = ITEM_SLOT_ID - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - var/mining_points = 0 //For redeeming at mining equipment vendors - var/list/access = list() - var/registered_name = null // The name registered_name on the card - var/assignment = null - var/access_txt // mapping aid - - - -/obj/item/card/id/Initialize(mapload) - . = ..() - if(mapload && access_txt) - access = text2access(access_txt) - -/obj/item/card/id/vv_edit_var(var_name, var_value) - . = ..() - if(.) - switch(var_name) - if("assignment","registered_name") - update_label() - -/obj/item/card/id/attack_self(mob/user) - if(Adjacent(user)) - user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name].", \ - "You show \the [src.name].") - add_fingerprint(user) - return - -/obj/item/card/id/examine(mob/user) - . = ..() - if(mining_points) - . += "There's [mining_points] mining equipment redemption point\s loaded onto this card." - -/obj/item/card/id/GetAccess() - return access - -/obj/item/card/id/GetID() - return src - -/obj/item/card/id/RemoveID() - return src - -/* -Usage: -update_label() - Sets the id name to whatever registered_name and assignment is - -update_label("John Doe", "Clowny") - Properly formats the name and occupation and sets the id name to the arguments -*/ -/obj/item/card/id/proc/update_label(newname, newjob) - if(newname || newjob) - name = "[(!newname) ? "identification card" : "[newname]'s ID Card"][(!newjob) ? "" : " ([newjob])"]" - return - - name = "[(!registered_name) ? "identification card" : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" - -/obj/item/card/id/silver - name = "silver identification card" - desc = "A silver card which shows honour and dedication." - icon_state = "silver" - item_state = "silver_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/id/silver/reaper - name = "Thirteen's ID Card (Reaper)" - access = list(ACCESS_MAINT_TUNNELS) - assignment = "Reaper" - registered_name = "Thirteen" - -/obj/item/card/id/gold - name = "gold identification card" - desc = "A golden card which shows power and might." - icon_state = "gold" - item_state = "gold_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - -/obj/item/card/id/syndicate - name = "agent card" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE) - var/anyone = FALSE //Can anyone forge the ID or just syndicate? - -/obj/item/card/id/syndicate/Initialize() - . = ..() - var/datum/action/item_action/chameleon/change/chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/card/id - chameleon_action.chameleon_name = "ID Card" - chameleon_action.initialize_disguises() - -/obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) - if(!proximity) - return - if(istype(O, /obj/item/card/id)) - var/obj/item/card/id/I = O - src.access |= I.access - if(isliving(user) && user.mind) - if(user.mind.special_role) - to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") - -/obj/item/card/id/syndicate/attack_self(mob/user) - if(isliving(user) && user.mind) - if(user.mind.special_role || anyone) - if(alert(user, "Action", "Agent ID", "Show", "Forge") == "Forge") - var/t = copytext(sanitize(input(user, "What name would you like to put on this card?", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name))as text | null),1,26) - if(!t || t == "Unknown" || t == "floor" || t == "wall" || t == "r-wall") //Same as mob/dead/new_player/prefrences.dm - if (t) - alert("Invalid name.") - return - registered_name = t - - var/u = copytext(sanitize(input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than Maintenance.", "Agent card job assignment", "Assistant")as text | null),1,MAX_MESSAGE_LEN) - if(!u) - registered_name = "" - return - assignment = u - update_label() - to_chat(user, "You successfully forge the ID card.") - return - ..() - -/obj/item/card/id/syndicate/anyone - anyone = TRUE - -/obj/item/card/id/syndicate/nuke_leader - name = "lead agent card" - access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) - -/obj/item/card/id/syndicate_command - name = "syndicate ID card" - desc = "An ID straight from the Syndicate." - registered_name = "Syndicate" - assignment = "Syndicate Overlord" - access = list(ACCESS_SYNDICATE) - -/obj/item/card/id/captains_spare - name = "captain's spare ID" - desc = "The spare ID of the High Lord himself." - icon_state = "gold" - item_state = "gold_id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - registered_name = "Captain" - assignment = "Captain" - -/obj/item/card/id/captains_spare/Initialize() - var/datum/job/captain/J = new/datum/job/captain - access = J.get_access() - . = ..() - -/obj/item/card/id/centcom - name = "\improper CentCom ID" - desc = "An ID straight from Central Command." - icon_state = "centcom" - registered_name = "Central Command" - assignment = "General" - -/obj/item/card/id/centcom/Initialize() - access = get_all_centcom_access() - . = ..() - -/obj/item/card/id/ert - name = "\improper CentCom ID" - desc = "An ERT ID card." - icon_state = "centcom" - registered_name = "Emergency Response Team Commander" - assignment = "Emergency Response Team Commander" - -/obj/item/card/id/ert/Initialize() - access = get_all_accesses()+get_ert_access("commander")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/Security - registered_name = "Security Response Officer" - assignment = "Security Response Officer" - -/obj/item/card/id/ert/Security/Initialize() - access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/Engineer - registered_name = "Engineer Response Officer" - assignment = "Engineer Response Officer" - -/obj/item/card/id/ert/Engineer/Initialize() - access = get_all_accesses()+get_ert_access("eng")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/Medical - registered_name = "Medical Response Officer" - assignment = "Medical Response Officer" - -/obj/item/card/id/ert/Medical/Initialize() - access = get_all_accesses()+get_ert_access("med")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/ert/chaplain - registered_name = "Religious Response Officer" - assignment = "Religious Response Officer" - -/obj/item/card/id/ert/chaplain/Initialize() - access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS - . = ..() - -/obj/item/card/id/prisoner - name = "prisoner ID card" - desc = "You are a number, you are not a free man." - icon_state = "orange" - item_state = "orange-id" - lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' - assignment = "Prisoner" - access = list(ACCESS_ENTER_GENPOP) - - //Lavaland labor camp - var/goal = 0 //How far from freedom? - var/points = 0 - //Genpop - var/sentence = 0 //When world.time is greater than this number, the card will have its ACCESS_ENTER_GENPOP access replaced with ACCESS_LEAVE_GENPOP the next time it's checked, unless this value is 0/null - var/crime= "\[REDACTED\]" - -/obj/item/card/id/prisoner/GetAccess() - if((sentence && world.time >= sentence) || (goal && points >= goal)) - access = list(ACCESS_LEAVE_GENPOP) - return ..() - -/obj/item/card/id/prisoner/process() - if(!sentence) - STOP_PROCESSING(SSobj, src) - return - if(world.time >= sentence) - playsound(loc, 'sound/machines/ping.ogg', 50, 1) - if(isliving(loc)) - to_chat(loc, "[src] buzzes: You have served your sentence! You may now exit prison through the turnstiles and collect your belongings.") - STOP_PROCESSING(SSobj, src) - return - -/obj/item/card/id/prisoner/examine(mob/user) - . = ..() - if(sentence && world.time < sentence) - . += "You're currently serving a sentence for [crime]. [DisplayTimeText(sentence - world.time)] left." - else if(goal) - . += "You have accumulated [points] out of the [goal] points you need for freedom." - else if(!sentence) - . += "You are currently serving a permanent sentence for [crime]." - else - . += "Your sentence is up! You're free!" - -/obj/item/card/id/prisoner/one - name = "Prisoner #13-001" - registered_name = "Prisoner #13-001" - -/obj/item/card/id/prisoner/two - name = "Prisoner #13-002" - registered_name = "Prisoner #13-002" - -/obj/item/card/id/prisoner/three - name = "Prisoner #13-003" - registered_name = "Prisoner #13-003" - -/obj/item/card/id/prisoner/four - name = "Prisoner #13-004" - registered_name = "Prisoner #13-004" - -/obj/item/card/id/prisoner/five - name = "Prisoner #13-005" - registered_name = "Prisoner #13-005" - -/obj/item/card/id/prisoner/six - name = "Prisoner #13-006" - registered_name = "Prisoner #13-006" - -/obj/item/card/id/prisoner/seven - name = "Prisoner #13-007" - registered_name = "Prisoner #13-007" - -/obj/item/card/id/mining - name = "mining ID" - access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) - -/obj/item/card/id/away - name = "a perfectly generic identification card" - desc = "A perfectly generic identification card. Looks like it could use some flavor." - access = list(ACCESS_AWAY_GENERAL) - -/obj/item/card/id/away/hotel - name = "Staff ID" - desc = "A staff ID used to access the hotel's doors." - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) - -/obj/item/card/id/away/hotel/securty - name = "Officer ID" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT, ACCESS_AWAY_SEC) - -/obj/item/card/id/away/old - name = "a perfectly generic identification card" - desc = "A perfectly generic identification card. Looks like it could use some flavor." - icon_state = "centcom" - -/obj/item/card/id/away/old/sec - name = "Charlie Station Security Officer's ID card" - desc = "A faded Charlie Station ID card. You can make out the rank \"Security Officer\"." - assignment = "Charlie Station Security Officer" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_SEC) - -/obj/item/card/id/away/old/sci - name = "Charlie Station Scientist's ID card" - desc = "A faded Charlie Station ID card. You can make out the rank \"Scientist\"." - assignment = "Charlie Station Scientist" - access = list(ACCESS_AWAY_GENERAL) - -/obj/item/card/id/away/old/eng - name = "Charlie Station Engineer's ID card" - desc = "A faded Charlie Station ID card. You can make out the rank \"Station Engineer\"." - assignment = "Charlie Station Engineer" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE) - -/obj/item/card/id/away/old/apc - name = "APC Access ID" - desc = "A special ID card that allows access to APC terminals." - access = list(ACCESS_ENGINE_EQUIP) - -//Polychromatic Knight Badge - -/obj/item/card/id/knight - var/id_color = "#00FF00" //defaults to green - name = "knight badge" - icon_state = "knight" - desc = "A badge denoting the owner as a knight! It has a strip for swiping like an ID" - -/obj/item/card/id/knight/update_label(newname, newjob) - if(newname || newjob) - name = "[(!newname) ? "knight badge" : "[newname]'s Knight Badge"][(!newjob) ? "" : " ([newjob])"]" - return - - name = "[(!registered_name) ? "knight badge" : "[registered_name]'s Knight Badge"][(!assignment) ? "" : " ([assignment])"]" - -/obj/item/card/id/knight/update_icon() - var/mutable_appearance/id_overlay = mutable_appearance(icon, "knight_overlay") - - if(id_color) - id_overlay.color = id_color - cut_overlays() - - add_overlay(id_overlay) - -/obj/item/card/id/knight/AltClick(mob/living/user) - . = ..() - if(!in_range(src, user)) //Basic checks to prevent abuse - return - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return - if(alert("Are you sure you want to recolor your id?", "Confirm Repaint", "Yes", "No") == "Yes") - var/energy_color_input = input(usr,"","Choose Energy Color",id_color) as color|null - if(!in_range(src, user) || !energy_color_input) - return - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return - id_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) - update_icon() - -/obj/item/card/id/knight/Initialize() - . = ..() - update_icon() - -/obj/item/card/id/knight/examine(mob/user) - . = ..() - . += "Alt-click to recolor it." - -/obj/item/card/id/knight/blue - id_color = "#0000FF" - -/obj/item/card/id/knight/captain - id_color = "#FFD700" - -/obj/item/card/id/away/snowdin/eng - name = "Arctic Station Engineer's ID card" - desc = "A faded Arctic Station ID card. You can make out the rank \"Station Engineer\"." - assignment = "Arctic Station Engineer" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE, ACCESS_AWAY_MAINT) - -/obj/item/card/id/away/snowdin/sci - name = "Arctic Station Scientist's ID card" - desc = "A faded Arctic Station ID card. You can make out the rank \"Scientist\"." - assignment = "Arctic Station Scientist" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) - -/obj/item/card/id/away/snowdin/med - name = "Arctic Station Doctor's ID card" - desc = "A faded Arctic Station ID card. You can make out the rank \"Doctor\"." - assignment = "Arctic Station Doctor" - access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MED, ACCESS_AWAY_MAINT) +/* Cards + * Contains: + * DATA CARD + * ID CARD + * FINGERPRINT CARD HOLDER + * FINGERPRINT CARD + */ + + + +/* + * DATA CARDS - Used for the IC data card reader + */ +/obj/item/card + name = "card" + desc = "Does card things." + icon = 'icons/obj/card.dmi' + w_class = WEIGHT_CLASS_TINY + + var/list/files = list() + +/obj/item/card/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins to swipe [user.p_their()] neck with \the [src]! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/card/data + name = "data card" + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has a stripe running down the middle." + icon_state = "data_1" + obj_flags = UNIQUE_RENAME + var/function = "storage" + var/data = "null" + var/special = null + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + var/detail_color = COLOR_ASSEMBLY_ORANGE + +/obj/item/card/data/Initialize() + .=..() + update_icon() + +/obj/item/card/data/update_icon() + cut_overlays() + if(detail_color == COLOR_FLOORTILE_GRAY) + return + var/mutable_appearance/detail_overlay = mutable_appearance('icons/obj/card.dmi', "[icon_state]-color") + detail_overlay.color = detail_color + add_overlay(detail_overlay) + +/obj/item/card/data/attackby(obj/item/I, mob/living/user) + if(istype(I, /obj/item/integrated_electronics/detailer)) + var/obj/item/integrated_electronics/detailer/D = I + detail_color = D.detail_color + update_icon() + return ..() + +/obj/item/proc/GetCard() + +/obj/item/card/data/GetCard() + return src + +/obj/item/card/data/full_color + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one has the entire card colored." + icon_state = "data_2" + +/obj/item/card/data/disk + desc = "A plastic magstripe card for simple and speedy data storage and transfer. This one inexplicibly looks like a floppy disk." + icon_state = "data_3" + +/* + * ID CARDS + */ +/obj/item/card/emag + desc = "It's a card with a magnetic strip attached to some circuitry." + name = "cryptographic sequencer" + icon_state = "emag" + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + item_flags = NO_MAT_REDEMPTION | NOBLUDGEON + var/prox_check = TRUE //If the emag requires you to be in range + var/uses = 10 + +/obj/item/card/emag/bluespace + name = "bluespace cryptographic sequencer" + desc = "It's a blue card with a magnetic strip attached to some circuitry. It appears to have some sort of transmitter attached to it." + color = rgb(40, 130, 255) + prox_check = FALSE + +/obj/item/card/emag/attack() + return + +/obj/item/card/emag/afterattack(atom/target, mob/user, proximity) + . = ..() + var/atom/A = target + if(!proximity && prox_check || !(isobj(A) || issilicon(A) || isbot(A) || isdrone(A))) + return + if(istype(A, /obj/item/storage) && !(istype(A, /obj/item/storage/lockbox) || istype(A, /obj/item/storage/pod))) + return + if(!uses) + user.visible_message("[src] emits a weak spark. It's burnt out!") + playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) + return + else if(uses <= 3) + playsound(src, 'sound/effects/light_flicker.ogg', 30, 1) //Tiiiiiiny warning sound to let ya know your emag's almost dead + if(!A.emag_act(user)) + return + uses = max(uses - 1, 0) + if(!uses) + user.visible_message("[src] fizzles and sparks. It seems like it's out of charges.") + playsound(src, 'sound/effects/light_flicker.ogg', 100, 1) + +/obj/item/card/emag/examine(mob/user) + . = ..() + . += "It has [uses ? uses : "no"] charges left." + +/obj/item/card/emag/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/emagrecharge)) + var/obj/item/emagrecharge/ER = W + if(ER.uses) + uses += ER.uses + to_chat(user, "You have added [ER.uses] charges to [src]. It now has [uses] charges.") + playsound(src, "sparks", 100, 1) + ER.uses = 0 + else + to_chat(user, "[ER] has no charges left.") + return + . = ..() + +/obj/item/emagrecharge + name = "electromagnet charging device" + desc = "A small cell with two prongs lazily jabbed into it. It looks like it's made for charging the small batteries found in electromagnetic devices, sadly this can't be recharged like a normal cell." + icon = 'icons/obj/module.dmi' + icon_state = "cell_mini" + item_flags = NOBLUDGEON + var/uses = 5 //Dictates how many charges the device adds to compatible items + +/obj/item/emagrecharge/examine(mob/user) + . = ..() + if(uses) + . += "It can add up to [uses] charges to compatible devices" + else + . += "It has a small, red, blinking light coming from inside of it. It's spent." + +/obj/item/card/emagfake + desc = "It's a card with a magnetic strip attached to some circuitry. Closer inspection shows that this card is a poorly made replica, with a \"DonkCo\" logo stamped on the back." + name = "cryptographic sequencer" + icon_state = "emag" + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/emagfake/afterattack() + . = ..() + playsound(src, 'sound/items/bikehorn.ogg', 50, 1) + +/obj/item/card/id + name = "identification card" + desc = "A card used to provide ID and determine access across the station." + icon_state = "id" + item_state = "card-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + slot_flags = ITEM_SLOT_ID + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + var/mining_points = 0 //For redeeming at mining equipment vendors + var/list/access = list() + var/registered_name = null // The name registered_name on the card + var/assignment = null + var/access_txt // mapping aid + + + +/obj/item/card/id/Initialize(mapload) + . = ..() + if(mapload && access_txt) + access = text2access(access_txt) + +/obj/item/card/id/vv_edit_var(var_name, var_value) + . = ..() + if(.) + switch(var_name) + if("assignment","registered_name") + update_label() + +/obj/item/card/id/attack_self(mob/user) + if(Adjacent(user)) + user.visible_message("[user] shows you: [icon2html(src, viewers(user))] [src.name].", \ + "You show \the [src.name].") + add_fingerprint(user) + return + +/obj/item/card/id/examine(mob/user) + . = ..() + if(mining_points) + . += "There's [mining_points] mining equipment redemption point\s loaded onto this card." + +/obj/item/card/id/GetAccess() + return access + +/obj/item/card/id/GetID() + return src + +/obj/item/card/id/RemoveID() + return src + +/* +Usage: +update_label() + Sets the id name to whatever registered_name and assignment is + +update_label("John Doe", "Clowny") + Properly formats the name and occupation and sets the id name to the arguments +*/ +/obj/item/card/id/proc/update_label(newname, newjob) + if(newname || newjob) + name = "[(!newname) ? "identification card" : "[newname]'s ID Card"][(!newjob) ? "" : " ([newjob])"]" + return + + name = "[(!registered_name) ? "identification card" : "[registered_name]'s ID Card"][(!assignment) ? "" : " ([assignment])"]" + +/obj/item/card/id/silver + name = "silver identification card" + desc = "A silver card which shows honour and dedication." + icon_state = "silver" + item_state = "silver_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/id/silver/reaper + name = "Thirteen's ID Card (Reaper)" + access = list(ACCESS_MAINT_TUNNELS) + assignment = "Reaper" + registered_name = "Thirteen" + +/obj/item/card/id/gold + name = "gold identification card" + desc = "A golden card which shows power and might." + icon_state = "gold" + item_state = "gold_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + +/obj/item/card/id/syndicate + name = "agent card" + access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE) + var/anyone = FALSE //Can anyone forge the ID or just syndicate? + +/obj/item/card/id/syndicate/Initialize() + . = ..() + var/datum/action/item_action/chameleon/change/chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/card/id + chameleon_action.chameleon_name = "ID Card" + chameleon_action.initialize_disguises() + +/obj/item/card/id/syndicate/afterattack(obj/item/O, mob/user, proximity) + if(!proximity) + return + if(istype(O, /obj/item/card/id)) + var/obj/item/card/id/I = O + src.access |= I.access + if(isliving(user) && user.mind) + if(user.mind.special_role) + to_chat(usr, "The card's microscanners activate as you pass it over the ID, copying its access.") + +/obj/item/card/id/syndicate/attack_self(mob/user) + if(isliving(user) && user.mind) + if(user.mind.special_role || anyone) + if(alert(user, "Action", "Agent ID", "Show", "Forge") == "Forge") + var/t = copytext(sanitize(input(user, "What name would you like to put on this card?", "Agent card name", registered_name ? registered_name : (ishuman(user) ? user.real_name : user.name))as text | null),1,26) + if(!t || t == "Unknown" || t == "floor" || t == "wall" || t == "r-wall") //Same as mob/dead/new_player/prefrences.dm + if (t) + alert("Invalid name.") + return + registered_name = t + + var/u = copytext(sanitize(input(user, "What occupation would you like to put on this card?\nNote: This will not grant any access levels other than Maintenance.", "Agent card job assignment", "Assistant")as text | null),1,MAX_MESSAGE_LEN) + if(!u) + registered_name = "" + return + assignment = u + update_label() + to_chat(user, "You successfully forge the ID card.") + return + ..() + +/obj/item/card/id/syndicate/anyone + anyone = TRUE + +/obj/item/card/id/syndicate/nuke_leader + name = "lead agent card" + access = list(ACCESS_MAINT_TUNNELS, ACCESS_SYNDICATE, ACCESS_SYNDICATE_LEADER) + +/obj/item/card/id/syndicate_command + name = "syndicate ID card" + desc = "An ID straight from the Syndicate." + registered_name = "Syndicate" + assignment = "Syndicate Overlord" + access = list(ACCESS_SYNDICATE) + +/obj/item/card/id/captains_spare + name = "captain's spare ID" + desc = "The spare ID of the High Lord himself." + icon_state = "gold" + item_state = "gold_id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + registered_name = "Captain" + assignment = "Captain" + +/obj/item/card/id/captains_spare/Initialize() + var/datum/job/captain/J = new/datum/job/captain + access = J.get_access() + . = ..() + +/obj/item/card/id/centcom + name = "\improper CentCom ID" + desc = "An ID straight from Central Command." + icon_state = "centcom" + registered_name = "Central Command" + assignment = "General" + +/obj/item/card/id/centcom/Initialize() + access = get_all_centcom_access() + . = ..() + +/obj/item/card/id/ert + name = "\improper CentCom ID" + desc = "An ERT ID card." + icon_state = "centcom" + registered_name = "Emergency Response Team Commander" + assignment = "Emergency Response Team Commander" + +/obj/item/card/id/ert/Initialize() + access = get_all_accesses()+get_ert_access("commander")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/Security + registered_name = "Security Response Officer" + assignment = "Security Response Officer" + +/obj/item/card/id/ert/Security/Initialize() + access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/Engineer + registered_name = "Engineer Response Officer" + assignment = "Engineer Response Officer" + +/obj/item/card/id/ert/Engineer/Initialize() + access = get_all_accesses()+get_ert_access("eng")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/Medical + registered_name = "Medical Response Officer" + assignment = "Medical Response Officer" + +/obj/item/card/id/ert/Medical/Initialize() + access = get_all_accesses()+get_ert_access("med")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/ert/chaplain + registered_name = "Religious Response Officer" + assignment = "Religious Response Officer" + +/obj/item/card/id/ert/chaplain/Initialize() + access = get_all_accesses()+get_ert_access("sec")-ACCESS_CHANGE_IDS + . = ..() + +/obj/item/card/id/prisoner + name = "prisoner ID card" + desc = "You are a number, you are not a free man." + icon_state = "orange" + item_state = "orange-id" + lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi' + assignment = "Prisoner" + access = list(ACCESS_ENTER_GENPOP) + + //Lavaland labor camp + var/goal = 0 //How far from freedom? + var/points = 0 + //Genpop + var/sentence = 0 //When world.time is greater than this number, the card will have its ACCESS_ENTER_GENPOP access replaced with ACCESS_LEAVE_GENPOP the next time it's checked, unless this value is 0/null + var/crime= "\[REDACTED\]" + +/obj/item/card/id/prisoner/GetAccess() + if((sentence && world.time >= sentence) || (goal && points >= goal)) + access = list(ACCESS_LEAVE_GENPOP) + return ..() + +/obj/item/card/id/prisoner/process() + if(!sentence) + STOP_PROCESSING(SSobj, src) + return + if(world.time >= sentence) + playsound(loc, 'sound/machines/ping.ogg', 50, 1) + if(isliving(loc)) + to_chat(loc, "[src] buzzes: You have served your sentence! You may now exit prison through the turnstiles and collect your belongings.") + STOP_PROCESSING(SSobj, src) + return + +/obj/item/card/id/prisoner/examine(mob/user) + . = ..() + if(sentence && world.time < sentence) + . += "You're currently serving a sentence for [crime]. [DisplayTimeText(sentence - world.time)] left." + else if(goal) + . += "You have accumulated [points] out of the [goal] points you need for freedom." + else if(!sentence) + . += "You are currently serving a permanent sentence for [crime]." + else + . += "Your sentence is up! You're free!" + +/obj/item/card/id/prisoner/one + name = "Prisoner #13-001" + registered_name = "Prisoner #13-001" + +/obj/item/card/id/prisoner/two + name = "Prisoner #13-002" + registered_name = "Prisoner #13-002" + +/obj/item/card/id/prisoner/three + name = "Prisoner #13-003" + registered_name = "Prisoner #13-003" + +/obj/item/card/id/prisoner/four + name = "Prisoner #13-004" + registered_name = "Prisoner #13-004" + +/obj/item/card/id/prisoner/five + name = "Prisoner #13-005" + registered_name = "Prisoner #13-005" + +/obj/item/card/id/prisoner/six + name = "Prisoner #13-006" + registered_name = "Prisoner #13-006" + +/obj/item/card/id/prisoner/seven + name = "Prisoner #13-007" + registered_name = "Prisoner #13-007" + +/obj/item/card/id/mining + name = "mining ID" + access = list(ACCESS_MINING, ACCESS_MINING_STATION, ACCESS_MAILSORTING, ACCESS_MINERAL_STOREROOM) + +/obj/item/card/id/away + name = "a perfectly generic identification card" + desc = "A perfectly generic identification card. Looks like it could use some flavor." + access = list(ACCESS_AWAY_GENERAL) + +/obj/item/card/id/away/hotel + name = "Staff ID" + desc = "A staff ID used to access the hotel's doors." + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) + +/obj/item/card/id/away/hotel/securty + name = "Officer ID" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT, ACCESS_AWAY_SEC) + +/obj/item/card/id/away/old + name = "a perfectly generic identification card" + desc = "A perfectly generic identification card. Looks like it could use some flavor." + icon_state = "centcom" + +/obj/item/card/id/away/old/sec + name = "Charlie Station Security Officer's ID card" + desc = "A faded Charlie Station ID card. You can make out the rank \"Security Officer\"." + assignment = "Charlie Station Security Officer" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_SEC) + +/obj/item/card/id/away/old/sci + name = "Charlie Station Scientist's ID card" + desc = "A faded Charlie Station ID card. You can make out the rank \"Scientist\"." + assignment = "Charlie Station Scientist" + access = list(ACCESS_AWAY_GENERAL) + +/obj/item/card/id/away/old/eng + name = "Charlie Station Engineer's ID card" + desc = "A faded Charlie Station ID card. You can make out the rank \"Station Engineer\"." + assignment = "Charlie Station Engineer" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE) + +/obj/item/card/id/away/old/apc + name = "APC Access ID" + desc = "A special ID card that allows access to APC terminals." + access = list(ACCESS_ENGINE_EQUIP) + +//Polychromatic Knight Badge + +/obj/item/card/id/knight + var/id_color = "#00FF00" //defaults to green + name = "knight badge" + icon_state = "knight" + desc = "A badge denoting the owner as a knight! It has a strip for swiping like an ID" + +/obj/item/card/id/knight/update_label(newname, newjob) + if(newname || newjob) + name = "[(!newname) ? "knight badge" : "[newname]'s Knight Badge"][(!newjob) ? "" : " ([newjob])"]" + return + + name = "[(!registered_name) ? "knight badge" : "[registered_name]'s Knight Badge"][(!assignment) ? "" : " ([assignment])"]" + +/obj/item/card/id/knight/update_icon() + var/mutable_appearance/id_overlay = mutable_appearance(icon, "knight_overlay") + + if(id_color) + id_overlay.color = id_color + cut_overlays() + + add_overlay(id_overlay) + +/obj/item/card/id/knight/AltClick(mob/living/user) + . = ..() + if(!in_range(src, user)) //Basic checks to prevent abuse + return + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + return TRUE + if(alert("Are you sure you want to recolor your id?", "Confirm Repaint", "Yes", "No") == "Yes") + var/energy_color_input = input(usr,"","Choose Energy Color",id_color) as color|null + if(!in_range(src, user) || !energy_color_input) + return TRUE + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + return TRUE + id_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) + update_icon() + return TRUE + +/obj/item/card/id/knight/Initialize() + . = ..() + update_icon() + +/obj/item/card/id/knight/examine(mob/user) + . = ..() + . += "Alt-click to recolor it." + +/obj/item/card/id/knight/blue + id_color = "#0000FF" + +/obj/item/card/id/knight/captain + id_color = "#FFD700" + +/obj/item/card/id/away/snowdin/eng + name = "Arctic Station Engineer's ID card" + desc = "A faded Arctic Station ID card. You can make out the rank \"Station Engineer\"." + assignment = "Arctic Station Engineer" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_ENGINE, ACCESS_AWAY_MAINT) + +/obj/item/card/id/away/snowdin/sci + name = "Arctic Station Scientist's ID card" + desc = "A faded Arctic Station ID card. You can make out the rank \"Scientist\"." + assignment = "Arctic Station Scientist" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MAINT) + +/obj/item/card/id/away/snowdin/med + name = "Arctic Station Doctor's ID card" + desc = "A faded Arctic Station ID card. You can make out the rank \"Doctor\"." + assignment = "Arctic Station Doctor" + access = list(ACCESS_AWAY_GENERAL, ACCESS_AWAY_MED, ACCESS_AWAY_MAINT) diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index 9e67633e..1a2dd434 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -974,10 +974,12 @@ to_chat(user, "You [suction ? "enable" : "disable"] the board's suction function.") /obj/item/circuitboard/machine/dish_drive/AltClick(mob/living/user) + . = ..() if(!user.Adjacent(src)) return transmit = !transmit to_chat(user, "You [transmit ? "enable" : "disable"] the board's automatic disposal transmission.") + return TRUE /obj/item/circuitboard/machine/stacking_unit_console name = "Stacking Machine Console (Machine Board)" diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 37b8e3ff..3573b4a4 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -1,1173 +1,1173 @@ - -//The advanced pea-green monochrome lcd of tomorrow. - -GLOBAL_LIST_EMPTY(PDAs) - -#define PDA_SCANNER_NONE 0 -#define PDA_SCANNER_MEDICAL 1 -#define PDA_SCANNER_FORENSICS 2 //unused -#define PDA_SCANNER_REAGENT 3 -#define PDA_SCANNER_HALOGEN 4 -#define PDA_SCANNER_GAS 5 -#define PDA_SPAM_DELAY 2 MINUTES -#define PDA_STANDARD_OVERLAYS list("pda-r", "blank", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay") - -//pda icon overlays list defines -#define PDA_OVERLAY_ALERT 1 -#define PDA_OVERLAY_SCREEN 2 -#define PDA_OVERLAY_ID 3 -#define PDA_OVERLAY_ITEM 4 -#define PDA_OVERLAY_LIGHT 5 -#define PDA_OVERLAY_PAI 6 - -/obj/item/pda - name = "\improper PDA" - desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." - icon = 'icons/obj/pda_alt.dmi' - icon_state = "pda" - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - item_flags = NOBLUDGEON - w_class = WEIGHT_CLASS_TINY - slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) - resistance_flags = FIRE_PROOF | ACID_PROOF - - - //Main variables - var/owner = null // String name of owner - var/default_cartridge = 0 // Access level defined by cartridge - var/obj/item/cartridge/cartridge = null //current cartridge - var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. - var/list/overlays_icons = list('icons/obj/pda_alt.dmi' = list("pda-r", "screen_default", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay")) - var/current_overlays = PDA_STANDARD_OVERLAYS - var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. - var/font_mode = "font-family:monospace;" //The currently selected font. - var/background_color = "#808000" //The currently selected background color. - - #define FONT_MONO "font-family:monospace;" - #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" - #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" - #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" - #define MODE_MONO 0 - #define MODE_SHARE 1 - #define MODE_ORBITRON 2 - #define MODE_VT 3 - - //Secondary variables - var/scanmode = PDA_SCANNER_NONE - var/fon = FALSE //Is the flashlight function on? - var/f_lum = 2.3 //Luminosity for the flashlight function - var/f_pow = 0.6 //Power for the flashlight function - var/f_col = "#FFCC66" //Color for the flashlight function - var/silent = FALSE //To beep or not to beep, that is the question - var/toff = FALSE //If TRUE, messenger disabled - var/tnote = null //Current Texts - var/last_text //No text spamming - var/last_everyone //No text for everyone spamming - var/last_noise //Also no honk spamming that's bad too - var/ttone = "beep" //The ringtone! - var/honkamt = 0 //How many honks left when infected with honk.exe - var/mimeamt = 0 //How many silence left when infected with mime.exe - var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant! To help with navigation, we have provided the following definitions. North: Fore. South: Aft. West: Port. East: Starboard. Quarter is either side of aft." //Current note in the notepad function - var/notehtml = "" - var/notescanned = FALSE // True if what is in the notekeeper was from a paper. - var/detonatable = TRUE // Can the PDA be blown up? - var/hidden = FALSE // Is the PDA hidden from the PDA list? - var/emped = FALSE - var/equipped = FALSE //used here to determine if this is the first time its been picked up - - var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. - var/ownjob = null //related to above - - var/obj/item/paicard/pai = null // A slot for a personal AI device - - var/datum/picture/picture //Scanned photo - - var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) - var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. - var/list/overlays_offsets // offsets to use for certain overlays - var/overlays_x_offset = 0 - var/overlays_y_offset = 0 - - var/underline_flag = TRUE //flag for underline - -/obj/item/pda/suicide_act(mob/living/carbon/user) - var/deathMessage = msg_input(user) - if (!deathMessage) - deathMessage = "i ded" - user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") - tnote += "→ To The Grim Reaper:
[deathMessage]
"//records a message in their PDA as being sent to the grim reaper - return BRUTELOSS - -/obj/item/pda/examine(mob/user) - . = ..() - . += id ? "Alt-click to remove the id." : "" - if(inserted_item && (!isturf(loc))) - . += "Ctrl-click to remove [inserted_item]." - if(LAZYLEN(GLOB.pda_reskins)) - . += "Ctrl-shift-click it to reskin it." - -/obj/item/pda/Initialize() - . = ..() - if(fon) - set_light(f_lum, f_pow, f_col) - - GLOB.PDAs += src - if(default_cartridge) - cartridge = new default_cartridge(src) - if(inserted_item) - inserted_item = new inserted_item(src) - else - inserted_item = new /obj/item/pen(src) - update_icon(FALSE, TRUE) - -/obj/item/pda/CtrlShiftClick(mob/living/user) - . = ..() - if(GLOB.pda_reskins && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - reskin_obj(user) - -/obj/item/pda/reskin_obj(mob/M) - if(!LAZYLEN(GLOB.pda_reskins)) - return - var/dat = "Reskin options for [name]:" - for(var/V in GLOB.pda_reskins) - var/output = icon2html(GLOB.pda_reskins[V], M, icon_state) - dat += "\n[V]: [output]" - to_chat(M, dat) - - var/choice = input(M, "Choose the a reskin for [src]","Reskin Object") as null|anything in GLOB.pda_reskins - var/new_icon = GLOB.pda_reskins[choice] - if(QDELETED(src) || isnull(new_icon) || new_icon == icon || !M.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - icon = new_icon - update_icon(FALSE, TRUE) - to_chat(M, "[src] is now skinned as '[choice]'.") - -/obj/item/pda/proc/set_new_overlays() - if(!overlays_offsets || !(icon in overlays_offsets)) - overlays_x_offset = 0 - overlays_y_offset = 0 - else - var/list/new_offsets = overlays_offsets[icon] - if(new_offsets) - overlays_x_offset = new_offsets[1] - overlays_y_offset = new_offsets[2] - if(!(icon in overlays_icons)) - current_overlays = PDA_STANDARD_OVERLAYS - return - current_overlays = overlays_icons[icon] - -/obj/item/pda/equipped(mob/user, slot) - . = ..() - if(equipped) - return - if(user.client) - background_color = user.client.prefs.pda_color - switch(user.client.prefs.pda_style) - if(MONO) - font_index = MODE_MONO - font_mode = FONT_MONO - if(SHARE) - font_index = MODE_SHARE - font_mode = FONT_SHARE - if(ORBITRON) - font_index = MODE_ORBITRON - font_mode = FONT_ORBITRON - if(VT) - font_index = MODE_VT - font_mode = FONT_VT - else - font_index = MODE_MONO - font_mode = FONT_MONO - var/pref_skin = GLOB.pda_reskins[user.client.prefs.pda_skin] - if(icon != pref_skin) - icon = pref_skin - update_icon(FALSE, TRUE) - equipped = TRUE - -/obj/item/pda/proc/update_label() - name = "PDA-[owner] ([ownjob])" //Name generalisation - -/obj/item/pda/GetAccess() - if(id) - return id.GetAccess() - else - return ..() - -/obj/item/pda/GetID() - return id - -/obj/item/pda/RemoveID() - return do_remove_id() - -/obj/item/pda/InsertID(obj/item/inserting_item) - var/obj/item/card/inserting_id = inserting_item.RemoveID() - if(!inserting_id) - return - insert_id(inserting_id) - if(id == inserting_id) - return TRUE - return FALSE - -/obj/item/pda/update_icon(alert = FALSE, new_overlays = FALSE) - if(new_overlays) - set_new_overlays() - cut_overlays() - add_overlay(alert ? current_overlays[PDA_OVERLAY_ALERT] : current_overlays[PDA_OVERLAY_SCREEN]) - var/mutable_appearance/overlay = new() - overlay.pixel_x = overlays_x_offset - if(id) - overlay.icon_state = current_overlays[PDA_OVERLAY_ID] - add_overlay(new /mutable_appearance(overlay)) - if(inserted_item) - overlay.icon_state = current_overlays[PDA_OVERLAY_ITEM] - add_overlay(new /mutable_appearance(overlay)) - if(fon) - overlay.icon_state = current_overlays[PDA_OVERLAY_LIGHT] - add_overlay(new /mutable_appearance(overlay)) - if(pai) - overlay.icon_state = "[current_overlays[PDA_OVERLAY_PAI]][pai.pai ? "" : "_off"]" - add_overlay(new /mutable_appearance(overlay)) - -/obj/item/pda/MouseDrop(mob/over, src_location, over_location) - var/mob/M = usr - if((M == over) && usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return attack_self(M) - return ..() - -/obj/item/pda/attack_self_tk(mob/user) - to_chat(user, "The PDA's capacitive touch screen doesn't seem to respond!") - return - -/obj/item/pda/interact(mob/user) - if(!user.IsAdvancedToolUser()) - to_chat(user, "You don't have the dexterity to do this!") - return - - ..() - - var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pda) - assets.send(user) - - user.set_machine(src) - - var/dat = "Personal Data Assistant" - dat += assets.css_tag() - - dat += "[PDAIMG(refresh)]Refresh" - - if ((!isnull(cartridge)) && (mode == 0)) - dat += " | [PDAIMG(eject)]Eject [cartridge]" - if (mode) - dat += " | [PDAIMG(menu)]Return" - - if (mode == 0) - dat += "
" - dat += "
Toggle Font" - dat += " | Change Color" - dat += " | Toggle Underline" //underline button - - dat += "
" - - dat += "
" - - if (!owner) - dat += "Warning: No owner information entered. Please swipe card.

" - dat += "[PDAIMG(refresh)]Retry" - else - switch (mode) - if (0) - dat += "

PERSONAL DATA ASSISTANT v.1.2

" - dat += "Owner: [owner], [ownjob]
" - dat += text("ID: [id ? "[id.registered_name], [id.assignment]" : "----------"]") - dat += text("
[id ? "Update PDA Info" : ""]

") - - dat += "[STATION_TIME_TIMESTAMP("hh:mm:ss")]
" //:[world.time / 100 % 6][world.time / 100 % 10]" - dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer]" - - dat += "

" - - dat += "

General Functions

" - dat += "" - if (cartridge.access & CART_ENGINE) - dat += "

Engineering Functions

" - dat += "" - if (cartridge.access & CART_MEDICAL) - dat += "

Medical Functions

" - dat += "" - if (cartridge.access & CART_SECURITY) - dat += "

Security Functions

" - dat += "" - if(cartridge.access & CART_QUARTERMASTER) - dat += "

Quartermaster Functions:

" - dat += "" - dat += "" - - dat += "

Utilities

" - dat += "" - - if (1) - dat += "

[PDAIMG(notes)] Notekeeper V2.2

" - dat += "Edit
" - if(notescanned) - dat += "(This is a scanned image, editing it may cause some text formatting to change.)
" - dat += "
[(!notehtml ? note : notehtml)]" - - if (2) - dat += "

[PDAIMG(mail)] SpaceMessenger V3.9.6

" - dat += "[PDAIMG(bell)]Ringer: [silent == 1 ? "Off" : "On"] | " - dat += "[PDAIMG(mail)]Send / Receive: [toff == 1 ? "Off" : "On"] | " - dat += "[PDAIMG(bell)]Set Ringtone | " - dat += "[PDAIMG(mail)]Messages
" - - if(cartridge) - dat += cartridge.message_header() - - dat += "

[PDAIMG(menu)] Detected PDAs

" - - dat += "
    " - var/count = 0 - - if (!toff) - for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) - if (P == src) - continue - dat += "
  • [P]" - if(cartridge) - dat += cartridge.message_special(P) - dat += "
  • " - count++ - dat += "
" - if (count == 0) - dat += "None detected.
" - else if(cartridge && cartridge.spam_enabled) - dat += "Send To All" - - if(21) - dat += "

[PDAIMG(mail)] SpaceMessenger V3.9.6

" - dat += "[PDAIMG(blank)]Clear Messages" - - dat += "

[PDAIMG(mail)] Messages

" - - dat += tnote - dat += "
" - - if (3) - dat += "

[PDAIMG(atmos)] Atmospheric Readings

" - - var/turf/T = user.loc - if (isnull(T)) - dat += "Unable to obtain a reading.
" - else - var/datum/gas_mixture/environment = T.return_air() - var/list/env_gases = environment.gases - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - dat += "Air Pressure: [round(pressure,0.1)] kPa
" - - if (total_moles) - for(var/id in env_gases) - var/gas_level = env_gases[id]/total_moles - if(gas_level > 0) - dat += "[GLOB.meta_gas_names[id]]: [round(gas_level*100, 0.01)]%
" - - dat += "Temperature: [round(environment.temperature-T0C)]°C
" - dat += "
" - else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. - dat += cartridge.generate_menu() - - dat += "" - - if (underline_flag) - dat = replacetext(dat, "text-decoration:none", "text-decoration:underline") - if (!underline_flag) - dat = replacetext(dat, "text-decoration:underline", "text-decoration:none") - - user << browse(dat, "window=pda;size=400x450;border=1;can_resize=1;can_minimize=0") - onclose(user, "pda", src) - -/obj/item/pda/Topic(href, href_list) - ..() - var/mob/living/U = usr - //Looking for master was kind of pointless since PDAs don't appear to have one. - - if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) - add_fingerprint(U) - U.set_machine(src) - - switch(href_list["choice"]) - -//BASIC FUNCTIONS=================================== - - if("Refresh")//Refresh, goes to the end of the proc. - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if ("Toggle_Font") - //CODE REVISION 2 - font_index = (font_index + 1) % 4 - - switch(font_index) - if (MODE_MONO) - font_mode = FONT_MONO - if (MODE_SHARE) - font_mode = FONT_SHARE - if (MODE_ORBITRON) - font_mode = FONT_ORBITRON - if (MODE_VT) - font_mode = FONT_VT - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if ("Change_Color") - var/new_color = input("Please enter a color name or hex value (Default is \'#808000\').",background_color)as color - background_color = new_color - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if ("Toggle_Underline") - underline_flag = !underline_flag - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if("Return")//Return - if(mode<=9) - mode = 0 - else - mode = round(mode/10) - if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. - mode = 0 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if ("Authenticate")//Checks for ID - id_check(U) - - if("UpdateInfo") - ownjob = id.assignment - if(istype(id, /obj/item/card/id/syndicate)) - owner = id.registered_name - update_label() - if (!silent) - playsound(src, 'sound/machines/terminal_processing.ogg', 15, 1) - addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/machines/terminal_success.ogg', 15, 1), 13) - - if("Eject")//Ejects the cart, only done from hub. - if (!isnull(cartridge)) - U.put_in_hands(cartridge) - to_chat(U, "You remove [cartridge] from [src].") - scanmode = PDA_SCANNER_NONE - cartridge.host_pda = null - cartridge = null - update_icon() - if (!silent) - playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) - -//MENU FUNCTIONS=================================== - - if("0")//Hub - mode = 0 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("1")//Notes - mode = 1 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("2")//Messenger - mode = 2 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("21")//Read messeges - mode = 21 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("3")//Atmos scan - mode = 3 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - if("4")//Redirects to hub - mode = 0 - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - -//MAIN FUNCTIONS=================================== - - if("Light") - toggle_light() - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if("Medical Scan") - if(scanmode == PDA_SCANNER_MEDICAL) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) - scanmode = PDA_SCANNER_MEDICAL - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if("Reagent Scan") - if(scanmode == PDA_SCANNER_REAGENT) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) - scanmode = PDA_SCANNER_REAGENT - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if("Halogen Counter") - if(scanmode == PDA_SCANNER_HALOGEN) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) - scanmode = PDA_SCANNER_HALOGEN - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if("Honk") - if ( !(last_noise && world.time < last_noise + 20) ) - playsound(src, 'sound/items/bikehorn.ogg', 50, 1) - last_noise = world.time - - if("Trombone") - if ( !(last_noise && world.time < last_noise + 20) ) - playsound(src, 'sound/misc/sadtrombone.ogg', 50, 1) - last_noise = world.time - - if("Gas Scan") - if(scanmode == PDA_SCANNER_GAS) - scanmode = PDA_SCANNER_NONE - else if((!isnull(cartridge)) && (cartridge.access & CART_ATMOS)) - scanmode = PDA_SCANNER_GAS - if (!silent) - playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) - - if("Drone Phone") - var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") - var/area/A = get_area(U) - if(A && alert_s && !QDELETED(U)) - var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" - _alert_drones(msg, TRUE, U) - to_chat(U, msg) - if (!silent) - playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) - - -//NOTEKEEPER FUNCTIONS=================================== - - if ("Edit") - var/n = stripped_multiline_input(U, "Please enter message", name, note) - if (in_range(src, U) && loc == U) - if (mode == 1 && n) - note = n - notehtml = parsemarkdown(n, U) - notescanned = FALSE - else - U << browse(null, "window=pda") - return - -//MESSENGER FUNCTIONS=================================== - - if("Toggle Messenger") - toff = !toff - if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status - silent = !silent - if("Clear")//Clears messages - tnote = null - if("Ringtone") - var/t = input(U, "Please enter new ringtone", name, ttone) as text - if(in_range(src, U) && loc == U && t) - if(SEND_SIGNAL(src, COMSIG_PDA_CHANGE_RINGTONE, U, t) & COMPONENT_STOP_RINGTONE_CHANGE) - U << browse(null, "window=pda") - return - else - ttone = copytext(sanitize(t), 1, 20) - else - U << browse(null, "window=pda") - return - if("Message") - create_message(U, locate(href_list["target"])) - - if("MessageAll") - send_to_all(U) - - if("cart") - if(cartridge) - cartridge.special(U, href_list) - else - U << browse(null, "window=pda") - return - -//SYNDICATE FUNCTIONS=================================== - - if("Toggle Door") - if(cartridge && cartridge.access & CART_REMOTE_DOOR) - for(var/obj/machinery/door/poddoor/M in GLOB.machines) - if(M.id == cartridge.remote_door_id) - if(M.density) - M.open() - else - M.close() - -//pAI FUNCTIONS=================================== - if("pai") - switch(href_list["option"]) - if("1") // Configure pAI device - pai.attack_self(U) - if("2") // Eject pAI device - var/turf/T = get_turf(loc) - if(T) - pai.forceMove(T) - -//LINK FUNCTIONS=================================== - - else//Cartridge menu linking - mode = max(text2num(href_list["choice"]), 0) - - else//If not in range, can't interact or not using the pda. - U.unset_machine() - U << browse(null, "window=pda") - return - -//EXTRA FUNCTIONS=================================== - - if (mode == 2 || mode == 21)//To clear message overlays. - update_icon() - - if ((honkamt > 0) && (prob(60)))//For clown virus. - honkamt-- - playsound(src, 'sound/items/bikehorn.ogg', 30, 1) - - if(U.machine == src && href_list["skiprefresh"]!="1")//Final safety. - attack_self(U)//It auto-closes the menu prior if the user is not in range and so on. - else - U.unset_machine() - U << browse(null, "window=pda") - return - -/obj/item/pda/proc/remove_id() - if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - do_remove_id(usr) - -/obj/item/pda/proc/do_remove_id(mob/user) - if(!id) - return - if(user) - user.put_in_hands(id) - to_chat(user, "You remove the ID from the [name].") - else - id.forceMove(get_turf(src)) - - . = id - id = null - update_icon() - - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - if(H.wear_id == src) - H.sec_hud_set_ID() - -/obj/item/pda/proc/msg_input(mob/living/U = usr) - var/t = stripped_input(U, "Please enter message", name) - if (!t || toff) - return - if(!U.canUseTopic(src, BE_CLOSE)) - return - if(emped) - t = Gibberish(t, 100) - return t - -/obj/item/pda/proc/send_message(mob/living/user, list/obj/item/pda/targets, everyone) - var/message = msg_input(user) - if(!message || !targets.len) - return - if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY)) - return - var/emoji_message = emoji_parse(message) - if(prob(1)) - message += "\nSent from my PDA" - // Send the signal - var/list/string_targets = list() - for (var/obj/item/pda/P in targets) - if (P.owner && P.ownjob) // != src is checked by the UI - string_targets += "[P.owner] ([P.ownjob])" - for (var/obj/machinery/computer/message_monitor/M in targets) - // In case of "Reply" to a message from a console, this will make the - // message be logged successfully. If the console is impersonating - // someone by matching their name and job, the reply will reach the - // impersonated PDA. - string_targets += "[M.customsender] ([M.customjob])" - if (!string_targets.len) - return - - var/datum/signal/subspace/pda/signal = new(src, list( - "name" = "[owner]", - "job" = "[ownjob]", - "message" = message, - "targets" = string_targets, - "emoji_message" = emoji_message - )) - if (picture) - signal.data["photo"] = picture - signal.send_to_receivers() - - // If it didn't reach, note that fact - if (!signal.data["done"]) - to_chat(user, "ERROR: Server isn't responding.") - return - if (!silent) - playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) - - var/target_text = signal.format_target() - // Log it in our logs - tnote += "→ To [target_text]:
[signal.format_message()]
" - // Show it to ghosts - var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message(TRUE)]" - for(var/mob/M in GLOB.player_list) - if(isobserver(M) && M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA)) - to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]") - // Log in the talk log - user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]") - to_chat(user, "Message sent to [target_text]: \"[emoji_message]\"") - if (!silent) - playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) - // Reset the photo - picture = null - last_text = world.time - if (everyone) - last_everyone = world.time - -/obj/item/pda/proc/receive_message(datum/signal/subspace/pda/signal) - tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
[signal.format_message()]
" - - if (!silent) - playsound(src, 'sound/machines/twobeep.ogg', 50, 1) - audible_message("[icon2html(src, hearers(src))] *[ttone]*", null, 3) - //Search for holder of the PDA. - var/mob/living/L = null - if(loc && isliving(loc)) - L = loc - //Maybe they are a pAI! - else - L = get(src, /mob/living/silicon) - - if(L && L.stat != UNCONSCIOUS) - var/hrefstart - var/hrefend - if (isAI(L)) - hrefstart = "" - hrefend = "" - - to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message(TRUE)] (Reply)") - - update_icon(TRUE) - -/obj/item/pda/proc/send_to_all(mob/living/U) - if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) - to_chat(U,"Send To All function is still on cooldown.") - return - send_message(U,get_viewable_pdas(), TRUE) - -/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) - send_message(U,list(P)) - -/obj/item/pda/AltClick() - ..() - - if(id) - remove_id() - playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) - else - remove_pen() - playsound(src, 'sound/machines/button4.ogg', 50, 1) - -/obj/item/pda/CtrlClick() - ..() - - if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. - return - - remove_pen() - -/obj/item/pda/verb/verb_toggle_light() - set category = "Object" - set name = "Toggle Flashlight" - - toggle_light() - -/obj/item/pda/verb/verb_remove_id() - set category = "Object" - set name = "Eject ID" - set src in usr - - if(id) - remove_id() - else - to_chat(usr, "This PDA does not have an ID in it!") - -/obj/item/pda/verb/verb_remove_pen() - set category = "Object" - set name = "Remove Pen" - set src in usr - - remove_pen() - -/obj/item/pda/proc/toggle_light() - if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE)) - return - if(fon) - fon = FALSE - set_light(0) - else if(f_lum) - fon = TRUE - set_light(f_lum, f_pow, f_col) - update_icon() - -/obj/item/pda/proc/remove_pen() - - if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) - return - - if(inserted_item) - usr.put_in_hands(inserted_item) - to_chat(usr, "You remove [inserted_item] from [src].") - inserted_item = null - update_icon() - else - to_chat(usr, "This PDA does not have a pen in it!") - -//trying to insert or remove an id -/obj/item/pda/proc/id_check(mob/user, obj/item/card/id/I) - if(!I) - if(id && (src in user.contents)) - remove_id() - return TRUE - else - var/obj/item/card/id/C = user.get_active_held_item() - if(istype(C)) - I = C - - if(I?.registered_name) - if(!user.transferItemToLoc(I, src)) - return FALSE - insert_id(I, user) - update_icon() - playsound(src, 'sound/machines/button.ogg', 50, 1) - return TRUE - -/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user) - var/obj/old_id = id - id = inserting_id - if(ishuman(loc)) - var/mob/living/carbon/human/human_wearer = loc - if(human_wearer.wear_id == src) - human_wearer.sec_hud_set_ID() - if(old_id) - if(user) - user.put_in_hands(old_id) - else - old_id.forceMove(get_turf(src)) - -// access to status display signals -/obj/item/pda/attackby(obj/item/C, mob/user, params) - if(istype(C, /obj/item/cartridge) && !cartridge) - if(!user.transferItemToLoc(C, src)) - return - cartridge = C - cartridge.host_pda = src - to_chat(user, "You insert [cartridge] into [src].") - update_icon() - playsound(src, 'sound/machines/button.ogg', 50, 1) - - else if(istype(C, /obj/item/card/id)) - var/obj/item/card/id/idcard = C - if(!idcard.registered_name) - to_chat(user, "\The [src] rejects the ID!") - return - if (!silent) - playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) - - if(!owner) - owner = idcard.registered_name - ownjob = idcard.assignment - update_label() - to_chat(user, "Card scanned.") - if (!silent) - playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) - else - //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. - if(((src in user.contents) || (isturf(loc) && in_range(src, user))) && (C in user.contents)) - if(!id_check(user, idcard)) - return - to_chat(user, "You put the ID into \the [src]'s slot.") - updateSelfDialog()//Update self dialog on success. - return //Return in case of failed check or when successful. - updateSelfDialog()//For the non-input related code. - else if(istype(C, /obj/item/paicard) && !pai) - if(!user.transferItemToLoc(C, src)) - return - pai = C - to_chat(user, "You slot \the [C] into [src].") - update_icon() - updateUsrDialog() - else if(is_type_in_list(C, contained_item)) //Checks if there is a pen - if(inserted_item) - to_chat(user, "There is already \a [inserted_item] in \the [src]!") - else - if(!user.transferItemToLoc(C, src)) - return - to_chat(user, "You slide \the [C] into \the [src].") - inserted_item = C - update_icon() - playsound(src, 'sound/machines/button.ogg', 50, 1) - - else if(istype(C, /obj/item/photo)) - var/obj/item/photo/P = C - picture = P.picture - to_chat(user, "You scan \the [C].") - else - return ..() - -/obj/item/pda/attack(mob/living/carbon/C, mob/living/user) - if(istype(C)) - switch(scanmode) - - if(PDA_SCANNER_MEDICAL) - C.visible_message("[user] has analyzed [C]'s vitals!") - healthscan(user, C, 1) - add_fingerprint(user) - - if(PDA_SCANNER_HALOGEN) - C.visible_message("[user] has analyzed [C]'s radiation levels!") - - user.show_message("Analyzing Results for [C]:") - if(C.radiation) - user.show_message("\green Radiation Level: \black [C.radiation]") - else - user.show_message("No radiation detected.") - -/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) - . = ..() - if(!proximity) - return - switch(scanmode) - if(PDA_SCANNER_REAGENT) - if(!isnull(A.reagents)) - if(A.reagents.reagent_list.len > 0) - var/reagents_length = A.reagents.reagent_list.len - to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") - for (var/re in A.reagents.reagent_list) - to_chat(user, "\t [re]") - else - to_chat(user, "No active chemical agents found in [A].") - else - to_chat(user, "No significant chemical agents found in [A].") - - if(PDA_SCANNER_GAS) - A.analyzer_act(user, src) - - if (!scanmode && istype(A, /obj/item/paper) && owner) - var/obj/item/paper/PP = A - if (!PP.info) - to_chat(user, "Unable to scan! Paper is blank.") - return - notehtml = PP.info - note = replacetext(notehtml, "
", "\[br\]") - note = replacetext(note, "
  • ", "\[*\]") - note = replacetext(note, "
      ", "\[list\]") - note = replacetext(note, "
    ", "\[/list\]") - note = html_encode(note) - notescanned = TRUE - to_chat(user, "Paper scanned. Saved to PDA's notekeeper." ) - - -/obj/item/pda/proc/explode() //This needs tuning. - if(!detonatable) - return - var/turf/T = get_turf(src) - - if (ismob(loc)) - var/mob/M = loc - M.show_message("Your [src] explodes!", 1) - else - visible_message("[src] explodes!", "You hear a loud *pop*!") - - if(T) - T.hotspot_expose(700,125) - if(istype(cartridge, /obj/item/cartridge/virus/syndicate)) - explosion(T, -1, 1, 3, 4) - else - explosion(T, -1, -1, 2, 3) - qdel(src) - return - -/obj/item/pda/Destroy() - GLOB.PDAs -= src - if(istype(id)) - QDEL_NULL(id) - if(istype(cartridge)) - QDEL_NULL(cartridge) - if(istype(pai)) - QDEL_NULL(pai) - if(istype(inserted_item)) - QDEL_NULL(inserted_item) - return ..() - -//AI verb and proc for sending PDA messages. - -/mob/living/silicon/ai/proc/cmd_send_pdamesg(mob/user) - var/list/plist = list() - var/list/namecounts = list() - - if(aiPDA.toff) - to_chat(user, "Turn on your receiver in order to send messages.") - return - - for (var/obj/item/pda/P in get_viewable_pdas()) - if (P == src) - continue - else if (P == aiPDA) - continue - - plist[avoid_assoc_duplicate_keys(P.owner, namecounts)] = P - - var/c = input(user, "Please select a PDA") as null|anything in sortList(plist) - - if (!c) - return - - var/selected = plist[c] - - if(aicamera.stored.len) - var/add_photo = input(user,"Do you want to attach a photo?","Photo","No") as null|anything in list("Yes","No") - if(add_photo=="Yes") - var/datum/picture/Pic = aicamera.selectpicture(user) - aiPDA.picture = Pic - - if(incapacitated()) - return - - aiPDA.create_message(src, selected) - - -/mob/living/silicon/ai/verb/cmd_toggle_pda_receiver() - set category = "AI Commands" - set name = "PDA - Toggle Sender/Receiver" - if(usr.stat == DEAD) - return //won't work if dead - if(!isnull(aiPDA)) - aiPDA.toff = !aiPDA.toff - to_chat(usr, "PDA sender/receiver toggled [(aiPDA.toff ? "Off" : "On")]!") - else - to_chat(usr, "You do not have a PDA. You should make an issue report about this.") - -/mob/living/silicon/ai/verb/cmd_toggle_pda_silent() - set category = "AI Commands" - set name = "PDA - Toggle Ringer" - if(usr.stat == DEAD) - return //won't work if dead - if(!isnull(aiPDA)) - //0 - aiPDA.silent = !aiPDA.silent - to_chat(usr, "PDA ringer toggled [(aiPDA.silent ? "Off" : "On")]!") - else - to_chat(usr, "You do not have a PDA. You should make an issue report about this.") - -/mob/living/silicon/ai/proc/cmd_show_message_log(mob/user) - if(incapacitated()) - return - if(!isnull(aiPDA)) - var/HTML = "AI PDA Message Log[aiPDA.tnote]" - user << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") - else - to_chat(user, "You do not have a PDA. You should make an issue report about this.") - - -// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP -/obj/item/pda/emp_act(severity) - . = ..() - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/atom/A in src) - A.emp_act(severity) - if (!(. & EMP_PROTECT_SELF)) - emped += 1 - spawn(200 * severity) - emped -= 1 - -/proc/get_viewable_pdas() - . = list() - // Returns a list of PDAs which can be viewed from another PDA/message monitor. - for(var/obj/item/pda/P in GLOB.PDAs) - if(!P.owner || P.toff || P.hidden) - continue - . += P - -#undef PDA_SCANNER_NONE -#undef PDA_SCANNER_MEDICAL -#undef PDA_SCANNER_FORENSICS -#undef PDA_SCANNER_REAGENT -#undef PDA_SCANNER_HALOGEN -#undef PDA_SCANNER_GAS -#undef PDA_SPAM_DELAY -#undef PDA_STANDARD_OVERLAYS - -#undef PDA_OVERLAY_ALERT -#undef PDA_OVERLAY_SCREEN -#undef PDA_OVERLAY_ID -#undef PDA_OVERLAY_ITEM -#undef PDA_OVERLAY_LIGHT -#undef PDA_OVERLAY_PAI + +//The advanced pea-green monochrome lcd of tomorrow. + +GLOBAL_LIST_EMPTY(PDAs) + +#define PDA_SCANNER_NONE 0 +#define PDA_SCANNER_MEDICAL 1 +#define PDA_SCANNER_FORENSICS 2 //unused +#define PDA_SCANNER_REAGENT 3 +#define PDA_SCANNER_HALOGEN 4 +#define PDA_SCANNER_GAS 5 +#define PDA_SPAM_DELAY 2 MINUTES +#define PDA_STANDARD_OVERLAYS list("pda-r", "blank", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay") + +//pda icon overlays list defines +#define PDA_OVERLAY_ALERT 1 +#define PDA_OVERLAY_SCREEN 2 +#define PDA_OVERLAY_ID 3 +#define PDA_OVERLAY_ITEM 4 +#define PDA_OVERLAY_LIGHT 5 +#define PDA_OVERLAY_PAI 6 + +/obj/item/pda + name = "\improper PDA" + desc = "A portable microcomputer by Thinktronic Systems, LTD. Functionality determined by a preprogrammed ROM cartridge." + icon = 'icons/obj/pda_alt.dmi' + icon_state = "pda" + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_TINY + slot_flags = ITEM_SLOT_ID | ITEM_SLOT_BELT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100) + resistance_flags = FIRE_PROOF | ACID_PROOF + + + //Main variables + var/owner = null // String name of owner + var/default_cartridge = 0 // Access level defined by cartridge + var/obj/item/cartridge/cartridge = null //current cartridge + var/mode = 0 //Controls what menu the PDA will display. 0 is hub; the rest are either built in or based on cartridge. + var/list/overlays_icons = list('icons/obj/pda_alt.dmi' = list("pda-r", "screen_default", "id_overlay", "insert_overlay", "light_overlay", "pai_overlay")) + var/current_overlays = PDA_STANDARD_OVERLAYS + var/font_index = 0 //This int tells DM which font is currently selected and lets DM know when the last font has been selected so that it can cycle back to the first font when "toggle font" is pressed again. + var/font_mode = "font-family:monospace;" //The currently selected font. + var/background_color = "#808000" //The currently selected background color. + + #define FONT_MONO "font-family:monospace;" + #define FONT_SHARE "font-family:\"Share Tech Mono\", monospace;letter-spacing:0px;" + #define FONT_ORBITRON "font-family:\"Orbitron\", monospace;letter-spacing:0px; font-size:15px" + #define FONT_VT "font-family:\"VT323\", monospace;letter-spacing:1px;" + #define MODE_MONO 0 + #define MODE_SHARE 1 + #define MODE_ORBITRON 2 + #define MODE_VT 3 + + //Secondary variables + var/scanmode = PDA_SCANNER_NONE + var/fon = FALSE //Is the flashlight function on? + var/f_lum = 2.3 //Luminosity for the flashlight function + var/f_pow = 0.6 //Power for the flashlight function + var/f_col = "#FFCC66" //Color for the flashlight function + var/silent = FALSE //To beep or not to beep, that is the question + var/toff = FALSE //If TRUE, messenger disabled + var/tnote = null //Current Texts + var/last_text //No text spamming + var/last_everyone //No text for everyone spamming + var/last_noise //Also no honk spamming that's bad too + var/ttone = "beep" //The ringtone! + var/honkamt = 0 //How many honks left when infected with honk.exe + var/mimeamt = 0 //How many silence left when infected with mime.exe + var/note = "Congratulations, your station has chosen the Thinktronic 5230 Personal Data Assistant! To help with navigation, we have provided the following definitions. North: Fore. South: Aft. West: Port. East: Starboard. Quarter is either side of aft." //Current note in the notepad function + var/notehtml = "" + var/notescanned = FALSE // True if what is in the notekeeper was from a paper. + var/detonatable = TRUE // Can the PDA be blown up? + var/hidden = FALSE // Is the PDA hidden from the PDA list? + var/emped = FALSE + var/equipped = FALSE //used here to determine if this is the first time its been picked up + + var/obj/item/card/id/id = null //Making it possible to slot an ID card into the PDA so it can function as both. + var/ownjob = null //related to above + + var/obj/item/paicard/pai = null // A slot for a personal AI device + + var/datum/picture/picture //Scanned photo + + var/list/contained_item = list(/obj/item/pen, /obj/item/toy/crayon, /obj/item/lipstick, /obj/item/flashlight/pen, /obj/item/clothing/mask/cigarette) + var/obj/item/inserted_item //Used for pen, crayon, and lipstick insertion or removal. Same as above. + var/list/overlays_offsets // offsets to use for certain overlays + var/overlays_x_offset = 0 + var/overlays_y_offset = 0 + + var/underline_flag = TRUE //flag for underline + +/obj/item/pda/suicide_act(mob/living/carbon/user) + var/deathMessage = msg_input(user) + if (!deathMessage) + deathMessage = "i ded" + user.visible_message("[user] is sending a message to the Grim Reaper! It looks like [user.p_theyre()] trying to commit suicide!") + tnote += "→ To The Grim Reaper:
    [deathMessage]
    "//records a message in their PDA as being sent to the grim reaper + return BRUTELOSS + +/obj/item/pda/examine(mob/user) + . = ..() + . += id ? "Alt-click to remove the id." : "" + if(inserted_item && (!isturf(loc))) + . += "Ctrl-click to remove [inserted_item]." + if(LAZYLEN(GLOB.pda_reskins)) + . += "Ctrl-shift-click it to reskin it." + +/obj/item/pda/Initialize() + . = ..() + if(fon) + set_light(f_lum, f_pow, f_col) + + GLOB.PDAs += src + if(default_cartridge) + cartridge = new default_cartridge(src) + if(inserted_item) + inserted_item = new inserted_item(src) + else + inserted_item = new /obj/item/pen(src) + update_icon(FALSE, TRUE) + +/obj/item/pda/CtrlShiftClick(mob/living/user) + . = ..() + if(GLOB.pda_reskins && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + reskin_obj(user) + +/obj/item/pda/reskin_obj(mob/M) + if(!LAZYLEN(GLOB.pda_reskins)) + return + var/dat = "Reskin options for [name]:" + for(var/V in GLOB.pda_reskins) + var/output = icon2html(GLOB.pda_reskins[V], M, icon_state) + dat += "\n[V]: [output]" + to_chat(M, dat) + + var/choice = input(M, "Choose the a reskin for [src]","Reskin Object") as null|anything in GLOB.pda_reskins + var/new_icon = GLOB.pda_reskins[choice] + if(QDELETED(src) || isnull(new_icon) || new_icon == icon || !M.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + icon = new_icon + update_icon(FALSE, TRUE) + to_chat(M, "[src] is now skinned as '[choice]'.") + +/obj/item/pda/proc/set_new_overlays() + if(!overlays_offsets || !(icon in overlays_offsets)) + overlays_x_offset = 0 + overlays_y_offset = 0 + else + var/list/new_offsets = overlays_offsets[icon] + if(new_offsets) + overlays_x_offset = new_offsets[1] + overlays_y_offset = new_offsets[2] + if(!(icon in overlays_icons)) + current_overlays = PDA_STANDARD_OVERLAYS + return + current_overlays = overlays_icons[icon] + +/obj/item/pda/equipped(mob/user, slot) + . = ..() + if(equipped) + return + if(user.client) + background_color = user.client.prefs.pda_color + switch(user.client.prefs.pda_style) + if(MONO) + font_index = MODE_MONO + font_mode = FONT_MONO + if(SHARE) + font_index = MODE_SHARE + font_mode = FONT_SHARE + if(ORBITRON) + font_index = MODE_ORBITRON + font_mode = FONT_ORBITRON + if(VT) + font_index = MODE_VT + font_mode = FONT_VT + else + font_index = MODE_MONO + font_mode = FONT_MONO + var/pref_skin = GLOB.pda_reskins[user.client.prefs.pda_skin] + if(icon != pref_skin) + icon = pref_skin + update_icon(FALSE, TRUE) + equipped = TRUE + +/obj/item/pda/proc/update_label() + name = "PDA-[owner] ([ownjob])" //Name generalisation + +/obj/item/pda/GetAccess() + if(id) + return id.GetAccess() + else + return ..() + +/obj/item/pda/GetID() + return id + +/obj/item/pda/RemoveID() + return do_remove_id() + +/obj/item/pda/InsertID(obj/item/inserting_item) + var/obj/item/card/inserting_id = inserting_item.RemoveID() + if(!inserting_id) + return + insert_id(inserting_id) + if(id == inserting_id) + return TRUE + return FALSE + +/obj/item/pda/update_icon(alert = FALSE, new_overlays = FALSE) + if(new_overlays) + set_new_overlays() + cut_overlays() + add_overlay(alert ? current_overlays[PDA_OVERLAY_ALERT] : current_overlays[PDA_OVERLAY_SCREEN]) + var/mutable_appearance/overlay = new() + overlay.pixel_x = overlays_x_offset + if(id) + overlay.icon_state = current_overlays[PDA_OVERLAY_ID] + add_overlay(new /mutable_appearance(overlay)) + if(inserted_item) + overlay.icon_state = current_overlays[PDA_OVERLAY_ITEM] + add_overlay(new /mutable_appearance(overlay)) + if(fon) + overlay.icon_state = current_overlays[PDA_OVERLAY_LIGHT] + add_overlay(new /mutable_appearance(overlay)) + if(pai) + overlay.icon_state = "[current_overlays[PDA_OVERLAY_PAI]][pai.pai ? "" : "_off"]" + add_overlay(new /mutable_appearance(overlay)) + +/obj/item/pda/MouseDrop(mob/over, src_location, over_location) + var/mob/M = usr + if((M == over) && usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return attack_self(M) + return ..() + +/obj/item/pda/attack_self_tk(mob/user) + to_chat(user, "The PDA's capacitive touch screen doesn't seem to respond!") + return + +/obj/item/pda/interact(mob/user) + if(!user.IsAdvancedToolUser()) + to_chat(user, "You don't have the dexterity to do this!") + return + + ..() + + var/datum/asset/spritesheet/assets = get_asset_datum(/datum/asset/spritesheet/simple/pda) + assets.send(user) + + user.set_machine(src) + + var/dat = "Personal Data Assistant" + dat += assets.css_tag() + + dat += "[PDAIMG(refresh)]Refresh" + + if ((!isnull(cartridge)) && (mode == 0)) + dat += " | [PDAIMG(eject)]Eject [cartridge]" + if (mode) + dat += " | [PDAIMG(menu)]Return" + + if (mode == 0) + dat += "
    " + dat += "
    Toggle Font" + dat += " | Change Color" + dat += " | Toggle Underline" //underline button + + dat += "
    " + + dat += "
    " + + if (!owner) + dat += "Warning: No owner information entered. Please swipe card.

    " + dat += "[PDAIMG(refresh)]Retry" + else + switch (mode) + if (0) + dat += "

    PERSONAL DATA ASSISTANT v.1.2

    " + dat += "Owner: [owner], [ownjob]
    " + dat += text("ID: [id ? "[id.registered_name], [id.assignment]" : "----------"]") + dat += text("
    [id ? "Update PDA Info" : ""]

    ") + + dat += "[STATION_TIME_TIMESTAMP("hh:mm:ss")]
    " //:[world.time / 100 % 6][world.time / 100 % 10]" + dat += "[time2text(world.realtime, "MMM DD")] [GLOB.year_integer]" + + dat += "

    " + + dat += "

    General Functions

    " + dat += "" + if (cartridge.access & CART_ENGINE) + dat += "

    Engineering Functions

    " + dat += "" + if (cartridge.access & CART_MEDICAL) + dat += "

    Medical Functions

    " + dat += "" + if (cartridge.access & CART_SECURITY) + dat += "

    Security Functions

    " + dat += "" + if(cartridge.access & CART_QUARTERMASTER) + dat += "

    Quartermaster Functions:

    " + dat += "" + dat += "" + + dat += "

    Utilities

    " + dat += "" + + if (1) + dat += "

    [PDAIMG(notes)] Notekeeper V2.2

    " + dat += "Edit
    " + if(notescanned) + dat += "(This is a scanned image, editing it may cause some text formatting to change.)
    " + dat += "
    [(!notehtml ? note : notehtml)]" + + if (2) + dat += "

    [PDAIMG(mail)] SpaceMessenger V3.9.6

    " + dat += "[PDAIMG(bell)]Ringer: [silent == 1 ? "Off" : "On"] | " + dat += "[PDAIMG(mail)]Send / Receive: [toff == 1 ? "Off" : "On"] | " + dat += "[PDAIMG(bell)]Set Ringtone | " + dat += "[PDAIMG(mail)]Messages
    " + + if(cartridge) + dat += cartridge.message_header() + + dat += "

    [PDAIMG(menu)] Detected PDAs

    " + + dat += "
      " + var/count = 0 + + if (!toff) + for (var/obj/item/pda/P in sortNames(get_viewable_pdas())) + if (P == src) + continue + dat += "
    • [P]" + if(cartridge) + dat += cartridge.message_special(P) + dat += "
    • " + count++ + dat += "
    " + if (count == 0) + dat += "None detected.
    " + else if(cartridge && cartridge.spam_enabled) + dat += "Send To All" + + if(21) + dat += "

    [PDAIMG(mail)] SpaceMessenger V3.9.6

    " + dat += "[PDAIMG(blank)]Clear Messages" + + dat += "

    [PDAIMG(mail)] Messages

    " + + dat += tnote + dat += "
    " + + if (3) + dat += "

    [PDAIMG(atmos)] Atmospheric Readings

    " + + var/turf/T = user.loc + if (isnull(T)) + dat += "Unable to obtain a reading.
    " + else + var/datum/gas_mixture/environment = T.return_air() + var/list/env_gases = environment.gases + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + dat += "Air Pressure: [round(pressure,0.1)] kPa
    " + + if (total_moles) + for(var/id in env_gases) + var/gas_level = env_gases[id]/total_moles + if(gas_level > 0) + dat += "[GLOB.meta_gas_names[id]]: [round(gas_level*100, 0.01)]%
    " + + dat += "Temperature: [round(environment.temperature-T0C)]°C
    " + dat += "
    " + else//Else it links to the cart menu proc. Although, it really uses menu hub 4--menu 4 doesn't really exist as it simply redirects to hub. + dat += cartridge.generate_menu() + + dat += "" + + if (underline_flag) + dat = replacetext(dat, "text-decoration:none", "text-decoration:underline") + if (!underline_flag) + dat = replacetext(dat, "text-decoration:underline", "text-decoration:none") + + user << browse(dat, "window=pda;size=400x450;border=1;can_resize=1;can_minimize=0") + onclose(user, "pda", src) + +/obj/item/pda/Topic(href, href_list) + ..() + var/mob/living/U = usr + //Looking for master was kind of pointless since PDAs don't appear to have one. + + if(usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !href_list["close"]) + add_fingerprint(U) + U.set_machine(src) + + switch(href_list["choice"]) + +//BASIC FUNCTIONS=================================== + + if("Refresh")//Refresh, goes to the end of the proc. + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if ("Toggle_Font") + //CODE REVISION 2 + font_index = (font_index + 1) % 4 + + switch(font_index) + if (MODE_MONO) + font_mode = FONT_MONO + if (MODE_SHARE) + font_mode = FONT_SHARE + if (MODE_ORBITRON) + font_mode = FONT_ORBITRON + if (MODE_VT) + font_mode = FONT_VT + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if ("Change_Color") + var/new_color = input("Please enter a color name or hex value (Default is \'#808000\').",background_color)as color + background_color = new_color + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if ("Toggle_Underline") + underline_flag = !underline_flag + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if("Return")//Return + if(mode<=9) + mode = 0 + else + mode = round(mode/10) + if(mode==4 || mode == 5)//Fix for cartridges. Redirects to hub. + mode = 0 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if ("Authenticate")//Checks for ID + id_check(U) + + if("UpdateInfo") + ownjob = id.assignment + if(istype(id, /obj/item/card/id/syndicate)) + owner = id.registered_name + update_label() + if (!silent) + playsound(src, 'sound/machines/terminal_processing.ogg', 15, 1) + addtimer(CALLBACK(GLOBAL_PROC, .proc/playsound, src, 'sound/machines/terminal_success.ogg', 15, 1), 13) + + if("Eject")//Ejects the cart, only done from hub. + if (!isnull(cartridge)) + U.put_in_hands(cartridge) + to_chat(U, "You remove [cartridge] from [src].") + scanmode = PDA_SCANNER_NONE + cartridge.host_pda = null + cartridge = null + update_icon() + if (!silent) + playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) + +//MENU FUNCTIONS=================================== + + if("0")//Hub + mode = 0 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("1")//Notes + mode = 1 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("2")//Messenger + mode = 2 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("21")//Read messeges + mode = 21 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("3")//Atmos scan + mode = 3 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + if("4")//Redirects to hub + mode = 0 + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + +//MAIN FUNCTIONS=================================== + + if("Light") + toggle_light() + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if("Medical Scan") + if(scanmode == PDA_SCANNER_MEDICAL) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_MEDICAL)) + scanmode = PDA_SCANNER_MEDICAL + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if("Reagent Scan") + if(scanmode == PDA_SCANNER_REAGENT) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_REAGENT_SCANNER)) + scanmode = PDA_SCANNER_REAGENT + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if("Halogen Counter") + if(scanmode == PDA_SCANNER_HALOGEN) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_ENGINE)) + scanmode = PDA_SCANNER_HALOGEN + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if("Honk") + if ( !(last_noise && world.time < last_noise + 20) ) + playsound(src, 'sound/items/bikehorn.ogg', 50, 1) + last_noise = world.time + + if("Trombone") + if ( !(last_noise && world.time < last_noise + 20) ) + playsound(src, 'sound/misc/sadtrombone.ogg', 50, 1) + last_noise = world.time + + if("Gas Scan") + if(scanmode == PDA_SCANNER_GAS) + scanmode = PDA_SCANNER_NONE + else if((!isnull(cartridge)) && (cartridge.access & CART_ATMOS)) + scanmode = PDA_SCANNER_GAS + if (!silent) + playsound(src, 'sound/machines/terminal_select.ogg', 15, 1) + + if("Drone Phone") + var/alert_s = input(U,"Alert severity level","Ping Drones",null) as null|anything in list("Low","Medium","High","Critical") + var/area/A = get_area(U) + if(A && alert_s && !QDELETED(U)) + var/msg = "NON-DRONE PING: [U.name]: [alert_s] priority alert in [A.name]!" + _alert_drones(msg, TRUE, U) + to_chat(U, msg) + if (!silent) + playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) + + +//NOTEKEEPER FUNCTIONS=================================== + + if ("Edit") + var/n = stripped_multiline_input(U, "Please enter message", name, note) + if (in_range(src, U) && loc == U) + if (mode == 1 && n) + note = n + notehtml = parsemarkdown(n, U) + notescanned = FALSE + else + U << browse(null, "window=pda") + return + +//MESSENGER FUNCTIONS=================================== + + if("Toggle Messenger") + toff = !toff + if("Toggle Ringer")//If viewing texts then erase them, if not then toggle silent status + silent = !silent + if("Clear")//Clears messages + tnote = null + if("Ringtone") + var/t = input(U, "Please enter new ringtone", name, ttone) as text + if(in_range(src, U) && loc == U && t) + if(SEND_SIGNAL(src, COMSIG_PDA_CHANGE_RINGTONE, U, t) & COMPONENT_STOP_RINGTONE_CHANGE) + U << browse(null, "window=pda") + return + else + ttone = copytext(sanitize(t), 1, 20) + else + U << browse(null, "window=pda") + return + if("Message") + create_message(U, locate(href_list["target"])) + + if("MessageAll") + send_to_all(U) + + if("cart") + if(cartridge) + cartridge.special(U, href_list) + else + U << browse(null, "window=pda") + return + +//SYNDICATE FUNCTIONS=================================== + + if("Toggle Door") + if(cartridge && cartridge.access & CART_REMOTE_DOOR) + for(var/obj/machinery/door/poddoor/M in GLOB.machines) + if(M.id == cartridge.remote_door_id) + if(M.density) + M.open() + else + M.close() + +//pAI FUNCTIONS=================================== + if("pai") + switch(href_list["option"]) + if("1") // Configure pAI device + pai.attack_self(U) + if("2") // Eject pAI device + var/turf/T = get_turf(loc) + if(T) + pai.forceMove(T) + +//LINK FUNCTIONS=================================== + + else//Cartridge menu linking + mode = max(text2num(href_list["choice"]), 0) + + else//If not in range, can't interact or not using the pda. + U.unset_machine() + U << browse(null, "window=pda") + return + +//EXTRA FUNCTIONS=================================== + + if (mode == 2 || mode == 21)//To clear message overlays. + update_icon() + + if ((honkamt > 0) && (prob(60)))//For clown virus. + honkamt-- + playsound(src, 'sound/items/bikehorn.ogg', 30, 1) + + if(U.machine == src && href_list["skiprefresh"]!="1")//Final safety. + attack_self(U)//It auto-closes the menu prior if the user is not in range and so on. + else + U.unset_machine() + U << browse(null, "window=pda") + return + +/obj/item/pda/proc/remove_id() + if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + do_remove_id(usr) + +/obj/item/pda/proc/do_remove_id(mob/user) + if(!id) + return + if(user) + user.put_in_hands(id) + to_chat(user, "You remove the ID from the [name].") + else + id.forceMove(get_turf(src)) + + . = id + id = null + update_icon() + + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + if(H.wear_id == src) + H.sec_hud_set_ID() + +/obj/item/pda/proc/msg_input(mob/living/U = usr) + var/t = stripped_input(U, "Please enter message", name) + if (!t || toff) + return + if(!U.canUseTopic(src, BE_CLOSE)) + return + if(emped) + t = Gibberish(t, 100) + return t + +/obj/item/pda/proc/send_message(mob/living/user, list/obj/item/pda/targets, everyone) + var/message = msg_input(user) + if(!message || !targets.len) + return + if((last_text && world.time < last_text + 10) || (everyone && last_everyone && world.time < last_everyone + PDA_SPAM_DELAY)) + return + var/emoji_message = emoji_parse(message) + if(prob(1)) + message += "\nSent from my PDA" + // Send the signal + var/list/string_targets = list() + for (var/obj/item/pda/P in targets) + if (P.owner && P.ownjob) // != src is checked by the UI + string_targets += "[P.owner] ([P.ownjob])" + for (var/obj/machinery/computer/message_monitor/M in targets) + // In case of "Reply" to a message from a console, this will make the + // message be logged successfully. If the console is impersonating + // someone by matching their name and job, the reply will reach the + // impersonated PDA. + string_targets += "[M.customsender] ([M.customjob])" + if (!string_targets.len) + return + + var/datum/signal/subspace/pda/signal = new(src, list( + "name" = "[owner]", + "job" = "[ownjob]", + "message" = message, + "targets" = string_targets, + "emoji_message" = emoji_message + )) + if (picture) + signal.data["photo"] = picture + signal.send_to_receivers() + + // If it didn't reach, note that fact + if (!signal.data["done"]) + to_chat(user, "ERROR: Server isn't responding.") + return + if (!silent) + playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) + + var/target_text = signal.format_target() + // Log it in our logs + tnote += "→ To [target_text]:
    [signal.format_message()]
    " + // Show it to ghosts + var/ghost_message = "[owner] PDA Message --> [target_text]: [signal.format_message(TRUE)]" + for(var/mob/M in GLOB.player_list) + if(isobserver(M) && M.client && (M.client.prefs.chat_toggles & CHAT_GHOSTPDA)) + to_chat(M, "[FOLLOW_LINK(M, user)] [ghost_message]") + // Log in the talk log + user.log_talk(message, LOG_PDA, tag="PDA: [initial(name)] to [target_text]") + to_chat(user, "Message sent to [target_text]: \"[emoji_message]\"") + if (!silent) + playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) + // Reset the photo + picture = null + last_text = world.time + if (everyone) + last_everyone = world.time + +/obj/item/pda/proc/receive_message(datum/signal/subspace/pda/signal) + tnote += "← From [signal.data["name"]] ([signal.data["job"]]):
    [signal.format_message()]
    " + + if (!silent) + playsound(src, 'sound/machines/twobeep.ogg', 50, 1) + audible_message("[icon2html(src, hearers(src))] *[ttone]*", null, 3) + //Search for holder of the PDA. + var/mob/living/L = null + if(loc && isliving(loc)) + L = loc + //Maybe they are a pAI! + else + L = get(src, /mob/living/silicon) + + if(L && L.stat != UNCONSCIOUS) + var/hrefstart + var/hrefend + if (isAI(L)) + hrefstart = "" + hrefend = "" + + to_chat(L, "[icon2html(src)] Message from [hrefstart][signal.data["name"]] ([signal.data["job"]])[hrefend], [signal.format_message(TRUE)] (Reply)") + + update_icon(TRUE) + +/obj/item/pda/proc/send_to_all(mob/living/U) + if (last_everyone && world.time < last_everyone + PDA_SPAM_DELAY) + to_chat(U,"Send To All function is still on cooldown.") + return + send_message(U,get_viewable_pdas(), TRUE) + +/obj/item/pda/proc/create_message(mob/living/U, obj/item/pda/P) + send_message(U,list(P)) + +/obj/item/pda/AltClick() + . = ..() + if(id) + remove_id() + playsound(src, 'sound/machines/terminal_eject_disc.ogg', 50, 1) + else + remove_pen() + playsound(src, 'sound/machines/button4.ogg', 50, 1) + return TRUE + +/obj/item/pda/CtrlClick() + ..() + + if(isturf(loc)) //stops the user from dragging the PDA by ctrl-clicking it. + return + + remove_pen() + +/obj/item/pda/verb/verb_toggle_light() + set category = "Object" + set name = "Toggle Flashlight" + + toggle_light() + +/obj/item/pda/verb/verb_remove_id() + set category = "Object" + set name = "Eject ID" + set src in usr + + if(id) + remove_id() + else + to_chat(usr, "This PDA does not have an ID in it!") + +/obj/item/pda/verb/verb_remove_pen() + set category = "Object" + set name = "Remove Pen" + set src in usr + + remove_pen() + +/obj/item/pda/proc/toggle_light() + if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE)) + return + if(fon) + fon = FALSE + set_light(0) + else if(f_lum) + fon = TRUE + set_light(f_lum, f_pow, f_col) + update_icon() + +/obj/item/pda/proc/remove_pen() + + if(issilicon(usr) || !usr.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + + if(inserted_item) + usr.put_in_hands(inserted_item) + to_chat(usr, "You remove [inserted_item] from [src].") + inserted_item = null + update_icon() + else + to_chat(usr, "This PDA does not have a pen in it!") + +//trying to insert or remove an id +/obj/item/pda/proc/id_check(mob/user, obj/item/card/id/I) + if(!I) + if(id && (src in user.contents)) + remove_id() + return TRUE + else + var/obj/item/card/id/C = user.get_active_held_item() + if(istype(C)) + I = C + + if(I?.registered_name) + if(!user.transferItemToLoc(I, src)) + return FALSE + insert_id(I, user) + update_icon() + playsound(src, 'sound/machines/button.ogg', 50, 1) + return TRUE + +/obj/item/pda/proc/insert_id(obj/item/card/id/inserting_id, mob/user) + var/obj/old_id = id + id = inserting_id + if(ishuman(loc)) + var/mob/living/carbon/human/human_wearer = loc + if(human_wearer.wear_id == src) + human_wearer.sec_hud_set_ID() + if(old_id) + if(user) + user.put_in_hands(old_id) + else + old_id.forceMove(get_turf(src)) + +// access to status display signals +/obj/item/pda/attackby(obj/item/C, mob/user, params) + if(istype(C, /obj/item/cartridge) && !cartridge) + if(!user.transferItemToLoc(C, src)) + return + cartridge = C + cartridge.host_pda = src + to_chat(user, "You insert [cartridge] into [src].") + update_icon() + playsound(src, 'sound/machines/button.ogg', 50, 1) + + else if(istype(C, /obj/item/card/id)) + var/obj/item/card/id/idcard = C + if(!idcard.registered_name) + to_chat(user, "\The [src] rejects the ID!") + return + if (!silent) + playsound(src, 'sound/machines/terminal_error.ogg', 15, 1) + + if(!owner) + owner = idcard.registered_name + ownjob = idcard.assignment + update_label() + to_chat(user, "Card scanned.") + if (!silent) + playsound(src, 'sound/machines/terminal_success.ogg', 15, 1) + else + //Basic safety check. If either both objects are held by user or PDA is on ground and card is in hand. + if(((src in user.contents) || (isturf(loc) && in_range(src, user))) && (C in user.contents)) + if(!id_check(user, idcard)) + return + to_chat(user, "You put the ID into \the [src]'s slot.") + updateSelfDialog()//Update self dialog on success. + return //Return in case of failed check or when successful. + updateSelfDialog()//For the non-input related code. + else if(istype(C, /obj/item/paicard) && !pai) + if(!user.transferItemToLoc(C, src)) + return + pai = C + to_chat(user, "You slot \the [C] into [src].") + update_icon() + updateUsrDialog() + else if(is_type_in_list(C, contained_item)) //Checks if there is a pen + if(inserted_item) + to_chat(user, "There is already \a [inserted_item] in \the [src]!") + else + if(!user.transferItemToLoc(C, src)) + return + to_chat(user, "You slide \the [C] into \the [src].") + inserted_item = C + update_icon() + playsound(src, 'sound/machines/button.ogg', 50, 1) + + else if(istype(C, /obj/item/photo)) + var/obj/item/photo/P = C + picture = P.picture + to_chat(user, "You scan \the [C].") + else + return ..() + +/obj/item/pda/attack(mob/living/carbon/C, mob/living/user) + if(istype(C)) + switch(scanmode) + + if(PDA_SCANNER_MEDICAL) + C.visible_message("[user] has analyzed [C]'s vitals!") + healthscan(user, C, 1) + add_fingerprint(user) + + if(PDA_SCANNER_HALOGEN) + C.visible_message("[user] has analyzed [C]'s radiation levels!") + + user.show_message("Analyzing Results for [C]:") + if(C.radiation) + user.show_message("\green Radiation Level: \black [C.radiation]") + else + user.show_message("No radiation detected.") + +/obj/item/pda/afterattack(atom/A as mob|obj|turf|area, mob/user, proximity) + . = ..() + if(!proximity) + return + switch(scanmode) + if(PDA_SCANNER_REAGENT) + if(!isnull(A.reagents)) + if(A.reagents.reagent_list.len > 0) + var/reagents_length = A.reagents.reagent_list.len + to_chat(user, "[reagents_length] chemical agent[reagents_length > 1 ? "s" : ""] found.") + for (var/re in A.reagents.reagent_list) + to_chat(user, "\t [re]") + else + to_chat(user, "No active chemical agents found in [A].") + else + to_chat(user, "No significant chemical agents found in [A].") + + if(PDA_SCANNER_GAS) + A.analyzer_act(user, src) + + if (!scanmode && istype(A, /obj/item/paper) && owner) + var/obj/item/paper/PP = A + if (!PP.info) + to_chat(user, "Unable to scan! Paper is blank.") + return + notehtml = PP.info + note = replacetext(notehtml, "
    ", "\[br\]") + note = replacetext(note, "
  • ", "\[*\]") + note = replacetext(note, "
      ", "\[list\]") + note = replacetext(note, "
    ", "\[/list\]") + note = html_encode(note) + notescanned = TRUE + to_chat(user, "Paper scanned. Saved to PDA's notekeeper." ) + + +/obj/item/pda/proc/explode() //This needs tuning. + if(!detonatable) + return + var/turf/T = get_turf(src) + + if (ismob(loc)) + var/mob/M = loc + M.show_message("Your [src] explodes!", 1) + else + visible_message("[src] explodes!", "You hear a loud *pop*!") + + if(T) + T.hotspot_expose(700,125) + if(istype(cartridge, /obj/item/cartridge/virus/syndicate)) + explosion(T, -1, 1, 3, 4) + else + explosion(T, -1, -1, 2, 3) + qdel(src) + return + +/obj/item/pda/Destroy() + GLOB.PDAs -= src + if(istype(id)) + QDEL_NULL(id) + if(istype(cartridge)) + QDEL_NULL(cartridge) + if(istype(pai)) + QDEL_NULL(pai) + if(istype(inserted_item)) + QDEL_NULL(inserted_item) + return ..() + +//AI verb and proc for sending PDA messages. + +/mob/living/silicon/ai/proc/cmd_send_pdamesg(mob/user) + var/list/plist = list() + var/list/namecounts = list() + + if(aiPDA.toff) + to_chat(user, "Turn on your receiver in order to send messages.") + return + + for (var/obj/item/pda/P in get_viewable_pdas()) + if (P == src) + continue + else if (P == aiPDA) + continue + + plist[avoid_assoc_duplicate_keys(P.owner, namecounts)] = P + + var/c = input(user, "Please select a PDA") as null|anything in sortList(plist) + + if (!c) + return + + var/selected = plist[c] + + if(aicamera.stored.len) + var/add_photo = input(user,"Do you want to attach a photo?","Photo","No") as null|anything in list("Yes","No") + if(add_photo=="Yes") + var/datum/picture/Pic = aicamera.selectpicture(user) + aiPDA.picture = Pic + + if(incapacitated()) + return + + aiPDA.create_message(src, selected) + + +/mob/living/silicon/ai/verb/cmd_toggle_pda_receiver() + set category = "AI Commands" + set name = "PDA - Toggle Sender/Receiver" + if(usr.stat == DEAD) + return //won't work if dead + if(!isnull(aiPDA)) + aiPDA.toff = !aiPDA.toff + to_chat(usr, "PDA sender/receiver toggled [(aiPDA.toff ? "Off" : "On")]!") + else + to_chat(usr, "You do not have a PDA. You should make an issue report about this.") + +/mob/living/silicon/ai/verb/cmd_toggle_pda_silent() + set category = "AI Commands" + set name = "PDA - Toggle Ringer" + if(usr.stat == DEAD) + return //won't work if dead + if(!isnull(aiPDA)) + //0 + aiPDA.silent = !aiPDA.silent + to_chat(usr, "PDA ringer toggled [(aiPDA.silent ? "Off" : "On")]!") + else + to_chat(usr, "You do not have a PDA. You should make an issue report about this.") + +/mob/living/silicon/ai/proc/cmd_show_message_log(mob/user) + if(incapacitated()) + return + if(!isnull(aiPDA)) + var/HTML = "AI PDA Message Log[aiPDA.tnote]" + user << browse(HTML, "window=log;size=400x444;border=1;can_resize=1;can_close=1;can_minimize=0") + else + to_chat(user, "You do not have a PDA. You should make an issue report about this.") + + +// Pass along the pulse to atoms in contents, largely added so pAIs are vulnerable to EMP +/obj/item/pda/emp_act(severity) + . = ..() + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/atom/A in src) + A.emp_act(severity) + if (!(. & EMP_PROTECT_SELF)) + emped += 1 + spawn(200 * severity) + emped -= 1 + +/proc/get_viewable_pdas() + . = list() + // Returns a list of PDAs which can be viewed from another PDA/message monitor. + for(var/obj/item/pda/P in GLOB.PDAs) + if(!P.owner || P.toff || P.hidden) + continue + . += P + +#undef PDA_SCANNER_NONE +#undef PDA_SCANNER_MEDICAL +#undef PDA_SCANNER_FORENSICS +#undef PDA_SCANNER_REAGENT +#undef PDA_SCANNER_HALOGEN +#undef PDA_SCANNER_GAS +#undef PDA_SPAM_DELAY +#undef PDA_STANDARD_OVERLAYS + +#undef PDA_OVERLAY_ALERT +#undef PDA_OVERLAY_SCREEN +#undef PDA_OVERLAY_ID +#undef PDA_OVERLAY_ITEM +#undef PDA_OVERLAY_LIGHT +#undef PDA_OVERLAY_PAI diff --git a/code/game/objects/items/devices/desynchronizer.dm b/code/game/objects/items/devices/desynchronizer.dm new file mode 100644 index 00000000..dc38f5cb --- /dev/null +++ b/code/game/objects/items/devices/desynchronizer.dm @@ -0,0 +1,91 @@ +//NOT YET IMPLEMENTED -- Archie + +/obj/item/desynchronizer + name = "desynchronizer" + desc = "An experimental device that can temporarily desynchronize the user from spacetime, effectively making them disappear while it's active." + icon = 'icons/obj/device.dmi' + icon_state = "desynchronizer" + item_state = "electronic" + w_class = WEIGHT_CLASS_SMALL + item_flags = NOBLUDGEON + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + materials = list(MAT_METAL = 250, MAT_GLASS = 500) + var/max_duration = 3000 + var/duration = 300 + var/last_use = 0 + var/next_use = 0 + var/obj/effect/abstract/sync_holder/sync_holder + +/obj/item/desynchronizer/attack_self(mob/living/user) + if(world.time < next_use) + to_chat(user, "[src] is still recharging.") + return + if(!sync_holder) + desync(user) + else + resync() + +/obj/item/desynchronizer/examine(mob/user) + . = ..() + if(world.time < next_use) + . += "Time left to recharge: [DisplayTimeText(next_use - world.time)]" + . += "Alt-click to customize the duration. Current duration: [DisplayTimeText(duration)]." + . += "Can be used again to interrupt the effect early. The recharge time is the same as the time spent in desync." + +/obj/item/desynchronizer/AltClick(mob/living/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + var/new_duration = input(user, "Set the duration (5-300):", "Desynchronizer", duration / 10) as null|num + if(new_duration) + new_duration = new_duration SECONDS + new_duration = CLAMP(new_duration, 50, max_duration) + duration = new_duration + to_chat(user, "You set the duration to [DisplayTimeText(duration)].") + return TRUE + +/obj/item/desynchronizer/proc/desync(mob/living/user) + if(sync_holder) + return + sync_holder = new(drop_location()) + new /obj/effect/temp_visual/desynchronizer(drop_location()) + to_chat(user, "You activate [src], desynchronizing yourself from the present. You can still see your surroundings, but you feel eerily dissociated from reality.") + user.forceMove(sync_holder) + SEND_SIGNAL(user, COMSIG_MOVABLE_SECLUDED_LOCATION) + for(var/thing in user) + var/atom/movable/AM = thing + SEND_SIGNAL(AM, COMSIG_MOVABLE_SECLUDED_LOCATION) + last_use = world.time + icon_state = "desynchronizer-on" + addtimer(CALLBACK(src, .proc/resync), duration) + +/obj/item/desynchronizer/proc/resync() + new /obj/effect/temp_visual/desynchronizer(sync_holder.drop_location()) + QDEL_NULL(sync_holder) + icon_state = initial(icon_state) + next_use = world.time + (world.time - last_use) // Could be 2*world.time-last_use but that would just be confusing + +/obj/item/desynchronizer/Destroy() + resync() + return ..() + +/obj/effect/abstract/sync_holder + name = "desyncronized pocket" + desc = "A pocket in spacetime, keeping the user a fraction of a second in the future." + icon = null + icon_state = null + alpha = 0 + invisibility = INVISIBILITY_ABSTRACT + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + anchored = TRUE + resistance_flags = INDESTRUCTIBLE + +/obj/effect/abstract/sync_holder/Destroy() + for(var/I in contents) + var/atom/movable/AM = I + AM.forceMove(drop_location()) + return ..() + +/obj/effect/abstract/sync_holder/AllowDrop() + return TRUE //no dropping spaghetti out of your spacetime pocket diff --git a/code/game/objects/items/devices/geiger_counter.dm b/code/game/objects/items/devices/geiger_counter.dm index 9d03ab47..0dbede18 100644 --- a/code/game/objects/items/devices/geiger_counter.dm +++ b/code/game/objects/items/devices/geiger_counter.dm @@ -184,14 +184,16 @@ return ..() /obj/item/geiger_counter/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) - return ..() + return if(!scanning) to_chat(usr, "[src] must be on to reset its radiation level!") - return 0 + return TRUE radiation_count = 0 to_chat(usr, "You flush [src]'s radiation counts, resetting it to normal.") update_icon() + return TRUE /obj/item/geiger_counter/emag_act(mob/user) if(obj_flags & EMAGGED) diff --git a/code/game/objects/items/devices/gps.dm b/code/game/objects/items/devices/gps.dm index 70f03a72..bb1c71d1 100644 --- a/code/game/objects/items/devices/gps.dm +++ b/code/game/objects/items/devices/gps.dm @@ -1,224 +1,226 @@ -GLOBAL_LIST_EMPTY(GPS_list) -/obj/item/gps - name = "global positioning system" - desc = "Helping lost spacemen find their way through the planets since 2016." - icon = 'icons/obj/telescience.dmi' - icon_state = "gps-c" - w_class = WEIGHT_CLASS_SMALL - slot_flags = ITEM_SLOT_BELT - obj_flags = UNIQUE_RENAME - var/gpstag = "COM0" - var/emped = FALSE - var/tracking = TRUE - var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user. - var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown - -/obj/item/gps/examine(mob/user) - . = ..() - . += "Alt-click to switch it [tracking ? "off":"on"]." - -/obj/item/gps/Initialize() - . = ..() - GLOB.GPS_list += src - name = "global positioning system ([gpstag])" - add_overlay("working") - -/obj/item/gps/Destroy() - GLOB.GPS_list -= src - return ..() - -/obj/item/gps/emp_act(severity) - . = ..() - if (. & EMP_PROTECT_SELF) - return - emped = TRUE - cut_overlay("working") - add_overlay("emp") - addtimer(CALLBACK(src, .proc/reboot), 300, TIMER_UNIQUE|TIMER_OVERRIDE) //if a new EMP happens, remove the old timer so it doesn't reactivate early - SStgui.close_uis(src) //Close the UI control if it is open. - -/obj/item/gps/proc/reboot() - emped = FALSE - cut_overlay("emp") - add_overlay("working") - -/obj/item/gps/AltClick(mob/user) - if(!user.canUseTopic(src, BE_CLOSE)) - return - toggletracking(user) - -/obj/item/gps/proc/toggletracking(mob/user) - if(!user.canUseTopic(src, BE_CLOSE)) - return //user not valid to use gps - if(emped) - to_chat(user, "It's busted!") - return - if(tracking) - cut_overlay("working") - to_chat(user, "[src] is no longer tracking, or visible to other GPS devices.") - tracking = FALSE - else - add_overlay("working") - to_chat(user, "[src] is now tracking, and visible to other GPS devices.") - tracking = TRUE - - -/obj/item/gps/ui_interact(mob/user, ui_key = "gps", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. - if(emped) - to_chat(user, "[src] fizzles weakly.") - return - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - var/gps_window_height = 300 + GLOB.GPS_list.len * 20 // Variable window height, depending on how many GPS units there are to show - ui = new(user, src, ui_key, "gps", "Global Positioning System", 600, gps_window_height, master_ui, state) //width, height - ui.open() - - ui.set_autoupdate(state = updating) - - -/obj/item/gps/ui_data(mob/user) - var/list/data = list() - data["power"] = tracking - data["tag"] = gpstag - data["updating"] = updating - data["globalmode"] = global_mode - if(!tracking || emped) //Do not bother scanning if the GPS is off or EMPed - return data - - var/turf/curr = get_turf(src) - data["current"] = "[get_area_name(curr, TRUE)] ([curr.x], [curr.y], [curr.z])" - - var/list/signals = list() - data["signals"] = list() - - for(var/gps in GLOB.GPS_list) - var/obj/item/gps/G = gps - if(G.emped || !G.tracking || G == src) - continue - var/turf/pos = get_turf(G) - if(!global_mode && pos.z != curr.z) - continue - var/list/signal = list() - signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS - signal["area"] = get_area_name(G, TRUE) - signal["coord"] = "[pos.x], [pos.y], [pos.z]" - if(pos.z == curr.z) //Distance/Direction calculations for same z-level only - signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs - signal["degrees"] = round(Get_Angle(curr, pos)) //0-360 degree directional bearing, for more precision. - var/direction = uppertext(dir2text(get_dir(curr, pos))) //Direction text (East, etc). Not as precise, but still helpful. - if(!direction) - direction = "CENTER" - signal["degrees"] = "N/A" - signal["direction"] = direction - - signals += list(signal) //Add this signal to the list of signals - data["signals"] = signals - return data - - - -/obj/item/gps/ui_act(action, params) - if(..()) - return - switch(action) - if("rename") - var/a = input("Please enter desired tag.", name, gpstag) as text - a = copytext(sanitize(a), 1, 20) - gpstag = a - . = TRUE - name = "global positioning system ([gpstag])" - - if("power") - toggletracking(usr) - . = TRUE - if("updating") - updating = !updating - . = TRUE - if("globalmode") - global_mode = !global_mode - . = TRUE - - -/obj/item/gps/science - icon_state = "gps-s" - gpstag = "SCI0" - -/obj/item/gps/engineering - icon_state = "gps-e" - gpstag = "ENG0" - -/obj/item/gps/mining - icon_state = "gps-m" - gpstag = "MINE0" - desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." - -/obj/item/gps/cyborg - icon_state = "gps-b" - gpstag = "BORG0" - desc = "A mining cyborg internal positioning system. Used as a recovery beacon for damaged cyborg assets, or a collaboration tool for mining teams." - -/obj/item/gps/cyborg/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) - -/obj/item/gps/internal - icon_state = null - item_flags = ABSTRACT - gpstag = "Eerie Signal" - desc = "Report to a coder immediately." - invisibility = INVISIBILITY_MAXIMUM - var/obj/item/implant/gps/implant - -/obj/item/gps/internal/Initialize(mapload, obj/item/implant/gps/_implant) - . = ..() - implant = _implant - -/obj/item/gps/internal/Destroy() - if(implant?.imp_in) - qdel(implant) - else - return ..() - -/obj/item/gps/internal/mining - icon_state = "gps-m" - gpstag = "MINER" - desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." - -/obj/item/gps/internal/base - gpstag = "NT_AUX" - desc = "A homing signal from Kinaris's mining base." - -/obj/item/gps/visible_debug - name = "visible GPS" - gpstag = "ADMIN" - desc = "This admin-spawn GPS unit leaves the coordinates visible \ - on any turf that it passes over, for debugging. Especially useful \ - for marking the area around the transition edges." - var/list/turf/tagged - -/obj/item/gps/visible_debug/Initialize() - . = ..() - tagged = list() - START_PROCESSING(SSfastprocess, src) - -/obj/item/gps/visible_debug/process() - var/turf/T = get_turf(src) - if(T) - // I assume it's faster to color,tag and OR the turf in, rather - // then checking if its there - T.color = RANDOM_COLOUR - T.maptext = "[T.x],[T.y],[T.z]" - tagged |= T - -/obj/item/gps/visible_debug/proc/clear() - while(tagged.len) - var/turf/T = pop(tagged) - T.color = initial(T.color) - T.maptext = initial(T.maptext) - -/obj/item/gps/visible_debug/Destroy() - if(tagged) - clear() - tagged = null - STOP_PROCESSING(SSfastprocess, src) - . = ..() +GLOBAL_LIST_EMPTY(GPS_list) +/obj/item/gps + name = "global positioning system" + desc = "Helping lost spacemen find their way through the planets since 2016." + icon = 'icons/obj/telescience.dmi' + icon_state = "gps-c" + w_class = WEIGHT_CLASS_SMALL + slot_flags = ITEM_SLOT_BELT + obj_flags = UNIQUE_RENAME + var/gpstag = "COM0" + var/emped = FALSE + var/tracking = TRUE + var/updating = TRUE //Automatic updating of GPS list. Can be set to manual by user. + var/global_mode = TRUE //If disabled, only GPS signals of the same Z level are shown + +/obj/item/gps/examine(mob/user) + . = ..() + . += "Alt-click to switch it [tracking ? "off":"on"]." + +/obj/item/gps/Initialize() + . = ..() + GLOB.GPS_list += src + name = "global positioning system ([gpstag])" + add_overlay("working") + +/obj/item/gps/Destroy() + GLOB.GPS_list -= src + return ..() + +/obj/item/gps/emp_act(severity) + . = ..() + if (. & EMP_PROTECT_SELF) + return + emped = TRUE + cut_overlay("working") + add_overlay("emp") + addtimer(CALLBACK(src, .proc/reboot), 300, TIMER_UNIQUE|TIMER_OVERRIDE) //if a new EMP happens, remove the old timer so it doesn't reactivate early + SStgui.close_uis(src) //Close the UI control if it is open. + +/obj/item/gps/proc/reboot() + emped = FALSE + cut_overlay("emp") + add_overlay("working") + +/obj/item/gps/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, BE_CLOSE)) + return + toggletracking(user) + return TRUE + +/obj/item/gps/proc/toggletracking(mob/user) + if(!user.canUseTopic(src, BE_CLOSE)) + return //user not valid to use gps + if(emped) + to_chat(user, "It's busted!") + return + if(tracking) + cut_overlay("working") + to_chat(user, "[src] is no longer tracking, or visible to other GPS devices.") + tracking = FALSE + else + add_overlay("working") + to_chat(user, "[src] is now tracking, and visible to other GPS devices.") + tracking = TRUE + + +/obj/item/gps/ui_interact(mob/user, ui_key = "gps", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) // Remember to use the appropriate state. + if(emped) + to_chat(user, "[src] fizzles weakly.") + return + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + var/gps_window_height = 300 + GLOB.GPS_list.len * 20 // Variable window height, depending on how many GPS units there are to show + ui = new(user, src, ui_key, "gps", "Global Positioning System", 600, gps_window_height, master_ui, state) //width, height + ui.open() + + ui.set_autoupdate(state = updating) + + +/obj/item/gps/ui_data(mob/user) + var/list/data = list() + data["power"] = tracking + data["tag"] = gpstag + data["updating"] = updating + data["globalmode"] = global_mode + if(!tracking || emped) //Do not bother scanning if the GPS is off or EMPed + return data + + var/turf/curr = get_turf(src) + data["current"] = "[get_area_name(curr, TRUE)] ([curr.x], [curr.y], [curr.z])" + + var/list/signals = list() + data["signals"] = list() + + for(var/gps in GLOB.GPS_list) + var/obj/item/gps/G = gps + if(G.emped || !G.tracking || G == src) + continue + var/turf/pos = get_turf(G) + if(!global_mode && pos.z != curr.z) + continue + var/list/signal = list() + signal["entrytag"] = G.gpstag //Name or 'tag' of the GPS + signal["area"] = get_area_name(G, TRUE) + signal["coord"] = "[pos.x], [pos.y], [pos.z]" + if(pos.z == curr.z) //Distance/Direction calculations for same z-level only + signal["dist"] = max(get_dist(curr, pos), 0) //Distance between the src and remote GPS turfs + signal["degrees"] = round(Get_Angle(curr, pos)) //0-360 degree directional bearing, for more precision. + var/direction = uppertext(dir2text(get_dir(curr, pos))) //Direction text (East, etc). Not as precise, but still helpful. + if(!direction) + direction = "CENTER" + signal["degrees"] = "N/A" + signal["direction"] = direction + + signals += list(signal) //Add this signal to the list of signals + data["signals"] = signals + return data + + + +/obj/item/gps/ui_act(action, params) + if(..()) + return + switch(action) + if("rename") + var/a = input("Please enter desired tag.", name, gpstag) as text + a = copytext(sanitize(a), 1, 20) + gpstag = a + . = TRUE + name = "global positioning system ([gpstag])" + + if("power") + toggletracking(usr) + . = TRUE + if("updating") + updating = !updating + . = TRUE + if("globalmode") + global_mode = !global_mode + . = TRUE + + +/obj/item/gps/science + icon_state = "gps-s" + gpstag = "SCI0" + +/obj/item/gps/engineering + icon_state = "gps-e" + gpstag = "ENG0" + +/obj/item/gps/mining + icon_state = "gps-m" + gpstag = "MINE0" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." + +/obj/item/gps/cyborg + icon_state = "gps-b" + gpstag = "BORG0" + desc = "A mining cyborg internal positioning system. Used as a recovery beacon for damaged cyborg assets, or a collaboration tool for mining teams." + +/obj/item/gps/cyborg/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, CYBORG_ITEM_TRAIT) + +/obj/item/gps/internal + icon_state = null + item_flags = ABSTRACT + gpstag = "Eerie Signal" + desc = "Report to a coder immediately." + invisibility = INVISIBILITY_MAXIMUM + var/obj/item/implant/gps/implant + +/obj/item/gps/internal/Initialize(mapload, obj/item/implant/gps/_implant) + . = ..() + implant = _implant + +/obj/item/gps/internal/Destroy() + if(implant?.imp_in) + qdel(implant) + else + return ..() + +/obj/item/gps/internal/mining + icon_state = "gps-m" + gpstag = "MINER" + desc = "A positioning system helpful for rescuing trapped or injured miners, keeping one on you at all times while mining might just save your life." + +/obj/item/gps/internal/base + gpstag = "NT_AUX" + desc = "A homing signal from Kinaris's mining base." + +/obj/item/gps/visible_debug + name = "visible GPS" + gpstag = "ADMIN" + desc = "This admin-spawn GPS unit leaves the coordinates visible \ + on any turf that it passes over, for debugging. Especially useful \ + for marking the area around the transition edges." + var/list/turf/tagged + +/obj/item/gps/visible_debug/Initialize() + . = ..() + tagged = list() + START_PROCESSING(SSfastprocess, src) + +/obj/item/gps/visible_debug/process() + var/turf/T = get_turf(src) + if(T) + // I assume it's faster to color,tag and OR the turf in, rather + // then checking if its there + T.color = RANDOM_COLOUR + T.maptext = "[T.x],[T.y],[T.z]" + tagged |= T + +/obj/item/gps/visible_debug/proc/clear() + while(tagged.len) + var/turf/T = pop(tagged) + T.color = initial(T.color) + T.maptext = initial(T.maptext) + +/obj/item/gps/visible_debug/Destroy() + if(tagged) + clear() + tagged = null + STOP_PROCESSING(SSfastprocess, src) + . = ..() diff --git a/code/game/objects/items/devices/quantum_keycard.dm b/code/game/objects/items/devices/quantum_keycard.dm index fc9ccdda..33f839fa 100644 --- a/code/game/objects/items/devices/quantum_keycard.dm +++ b/code/game/objects/items/devices/quantum_keycard.dm @@ -18,12 +18,14 @@ . += "Insert [src] into an active quantum pad to link it." /obj/item/quantum_keycard/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return to_chat(user, "You start pressing [src]'s unlink button...") if(do_after(user, 40, target = src)) to_chat(user, "The keycard beeps twice and disconnects the quantum link.") qpad = null + return TRUE /obj/item/quantum_keycard/update_icon() if(qpad) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index c5a52b42..422110ae 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -1,333 +1,335 @@ -// Used for translating channels to tokens on examination -GLOBAL_LIST_INIT(channel_tokens, list( - RADIO_CHANNEL_COMMON = RADIO_KEY_COMMON, - RADIO_CHANNEL_SCIENCE = RADIO_TOKEN_SCIENCE, - RADIO_CHANNEL_COMMAND = RADIO_TOKEN_COMMAND, - RADIO_CHANNEL_MEDICAL = RADIO_TOKEN_MEDICAL, - RADIO_CHANNEL_ENGINEERING = RADIO_TOKEN_ENGINEERING, - RADIO_CHANNEL_SECURITY = RADIO_TOKEN_SECURITY, - RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM, - RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE, - RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY, - RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE, - MODE_BINARY = MODE_TOKEN_BINARY, - RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE -)) - -/obj/item/radio/headset - name = "radio headset" - desc = "An updated, modular intercom that fits over the head. Takes encryption keys." - icon_state = "headset" - item_state = "headset" - materials = list(MAT_METAL=75) - subspace_transmission = TRUE - canhear_range = 0 // can't hear headsets from very far away - - slot_flags = ITEM_SLOT_EARS - var/obj/item/encryptionkey/keyslot2 = null - dog_fashion = null - -/obj/item/radio/headset/suicide_act(mob/living/carbon/user) - user.visible_message("[user] begins putting \the [src]'s antenna up [user.p_their()] nose! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer!") - return TOXLOSS - -/obj/item/radio/headset/examine(mob/user) - . = ..() - - if(item_flags & IN_INVENTORY && loc == user) - // construction of frequency description - var/list/avail_chans = list("Use [RADIO_KEY_COMMON] for the currently tuned frequency") - if(translate_binary) - avail_chans += "use [MODE_TOKEN_BINARY] for [MODE_BINARY]" - if(length(channels)) - for(var/i in 1 to length(channels)) - if(i == 1) - avail_chans += "use [MODE_TOKEN_DEPARTMENT] or [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" - else - avail_chans += "use [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" - . += "A small screen on the headset displays the following available frequencies:\n[english_list(avail_chans)]." - - if(command) - . += "Alt-click to toggle the high-volume mode." - else - . += "A small screen on the headset flashes, it's too small to read without holding or wearing the headset." - -/obj/item/radio/headset/Initialize() - . = ..() - recalculateChannels() - -/obj/item/radio/headset/Destroy() - QDEL_NULL(keyslot2) - return ..() - -/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language) - if (!listening) - return ITALICS | REDUCE_RANGE - return ..() - -/obj/item/radio/headset/can_receive(freq, level, AIuser) - if(ishuman(src.loc)) - var/mob/living/carbon/human/H = src.loc - if(H.ears == src) - return ..(freq, level) - else if(AIuser) - return ..(freq, level) - return FALSE - -/obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops - -/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection - name = "syndicate headset" - desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." - icon_state = "syndie_headset" - item_state = "syndie_headset" - -/obj/item/radio/headset/syndicate/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) - -/obj/item/radio/headset/syndicate/alt/leader - name = "team leader headset" - command = TRUE - -/obj/item/radio/headset/syndicate/Initialize() - . = ..() - make_syndie() - -/obj/item/radio/headset/binary -/obj/item/radio/headset/binary/Initialize() - . = ..() - qdel(keyslot) - keyslot = new /obj/item/encryptionkey/binary - recalculateChannels() - -/obj/item/radio/headset/headset_sec - name = "security radio headset" - desc = "This is used by your elite security force." - icon_state = "sec_headset" - keyslot = new /obj/item/encryptionkey/headset_sec - -/obj/item/radio/headset/headset_sec/alt - name = "security bowman headset" - desc = "This is used by your elite security force. Protects ears from flashbangs." - icon_state = "sec_headset_alt" - item_state = "sec_headset_alt" - -/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) - -/obj/item/radio/headset/headset_eng - name = "engineering radio headset" - desc = "When the engineers wish to chat like girls." - icon_state = "eng_headset" - keyslot = new /obj/item/encryptionkey/headset_eng - -/obj/item/radio/headset/headset_rob - name = "robotics radio headset" - desc = "Made specifically for the roboticists, who cannot decide between departments." - icon_state = "rob_headset" - keyslot = new /obj/item/encryptionkey/headset_rob - -/obj/item/radio/headset/headset_med - name = "medical radio headset" - desc = "A headset for the trained staff of the medbay." - icon_state = "med_headset" - keyslot = new /obj/item/encryptionkey/headset_med - -/obj/item/radio/headset/headset_sci - name = "science radio headset" - desc = "A sciency headset. Like usual." - icon_state = "sci_headset" - keyslot = new /obj/item/encryptionkey/headset_sci - -/obj/item/radio/headset/headset_medsci - name = "medical research radio headset" - desc = "A headset that is a result of the mating between medical and science." - icon_state = "medsci_headset" - keyslot = new /obj/item/encryptionkey/headset_medsci - -/obj/item/radio/headset/headset_com - name = "command radio headset" - desc = "A headset with a commanding channel.\nTo access the command channel, use :c." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/headset_com - -/obj/item/radio/headset/heads - command = TRUE - -/obj/item/radio/headset/heads/captain - name = "\proper the captain's headset" - desc = "The headset of the king." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/heads/captain/alt - name = "\proper the captain's bowman headset" - desc = "The headset of the boss. Protects ears from flashbangs." - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) - -/obj/item/radio/headset/heads/rd - name = "\proper the research director's headset" - desc = "Headset of the fellow who keeps society marching towards technological singularity." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/rd - -/obj/item/radio/headset/heads/hos - name = "\proper the head of security's headset" - desc = "The headset of the man in charge of keeping order and protecting the station." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/hos - -/obj/item/radio/headset/heads/hos/alt - name = "\proper the head of security's bowman headset" - desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." - icon_state = "com_headset_alt" - item_state = "com_headset_alt" - -/obj/item/radio/headset/heads/hos/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) - -/obj/item/radio/headset/heads/ce - name = "\proper the chief engineer's headset" - desc = "The headset of the guy in charge of keeping the station powered and undamaged." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/ce - -/obj/item/radio/headset/heads/cmo - name = "\proper the chief medical officer's headset" - desc = "The headset of the highly trained medical chief." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/cmo - -/obj/item/radio/headset/heads/hop - name = "\proper the head of personnel's headset" - desc = "The headset of the guy who will one day be captain." - icon_state = "com_headset" - keyslot = new /obj/item/encryptionkey/heads/hop - -/obj/item/radio/headset/headset_cargo - name = "supply radio headset" - desc = "A headset used by the QM and his slaves." - icon_state = "cargo_headset" - keyslot = new /obj/item/encryptionkey/headset_cargo - -/obj/item/radio/headset/headset_cargo/mining - name = "mining radio headset" - desc = "Headset used by shaft miners." - icon_state = "mine_headset" - keyslot = new /obj/item/encryptionkey/headset_mining - -/obj/item/radio/headset/headset_srv - name = "service radio headset" - desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." - icon_state = "srv_headset" - keyslot = new /obj/item/encryptionkey/headset_service - -/obj/item/radio/headset/headset_cent - name = "\improper CentCom headset" - desc = "A headset used by the upper echelons of Kinaris." - icon_state = "cent_headset" - keyslot = new /obj/item/encryptionkey/headset_com - keyslot2 = new /obj/item/encryptionkey/headset_cent - -/obj/item/radio/headset/headset_cent/empty - keyslot = null - keyslot2 = null - -/obj/item/radio/headset/headset_cent/commander - keyslot = new /obj/item/encryptionkey/heads/captain - -/obj/item/radio/headset/headset_cent/alt - name = "\improper CentCom bowman headset" - desc = "A headset especially for emergency response personnel. Protects ears from flashbangs." - icon_state = "cent_headset_alt" - item_state = "cent_headset_alt" - keyslot = null - -/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() - . = ..() - AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) - -/obj/item/radio/headset/ai - name = "\proper Integrated Subspace Transceiver " - keyslot2 = new /obj/item/encryptionkey/ai - command = TRUE - -/obj/item/radio/headset/ai/can_receive(freq, level) - return ..(freq, level, TRUE) - -/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) - user.set_machine(src) - - if(istype(W, /obj/item/screwdriver)) - if(keyslot || keyslot2) - for(var/ch_name in channels) - SSradio.remove_object(src, GLOB.radiochannels[ch_name]) - secure_radio_connections[ch_name] = null - - var/turf/T = user.drop_location() - if(T) - if(keyslot) - keyslot.forceMove(T) - keyslot = null - if(keyslot2) - keyslot2.forceMove(T) - keyslot2 = null - - recalculateChannels() - to_chat(user, "You pop out the encryption keys in the headset.") - - else - to_chat(user, "This headset doesn't have any unique encryption keys! How useless...") - - else if(istype(W, /obj/item/encryptionkey)) - if(keyslot && keyslot2) - to_chat(user, "The headset can't hold another key!") - return - - if(!keyslot) - if(!user.transferItemToLoc(W, src)) - return - keyslot = W - - else - if(!user.transferItemToLoc(W, src)) - return - keyslot2 = W - - - recalculateChannels() - else - return ..() - - -/obj/item/radio/headset/recalculateChannels() - ..() - if(keyslot2) - for(var/ch_name in keyslot2.channels) - if(!(ch_name in src.channels)) - channels[ch_name] = keyslot2.channels[ch_name] - - if(keyslot2.translate_binary) - translate_binary = TRUE - if(keyslot2.syndie) - syndie = TRUE - if (keyslot2.independent) - independent = TRUE - - for(var/ch_name in channels) - secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) - -/obj/item/radio/headset/AltClick(mob/living/user) - if(!istype(user) || !Adjacent(user) || user.incapacitated()) - return - if (command) - use_command = !use_command - to_chat(user, "You toggle high-volume mode [use_command ? "on" : "off"].") +// Used for translating channels to tokens on examination +GLOBAL_LIST_INIT(channel_tokens, list( + RADIO_CHANNEL_COMMON = RADIO_KEY_COMMON, + RADIO_CHANNEL_SCIENCE = RADIO_TOKEN_SCIENCE, + RADIO_CHANNEL_COMMAND = RADIO_TOKEN_COMMAND, + RADIO_CHANNEL_MEDICAL = RADIO_TOKEN_MEDICAL, + RADIO_CHANNEL_ENGINEERING = RADIO_TOKEN_ENGINEERING, + RADIO_CHANNEL_SECURITY = RADIO_TOKEN_SECURITY, + RADIO_CHANNEL_CENTCOM = RADIO_TOKEN_CENTCOM, + RADIO_CHANNEL_SYNDICATE = RADIO_TOKEN_SYNDICATE, + RADIO_CHANNEL_SUPPLY = RADIO_TOKEN_SUPPLY, + RADIO_CHANNEL_SERVICE = RADIO_TOKEN_SERVICE, + MODE_BINARY = MODE_TOKEN_BINARY, + RADIO_CHANNEL_AI_PRIVATE = RADIO_TOKEN_AI_PRIVATE +)) + +/obj/item/radio/headset + name = "radio headset" + desc = "An updated, modular intercom that fits over the head. Takes encryption keys." + icon_state = "headset" + item_state = "headset" + materials = list(MAT_METAL=75) + subspace_transmission = TRUE + canhear_range = 0 // can't hear headsets from very far away + + slot_flags = ITEM_SLOT_EARS + var/obj/item/encryptionkey/keyslot2 = null + dog_fashion = null + +/obj/item/radio/headset/suicide_act(mob/living/carbon/user) + user.visible_message("[user] begins putting \the [src]'s antenna up [user.p_their()] nose! It looks like [user.p_theyre()] trying to give [user.p_them()]self cancer!") + return TOXLOSS + +/obj/item/radio/headset/examine(mob/user) + . = ..() + + if(item_flags & IN_INVENTORY && loc == user) + // construction of frequency description + var/list/avail_chans = list("Use [RADIO_KEY_COMMON] for the currently tuned frequency") + if(translate_binary) + avail_chans += "use [MODE_TOKEN_BINARY] for [MODE_BINARY]" + if(length(channels)) + for(var/i in 1 to length(channels)) + if(i == 1) + avail_chans += "use [MODE_TOKEN_DEPARTMENT] or [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" + else + avail_chans += "use [GLOB.channel_tokens[channels[i]]] for [lowertext(channels[i])]" + . += "A small screen on the headset displays the following available frequencies:\n[english_list(avail_chans)]." + + if(command) + . += "Alt-click to toggle the high-volume mode." + else + . += "A small screen on the headset flashes, it's too small to read without holding or wearing the headset." + +/obj/item/radio/headset/Initialize() + . = ..() + recalculateChannels() + +/obj/item/radio/headset/Destroy() + QDEL_NULL(keyslot2) + return ..() + +/obj/item/radio/headset/talk_into(mob/living/M, message, channel, list/spans,datum/language/language) + if (!listening) + return ITALICS | REDUCE_RANGE + return ..() + +/obj/item/radio/headset/can_receive(freq, level, AIuser) + if(ishuman(src.loc)) + var/mob/living/carbon/human/H = src.loc + if(H.ears == src) + return ..(freq, level) + else if(AIuser) + return ..(freq, level) + return FALSE + +/obj/item/radio/headset/syndicate //disguised to look like a normal headset for stealth ops + +/obj/item/radio/headset/syndicate/alt //undisguised bowman with flash protection + name = "syndicate headset" + desc = "A syndicate headset that can be used to hear all radio frequencies. Protects ears from flashbangs." + icon_state = "syndie_headset" + item_state = "syndie_headset" + +/obj/item/radio/headset/syndicate/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) + +/obj/item/radio/headset/syndicate/alt/leader + name = "team leader headset" + command = TRUE + +/obj/item/radio/headset/syndicate/Initialize() + . = ..() + make_syndie() + +/obj/item/radio/headset/binary +/obj/item/radio/headset/binary/Initialize() + . = ..() + qdel(keyslot) + keyslot = new /obj/item/encryptionkey/binary + recalculateChannels() + +/obj/item/radio/headset/headset_sec + name = "security radio headset" + desc = "This is used by your elite security force." + icon_state = "sec_headset" + keyslot = new /obj/item/encryptionkey/headset_sec + +/obj/item/radio/headset/headset_sec/alt + name = "security bowman headset" + desc = "This is used by your elite security force. Protects ears from flashbangs." + icon_state = "sec_headset_alt" + item_state = "sec_headset_alt" + +/obj/item/radio/headset/headset_sec/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) + +/obj/item/radio/headset/headset_eng + name = "engineering radio headset" + desc = "When the engineers wish to chat like girls." + icon_state = "eng_headset" + keyslot = new /obj/item/encryptionkey/headset_eng + +/obj/item/radio/headset/headset_rob + name = "robotics radio headset" + desc = "Made specifically for the roboticists, who cannot decide between departments." + icon_state = "rob_headset" + keyslot = new /obj/item/encryptionkey/headset_rob + +/obj/item/radio/headset/headset_med + name = "medical radio headset" + desc = "A headset for the trained staff of the medbay." + icon_state = "med_headset" + keyslot = new /obj/item/encryptionkey/headset_med + +/obj/item/radio/headset/headset_sci + name = "science radio headset" + desc = "A sciency headset. Like usual." + icon_state = "sci_headset" + keyslot = new /obj/item/encryptionkey/headset_sci + +/obj/item/radio/headset/headset_medsci + name = "medical research radio headset" + desc = "A headset that is a result of the mating between medical and science." + icon_state = "medsci_headset" + keyslot = new /obj/item/encryptionkey/headset_medsci + +/obj/item/radio/headset/headset_com + name = "command radio headset" + desc = "A headset with a commanding channel.\nTo access the command channel, use :c." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/headset_com + +/obj/item/radio/headset/heads + command = TRUE + +/obj/item/radio/headset/heads/captain + name = "\proper the captain's headset" + desc = "The headset of the king." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/heads/captain/alt + name = "\proper the captain's bowman headset" + desc = "The headset of the boss. Protects ears from flashbangs." + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/captain/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) + +/obj/item/radio/headset/heads/rd + name = "\proper the research director's headset" + desc = "Headset of the fellow who keeps society marching towards technological singularity." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/rd + +/obj/item/radio/headset/heads/hos + name = "\proper the head of security's headset" + desc = "The headset of the man in charge of keeping order and protecting the station." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/hos + +/obj/item/radio/headset/heads/hos/alt + name = "\proper the head of security's bowman headset" + desc = "The headset of the man in charge of keeping order and protecting the station. Protects ears from flashbangs." + icon_state = "com_headset_alt" + item_state = "com_headset_alt" + +/obj/item/radio/headset/heads/hos/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) + +/obj/item/radio/headset/heads/ce + name = "\proper the chief engineer's headset" + desc = "The headset of the guy in charge of keeping the station powered and undamaged." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/ce + +/obj/item/radio/headset/heads/cmo + name = "\proper the chief medical officer's headset" + desc = "The headset of the highly trained medical chief." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/cmo + +/obj/item/radio/headset/heads/hop + name = "\proper the head of personnel's headset" + desc = "The headset of the guy who will one day be captain." + icon_state = "com_headset" + keyslot = new /obj/item/encryptionkey/heads/hop + +/obj/item/radio/headset/headset_cargo + name = "supply radio headset" + desc = "A headset used by the QM and his slaves." + icon_state = "cargo_headset" + keyslot = new /obj/item/encryptionkey/headset_cargo + +/obj/item/radio/headset/headset_cargo/mining + name = "mining radio headset" + desc = "Headset used by shaft miners." + icon_state = "mine_headset" + keyslot = new /obj/item/encryptionkey/headset_mining + +/obj/item/radio/headset/headset_srv + name = "service radio headset" + desc = "Headset used by the service staff, tasked with keeping the station full, happy and clean." + icon_state = "srv_headset" + keyslot = new /obj/item/encryptionkey/headset_service + +/obj/item/radio/headset/headset_cent + name = "\improper CentCom headset" + desc = "A headset used by the upper echelons of Kinaris." + icon_state = "cent_headset" + keyslot = new /obj/item/encryptionkey/headset_com + keyslot2 = new /obj/item/encryptionkey/headset_cent + +/obj/item/radio/headset/headset_cent/empty + keyslot = null + keyslot2 = null + +/obj/item/radio/headset/headset_cent/commander + keyslot = new /obj/item/encryptionkey/heads/captain + +/obj/item/radio/headset/headset_cent/alt + name = "\improper CentCom bowman headset" + desc = "A headset especially for emergency response personnel. Protects ears from flashbangs." + icon_state = "cent_headset_alt" + item_state = "cent_headset_alt" + keyslot = null + +/obj/item/radio/headset/headset_cent/alt/ComponentInitialize() + . = ..() + AddComponent(/datum/component/wearertargeting/earprotection, list(SLOT_EARS)) + +/obj/item/radio/headset/ai + name = "\proper Integrated Subspace Transceiver " + keyslot2 = new /obj/item/encryptionkey/ai + command = TRUE + +/obj/item/radio/headset/ai/can_receive(freq, level) + return ..(freq, level, TRUE) + +/obj/item/radio/headset/attackby(obj/item/W, mob/user, params) + user.set_machine(src) + + if(istype(W, /obj/item/screwdriver)) + if(keyslot || keyslot2) + for(var/ch_name in channels) + SSradio.remove_object(src, GLOB.radiochannels[ch_name]) + secure_radio_connections[ch_name] = null + + var/turf/T = user.drop_location() + if(T) + if(keyslot) + keyslot.forceMove(T) + keyslot = null + if(keyslot2) + keyslot2.forceMove(T) + keyslot2 = null + + recalculateChannels() + to_chat(user, "You pop out the encryption keys in the headset.") + + else + to_chat(user, "This headset doesn't have any unique encryption keys! How useless...") + + else if(istype(W, /obj/item/encryptionkey)) + if(keyslot && keyslot2) + to_chat(user, "The headset can't hold another key!") + return + + if(!keyslot) + if(!user.transferItemToLoc(W, src)) + return + keyslot = W + + else + if(!user.transferItemToLoc(W, src)) + return + keyslot2 = W + + + recalculateChannels() + else + return ..() + + +/obj/item/radio/headset/recalculateChannels() + ..() + if(keyslot2) + for(var/ch_name in keyslot2.channels) + if(!(ch_name in src.channels)) + channels[ch_name] = keyslot2.channels[ch_name] + + if(keyslot2.translate_binary) + translate_binary = TRUE + if(keyslot2.syndie) + syndie = TRUE + if (keyslot2.independent) + independent = TRUE + + for(var/ch_name in channels) + secure_radio_connections[ch_name] = add_radio(src, GLOB.radiochannels[ch_name]) + +/obj/item/radio/headset/AltClick(mob/living/user) + . = ..() + if(!istype(user) || !Adjacent(user) || user.incapacitated()) + return + if (command) + use_command = !use_command + to_chat(user, "You toggle high-volume mode [use_command ? "on" : "off"].") + return TRUE diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners.dm index 4fdcca94..413f049c 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners.dm @@ -595,10 +595,10 @@ GENE SCANNER to_chat(user, "Temperature: [round(environment.temperature-T0C, 0.01)] °C ([round(environment.temperature, 0.01)] K)") /obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens - ..() + . = ..() if(user.canUseTopic(src)) - + . = TRUE if(cooldown) to_chat(user, "[src]'s barometer function is preparing itself.") return diff --git a/code/game/objects/items/flamethrower.dm b/code/game/objects/items/flamethrower.dm index d9525ab9..906572d0 100644 --- a/code/game/objects/items/flamethrower.dm +++ b/code/game/objects/items/flamethrower.dm @@ -138,11 +138,13 @@ toggle_igniter(user) /obj/item/flamethrower/AltClick(mob/user) + . = ..() if(ptank && isliving(user) && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) user.put_in_hands(ptank) ptank = null to_chat(user, "You remove the plasma tank from [src]!") update_icon() + return TRUE /obj/item/flamethrower/examine(mob/user) . = ..() diff --git a/code/game/objects/items/pet_carrier.dm b/code/game/objects/items/pet_carrier.dm index 41b6f052..4f0232b0 100644 --- a/code/game/objects/items/pet_carrier.dm +++ b/code/game/objects/items/pet_carrier.dm @@ -70,6 +70,7 @@ update_icon() /obj/item/pet_carrier/AltClick(mob/living/user) + . = ..() if(open || !user.canUseTopic(src, BE_CLOSE)) return locked = !locked @@ -79,6 +80,7 @@ else playsound(user, 'sound/machines/boltsup.ogg', 30, TRUE) update_icon() + return TRUE /obj/item/pet_carrier/attack(mob/living/target, mob/living/user) if(user.a_intent == INTENT_HARM) diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 6173ac52..3e1e028b 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -350,6 +350,7 @@ . = ..() /obj/item/stack/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return if(is_cyborg) @@ -363,10 +364,11 @@ max = get_amount() stackmaterial = min(max, stackmaterial) if(stackmaterial == null || stackmaterial <= 0 || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return + return TRUE else change_stack(user, stackmaterial) to_chat(user, "You take [stackmaterial] sheets out of the stack") + return TRUE /obj/item/stack/proc/change_stack(mob/user, amount) if(!use(amount, TRUE, FALSE)) diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 4e13446f..7c683455 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -842,23 +842,13 @@ obj/item/storage/belt/slut/ComponentInitialize() STR.rustle_sound = FALSE STR.max_w_class = WEIGHT_CLASS_BULKY STR.can_hold = typecacheof(fitting_swords) + STR.quickdraw = TRUE /obj/item/storage/belt/sabre/examine(mob/user) . = ..() if(length(contents)) . += "Alt-click it to quickly draw the blade." -/obj/item/storage/belt/sabre/AltClick(mob/user) - if(!iscarbon(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - if(length(contents)) - var/obj/item/I = contents[1] - user.visible_message("[user] takes [I] out of [src].", "You take [I] out of [src].") - user.put_in_hands(I) - update_icon() - else - to_chat(user, "[src] is empty.") - /obj/item/storage/belt/sabre/update_icon() . = ..() if(isliving(loc)) diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm index 6101f641..c59cf773 100644 --- a/code/game/objects/items/storage/fancy.dm +++ b/code/game/objects/items/storage/fancy.dm @@ -1,389 +1,390 @@ -/* - * The 'fancy' path is for objects like donut boxes that show how many items are in the storage item on the sprite itself - * .. Sorry for the shitty path name, I couldnt think of a better one. - * - * WARNING: var/icon_type is used for both examine text and sprite name. Please look at the procs below and adjust your sprite names accordingly - * TODO: Cigarette boxes should be ported to this standard - * - * Contains: - * Donut Box - * Egg Box - * Candle Box - * Cigarette Box - * Cigar Case - * Heart Shaped Box w/ Chocolates - * Ring Box - */ - -/obj/item/storage/fancy - icon = 'icons/obj/food/containers.dmi' - icon_state = "donutbox6" - name = "donut box" - desc = "Mmm. Donuts." - resistance_flags = FLAMMABLE - var/icon_type = "donut" - var/spawn_type = null - var/fancy_open = FALSE - -/obj/item/storage/fancy/PopulateContents() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - for(var/i = 1 to STR.max_items) - new spawn_type(src) - -/obj/item/storage/fancy/update_icon() - if(fancy_open) - icon_state = "[icon_type]box[contents.len]" - else - icon_state = "[icon_type]box" - -/obj/item/storage/fancy/examine(mob/user) - . = ..() - if(fancy_open) - if(length(contents) == 1) - . += "There is one [icon_type] left." - else - . += "There are [contents.len <= 0 ? "no" : "[contents.len]"] [icon_type]s left." - -/obj/item/storage/fancy/attack_self(mob/user) - fancy_open = !fancy_open - update_icon() - . = ..() - -/obj/item/storage/fancy/Exited() - . = ..() - fancy_open = TRUE - update_icon() - -/obj/item/storage/fancy/Entered() - . = ..() - fancy_open = TRUE - update_icon() - -/* - * Donut Box - */ - -/obj/item/storage/fancy/donut_box - icon = 'icons/obj/food/containers.dmi' - icon_state = "donutbox6" - icon_type = "donut" - name = "donut box" - spawn_type = /obj/item/reagent_containers/food/snacks/donut - fancy_open = TRUE - -/obj/item/storage/fancy/donut_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donut)) - -/* - * Egg Box - */ - -/obj/item/storage/fancy/egg_box - icon = 'icons/obj/food/containers.dmi' - item_state = "eggbox" - icon_state = "eggbox" - icon_type = "egg" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - name = "egg box" - desc = "A carton for containing eggs." - spawn_type = /obj/item/reagent_containers/food/snacks/egg - -/obj/item/storage/fancy/egg_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 12 - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/egg)) - -/* - * Candle Box - */ - -/obj/item/storage/fancy/candle_box - name = "candle pack" - desc = "A pack of red candles." - icon = 'icons/obj/candle.dmi' - icon_state = "candlebox5" - icon_type = "candle" - item_state = "candlebox5" - throwforce = 2 - slot_flags = ITEM_SLOT_BELT - spawn_type = /obj/item/candle - fancy_open = TRUE - -/obj/item/storage/fancy/candle_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - -/obj/item/storage/fancy/candle_box/attack_self(mob_user) - return - -//////////// -//CIG PACK// -//////////// -/obj/item/storage/fancy/cigarettes - name = "\improper Space Cigarettes packet" - desc = "The most popular brand of cigarettes, sponsors of the Space Olympics." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig" - item_state = "cigpacket" - w_class = WEIGHT_CLASS_TINY - throwforce = 0 - slot_flags = ITEM_SLOT_BELT - icon_type = "cigarette" - spawn_type = /obj/item/clothing/mask/cigarette/space_cigarette - -/obj/item/storage/fancy/cigarettes/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 6 - STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) - -/obj/item/storage/fancy/cigarettes/examine(mob/user) - . = ..() - . += "Alt-click to extract contents." - -/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents - if(W && contents.len > 0) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user) - user.put_in_hands(W) - contents -= W - to_chat(user, "You take \a [W] out of the pack.") - else - to_chat(user, "There are no [icon_type]s left in the pack.") - -/obj/item/storage/fancy/cigarettes/update_icon() - if(fancy_open || !contents.len) - cut_overlays() - if(!contents.len) - icon_state = "[initial(icon_state)]_empty" - else - icon_state = initial(icon_state) - add_overlay("[icon_state]_open") - var/cig_position = 1 - for(var/C in contents) - var/mutable_appearance/inserted_overlay = mutable_appearance(icon) - - if(istype(C, /obj/item/lighter/greyscale)) - inserted_overlay.icon_state = "lighter_in" - else if(istype(C, /obj/item/lighter)) - inserted_overlay.icon_state = "zippo_in" - else - inserted_overlay.icon_state = "cigarette" - - inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" - add_overlay(inserted_overlay) - cig_position++ - else - cut_overlays() - -/obj/item/storage/fancy/cigarettes/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) - if(!ismob(M)) - return - var/obj/item/clothing/mask/cigarette/cig = locate(/obj/item/clothing/mask/cigarette) in contents - if(cig) - if(M == user && contents.len > 0 && !user.wear_mask) - var/obj/item/clothing/mask/cigarette/W = cig - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M) - M.equip_to_slot_if_possible(W, SLOT_WEAR_MASK) - contents -= W - to_chat(user, "You take \a [W] out of the pack.") - else - ..() - else - to_chat(user, "There are no [icon_type]s left in the pack.") - -/obj/item/storage/fancy/cigarettes/dromedaryco - name = "\improper DromedaryCo packet" - desc = "A packet of six imported DromedaryCo cancer sticks. A label on the packaging reads, \"Wouldn't a slow death make a change?\"" - icon_state = "dromedary" - spawn_type = /obj/item/clothing/mask/cigarette/dromedary - -/obj/item/storage/fancy/cigarettes/smokekins - name = "\improper smokekins packet" - desc = "Let the Radiance fill your lungs" - icon_state = "smokekins" - spawn_type = /obj/item/clothing/mask/cigarette/smokekins - -/obj/item/storage/fancy/cigarettes/cigpack_uplift - name = "\improper Uplift Smooth packet" - desc = "Your favorite brand, now menthol flavored." - icon_state = "uplift" - spawn_type = /obj/item/clothing/mask/cigarette/uplift - -/obj/item/storage/fancy/cigarettes/cigpack_robust - name = "\improper Robust packet" - desc = "Smoked by the robust." - icon_state = "robust" - spawn_type = /obj/item/clothing/mask/cigarette/robust - -/obj/item/storage/fancy/cigarettes/cigpack_robustgold - name = "\improper Robust Gold packet" - desc = "Smoked by the truly robust." - icon_state = "robustg" - spawn_type = /obj/item/clothing/mask/cigarette/robustgold - -/obj/item/storage/fancy/cigarettes/cigpack_carp - name = "\improper Carp Classic packet" - desc = "Since 2313." - icon_state = "carp" - spawn_type = /obj/item/clothing/mask/cigarette/carp - -/obj/item/storage/fancy/cigarettes/cigpack_syndicate - name = "cigarette packet" - desc = "An obscure brand of cigarettes." - icon_state = "syndie" - spawn_type = /obj/item/clothing/mask/cigarette/syndicate - -/obj/item/storage/fancy/cigarettes/cigpack_midori - name = "\improper Midori Tabako packet" - desc = "You can't understand the runes, but the packet smells funny." - icon_state = "midori" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/nicotine - -/obj/item/storage/fancy/cigarettes/cigpack_shadyjims - name = "\improper Shady Jim's Super Slims packet" - desc = "Is your weight slowing you down? Having trouble running away from gravitational singularities? Can't stop stuffing your mouth? Smoke Shady Jim's Super Slims and watch all that fat burn away. Guaranteed results!" - icon_state = "shadyjim" - spawn_type = /obj/item/clothing/mask/cigarette/shadyjims - -/obj/item/storage/fancy/cigarettes/cigpack_xeno - name = "\improper Xeno Filtered packet" - desc = "Loaded with 100% pure slime. And also nicotine." - icon_state = "slime" - spawn_type = /obj/item/clothing/mask/cigarette/xeno - -/obj/item/storage/fancy/cigarettes/cigpack_cannabis - name = "\improper Freak Brothers' Special packet" - desc = "A label on the packaging reads, \"Endorsed by Phineas, Freddy and Franklin.\"" - icon_state = "midori" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/cannabis - -/obj/item/storage/fancy/cigarettes/cigpack_mindbreaker - name = "\improper Leary's Delight packet" - desc = "Banned in over 36 galaxies." - icon_state = "shadyjim" - spawn_type = /obj/item/clothing/mask/cigarette/rollie/mindbreaker - -/obj/item/storage/fancy/rollingpapers - name = "rolling paper pack" - desc = "A pack of Kinaris brand rolling papers." - w_class = WEIGHT_CLASS_TINY - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cig_paper_pack" - icon_type = "rolling paper" - spawn_type = /obj/item/rollingpaper - -/obj/item/storage/fancy/rollingpapers/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 10 - STR.can_hold = typecacheof(list(/obj/item/rollingpaper)) - -/obj/item/storage/fancy/rollingpapers/update_icon() - cut_overlays() - if(!contents.len) - add_overlay("[icon_state]_empty") - -///////////// -//CIGAR BOX// -///////////// - -/obj/item/storage/fancy/cigarettes/cigars - name = "\improper premium cigar case" - desc = "A case of premium cigars. Very expensive." - icon = 'icons/obj/cigarettes.dmi' - icon_state = "cigarcase" - w_class = WEIGHT_CLASS_NORMAL - icon_type = "premium cigar" - spawn_type = /obj/item/clothing/mask/cigarette/cigar - -/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 5 - STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette/cigar)) - -/obj/item/storage/fancy/cigarettes/cigars/update_icon() - cut_overlays() - if(fancy_open) - icon_state = "[initial(icon_state)]_open" - - var/cigar_position = 1 //generate sprites for cigars in the box - for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) - var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") - add_overlay(cigar_overlay) - cigar_position++ - - else - icon_state = "[initial(icon_state)]" - -/obj/item/storage/fancy/cigarettes/cigars/cohiba - name = "\improper Cohiba Robusto cigar case" - desc = "A case of imported Cohiba cigars, renowned for their strong flavor." - icon_state = "cohibacase" - spawn_type = /obj/item/clothing/mask/cigarette/cigar/cohiba - -/obj/item/storage/fancy/cigarettes/cigars/havana - name = "\improper premium Havanian cigar case" - desc = "A case of classy Havanian cigars." - icon_state = "cohibacase" - spawn_type = /obj/item/clothing/mask/cigarette/cigar/havana - -/* - * Heart Shaped Box w/ Chocolates - */ - -/obj/item/storage/fancy/heart_box - name = "heart-shaped box" - desc = "A heart-shaped box for holding tiny chocolates." - icon = 'icons/obj/food/containers.dmi' - item_state = "chocolatebox" - icon_state = "chocolatebox" - icon_type = "chocolate" - lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' - spawn_type = /obj/item/reagent_containers/food/snacks/tinychocolate - -/obj/item/storage/fancy/heart_box/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 8 - STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) - -/* - * Ring Box - */ - -/obj/item/storage/fancy/ringbox - name = "ring box" - desc = "A tiny box covered in soft red felt made for holding rings." - icon = 'icons/obj/ring.dmi' - icon_state = "gold ringbox" - icon_type = "gold ring" - w_class = WEIGHT_CLASS_TINY - spawn_type = /obj/item/clothing/gloves/ring - -/obj/item/storage/fancy/ringbox/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_items = 1 - STR.can_hold = typecacheof(list(/obj/item/clothing/gloves/ring)) - -/obj/item/storage/fancy/ringbox/diamond - icon_state = "diamond ringbox" - icon_type = "diamond ring" - spawn_type = /obj/item/clothing/gloves/ring/diamond - -/obj/item/storage/fancy/ringbox/silver - icon_state = "silver ringbox" - icon_type = "silver ring" - spawn_type = /obj/item/clothing/gloves/ring/silver - +/* + * The 'fancy' path is for objects like donut boxes that show how many items are in the storage item on the sprite itself + * .. Sorry for the shitty path name, I couldnt think of a better one. + * + * WARNING: var/icon_type is used for both examine text and sprite name. Please look at the procs below and adjust your sprite names accordingly + * TODO: Cigarette boxes should be ported to this standard + * + * Contains: + * Donut Box + * Egg Box + * Candle Box + * Cigarette Box + * Cigar Case + * Heart Shaped Box w/ Chocolates + * Ring Box + */ + +/obj/item/storage/fancy + icon = 'icons/obj/food/containers.dmi' + icon_state = "donutbox6" + name = "donut box" + desc = "Mmm. Donuts." + resistance_flags = FLAMMABLE + var/icon_type = "donut" + var/spawn_type = null + var/fancy_open = FALSE + +/obj/item/storage/fancy/PopulateContents() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + for(var/i = 1 to STR.max_items) + new spawn_type(src) + +/obj/item/storage/fancy/update_icon() + if(fancy_open) + icon_state = "[icon_type]box[contents.len]" + else + icon_state = "[icon_type]box" + +/obj/item/storage/fancy/examine(mob/user) + . = ..() + if(fancy_open) + if(length(contents) == 1) + . += "There is one [icon_type] left." + else + . += "There are [contents.len <= 0 ? "no" : "[contents.len]"] [icon_type]s left." + +/obj/item/storage/fancy/attack_self(mob/user) + fancy_open = !fancy_open + update_icon() + . = ..() + +/obj/item/storage/fancy/Exited() + . = ..() + fancy_open = TRUE + update_icon() + +/obj/item/storage/fancy/Entered() + . = ..() + fancy_open = TRUE + update_icon() + +/* + * Donut Box + */ + +/obj/item/storage/fancy/donut_box + icon = 'icons/obj/food/containers.dmi' + icon_state = "donutbox6" + icon_type = "donut" + name = "donut box" + spawn_type = /obj/item/reagent_containers/food/snacks/donut + fancy_open = TRUE + +/obj/item/storage/fancy/donut_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/donut)) + +/* + * Egg Box + */ + +/obj/item/storage/fancy/egg_box + icon = 'icons/obj/food/containers.dmi' + item_state = "eggbox" + icon_state = "eggbox" + icon_type = "egg" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + name = "egg box" + desc = "A carton for containing eggs." + spawn_type = /obj/item/reagent_containers/food/snacks/egg + +/obj/item/storage/fancy/egg_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 12 + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/egg)) + +/* + * Candle Box + */ + +/obj/item/storage/fancy/candle_box + name = "candle pack" + desc = "A pack of red candles." + icon = 'icons/obj/candle.dmi' + icon_state = "candlebox5" + icon_type = "candle" + item_state = "candlebox5" + throwforce = 2 + slot_flags = ITEM_SLOT_BELT + spawn_type = /obj/item/candle + fancy_open = TRUE + +/obj/item/storage/fancy/candle_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + +/obj/item/storage/fancy/candle_box/attack_self(mob_user) + return + +//////////// +//CIG PACK// +//////////// +/obj/item/storage/fancy/cigarettes + name = "\improper Space Cigarettes packet" + desc = "The most popular brand of cigarettes, sponsors of the Space Olympics." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig" + item_state = "cigpacket" + w_class = WEIGHT_CLASS_TINY + throwforce = 0 + slot_flags = ITEM_SLOT_BELT + icon_type = "cigarette" + spawn_type = /obj/item/clothing/mask/cigarette/space_cigarette + +/obj/item/storage/fancy/cigarettes/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette, /obj/item/lighter)) + +/obj/item/storage/fancy/cigarettes/examine(mob/user) + . = ..() + . += "Alt-click to extract contents." + +/obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user) + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + var/obj/item/clothing/mask/cigarette/W = locate(/obj/item/clothing/mask/cigarette) in contents + if(W && contents.len > 0) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, user) + user.put_in_hands(W) + contents -= W + to_chat(user, "You take \a [W] out of the pack.") + else + to_chat(user, "There are no [icon_type]s left in the pack.") + return TRUE + +/obj/item/storage/fancy/cigarettes/update_icon() + if(fancy_open || !contents.len) + cut_overlays() + if(!contents.len) + icon_state = "[initial(icon_state)]_empty" + else + icon_state = initial(icon_state) + add_overlay("[icon_state]_open") + var/cig_position = 1 + for(var/C in contents) + var/mutable_appearance/inserted_overlay = mutable_appearance(icon) + + if(istype(C, /obj/item/lighter/greyscale)) + inserted_overlay.icon_state = "lighter_in" + else if(istype(C, /obj/item/lighter)) + inserted_overlay.icon_state = "zippo_in" + else + inserted_overlay.icon_state = "cigarette" + + inserted_overlay.icon_state = "[inserted_overlay.icon_state]_[cig_position]" + add_overlay(inserted_overlay) + cig_position++ + else + cut_overlays() + +/obj/item/storage/fancy/cigarettes/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob) + if(!ismob(M)) + return + var/obj/item/clothing/mask/cigarette/cig = locate(/obj/item/clothing/mask/cigarette) in contents + if(cig) + if(M == user && contents.len > 0 && !user.wear_mask) + var/obj/item/clothing/mask/cigarette/W = cig + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_TAKE, W, M) + M.equip_to_slot_if_possible(W, SLOT_WEAR_MASK) + contents -= W + to_chat(user, "You take \a [W] out of the pack.") + else + ..() + else + to_chat(user, "There are no [icon_type]s left in the pack.") + +/obj/item/storage/fancy/cigarettes/dromedaryco + name = "\improper DromedaryCo packet" + desc = "A packet of six imported DromedaryCo cancer sticks. A label on the packaging reads, \"Wouldn't a slow death make a change?\"" + icon_state = "dromedary" + spawn_type = /obj/item/clothing/mask/cigarette/dromedary + +/obj/item/storage/fancy/cigarettes/smokekins + name = "\improper smokekins packet" + desc = "Let the Radiance fill your lungs" + icon_state = "smokekins" + spawn_type = /obj/item/clothing/mask/cigarette/smokekins + +/obj/item/storage/fancy/cigarettes/cigpack_uplift + name = "\improper Uplift Smooth packet" + desc = "Your favorite brand, now menthol flavored." + icon_state = "uplift" + spawn_type = /obj/item/clothing/mask/cigarette/uplift + +/obj/item/storage/fancy/cigarettes/cigpack_robust + name = "\improper Robust packet" + desc = "Smoked by the robust." + icon_state = "robust" + spawn_type = /obj/item/clothing/mask/cigarette/robust + +/obj/item/storage/fancy/cigarettes/cigpack_robustgold + name = "\improper Robust Gold packet" + desc = "Smoked by the truly robust." + icon_state = "robustg" + spawn_type = /obj/item/clothing/mask/cigarette/robustgold + +/obj/item/storage/fancy/cigarettes/cigpack_carp + name = "\improper Carp Classic packet" + desc = "Since 2313." + icon_state = "carp" + spawn_type = /obj/item/clothing/mask/cigarette/carp + +/obj/item/storage/fancy/cigarettes/cigpack_syndicate + name = "cigarette packet" + desc = "An obscure brand of cigarettes." + icon_state = "syndie" + spawn_type = /obj/item/clothing/mask/cigarette/syndicate + +/obj/item/storage/fancy/cigarettes/cigpack_midori + name = "\improper Midori Tabako packet" + desc = "You can't understand the runes, but the packet smells funny." + icon_state = "midori" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/nicotine + +/obj/item/storage/fancy/cigarettes/cigpack_shadyjims + name = "\improper Shady Jim's Super Slims packet" + desc = "Is your weight slowing you down? Having trouble running away from gravitational singularities? Can't stop stuffing your mouth? Smoke Shady Jim's Super Slims and watch all that fat burn away. Guaranteed results!" + icon_state = "shadyjim" + spawn_type = /obj/item/clothing/mask/cigarette/shadyjims + +/obj/item/storage/fancy/cigarettes/cigpack_xeno + name = "\improper Xeno Filtered packet" + desc = "Loaded with 100% pure slime. And also nicotine." + icon_state = "slime" + spawn_type = /obj/item/clothing/mask/cigarette/xeno + +/obj/item/storage/fancy/cigarettes/cigpack_cannabis + name = "\improper Freak Brothers' Special packet" + desc = "A label on the packaging reads, \"Endorsed by Phineas, Freddy and Franklin.\"" + icon_state = "midori" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/cannabis + +/obj/item/storage/fancy/cigarettes/cigpack_mindbreaker + name = "\improper Leary's Delight packet" + desc = "Banned in over 36 galaxies." + icon_state = "shadyjim" + spawn_type = /obj/item/clothing/mask/cigarette/rollie/mindbreaker + +/obj/item/storage/fancy/rollingpapers + name = "rolling paper pack" + desc = "A pack of Kinaris brand rolling papers." + w_class = WEIGHT_CLASS_TINY + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cig_paper_pack" + icon_type = "rolling paper" + spawn_type = /obj/item/rollingpaper + +/obj/item/storage/fancy/rollingpapers/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 10 + STR.can_hold = typecacheof(list(/obj/item/rollingpaper)) + +/obj/item/storage/fancy/rollingpapers/update_icon() + cut_overlays() + if(!contents.len) + add_overlay("[icon_state]_empty") + +///////////// +//CIGAR BOX// +///////////// + +/obj/item/storage/fancy/cigarettes/cigars + name = "\improper premium cigar case" + desc = "A case of premium cigars. Very expensive." + icon = 'icons/obj/cigarettes.dmi' + icon_state = "cigarcase" + w_class = WEIGHT_CLASS_NORMAL + icon_type = "premium cigar" + spawn_type = /obj/item/clothing/mask/cigarette/cigar + +/obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 5 + STR.can_hold = typecacheof(list(/obj/item/clothing/mask/cigarette/cigar)) + +/obj/item/storage/fancy/cigarettes/cigars/update_icon() + cut_overlays() + if(fancy_open) + icon_state = "[initial(icon_state)]_open" + + var/cigar_position = 1 //generate sprites for cigars in the box + for(var/obj/item/clothing/mask/cigarette/cigar/smokes in contents) + var/mutable_appearance/cigar_overlay = mutable_appearance(icon, "[smokes.icon_off]_[cigar_position]") + add_overlay(cigar_overlay) + cigar_position++ + + else + icon_state = "[initial(icon_state)]" + +/obj/item/storage/fancy/cigarettes/cigars/cohiba + name = "\improper Cohiba Robusto cigar case" + desc = "A case of imported Cohiba cigars, renowned for their strong flavor." + icon_state = "cohibacase" + spawn_type = /obj/item/clothing/mask/cigarette/cigar/cohiba + +/obj/item/storage/fancy/cigarettes/cigars/havana + name = "\improper premium Havanian cigar case" + desc = "A case of classy Havanian cigars." + icon_state = "cohibacase" + spawn_type = /obj/item/clothing/mask/cigarette/cigar/havana + +/* + * Heart Shaped Box w/ Chocolates + */ + +/obj/item/storage/fancy/heart_box + name = "heart-shaped box" + desc = "A heart-shaped box for holding tiny chocolates." + icon = 'icons/obj/food/containers.dmi' + item_state = "chocolatebox" + icon_state = "chocolatebox" + icon_type = "chocolate" + lefthand_file = 'icons/mob/inhands/misc/food_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/food_righthand.dmi' + spawn_type = /obj/item/reagent_containers/food/snacks/tinychocolate + +/obj/item/storage/fancy/heart_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 8 + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/tinychocolate)) + +/* + * Ring Box + */ + +/obj/item/storage/fancy/ringbox + name = "ring box" + desc = "A tiny box covered in soft red felt made for holding rings." + icon = 'icons/obj/ring.dmi' + icon_state = "gold ringbox" + icon_type = "gold ring" + w_class = WEIGHT_CLASS_TINY + spawn_type = /obj/item/clothing/gloves/ring + +/obj/item/storage/fancy/ringbox/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 1 + STR.can_hold = typecacheof(list(/obj/item/clothing/gloves/ring)) + +/obj/item/storage/fancy/ringbox/diamond + icon_state = "diamond ringbox" + icon_type = "diamond ring" + spawn_type = /obj/item/clothing/gloves/ring/diamond + +/obj/item/storage/fancy/ringbox/silver + icon_state = "silver ringbox" + icon_type = "silver ring" + spawn_type = /obj/item/clothing/gloves/ring/silver + diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index bd0a34b5..588c6ea0 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -1,198 +1,199 @@ -/obj/item/storage/lockbox - name = "lockbox" - desc = "A locked box." - icon_state = "lockbox+l" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_BULKY - req_access = list(ACCESS_ARMORY) - var/broken = FALSE - var/open = FALSE - var/icon_locked = "lockbox+l" - var/icon_closed = "lockbox" - var/icon_broken = "lockbox+b" - -/obj/item/storage/lockbox/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_NORMAL - STR.max_combined_w_class = 14 - STR.max_items = 4 - STR.locked = TRUE - -/obj/item/storage/lockbox/attackby(obj/item/W, mob/user, params) - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(W.GetID()) - if(broken) - to_chat(user, "It appears to be broken.") - return - if(allowed(user)) - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, !locked) - locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - icon_state = icon_locked - to_chat(user, "You lock the [src.name]!") - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_ALL) - return - else - icon_state = icon_closed - to_chat(user, "You unlock the [src.name]!") - return - else - to_chat(user, "Access Denied.") - return - if(!locked) - return ..() - else - to_chat(user, "It's locked!") - -/obj/item/storage/lockbox/emag_act(mob/user) - if(!broken) - broken = TRUE - SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) - desc += "It appears to be broken." - icon_state = src.icon_broken - if(user) - visible_message("\The [src] has been broken by [user] with an electromagnetic card!") - return - -/obj/item/storage/lockbox/Entered() - . = ..() - open = TRUE - update_icon() - -/obj/item/storage/lockbox/Exited() - . = ..() - open = TRUE - update_icon() - -/obj/item/storage/lockbox/loyalty - name = "lockbox of mindshield implants" - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/loyalty/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/implantcase/mindshield(src) - new /obj/item/implanter/mindshield(src) - -/obj/item/storage/lockbox/clusterbang - name = "lockbox of clusterbangs" - desc = "You have a bad feeling about opening this." - req_access = list(ACCESS_SECURITY) - -/obj/item/storage/lockbox/clusterbang/PopulateContents() - new /obj/item/grenade/clusterbuster(src) - -/obj/item/storage/lockbox/medal - name = "medal box" - desc = "A locked box used to store medals of honor." - icon_state = "medalbox+l" - item_state = "syringe_kit" - lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' - righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - w_class = WEIGHT_CLASS_NORMAL - req_access = list(ACCESS_CAPTAIN) - icon_locked = "medalbox+l" - icon_closed = "medalbox" - icon_broken = "medalbox+b" - -/obj/item/storage/lockbox/medal/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_SMALL - STR.max_items = 10 - STR.max_combined_w_class = 20 - STR.can_hold = typecacheof(list(/obj/item/clothing/accessory/medal)) - -/obj/item/storage/lockbox/medal/examine(mob/user) - . = ..() - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(!locked) - . += "Alt-click to [open ? "close":"open"] it." - -/obj/item/storage/lockbox/medal/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE)) - if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) - open = (open ? FALSE : TRUE) - update_icon() - ..() - -/obj/item/storage/lockbox/medal/PopulateContents() - new /obj/item/clothing/accessory/medal/gold/captain(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/valor(src) - new /obj/item/clothing/accessory/medal/silver/security(src) - new /obj/item/clothing/accessory/medal/bronze_heart(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/conduct(src) - -/obj/item/storage/lockbox/medal/update_icon() - cut_overlays() - var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) - if(locked) - icon_state = "medalbox+l" - open = FALSE - else - icon_state = "medalbox" - if(open) - icon_state += "open" - if(broken) - icon_state += "+b" - if(contents && open) - for (var/i in 1 to contents.len) - var/obj/item/clothing/accessory/medal/M = contents[i] - var/mutable_appearance/medalicon = mutable_appearance(initial(icon), M.medaltype) - if(i > 1 && i <= 5) - medalicon.pixel_x += ((i-1)*3) - else if(i > 5) - medalicon.pixel_y -= 7 - medalicon.pixel_x -= 2 - medalicon.pixel_x += ((i-6)*3) - add_overlay(medalicon) - -/obj/item/storage/lockbox/medal/sec - name = "security medal box" - desc = "A locked box used to store medals to be given to members of the security department." - req_access = list(ACCESS_HOS) - -/obj/item/storage/lockbox/medal/sec/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/silver/security(src) - -/obj/item/storage/lockbox/medal/cargo - name = "cargo award box" - desc = "A locked box used to store awards to be given to members of the cargo department." - req_access = list(ACCESS_QM) - -/obj/item/storage/lockbox/medal/cargo/PopulateContents() - new /obj/item/clothing/accessory/medal/ribbon/cargo(src) - -/obj/item/storage/lockbox/medal/sci - name = "science medal box" - desc = "A locked box used to store medals to be given to members of the science department." - req_access = list(ACCESS_RD) - -/obj/item/storage/lockbox/medal/sci/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) - -/obj/item/storage/lockbox/medal/engineering - name = "engineering medal box" - desc = "A locked box used to store medals to be given to the members of the engineering department." - req_access = list(ACCESS_CE) - -/obj/item/storage/lockbox/medal/engineering/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/engineer(src) - -/obj/item/storage/lockbox/medal/medical - name = "medical medal box" - desc = "A locked box used to store medals to be given to the members of the medical department." - req_access = list(ACCESS_CMO) - -/obj/item/storage/lockbox/medal/medical/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/ribbon/medical_doctor(src) +/obj/item/storage/lockbox + name = "lockbox" + desc = "A locked box." + icon_state = "lockbox+l" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_BULKY + req_access = list(ACCESS_ARMORY) + var/broken = FALSE + var/open = FALSE + var/icon_locked = "lockbox+l" + var/icon_closed = "lockbox" + var/icon_broken = "lockbox+b" + +/obj/item/storage/lockbox/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_NORMAL + STR.max_combined_w_class = 14 + STR.max_items = 4 + STR.locked = TRUE + +/obj/item/storage/lockbox/attackby(obj/item/W, mob/user, params) + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(W.GetID()) + if(broken) + to_chat(user, "It appears to be broken.") + return + if(allowed(user)) + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, !locked) + locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = icon_locked + to_chat(user, "You lock the [src.name]!") + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_HIDE_ALL) + return + else + icon_state = icon_closed + to_chat(user, "You unlock the [src.name]!") + return + else + to_chat(user, "Access Denied.") + return + if(!locked) + return ..() + else + to_chat(user, "It's locked!") + +/obj/item/storage/lockbox/emag_act(mob/user) + if(!broken) + broken = TRUE + SEND_SIGNAL(src, COMSIG_TRY_STORAGE_SET_LOCKSTATE, FALSE) + desc += "It appears to be broken." + icon_state = src.icon_broken + if(user) + visible_message("\The [src] has been broken by [user] with an electromagnetic card!") + return + +/obj/item/storage/lockbox/Entered() + . = ..() + open = TRUE + update_icon() + +/obj/item/storage/lockbox/Exited() + . = ..() + open = TRUE + update_icon() + +/obj/item/storage/lockbox/loyalty + name = "lockbox of mindshield implants" + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/loyalty/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/implantcase/mindshield(src) + new /obj/item/implanter/mindshield(src) + +/obj/item/storage/lockbox/clusterbang + name = "lockbox of clusterbangs" + desc = "You have a bad feeling about opening this." + req_access = list(ACCESS_SECURITY) + +/obj/item/storage/lockbox/clusterbang/PopulateContents() + new /obj/item/grenade/clusterbuster(src) + +/obj/item/storage/lockbox/medal + name = "medal box" + desc = "A locked box used to store medals of honor." + icon_state = "medalbox+l" + item_state = "syringe_kit" + lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' + w_class = WEIGHT_CLASS_NORMAL + req_access = list(ACCESS_CAPTAIN) + icon_locked = "medalbox+l" + icon_closed = "medalbox" + icon_broken = "medalbox+b" + +/obj/item/storage/lockbox/medal/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_SMALL + STR.max_items = 10 + STR.max_combined_w_class = 20 + STR.can_hold = typecacheof(list(/obj/item/clothing/accessory/medal)) + +/obj/item/storage/lockbox/medal/examine(mob/user) + . = ..() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(!locked) + . += "Alt-click to [open ? "close":"open"] it." + +/obj/item/storage/lockbox/medal/AltClick(mob/user) + . = ..() + if(user.canUseTopic(src, BE_CLOSE)) + if(!SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED)) + open = (open ? FALSE : TRUE) + update_icon() + return TRUE + +/obj/item/storage/lockbox/medal/PopulateContents() + new /obj/item/clothing/accessory/medal/gold/captain(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/valor(src) + new /obj/item/clothing/accessory/medal/silver/security(src) + new /obj/item/clothing/accessory/medal/bronze_heart(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/conduct(src) + +/obj/item/storage/lockbox/medal/update_icon() + cut_overlays() + var/locked = SEND_SIGNAL(src, COMSIG_IS_STORAGE_LOCKED) + if(locked) + icon_state = "medalbox+l" + open = FALSE + else + icon_state = "medalbox" + if(open) + icon_state += "open" + if(broken) + icon_state += "+b" + if(contents && open) + for (var/i in 1 to contents.len) + var/obj/item/clothing/accessory/medal/M = contents[i] + var/mutable_appearance/medalicon = mutable_appearance(initial(icon), M.medaltype) + if(i > 1 && i <= 5) + medalicon.pixel_x += ((i-1)*3) + else if(i > 5) + medalicon.pixel_y -= 7 + medalicon.pixel_x -= 2 + medalicon.pixel_x += ((i-6)*3) + add_overlay(medalicon) + +/obj/item/storage/lockbox/medal/sec + name = "security medal box" + desc = "A locked box used to store medals to be given to members of the security department." + req_access = list(ACCESS_HOS) + +/obj/item/storage/lockbox/medal/sec/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/silver/security(src) + +/obj/item/storage/lockbox/medal/cargo + name = "cargo award box" + desc = "A locked box used to store awards to be given to members of the cargo department." + req_access = list(ACCESS_QM) + +/obj/item/storage/lockbox/medal/cargo/PopulateContents() + new /obj/item/clothing/accessory/medal/ribbon/cargo(src) + +/obj/item/storage/lockbox/medal/sci + name = "science medal box" + desc = "A locked box used to store medals to be given to members of the science department." + req_access = list(ACCESS_RD) + +/obj/item/storage/lockbox/medal/sci/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) + +/obj/item/storage/lockbox/medal/engineering + name = "engineering medal box" + desc = "A locked box used to store medals to be given to the members of the engineering department." + req_access = list(ACCESS_CE) + +/obj/item/storage/lockbox/medal/engineering/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/engineer(src) + +/obj/item/storage/lockbox/medal/medical + name = "medical medal box" + desc = "A locked box used to store medals to be given to the members of the medical department." + req_access = list(ACCESS_CMO) + +/obj/item/storage/lockbox/medal/medical/PopulateContents() + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/ribbon/medical_doctor(src) diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm index 67c3ab8e..765efbf6 100644 --- a/code/game/objects/items/twohanded.dm +++ b/code/game/objects/items/twohanded.dm @@ -546,6 +546,7 @@ qdel(src) /obj/item/twohanded/spear/AltClick(mob/user) + . = ..() if(user.canUseTopic(src, BE_CLOSE)) ..() if(!explosive) @@ -554,6 +555,7 @@ var/input = stripped_input(user,"What do you want your war cry to be? You will shout it when you hit someone in melee.", ,"", 50) if(input) src.war_cry = input + return TRUE /obj/item/twohanded/spear/CheckParts(list/parts_list) var/obj/item/twohanded/spear/S = locate() in parts_list diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 7b8fdbd5..784c6bf8 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -1,252 +1,253 @@ - -/obj - var/crit_fail = FALSE - animate_movement = 2 - speech_span = SPAN_ROBOT - var/obj_flags = CAN_BE_HIT - var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT. - - var/damtype = BRUTE - var/force = 0 - - var/datum/armor/armor - var/obj_integrity //defaults to max_integrity - var/max_integrity = 500 - var/integrity_failure = 0 //0 if we have no special broken behavior - - var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF - - var/acid_level = 0 //how much acid is on that obj - - var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset. - var/current_skin //the item reskin - var/list/unique_reskin //List of options to reskin. - var/always_reskinnable = FALSE - - // Access levels, used in modules\jobs\access.dm - var/list/req_access - var/req_access_txt = "0" - var/list/req_one_access - var/req_one_access_txt = "0" - - var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object - -/obj/vv_edit_var(vname, vval) - switch(vname) - if("anchored") - setAnchored(vval) - return TRUE - if("obj_flags") - if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) - return FALSE - if("control_object") - var/obj/O = vval - if(istype(O) && (O.obj_flags & DANGEROUS_POSSESSION)) - return FALSE - return ..() - -/obj/Initialize() - . = ..() - if (islist(armor)) - armor = getArmor(arglist(armor)) - else if (!armor) - armor = getArmor() - else if (!istype(armor, /datum/armor)) - stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()") - - if(obj_integrity == null) - obj_integrity = max_integrity - if (set_obj_flags) - var/flagslist = splittext(set_obj_flags,";") - var/list/string_to_objflag = GLOB.bitfields["obj_flags"] - for (var/flag in flagslist) - if (findtext(flag,"!",1,2)) - flag = copytext(flag,1-(length(flag))) // Get all but the initial ! - obj_flags &= ~string_to_objflag[flag] - else - obj_flags |= string_to_objflag[flag] - if((obj_flags & ON_BLUEPRINTS) && isturf(loc)) - var/turf/T = loc - T.add_blueprints_preround(src) - - -/obj/Destroy(force=FALSE) - if(!ismachinery(src)) - STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists - SStgui.close_uis(src) - . = ..() - -/obj/proc/setAnchored(anchorvalue) - SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) - anchored = anchorvalue - -/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE) - . = ..() - if(obj_flags & FROZEN) - visible_message("[src] shatters into a million pieces!") - qdel(src) - - -/obj/assume_air(datum/gas_mixture/giver) - if(loc) - return loc.assume_air(giver) - else - return null - -/obj/remove_air(amount) - if(loc) - return loc.remove_air(amount) - else - return null - -/obj/return_air() - if(loc) - return loc.return_air() - else - return null - -/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) - //Return: (NONSTANDARD) - // null if object handles breathing logic for lifeform - // datum/air_group to tell lifeform to process using that breath return - //DEFAULT: Take air from turf to give to have mob process - - if(breath_request>0) - var/datum/gas_mixture/environment = return_air() - var/breath_percentage = BREATH_VOLUME / environment.return_volume() - return remove_air(environment.total_moles() * breath_percentage) - else - return null - -/obj/proc/updateUsrDialog() - if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) - var/is_in_use = FALSE - var/list/nearby = viewers(1, src) - for(var/mob/M in nearby) - if ((M.client && M.machine == src)) - is_in_use = TRUE - ui_interact(M) - if(isAI(usr) || iscyborg(usr) || IsAdminGhost(usr)) - if (!(usr in nearby)) - if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. - is_in_use = TRUE - ui_interact(usr) - - // check for TK users - - if(ishuman(usr)) - var/mob/living/carbon/human/H = usr - if(!(usr in nearby)) - if(usr.client && usr.machine==src) - if(H.dna.check_mutation(TK)) - is_in_use = TRUE - ui_interact(usr) - if (is_in_use) - obj_flags |= IN_USE - else - obj_flags &= ~IN_USE - -/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) - // Check that people are actually using the machine. If not, don't update anymore. - if(obj_flags & IN_USE) - var/is_in_use = FALSE - if(update_viewers) - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - is_in_use = TRUE - src.interact(M) - var/ai_in_use = FALSE - if(update_ais) - ai_in_use = AutoUpdateAI(src) - - if(update_viewers && update_ais) //State change is sure only if we check both - if(!ai_in_use && !is_in_use) - obj_flags &= ~IN_USE - - -/obj/attack_ghost(mob/user) - . = ..() - if(.) - return - ui_interact(user) - -/obj/proc/container_resist(mob/living/user) - return - -/obj/proc/update_icon() - return - -/mob/proc/unset_machine() - if(machine) - machine.on_unset_machine(src) - machine = null - -//called when the user unsets the machine. -/atom/movable/proc/on_unset_machine(mob/user) - return - -/mob/proc/set_machine(obj/O) - if(src.machine) - unset_machine() - src.machine = O - if(istype(O)) - O.obj_flags |= IN_USE - -/obj/item/proc/updateSelfDialog() - var/mob/M = src.loc - if(istype(M) && M.client && M.machine == src) - src.attack_self(M) - -/obj/proc/hide(h) - return - -/obj/singularity_pull(S, current_size) - ..() - if(!anchored || current_size >= STAGE_FIVE) - step_towards(src,S) - -/obj/get_dumping_location(datum/component/storage/source,mob/user) - return get_turf(src) - -/obj/proc/CanAStarPass() - . = !density - -/obj/proc/check_uplink_validity() - return 1 - -/obj/proc/intercept_user_move(dir, mob, newLoc, oldLoc) - return - -/obj/vv_get_dropdown() - . = ..() - .["Delete all of type"] = "?_src_=vars;[HrefToken()];delall=[REF(src)]" - .["Osay"] = "?_src_=vars;[HrefToken()];osay[REF(src)]" - .["Modify armor values"] = "?_src_=vars;[HrefToken()];modarmor=[REF(src)]" - -/obj/examine(mob/user) - . = ..() - if(obj_flags & UNIQUE_RENAME) - . += "Use a pen on it to rename it or change its description." - if(unique_reskin && (!current_skin || always_reskinnable)) - . += "Alt-click it to reskin it." - -/obj/AltClick(mob/user) - . = ..() - if(unique_reskin && (!current_skin || always_reskinnable) && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) - reskin_obj(user) - -/obj/proc/reskin_obj(mob/M) - if(!LAZYLEN(unique_reskin)) - return - var/dat = "Reskin options for [name]:\n" - for(var/V in unique_reskin) - var/output = icon2html(src, M, unique_reskin[V]) - dat += "[V]: [output]\n" - to_chat(M, dat) - - var/choice = input(M, always_reskinnable ? "Choose the a reskin for [src]" : "Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin - if(QDELETED(src) || !choice || (current_skin && !always_reskinnable) || M.incapacitated() || !in_range(M,src) || !unique_reskin[choice] || unique_reskin[choice] == current_skin) - return - current_skin = choice - icon_state = unique_reskin[choice] - to_chat(M, "[src] is now skinned as '[choice]'.") + +/obj + var/crit_fail = FALSE + animate_movement = 2 + speech_span = SPAN_ROBOT + var/obj_flags = CAN_BE_HIT + var/set_obj_flags // ONLY FOR MAPPING: Sets flags from a string list, handled in Initialize. Usage: set_obj_flags = "EMAGGED;!CAN_BE_HIT" to set EMAGGED and clear CAN_BE_HIT. + + var/damtype = BRUTE + var/force = 0 + + var/datum/armor/armor + var/obj_integrity //defaults to max_integrity + var/max_integrity = 500 + var/integrity_failure = 0 //0 if we have no special broken behavior + + var/resistance_flags = NONE // INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ON_FIRE | UNACIDABLE | ACID_PROOF + + var/acid_level = 0 //how much acid is on that obj + + var/persistence_replacement //have something WAY too amazing to live to the next round? Set a new path here. Overuse of this var will make me upset. + var/current_skin //the item reskin + var/list/unique_reskin //List of options to reskin. + var/always_reskinnable = FALSE + + // Access levels, used in modules\jobs\access.dm + var/list/req_access + var/req_access_txt = "0" + var/list/req_one_access + var/req_one_access_txt = "0" + + var/renamedByPlayer = FALSE //set when a player uses a pen on a renamable object + +/obj/vv_edit_var(vname, vval) + switch(vname) + if("anchored") + setAnchored(vval) + return TRUE + if("obj_flags") + if ((obj_flags & DANGEROUS_POSSESSION) && !(vval & DANGEROUS_POSSESSION)) + return FALSE + if("control_object") + var/obj/O = vval + if(istype(O) && (O.obj_flags & DANGEROUS_POSSESSION)) + return FALSE + return ..() + +/obj/Initialize() + . = ..() + if (islist(armor)) + armor = getArmor(arglist(armor)) + else if (!armor) + armor = getArmor() + else if (!istype(armor, /datum/armor)) + stack_trace("Invalid type [armor.type] found in .armor during /obj Initialize()") + + if(obj_integrity == null) + obj_integrity = max_integrity + if (set_obj_flags) + var/flagslist = splittext(set_obj_flags,";") + var/list/string_to_objflag = GLOB.bitfields["obj_flags"] + for (var/flag in flagslist) + if (findtext(flag,"!",1,2)) + flag = copytext(flag,1-(length(flag))) // Get all but the initial ! + obj_flags &= ~string_to_objflag[flag] + else + obj_flags |= string_to_objflag[flag] + if((obj_flags & ON_BLUEPRINTS) && isturf(loc)) + var/turf/T = loc + T.add_blueprints_preround(src) + + +/obj/Destroy(force=FALSE) + if(!ismachinery(src)) + STOP_PROCESSING(SSobj, src) // TODO: Have a processing bitflag to reduce on unnecessary loops through the processing lists + SStgui.close_uis(src) + . = ..() + +/obj/proc/setAnchored(anchorvalue) + SEND_SIGNAL(src, COMSIG_OBJ_SETANCHORED, anchorvalue) + anchored = anchorvalue + +/obj/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE) + . = ..() + if(obj_flags & FROZEN) + visible_message("[src] shatters into a million pieces!") + qdel(src) + + +/obj/assume_air(datum/gas_mixture/giver) + if(loc) + return loc.assume_air(giver) + else + return null + +/obj/remove_air(amount) + if(loc) + return loc.remove_air(amount) + else + return null + +/obj/return_air() + if(loc) + return loc.return_air() + else + return null + +/obj/proc/handle_internal_lifeform(mob/lifeform_inside_me, breath_request) + //Return: (NONSTANDARD) + // null if object handles breathing logic for lifeform + // datum/air_group to tell lifeform to process using that breath return + //DEFAULT: Take air from turf to give to have mob process + + if(breath_request>0) + var/datum/gas_mixture/environment = return_air() + var/breath_percentage = BREATH_VOLUME / environment.return_volume() + return remove_air(environment.total_moles() * breath_percentage) + else + return null + +/obj/proc/updateUsrDialog() + if((obj_flags & IN_USE) && !(obj_flags & USES_TGUI)) + var/is_in_use = FALSE + var/list/nearby = viewers(1, src) + for(var/mob/M in nearby) + if ((M.client && M.machine == src)) + is_in_use = TRUE + ui_interact(M) + if(isAI(usr) || iscyborg(usr) || IsAdminGhost(usr)) + if (!(usr in nearby)) + if (usr.client && usr.machine==src) // && M.machine == src is omitted because if we triggered this by using the dialog, it doesn't matter if our machine changed in between triggering it and this - the dialog is probably still supposed to refresh. + is_in_use = TRUE + ui_interact(usr) + + // check for TK users + + if(ishuman(usr)) + var/mob/living/carbon/human/H = usr + if(!(usr in nearby)) + if(usr.client && usr.machine==src) + if(H.dna.check_mutation(TK)) + is_in_use = TRUE + ui_interact(usr) + if (is_in_use) + obj_flags |= IN_USE + else + obj_flags &= ~IN_USE + +/obj/proc/updateDialog(update_viewers = TRUE,update_ais = TRUE) + // Check that people are actually using the machine. If not, don't update anymore. + if(obj_flags & IN_USE) + var/is_in_use = FALSE + if(update_viewers) + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + is_in_use = TRUE + src.interact(M) + var/ai_in_use = FALSE + if(update_ais) + ai_in_use = AutoUpdateAI(src) + + if(update_viewers && update_ais) //State change is sure only if we check both + if(!ai_in_use && !is_in_use) + obj_flags &= ~IN_USE + + +/obj/attack_ghost(mob/user) + . = ..() + if(.) + return + ui_interact(user) + +/obj/proc/container_resist(mob/living/user) + return + +/obj/proc/update_icon() + return + +/mob/proc/unset_machine() + if(machine) + machine.on_unset_machine(src) + machine = null + +//called when the user unsets the machine. +/atom/movable/proc/on_unset_machine(mob/user) + return + +/mob/proc/set_machine(obj/O) + if(src.machine) + unset_machine() + src.machine = O + if(istype(O)) + O.obj_flags |= IN_USE + +/obj/item/proc/updateSelfDialog() + var/mob/M = src.loc + if(istype(M) && M.client && M.machine == src) + src.attack_self(M) + +/obj/proc/hide(h) + return + +/obj/singularity_pull(S, current_size) + ..() + if(!anchored || current_size >= STAGE_FIVE) + step_towards(src,S) + +/obj/get_dumping_location(datum/component/storage/source,mob/user) + return get_turf(src) + +/obj/proc/CanAStarPass() + . = !density + +/obj/proc/check_uplink_validity() + return 1 + +/obj/proc/intercept_user_move(dir, mob, newLoc, oldLoc) + return + +/obj/vv_get_dropdown() + . = ..() + .["Delete all of type"] = "?_src_=vars;[HrefToken()];delall=[REF(src)]" + .["Osay"] = "?_src_=vars;[HrefToken()];osay[REF(src)]" + .["Modify armor values"] = "?_src_=vars;[HrefToken()];modarmor=[REF(src)]" + +/obj/examine(mob/user) + . = ..() + if(obj_flags & UNIQUE_RENAME) + . += "Use a pen on it to rename it or change its description." + if(unique_reskin && (!current_skin || always_reskinnable)) + . += "Alt-click it to reskin it." + +/obj/AltClick(mob/user) + . = ..() + if(unique_reskin && (!current_skin || always_reskinnable) && user.canUseTopic(src, BE_CLOSE, NO_DEXTERY)) + reskin_obj(user) + return TRUE + +/obj/proc/reskin_obj(mob/M) + if(!LAZYLEN(unique_reskin)) + return + var/dat = "Reskin options for [name]:\n" + for(var/V in unique_reskin) + var/output = icon2html(src, M, unique_reskin[V]) + dat += "[V]: [output]\n" + to_chat(M, dat) + + var/choice = input(M, always_reskinnable ? "Choose the a reskin for [src]" : "Warning, you can only reskin [src] once!","Reskin Object") as null|anything in unique_reskin + if(QDELETED(src) || !choice || (current_skin && !always_reskinnable) || M.incapacitated() || !in_range(M,src) || !unique_reskin[choice] || unique_reskin[choice] == current_skin) + return + current_skin = choice + icon_state = unique_reskin[choice] + to_chat(M, "[src] is now skinned as '[choice]'.") diff --git a/code/game/objects/structures/beds_chairs/chair.dm b/code/game/objects/structures/beds_chairs/chair.dm index 74036dab..a145bffe 100644 --- a/code/game/objects/structures/beds_chairs/chair.dm +++ b/code/game/objects/structures/beds_chairs/chair.dm @@ -449,6 +449,9 @@ item_chair = null var/turns = 0 +/obj/structure/chair/brass/ComponentInitialize() + return //it spins with the power of ratvar, not components. + /obj/structure/chair/brass/Destroy() STOP_PROCESSING(SSfastprocess, src) . = ..() @@ -464,6 +467,7 @@ return /obj/structure/chair/brass/AltClick(mob/living/user) + . = ..() turns = 0 if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return @@ -475,6 +479,7 @@ user.visible_message("[user] stops [src]'s uncontrollable spinning.", \ "You grab [src] and stop its wild spinning.") STOP_PROCESSING(SSfastprocess, src) + return TRUE /obj/structure/chair/bronze name = "brass chair" diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index dff74ef8..e14646e2 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -1,616 +1,617 @@ -/obj/structure/closet - name = "closet" - desc = "It's a basic storage unit." - icon = 'icons/obj/closet.dmi' - icon_state = "generic" - density = TRUE - layer = BELOW_OBJ_LAYER - var/icon_door = null - var/icon_door_override = FALSE //override to have open overlay use icon different to its base's - var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights - var/opened = FALSE - var/welded = FALSE - var/locked = FALSE - var/large = TRUE - var/wall_mounted = 0 //never solid (You can always pass over it) - max_integrity = 200 - integrity_failure = 50 - armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) - var/breakout_time = 1200 - var/message_cooldown - var/can_weld_shut = TRUE - var/horizontal = FALSE - var/allow_objects = FALSE - var/allow_dense = FALSE - var/dense_when_open = FALSE //if it's dense when open or not - var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container - var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet. - var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. - var/cutting_tool = /obj/item/weldingtool - var/open_sound = 'sound/machines/click.ogg' - var/close_sound = 'sound/machines/click.ogg' - var/material_drop = /obj/item/stack/sheet/metal - var/material_drop_amount = 2 - var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable. - var/anchorable = TRUE - var/icon_welded = "welded" - var/obj/item/electronics/airlock/lockerelectronics //Installed electronics - var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further - var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus. - var/obj/structure/closet/eigen_target //Where you go to. - - -/obj/structure/closet/Initialize(mapload) - . = ..() - update_icon() - PopulateContents() - if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents - take_contents() - if(secure) - lockerelectronics = new(src) - lockerelectronics.accesses = req_access - -//USE THIS TO FILL IT, NOT INITIALIZE OR NEW -/obj/structure/closet/proc/PopulateContents() - return - -/obj/structure/closet/Destroy() - dump_contents(override = FALSE) - return ..() - -/obj/structure/closet/update_icon() - cut_overlays() - if(opened & icon_door_override) - add_overlay("[icon_door]_open") - layer = OBJ_LAYER - return - else if(opened) - add_overlay("[icon_state]_open") - return - if(icon_door) - add_overlay("[icon_door]_door") - else - layer = BELOW_OBJ_LAYER - add_overlay("[icon_state]_door") - if(welded) - add_overlay("welded") - if(!secure) - return - if(broken) - add_overlay("off") - add_overlay("sparking") - else if(locked) - add_overlay("locked") - else - add_overlay("unlocked") - -/obj/structure/closet/examine(mob/user) - . = ..() - if(welded) - . += "It's welded shut." - if(anchored) - . += "It is bolted to the ground." - if(opened) - . += "The parts are welded together." - else if(secure && !opened) - else if(broken) - . += "The lock is screwed in." - else if(secure) - . += "Alt-click to [locked ? "unlock" : "lock"]." - if(isliving(user)) - var/mob/living/L = user - if(HAS_TRAIT(L, TRAIT_SKITTISH)) - . += "Ctrl-Shift-click [src] to jump inside." - -/obj/structure/closet/CanPass(atom/movable/mover, turf/target) - if(wall_mounted) - return TRUE - return !density - -/obj/structure/closet/proc/can_open(mob/living/user) - if(welded || locked) - return FALSE - var/turf/T = get_turf(src) - for(var/mob/living/L in T) - if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) - if(user) - to_chat(user, "There's something large on top of [src], preventing it from opening." ) - return FALSE - return TRUE - -/obj/structure/closet/proc/can_close(mob/living/user) - var/turf/T = get_turf(src) - for(var/obj/structure/closet/closet in T) - if(closet != src && !closet.wall_mounted) - return FALSE - for(var/mob/living/L in T) - if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) - if(user) - to_chat(user, "There's something too large in [src], preventing it from closing.") - return FALSE - return TRUE - -/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access - if(!secure) - return FALSE - if(broken) - to_chat(user, "[src] is broken!") - return FALSE - if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics - to_chat(user, "[src] is missing locker electronics!") - return FALSE - if(!check_access) - return TRUE - if(allowed(user)) - return TRUE - to_chat(user, "Access denied.") - -/obj/structure/closet/proc/togglelock(mob/living/user) - add_fingerprint(user) - if(eigen_target) - return - if(opened) - return - if(!can_lock(user)) - return - locked = !locked - user.visible_message("[user] [locked ? null : "un"]locks [src].", - "You [locked ? null : "un"]lock [src].") - update_icon() - -/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example - var/atom/L = drop_location() - for(var/atom/movable/AM in src) - if(AM == lockerelectronics && override) - continue - AM.forceMove(L) - if(throwing) // you keep some momentum when getting out of a thrown closet - step(AM, dir) - if(throwing) - throwing.finalize(FALSE) - -/obj/structure/closet/proc/take_contents() - var/atom/L = drop_location() - for(var/atom/movable/AM in L) - if(AM != src && insert(AM) == -1) // limit reached - break - -/obj/structure/closet/proc/open(mob/living/user) - if(opened || !can_open(user)) - return - playsound(loc, open_sound, 15, 1, -3) - opened = TRUE - if(!dense_when_open) - density = FALSE - climb_time *= 0.5 //it's faster to climb onto an open thing - dump_contents() - update_icon() - return 1 - -/obj/structure/closet/proc/insert(atom/movable/AM) - if(contents.len >= storage_capacity) - return -1 - if(insertion_allowed(AM)) - if(eigen_teleport) // For teleporting people with linked lockers. - do_teleport(AM, get_turf(eigen_target), 0) - if(eigen_target.opened == FALSE) - eigen_target.bust_open() - else - AM.forceMove(src) - return TRUE - else - return FALSE - - -/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) - if(ismob(AM)) - if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... - return FALSE - var/mob/living/L = AM - if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) - return FALSE - if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. - if(horizontal && L.density) - return FALSE - if(L.mob_size > max_mob_size) - return FALSE - var/mobs_stored = 0 - for(var/mob/living/M in contents) - if(++mobs_stored >= mob_storage_capacity) - return FALSE - L.stop_pulling() - - else if(istype(AM, /obj/structure/closet)) - return FALSE - - else if(istype(AM, /obj/effect)) - return FALSE - - else if(isobj(AM)) - if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) - return FALSE - if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) - return TRUE - else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) - return FALSE - else - return FALSE - - return TRUE - -/obj/structure/closet/proc/close(mob/living/user) - if(!opened || !can_close(user)) - return FALSE - take_contents() - playsound(loc, close_sound, 15, 1, -3) - climb_time = initial(climb_time) - opened = FALSE - density = TRUE - update_icon() - return TRUE - -/obj/structure/closet/proc/toggle(mob/living/user) - if(opened) - return close(user) - else - return open(user) - -/obj/structure/closet/proc/bust_open() - welded = FALSE //applies to all lockers - locked = FALSE //applies to critter crates and secure lockers only - broken = TRUE //applies to secure lockers only - open() - -/obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E) - add_fingerprint(user) - if(lock_in_use) - to_chat(user, "Wait for work on [src] to be done first!") - return - if(secure) - to_chat(user, "This locker already has a lock!") - return - if(broken) - to_chat(user, "Unscrew the broken lock first!") - return - if(!istype(E)) - return - user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...") - lock_in_use = TRUE - playsound(loc, 'sound/items/screwdriver.ogg', 50, 1) - if(!do_after(user, 60, target = src)) - lock_in_use = FALSE - return - lock_in_use = FALSE - to_chat(user, "You finish the lock on [src]!") - E.forceMove(src) - lockerelectronics = E - req_access = E.accesses - secure = TRUE - update_icon() - return TRUE - -/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S) - if(lock_in_use) - to_chat(user, "Wait for work on [src] to be done first!") - return - if(locked) - to_chat(user, "Unlock it first!") - return - if(!secure) - to_chat(user, "[src] doesn't have a lock that you can remove!") - return - if(!istype(S)) - return - var/brokenword = broken ? "broken " : null - user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...") - playsound(loc, S.usesound, 50, 1) - lock_in_use = TRUE - if(!do_after(user, 100 * S.toolspeed, target = src)) - lock_in_use = FALSE - return - to_chat(user, "You remove the [brokenword]lock from [src]!") - if(!QDELETED(lockerelectronics)) - lockerelectronics.add_fingerprint(user) - lockerelectronics.forceMove(user.loc) - lockerelectronics = null - req_access = null - secure = FALSE - broken = FALSE - locked = FALSE - lock_in_use = FALSE - update_icon() - return TRUE - - -/obj/structure/closet/deconstruct(disassembled = TRUE) - if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) - new material_drop(loc, material_drop_amount) - qdel(src) - -/obj/structure/closet/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - bust_open() - -/obj/structure/closet/attackby(obj/item/W, mob/user, params) - if(user in src) - return - if(src.tool_interact(W,user)) - return 1 // No afterattack - else - return ..() - -/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise - . = TRUE - if(opened) - if(istype(W, cutting_tool)) - if(istype(W, /obj/item/weldingtool)) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin cutting \the [src] apart...") - if(W.use_tool(src, user, 40, volume=50)) - if(eigen_teleport) - to_chat(user, "The unstable nature of \the [src] makes it impossible to cut!") - return - if(!opened) - return - user.visible_message("[user] slices apart \the [src].", - "You cut \the [src] apart with \the [W].", - "You hear welding.") - deconstruct(TRUE) - return - else // for example cardboard box is cut with wirecutters - user.visible_message("[user] cut apart \the [src].", \ - "You cut \the [src] apart with \the [W].") - deconstruct(TRUE) - return - if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too - return TRUE - else if(istype(W, /obj/item/electronics/airlock)) - handle_lock_addition(user, W) - else if(istype(W, /obj/item/screwdriver)) - handle_lock_removal(user, W) - else if(istype(W, /obj/item/weldingtool) && can_weld_shut) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") - if(W.use_tool(src, user, 40, volume=50)) - if(eigen_teleport) - to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!") - return - if(opened) - return - welded = !welded - after_weld(welded) - user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].", - "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", - "You hear welding.") - update_icon() - else if(istype(W, /obj/item/wrench) && anchorable) - if(isinspace() && !anchored) - return - setAnchored(!anchored) - W.play_tool_sound(src, 75) - user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You hear a ratchet.") - else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) - if(W.GetID() || !toggle(user)) - togglelock(user) - else - return FALSE - -/obj/structure/closet/proc/after_weld(weld_state) - return - -/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) - if(!istype(O) || O.anchored || istype(O, /obj/screen)) - return - if(!istype(user) || user.incapacitated() || user.lying) - return - if(!Adjacent(user) || !user.Adjacent(O)) - return - if(user == O) //try to climb onto it - return ..() - if(!opened) - return - if(!isturf(O.loc)) - return - - var/actuallyismob = 0 - if(isliving(O)) - actuallyismob = 1 - else if(!isitem(O)) - return - var/turf/T = get_turf(src) - var/list/targets = list(O, src) - add_fingerprint(user) - user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ - "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ - "You hear clanging.") - if(actuallyismob) - if(do_after_mob(user, targets, 40)) - user.visible_message("[user] stuffs [O] into [src].", \ - "You stuff [O] into [src].", \ - "You hear a loud metal bang.") - var/mob/living/L = O - if(!issilicon(L)) - L.Knockdown(40) - O.forceMove(T) - close() - else - O.forceMove(T) - return 1 - -/obj/structure/closet/relaymove(mob/user) - if(user.stat || !isturf(loc) || !isliving(user)) - return - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - container_resist(user) - -/obj/structure/closet/attack_hand(mob/user) - . = ..() - if(.) - return - if(user.lying && get_dist(src, user) > 0) - return - - if(!toggle(user)) - togglelock(user) - -/obj/structure/closet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/closet/attack_robot(mob/user) - if(user.Adjacent(src)) - return attack_hand(user) - -// tk grab then use on self -/obj/structure/closet/attack_self_tk(mob/user) - return attack_hand(user) - -/obj/structure/closet/verb/verb_toggleopen() - set src in oview(1) - set category = "Object" - set name = "Toggle Open" - - if(!usr.canmove || usr.stat || usr.restrained()) - return - - if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) - return attack_hand(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -// Objects that try to exit a locker by stepping were doing so successfully, -// and due to an oversight in turf/Enter() were going through walls. That -// should be independently resolved, but this is also an interesting twist. -/obj/structure/closet/Exit(atom/movable/AM) - open() - if(AM.loc == src) - return 0 - return 1 - -/obj/structure/closet/container_resist(mob/living/user) - if(opened) - return - if(ismovableatom(loc)) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - var/atom/movable/AM = loc - AM.relay_container_resist(user, src) - return - if(!welded && !locked) - open() - return - - //okay, so the closet is either welded or locked... resist!!! - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("[src] begins to shake violently!", \ - "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear banging from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) - return - //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting - user.visible_message("[user] successfully broke out of [src]!", - "You successfully break out of [src]!") - bust_open() - else - if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. - to_chat(user, "You fail to break out of [src]!") - -/obj/structure/closet/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc)) - to_chat(user, "You can't do that right now!") - return - togglelock(user) - -/obj/structure/closet/CtrlShiftClick(mob/living/user) - if(!HAS_TRAIT(user, TRAIT_SKITTISH)) - return ..() - if(!user.canUseTopic(src) || !isturf(user.loc)) - return - dive_into(user) - -/obj/structure/closet/emag_act(mob/user) - if(secure && !broken) - user.visible_message("Sparks fly from [src]!", - "You scramble [src]'s lock, breaking it open!", - "You hear a faint electrical spark.") - playsound(src, "sparks", 50, 1) - broken = TRUE - locked = FALSE - if(!QDELETED(lockerelectronics)) - qdel(lockerelectronics) - lockerelectronics = null - update_icon() - -/obj/structure/closet/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/structure/closet/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/obj/O in src) - O.emp_act(severity) - if(!secure || broken) - return ..() - if(prob(50 / severity)) - locked = !locked - update_icon() - if(prob(20 / severity) && !opened) - if(!locked) - open() - else - req_access = list() - req_access += pick(get_all_accesses()) - if(!QDELETED(lockerelectronics)) - lockerelectronics.accesses = req_access - -/obj/structure/closet/contents_explosion(severity, target) - for(var/atom/A in contents) - A.ex_act(severity, target) - CHECK_TICK - -/obj/structure/closet/singularity_act() - dump_contents() - ..() - -/obj/structure/closet/AllowDrop() - return TRUE - - -/obj/structure/closet/return_temperature() - return - -/obj/structure/closet/proc/dive_into(mob/living/user) - var/turf/T1 = get_turf(user) - var/turf/T2 = get_turf(src) - if(!opened) - if(locked) - togglelock(user, TRUE) - if(!open(user)) - to_chat(user, "It won't budge!") - return - step_towards(user, T2) - T1 = get_turf(user) - if(T1 == T2) - user.resting = TRUE //so people can jump into crates without slamming the lid on their head - if(!close(user)) - to_chat(user, "You can't get [src] to close!") - user.resting = FALSE - return - user.resting = FALSE - togglelock(user) - T1.visible_message("[user] dives into [src]!") +/obj/structure/closet + name = "closet" + desc = "It's a basic storage unit." + icon = 'icons/obj/closet.dmi' + icon_state = "generic" + density = TRUE + layer = BELOW_OBJ_LAYER + var/icon_door = null + var/icon_door_override = FALSE //override to have open overlay use icon different to its base's + var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights + var/opened = FALSE + var/welded = FALSE + var/locked = FALSE + var/large = TRUE + var/wall_mounted = 0 //never solid (You can always pass over it) + max_integrity = 200 + integrity_failure = 50 + armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + var/breakout_time = 1200 + var/message_cooldown + var/can_weld_shut = TRUE + var/horizontal = FALSE + var/allow_objects = FALSE + var/allow_dense = FALSE + var/dense_when_open = FALSE //if it's dense when open or not + var/max_mob_size = MOB_SIZE_HUMAN //Biggest mob_size accepted by the container + var/mob_storage_capacity = 3 // how many human sized mob/living can fit together inside a closet. + var/storage_capacity = 30 //This is so that someone can't pack hundreds of items in a locker/crate then open it in a populated area to crash clients. + var/cutting_tool = /obj/item/weldingtool + var/open_sound = 'sound/machines/click.ogg' + var/close_sound = 'sound/machines/click.ogg' + var/material_drop = /obj/item/stack/sheet/metal + var/material_drop_amount = 2 + var/delivery_icon = "deliverycloset" //which icon to use when packagewrapped. null to be unwrappable. + var/anchorable = TRUE + var/icon_welded = "welded" + var/obj/item/electronics/airlock/lockerelectronics //Installed electronics + var/lock_in_use = FALSE //Someone is doing some stuff with the lock here, better not proceed further + var/eigen_teleport = FALSE //If the closet leads to Mr Tumnus. + var/obj/structure/closet/eigen_target //Where you go to. + + +/obj/structure/closet/Initialize(mapload) + . = ..() + update_icon() + PopulateContents() + if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents + take_contents() + if(secure) + lockerelectronics = new(src) + lockerelectronics.accesses = req_access + +//USE THIS TO FILL IT, NOT INITIALIZE OR NEW +/obj/structure/closet/proc/PopulateContents() + return + +/obj/structure/closet/Destroy() + dump_contents(override = FALSE) + return ..() + +/obj/structure/closet/update_icon() + cut_overlays() + if(opened & icon_door_override) + add_overlay("[icon_door]_open") + layer = OBJ_LAYER + return + else if(opened) + add_overlay("[icon_state]_open") + return + if(icon_door) + add_overlay("[icon_door]_door") + else + layer = BELOW_OBJ_LAYER + add_overlay("[icon_state]_door") + if(welded) + add_overlay("welded") + if(!secure) + return + if(broken) + add_overlay("off") + add_overlay("sparking") + else if(locked) + add_overlay("locked") + else + add_overlay("unlocked") + +/obj/structure/closet/examine(mob/user) + . = ..() + if(welded) + . += "It's welded shut." + if(anchored) + . += "It is bolted to the ground." + if(opened) + . += "The parts are welded together." + else if(secure && !opened) + else if(broken) + . += "The lock is screwed in." + else if(secure) + . += "Alt-click to [locked ? "unlock" : "lock"]." + if(isliving(user)) + var/mob/living/L = user + if(HAS_TRAIT(L, TRAIT_SKITTISH)) + . += "Ctrl-Shift-click [src] to jump inside." + +/obj/structure/closet/CanPass(atom/movable/mover, turf/target) + if(wall_mounted) + return TRUE + return !density + +/obj/structure/closet/proc/can_open(mob/living/user) + if(welded || locked) + return FALSE + var/turf/T = get_turf(src) + for(var/mob/living/L in T) + if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) + if(user) + to_chat(user, "There's something large on top of [src], preventing it from opening." ) + return FALSE + return TRUE + +/obj/structure/closet/proc/can_close(mob/living/user) + var/turf/T = get_turf(src) + for(var/obj/structure/closet/closet in T) + if(closet != src && !closet.wall_mounted) + return FALSE + for(var/mob/living/L in T) + if(L.anchored || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) + if(user) + to_chat(user, "There's something too large in [src], preventing it from closing.") + return FALSE + return TRUE + +/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access + if(!secure) + return FALSE + if(broken) + to_chat(user, "[src] is broken!") + return FALSE + if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics + to_chat(user, "[src] is missing locker electronics!") + return FALSE + if(!check_access) + return TRUE + if(allowed(user)) + return TRUE + to_chat(user, "Access denied.") + +/obj/structure/closet/proc/togglelock(mob/living/user) + add_fingerprint(user) + if(eigen_target) + return + if(opened) + return + if(!can_lock(user)) + return + locked = !locked + user.visible_message("[user] [locked ? null : "un"]locks [src].", + "You [locked ? null : "un"]lock [src].") + update_icon() + +/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example + var/atom/L = drop_location() + for(var/atom/movable/AM in src) + if(AM == lockerelectronics && override) + continue + AM.forceMove(L) + if(throwing) // you keep some momentum when getting out of a thrown closet + step(AM, dir) + if(throwing) + throwing.finalize(FALSE) + +/obj/structure/closet/proc/take_contents() + var/atom/L = drop_location() + for(var/atom/movable/AM in L) + if(AM != src && insert(AM) == -1) // limit reached + break + +/obj/structure/closet/proc/open(mob/living/user) + if(opened || !can_open(user)) + return + playsound(loc, open_sound, 15, 1, -3) + opened = TRUE + if(!dense_when_open) + density = FALSE + climb_time *= 0.5 //it's faster to climb onto an open thing + dump_contents() + update_icon() + return 1 + +/obj/structure/closet/proc/insert(atom/movable/AM) + if(contents.len >= storage_capacity) + return -1 + if(insertion_allowed(AM)) + if(eigen_teleport) // For teleporting people with linked lockers. + do_teleport(AM, get_turf(eigen_target), 0) + if(eigen_target.opened == FALSE) + eigen_target.bust_open() + else + AM.forceMove(src) + return TRUE + else + return FALSE + + +/obj/structure/closet/proc/insertion_allowed(atom/movable/AM) + if(ismob(AM)) + if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... + return FALSE + var/mob/living/L = AM + if(L.anchored || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) + return FALSE + if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. + if(horizontal && L.density) + return FALSE + if(L.mob_size > max_mob_size) + return FALSE + var/mobs_stored = 0 + for(var/mob/living/M in contents) + if(++mobs_stored >= mob_storage_capacity) + return FALSE + L.stop_pulling() + + else if(istype(AM, /obj/structure/closet)) + return FALSE + + else if(istype(AM, /obj/effect)) + return FALSE + + else if(isobj(AM)) + if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) + return FALSE + if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) + return TRUE + else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) + return FALSE + else + return FALSE + + return TRUE + +/obj/structure/closet/proc/close(mob/living/user) + if(!opened || !can_close(user)) + return FALSE + take_contents() + playsound(loc, close_sound, 15, 1, -3) + climb_time = initial(climb_time) + opened = FALSE + density = TRUE + update_icon() + return TRUE + +/obj/structure/closet/proc/toggle(mob/living/user) + if(opened) + return close(user) + else + return open(user) + +/obj/structure/closet/proc/bust_open() + welded = FALSE //applies to all lockers + locked = FALSE //applies to critter crates and secure lockers only + broken = TRUE //applies to secure lockers only + open() + +/obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E) + add_fingerprint(user) + if(lock_in_use) + to_chat(user, "Wait for work on [src] to be done first!") + return + if(secure) + to_chat(user, "This locker already has a lock!") + return + if(broken) + to_chat(user, "Unscrew the broken lock first!") + return + if(!istype(E)) + return + user.visible_message("[user] begins installing a lock on [src]...","You begin installing a lock on [src]...") + lock_in_use = TRUE + playsound(loc, 'sound/items/screwdriver.ogg', 50, 1) + if(!do_after(user, 60, target = src)) + lock_in_use = FALSE + return + lock_in_use = FALSE + to_chat(user, "You finish the lock on [src]!") + E.forceMove(src) + lockerelectronics = E + req_access = E.accesses + secure = TRUE + update_icon() + return TRUE + +/obj/structure/closet/proc/handle_lock_removal(mob/user, obj/item/screwdriver/S) + if(lock_in_use) + to_chat(user, "Wait for work on [src] to be done first!") + return + if(locked) + to_chat(user, "Unlock it first!") + return + if(!secure) + to_chat(user, "[src] doesn't have a lock that you can remove!") + return + if(!istype(S)) + return + var/brokenword = broken ? "broken " : null + user.visible_message("[user] begins removing the [brokenword]lock on [src]...","You begin removing the [brokenword]lock on [src]...") + playsound(loc, S.usesound, 50, 1) + lock_in_use = TRUE + if(!do_after(user, 100 * S.toolspeed, target = src)) + lock_in_use = FALSE + return + to_chat(user, "You remove the [brokenword]lock from [src]!") + if(!QDELETED(lockerelectronics)) + lockerelectronics.add_fingerprint(user) + lockerelectronics.forceMove(user.loc) + lockerelectronics = null + req_access = null + secure = FALSE + broken = FALSE + locked = FALSE + lock_in_use = FALSE + update_icon() + return TRUE + + +/obj/structure/closet/deconstruct(disassembled = TRUE) + if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) + new material_drop(loc, material_drop_amount) + qdel(src) + +/obj/structure/closet/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + bust_open() + +/obj/structure/closet/attackby(obj/item/W, mob/user, params) + if(user in src) + return + if(src.tool_interact(W,user)) + return 1 // No afterattack + else + return ..() + +/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise + . = TRUE + if(opened) + if(istype(W, cutting_tool)) + if(istype(W, /obj/item/weldingtool)) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin cutting \the [src] apart...") + if(W.use_tool(src, user, 40, volume=50)) + if(eigen_teleport) + to_chat(user, "The unstable nature of \the [src] makes it impossible to cut!") + return + if(!opened) + return + user.visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [W].", + "You hear welding.") + deconstruct(TRUE) + return + else // for example cardboard box is cut with wirecutters + user.visible_message("[user] cut apart \the [src].", \ + "You cut \the [src] apart with \the [W].") + deconstruct(TRUE) + return + if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too + return TRUE + else if(istype(W, /obj/item/electronics/airlock)) + handle_lock_addition(user, W) + else if(istype(W, /obj/item/screwdriver)) + handle_lock_removal(user, W) + else if(istype(W, /obj/item/weldingtool) && can_weld_shut) + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") + if(W.use_tool(src, user, 40, volume=50)) + if(eigen_teleport) + to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!") + return + if(opened) + return + welded = !welded + after_weld(welded) + user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].", + "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", + "You hear welding.") + update_icon() + else if(istype(W, /obj/item/wrench) && anchorable) + if(isinspace() && !anchored) + return + setAnchored(!anchored) + W.play_tool_sound(src, 75) + user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You hear a ratchet.") + else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) + if(W.GetID() || !toggle(user)) + togglelock(user) + else + return FALSE + +/obj/structure/closet/proc/after_weld(weld_state) + return + +/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) + if(!istype(O) || O.anchored || istype(O, /obj/screen)) + return + if(!istype(user) || user.incapacitated() || user.lying) + return + if(!Adjacent(user) || !user.Adjacent(O)) + return + if(user == O) //try to climb onto it + return ..() + if(!opened) + return + if(!isturf(O.loc)) + return + + var/actuallyismob = 0 + if(isliving(O)) + actuallyismob = 1 + else if(!isitem(O)) + return + var/turf/T = get_turf(src) + var/list/targets = list(O, src) + add_fingerprint(user) + user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ + "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ + "You hear clanging.") + if(actuallyismob) + if(do_after_mob(user, targets, 40)) + user.visible_message("[user] stuffs [O] into [src].", \ + "You stuff [O] into [src].", \ + "You hear a loud metal bang.") + var/mob/living/L = O + if(!issilicon(L)) + L.Knockdown(40) + O.forceMove(T) + close() + else + O.forceMove(T) + return 1 + +/obj/structure/closet/relaymove(mob/user) + if(user.stat || !isturf(loc) || !isliving(user)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + container_resist(user) + +/obj/structure/closet/attack_hand(mob/user) + . = ..() + if(.) + return + if(user.lying && get_dist(src, user) > 0) + return + + if(!toggle(user)) + togglelock(user) + +/obj/structure/closet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/closet/attack_robot(mob/user) + if(user.Adjacent(src)) + return attack_hand(user) + +// tk grab then use on self +/obj/structure/closet/attack_self_tk(mob/user) + return attack_hand(user) + +/obj/structure/closet/verb/verb_toggleopen() + set src in oview(1) + set category = "Object" + set name = "Toggle Open" + + if(!usr.canmove || usr.stat || usr.restrained()) + return + + if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) + return attack_hand(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +// Objects that try to exit a locker by stepping were doing so successfully, +// and due to an oversight in turf/Enter() were going through walls. That +// should be independently resolved, but this is also an interesting twist. +/obj/structure/closet/Exit(atom/movable/AM) + open() + if(AM.loc == src) + return 0 + return 1 + +/obj/structure/closet/container_resist(mob/living/user) + if(opened) + return + if(ismovableatom(loc)) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + var/atom/movable/AM = loc + AM.relay_container_resist(user, src) + return + if(!welded && !locked) + open() + return + + //okay, so the closet is either welded or locked... resist!!! + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("[src] begins to shake violently!", \ + "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear banging from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) + return + //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting + user.visible_message("[user] successfully broke out of [src]!", + "You successfully break out of [src]!") + bust_open() + else + if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. + to_chat(user, "You fail to break out of [src]!") + +/obj/structure/closet/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc)) + to_chat(user, "You can't do that right now!") + return TRUE + togglelock(user) + return TRUE + +/obj/structure/closet/CtrlShiftClick(mob/living/user) + if(!HAS_TRAIT(user, TRAIT_SKITTISH)) + return ..() + if(!user.canUseTopic(src) || !isturf(user.loc)) + return + dive_into(user) + +/obj/structure/closet/emag_act(mob/user) + if(secure && !broken) + user.visible_message("Sparks fly from [src]!", + "You scramble [src]'s lock, breaking it open!", + "You hear a faint electrical spark.") + playsound(src, "sparks", 50, 1) + broken = TRUE + locked = FALSE + if(!QDELETED(lockerelectronics)) + qdel(lockerelectronics) + lockerelectronics = null + update_icon() + +/obj/structure/closet/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/structure/closet/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/obj/O in src) + O.emp_act(severity) + if(!secure || broken) + return ..() + if(prob(50 / severity)) + locked = !locked + update_icon() + if(prob(20 / severity) && !opened) + if(!locked) + open() + else + req_access = list() + req_access += pick(get_all_accesses()) + if(!QDELETED(lockerelectronics)) + lockerelectronics.accesses = req_access + +/obj/structure/closet/contents_explosion(severity, target) + for(var/atom/A in contents) + A.ex_act(severity, target) + CHECK_TICK + +/obj/structure/closet/singularity_act() + dump_contents() + ..() + +/obj/structure/closet/AllowDrop() + return TRUE + + +/obj/structure/closet/return_temperature() + return + +/obj/structure/closet/proc/dive_into(mob/living/user) + var/turf/T1 = get_turf(user) + var/turf/T2 = get_turf(src) + if(!opened) + if(locked) + togglelock(user, TRUE) + if(!open(user)) + to_chat(user, "It won't budge!") + return + step_towards(user, T2) + T1 = get_turf(user) + if(T1 == T2) + user.resting = TRUE //so people can jump into crates without slamming the lid on their head + if(!close(user)) + to_chat(user, "You can't get [src] to close!") + user.resting = FALSE + return + user.resting = FALSE + togglelock(user) + T1.visible_message("[user] dives into [src]!") diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm index e9fdad3c..d6beaa26 100644 --- a/code/game/objects/structures/extinguisher.dm +++ b/code/game/objects/structures/extinguisher.dm @@ -1,154 +1,156 @@ -/obj/structure/extinguisher_cabinet - name = "extinguisher cabinet" - desc = "A small wall mounted cabinet designed to hold a fire extinguisher." - icon = 'icons/obj/wallmounts.dmi' - icon_state = "extinguisher_closed" - anchored = TRUE - density = FALSE - max_integrity = 200 - integrity_failure = 50 - var/obj/item/extinguisher/stored_extinguisher - var/opened = FALSE - -/obj/structure/extinguisher_cabinet/Initialize(mapload, ndir, building) - . = ..() - if(building) - setDir(ndir) - pixel_x = (dir & 3)? 0 : (dir == 4 ? -27 : 27) - pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 - opened = TRUE - icon_state = "extinguisher_empty" - else - stored_extinguisher = new /obj/item/extinguisher(src) - -/obj/structure/extinguisher_cabinet/examine(mob/user) - . = ..() - . += "Alt-click to [opened ? "close":"open"] it." - -/obj/structure/extinguisher_cabinet/Destroy() - if(stored_extinguisher) - qdel(stored_extinguisher) - stored_extinguisher = null - return ..() - -/obj/structure/extinguisher_cabinet/contents_explosion(severity, target) - if(stored_extinguisher) - stored_extinguisher.ex_act(severity, target) - -/obj/structure/extinguisher_cabinet/handle_atom_del(atom/A) - if(A == stored_extinguisher) - stored_extinguisher = null - update_icon() - -/obj/structure/extinguisher_cabinet/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/wrench) && !stored_extinguisher) - to_chat(user, "You start unsecuring [name]...") - I.play_tool_sound(src) - if(I.use_tool(src, user, 60)) - playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) - to_chat(user, "You unsecure [name].") - deconstruct(TRUE) - return - - if(iscyborg(user) || isalien(user)) - return - if(istype(I, /obj/item/extinguisher)) - if(!stored_extinguisher && opened) - if(!user.transferItemToLoc(I, src)) - return - stored_extinguisher = I - to_chat(user, "You place [I] in [src].") - update_icon() - return TRUE - else - toggle_cabinet(user) - else if(user.a_intent != INTENT_HARM) - toggle_cabinet(user) - else - return ..() - - -/obj/structure/extinguisher_cabinet/attack_hand(mob/user) - . = ..() - if(.) - return - if(iscyborg(user) || isalien(user)) - return - if(stored_extinguisher) - user.put_in_hands(stored_extinguisher) - to_chat(user, "You take [stored_extinguisher] from [src].") - stored_extinguisher = null - if(!opened) - opened = 1 - playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) - update_icon() - else - toggle_cabinet(user) - - -/obj/structure/extinguisher_cabinet/attack_tk(mob/user) - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - to_chat(user, "You telekinetically remove [stored_extinguisher] from [src].") - stored_extinguisher = null - opened = 1 - playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) - update_icon() - else - toggle_cabinet(user) - - -/obj/structure/extinguisher_cabinet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/extinguisher_cabinet/AltClick(mob/living/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - toggle_cabinet(user) - -/obj/structure/extinguisher_cabinet/proc/toggle_cabinet(mob/user) - if(opened && broken) - to_chat(user, "[src] is broken open.") - else - playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) - opened = !opened - update_icon() - -/obj/structure/extinguisher_cabinet/update_icon() - if(!opened) - icon_state = "extinguisher_closed" - return - if(stored_extinguisher) - if(istype(stored_extinguisher, /obj/item/extinguisher/mini)) - icon_state = "extinguisher_mini" - else - icon_state = "extinguisher_full" - else - icon_state = "extinguisher_empty" - -/obj/structure/extinguisher_cabinet/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - broken = 1 - opened = 1 - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - stored_extinguisher = null - update_icon() - - -/obj/structure/extinguisher_cabinet/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(disassembled) - new /obj/item/wallframe/extinguisher_cabinet(loc) - else - new /obj/item/stack/sheet/metal (loc, 2) - if(stored_extinguisher) - stored_extinguisher.forceMove(loc) - stored_extinguisher = null - qdel(src) - -/obj/item/wallframe/extinguisher_cabinet - name = "extinguisher cabinet frame" - desc = "Used for building wall-mounted extinguisher cabinets." - icon_state = "extinguisher" - result_path = /obj/structure/extinguisher_cabinet +/obj/structure/extinguisher_cabinet + name = "extinguisher cabinet" + desc = "A small wall mounted cabinet designed to hold a fire extinguisher." + icon = 'icons/obj/wallmounts.dmi' + icon_state = "extinguisher_closed" + anchored = TRUE + density = FALSE + max_integrity = 200 + integrity_failure = 50 + var/obj/item/extinguisher/stored_extinguisher + var/opened = FALSE + +/obj/structure/extinguisher_cabinet/Initialize(mapload, ndir, building) + . = ..() + if(building) + setDir(ndir) + pixel_x = (dir & 3)? 0 : (dir == 4 ? -27 : 27) + pixel_y = (dir & 3)? (dir ==1 ? -30 : 30) : 0 + opened = TRUE + icon_state = "extinguisher_empty" + else + stored_extinguisher = new /obj/item/extinguisher(src) + +/obj/structure/extinguisher_cabinet/examine(mob/user) + . = ..() + . += "Alt-click to [opened ? "close":"open"] it." + +/obj/structure/extinguisher_cabinet/Destroy() + if(stored_extinguisher) + qdel(stored_extinguisher) + stored_extinguisher = null + return ..() + +/obj/structure/extinguisher_cabinet/contents_explosion(severity, target) + if(stored_extinguisher) + stored_extinguisher.ex_act(severity, target) + +/obj/structure/extinguisher_cabinet/handle_atom_del(atom/A) + if(A == stored_extinguisher) + stored_extinguisher = null + update_icon() + +/obj/structure/extinguisher_cabinet/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/wrench) && !stored_extinguisher) + to_chat(user, "You start unsecuring [name]...") + I.play_tool_sound(src) + if(I.use_tool(src, user, 60)) + playsound(loc, 'sound/items/deconstruct.ogg', 50, 1) + to_chat(user, "You unsecure [name].") + deconstruct(TRUE) + return + + if(iscyborg(user) || isalien(user)) + return + if(istype(I, /obj/item/extinguisher)) + if(!stored_extinguisher && opened) + if(!user.transferItemToLoc(I, src)) + return + stored_extinguisher = I + to_chat(user, "You place [I] in [src].") + update_icon() + return TRUE + else + toggle_cabinet(user) + else if(user.a_intent != INTENT_HARM) + toggle_cabinet(user) + else + return ..() + + +/obj/structure/extinguisher_cabinet/attack_hand(mob/user) + . = ..() + if(.) + return + if(iscyborg(user) || isalien(user)) + return + if(stored_extinguisher) + user.put_in_hands(stored_extinguisher) + to_chat(user, "You take [stored_extinguisher] from [src].") + stored_extinguisher = null + if(!opened) + opened = 1 + playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) + update_icon() + else + toggle_cabinet(user) + + +/obj/structure/extinguisher_cabinet/attack_tk(mob/user) + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + to_chat(user, "You telekinetically remove [stored_extinguisher] from [src].") + stored_extinguisher = null + opened = 1 + playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) + update_icon() + else + toggle_cabinet(user) + + +/obj/structure/extinguisher_cabinet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/extinguisher_cabinet/AltClick(mob/living/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + toggle_cabinet(user) + return TRUE + +/obj/structure/extinguisher_cabinet/proc/toggle_cabinet(mob/user) + if(opened && broken) + to_chat(user, "[src] is broken open.") + else + playsound(loc, 'sound/machines/click.ogg', 15, 1, -3) + opened = !opened + update_icon() + +/obj/structure/extinguisher_cabinet/update_icon() + if(!opened) + icon_state = "extinguisher_closed" + return + if(stored_extinguisher) + if(istype(stored_extinguisher, /obj/item/extinguisher/mini)) + icon_state = "extinguisher_mini" + else + icon_state = "extinguisher_full" + else + icon_state = "extinguisher_empty" + +/obj/structure/extinguisher_cabinet/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + broken = 1 + opened = 1 + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + stored_extinguisher = null + update_icon() + + +/obj/structure/extinguisher_cabinet/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(disassembled) + new /obj/item/wallframe/extinguisher_cabinet(loc) + else + new /obj/item/stack/sheet/metal (loc, 2) + if(stored_extinguisher) + stored_extinguisher.forceMove(loc) + stored_extinguisher = null + qdel(src) + +/obj/item/wallframe/extinguisher_cabinet + name = "extinguisher cabinet frame" + desc = "Used for building wall-mounted extinguisher cabinets." + icon_state = "extinguisher" + result_path = /obj/structure/extinguisher_cabinet diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index 1c26422f..0cd09340 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -1,444 +1,445 @@ -/* Morgue stuff - * Contains: - * Morgue - * Morgue tray - * Crematorium - * Creamatorium - * Crematorium tray - * Crematorium button - */ - -/* - * Bodycontainer - * Parent class for morgue and crematorium - * For overriding only - */ -GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants and other ghosties. - -/obj/structure/bodycontainer - icon = 'icons/obj/stationobjs.dmi' - icon_state = "morgue1" - density = TRUE - anchored = TRUE - max_integrity = 400 - - var/obj/structure/tray/connected = null - var/locked = FALSE - dir = SOUTH - var/message_cooldown - var/breakout_time = 600 - var/obj/item/radio/radio = null - var/hasradio = FALSE - -/obj/structure/bodycontainer/Initialize() - . = ..() - GLOB.bodycontainers += src - recursive_organ_check(src) - -/obj/structure/bodycontainer/Destroy() - GLOB.bodycontainers -= src - open() - if(connected) - qdel(connected) - connected = null - return ..() - -/obj/structure/bodycontainer/on_log(login) - ..() - update_icon() - -/obj/structure/bodycontainer/update_icon() - return - -/obj/structure/bodycontainer/relaymove(mob/user) - if(user.stat || !isturf(loc)) - return - if(locked) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - open() - -/obj/structure/bodycontainer/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/bodycontainer/attack_hand(mob/user) - . = ..() - if(.) - return - if(locked) - to_chat(user, "It's locked.") - return - if(!connected) - to_chat(user, "That doesn't appear to have a tray.") - return - if(connected.loc == src) - open() - else - close() - add_fingerprint(user) - -/obj/structure/bodycontainer/attack_robot(mob/user) - if(!user.Adjacent(src)) - return - return attack_hand(user) - -/obj/structure/bodycontainer/attackby(obj/P, mob/user, params) - add_fingerprint(user) - if(istype(P, /obj/item/pen)) - if(!user.is_literate()) - to_chat(user, "You scribble illegibly on the side of [src]!") - return - var/t = stripped_input(user, "What would you like the label to be?", text("[]", name), null) - if (user.get_active_held_item() != P) - return - if(!user.canUseTopic(src, BE_CLOSE)) - return - if (t) - name = text("[]- '[]'", initial(name), t) - else - name = initial(name) - else - return ..() - -/obj/structure/bodycontainer/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 5) - recursive_organ_check(src) - qdel(src) - -/obj/structure/bodycontainer/container_resist(mob/living/user) - if(!locked) - open() - return - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message(null, \ - "You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a metallic creaking from [src].") - if(do_after(user,(breakout_time), target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open() - -/obj/structure/bodycontainer/proc/open() - recursive_organ_check(src) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - playsound(src, 'sound/effects/roll.ogg', 5, 1) - var/turf/T = get_step(src, dir) - connected.setDir(dir) - for(var/atom/movable/AM in src) - if(AM != radio) - AM.forceMove(T) - update_icon() - -/obj/structure/bodycontainer/proc/close() - playsound(src, 'sound/effects/roll.ogg', 5, 1) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - for(var/atom/movable/AM in connected.loc) - if(!AM.anchored || AM == connected) - AM.forceMove(src) - recursive_organ_check(src) - update_icon() - -/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) -/* - * Morgue - */ -/obj/structure/bodycontainer/morgue - name = "morgue" - desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." - icon_state = "morgue1" - dir = EAST - var/beeper = TRUE - var/beep_cooldown = 50 - var/next_beep = 0 - //Hyperstation edit - var/radio_key = /obj/item/encryptionkey/headset_med - var/medical_channel = "Medical" - var/inuse = FALSE - hasradio = TRUE - -/obj/structure/bodycontainer/morgue/Initialize() - . = ..() - radio = new(src) - radio.anchored = TRUE - radio.keyslot = new radio_key - radio.listening = 0 - radio.recalculateChannels() - -/obj/structure/bodycontainer/morgue/Destroy() - QDEL_NULL(radio) - GLOB.bodycontainers -= src - open() - if(connected) - qdel(connected) - connected = null - return ..() -/* -/obj/structure/bodycontainer/morgue/proc/open2() - recursive_organ_check(src) - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) - playsound(src, 'sound/effects/roll.ogg', 5, 1) - var/turf/T = get_step(src, dir) - connected.setDir(dir) - for(var/atom/movable/AM in src) - if(AM != radio) - AM.forceMove(T) - update_icon() -*/ -//end of edit -/obj/structure/bodycontainer/morgue/New() - connected = new/obj/structure/tray/m_tray(src) - connected.connected = src - ..() - -/obj/structure/bodycontainer/morgue/examine(mob/user) - . = ..() - . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." - -/obj/structure/bodycontainer/morgue/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, !issilicon(user))) - return - beeper = !beeper - to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") - -/obj/structure/bodycontainer/morgue/update_icon() - if (!connected || connected.loc != src) // Open or tray is gone. - icon_state = "morgue0" - else - if(contents.len == 2) // Empty. Was length 1 because of the tray, is now length 2 because it includes a radio. - icon_state = "morgue1" - else - icon_state = "morgue2" // Dead, brainded mob. - var/list/compiled = recursive_mob_check(src, 0, 0) // Search for mobs in all contents. - if(!length(compiled)) // No mobs? - icon_state = "morgue3" - return - - for(var/mob/living/M in compiled) - var/mob/living/mob_occupant = get_mob_or_brainmob(M) - if(mob_occupant.client && !mob_occupant.suiciding && !(HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && !mob_occupant.hellbound) - icon_state = "morgue4" // Cloneable - if(mob_occupant.stat == DEAD && beeper) - if(world.time > next_beep) - playsound(src, 'sound/weapons/smg_empty_alarm.ogg', 50, 0) //Clone them you blind fucks - next_beep = world.time + beep_cooldown - break - - -/obj/item/paper/guides/jobs/medical/morgue - name = "morgue memo" - info = "Since this station's medbay never seems to fail to be staffed by the mindless monkeys meant for genetics experiments, I'm leaving a reminder here for anyone handling the pile of cadavers the quacks are sure to leave.

    Red lights mean there's a plain ol' dead body inside.

    Yellow lights mean there's non-body objects inside.
    Probably stuff pried off a corpse someone grabbed, or if you're lucky it's stashed booze.

    Green lights mean the morgue system detects the body may be able to be cloned.

    I don't know how that works, but keep it away from the kitchen and go yell at the geneticists.

    - CentCom medical inspector" - -/* - * Crematorium - */ -GLOBAL_LIST_EMPTY(crematoriums) -/obj/structure/bodycontainer/crematorium - name = "crematorium" - desc = "A human incinerator. Works well on barbecue nights." - icon_state = "crema1" - dir = SOUTH - var/id = 1 - -/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help - to_chat(user, "[src] is locked against you.") - return - -/obj/structure/bodycontainer/crematorium/Destroy() - GLOB.crematoriums.Remove(src) - return ..() - -/obj/structure/bodycontainer/crematorium/New() - connected = new/obj/structure/tray/c_tray(src) - connected.connected = src - - GLOB.crematoriums.Add(src) - ..() - -/obj/structure/bodycontainer/crematorium/update_icon() - if(!connected || connected.loc != src) - icon_state = "crema0" - else - - if(src.contents.len > 1) - src.icon_state = "crema2" - else - src.icon_state = "crema1" - - if(locked) - src.icon_state = "crema_active" - - return - -/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user) - if(locked) - return //don't let you cremate something twice or w/e - // Make sure we don't delete the actual morgue and its tray - var/list/conts = GetAllContents() - src - connected - - if(!conts.len) - audible_message("You hear a hollow crackle.") - return - - else - audible_message("You hear a roar as the crematorium activates.") - - locked = TRUE - update_icon() - - for(var/mob/living/M in conts) - if (M.stat != DEAD) - M.emote("scream") - if(user) - log_combat(user, M, "cremated") - else - M.log_message("was cremated", LOG_ATTACK) - - M.death(1) - if(M) //some animals get automatically deleted on death. - M.ghostize() - qdel(M) - - for(var/obj/O in conts) //conts defined above, ignores crematorium and tray - qdel(O) - - if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up - new/obj/effect/decal/cleanable/ash/crematorium(src) - - sleep(30) - - if(!QDELETED(src)) - locked = FALSE - update_icon() - playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) //you horrible people - -/obj/structure/bodycontainer/crematorium/creamatorium - name = "creamatorium" - desc = "A human incinerator. Works well during ice cream socials." - -/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user) - var/list/icecreams = new() - for(var/mob/living/i_scream in GetAllContents()) - var/obj/item/reagent_containers/food/snacks/icecream/IC = new() - IC.set_cone_type("waffle") - IC.add_mob_flavor(i_scream) - icecreams += IC - . = ..() - for(var/obj/IC in icecreams) - IC.forceMove(src) - -/* - * Generic Tray - * Parent class for morguetray and crematoriumtray - * For overriding only - */ -/obj/structure/tray - icon = 'icons/obj/stationobjs.dmi' - density = TRUE - layer = BELOW_OBJ_LAYER - var/obj/structure/bodycontainer/connected = null - anchored = TRUE - pass_flags = LETPASSTHROW - max_integrity = 350 - -/obj/structure/tray/Destroy() - if(connected) - connected.connected = null - connected.update_icon() - connected = null - return ..() - -/obj/structure/tray/deconstruct(disassembled = TRUE) - new /obj/item/stack/sheet/metal (loc, 2) - qdel(src) - -/obj/structure/tray/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/tray/attack_hand(mob/user) - . = ..() - if(.) - return - if (src.connected) - connected.close() - add_fingerprint(user) - else - to_chat(user, "That's not connected to anything!") - -/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user) - if(!ismovableatom(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user) - return - if(!ismob(O)) - if(!istype(O, /obj/structure/closet/body_bag)) - return - else - var/mob/M = O - if(M.buckled) - return - if(!ismob(user) || user.lying || user.incapacitated()) - return - O.forceMove(src.loc) - if (user != O) - visible_message("[user] stuffs [O] into [src].") - return - -/* - * Crematorium tray - */ -/obj/structure/tray/c_tray - name = "crematorium tray" - desc = "Apply body before burning." - icon_state = "cremat" - -/* - * Morgue tray - */ -/obj/structure/tray/m_tray - name = "morgue tray" - desc = "Apply corpse before closing." - icon_state = "morguet" - -/obj/structure/tray/m_tray/CanPass(atom/movable/mover, turf/target) - if(istype(mover) && (mover.pass_flags & PASSTABLE)) - return 1 - if(locate(/obj/structure/table) in get_turf(mover)) - return 1 - else - return 0 - -/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) - . = !density - if(ismovableatom(caller)) - var/atom/movable/mover = caller - . = . || (mover.pass_flags & PASSTABLE) - -//Hyperstation edit starts here -/obj/structure/bodycontainer/morgue/attack_ghost(mob/user) - . = ..() - if(!beeper || inuse) - return - - var/list/compiled = recursive_mob_check(src, 0, 0) // Search for mobs in all contents. - if(!length(compiled)) // No mobs? - return - - for(var/mob/living/M in compiled) - var/mob/living/mob_occupant = get_mob_or_brainmob(M) - if(!mob_occupant.suiciding && !mob_occupant.hellbound) - if(mob_occupant.stat == DEAD && mob_occupant.mind.key == user.client.key) - inuse = TRUE - visible_message("One of the morgue coffins currently holds a soul that is eager to have its body revived.") - radio.talk_into(src, "One of the morgue coffins currently holds a soul that is eager to have its body revived.", medical_channel) - playsound(loc, 'sound/machines/ping.ogg', 50) - addtimer(CALLBACK(src, .proc/liftcooldown), 500) - -/obj/structure/bodycontainer/morgue/proc/liftcooldown() - inuse = FALSE -//Hyperstation edit ends here +/* Morgue stuff + * Contains: + * Morgue + * Morgue tray + * Crematorium + * Creamatorium + * Crematorium tray + * Crematorium button + */ + +/* + * Bodycontainer + * Parent class for morgue and crematorium + * For overriding only + */ +GLOBAL_LIST_EMPTY(bodycontainers) //Let them act as spawnpoints for revenants and other ghosties. + +/obj/structure/bodycontainer + icon = 'icons/obj/stationobjs.dmi' + icon_state = "morgue1" + density = TRUE + anchored = TRUE + max_integrity = 400 + + var/obj/structure/tray/connected = null + var/locked = FALSE + dir = SOUTH + var/message_cooldown + var/breakout_time = 600 + var/obj/item/radio/radio = null + var/hasradio = FALSE + +/obj/structure/bodycontainer/Initialize() + . = ..() + GLOB.bodycontainers += src + recursive_organ_check(src) + +/obj/structure/bodycontainer/Destroy() + GLOB.bodycontainers -= src + open() + if(connected) + qdel(connected) + connected = null + return ..() + +/obj/structure/bodycontainer/on_log(login) + ..() + update_icon() + +/obj/structure/bodycontainer/update_icon() + return + +/obj/structure/bodycontainer/relaymove(mob/user) + if(user.stat || !isturf(loc)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + open() + +/obj/structure/bodycontainer/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/bodycontainer/attack_hand(mob/user) + . = ..() + if(.) + return + if(locked) + to_chat(user, "It's locked.") + return + if(!connected) + to_chat(user, "That doesn't appear to have a tray.") + return + if(connected.loc == src) + open() + else + close() + add_fingerprint(user) + +/obj/structure/bodycontainer/attack_robot(mob/user) + if(!user.Adjacent(src)) + return + return attack_hand(user) + +/obj/structure/bodycontainer/attackby(obj/P, mob/user, params) + add_fingerprint(user) + if(istype(P, /obj/item/pen)) + if(!user.is_literate()) + to_chat(user, "You scribble illegibly on the side of [src]!") + return + var/t = stripped_input(user, "What would you like the label to be?", text("[]", name), null) + if (user.get_active_held_item() != P) + return + if(!user.canUseTopic(src, BE_CLOSE)) + return + if (t) + name = text("[]- '[]'", initial(name), t) + else + name = initial(name) + else + return ..() + +/obj/structure/bodycontainer/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 5) + recursive_organ_check(src) + qdel(src) + +/obj/structure/bodycontainer/container_resist(mob/living/user) + if(!locked) + open() + return + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message(null, \ + "You lean on the back of [src] and start pushing the tray open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a metallic creaking from [src].") + if(do_after(user,(breakout_time), target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open() + +/obj/structure/bodycontainer/proc/open() + recursive_organ_check(src) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + playsound(src, 'sound/effects/roll.ogg', 5, 1) + var/turf/T = get_step(src, dir) + connected.setDir(dir) + for(var/atom/movable/AM in src) + if(AM != radio) + AM.forceMove(T) + update_icon() + +/obj/structure/bodycontainer/proc/close() + playsound(src, 'sound/effects/roll.ogg', 5, 1) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + for(var/atom/movable/AM in connected.loc) + if(!AM.anchored || AM == connected) + AM.forceMove(src) + recursive_organ_check(src) + update_icon() + +/obj/structure/bodycontainer/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 2) +/* + * Morgue + */ +/obj/structure/bodycontainer/morgue + name = "morgue" + desc = "Used to keep bodies in until someone fetches them. Now includes a high-tech alert system." + icon_state = "morgue1" + dir = EAST + var/beeper = TRUE + var/beep_cooldown = 50 + var/next_beep = 0 + //Hyperstation edit + var/radio_key = /obj/item/encryptionkey/headset_med + var/medical_channel = "Medical" + var/inuse = FALSE + hasradio = TRUE + +/obj/structure/bodycontainer/morgue/Initialize() + . = ..() + radio = new(src) + radio.anchored = TRUE + radio.keyslot = new radio_key + radio.listening = 0 + radio.recalculateChannels() + +/obj/structure/bodycontainer/morgue/Destroy() + QDEL_NULL(radio) + GLOB.bodycontainers -= src + open() + if(connected) + qdel(connected) + connected = null + return ..() +/* +/obj/structure/bodycontainer/morgue/proc/open2() + recursive_organ_check(src) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + playsound(src, 'sound/effects/roll.ogg', 5, 1) + var/turf/T = get_step(src, dir) + connected.setDir(dir) + for(var/atom/movable/AM in src) + if(AM != radio) + AM.forceMove(T) + update_icon() +*/ +//end of edit +/obj/structure/bodycontainer/morgue/New() + connected = new/obj/structure/tray/m_tray(src) + connected.connected = src + ..() + +/obj/structure/bodycontainer/morgue/examine(mob/user) + . = ..() + . += "The speaker is [beeper ? "enabled" : "disabled"]. Alt-click to toggle it." + +/obj/structure/bodycontainer/morgue/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, !issilicon(user))) + return + beeper = !beeper + to_chat(user, "You turn the speaker function [beeper ? "on" : "off"].") + return TRUE + +/obj/structure/bodycontainer/morgue/update_icon() + if (!connected || connected.loc != src) // Open or tray is gone. + icon_state = "morgue0" + else + if(contents.len == 2) // Empty. Was length 1 because of the tray, is now length 2 because it includes a radio. + icon_state = "morgue1" + else + icon_state = "morgue2" // Dead, brainded mob. + var/list/compiled = recursive_mob_check(src, 0, 0) // Search for mobs in all contents. + if(!length(compiled)) // No mobs? + icon_state = "morgue3" + return + + for(var/mob/living/M in compiled) + var/mob/living/mob_occupant = get_mob_or_brainmob(M) + if(mob_occupant.client && !mob_occupant.suiciding && !(HAS_TRAIT(mob_occupant, TRAIT_NOCLONE)) && !mob_occupant.hellbound) + icon_state = "morgue4" // Cloneable + if(mob_occupant.stat == DEAD && beeper) + if(world.time > next_beep) + playsound(src, 'sound/weapons/smg_empty_alarm.ogg', 50, 0) //Clone them you blind fucks + next_beep = world.time + beep_cooldown + break + + +/obj/item/paper/guides/jobs/medical/morgue + name = "morgue memo" + info = "Since this station's medbay never seems to fail to be staffed by the mindless monkeys meant for genetics experiments, I'm leaving a reminder here for anyone handling the pile of cadavers the quacks are sure to leave.

    Red lights mean there's a plain ol' dead body inside.

    Yellow lights mean there's non-body objects inside.
    Probably stuff pried off a corpse someone grabbed, or if you're lucky it's stashed booze.

    Green lights mean the morgue system detects the body may be able to be cloned.

    I don't know how that works, but keep it away from the kitchen and go yell at the geneticists.

    - CentCom medical inspector" + +/* + * Crematorium + */ +GLOBAL_LIST_EMPTY(crematoriums) +/obj/structure/bodycontainer/crematorium + name = "crematorium" + desc = "A human incinerator. Works well on barbecue nights." + icon_state = "crema1" + dir = SOUTH + var/id = 1 + +/obj/structure/bodycontainer/crematorium/attack_robot(mob/user) //Borgs can't use crematoriums without help + to_chat(user, "[src] is locked against you.") + return + +/obj/structure/bodycontainer/crematorium/Destroy() + GLOB.crematoriums.Remove(src) + return ..() + +/obj/structure/bodycontainer/crematorium/New() + connected = new/obj/structure/tray/c_tray(src) + connected.connected = src + + GLOB.crematoriums.Add(src) + ..() + +/obj/structure/bodycontainer/crematorium/update_icon() + if(!connected || connected.loc != src) + icon_state = "crema0" + else + + if(src.contents.len > 1) + src.icon_state = "crema2" + else + src.icon_state = "crema1" + + if(locked) + src.icon_state = "crema_active" + + return + +/obj/structure/bodycontainer/crematorium/proc/cremate(mob/user) + if(locked) + return //don't let you cremate something twice or w/e + // Make sure we don't delete the actual morgue and its tray + var/list/conts = GetAllContents() - src - connected + + if(!conts.len) + audible_message("You hear a hollow crackle.") + return + + else + audible_message("You hear a roar as the crematorium activates.") + + locked = TRUE + update_icon() + + for(var/mob/living/M in conts) + if (M.stat != DEAD) + M.emote("scream") + if(user) + log_combat(user, M, "cremated") + else + M.log_message("was cremated", LOG_ATTACK) + + M.death(1) + if(M) //some animals get automatically deleted on death. + M.ghostize() + qdel(M) + + for(var/obj/O in conts) //conts defined above, ignores crematorium and tray + qdel(O) + + if(!locate(/obj/effect/decal/cleanable/ash) in get_step(src, dir))//prevent pile-up + new/obj/effect/decal/cleanable/ash/crematorium(src) + + sleep(30) + + if(!QDELETED(src)) + locked = FALSE + update_icon() + playsound(src.loc, 'sound/machines/ding.ogg', 50, 1) //you horrible people + +/obj/structure/bodycontainer/crematorium/creamatorium + name = "creamatorium" + desc = "A human incinerator. Works well during ice cream socials." + +/obj/structure/bodycontainer/crematorium/creamatorium/cremate(mob/user) + var/list/icecreams = new() + for(var/mob/living/i_scream in GetAllContents()) + var/obj/item/reagent_containers/food/snacks/icecream/IC = new() + IC.set_cone_type("waffle") + IC.add_mob_flavor(i_scream) + icecreams += IC + . = ..() + for(var/obj/IC in icecreams) + IC.forceMove(src) + +/* + * Generic Tray + * Parent class for morguetray and crematoriumtray + * For overriding only + */ +/obj/structure/tray + icon = 'icons/obj/stationobjs.dmi' + density = TRUE + layer = BELOW_OBJ_LAYER + var/obj/structure/bodycontainer/connected = null + anchored = TRUE + pass_flags = LETPASSTHROW + max_integrity = 350 + +/obj/structure/tray/Destroy() + if(connected) + connected.connected = null + connected.update_icon() + connected = null + return ..() + +/obj/structure/tray/deconstruct(disassembled = TRUE) + new /obj/item/stack/sheet/metal (loc, 2) + qdel(src) + +/obj/structure/tray/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/tray/attack_hand(mob/user) + . = ..() + if(.) + return + if (src.connected) + connected.close() + add_fingerprint(user) + else + to_chat(user, "That's not connected to anything!") + +/obj/structure/tray/MouseDrop_T(atom/movable/O as mob|obj, mob/user) + if(!ismovableatom(O) || O.anchored || !Adjacent(user) || !user.Adjacent(O) || O.loc == user) + return + if(!ismob(O)) + if(!istype(O, /obj/structure/closet/body_bag)) + return + else + var/mob/M = O + if(M.buckled) + return + if(!ismob(user) || user.lying || user.incapacitated()) + return + O.forceMove(src.loc) + if (user != O) + visible_message("[user] stuffs [O] into [src].") + return + +/* + * Crematorium tray + */ +/obj/structure/tray/c_tray + name = "crematorium tray" + desc = "Apply body before burning." + icon_state = "cremat" + +/* + * Morgue tray + */ +/obj/structure/tray/m_tray + name = "morgue tray" + desc = "Apply corpse before closing." + icon_state = "morguet" + +/obj/structure/tray/m_tray/CanPass(atom/movable/mover, turf/target) + if(istype(mover) && (mover.pass_flags & PASSTABLE)) + return 1 + if(locate(/obj/structure/table) in get_turf(mover)) + return 1 + else + return 0 + +/obj/structure/tray/m_tray/CanAStarPass(ID, dir, caller) + . = !density + if(ismovableatom(caller)) + var/atom/movable/mover = caller + . = . || (mover.pass_flags & PASSTABLE) + +//Hyperstation edit starts here +/obj/structure/bodycontainer/morgue/attack_ghost(mob/user) + . = ..() + if(!beeper || inuse) + return + + var/list/compiled = recursive_mob_check(src, 0, 0) // Search for mobs in all contents. + if(!length(compiled)) // No mobs? + return + + for(var/mob/living/M in compiled) + var/mob/living/mob_occupant = get_mob_or_brainmob(M) + if(!mob_occupant.suiciding && !mob_occupant.hellbound) + if(mob_occupant.stat == DEAD && mob_occupant.mind.key == user.client.key) + inuse = TRUE + visible_message("One of the morgue coffins currently holds a soul that is eager to have its body revived.") + radio.talk_into(src, "One of the morgue coffins currently holds a soul that is eager to have its body revived.", medical_channel) + playsound(loc, 'sound/machines/ping.ogg', 50) + addtimer(CALLBACK(src, .proc/liftcooldown), 500) + +/obj/structure/bodycontainer/morgue/proc/liftcooldown() + inuse = FALSE +//Hyperstation edit ends here diff --git a/code/game/objects/structures/reflector.dm b/code/game/objects/structures/reflector.dm index 15de7e37..515938fb 100644 --- a/code/game/objects/structures/reflector.dm +++ b/code/game/objects/structures/reflector.dm @@ -168,10 +168,12 @@ return TRUE /obj/structure/reflector/AltClick(mob/user) + . = ..() if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return else if(finished) rotate(user) + return TRUE //TYPES OF REFLECTORS, SINGLE, DOUBLE, BOX diff --git a/code/modules/admin/sound_emitter.dm b/code/modules/admin/sound_emitter.dm index 2901659c..702e2071 100644 --- a/code/modules/admin/sound_emitter.dm +++ b/code/modules/admin/sound_emitter.dm @@ -52,9 +52,11 @@ edit_emitter(user) /obj/effect/sound_emitter/AltClick(mob/user) + . = ..() if(check_rights_for(user.client, R_SOUNDS)) activate(user) to_chat(user, "Sound emitter activated.") + return TRUE /obj/effect/sound_emitter/proc/edit_emitter(mob/user) var/dat = "" diff --git a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm index d7646c55..2322a9ef 100644 --- a/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm +++ b/code/modules/antagonists/clockcult/clock_items/clockwork_slab.dm @@ -180,9 +180,11 @@ access_display(user) /obj/item/clockwork/slab/AltClick(mob/living/user) + . = ..() if(is_servant_of_ratvar(user) && linking && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) linking = null to_chat(user, "Object link canceled.") + return TRUE /obj/item/clockwork/slab/proc/access_display(mob/living/user) if(!is_servant_of_ratvar(user)) diff --git a/code/modules/antagonists/revenant/revenant_abilities.dm b/code/modules/antagonists/revenant/revenant_abilities.dm index 481d200f..e69e0a9f 100644 --- a/code/modules/antagonists/revenant/revenant_abilities.dm +++ b/code/modules/antagonists/revenant/revenant_abilities.dm @@ -5,7 +5,7 @@ ShiftClickOn(A) return if(modifiers["alt"]) - AltClickNoInteract(src, A) + altclick_listed_turf(A) return if(iscarbon(A)) diff --git a/code/modules/atmospherics/machinery/airalarm.dm b/code/modules/atmospherics/machinery/airalarm.dm index 365cc738..1ed9f70e 100644 --- a/code/modules/atmospherics/machinery/airalarm.dm +++ b/code/modules/atmospherics/machinery/airalarm.dm @@ -838,11 +838,11 @@ return FALSE /obj/machinery/airalarm/AltClick(mob/user) - ..() + . = ..() if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc)) return - else - togglelock(user) + togglelock(user) + return TRUE /obj/machinery/airalarm/proc/togglelock(mob/living/user) if(stat & (NOPOWER|BROKEN)) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index 045e0b17..f6bbe7b3 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -1,342 +1,341 @@ -// Quick overview: -// -// Pipes combine to form pipelines -// Pipelines and other atmospheric objects combine to form pipe_networks -// Note: A single pipe_network represents a completely open space -// -// Pipes -> Pipelines -// Pipelines + Other Objects -> Pipe network - -#define PIPE_VISIBLE_LEVEL 2 -#define PIPE_HIDDEN_LEVEL 1 - -/obj/machinery/atmospherics - anchored = TRUE - move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. - idle_power_usage = 0 - active_power_usage = 0 - power_channel = ENVIRON - layer = GAS_PIPE_HIDDEN_LAYER //under wires - resistance_flags = FIRE_PROOF - max_integrity = 200 - obj_flags = CAN_BE_HIT | ON_BLUEPRINTS - var/nodealert = 0 - var/can_unwrench = 0 - var/initialize_directions = 0 - var/pipe_color - var/piping_layer = PIPING_LAYER_DEFAULT - var/pipe_flags = NONE - - var/global/list/iconsetids = list() - var/global/list/pipeimages = list() - - var/image/pipe_vision_img = null - - var/device_type = 0 - var/list/obj/machinery/atmospherics/nodes - - var/construction_type - var/pipe_state //icon_state as a pipe item - var/on = FALSE - -/obj/machinery/atmospherics/examine(mob/user) - . = ..() - if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) - var/mob/living/L = user - if(L.ventcrawler) - . += "Alt-click to crawl through it." - -/obj/machinery/atmospherics/New(loc, process = TRUE, setdir) - if(!isnull(setdir)) - setDir(setdir) - if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE) - normalize_cardinal_directions() - nodes = new(device_type) - if (!armor) - armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) - ..() - if(process) - SSair.atmos_machinery += src - SetInitDirections() - -/obj/machinery/atmospherics/Destroy() - for(var/i in 1 to device_type) - nullifyNode(i) - - SSair.atmos_machinery -= src - - dropContents() - if(pipe_vision_img) - qdel(pipe_vision_img) - - return ..() - //return QDEL_HINT_FINDREFERENCE - -/obj/machinery/atmospherics/proc/destroy_network() - return - -/obj/machinery/atmospherics/proc/build_network() - // Called to build a network from this node - return - -/obj/machinery/atmospherics/proc/nullifyNode(i) - if(nodes[i]) - var/obj/machinery/atmospherics/N = nodes[i] - N.disconnect(src) - nodes[i] = null - -/obj/machinery/atmospherics/proc/getNodeConnects() - var/list/node_connects = list() - node_connects.len = device_type - - for(var/i in 1 to device_type) - for(var/D in GLOB.cardinals) - if(D & GetInitDirections()) - if(D in node_connects) - continue - node_connects[i] = D - break - return node_connects - -/obj/machinery/atmospherics/proc/normalize_cardinal_directions() - switch(dir) - if(SOUTH) - setDir(NORTH) - if(WEST) - setDir(EAST) - -//this is called just after the air controller sets up turfs -/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) - if(!node_connects) //for pipes where order of nodes doesn't matter - node_connects = getNodeConnects() - - for(var/i in 1 to device_type) - for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) - if(can_be_node(target, i)) - nodes[i] = target - break - update_icon() - -/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) - piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer - update_icon() - -/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) - return connection_check(target, piping_layer) - -//Find a connecting /obj/machinery/atmospherics in specified direction -/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) - for(var/obj/machinery/atmospherics/target in get_step(src, direction)) - if(target.initialize_directions & get_dir(target,src)) - if(connection_check(target, prompted_layer)) - return target - -/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) - if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) - if(isnull(given_layer)) - given_layer = piping_layer - if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) - return TRUE - return FALSE - -/obj/machinery/atmospherics/proc/pipeline_expansion() - return nodes - -/obj/machinery/atmospherics/proc/SetInitDirections() - return - -/obj/machinery/atmospherics/proc/GetInitDirections() - return initialize_directions - -/obj/machinery/atmospherics/proc/returnPipenet() - return - -/obj/machinery/atmospherics/proc/returnPipenetAir() - return - -/obj/machinery/atmospherics/proc/setPipenet() - return - -/obj/machinery/atmospherics/proc/replacePipenet() - return - -/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) - if(istype(reference, /obj/machinery/atmospherics/pipe)) - var/obj/machinery/atmospherics/pipe/P = reference - P.destroy_network() - nodes[nodes.Find(reference)] = null - update_icon() - -/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/pipe)) //lets you autodrop - var/obj/item/pipe/pipe = W - if(user.dropItemToGround(pipe)) - pipe.setPipingLayer(piping_layer) //align it with us - return TRUE - else - return ..() - -/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) - if(!can_unwrench(user)) - return ..() - - var/turf/T = get_turf(src) - if (level==1 && isturf(T) && T.intact) - to_chat(user, "You must remove the plating first!") - return TRUE - - var/datum/gas_mixture/int_air = return_air() - var/datum/gas_mixture/env_air = loc.return_air() - add_fingerprint(user) - - var/unsafe_wrenching = FALSE - var/internal_pressure = int_air.return_pressure()-env_air.return_pressure() - - to_chat(user, "You begin to unfasten \the [src]...") - - if (internal_pressure > 2*ONE_ATMOSPHERE) - to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?") - unsafe_wrenching = TRUE //Oh dear oh dear - - if(I.use_tool(src, user, 20, volume=50)) - user.visible_message( \ - "[user] unfastens \the [src].", \ - "You unfasten \the [src].", \ - "You hear ratchet.") - investigate_log("was REMOVED by [key_name(usr)]", INVESTIGATE_ATMOS) - - //You unwrenched a pipe full of pressure? Let's splat you into the wall, silly. - if(unsafe_wrenching) - unsafe_pressure_release(user, internal_pressure) - deconstruct(TRUE) - return TRUE - -/obj/machinery/atmospherics/proc/can_unwrench(mob/user) - return can_unwrench - -// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure. -/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) - if(!user) - return - if(!pressures) - var/datum/gas_mixture/int_air = return_air() - var/datum/gas_mixture/env_air = loc.return_air() - pressures = int_air.return_pressure() - env_air.return_pressure() - - user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") - - // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir - // otherwise, edge_target_turf uses a random cardinal direction - // range is pressures / 250 - // speed is pressures / 1250 - user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) - -/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) - if(!(flags_1 & NODECONSTRUCT_1)) - if(can_unwrench) - var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) - stored.setPipingLayer(piping_layer) - if(!disassembled) - stored.obj_integrity = stored.max_integrity * 0.5 - transfer_fingerprints_to(stored) - ..() - -/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) - - //Add identifiers for the iconset - if(iconsetids[iconset] == null) - iconsetids[iconset] = num2text(iconsetids.len + 1) - - //Generate a unique identifier for this image combination - var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" - - if((!(. = pipeimages[identifier]))) - var/image/pipe_overlay - pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) - pipe_overlay.color = col - PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) - -/obj/machinery/atmospherics/on_construction(obj_color, set_layer) - if(can_unwrench) - add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) - pipe_color = obj_color - setPipingLayer(set_layer) - var/turf/T = get_turf(src) - level = T.intact ? 2 : 1 - atmosinit() - var/list/nodes = pipeline_expansion() - for(var/obj/machinery/atmospherics/A in nodes) - A.atmosinit() - A.addMember(src) - build_network() - -/obj/machinery/atmospherics/Entered(atom/movable/AM) - if(istype(AM, /mob/living)) - var/mob/living/L = AM - L.ventcrawl_layer = piping_layer - return ..() - -/obj/machinery/atmospherics/singularity_pull(S, current_size) - if(current_size >= STAGE_FIVE) - deconstruct(FALSE) - return ..() - -#define VENT_SOUND_DELAY 30 - -/obj/machinery/atmospherics/relaymove(mob/living/user, direction) - direction &= initialize_directions - if(!direction || !(direction in GLOB.cardinals)) //cant go this way. - return - - if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug - return - - var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer) - if(target_move) - if(target_move.can_crawl_through()) - if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery)) - user.forceMove(target_move.loc) //handle entering and so on. - user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") - else - var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() - if(pipenetdiff.len) - user.update_pipe_vision(target_move) - user.forceMove(target_move) - user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement - if(world.time - user.last_played_vent > VENT_SOUND_DELAY) - user.last_played_vent = world.time - playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3) - else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent - user.forceMove(loc) - user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") - - user.canmove = FALSE - addtimer(VARSET_CALLBACK(user, canmove, TRUE), 1) - - -/obj/machinery/atmospherics/AltClick(mob/living/L) - if(is_type_in_list(src, GLOB.ventcrawl_machinery)) - L.handle_ventcrawl(src) - return - ..() - - -/obj/machinery/atmospherics/proc/can_crawl_through() - return TRUE - -/obj/machinery/atmospherics/proc/returnPipenets() - return list() - -/obj/machinery/atmospherics/update_remote_sight(mob/user) - user.sight |= (SEE_TURFS|BLIND) - -//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. -/obj/machinery/atmospherics/proc/can_see_pipes() - return TRUE - -/obj/machinery/atmospherics/proc/update_layer() +// Quick overview: +// +// Pipes combine to form pipelines +// Pipelines and other atmospheric objects combine to form pipe_networks +// Note: A single pipe_network represents a completely open space +// +// Pipes -> Pipelines +// Pipelines + Other Objects -> Pipe network + +#define PIPE_VISIBLE_LEVEL 2 +#define PIPE_HIDDEN_LEVEL 1 + +/obj/machinery/atmospherics + anchored = TRUE + move_resist = INFINITY //Moving a connected machine without actually doing the normal (dis)connection things will probably cause a LOT of issues. + idle_power_usage = 0 + active_power_usage = 0 + power_channel = ENVIRON + layer = GAS_PIPE_HIDDEN_LAYER //under wires + resistance_flags = FIRE_PROOF + max_integrity = 200 + obj_flags = CAN_BE_HIT | ON_BLUEPRINTS + var/nodealert = 0 + var/can_unwrench = 0 + var/initialize_directions = 0 + var/pipe_color + var/piping_layer = PIPING_LAYER_DEFAULT + var/pipe_flags = NONE + + var/global/list/iconsetids = list() + var/global/list/pipeimages = list() + + var/image/pipe_vision_img = null + + var/device_type = 0 + var/list/obj/machinery/atmospherics/nodes + + var/construction_type + var/pipe_state //icon_state as a pipe item + var/on = FALSE + +/obj/machinery/atmospherics/examine(mob/user) + . = ..() + if(is_type_in_list(src, GLOB.ventcrawl_machinery) && isliving(user)) + var/mob/living/L = user + if(L.ventcrawler) + . += "Alt-click to crawl through it." + +/obj/machinery/atmospherics/New(loc, process = TRUE, setdir) + if(!isnull(setdir)) + setDir(setdir) + if(pipe_flags & PIPING_CARDINAL_AUTONORMALIZE) + normalize_cardinal_directions() + nodes = new(device_type) + if (!armor) + armor = list("melee" = 25, "bullet" = 10, "laser" = 10, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 70) + ..() + if(process) + SSair.atmos_machinery += src + SetInitDirections() + +/obj/machinery/atmospherics/Destroy() + for(var/i in 1 to device_type) + nullifyNode(i) + + SSair.atmos_machinery -= src + + dropContents() + if(pipe_vision_img) + qdel(pipe_vision_img) + + return ..() + //return QDEL_HINT_FINDREFERENCE + +/obj/machinery/atmospherics/proc/destroy_network() + return + +/obj/machinery/atmospherics/proc/build_network() + // Called to build a network from this node + return + +/obj/machinery/atmospherics/proc/nullifyNode(i) + if(nodes[i]) + var/obj/machinery/atmospherics/N = nodes[i] + N.disconnect(src) + nodes[i] = null + +/obj/machinery/atmospherics/proc/getNodeConnects() + var/list/node_connects = list() + node_connects.len = device_type + + for(var/i in 1 to device_type) + for(var/D in GLOB.cardinals) + if(D & GetInitDirections()) + if(D in node_connects) + continue + node_connects[i] = D + break + return node_connects + +/obj/machinery/atmospherics/proc/normalize_cardinal_directions() + switch(dir) + if(SOUTH) + setDir(NORTH) + if(WEST) + setDir(EAST) + +//this is called just after the air controller sets up turfs +/obj/machinery/atmospherics/proc/atmosinit(list/node_connects) + if(!node_connects) //for pipes where order of nodes doesn't matter + node_connects = getNodeConnects() + + for(var/i in 1 to device_type) + for(var/obj/machinery/atmospherics/target in get_step(src,node_connects[i])) + if(can_be_node(target, i)) + nodes[i] = target + break + update_icon() + +/obj/machinery/atmospherics/proc/setPipingLayer(new_layer) + piping_layer = (pipe_flags & PIPING_DEFAULT_LAYER_ONLY) ? PIPING_LAYER_DEFAULT : new_layer + update_icon() + +/obj/machinery/atmospherics/proc/can_be_node(obj/machinery/atmospherics/target, iteration) + return connection_check(target, piping_layer) + +//Find a connecting /obj/machinery/atmospherics in specified direction +/obj/machinery/atmospherics/proc/findConnecting(direction, prompted_layer) + for(var/obj/machinery/atmospherics/target in get_step(src, direction)) + if(target.initialize_directions & get_dir(target,src)) + if(connection_check(target, prompted_layer)) + return target + +/obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) + if(isConnectable(target, given_layer) && target.isConnectable(src, given_layer) && (target.initialize_directions & get_dir(target,src))) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/isConnectable(obj/machinery/atmospherics/target, given_layer) + if(isnull(given_layer)) + given_layer = piping_layer + if((target.piping_layer == given_layer) || (target.pipe_flags & PIPING_ALL_LAYER)) + return TRUE + return FALSE + +/obj/machinery/atmospherics/proc/pipeline_expansion() + return nodes + +/obj/machinery/atmospherics/proc/SetInitDirections() + return + +/obj/machinery/atmospherics/proc/GetInitDirections() + return initialize_directions + +/obj/machinery/atmospherics/proc/returnPipenet() + return + +/obj/machinery/atmospherics/proc/returnPipenetAir() + return + +/obj/machinery/atmospherics/proc/setPipenet() + return + +/obj/machinery/atmospherics/proc/replacePipenet() + return + +/obj/machinery/atmospherics/proc/disconnect(obj/machinery/atmospherics/reference) + if(istype(reference, /obj/machinery/atmospherics/pipe)) + var/obj/machinery/atmospherics/pipe/P = reference + P.destroy_network() + nodes[nodes.Find(reference)] = null + update_icon() + +/obj/machinery/atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/pipe)) //lets you autodrop + var/obj/item/pipe/pipe = W + if(user.dropItemToGround(pipe)) + pipe.setPipingLayer(piping_layer) //align it with us + return TRUE + else + return ..() + +/obj/machinery/atmospherics/wrench_act(mob/living/user, obj/item/I) + if(!can_unwrench(user)) + return ..() + + var/turf/T = get_turf(src) + if (level==1 && isturf(T) && T.intact) + to_chat(user, "You must remove the plating first!") + return TRUE + + var/datum/gas_mixture/int_air = return_air() + var/datum/gas_mixture/env_air = loc.return_air() + add_fingerprint(user) + + var/unsafe_wrenching = FALSE + var/internal_pressure = int_air.return_pressure()-env_air.return_pressure() + + to_chat(user, "You begin to unfasten \the [src]...") + + if (internal_pressure > 2*ONE_ATMOSPHERE) + to_chat(user, "As you begin unwrenching \the [src] a gush of air blows in your face... maybe you should reconsider?") + unsafe_wrenching = TRUE //Oh dear oh dear + + if(I.use_tool(src, user, 20, volume=50)) + user.visible_message( \ + "[user] unfastens \the [src].", \ + "You unfasten \the [src].", \ + "You hear ratchet.") + investigate_log("was REMOVED by [key_name(usr)]", INVESTIGATE_ATMOS) + + //You unwrenched a pipe full of pressure? Let's splat you into the wall, silly. + if(unsafe_wrenching) + unsafe_pressure_release(user, internal_pressure) + deconstruct(TRUE) + return TRUE + +/obj/machinery/atmospherics/proc/can_unwrench(mob/user) + return can_unwrench + +// Throws the user when they unwrench a pipe with a major difference between the internal and environmental pressure. +/obj/machinery/atmospherics/proc/unsafe_pressure_release(mob/user, pressures = null) + if(!user) + return + if(!pressures) + var/datum/gas_mixture/int_air = return_air() + var/datum/gas_mixture/env_air = loc.return_air() + pressures = int_air.return_pressure() - env_air.return_pressure() + + user.visible_message("[user] is sent flying by pressure!","The pressure sends you flying!") + + // if get_dir(src, user) is not 0, target is the edge_target_turf on that dir + // otherwise, edge_target_turf uses a random cardinal direction + // range is pressures / 250 + // speed is pressures / 1250 + user.throw_at(get_edge_target_turf(user, get_dir(src, user) || pick(GLOB.cardinals)), pressures / 250, pressures / 1250) + +/obj/machinery/atmospherics/deconstruct(disassembled = TRUE) + if(!(flags_1 & NODECONSTRUCT_1)) + if(can_unwrench) + var/obj/item/pipe/stored = new construction_type(loc, null, dir, src) + stored.setPipingLayer(piping_layer) + if(!disassembled) + stored.obj_integrity = stored.max_integrity * 0.5 + transfer_fingerprints_to(stored) + ..() + +/obj/machinery/atmospherics/proc/getpipeimage(iconset, iconstate, direction, col=rgb(255,255,255), piping_layer=2) + + //Add identifiers for the iconset + if(iconsetids[iconset] == null) + iconsetids[iconset] = num2text(iconsetids.len + 1) + + //Generate a unique identifier for this image combination + var/identifier = iconsetids[iconset] + "_[iconstate]_[direction]_[col]_[piping_layer]" + + if((!(. = pipeimages[identifier]))) + var/image/pipe_overlay + pipe_overlay = . = pipeimages[identifier] = image(iconset, iconstate, dir = direction) + pipe_overlay.color = col + PIPING_LAYER_SHIFT(pipe_overlay, piping_layer) + +/obj/machinery/atmospherics/on_construction(obj_color, set_layer) + if(can_unwrench) + add_atom_colour(obj_color, FIXED_COLOUR_PRIORITY) + pipe_color = obj_color + setPipingLayer(set_layer) + var/turf/T = get_turf(src) + level = T.intact ? 2 : 1 + atmosinit() + var/list/nodes = pipeline_expansion() + for(var/obj/machinery/atmospherics/A in nodes) + A.atmosinit() + A.addMember(src) + build_network() + +/obj/machinery/atmospherics/Entered(atom/movable/AM) + if(istype(AM, /mob/living)) + var/mob/living/L = AM + L.ventcrawl_layer = piping_layer + return ..() + +/obj/machinery/atmospherics/singularity_pull(S, current_size) + if(current_size >= STAGE_FIVE) + deconstruct(FALSE) + return ..() + +#define VENT_SOUND_DELAY 30 + +/obj/machinery/atmospherics/relaymove(mob/living/user, direction) + direction &= initialize_directions + if(!direction || !(direction in GLOB.cardinals)) //cant go this way. + return + + if(user in buckled_mobs)// fixes buckle ventcrawl edgecase fuck bug + return + + var/obj/machinery/atmospherics/target_move = findConnecting(direction, user.ventcrawl_layer) + if(target_move) + if(target_move.can_crawl_through()) + if(is_type_in_typecache(target_move, GLOB.ventcrawl_machinery)) + user.forceMove(target_move.loc) //handle entering and so on. + user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") + else + var/list/pipenetdiff = returnPipenets() ^ target_move.returnPipenets() + if(pipenetdiff.len) + user.update_pipe_vision(target_move) + user.forceMove(target_move) + user.client.eye = target_move //Byond only updates the eye every tick, This smooths out the movement + if(world.time - user.last_played_vent > VENT_SOUND_DELAY) + user.last_played_vent = world.time + playsound(src, 'sound/machines/ventcrawl.ogg', 50, 1, -3) + else if(is_type_in_typecache(src, GLOB.ventcrawl_machinery) && can_crawl_through()) //if we move in a way the pipe can connect, but doesn't - or we're in a vent + user.forceMove(loc) + user.visible_message("You hear something squeezing through the ducts...", "You climb out the ventilation system.") + + user.canmove = FALSE + addtimer(VARSET_CALLBACK(user, canmove, TRUE), 1) + + +/obj/machinery/atmospherics/AltClick(mob/living/L) + if(is_type_in_typecache(src, GLOB.ventcrawl_machinery)) + return L.handle_ventcrawl(src) + return ..() + + +/obj/machinery/atmospherics/proc/can_crawl_through() + return TRUE + +/obj/machinery/atmospherics/proc/returnPipenets() + return list() + +/obj/machinery/atmospherics/update_remote_sight(mob/user) + user.sight |= (SEE_TURFS|BLIND) + +//Used for certain children of obj/machinery/atmospherics to not show pipe vision when mob is inside it. +/obj/machinery/atmospherics/proc/can_see_pipes() + return TRUE + +/obj/machinery/atmospherics/proc/update_layer() layer = initial(layer) + (piping_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_LCHANGE \ No newline at end of file diff --git a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm index 6bd38ace..9e49953d 100644 --- a/code/modules/atmospherics/machinery/components/binary_devices/pump.dm +++ b/code/modules/atmospherics/machinery/components/binary_devices/pump.dm @@ -43,6 +43,7 @@ return ..() /obj/machinery/atmospherics/components/binary/pump/AltClick(mob/user) + . = ..() var/area/A = get_area(src) var/turf/T = get_turf(src) if(user.canUseTopic(src, BE_CLOSE, FALSE,)) @@ -50,6 +51,7 @@ to_chat(user,"You maximize the pressure on the [src].") investigate_log("Pump, [src.name], was maximized by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS) message_admins("Pump, [src.name], was maximized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]") + return TRUE /obj/machinery/atmospherics/components/binary/pump/Destroy() SSradio.remove_object(src,frequency) diff --git a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm index a44be00e..8c7d82d5 100644 --- a/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm +++ b/code/modules/atmospherics/machinery/components/trinary_devices/mixer.dm @@ -31,6 +31,7 @@ return ..() /obj/machinery/atmospherics/components/trinary/mixer/AltClick(mob/user) + . = ..() var/area/A = get_area(src) var/turf/T = get_turf(src) if(user.canUseTopic(src, BE_CLOSE, FALSE,)) @@ -38,6 +39,7 @@ to_chat(user,"You maximize the pressure on the [src].") investigate_log("Mixer, [src.name], was maximized by [key_name(usr)] at [x], [y], [z], [A]", INVESTIGATE_ATMOS) message_admins("Mixer, [src.name], was maximized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]") + return TRUE //node 3 is the outlet, nodes 1 & 2 are intakes diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 78f1af4e..0a54503b 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -1,430 +1,446 @@ -/obj/machinery/atmospherics/components/unary/cryo_cell - name = "cryo cell" - icon = 'icons/obj/cryogenics.dmi' - icon_state = "pod-off" - density = TRUE - max_integrity = 350 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) - layer = ABOVE_WINDOW_LAYER - state_open = FALSE - circuit = /obj/item/circuitboard/machine/cryo_tube - pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY - occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) - - var/autoeject = FALSE - var/volume = 100 - - var/efficiency = 1 - var/sleep_factor = 0.00125 - var/unconscious_factor = 0.001 - var/heat_capacity = 20000 - var/conduction_coefficient = 0.3 - - var/obj/item/reagent_containers/glass/beaker = null - var/reagent_transfer = 0 - - var/obj/item/radio/radio - var/radio_key = /obj/item/encryptionkey/headset_med - var/radio_channel = RADIO_CHANNEL_MEDICAL - - var/running_anim = FALSE - - var/escape_in_progress = FALSE - var/message_cooldown - var/breakout_time = 300 - -/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() - . = ..() - initialize_directions = dir - - radio = new(src) - radio.keyslot = new radio_key - radio.subspace_transmission = TRUE - radio.canhear_range = 0 - radio.recalculateChannels() - -/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() - ..(dir, dir) - -/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() - var/C - for(var/obj/item/stock_parts/matter_bin/M in component_parts) - C += M.rating - - efficiency = initial(efficiency) * C - sleep_factor = initial(sleep_factor) * C - unconscious_factor = initial(unconscious_factor) * C - heat_capacity = initial(heat_capacity) / C - conduction_coefficient = initial(conduction_coefficient) * C - -/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() - QDEL_NULL(radio) - QDEL_NULL(beaker) - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) - ..() - if(beaker) - beaker.ex_act(severity, target) - -/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A) - ..() - if(A == beaker) - beaker = null - updateUsrDialog() - -/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction() - if(beaker) - beaker.forceMove(drop_location()) - beaker = null - -/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon() - cut_overlays() - - if(panel_open) - add_overlay("pod-panel") - - if(state_open) - icon_state = "pod-open" - return - - if(occupant) - var/image/occupant_overlay - - if(ismonkey(occupant)) // Monkey - occupant_overlay = image(CRYOMOBS, "monkey") - else if(isalienadult(occupant)) - if(isalienroyal(occupant)) // Queen and prae - occupant_overlay = image(CRYOMOBS, "alienq") - else if(isalienhunter(occupant)) // Hunter - occupant_overlay = image(CRYOMOBS, "alienh") - else if(isaliensentinel(occupant)) // Sentinel - occupant_overlay = image(CRYOMOBS, "aliens") - else // Drone or other - occupant_overlay = image(CRYOMOBS, "aliend") - - else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube - occupant_overlay = image(occupant.icon, occupant.icon_state) - occupant_overlay.copy_overlays(occupant) - - else - occupant_overlay = image(CRYOMOBS, "generic") - - occupant_overlay.dir = SOUTH - occupant_overlay.pixel_y = 22 - - if(on && !running_anim && is_operational()) - icon_state = "pod-on" - running_anim = TRUE - run_anim(TRUE, occupant_overlay) - else - icon_state = "pod-off" - add_overlay(occupant_overlay) - add_overlay("cover-off") - - else if(on && is_operational()) - icon_state = "pod-on" - add_overlay("cover-on") - else - icon_state = "pod-off" - add_overlay("cover-off") - -/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay) - if(!on || !occupant || !is_operational()) - running_anim = FALSE - return - cut_overlays() - if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24 - anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE - if(anim_up) - occupant_overlay.pixel_y++ - else - occupant_overlay.pixel_y-- - add_overlay(occupant_overlay) - add_overlay("cover-on") - addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE) - -/obj/machinery/atmospherics/components/unary/cryo_cell/process() - ..() - - if(!on) - return - if(!is_operational()) - on = FALSE - update_icon() - return - if(!occupant) - return - - var/mob/living/mob_occupant = occupant - - if(mob_occupant.stat == DEAD) // We don't bother with dead people. - return - - if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. - on = FALSE - update_icon() - playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. - var/msg = "Patient fully restored." - if(autoeject) // Eject if configured. - msg += " Auto ejecting patient now." - open_machine() - radio.talk_into(src, msg, radio_channel) - return - - var/datum/gas_mixture/air1 = airs[1] - - if(air1.gases.len) - if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic. - mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000) - mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000) - if(beaker) - if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic. - beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. - beaker.reagents.reaction(occupant, VAPOR) - air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this - GAS_GARBAGE_COLLECT(air1.gases) - if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). - reagent_transfer = 0 - - return 1 - -/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() - ..() - - if(!on) - return - - var/datum/gas_mixture/air1 = airs[1] - - if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen] < 5) // Turn off if the machine won't work. - on = FALSE - update_icon() - return - - if(occupant) - var/mob/living/mob_occupant = occupant - var/cold_protection = 0 - var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. - - if(ishuman(occupant)) - var/mob/living/carbon/human/H = occupant - cold_protection = H.get_thermal_protection(air1.temperature, TRUE) - - if(abs(temperature_delta) > 1) - var/air_heat_capacity = air1.heat_capacity() - - var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) - - air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB) - mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) - - air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. - GAS_GARBAGE_COLLECT(air1.gases) - -/obj/machinery/atmospherics/components/unary/cryo_cell/power_change() - ..() - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - -/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = 0) - if(!state_open && !panel_open) - on = FALSE - ..() - for(var/mob/M in contents) //only drop mobs - M.forceMove(get_turf(src)) - if(isliving(M)) - var/mob/living/L = M - L.update_canmove() - occupant = null - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) - if((isnull(user) || istype(user)) && state_open && !panel_open) - ..(user) - return occupant - -/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user) - user.changeNext_move(CLICK_CD_BREAKOUT) - user.last_special = world.time + CLICK_CD_BREAKOUT - user.visible_message("You see [user] kicking against the glass of [src]!", \ - "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear a thump from [src].") - if(do_after(user, breakout_time, target = src)) - if(!user || user.stat != CONSCIOUS || user.loc != src ) - return - user.visible_message("[user] successfully broke out of [src]!", \ - "You successfully break out of [src]!") - open_machine() - -/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) - . = ..() - if(occupant) - if(on) - . += "Someone's inside [src]!" - else - . += "You can barely make out a form floating in [src]." - else - . += "[src] seems empty." - -/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user) - if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) - return - if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious()) - close_machine(target) - else - user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") - if (do_after(user, 25, target=target)) - close_machine(target) - -/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/reagent_containers/glass)) - . = 1 //no afterattack - if(beaker) - to_chat(user, "A beaker is already loaded into [src]!") - return - if(!user.transferItemToLoc(I, src)) - return - beaker = I - user.visible_message("[user] places [I] in [src].", \ - "You place [I] in [src].") - var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) - log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") - return - if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ - || default_change_direction_wrench(user, I) \ - || default_pry_open(I) \ - || default_deconstruction_crowbar(I)) - update_icon() - return - else if(istype(I, /obj/item/screwdriver)) - to_chat(user, "You can't access the maintenance panel while the pod is " \ - + (on ? "active" : (occupant ? "full" : "open")) + ".") - return - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ - datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) - ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) - if(!ui) - ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state) - ui.open() - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data() - var/list/data = list() - data["isOperating"] = on - data["hasOccupant"] = occupant ? TRUE : FALSE - data["isOpen"] = state_open - data["autoEject"] = autoeject - - data["occupant"] = list() - if(occupant) - var/mob/living/mob_occupant = occupant - data["occupant"]["name"] = mob_occupant.name - switch(mob_occupant.stat) - if(CONSCIOUS) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "good" - if(SOFT_CRIT) - data["occupant"]["stat"] = "Conscious" - data["occupant"]["statstate"] = "average" - if(UNCONSCIOUS) - data["occupant"]["stat"] = "Unconscious" - data["occupant"]["statstate"] = "average" - if(DEAD) - data["occupant"]["stat"] = "Dead" - data["occupant"]["statstate"] = "bad" - data["occupant"]["health"] = round(mob_occupant.health, 1) - data["occupant"]["maxHealth"] = mob_occupant.maxHealth - data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD - data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1) - data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1) - data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1) - data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1) - data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1) - if(mob_occupant.bodytemperature < TCRYO) - data["occupant"]["temperaturestatus"] = "good" - else if(mob_occupant.bodytemperature < T0C) - data["occupant"]["temperaturestatus"] = "average" - else - data["occupant"]["temperaturestatus"] = "bad" - - var/datum/gas_mixture/air1 = airs[1] - data["cellTemperature"] = round(air1.temperature, 1) - - data["isBeakerLoaded"] = beaker ? TRUE : FALSE - var/beakerContents = list() - if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) - for(var/datum/reagent/R in beaker.reagents.reagent_list) - beakerContents += list(list("name" = R.name, "volume" = R.volume)) - data["beakerContents"] = beakerContents - return data - -/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params) - if(..()) - return - switch(action) - if("power") - if(on) - on = FALSE - else if(!state_open) - on = TRUE - . = TRUE - if("door") - if(state_open) - close_machine() - else - open_machine() - . = TRUE - if("autoeject") - autoeject = !autoeject - . = TRUE - if("ejectbeaker") - if(beaker) - beaker.forceMove(drop_location()) - if(Adjacent(usr) && !issilicon(usr)) - usr.put_in_hands(beaker) - beaker = null - . = TRUE - update_icon() - -/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) - return // we don't see the pipe network while inside cryo. - -/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through() - return // can't ventcrawl in or out of cryo. - -/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes() - return 0 // you can't see the pipe network when inside a cryo cell. - -/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature() - var/datum/gas_mixture/G = airs[1] - - if(G.total_moles() > 10) - return G.temperature - return ..() - -/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W) - . = ..() - if(.) - SetInitDirections() - var/obj/machinery/atmospherics/node = nodes[1] - if(node) - node.disconnect(src) - nodes[1] = null - nullifyPipenet(parents[1]) - atmosinit() - node = nodes[1] - if(node) - node.atmosinit() - node.addMember(src) - build_network() - -#undef CRYOMOBS +/obj/machinery/atmospherics/components/unary/cryo_cell + name = "cryo cell" + icon = 'icons/obj/cryogenics.dmi' + icon_state = "pod-off" + density = TRUE + max_integrity = 350 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30) + layer = ABOVE_WINDOW_LAYER + state_open = FALSE + circuit = /obj/item/circuitboard/machine/cryo_tube + pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY + occupant_typecache = list(/mob/living/carbon, /mob/living/simple_animal) + + var/autoeject = FALSE + var/volume = 100 + + var/efficiency = 1 + var/sleep_factor = 0.00125 + var/unconscious_factor = 0.001 + var/heat_capacity = 20000 + var/conduction_coefficient = 0.3 + + var/obj/item/reagent_containers/glass/beaker = null + var/reagent_transfer = 0 + + var/obj/item/radio/radio + var/radio_key = /obj/item/encryptionkey/headset_med + var/radio_channel = RADIO_CHANNEL_MEDICAL + + var/running_anim = FALSE + + var/escape_in_progress = FALSE + var/message_cooldown + var/breakout_time = 300 + +/obj/machinery/atmospherics/components/unary/cryo_cell/Initialize() + . = ..() + initialize_directions = dir + + radio = new(src) + radio.keyslot = new radio_key + radio.subspace_transmission = TRUE + radio.canhear_range = 0 + radio.recalculateChannels() + +/obj/machinery/atmospherics/components/unary/cryo_cell/on_construction() + ..(dir, dir) + +/obj/machinery/atmospherics/components/unary/cryo_cell/RefreshParts() + var/C + for(var/obj/item/stock_parts/matter_bin/M in component_parts) + C += M.rating + + efficiency = initial(efficiency) * C + sleep_factor = initial(sleep_factor) * C + unconscious_factor = initial(unconscious_factor) * C + heat_capacity = initial(heat_capacity) / C + conduction_coefficient = initial(conduction_coefficient) * C + +/obj/machinery/atmospherics/components/unary/cryo_cell/Destroy() + QDEL_NULL(radio) + QDEL_NULL(beaker) + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/contents_explosion(severity, target) + ..() + if(beaker) + beaker.ex_act(severity, target) + +/obj/machinery/atmospherics/components/unary/cryo_cell/handle_atom_del(atom/A) + ..() + if(A == beaker) + beaker = null + updateUsrDialog() + +/obj/machinery/atmospherics/components/unary/cryo_cell/on_deconstruction() + if(beaker) + beaker.forceMove(drop_location()) + beaker = null + +/obj/machinery/atmospherics/components/unary/cryo_cell/update_icon() + cut_overlays() + + if(panel_open) + add_overlay("pod-panel") + + if(state_open) + icon_state = "pod-open" + return + + if(occupant) + var/image/occupant_overlay + + if(ismonkey(occupant)) // Monkey + occupant_overlay = image(CRYOMOBS, "monkey") + else if(isalienadult(occupant)) + if(isalienroyal(occupant)) // Queen and prae + occupant_overlay = image(CRYOMOBS, "alienq") + else if(isalienhunter(occupant)) // Hunter + occupant_overlay = image(CRYOMOBS, "alienh") + else if(isaliensentinel(occupant)) // Sentinel + occupant_overlay = image(CRYOMOBS, "aliens") + else // Drone or other + occupant_overlay = image(CRYOMOBS, "aliend") + + else if(ishuman(occupant) || islarva(occupant) || (isanimal(occupant) && !ismegafauna(occupant))) // Mobs that are smaller than cryotube + occupant_overlay = image(occupant.icon, occupant.icon_state) + occupant_overlay.copy_overlays(occupant) + + else + occupant_overlay = image(CRYOMOBS, "generic") + + occupant_overlay.dir = SOUTH + occupant_overlay.pixel_y = 22 + + if(on && !running_anim && is_operational()) + icon_state = "pod-on" + running_anim = TRUE + run_anim(TRUE, occupant_overlay) + else + icon_state = "pod-off" + add_overlay(occupant_overlay) + add_overlay("cover-off") + + else if(on && is_operational()) + icon_state = "pod-on" + add_overlay("cover-on") + else + icon_state = "pod-off" + add_overlay("cover-off") + +/obj/machinery/atmospherics/components/unary/cryo_cell/proc/run_anim(anim_up, image/occupant_overlay) + if(!on || !occupant || !is_operational()) + running_anim = FALSE + return + cut_overlays() + if(occupant_overlay.pixel_y != 23) // Same effect as occupant_overlay.pixel_y == 22 || occupant_overlay.pixel_y == 24 + anim_up = occupant_overlay.pixel_y == 22 // Same effect as if(occupant_overlay.pixel_y == 22) anim_up = TRUE ; if(occupant_overlay.pixel_y == 24) anim_up = FALSE + if(anim_up) + occupant_overlay.pixel_y++ + else + occupant_overlay.pixel_y-- + add_overlay(occupant_overlay) + add_overlay("cover-on") + addtimer(CALLBACK(src, .proc/run_anim, anim_up, occupant_overlay), 7, TIMER_UNIQUE) + +/obj/machinery/atmospherics/components/unary/cryo_cell/process() + ..() + + if(!on) + return + if(!is_operational()) + on = FALSE + update_icon() + return + if(!occupant) + return + + var/mob/living/mob_occupant = occupant + + if(mob_occupant.stat == DEAD) // We don't bother with dead people. + return + + if(mob_occupant.health >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. + on = FALSE + update_icon() + playsound(src, 'sound/machines/cryo_warning.ogg', volume) // Bug the doctors. + var/msg = "Patient fully restored." + if(autoeject) // Eject if configured. + msg += " Auto ejecting patient now." + open_machine() + radio.talk_into(src, msg, radio_channel) + return + + var/datum/gas_mixture/air1 = airs[1] + + if(air1.gases.len) + if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic. + mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000) + mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000) + if(beaker) + if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic. + beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents. + beaker.reagents.reaction(occupant, VAPOR) + air1.gases[/datum/gas/oxygen] -= max(0,air1.gases[/datum/gas/oxygen] - 2 / efficiency) //Let's use gas for this + GAS_GARBAGE_COLLECT(air1.gases) + if(++reagent_transfer >= 10 * efficiency) // Throttle reagent transfer (higher efficiency will transfer the same amount but consume less from the beaker). + reagent_transfer = 0 + + return 1 + +/obj/machinery/atmospherics/components/unary/cryo_cell/process_atmos() + ..() + + if(!on) + return + + var/datum/gas_mixture/air1 = airs[1] + + if(!nodes[1] || !airs[1] || !air1.gases.len || air1.gases[/datum/gas/oxygen] < 5) // Turn off if the machine won't work. + on = FALSE + update_icon() + return + + if(occupant) + var/mob/living/mob_occupant = occupant + var/cold_protection = 0 + var/temperature_delta = air1.temperature - mob_occupant.bodytemperature // The only semi-realistic thing here: share temperature between the cell and the occupant. + + if(ishuman(occupant)) + var/mob/living/carbon/human/H = occupant + cold_protection = H.get_thermal_protection(air1.temperature, TRUE) + + if(abs(temperature_delta) > 1) + var/air_heat_capacity = air1.heat_capacity() + + var/heat = ((1 - cold_protection) * 0.1 + conduction_coefficient) * temperature_delta * (air_heat_capacity * heat_capacity / (air_heat_capacity + heat_capacity)) + + air1.temperature = max(air1.temperature - heat / air_heat_capacity, TCMB) + mob_occupant.adjust_bodytemperature(heat / heat_capacity, TCMB) + + air1.gases[/datum/gas/oxygen] = max(0,air1.gases[/datum/gas/oxygen] - 0.5 / efficiency) // Magically consume gas? Why not, we run on cryo magic. + GAS_GARBAGE_COLLECT(air1.gases) + +/obj/machinery/atmospherics/components/unary/cryo_cell/power_change() + ..() + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/relaymove(mob/user) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + +/obj/machinery/atmospherics/components/unary/cryo_cell/open_machine(drop = 0) + if(!state_open && !panel_open) + on = FALSE + ..() + for(var/mob/M in contents) //only drop mobs + M.forceMove(get_turf(src)) + if(isliving(M)) + var/mob/living/L = M + L.update_canmove() + occupant = null + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/close_machine(mob/living/carbon/user) + if((isnull(user) || istype(user)) && state_open && !panel_open) + ..(user) + return occupant + +/obj/machinery/atmospherics/components/unary/cryo_cell/container_resist(mob/living/user) + user.changeNext_move(CLICK_CD_BREAKOUT) + user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("You see [user] kicking against the glass of [src]!", \ + "You struggle inside [src], kicking the release with your foot... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear a thump from [src].") + if(do_after(user, breakout_time, target = src)) + if(!user || user.stat != CONSCIOUS || user.loc != src ) + return + user.visible_message("[user] successfully broke out of [src]!", \ + "You successfully break out of [src]!") + open_machine() + +/obj/machinery/atmospherics/components/unary/cryo_cell/examine(mob/user) + . = ..() + if(occupant) + if(on) + . += "Someone's inside [src]!" + else + . += "You can barely make out a form floating in [src]." + else + . += "[src] seems empty." + +/obj/machinery/atmospherics/components/unary/cryo_cell/MouseDrop_T(mob/target, mob/user) + if(user.stat || user.lying || !Adjacent(user) || !user.Adjacent(target) || !iscarbon(target) || !user.IsAdvancedToolUser()) + return + if (target.IsKnockdown() || target.IsStun() || target.IsSleeping() || target.IsUnconscious()) + close_machine(target) + else + user.visible_message("[user] starts shoving [target] inside [src].", "You start shoving [target] inside [src].") + if (do_after(user, 25, target=target)) + close_machine(target) + +/obj/machinery/atmospherics/components/unary/cryo_cell/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/reagent_containers/glass)) + . = 1 //no afterattack + if(beaker) + to_chat(user, "A beaker is already loaded into [src]!") + return + if(!user.transferItemToLoc(I, src)) + return + beaker = I + user.visible_message("[user] places [I] in [src].", \ + "You place [I] in [src].") + var/reagentlist = pretty_string_from_reagent_list(I.reagents.reagent_list) + log_game("[key_name(user)] added an [I] to cryo containing [reagentlist]") + return + if(!on && !occupant && !state_open && (default_deconstruction_screwdriver(user, "pod-off", "pod-off", I)) \ + || default_change_direction_wrench(user, I) \ + || default_pry_open(I) \ + || default_deconstruction_crowbar(I)) + update_icon() + return + else if(istype(I, /obj/item/screwdriver)) + to_chat(user, "You can't access the maintenance panel while the pod is " \ + + (on ? "active" : (occupant ? "full" : "open")) + ".") + return + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, \ + datum/tgui/master_ui = null, datum/ui_state/state = GLOB.notcontained_state) + ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open) + if(!ui) + ui = new(user, src, ui_key, "cryo", name, 400, 550, master_ui, state) + ui.open() + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_data() + var/list/data = list() + data["isOperating"] = on + data["hasOccupant"] = occupant ? TRUE : FALSE + data["isOpen"] = state_open + data["autoEject"] = autoeject + + data["occupant"] = list() + if(occupant) + var/mob/living/mob_occupant = occupant + data["occupant"]["name"] = mob_occupant.name + switch(mob_occupant.stat) + if(CONSCIOUS) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "good" + if(SOFT_CRIT) + data["occupant"]["stat"] = "Conscious" + data["occupant"]["statstate"] = "average" + if(UNCONSCIOUS) + data["occupant"]["stat"] = "Unconscious" + data["occupant"]["statstate"] = "average" + if(DEAD) + data["occupant"]["stat"] = "Dead" + data["occupant"]["statstate"] = "bad" + data["occupant"]["health"] = round(mob_occupant.health, 1) + data["occupant"]["maxHealth"] = mob_occupant.maxHealth + data["occupant"]["minHealth"] = HEALTH_THRESHOLD_DEAD + data["occupant"]["bruteLoss"] = round(mob_occupant.getBruteLoss(), 1) + data["occupant"]["oxyLoss"] = round(mob_occupant.getOxyLoss(), 1) + data["occupant"]["toxLoss"] = round(mob_occupant.getToxLoss(), 1) + data["occupant"]["fireLoss"] = round(mob_occupant.getFireLoss(), 1) + data["occupant"]["bodyTemperature"] = round(mob_occupant.bodytemperature, 1) + if(mob_occupant.bodytemperature < TCRYO) + data["occupant"]["temperaturestatus"] = "good" + else if(mob_occupant.bodytemperature < T0C) + data["occupant"]["temperaturestatus"] = "average" + else + data["occupant"]["temperaturestatus"] = "bad" + + var/datum/gas_mixture/air1 = airs[1] + data["cellTemperature"] = round(air1.temperature, 1) + + data["isBeakerLoaded"] = beaker ? TRUE : FALSE + var/beakerContents = list() + if(beaker && beaker.reagents && beaker.reagents.reagent_list.len) + for(var/datum/reagent/R in beaker.reagents.reagent_list) + beakerContents += list(list("name" = R.name, "volume" = R.volume)) + data["beakerContents"] = beakerContents + return data + +/obj/machinery/atmospherics/components/unary/cryo_cell/ui_act(action, params) + if(..()) + return + switch(action) + if("power") + if(on) + on = FALSE + else if(!state_open) + on = TRUE + . = TRUE + if("door") + if(state_open) + close_machine() + else + open_machine() + . = TRUE + if("autoeject") + autoeject = !autoeject + . = TRUE + if("ejectbeaker") + if(beaker) + beaker.forceMove(drop_location()) + if(Adjacent(usr) && !issilicon(usr)) + usr.put_in_hands(beaker) + beaker = null + . = TRUE + update_icon() + +/obj/machinery/atmospherics/components/unary/cryo_cell/CtrlClick(mob/user) + if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK) && !state_open) + on = !on + update_icon() + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/AltClick(mob/user) + . = ..() + if(user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + if(state_open) + close_machine() + else + open_machine() + update_icon() + return TRUE + +/obj/machinery/atmospherics/components/unary/cryo_cell/update_remote_sight(mob/living/user) + return // we don't see the pipe network while inside cryo. + +/obj/machinery/atmospherics/components/unary/cryo_cell/get_remote_view_fullscreens(mob/user) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/machinery/atmospherics/components/unary/cryo_cell/can_crawl_through() + return // can't ventcrawl in or out of cryo. + +/obj/machinery/atmospherics/components/unary/cryo_cell/can_see_pipes() + return 0 // you can't see the pipe network when inside a cryo cell. + +/obj/machinery/atmospherics/components/unary/cryo_cell/return_temperature() + var/datum/gas_mixture/G = airs[1] + + if(G.total_moles() > 10) + return G.temperature + return ..() + +/obj/machinery/atmospherics/components/unary/cryo_cell/default_change_direction_wrench(mob/user, obj/item/wrench/W) + . = ..() + if(.) + SetInitDirections() + var/obj/machinery/atmospherics/node = nodes[1] + if(node) + node.disconnect(src) + nodes[1] = null + nullifyPipenet(parents[1]) + atmosinit() + node = nodes[1] + if(node) + node.atmosinit() + node.addMember(src) + build_network() + +#undef CRYOMOBS diff --git a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm index fd65ef66..c69c4c47 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/thermomachine.dm @@ -217,13 +217,15 @@ min_temperature = max(T0C - (initial(min_temperature) + L * 15), TCMB) //73.15K with T1 stock parts /obj/machinery/atmospherics/components/unary/thermomachine/freezer/AltClick(mob/living/user) + . = ..() var/area/A = get_area(src) var/turf/T = get_turf(src) if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return - target_temperature = min_temperature + target_temperature = min_temperature investigate_log("was set to [target_temperature] K by [key_name(usr)]", INVESTIGATE_ATMOS) message_admins("[src.name] was minimized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]") + return TRUE /obj/machinery/atmospherics/components/unary/thermomachine/heater name = "heater" @@ -247,6 +249,7 @@ max_temperature = T20C + (initial(max_temperature) * L) //573.15K with T1 stock parts /obj/machinery/atmospherics/components/unary/thermomachine/heater/AltClick(mob/living/user) + . = ..() var/area/A = get_area(src) var/turf/T = get_turf(src) if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) @@ -254,3 +257,4 @@ target_temperature = max_temperature investigate_log("was set to [target_temperature] K by [key_name(usr)]", INVESTIGATE_ATMOS) message_admins("[src.name] was maximized by [ADMIN_LOOKUPFLW(usr)] at [ADMIN_COORDJMP(T)], [A]") + return TRUE diff --git a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm index 7e313fd7..b47b6b42 100644 --- a/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm +++ b/code/modules/atmospherics/machinery/portable/portable_atmospherics.dm @@ -1,154 +1,156 @@ -/obj/machinery/portable_atmospherics - name = "portable_atmospherics" - icon = 'icons/obj/atmos.dmi' - use_power = NO_POWER_USE - max_integrity = 250 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) - anchored = FALSE - - var/datum/gas_mixture/air_contents - var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port - var/obj/item/tank/holding - - var/volume = 0 - - var/maximum_pressure = 90 * ONE_ATMOSPHERE - -/obj/machinery/portable_atmospherics/New() - ..() - SSair.atmos_machinery += src - - air_contents = new - air_contents.volume = volume - air_contents.temperature = T20C - - return 1 - -/obj/machinery/portable_atmospherics/Destroy() - SSair.atmos_machinery -= src - - disconnect() - qdel(air_contents) - air_contents = null - - return ..() - -/obj/machinery/portable_atmospherics/process_atmos() - if(!connected_port) // Pipe network handles reactions if connected. - air_contents.react(src) - else - update_icon() - -/obj/machinery/portable_atmospherics/return_air() - return air_contents - -/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) - //Make sure not already connected to something else - if(connected_port || !new_port || new_port.connected_device) - return FALSE - - //Make sure are close enough for a valid connection - if(new_port.loc != get_turf(src)) - return FALSE - - //Perform the connection - connected_port = new_port - connected_port.connected_device = src - var/datum/pipeline/connected_port_parent = connected_port.parents[1] - connected_port_parent.reconcile_air() - - anchored = TRUE //Prevent movement - pixel_x = new_port.pixel_x - pixel_y = new_port.pixel_y - return TRUE - -/obj/machinery/portable_atmospherics/Move() - . = ..() - if(.) - disconnect() - -/obj/machinery/portable_atmospherics/proc/disconnect() - if(!connected_port) - return FALSE - anchored = FALSE - connected_port.connected_device = null - connected_port = null - pixel_x = 0 - pixel_y = 0 - return TRUE - -/obj/machinery/portable_atmospherics/portableConnectorReturnAir() - return air_contents - -/obj/machinery/portable_atmospherics/AltClick(mob/living/user) - if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user))) - return - if(holding) - to_chat(user, "You remove [holding] from [src].") - replace_tank(user, TRUE) - -/obj/machinery/portable_atmospherics/examine(mob/user) - . = ..() - if(holding) - . += "\The [src] contains [holding]. Alt-click [src] to remove it." - . += "Click [src] with another gas tank to hot swap [holding]." - -/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) - if(holding) - holding.forceMove(drop_location()) - if(Adjacent(user) && !issilicon(user)) - user.put_in_hands(holding) - if(new_tank) - holding = new_tank - else - holding = null - update_icon() - return TRUE - -/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) - if(istype(W, /obj/item/tank)) - if(!(stat & BROKEN)) - var/obj/item/tank/T = W - if(!user.transferItemToLoc(T, src)) - return - to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") - replace_tank(user, FALSE, T) - update_icon() - else if(istype(W, /obj/item/wrench)) - if(!(stat & BROKEN)) - if(connected_port) - disconnect() - W.play_tool_sound(src) - user.visible_message( \ - "[user] disconnects [src].", \ - "You unfasten [src] from the port.", \ - "You hear a ratchet.") - update_icon() - return - else - var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc - if(!possible_port) - to_chat(user, "Nothing happens.") - return - if(!connect(possible_port)) - to_chat(user, "[name] failed to connect to the port.") - return - W.play_tool_sound(src) - user.visible_message( \ - "[user] connects [src].", \ - "You fasten [src] to the port.", \ - "You hear a ratchet.") - update_icon() - else - return ..() - -/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I) - atmosanalyzer_scan(air_contents, user, src) - -/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) - if(I.force < 10 && !(stat & BROKEN)) - take_damage(0) - else - investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS) - add_fingerprint(user) - ..() +/obj/machinery/portable_atmospherics + name = "portable_atmospherics" + icon = 'icons/obj/atmos.dmi' + use_power = NO_POWER_USE + max_integrity = 250 + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 60, "acid" = 30) + anchored = FALSE + + var/datum/gas_mixture/air_contents + var/obj/machinery/atmospherics/components/unary/portables_connector/connected_port + var/obj/item/tank/holding + + var/volume = 0 + + var/maximum_pressure = 90 * ONE_ATMOSPHERE + +/obj/machinery/portable_atmospherics/New() + ..() + SSair.atmos_machinery += src + + air_contents = new + air_contents.volume = volume + air_contents.temperature = T20C + + return 1 + +/obj/machinery/portable_atmospherics/Destroy() + SSair.atmos_machinery -= src + + disconnect() + qdel(air_contents) + air_contents = null + + return ..() + +/obj/machinery/portable_atmospherics/process_atmos() + if(!connected_port) // Pipe network handles reactions if connected. + air_contents.react(src) + else + update_icon() + +/obj/machinery/portable_atmospherics/return_air() + return air_contents + +/obj/machinery/portable_atmospherics/proc/connect(obj/machinery/atmospherics/components/unary/portables_connector/new_port) + //Make sure not already connected to something else + if(connected_port || !new_port || new_port.connected_device) + return FALSE + + //Make sure are close enough for a valid connection + if(new_port.loc != get_turf(src)) + return FALSE + + //Perform the connection + connected_port = new_port + connected_port.connected_device = src + var/datum/pipeline/connected_port_parent = connected_port.parents[1] + connected_port_parent.reconcile_air() + + anchored = TRUE //Prevent movement + pixel_x = new_port.pixel_x + pixel_y = new_port.pixel_y + return TRUE + +/obj/machinery/portable_atmospherics/Move() + . = ..() + if(.) + disconnect() + +/obj/machinery/portable_atmospherics/proc/disconnect() + if(!connected_port) + return FALSE + anchored = FALSE + connected_port.connected_device = null + connected_port = null + pixel_x = 0 + pixel_y = 0 + return TRUE + +/obj/machinery/portable_atmospherics/portableConnectorReturnAir() + return air_contents + +/obj/machinery/portable_atmospherics/AltClick(mob/living/user) + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, !ismonkey(user))) + return + if(holding) + to_chat(user, "You remove [holding] from [src].") + replace_tank(user, TRUE) + return TRUE + +/obj/machinery/portable_atmospherics/examine(mob/user) + . = ..() + if(holding) + . += "\The [src] contains [holding]. Alt-click [src] to remove it." + . += "Click [src] with another gas tank to hot swap [holding]." + +/obj/machinery/portable_atmospherics/proc/replace_tank(mob/living/user, close_valve, obj/item/tank/new_tank) + if(holding) + holding.forceMove(drop_location()) + if(Adjacent(user) && !issilicon(user)) + user.put_in_hands(holding) + if(new_tank) + holding = new_tank + else + holding = null + update_icon() + return TRUE + +/obj/machinery/portable_atmospherics/attackby(obj/item/W, mob/user, params) + if(istype(W, /obj/item/tank)) + if(!(stat & BROKEN)) + var/obj/item/tank/T = W + if(!user.transferItemToLoc(T, src)) + return + to_chat(user, "[holding ? "In one smooth motion you pop [holding] out of [src]'s connector and replace it with [T]" : "You insert [T] into [src]"].") + replace_tank(user, FALSE, T) + update_icon() + else if(istype(W, /obj/item/wrench)) + if(!(stat & BROKEN)) + if(connected_port) + disconnect() + W.play_tool_sound(src) + user.visible_message( \ + "[user] disconnects [src].", \ + "You unfasten [src] from the port.", \ + "You hear a ratchet.") + update_icon() + return + else + var/obj/machinery/atmospherics/components/unary/portables_connector/possible_port = locate(/obj/machinery/atmospherics/components/unary/portables_connector) in loc + if(!possible_port) + to_chat(user, "Nothing happens.") + return + if(!connect(possible_port)) + to_chat(user, "[name] failed to connect to the port.") + return + W.play_tool_sound(src) + user.visible_message( \ + "[user] connects [src].", \ + "You fasten [src] to the port.", \ + "You hear a ratchet.") + update_icon() + else + return ..() + +/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I) + atmosanalyzer_scan(air_contents, user, src) + +/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user) + if(I.force < 10 && !(stat & BROKEN)) + take_damage(0) + else + investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS) + add_fingerprint(user) + ..() diff --git a/code/modules/cargo/supplypod_beacon.dm b/code/modules/cargo/supplypod_beacon.dm index 915d671f..006f1a80 100644 --- a/code/modules/cargo/supplypod_beacon.dm +++ b/code/modules/cargo/supplypod_beacon.dm @@ -76,12 +76,14 @@ to_chat(user, "[src] linked to [C].") /obj/item/supplypod_beacon/AltClick(mob/user) + . = ..() if (!user.canUseTopic(src, !issilicon(user))) return if (express_console) unlink_console() else to_chat(user, "There is no linked console!") + return TRUE /obj/item/supplypod_beacon/attackby(obj/item/W, mob/user) if(istype(W, /obj/item/pen)) //give a tag that is visible from the linked express console diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm index a28b15f8..cb56e303 100644 --- a/code/modules/clothing/clothing.dm +++ b/code/modules/clothing/clothing.dm @@ -269,16 +269,14 @@ BLIND // can't see anything H.update_suit_sensors() /obj/item/clothing/under/AltClick(mob/user) - if(..()) - return 1 - + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return + if(attached_accessory) + remove_accessory(user) else - if(attached_accessory) - remove_accessory(user) - else - rolldown() + rolldown() + return TRUE /obj/item/clothing/under/verb/jumpsuit_adjust() set name = "Adjust Jumpsuit Style" diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm index aa072f92..5c2bdab3 100644 --- a/code/modules/clothing/glasses/_glasses.dm +++ b/code/modules/clothing/glasses/_glasses.dm @@ -1,443 +1,441 @@ -//Glasses -/obj/item/clothing/glasses - name = "glasses" - icon = 'icons/obj/clothing/glasses.dmi' - w_class = WEIGHT_CLASS_SMALL - flags_cover = GLASSESCOVERSEYES - slot_flags = ITEM_SLOT_EYES - strip_delay = 20 - equip_delay_other = 25 - resistance_flags = NONE - materials = list(MAT_GLASS = 250) - var/vision_flags = 0 - var/darkness_view = 2//Base human is 2 - var/invis_view = SEE_INVISIBLE_LIVING //admin only for now - var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis - var/lighting_alpha - var/list/icon/current = list() //the current hud icons - var/vision_correction = 0 //does wearing these glasses correct some of our vision defects? - var/glass_colour_type //colors your vision when worn - -/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/clothing/glasses/examine(mob/user) - . = ..() - if(glass_colour_type && ishuman(user)) - . += "Alt-click to toggle its colors." - -/obj/item/clothing/glasses/visor_toggling() - ..() - if(visor_vars_to_toggle & VISOR_VISIONFLAGS) - vision_flags ^= initial(vision_flags) - if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) - darkness_view ^= initial(darkness_view) - if(visor_vars_to_toggle & VISOR_INVISVIEW) - invis_view ^= initial(invis_view) - -/obj/item/clothing/glasses/weldingvisortoggle(mob/user) - . = ..() - if(. && user) - user.update_sight() - -//called when thermal glasses are emped. -/obj/item/clothing/glasses/proc/thermal_overload() - if(ishuman(src.loc)) - var/mob/living/carbon/human/H = src.loc - if(!(HAS_TRAIT(H, TRAIT_BLIND))) - if(H.glasses == src) - to_chat(H, "[src] overloads and blinds you!") - H.flash_act(visual = 1) - H.blind_eyes(3) - H.blur_eyes(5) - H.adjust_eye_damage(5) - -/obj/item/clothing/glasses/meson - name = "optical meson scanner" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." - icon_state = "meson" - item_state = "meson" - darkness_view = 2 - vision_flags = SEE_TURFS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - glass_colour_type = /datum/client_colour/glass_colour/lightgreen - -/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!") - return BRUTELOSS - -/obj/item/clothing/glasses/meson/prescription - name = "prescription optical meson scanner" - desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions. This one has prescription lens fitted in." - vision_correction = 1 - -/obj/item/clothing/glasses/meson/night - name = "night vision meson scanner" - desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." - icon_state = "nvgmeson" - item_state = "nvgmeson" - darkness_view = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/meson/gar - name = "gar mesons" - icon_state = "garm" - item_state = "garm" - desc = "Do the impossible, see the invisible!" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/science - name = "science goggles" - desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." - icon_state = "purple" - item_state = "glasses" - clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles - actions_types = list(/datum/action/item_action/toggle_research_scanner) - glass_colour_type = /datum/client_colour/glass_colour/purple - resistance_flags = ACID_PROOF - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) - -/obj/item/clothing/glasses/science/item_action_slot_check(slot) - if(slot == SLOT_GLASSES) - return 1 - -/obj/item/clothing/glasses/night - name = "night vision goggles" - desc = "You can totally see in the dark now!" - icon_state = "night" - item_state = "glasses" - darkness_view = 8 - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - glass_colour_type = /datum/client_colour/glass_colour/green - -/obj/item/clothing/glasses/night/prescription - name = "prescription night vision goggles" - desc = "NVGs but for those with nearsightedness." - vision_correction = 1 - -/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/glasses/eyepatch - name = "eyepatch" - desc = "Yarr." - icon_state = "eyepatch" - item_state = "eyepatch" - -/obj/item/clothing/glasses/monocle - name = "monocle" - desc = "Such a dapper eyepiece!" - icon_state = "monocle" - item_state = "headset" // lol - -/obj/item/clothing/glasses/material - name = "optical material scanner" - desc = "Very confusing glasses." - icon_state = "material" - item_state = "glasses" - vision_flags = SEE_OBJS - glass_colour_type = /datum/client_colour/glass_colour/lightblue - -/obj/item/clothing/glasses/material/mining - name = "optical material scanner" - desc = "Used by miners to detect ores deep within the rock." - icon_state = "material" - item_state = "glasses" - darkness_view = 0 - -/obj/item/clothing/glasses/material/mining/gar - name = "gar material scanner" - icon_state = "garm" - item_state = "garm" - desc = "Do the impossible, see the invisible!" - force = 10 - throwforce = 20 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - vision_correction = 1 - glass_colour_type = /datum/client_colour/glass_colour/lightgreen - -/obj/item/clothing/glasses/regular - name = "prescription glasses" - desc = "Made by Nerd. Co." - icon_state = "glasses" - item_state = "glasses" - vision_correction = 1 //corrects nearsightedness - -/obj/item/clothing/glasses/regular/jamjar - name = "jamjar glasses" - desc = "Also known as Virginity Protectors." - icon_state = "jamjar_glasses" - item_state = "jamjar_glasses" - -/obj/item/clothing/glasses/regular/hipster - name = "prescription glasses" - desc = "Made by Uncool. Co." - icon_state = "hipster_glasses" - item_state = "hipster_glasses" - -//Here lies green glasses, so ugly they died. RIP - -/obj/item/clothing/glasses/sunglasses - name = "sunglasses" - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." - icon_state = "sun" - item_state = "sunglasses" - darkness_view = 1 - flash_protect = 1 - tint = 1 - glass_colour_type = /datum/client_colour/glass_colour/gray - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/glasses/sunglasses/reagent - name = "beer goggles" - desc = "A pair of sunglasses outfitted with apparatus to scan reagents." - clothing_flags = SCAN_REAGENTS - -/obj/item/clothing/glasses/sunglasses/garb - name = "black gar glasses" - desc = "Go beyond impossible and kick reason to the curb!" - icon_state = "garb" - item_state = "garb" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - -/obj/item/clothing/glasses/sunglasses/garb/supergarb - name = "black giga gar glasses" - desc = "Believe in us humans." - icon_state = "supergarb" - item_state = "garb" - force = 12 - throwforce = 12 - -/obj/item/clothing/glasses/garb - name = "plain black gar glasses" - desc = "Go beyond impossible and kick reason to the curb! Doesn't seem to have flash protection and doesn't seem sharp either." - icon_state = "garb" - item_state = "garb" - -/obj/item/clothing/glasses/garb/supergarb - name = "plain black giga gar glasses" - desc = "Believe in us humans. Also doesn't seem to have flash protection and doesn't seem sharp either." - icon_state = "supergarb" - -/obj/item/clothing/glasses/sunglasses/gar - name = "gar glasses" - desc = "Just who the hell do you think I am?!" - icon_state = "gar" - item_state = "gar" - force = 10 - throwforce = 10 - throw_speed = 4 - attack_verb = list("sliced") - hitsound = 'sound/weapons/bladeslice.ogg' - sharpness = IS_SHARP - glass_colour_type = /datum/client_colour/glass_colour/orange - -/obj/item/clothing/glasses/sunglasses/gar/supergar - name = "giga gar glasses" - desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!" - icon_state = "supergar" - item_state = "gar" - force = 12 - throwforce = 12 - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/welding - name = "welding goggles" - desc = "Protects the eyes from welders; approved by the mad scientist association." - icon_state = "welding-g" - item_state = "welding-g" - actions_types = list(/datum/action/item_action/toggle) - materials = list(MAT_METAL = 250) - flash_protect = 2 - tint = 2 - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - flags_cover = GLASSESCOVERSEYES - visor_flags_inv = HIDEEYES - glass_colour_type = /datum/client_colour/glass_colour/gray - -/obj/item/clothing/glasses/welding/attack_self(mob/user) - weldingvisortoggle(user) - - -/obj/item/clothing/glasses/sunglasses/blindfold - name = "blindfold" - desc = "Covers the eyes, preventing sight." - icon_state = "blindfold" - item_state = "blindfold" - flash_protect = 2 - tint = 3 // to make them blind - -/obj/item/clothing/glasses/sunglasses/blindfold/equipped(mob/living/carbon/human/user, slot) - . = ..() - if(slot == SLOT_GLASSES) - user.become_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/sunglasses/blindfold/dropped(mob/living/carbon/human/user) - ..() - user.cure_blind("blindfold_[REF(src)]") - -/obj/item/clothing/glasses/sunglasses/big - desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes." - icon_state = "bigsunglasses" - item_state = "bigsunglasses" - -/obj/item/clothing/glasses/thermal - name = "optical thermal scanner" - desc = "Thermals in the shape of glasses." - icon_state = "thermal" - item_state = "glasses" - vision_flags = SEE_MOBS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE - flash_protect = 0 - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/thermal/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - thermal_overload() - -/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete - name = "chameleon thermals" - desc = "A pair of thermal optic goggles with an onboard chameleon generator." - flash_protect = -1 - - var/datum/action/item_action/chameleon/change/chameleon_action - -/obj/item/clothing/glasses/thermal/syndi/New() - ..() - chameleon_action = new(src) - chameleon_action.chameleon_type = /obj/item/clothing/glasses - chameleon_action.chameleon_name = "Glasses" - chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) - chameleon_action.initialize_disguises() - -/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - chameleon_action.emp_randomise() - -/obj/item/clothing/glasses/thermal/monocle - name = "thermoncle" - desc = "Never before has seeing through walls felt so gentlepersonly." - icon_state = "thermoncle" - flags_1 = null //doesn't protect eyes because it's a monocle, duh - -/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! - var/desk = desc - if(user.gender == MALE) - desc = replacetext(desc, "person", "man") - else if(user.gender == FEMALE) - desc = replacetext(desc, "person", "woman") - . = ..() - desc = desk - -/obj/item/clothing/glasses/thermal/eyepatch - name = "optical thermal eyepatch" - desc = "An eyepatch with built-in thermal optics." - icon_state = "eyepatch" - item_state = "eyepatch" - -/obj/item/clothing/glasses/cold - name = "cold goggles" - desc = "A pair of goggles meant for low temperatures." - icon_state = "cold" - item_state = "cold" - -/obj/item/clothing/glasses/heat - name = "heat goggles" - desc = "A pair of goggles meant for high temperatures." - icon_state = "heat" - item_state = "heat" - -/obj/item/clothing/glasses/orange - name = "orange glasses" - desc = "A sweet pair of orange shades." - icon_state = "orangeglasses" - item_state = "orangeglasses" - glass_colour_type = /datum/client_colour/glass_colour/lightorange - -/obj/item/clothing/glasses/red - name = "red glasses" - desc = "Hey, you're looking good, senpai!" - icon_state = "redglasses" - item_state = "redglasses" - glass_colour_type = /datum/client_colour/glass_colour/red - -/obj/item/clothing/glasses/godeye - name = "eye of god" - desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." - icon_state = "godeye" - item_state = "godeye" - vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS - darkness_view = 8 - clothing_flags = SCAN_REAGENTS - lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - resistance_flags = LAVA_PROOF | FIRE_PROOF - -/obj/item/clothing/glasses/godeye/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) - -/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) - if(istype(W, src) && W != src && W.loc == user) - if(W.icon_state == "godeye") - W.icon_state = "doublegodeye" - W.item_state = "doublegodeye" - W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." - if(iscarbon(user)) - var/mob/living/carbon/C = user - C.update_inv_wear_mask() - else - to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") - qdel(src) - ..() - -/obj/item/clothing/glasses/AltClick(mob/user) - if(glass_colour_type && ishuman(user)) - var/mob/living/carbon/human/H = user - if(H.client) - if(H.client.prefs) - if(src == H.glasses) - H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour - if(H.client.prefs.uses_glasses_colour) - to_chat(H, "You will now see glasses colors.") - else - to_chat(H, "You will no longer see glasses colors.") - H.update_glasses_color(src, 1) - else - return ..() - -/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) - var/old_colour_type = glass_colour_type - if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. - glass_colour_type = new_color_type - if(H && H.glasses == src) - if(old_colour_type) - H.remove_client_colour(old_colour_type) - if(glass_colour_type) - H.update_glasses_color(src, 1) - - -/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) - if(client && client.prefs.uses_glasses_colour && glasses_equipped) - add_client_colour(G.glass_colour_type) - else - remove_client_colour(G.glass_colour_type) +//Glasses +/obj/item/clothing/glasses + name = "glasses" + icon = 'icons/obj/clothing/glasses.dmi' + w_class = WEIGHT_CLASS_SMALL + flags_cover = GLASSESCOVERSEYES + slot_flags = ITEM_SLOT_EYES + strip_delay = 20 + equip_delay_other = 25 + resistance_flags = NONE + materials = list(MAT_GLASS = 250) + var/vision_flags = 0 + var/darkness_view = 2//Base human is 2 + var/invis_view = SEE_INVISIBLE_LIVING //admin only for now + var/invis_override = 0 //Override to allow glasses to set higher than normal see_invis + var/lighting_alpha + var/list/icon/current = list() //the current hud icons + var/vision_correction = 0 //does wearing these glasses correct some of our vision defects? + var/glass_colour_type //colors your vision when worn + +/obj/item/clothing/glasses/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is stabbing \the [src] into [user.p_their()] eyes! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/clothing/glasses/examine(mob/user) + . = ..() + if(glass_colour_type && ishuman(user)) + . += "Alt-click to toggle its colors." + +/obj/item/clothing/glasses/visor_toggling() + ..() + if(visor_vars_to_toggle & VISOR_VISIONFLAGS) + vision_flags ^= initial(vision_flags) + if(visor_vars_to_toggle & VISOR_DARKNESSVIEW) + darkness_view ^= initial(darkness_view) + if(visor_vars_to_toggle & VISOR_INVISVIEW) + invis_view ^= initial(invis_view) + +/obj/item/clothing/glasses/weldingvisortoggle(mob/user) + . = ..() + if(. && user) + user.update_sight() + +//called when thermal glasses are emped. +/obj/item/clothing/glasses/proc/thermal_overload() + if(ishuman(src.loc)) + var/mob/living/carbon/human/H = src.loc + if(!(HAS_TRAIT(H, TRAIT_BLIND))) + if(H.glasses == src) + to_chat(H, "[src] overloads and blinds you!") + H.flash_act(visual = 1) + H.blind_eyes(3) + H.blur_eyes(5) + H.adjust_eye_damage(5) + +/obj/item/clothing/glasses/meson + name = "optical meson scanner" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions." + icon_state = "meson" + item_state = "meson" + darkness_view = 2 + vision_flags = SEE_TURFS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + glass_colour_type = /datum/client_colour/glass_colour/lightgreen + +/obj/item/clothing/glasses/meson/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is putting \the [src] to [user.p_their()] eyes and overloading the brightness! It looks like [user.p_theyre()] trying to commit suicide!") + return BRUTELOSS + +/obj/item/clothing/glasses/meson/prescription + name = "prescription optical meson scanner" + desc = "Used by engineering and mining staff to see basic structural and terrain layouts through walls, regardless of lighting conditions. This one has prescription lens fitted in." + vision_correction = 1 + +/obj/item/clothing/glasses/meson/night + name = "night vision meson scanner" + desc = "An optical meson scanner fitted with an amplified visible light spectrum overlay, providing greater visual clarity in darkness." + icon_state = "nvgmeson" + item_state = "nvgmeson" + darkness_view = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/meson/gar + name = "gar mesons" + icon_state = "garm" + item_state = "garm" + desc = "Do the impossible, see the invisible!" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/science + name = "science goggles" + desc = "A pair of snazzy goggles used to protect against chemical spills. Fitted with an analyzer for scanning items and reagents." + icon_state = "purple" + item_state = "glasses" + clothing_flags = SCAN_REAGENTS //You can see reagents while wearing science goggles + actions_types = list(/datum/action/item_action/toggle_research_scanner) + glass_colour_type = /datum/client_colour/glass_colour/purple + resistance_flags = ACID_PROOF + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 100) + +/obj/item/clothing/glasses/science/item_action_slot_check(slot) + if(slot == SLOT_GLASSES) + return 1 + +/obj/item/clothing/glasses/night + name = "night vision goggles" + desc = "You can totally see in the dark now!" + icon_state = "night" + item_state = "glasses" + darkness_view = 8 + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + glass_colour_type = /datum/client_colour/glass_colour/green + +/obj/item/clothing/glasses/night/prescription + name = "prescription night vision goggles" + desc = "NVGs but for those with nearsightedness." + vision_correction = 1 + +/obj/item/clothing/glasses/science/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is tightening \the [src]'s straps around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/glasses/eyepatch + name = "eyepatch" + desc = "Yarr." + icon_state = "eyepatch" + item_state = "eyepatch" + +/obj/item/clothing/glasses/monocle + name = "monocle" + desc = "Such a dapper eyepiece!" + icon_state = "monocle" + item_state = "headset" // lol + +/obj/item/clothing/glasses/material + name = "optical material scanner" + desc = "Very confusing glasses." + icon_state = "material" + item_state = "glasses" + vision_flags = SEE_OBJS + glass_colour_type = /datum/client_colour/glass_colour/lightblue + +/obj/item/clothing/glasses/material/mining + name = "optical material scanner" + desc = "Used by miners to detect ores deep within the rock." + icon_state = "material" + item_state = "glasses" + darkness_view = 0 + +/obj/item/clothing/glasses/material/mining/gar + name = "gar material scanner" + icon_state = "garm" + item_state = "garm" + desc = "Do the impossible, see the invisible!" + force = 10 + throwforce = 20 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + vision_correction = 1 + glass_colour_type = /datum/client_colour/glass_colour/lightgreen + +/obj/item/clothing/glasses/regular + name = "prescription glasses" + desc = "Made by Nerd. Co." + icon_state = "glasses" + item_state = "glasses" + vision_correction = 1 //corrects nearsightedness + +/obj/item/clothing/glasses/regular/jamjar + name = "jamjar glasses" + desc = "Also known as Virginity Protectors." + icon_state = "jamjar_glasses" + item_state = "jamjar_glasses" + +/obj/item/clothing/glasses/regular/hipster + name = "prescription glasses" + desc = "Made by Uncool. Co." + icon_state = "hipster_glasses" + item_state = "hipster_glasses" + +//Here lies green glasses, so ugly they died. RIP + +/obj/item/clothing/glasses/sunglasses + name = "sunglasses" + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Enhanced shielding blocks flashes." + icon_state = "sun" + item_state = "sunglasses" + darkness_view = 1 + flash_protect = 1 + tint = 1 + glass_colour_type = /datum/client_colour/glass_colour/gray + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/glasses/sunglasses/reagent + name = "beer goggles" + desc = "A pair of sunglasses outfitted with apparatus to scan reagents." + clothing_flags = SCAN_REAGENTS + +/obj/item/clothing/glasses/sunglasses/garb + name = "black gar glasses" + desc = "Go beyond impossible and kick reason to the curb!" + icon_state = "garb" + item_state = "garb" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + +/obj/item/clothing/glasses/sunglasses/garb/supergarb + name = "black giga gar glasses" + desc = "Believe in us humans." + icon_state = "supergarb" + item_state = "garb" + force = 12 + throwforce = 12 + +/obj/item/clothing/glasses/garb + name = "plain black gar glasses" + desc = "Go beyond impossible and kick reason to the curb! Doesn't seem to have flash protection and doesn't seem sharp either." + icon_state = "garb" + item_state = "garb" + +/obj/item/clothing/glasses/garb/supergarb + name = "plain black giga gar glasses" + desc = "Believe in us humans. Also doesn't seem to have flash protection and doesn't seem sharp either." + icon_state = "supergarb" + +/obj/item/clothing/glasses/sunglasses/gar + name = "gar glasses" + desc = "Just who the hell do you think I am?!" + icon_state = "gar" + item_state = "gar" + force = 10 + throwforce = 10 + throw_speed = 4 + attack_verb = list("sliced") + hitsound = 'sound/weapons/bladeslice.ogg' + sharpness = IS_SHARP + glass_colour_type = /datum/client_colour/glass_colour/orange + +/obj/item/clothing/glasses/sunglasses/gar/supergar + name = "giga gar glasses" + desc = "We evolve past the person we were a minute before. Little by little we advance with each turn. That's how a drill works!" + icon_state = "supergar" + item_state = "gar" + force = 12 + throwforce = 12 + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/welding + name = "welding goggles" + desc = "Protects the eyes from welders; approved by the mad scientist association." + icon_state = "welding-g" + item_state = "welding-g" + actions_types = list(/datum/action/item_action/toggle) + materials = list(MAT_METAL = 250) + flash_protect = 2 + tint = 2 + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + flags_cover = GLASSESCOVERSEYES + visor_flags_inv = HIDEEYES + glass_colour_type = /datum/client_colour/glass_colour/gray + +/obj/item/clothing/glasses/welding/attack_self(mob/user) + weldingvisortoggle(user) + + +/obj/item/clothing/glasses/sunglasses/blindfold + name = "blindfold" + desc = "Covers the eyes, preventing sight." + icon_state = "blindfold" + item_state = "blindfold" + flash_protect = 2 + tint = 3 // to make them blind + +/obj/item/clothing/glasses/sunglasses/blindfold/equipped(mob/living/carbon/human/user, slot) + . = ..() + if(slot == SLOT_GLASSES) + user.become_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/sunglasses/blindfold/dropped(mob/living/carbon/human/user) + ..() + user.cure_blind("blindfold_[REF(src)]") + +/obj/item/clothing/glasses/sunglasses/big + desc = "Strangely ancient technology used to help provide rudimentary eye cover. Larger than average enhanced shielding blocks flashes." + icon_state = "bigsunglasses" + item_state = "bigsunglasses" + +/obj/item/clothing/glasses/thermal + name = "optical thermal scanner" + desc = "Thermals in the shape of glasses." + icon_state = "thermal" + item_state = "glasses" + vision_flags = SEE_MOBS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + flash_protect = 0 + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/thermal/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + thermal_overload() + +/obj/item/clothing/glasses/thermal/syndi //These are now a traitor item, concealed as mesons. -Pete + name = "chameleon thermals" + desc = "A pair of thermal optic goggles with an onboard chameleon generator." + flash_protect = -1 + + var/datum/action/item_action/chameleon/change/chameleon_action + +/obj/item/clothing/glasses/thermal/syndi/New() + ..() + chameleon_action = new(src) + chameleon_action.chameleon_type = /obj/item/clothing/glasses + chameleon_action.chameleon_name = "Glasses" + chameleon_action.chameleon_blacklist = typecacheof(/obj/item/clothing/glasses/changeling, only_root_path = TRUE) + chameleon_action.initialize_disguises() + +/obj/item/clothing/glasses/thermal/syndi/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + chameleon_action.emp_randomise() + +/obj/item/clothing/glasses/thermal/monocle + name = "thermoncle" + desc = "Never before has seeing through walls felt so gentlepersonly." + icon_state = "thermoncle" + flags_1 = null //doesn't protect eyes because it's a monocle, duh + +/obj/item/clothing/glasses/thermal/monocle/examine(mob/user) //Different examiners see a different description! + var/desk = desc + if(user.gender == MALE) + desc = replacetext(desc, "person", "man") + else if(user.gender == FEMALE) + desc = replacetext(desc, "person", "woman") + . = ..() + desc = desk + +/obj/item/clothing/glasses/thermal/eyepatch + name = "optical thermal eyepatch" + desc = "An eyepatch with built-in thermal optics." + icon_state = "eyepatch" + item_state = "eyepatch" + +/obj/item/clothing/glasses/cold + name = "cold goggles" + desc = "A pair of goggles meant for low temperatures." + icon_state = "cold" + item_state = "cold" + +/obj/item/clothing/glasses/heat + name = "heat goggles" + desc = "A pair of goggles meant for high temperatures." + icon_state = "heat" + item_state = "heat" + +/obj/item/clothing/glasses/orange + name = "orange glasses" + desc = "A sweet pair of orange shades." + icon_state = "orangeglasses" + item_state = "orangeglasses" + glass_colour_type = /datum/client_colour/glass_colour/lightorange + +/obj/item/clothing/glasses/red + name = "red glasses" + desc = "Hey, you're looking good, senpai!" + icon_state = "redglasses" + item_state = "redglasses" + glass_colour_type = /datum/client_colour/glass_colour/red + +/obj/item/clothing/glasses/godeye + name = "eye of god" + desc = "A strange eye, said to have been torn from an omniscient creature that used to roam the wastes." + icon_state = "godeye" + item_state = "godeye" + vision_flags = SEE_TURFS|SEE_MOBS|SEE_OBJS + darkness_view = 8 + clothing_flags = SCAN_REAGENTS + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + resistance_flags = LAVA_PROOF | FIRE_PROOF + +/obj/item/clothing/glasses/godeye/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, EYE_OF_GOD_TRAIT) + +/obj/item/clothing/glasses/godeye/attackby(obj/item/W as obj, mob/user as mob, params) + if(istype(W, src) && W != src && W.loc == user) + if(W.icon_state == "godeye") + W.icon_state = "doublegodeye" + W.item_state = "doublegodeye" + W.desc = "A pair of strange eyes, said to have been torn from an omniscient creature that used to roam the wastes. There's no real reason to have two, but that isn't stopping you." + if(iscarbon(user)) + var/mob/living/carbon/C = user + C.update_inv_wear_mask() + else + to_chat(user, "The eye winks at you and vanishes into the abyss, you feel really unlucky.") + qdel(src) + ..() + +/obj/item/clothing/glasses/AltClick(mob/user) + . = ..() + if(glass_colour_type && ishuman(user)) + var/mob/living/carbon/human/H = user + if(H.client?.prefs && src == H.glasses) + H.client.prefs.uses_glasses_colour = !H.client.prefs.uses_glasses_colour + if(H.client.prefs.uses_glasses_colour) + to_chat(H, "You will now see glasses colors.") + else + to_chat(H, "You will no longer see glasses colors.") + H.update_glasses_color(src, 1) + return TRUE + +/obj/item/clothing/glasses/proc/change_glass_color(mob/living/carbon/human/H, datum/client_colour/glass_colour/new_color_type) + var/old_colour_type = glass_colour_type + if(!new_color_type || ispath(new_color_type)) //the new glass colour type must be null or a path. + glass_colour_type = new_color_type + if(H && H.glasses == src) + if(old_colour_type) + H.remove_client_colour(old_colour_type) + if(glass_colour_type) + H.update_glasses_color(src, 1) + + +/mob/living/carbon/human/proc/update_glasses_color(obj/item/clothing/glasses/G, glasses_equipped) + if(client && client.prefs.uses_glasses_colour && glasses_equipped) + add_client_colour(G.glass_colour_type) + else + remove_client_colour(G.glass_colour_type) diff --git a/code/modules/clothing/head/hardhat.dm b/code/modules/clothing/head/hardhat.dm index 40d01607..82e5e782 100644 --- a/code/modules/clothing/head/hardhat.dm +++ b/code/modules/clothing/head/hardhat.dm @@ -1,159 +1,161 @@ -/obj/item/clothing/head/hardhat - name = "hard hat" - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight." - icon_state = "hardhat0_yellow" - item_state = "hardhat0_yellow" - var/brightness_on = 4 //luminosity when on - light_color = "#FFCC66" - var/power_on = 0.8 - var/on = FALSE - item_color = "yellow" //Determines used sprites: hardhat[on]_[item_color] and hardhat[on]_[item_color]2 (lying down sprite) - armor = list("melee" = 15, "bullet" = 5, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) - flags_inv = 0 - actions_types = list(/datum/action/item_action/toggle_helmet_light) - resistance_flags = FIRE_PROOF - dynamic_hair_suffix = "+generic" - - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/attack_self(mob/living/user) - toggle_helmet_light(user) - -/obj/item/clothing/head/hardhat/proc/toggle_helmet_light(mob/living/user) - on = !on - if(on) - turn_on(user) - else - turn_off(user) - update_icon() - -/obj/item/clothing/head/hardhat/update_icon() - icon_state = "hardhat[on]_[item_color]" - item_state = "hardhat[on]_[item_color]" - if(ishuman(loc)) - var/mob/living/carbon/human/H = loc - H.update_inv_head() - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon(force = TRUE) - -/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) - set_light(brightness_on, power_on) - -/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) - set_light(0) - -/obj/item/clothing/head/hardhat/orange - icon_state = "hardhat0_orange" - item_state = "hardhat0_orange" - item_color = "orange" - dog_fashion = null - -/obj/item/clothing/head/hardhat/red - icon_state = "hardhat0_red" - item_state = "hardhat0_red" - item_color = "red" - dog_fashion = null - name = "firefighter helmet" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - -/obj/item/clothing/head/hardhat/white - icon_state = "hardhat0_white" - item_state = "hardhat0_white" - item_color = "white" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - dog_fashion = /datum/dog_fashion/head - -/obj/item/clothing/head/hardhat/dblue - icon_state = "hardhat0_dblue" - item_state = "hardhat0_dblue" - item_color = "dblue" - dog_fashion = null - -/obj/item/clothing/head/hardhat/atmos - icon_state = "hardhat0_atmos" - item_state = "hardhat0_atmos" - item_color = "atmos" - dog_fashion = null - name = "atmospheric technician's firefighting helmet" - desc = "A firefighter's helmet, able to keep the user cool in any situation." - clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT - flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT - heat_protection = HEAD - max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - mutantrace_variation = MUTANTRACE_VARIATION - -/obj/item/clothing/head/hardhat/weldhat - name = "welding hard hat" - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield! The bulb seems a little smaller though." - brightness_on = 3 //Needs a little bit of tradeoff - dog_fashion = null - actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) - flash_protect = 2 - tint = 2 - flags_inv = HIDEEYES | HIDEFACE - flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT - visor_flags_inv = HIDEEYES | HIDEFACE - visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH - -/obj/item/clothing/head/hardhat/weldhat/Initialize() - . = ..() - update_icon() - -/obj/item/clothing/head/hardhat/weldhat/attack_self(mob/living/user) - toggle_helmet_light(user) - -/obj/item/clothing/head/hardhat/weldhat/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE)) - toggle_welding_screen(user) - -/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) - if(weldingvisortoggle(user)) - playsound(src, 'sound/mecha/mechmove03.ogg', 30, TRUE) //Visors don't just come from nothing - update_icon() - -/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) - . = ..() - if(!isinhands) - . += mutable_appearance('icons/mob/head.dmi', "weldhelmet") - if(!up) - . += mutable_appearance('icons/mob/head.dmi', "weldvisor") - -/obj/item/clothing/head/hardhat/weldhat/update_icon() - cut_overlays() - if(!up) - add_overlay("weldvisor") - ..() - -/obj/item/clothing/head/hardhat/weldhat/orange - icon_state = "hardhat0_orange" - item_state = "hardhat0_orange" - item_color = "orange" - -/obj/item/clothing/head/hardhat/weldhat/white - desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield!" //This bulb is not smaller - icon_state = "hardhat0_white" - item_state = "hardhat0_white" - brightness_on = 4 //Boss always takes the best stuff - item_color = "white" - clothing_flags = STOPSPRESSUREDAMAGE - heat_protection = HEAD - max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT - cold_protection = HEAD - min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT - -/obj/item/clothing/head/hardhat/weldhat/dblue - icon_state = "hardhat0_dblue" - item_state = "hardhat0_dblue" +/obj/item/clothing/head/hardhat + name = "hard hat" + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight." + icon_state = "hardhat0_yellow" + item_state = "hardhat0_yellow" + var/brightness_on = 4 //luminosity when on + light_color = "#FFCC66" + var/power_on = 0.8 + var/on = FALSE + item_color = "yellow" //Determines used sprites: hardhat[on]_[item_color] and hardhat[on]_[item_color]2 (lying down sprite) + armor = list("melee" = 15, "bullet" = 5, "laser" = 20,"energy" = 10, "bomb" = 20, "bio" = 10, "rad" = 20, "fire" = 100, "acid" = 50) + flags_inv = 0 + actions_types = list(/datum/action/item_action/toggle_helmet_light) + resistance_flags = FIRE_PROOF + dynamic_hair_suffix = "+generic" + + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/attack_self(mob/living/user) + toggle_helmet_light(user) + +/obj/item/clothing/head/hardhat/proc/toggle_helmet_light(mob/living/user) + on = !on + if(on) + turn_on(user) + else + turn_off(user) + update_icon() + +/obj/item/clothing/head/hardhat/update_icon() + icon_state = "hardhat[on]_[item_color]" + item_state = "hardhat[on]_[item_color]" + if(ishuman(loc)) + var/mob/living/carbon/human/H = loc + H.update_inv_head() + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon(force = TRUE) + +/obj/item/clothing/head/hardhat/proc/turn_on(mob/user) + set_light(brightness_on, power_on) + +/obj/item/clothing/head/hardhat/proc/turn_off(mob/user) + set_light(0) + +/obj/item/clothing/head/hardhat/orange + icon_state = "hardhat0_orange" + item_state = "hardhat0_orange" + item_color = "orange" + dog_fashion = null + +/obj/item/clothing/head/hardhat/red + icon_state = "hardhat0_red" + item_state = "hardhat0_red" + item_color = "red" + dog_fashion = null + name = "firefighter helmet" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + +/obj/item/clothing/head/hardhat/white + icon_state = "hardhat0_white" + item_state = "hardhat0_white" + item_color = "white" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + dog_fashion = /datum/dog_fashion/head + +/obj/item/clothing/head/hardhat/dblue + icon_state = "hardhat0_dblue" + item_state = "hardhat0_dblue" + item_color = "dblue" + dog_fashion = null + +/obj/item/clothing/head/hardhat/atmos + icon_state = "hardhat0_atmos" + item_state = "hardhat0_atmos" + item_color = "atmos" + dog_fashion = null + name = "atmospheric technician's firefighting helmet" + desc = "A firefighter's helmet, able to keep the user cool in any situation." + clothing_flags = STOPSPRESSUREDAMAGE | THICKMATERIAL | BLOCK_GAS_SMOKE_EFFECT + flags_inv = HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDEFACIALHAIR|HIDESNOUT + heat_protection = HEAD + max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + mutantrace_variation = MUTANTRACE_VARIATION + +/obj/item/clothing/head/hardhat/weldhat + name = "welding hard hat" + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield! The bulb seems a little smaller though." + brightness_on = 3 //Needs a little bit of tradeoff + dog_fashion = null + actions_types = list(/datum/action/item_action/toggle_helmet_light, /datum/action/item_action/toggle_welding_screen) + flash_protect = 2 + tint = 2 + flags_inv = HIDEEYES | HIDEFACE + flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + visor_vars_to_toggle = VISOR_FLASHPROTECT | VISOR_TINT + visor_flags_inv = HIDEEYES | HIDEFACE + visor_flags_cover = HEADCOVERSEYES | HEADCOVERSMOUTH + +/obj/item/clothing/head/hardhat/weldhat/Initialize() + . = ..() + update_icon() + +/obj/item/clothing/head/hardhat/weldhat/attack_self(mob/living/user) + toggle_helmet_light(user) + +/obj/item/clothing/head/hardhat/weldhat/AltClick(mob/user) + . = ..() + if(user.canUseTopic(src, BE_CLOSE)) + toggle_welding_screen(user) + return TRUE + +/obj/item/clothing/head/hardhat/weldhat/proc/toggle_welding_screen(mob/living/user) + if(weldingvisortoggle(user)) + playsound(src, 'sound/mecha/mechmove03.ogg', 30, TRUE) //Visors don't just come from nothing + update_icon() + +/obj/item/clothing/head/hardhat/weldhat/worn_overlays(isinhands) + . = ..() + if(!isinhands) + . += mutable_appearance('icons/mob/head.dmi', "weldhelmet") + if(!up) + . += mutable_appearance('icons/mob/head.dmi', "weldvisor") + +/obj/item/clothing/head/hardhat/weldhat/update_icon() + cut_overlays() + if(!up) + add_overlay("weldvisor") + ..() + +/obj/item/clothing/head/hardhat/weldhat/orange + icon_state = "hardhat0_orange" + item_state = "hardhat0_orange" + item_color = "orange" + +/obj/item/clothing/head/hardhat/weldhat/white + desc = "A piece of headgear used in dangerous working conditions to protect the head. Comes with a built-in flashlight AND welding shield!" //This bulb is not smaller + icon_state = "hardhat0_white" + item_state = "hardhat0_white" + brightness_on = 4 //Boss always takes the best stuff + item_color = "white" + clothing_flags = STOPSPRESSUREDAMAGE + heat_protection = HEAD + max_heat_protection_temperature = FIRE_HELM_MAX_TEMP_PROTECT + cold_protection = HEAD + min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT + +/obj/item/clothing/head/hardhat/weldhat/dblue + icon_state = "hardhat0_dblue" + item_state = "hardhat0_dblue" item_color = "dblue" \ No newline at end of file diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index fcac7874..b181da1e 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -1,297 +1,297 @@ -//defines the drill hat's yelling setting -#define DRILL_DEFAULT "default" -#define DRILL_SHOUTING "shouting" -#define DRILL_YELLING "yelling" -#define DRILL_CANADIAN "canadian" - -//Chef -/obj/item/clothing/head/chefhat - name = "chef's hat" - item_state = "chef" - icon_state = "chef" - desc = "The commander in chef's head wear." - strip_delay = 10 - equip_delay_other = 10 - dynamic_hair_suffix = "" - dog_fashion = /datum/dog_fashion/head/chef - -/obj/item/clothing/head/chefhat/suicide_act(mob/user) - user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to become a chef.") - user.say("Bork Bork Bork!", forced = "chef hat suicide") - sleep(20) - user.visible_message("[user] climbs into an imaginary oven!") - user.say("BOOORK!", forced = "chef hat suicide") - playsound(user, 'sound/machines/ding.ogg', 50, 1) - return(FIRELOSS) - -//Captain -/obj/item/clothing/head/caphat - name = "captain's hat" - desc = "It's good being the king." - icon_state = "captain" - item_state = "that" - flags_inv = 0 - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/captain - -//Captain: This is no longer space-worthy -/obj/item/clothing/head/caphat/parade - name = "captain's parade cap" - desc = "Worn only by Captains with an abundance of class." - icon_state = "capcap" - - dog_fashion = null - -/obj/item/clothing/head/caphat/beret - name = "captain's beret" - desc = "A beret fit for a leader." - icon_state = "capberet" - dynamic_hair_suffix = "" - - dog_fashion = null - -//Head of Personnel -/obj/item/clothing/head/hopcap - name = "head of personnel's cap" - icon_state = "hopcap" - desc = "The symbol of true bureaucratic micromanagement." - armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - dog_fashion = /datum/dog_fashion/head/hop - -/obj/item/clothing/head/hopcap/beret - name = "head of personnel's beret" - desc = "The symbol of true bureaucratic micromanagement, although in a fancy form." - icon_state = "hopberet" - dynamic_hair_suffix = "" - - dog_fashion = null - -//Chaplain -/obj/item/clothing/head/nun_hood - name = "nun hood" - desc = "Maximum piety in this star system." - icon_state = "nun_hood" - flags_inv = HIDEHAIR - flags_cover = HEADCOVERSEYES - -//Detective -/obj/item/clothing/head/fedora/det_hat - name = "detective's fedora" - desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." - armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) - icon_state = "detective" - var/candy_cooldown = 0 - pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/detective - dog_fashion = /datum/dog_fashion/head/detective - -/obj/item/clothing/head/fedora/det_hat/Initialize() - . = ..() - new /obj/item/reagent_containers/food/drinks/flask/det(src) - -/obj/item/clothing/head/fedora/det_hat/examine(mob/user) - . = ..() - . += "Alt-click to take a candy corn." - -/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) - if(user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - ..() - if(loc == user) - if(candy_cooldown < world.time) - var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) - user.put_in_hands(CC) - to_chat(user, "You slip a candy corn from your hat.") - candy_cooldown = world.time+1200 - else - to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") - - -//Mime -/obj/item/clothing/head/beret - name = "beret" - desc = "A beret, a mime's favorite headwear." - icon_state = "beret" - dog_fashion = /datum/dog_fashion/head/beret - dynamic_hair_suffix = "" - -/obj/item/clothing/head/beret/black - name = "black beret" - desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." - icon_state = "beretblack" - -/obj/item/clothing/head/beret/highlander - desc = "That was white fabric. Was." - dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES - -/obj/item/clothing/head/beret/highlander/Initialize() - . = ..() - ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) - -//Security - -/obj/item/clothing/head/HoS - name = "head of security cap" - desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." - icon_state = "hoscap" - armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) - strip_delay = 80 - dynamic_hair_suffix = "" - -/obj/item/clothing/head/HoS/syndicate - name = "syndicate cap" - desc = "A black cap fit for a high ranking syndicate officer." - -/obj/item/clothing/head/HoS/beret - name = "head of security beret" - desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." - icon_state = "hosberetblack" - -/obj/item/clothing/head/HoS/beret/syndicate - name = "syndicate beret" - desc = "A black beret with thick armor padding inside. Stylish and robust." - -/obj/item/clothing/head/warden - name = "warden's police hat" - desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." - icon_state = "policehelm" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) - strip_delay = 60 - dog_fashion = /datum/dog_fashion/head/warden - -/obj/item/clothing/head/warden/drill - name = "warden's campaign hat" - desc = "A special armored campaign hat with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection. Has the letters 'FMJ' enscribed on its side." - icon_state = "wardendrill" - item_state = "wardendrill" - dog_fashion = null - var/mode = DRILL_DEFAULT - -/obj/item/clothing/head/warden/drill/screwdriver_act(mob/living/carbon/human/user, obj/item/I) - if(..()) - return TRUE - switch(mode) - if(DRILL_DEFAULT) - to_chat(user, "You set the voice circuit to the middle position.") - mode = DRILL_SHOUTING - if(DRILL_SHOUTING) - to_chat(user, "You set the voice circuit to the last position.") - mode = DRILL_YELLING - if(DRILL_YELLING) - to_chat(user, "You set the voice circuit to the first position.") - mode = DRILL_DEFAULT - if(DRILL_CANADIAN) - to_chat(user, "You adjust voice circuit but nothing happens, probably because it's broken.") - return TRUE - -/obj/item/clothing/head/warden/drill/wirecutter_act(mob/living/user, obj/item/I) - if(mode != DRILL_CANADIAN) - to_chat(user, "You broke the voice circuit!") - mode = DRILL_CANADIAN - return TRUE - -/obj/item/clothing/head/warden/drill/equipped(mob/M, slot) - . = ..() - if (slot == SLOT_HEAD) - RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) - else - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/warden/drill/dropped(mob/M) - . = ..() - UnregisterSignal(M, COMSIG_MOB_SAY) - -/obj/item/clothing/head/warden/drill/proc/handle_speech(datum/source, mob/speech_args) - var/message = speech_args[SPEECH_MESSAGE] - if(message[1] != "*") - switch (mode) - if(DRILL_SHOUTING) - message += "!" - if(DRILL_YELLING) - message += "!!" - if(DRILL_CANADIAN) - message = " [message]" - var/list/canadian_words = strings("canadian_replacement.json", "canadian") - - for(var/key in canadian_words) - var/value = canadian_words[key] - if(islist(value)) - value = pick(value) - - message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") - message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") - message = replacetextEx(message, " [key]", " [value]") - - if(prob(30)) - message += pick(", eh?", ", EH?") - speech_args[SPEECH_MESSAGE] = message - -/obj/item/clothing/head/beret/sec - name = "security beret" - desc = "A robust beret with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." - icon_state = "beret_badge" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/beret/sec/navyhos - name = "head of security's beret" - desc = "A special beret with the Head of Security's insignia emblazoned on it. A symbol of excellence, a badge of courage, a mark of distinction." - icon_state = "hosberet" - -/obj/item/clothing/head/beret/sec/navywarden - name = "warden's beret" - desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." - icon_state = "wardenberet" - armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) - strip_delay = 60 - -/obj/item/clothing/head/beret/sec/navyofficer - desc = "A special beret with the security insignia emblazoned on it. For officers with class." - icon_state = "officerberet" - -/obj/item/clothing/head/beret/sec/bitch - name = "security's bitch beret" - desc = "A softer beret with the word 'BITCH' embroidered on it in pink thread." - icon_state = "bitchberet" - -//Curator -/obj/item/clothing/head/fedora/curator - name = "treasure hunter's fedora" - desc = "You got red text today kid, but it doesn't mean you have to like it." - icon_state = "curator" - -//Chief Medical Officer -/obj/item/clothing/head/beret/cmo - name = "chief medical officer's beret" - desc = "A fancy beret with a green cross, signifying your status in the station's medbay." - icon_state = "cmoberet" - -//Research Director -/obj/item/clothing/head/beret/rd - name = "research director's beret" - desc = "A beret worn only by highly intelligent people." - icon_state = "rdberet" - -//Chief Engineer -/obj/item/clothing/head/beret/ce - name = "chief engineer's beret" - desc = "A beret that will surely make you look way cooler than a hard hat, although lack of protection is the price." - icon_state = "ceberet" - -//Quartermaster -/obj/item/clothing/head/beret/qm - name = "quartermaster's beret" - desc = "This headwear shows off your Cargonian leadership" - icon_state = "qmberet" - -/obj/item/clothing/head/beret/durathread - name = "durathread beret" - desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." - icon_state = "beretdurathread" - item_color = null - armor = list("melee" = 25, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 30, "bio" = 15, "rad" = 20, "fire" = 100, "acid" = 50) - -#undef DRILL_DEFAULT -#undef DRILL_SHOUTING -#undef DRILL_YELLING -#undef DRILL_CANADIAN +//defines the drill hat's yelling setting +#define DRILL_DEFAULT "default" +#define DRILL_SHOUTING "shouting" +#define DRILL_YELLING "yelling" +#define DRILL_CANADIAN "canadian" + +//Chef +/obj/item/clothing/head/chefhat + name = "chef's hat" + item_state = "chef" + icon_state = "chef" + desc = "The commander in chef's head wear." + strip_delay = 10 + equip_delay_other = 10 + dynamic_hair_suffix = "" + dog_fashion = /datum/dog_fashion/head/chef + +/obj/item/clothing/head/chefhat/suicide_act(mob/user) + user.visible_message("[user] is donning [src]! It looks like [user.p_theyre()] trying to become a chef.") + user.say("Bork Bork Bork!", forced = "chef hat suicide") + sleep(20) + user.visible_message("[user] climbs into an imaginary oven!") + user.say("BOOORK!", forced = "chef hat suicide") + playsound(user, 'sound/machines/ding.ogg', 50, 1) + return(FIRELOSS) + +//Captain +/obj/item/clothing/head/caphat + name = "captain's hat" + desc = "It's good being the king." + icon_state = "captain" + item_state = "that" + flags_inv = 0 + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/captain + +//Captain: This is no longer space-worthy +/obj/item/clothing/head/caphat/parade + name = "captain's parade cap" + desc = "Worn only by Captains with an abundance of class." + icon_state = "capcap" + + dog_fashion = null + +/obj/item/clothing/head/caphat/beret + name = "captain's beret" + desc = "A beret fit for a leader." + icon_state = "capberet" + dynamic_hair_suffix = "" + + dog_fashion = null + +//Head of Personnel +/obj/item/clothing/head/hopcap + name = "head of personnel's cap" + icon_state = "hopcap" + desc = "The symbol of true bureaucratic micromanagement." + armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + dog_fashion = /datum/dog_fashion/head/hop + +/obj/item/clothing/head/hopcap/beret + name = "head of personnel's beret" + desc = "The symbol of true bureaucratic micromanagement, although in a fancy form." + icon_state = "hopberet" + dynamic_hair_suffix = "" + + dog_fashion = null + +//Chaplain +/obj/item/clothing/head/nun_hood + name = "nun hood" + desc = "Maximum piety in this star system." + icon_state = "nun_hood" + flags_inv = HIDEHAIR + flags_cover = HEADCOVERSEYES + +//Detective +/obj/item/clothing/head/fedora/det_hat + name = "detective's fedora" + desc = "There's only one man who can sniff out the dirty stench of crime, and he's likely wearing this hat." + armor = list("melee" = 25, "bullet" = 5, "laser" = 25, "energy" = 10, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 50) + icon_state = "detective" + var/candy_cooldown = 0 + pocket_storage_component_path = /datum/component/storage/concrete/pockets/small/detective + dog_fashion = /datum/dog_fashion/head/detective + +/obj/item/clothing/head/fedora/det_hat/Initialize() + . = ..() + new /obj/item/reagent_containers/food/drinks/flask/det(src) + +/obj/item/clothing/head/fedora/det_hat/examine(mob/user) + . = ..() + . += "Alt-click to take a candy corn." + +/obj/item/clothing/head/fedora/det_hat/AltClick(mob/user) + . = ..() + if(loc == user && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + if(candy_cooldown < world.time) + var/obj/item/reagent_containers/food/snacks/candy_corn/CC = new /obj/item/reagent_containers/food/snacks/candy_corn(src) + user.put_in_hands(CC) + to_chat(user, "You slip a candy corn from your hat.") + candy_cooldown = world.time+1200 + else + to_chat(user, "You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.") + return TRUE + + +//Mime +/obj/item/clothing/head/beret + name = "beret" + desc = "A beret, a mime's favorite headwear." + icon_state = "beret" + dog_fashion = /datum/dog_fashion/head/beret + dynamic_hair_suffix = "" + +/obj/item/clothing/head/beret/black + name = "black beret" + desc = "A black beret, perfect for war veterans and dark, brooding, anti-hero mimes." + icon_state = "beretblack" + +/obj/item/clothing/head/beret/highlander + desc = "That was white fabric. Was." + dog_fashion = null //THIS IS FOR SLAUGHTER, NOT PUPPIES + +/obj/item/clothing/head/beret/highlander/Initialize() + . = ..() + ADD_TRAIT(src, TRAIT_NODROP, HIGHLANDER) + +//Security + +/obj/item/clothing/head/HoS + name = "head of security cap" + desc = "The robust standard-issue cap of the Head of Security. For showing the officers who's in charge." + icon_state = "hoscap" + armor = list("melee" = 40, "bullet" = 30, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 10, "rad" = 0, "fire" = 50, "acid" = 60) + strip_delay = 80 + dynamic_hair_suffix = "" + +/obj/item/clothing/head/HoS/syndicate + name = "syndicate cap" + desc = "A black cap fit for a high ranking syndicate officer." + +/obj/item/clothing/head/HoS/beret + name = "head of security beret" + desc = "A robust beret for the Head of Security, for looking stylish while not sacrificing protection." + icon_state = "hosberetblack" + +/obj/item/clothing/head/HoS/beret/syndicate + name = "syndicate beret" + desc = "A black beret with thick armor padding inside. Stylish and robust." + +/obj/item/clothing/head/warden + name = "warden's police hat" + desc = "It's a special armored hat issued to the Warden of a security force. Protects the head from impacts." + icon_state = "policehelm" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 30, "acid" = 60) + strip_delay = 60 + dog_fashion = /datum/dog_fashion/head/warden + +/obj/item/clothing/head/warden/drill + name = "warden's campaign hat" + desc = "A special armored campaign hat with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection. Has the letters 'FMJ' enscribed on its side." + icon_state = "wardendrill" + item_state = "wardendrill" + dog_fashion = null + var/mode = DRILL_DEFAULT + +/obj/item/clothing/head/warden/drill/screwdriver_act(mob/living/carbon/human/user, obj/item/I) + if(..()) + return TRUE + switch(mode) + if(DRILL_DEFAULT) + to_chat(user, "You set the voice circuit to the middle position.") + mode = DRILL_SHOUTING + if(DRILL_SHOUTING) + to_chat(user, "You set the voice circuit to the last position.") + mode = DRILL_YELLING + if(DRILL_YELLING) + to_chat(user, "You set the voice circuit to the first position.") + mode = DRILL_DEFAULT + if(DRILL_CANADIAN) + to_chat(user, "You adjust voice circuit but nothing happens, probably because it's broken.") + return TRUE + +/obj/item/clothing/head/warden/drill/wirecutter_act(mob/living/user, obj/item/I) + if(mode != DRILL_CANADIAN) + to_chat(user, "You broke the voice circuit!") + mode = DRILL_CANADIAN + return TRUE + +/obj/item/clothing/head/warden/drill/equipped(mob/M, slot) + . = ..() + if (slot == SLOT_HEAD) + RegisterSignal(M, COMSIG_MOB_SAY, .proc/handle_speech) + else + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/warden/drill/dropped(mob/M) + . = ..() + UnregisterSignal(M, COMSIG_MOB_SAY) + +/obj/item/clothing/head/warden/drill/proc/handle_speech(datum/source, mob/speech_args) + var/message = speech_args[SPEECH_MESSAGE] + if(message[1] != "*") + switch (mode) + if(DRILL_SHOUTING) + message += "!" + if(DRILL_YELLING) + message += "!!" + if(DRILL_CANADIAN) + message = " [message]" + var/list/canadian_words = strings("canadian_replacement.json", "canadian") + + for(var/key in canadian_words) + var/value = canadian_words[key] + if(islist(value)) + value = pick(value) + + message = replacetextEx(message, " [uppertext(key)]", " [uppertext(value)]") + message = replacetextEx(message, " [capitalize(key)]", " [capitalize(value)]") + message = replacetextEx(message, " [key]", " [value]") + + if(prob(30)) + message += pick(", eh?", ", EH?") + speech_args[SPEECH_MESSAGE] = message + +/obj/item/clothing/head/beret/sec + name = "security beret" + desc = "A robust beret with the security insignia emblazoned on it. Uses reinforced fabric to offer sufficient protection." + icon_state = "beret_badge" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30,"energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/beret/sec/navyhos + name = "head of security's beret" + desc = "A special beret with the Head of Security's insignia emblazoned on it. A symbol of excellence, a badge of courage, a mark of distinction." + icon_state = "hosberet" + +/obj/item/clothing/head/beret/sec/navywarden + name = "warden's beret" + desc = "A special beret with the Warden's insignia emblazoned on it. For wardens with class." + icon_state = "wardenberet" + armor = list("melee" = 40, "bullet" = 30, "laser" = 30, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = 60 + +/obj/item/clothing/head/beret/sec/navyofficer + desc = "A special beret with the security insignia emblazoned on it. For officers with class." + icon_state = "officerberet" + +/obj/item/clothing/head/beret/sec/bitch + name = "security's bitch beret" + desc = "A softer beret with the word 'BITCH' embroidered on it in pink thread." + icon_state = "bitchberet" + +//Curator +/obj/item/clothing/head/fedora/curator + name = "treasure hunter's fedora" + desc = "You got red text today kid, but it doesn't mean you have to like it." + icon_state = "curator" + +//Chief Medical Officer +/obj/item/clothing/head/beret/cmo + name = "chief medical officer's beret" + desc = "A fancy beret with a green cross, signifying your status in the station's medbay." + icon_state = "cmoberet" + +//Research Director +/obj/item/clothing/head/beret/rd + name = "research director's beret" + desc = "A beret worn only by highly intelligent people." + icon_state = "rdberet" + +//Chief Engineer +/obj/item/clothing/head/beret/ce + name = "chief engineer's beret" + desc = "A beret that will surely make you look way cooler than a hard hat, although lack of protection is the price." + icon_state = "ceberet" + +//Quartermaster +/obj/item/clothing/head/beret/qm + name = "quartermaster's beret" + desc = "This headwear shows off your Cargonian leadership" + icon_state = "qmberet" + +/obj/item/clothing/head/beret/durathread + name = "durathread beret" + desc = "A beret made from durathread, its resilient fibres provide some protection to the wearer." + icon_state = "beretdurathread" + item_color = null + armor = list("melee" = 25, "bullet" = 10, "laser" = 20,"energy" = 10, "bomb" = 30, "bio" = 15, "rad" = 20, "fire" = 100, "acid" = 50) + +#undef DRILL_DEFAULT +#undef DRILL_SHOUTING +#undef DRILL_YELLING +#undef DRILL_CANADIAN diff --git a/code/modules/clothing/head/soft_caps.dm b/code/modules/clothing/head/soft_caps.dm index 9e3e4312..364bb633 100644 --- a/code/modules/clothing/head/soft_caps.dm +++ b/code/modules/clothing/head/soft_caps.dm @@ -1,142 +1,142 @@ -/obj/item/clothing/head/soft - name = "cargo cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "cargosoft" - item_state = "helmet" - item_color = "cargo" - - dog_fashion = /datum/dog_fashion/head/cargo_tech - - var/flipped = 0 - -/obj/item/clothing/head/soft/dropped() - src.icon_state = "[item_color]soft" - src.flipped=0 - ..() - -/obj/item/clothing/head/soft/verb/flipcap() - set category = "Object" - set name = "Flip cap" - - flip(usr) - - -/obj/item/clothing/head/soft/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - flip(user) - - -/obj/item/clothing/head/soft/proc/flip(mob/user) - if(!user.incapacitated()) - src.flipped = !src.flipped - if(src.flipped) - icon_state = "[item_color]soft_flipped" - to_chat(user, "You flip the hat backwards.") - else - icon_state = "[item_color]soft" - to_chat(user, "You flip the hat back in normal position.") - usr.update_inv_head() //so our mob-overlays update - -/obj/item/clothing/head/soft/examine(mob/user) - . = ..() - . += "Alt-click the cap to flip it [flipped ? "forwards" : "backwards"]." - -/obj/item/clothing/head/soft/red - name = "red cap" - desc = "It's a baseball hat in a tasteless red colour." - icon_state = "redsoft" - item_color = "red" - dog_fashion = null - -/obj/item/clothing/head/soft/blue - name = "blue cap" - desc = "It's a baseball hat in a tasteless blue colour." - icon_state = "bluesoft" - item_color = "blue" - dog_fashion = null - -/obj/item/clothing/head/soft/green - name = "green cap" - desc = "It's a baseball hat in a tasteless green colour." - icon_state = "greensoft" - item_color = "green" - dog_fashion = null - -/obj/item/clothing/head/soft/yellow - name = "yellow cap" - desc = "It's a baseball hat in a tasteless yellow colour." - icon_state = "yellowsoft" - item_color = "yellow" - dog_fashion = null - -/obj/item/clothing/head/soft/grey - name = "grey cap" - desc = "It's a baseball hat in a tasteful grey colour." - icon_state = "greysoft" - item_color = "grey" - dog_fashion = null - -/obj/item/clothing/head/soft/orange - name = "orange cap" - desc = "It's a baseball hat in a tasteless orange colour." - icon_state = "orangesoft" - item_color = "orange" - dog_fashion = null - -/obj/item/clothing/head/soft/mime - name = "white cap" - desc = "It's a baseball hat in a tasteless white colour." - icon_state = "mimesoft" - item_color = "mime" - dog_fashion = null - -/obj/item/clothing/head/soft/purple - name = "purple cap" - desc = "It's a baseball hat in a tasteless purple colour." - icon_state = "purplesoft" - item_color = "purple" - dog_fashion = null - -/obj/item/clothing/head/soft/black - name = "black cap" - desc = "It's a baseball hat in a tasteless black colour." - icon_state = "blacksoft" - item_color = "black" - dog_fashion = null - -/obj/item/clothing/head/soft/rainbow - name = "rainbow cap" - desc = "It's a baseball hat in a bright rainbow of colors." - icon_state = "rainbowsoft" - item_color = "rainbow" - dog_fashion = null - -/obj/item/clothing/head/soft/sec - name = "security cap" - desc = "It's a robust baseball hat in tasteful red colour." - icon_state = "secsoft" - item_color = "sec" - armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) - strip_delay = 60 - dog_fashion = null - -/obj/item/clothing/head/soft/emt - name = "paramedic cap" - desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." - icon_state = "emtsoft" - item_color = "emt" - dog_fashion = null - -/obj/item/clothing/head/soft/baseball - name = "baseball cap" - desc = "It's a robust baseball hat, this one belongs to syndicate major league team." - icon_state = "baseballsoft" - item_color = "baseballsoft" - item_state = "baseballsoft" - flags_inv = HIDEEYES|HIDEFACE - armor = list("melee" = 35, "bullet" = 35, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 90) - strip_delay = 90 //You dont take a Major Leage cap - dog_fashion = null +/obj/item/clothing/head/soft + name = "cargo cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "cargosoft" + item_state = "helmet" + item_color = "cargo" + + dog_fashion = /datum/dog_fashion/head/cargo_tech + + var/flipped = 0 + +/obj/item/clothing/head/soft/dropped() + src.icon_state = "[item_color]soft" + src.flipped=0 + ..() + +/obj/item/clothing/head/soft/verb/flipcap() + set category = "Object" + set name = "Flip cap" + + flip(usr) + + +/obj/item/clothing/head/soft/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + flip(user) + return TRUE + + +/obj/item/clothing/head/soft/proc/flip(mob/user) + if(!user.incapacitated()) + src.flipped = !src.flipped + if(src.flipped) + icon_state = "[item_color]soft_flipped" + to_chat(user, "You flip the hat backwards.") + else + icon_state = "[item_color]soft" + to_chat(user, "You flip the hat back in normal position.") + usr.update_inv_head() //so our mob-overlays update + +/obj/item/clothing/head/soft/examine(mob/user) + . = ..() + . += "Alt-click the cap to flip it [flipped ? "forwards" : "backwards"]." + +/obj/item/clothing/head/soft/red + name = "red cap" + desc = "It's a baseball hat in a tasteless red colour." + icon_state = "redsoft" + item_color = "red" + dog_fashion = null + +/obj/item/clothing/head/soft/blue + name = "blue cap" + desc = "It's a baseball hat in a tasteless blue colour." + icon_state = "bluesoft" + item_color = "blue" + dog_fashion = null + +/obj/item/clothing/head/soft/green + name = "green cap" + desc = "It's a baseball hat in a tasteless green colour." + icon_state = "greensoft" + item_color = "green" + dog_fashion = null + +/obj/item/clothing/head/soft/yellow + name = "yellow cap" + desc = "It's a baseball hat in a tasteless yellow colour." + icon_state = "yellowsoft" + item_color = "yellow" + dog_fashion = null + +/obj/item/clothing/head/soft/grey + name = "grey cap" + desc = "It's a baseball hat in a tasteful grey colour." + icon_state = "greysoft" + item_color = "grey" + dog_fashion = null + +/obj/item/clothing/head/soft/orange + name = "orange cap" + desc = "It's a baseball hat in a tasteless orange colour." + icon_state = "orangesoft" + item_color = "orange" + dog_fashion = null + +/obj/item/clothing/head/soft/mime + name = "white cap" + desc = "It's a baseball hat in a tasteless white colour." + icon_state = "mimesoft" + item_color = "mime" + dog_fashion = null + +/obj/item/clothing/head/soft/purple + name = "purple cap" + desc = "It's a baseball hat in a tasteless purple colour." + icon_state = "purplesoft" + item_color = "purple" + dog_fashion = null + +/obj/item/clothing/head/soft/black + name = "black cap" + desc = "It's a baseball hat in a tasteless black colour." + icon_state = "blacksoft" + item_color = "black" + dog_fashion = null + +/obj/item/clothing/head/soft/rainbow + name = "rainbow cap" + desc = "It's a baseball hat in a bright rainbow of colors." + icon_state = "rainbowsoft" + item_color = "rainbow" + dog_fashion = null + +/obj/item/clothing/head/soft/sec + name = "security cap" + desc = "It's a robust baseball hat in tasteful red colour." + icon_state = "secsoft" + item_color = "sec" + armor = list("melee" = 30, "bullet" = 25, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 50) + strip_delay = 60 + dog_fashion = null + +/obj/item/clothing/head/soft/emt + name = "paramedic cap" + desc = "It's a baseball hat with a dark turquoise color and a reflective cross on the top." + icon_state = "emtsoft" + item_color = "emt" + dog_fashion = null + +/obj/item/clothing/head/soft/baseball + name = "baseball cap" + desc = "It's a robust baseball hat, this one belongs to syndicate major league team." + icon_state = "baseballsoft" + item_color = "baseballsoft" + item_state = "baseballsoft" + flags_inv = HIDEEYES|HIDEFACE + armor = list("melee" = 35, "bullet" = 35, "laser" = 25, "energy" = 10, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 20, "acid" = 90) + strip_delay = 90 //You dont take a Major Leage cap + dog_fashion = null diff --git a/code/modules/clothing/masks/breath.dm b/code/modules/clothing/masks/breath.dm index bf98035b..f4335d17 100644 --- a/code/modules/clothing/masks/breath.dm +++ b/code/modules/clothing/masks/breath.dm @@ -1,42 +1,42 @@ -/obj/item/clothing/mask/breath - desc = "A close-fitting mask that can be connected to an air supply." - name = "breath mask" - icon_state = "breath" - item_state = "m_mask" - body_parts_covered = 0 - clothing_flags = ALLOWINTERNALS - visor_flags = ALLOWINTERNALS - w_class = WEIGHT_CLASS_SMALL - gas_transfer_coefficient = 0.1 - permeability_coefficient = 0.5 - actions_types = list(/datum/action/item_action/adjust) - flags_cover = MASKCOVERSMOUTH - visor_flags_cover = MASKCOVERSMOUTH - resistance_flags = NONE - mutantrace_variation = MUTANTRACE_VARIATION - -/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) - user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") - return OXYLOSS - -/obj/item/clothing/mask/breath/attack_self(mob/user) - adjustmask(user) - -/obj/item/clothing/mask/breath/AltClick(mob/user) - ..() - if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - return - else - adjustmask(user) - -/obj/item/clothing/mask/breath/examine(mob/user) - . = ..() - . += "Alt-click [src] to adjust it." - -/obj/item/clothing/mask/breath/medical - desc = "A close-fitting sterile mask that can be connected to an air supply." - name = "medical mask" - icon_state = "medical" - item_state = "m_mask" - permeability_coefficient = 0.01 - equip_delay_other = 10 +/obj/item/clothing/mask/breath + desc = "A close-fitting mask that can be connected to an air supply." + name = "breath mask" + icon_state = "breath" + item_state = "m_mask" + body_parts_covered = 0 + clothing_flags = ALLOWINTERNALS + visor_flags = ALLOWINTERNALS + w_class = WEIGHT_CLASS_SMALL + gas_transfer_coefficient = 0.1 + permeability_coefficient = 0.5 + actions_types = list(/datum/action/item_action/adjust) + flags_cover = MASKCOVERSMOUTH + visor_flags_cover = MASKCOVERSMOUTH + resistance_flags = NONE + mutantrace_variation = MUTANTRACE_VARIATION + +/obj/item/clothing/mask/breath/suicide_act(mob/living/carbon/user) + user.visible_message("[user] is wrapping \the [src]'s tube around [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!") + return OXYLOSS + +/obj/item/clothing/mask/breath/attack_self(mob/user) + adjustmask(user) + +/obj/item/clothing/mask/breath/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + return + adjustmask(user) + return TRUE + +/obj/item/clothing/mask/breath/examine(mob/user) + . = ..() + . += "Alt-click [src] to adjust it." + +/obj/item/clothing/mask/breath/medical + desc = "A close-fitting sterile mask that can be connected to an air supply." + name = "medical mask" + icon_state = "medical" + item_state = "m_mask" + permeability_coefficient = 0.01 + equip_delay_other = 10 diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index cfca0db7..a524e0c0 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -865,3 +865,126 @@ strip_delay = 130 max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT actions_types = list() + +/* + CYDONIAN ARMOR THAT IS RGB AND STUFF WOOOOOOOOOO +*/ + +/obj/item/clothing/head/helmet/space/hardsuit/lavaknight + name = "cydonian helmet" + desc = "A helmet designed with both form and function in mind, it protects the user against physical trauma and hazardous conditions while also having polychromic light strips." + icon_state = "knight_cydonia" + item_state = "knight_yellow" + item_color = null + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | LAVA_PROOF + heat_protection = HEAD + armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100) + brightness_on = 7 + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator) + var/energy_color = "#35FFF0" + var/obj/item/clothing/suit/space/hardsuit/lavaknight/linkedsuit = null + mutantrace_variation = NO_MUTANTRACE_VARIATION + +/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/Initialize() + . = ..() + if(istype(loc, /obj/item/clothing/suit/space/hardsuit/lavaknight)) + var/obj/item/clothing/suit/space/hardsuit/lavaknight/S = loc + energy_color = S.energy_color + update_icon() + +/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/attack_self(mob/user) + on = !on + + if(on) + set_light(brightness_on) + else + set_light(0) + for(var/X in actions) + var/datum/action/A = X + A.UpdateButtonIcon() + +/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/update_icon() + var/mutable_appearance/helm_overlay = mutable_appearance(icon, "knight_cydonia_overlay") + + if(energy_color) + helm_overlay.color = energy_color + + cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other + + add_overlay(helm_overlay) + +/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/worn_overlays(isinhands = FALSE, icon_file) + . = ..() + if(!isinhands) + var/mutable_appearance/energy_overlay = mutable_appearance(icon_file, "knight_cydonia_overlay", ABOVE_LIGHTING_LAYER) + energy_overlay.plane = ABOVE_LIGHTING_LAYER + energy_overlay.color = energy_color + . += energy_overlay + +/obj/item/clothing/suit/space/hardsuit/lavaknight + icon_state = "knight_cydonia" + name = "cydonian armor" + desc = "A suit designed with both form and function in mind, it protects the user against physical trauma and hazardous conditions while also having polychromic light strips." + item_state = "swat_suit" + max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT + resistance_flags = FIRE_PROOF | LAVA_PROOF + armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100) + allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/storage/bag/ore, /obj/item/pickaxe) + helmettype = /obj/item/clothing/head/helmet/space/hardsuit/lavaknight + heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS + tauric = TRUE //Citadel Add for tauric hardsuits + + var/energy_color = "#35FFF0" + +/obj/item/clothing/suit/space/hardsuit/lavaknight/Initialize() + ..() + light_color = energy_color + set_light(1) + update_icon() + +/obj/item/clothing/suit/space/hardsuit/lavaknight/update_icon() + var/mutable_appearance/suit_overlay = mutable_appearance(icon, "knight_cydonia_overlay") + + if(energy_color) + suit_overlay.color = energy_color + + cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other + + add_overlay(suit_overlay) + +/obj/item/clothing/suit/space/hardsuit/lavaknight/worn_overlays(isinhands = FALSE, icon_file) + . = ..() + if(!isinhands) + var/mutable_appearance/energy_overlay = mutable_appearance(icon_file, "knight_cydonia_overlay", ABOVE_LIGHTING_LAYER) + energy_overlay.plane = ABOVE_LIGHTING_LAYER + energy_overlay.color = energy_color + . += energy_overlay + +/obj/item/clothing/suit/space/hardsuit/lavaknight/AltClick(mob/living/user) + . = ..() + if(!in_range(src, user) || !istype(user)) + return + if(user.incapacitated()) + to_chat(user, "You can't do that right now!") + return TRUE + + if(alert("Are you sure you want to recolor your armor stripes?", "Confirm Repaint", "Yes", "No") == "Yes") + var/energy_color_input = input(usr,"","Choose Energy Color",energy_color) as color|null + if(energy_color_input) + energy_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) + user.update_inv_wear_suit() + if(helmet) + var/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/H = helmet + H.energy_color = energy_color + user.update_inv_head() + H.update_icon() + update_icon() + user.update_inv_wear_suit() + light_color = energy_color + update_light() + return TRUE + +/obj/item/clothing/suit/space/hardsuit/lavaknight/examine(mob/user) + . = ..() + . += "Alt-click to recolor it." diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm index 7d91b2f7..0e2ea6f1 100644 --- a/code/modules/clothing/suits/toggles.dm +++ b/code/modules/clothing/suits/toggles.dm @@ -92,11 +92,11 @@ //Toggle exosuits for different aesthetic styles (hoodies, suit jacket buttons, etc) /obj/item/clothing/suit/toggle/AltClick(mob/user) - ..() + . = ..() if(!user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return - else - suit_toggle(user) + suit_toggle(user) + return TRUE /obj/item/clothing/suit/toggle/ui_action_click() suit_toggle() diff --git a/code/modules/clothing/under/accessories.dm b/code/modules/clothing/under/accessories.dm index 4c53e837..e71a5e89 100644 --- a/code/modules/clothing/under/accessories.dm +++ b/code/modules/clothing/under/accessories.dm @@ -67,10 +67,12 @@ return /obj/item/clothing/accessory/AltClick(mob/user) + . = ..() if(istype(user) && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) if(initial(above_suit)) above_suit = !above_suit to_chat(user, "[src] will be worn [above_suit ? "above" : "below"] your suit.") + return TRUE /obj/item/clothing/accessory/examine(mob/user) . = ..() diff --git a/code/modules/detectivework/scanner.dm b/code/modules/detectivework/scanner.dm index bff010a3..9dcdd61d 100644 --- a/code/modules/detectivework/scanner.dm +++ b/code/modules/detectivework/scanner.dm @@ -1,217 +1,219 @@ -//CONTAINS: Detective's Scanner - -// TODO: Split everything into easy to manage procs. - -/obj/item/detective_scanner - name = "forensic scanner" - desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." - icon = 'icons/obj/device.dmi' - icon_state = "forensicnew" - w_class = WEIGHT_CLASS_SMALL - item_state = "electronic" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - flags_1 = CONDUCT_1 - item_flags = NOBLUDGEON - slot_flags = ITEM_SLOT_BELT - var/scanning = FALSE - var/list/log = list() - var/range = 8 - var/view_check = TRUE - actions_types = list(/datum/action/item_action/displayDetectiveScanResults) - -/datum/action/item_action/displayDetectiveScanResults - name = "Display Forensic Scanner Results" - -/datum/action/item_action/displayDetectiveScanResults/Trigger() - var/obj/item/detective_scanner/scanner = target - if(istype(scanner)) - scanner.displayDetectiveScanResults(usr) - -/obj/item/detective_scanner/attack_self(mob/user) - if(log.len && !scanning) - scanning = TRUE - to_chat(user, "Printing report, please wait...") - addtimer(CALLBACK(src, .proc/PrintReport), 100) - else - to_chat(user, "The scanner has no logs or is in use.") - -/obj/item/detective_scanner/attack(mob/living/M, mob/user) - return - -/obj/item/detective_scanner/proc/PrintReport() - // Create our paper - var/obj/item/paper/P = new(get_turf(src)) - P.name = "paper- 'Scanner Report'" - P.info = "
    Scanner Report


    " - P.info += jointext(log, "
    ") - P.info += "
    Notes:
    " - P.info_links = P.info - P.updateinfolinks() - - if(ismob(loc)) - var/mob/M = loc - M.put_in_hands(P) - to_chat(M, "Report printed. Log cleared.") - - // Clear the logs - log = list() - scanning = FALSE - -/obj/item/detective_scanner/afterattack(atom/A, mob/user, params) - . = ..() - scan(A, user) - return FALSE - -/obj/item/detective_scanner/proc/scan(atom/A, mob/user) - set waitfor = 0 - if(!scanning) - // Can remotely scan objects and mobs. - if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user)) - return - - scanning = TRUE - - user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.") - to_chat(user, "You scan \the [A]. The scanner is now analysing the results...") - - - // GATHER INFORMATION - - //Make our lists - var/list/fingerprints = list() - var/list/blood = list() - var/list/fibers = list() - var/list/reagents = list() - - var/target_name = A.name - - // Start gathering - - if(A.blood_DNA && A.blood_DNA.len) - blood = A.blood_DNA.Copy() - - if(A.suit_fibers && A.suit_fibers.len) - fibers = A.suit_fibers.Copy() - - if(ishuman(A)) - - var/mob/living/carbon/human/H = A - if(!H.gloves) - fingerprints += md5(H.dna.uni_identity) - - else if(!ismob(A)) - - if(A.fingerprints && A.fingerprints.len) - fingerprints = A.fingerprints.Copy() - - // Only get reagents from non-mobs. - if(A.reagents && A.reagents.reagent_list.len) - - for(var/datum/reagent/R in A.reagents.reagent_list) - reagents[R.name] = R.volume - - // Get blood data from the blood reagent. - if(istype(R, /datum/reagent/blood)) - - if(R.data["blood_DNA"] && R.data["blood_type"]) - var/blood_DNA = R.data["blood_DNA"] - var/blood_type = R.data["blood_type"] - LAZYINITLIST(blood) - blood[blood_DNA] = blood_type - - // We gathered everything. Create a fork and slowly display the results to the holder of the scanner. - - var/found_something = FALSE - add_log("[STATION_TIME_TIMESTAMP("hh:mm:ss")][get_timestamp()] - [target_name]", 0) - - // Fingerprints - if(length(fingerprints)) - sleep(3 SECONDS) - add_log("Prints:") - for(var/finger in fingerprints) - add_log("[finger]") - found_something = TRUE - - // Blood - if (length(blood)) - sleep(3 SECONDS) - add_log("Blood:") - found_something = TRUE - for(var/B in blood) - add_log("Type: [blood[B]] DNA: [B]") - - //Fibers - if(length(fibers)) - sleep(3 SECONDS) - add_log("Fibers:") - for(var/fiber in fibers) - add_log("[fiber]") - found_something = TRUE - - //Reagents - if(length(reagents)) - sleep(3 SECONDS) - add_log("Reagents:") - for(var/R in reagents) - add_log("Reagent: [R] Volume: [reagents[R]]") - found_something = TRUE - - // Get a new user - var/mob/holder = null - if(ismob(src.loc)) - holder = src.loc - - if(!found_something) - add_log("# No forensic traces found #", 0) // Don't display this to the holder user - if(holder) - to_chat(holder, "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!") - else - if(holder) - to_chat(holder, "You finish scanning \the [target_name].") - - add_log("---------------------------------------------------------", 0) - scanning = FALSE - return - -/obj/item/detective_scanner/proc/add_log(msg, broadcast = TRUE) - if(scanning) - if(broadcast && ismob(loc)) - var/mob/M = loc - to_chat(M, msg) - log += "  [msg]" - else - CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!") - -/proc/get_timestamp() - return time2text(world.time + 432000, ":ss") - -/obj/item/detective_scanner/AltClick(mob/living/user) - // Best way for checking if a player can use while not incapacitated, etc - if(!user.canUseTopic(src, be_close=TRUE)) - return - if(!LAZYLEN(log)) - to_chat(user, "Cannot clear logs, the scanner has no logs.") - return - if(scanning) - to_chat(user, "Cannot clear logs, the scanner is in use.") - return - to_chat(user, "The scanner logs are cleared.") - log = list() - -/obj/item/detective_scanner/examine(mob/user) - . = ..() - if(LAZYLEN(log) && !scanning) - . += "Alt-click to clear scanner logs." - -/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user) - // No need for can-use checks since the action button should do proper checks - if(!LAZYLEN(log)) - to_chat(user, "Cannot display logs, the scanner has no logs.") - return - if(scanning) - to_chat(user, "Cannot display logs, the scanner is in use.") - return - to_chat(user, "Scanner Report") - for(var/iterLog in log) - to_chat(user, iterLog) +//CONTAINS: Detective's Scanner + +// TODO: Split everything into easy to manage procs. + +/obj/item/detective_scanner + name = "forensic scanner" + desc = "Used to remotely scan objects and biomass for DNA and fingerprints. Can print a report of the findings." + icon = 'icons/obj/device.dmi' + icon_state = "forensicnew" + w_class = WEIGHT_CLASS_SMALL + item_state = "electronic" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + flags_1 = CONDUCT_1 + item_flags = NOBLUDGEON + slot_flags = ITEM_SLOT_BELT + var/scanning = FALSE + var/list/log = list() + var/range = 8 + var/view_check = TRUE + actions_types = list(/datum/action/item_action/displayDetectiveScanResults) + +/datum/action/item_action/displayDetectiveScanResults + name = "Display Forensic Scanner Results" + +/datum/action/item_action/displayDetectiveScanResults/Trigger() + var/obj/item/detective_scanner/scanner = target + if(istype(scanner)) + scanner.displayDetectiveScanResults(usr) + +/obj/item/detective_scanner/attack_self(mob/user) + if(log.len && !scanning) + scanning = TRUE + to_chat(user, "Printing report, please wait...") + addtimer(CALLBACK(src, .proc/PrintReport), 100) + else + to_chat(user, "The scanner has no logs or is in use.") + +/obj/item/detective_scanner/attack(mob/living/M, mob/user) + return + +/obj/item/detective_scanner/proc/PrintReport() + // Create our paper + var/obj/item/paper/P = new(get_turf(src)) + P.name = "paper- 'Scanner Report'" + P.info = "
    Scanner Report


    " + P.info += jointext(log, "
    ") + P.info += "
    Notes:
    " + P.info_links = P.info + P.updateinfolinks() + + if(ismob(loc)) + var/mob/M = loc + M.put_in_hands(P) + to_chat(M, "Report printed. Log cleared.") + + // Clear the logs + log = list() + scanning = FALSE + +/obj/item/detective_scanner/afterattack(atom/A, mob/user, params) + . = ..() + scan(A, user) + return FALSE + +/obj/item/detective_scanner/proc/scan(atom/A, mob/user) + set waitfor = 0 + if(!scanning) + // Can remotely scan objects and mobs. + if((get_dist(A, user) > range) || (!(A in view(range, user)) && view_check) || (loc != user)) + return + + scanning = TRUE + + user.visible_message("\The [user] points the [src.name] at \the [A] and performs a forensic scan.") + to_chat(user, "You scan \the [A]. The scanner is now analysing the results...") + + + // GATHER INFORMATION + + //Make our lists + var/list/fingerprints = list() + var/list/blood = list() + var/list/fibers = list() + var/list/reagents = list() + + var/target_name = A.name + + // Start gathering + + if(A.blood_DNA && A.blood_DNA.len) + blood = A.blood_DNA.Copy() + + if(A.suit_fibers && A.suit_fibers.len) + fibers = A.suit_fibers.Copy() + + if(ishuman(A)) + + var/mob/living/carbon/human/H = A + if(!H.gloves) + fingerprints += md5(H.dna.uni_identity) + + else if(!ismob(A)) + + if(A.fingerprints && A.fingerprints.len) + fingerprints = A.fingerprints.Copy() + + // Only get reagents from non-mobs. + if(A.reagents && A.reagents.reagent_list.len) + + for(var/datum/reagent/R in A.reagents.reagent_list) + reagents[R.name] = R.volume + + // Get blood data from the blood reagent. + if(istype(R, /datum/reagent/blood)) + + if(R.data["blood_DNA"] && R.data["blood_type"]) + var/blood_DNA = R.data["blood_DNA"] + var/blood_type = R.data["blood_type"] + LAZYINITLIST(blood) + blood[blood_DNA] = blood_type + + // We gathered everything. Create a fork and slowly display the results to the holder of the scanner. + + var/found_something = FALSE + add_log("[STATION_TIME_TIMESTAMP("hh:mm:ss")][get_timestamp()] - [target_name]", 0) + + // Fingerprints + if(length(fingerprints)) + sleep(3 SECONDS) + add_log("Prints:") + for(var/finger in fingerprints) + add_log("[finger]") + found_something = TRUE + + // Blood + if (length(blood)) + sleep(3 SECONDS) + add_log("Blood:") + found_something = TRUE + for(var/B in blood) + add_log("Type: [blood[B]] DNA: [B]") + + //Fibers + if(length(fibers)) + sleep(3 SECONDS) + add_log("Fibers:") + for(var/fiber in fibers) + add_log("[fiber]") + found_something = TRUE + + //Reagents + if(length(reagents)) + sleep(3 SECONDS) + add_log("Reagents:") + for(var/R in reagents) + add_log("Reagent: [R] Volume: [reagents[R]]") + found_something = TRUE + + // Get a new user + var/mob/holder = null + if(ismob(src.loc)) + holder = src.loc + + if(!found_something) + add_log("# No forensic traces found #", 0) // Don't display this to the holder user + if(holder) + to_chat(holder, "Unable to locate any fingerprints, materials, fibers, or blood on \the [target_name]!") + else + if(holder) + to_chat(holder, "You finish scanning \the [target_name].") + + add_log("---------------------------------------------------------", 0) + scanning = FALSE + return + +/obj/item/detective_scanner/proc/add_log(msg, broadcast = TRUE) + if(scanning) + if(broadcast && ismob(loc)) + var/mob/M = loc + to_chat(M, msg) + log += "  [msg]" + else + CRASH("[src] [REF(src)] is adding a log when it was never put in scanning mode!") + +/proc/get_timestamp() + return time2text(world.time + 432000, ":ss") + +/obj/item/detective_scanner/AltClick(mob/living/user) + . = ..() + // Best way for checking if a player can use while not incapacitated, etc + if(!user.canUseTopic(src, be_close=TRUE)) + return + . = TRUE + if(!LAZYLEN(log)) + to_chat(user, "Cannot clear logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot clear logs, the scanner is in use.") + return + to_chat(user, "The scanner logs are cleared.") + log = list() + +/obj/item/detective_scanner/examine(mob/user) + . = ..() + if(LAZYLEN(log) && !scanning) + . += "Alt-click to clear scanner logs." + +/obj/item/detective_scanner/proc/displayDetectiveScanResults(mob/living/user) + // No need for can-use checks since the action button should do proper checks + if(!LAZYLEN(log)) + to_chat(user, "Cannot display logs, the scanner has no logs.") + return + if(scanning) + to_chat(user, "Cannot display logs, the scanner is in use.") + return + to_chat(user, "Scanner Report") + for(var/iterLog in log) + to_chat(user, iterLog) diff --git a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm index 6118e262..e4d36b29 100644 --- a/code/modules/food_and_drinks/kitchen_machinery/microwave.dm +++ b/code/modules/food_and_drinks/kitchen_machinery/microwave.dm @@ -186,8 +186,10 @@ ..() /obj/machinery/microwave/AltClick(mob/user) + . = ..() if(user.canUseTopic(src, !issilicon(usr))) cook() + return TRUE /obj/machinery/microwave/ui_interact(mob/user) . = ..() diff --git a/code/modules/games/cas.dm b/code/modules/games/cas.dm index fe038ce3..4fbb931b 100644 --- a/code/modules/games/cas.dm +++ b/code/modules/games/cas.dm @@ -130,9 +130,11 @@ update_icon() /obj/item/toy/cards/singlecard/cas/AltClick(mob/living/user) + . = ..() if(!ishuman(user) || !user.canUseTopic(src, BE_CLOSE)) return Flip() + return TRUE /obj/item/toy/cards/singlecard/cas/update_icon() if(flipped) diff --git a/code/modules/mining/abandoned_crates.dm b/code/modules/mining/abandoned_crates.dm index 86499e69..b0e6d873 100644 --- a/code/modules/mining/abandoned_crates.dm +++ b/code/modules/mining/abandoned_crates.dm @@ -180,10 +180,11 @@ else return ..() +//this helps you not blow up so easily by overriding unlocking which results in an immediate boom. /obj/structure/closet/crate/secure/loot/AltClick(mob/living/user) - if(!user.canUseTopic(src, BE_CLOSE)) - return - return attack_hand(user) //this helps you not blow up so easily by overriding unlocking which results in an immediate boom. + if(user.canUseTopic(src, BE_CLOSE)) + attack_hand(user) + return TRUE /obj/structure/closet/crate/secure/loot/attackby(obj/item/W, mob/user) if(locked) diff --git a/code/modules/mining/equipment/marker_beacons.dm b/code/modules/mining/equipment/marker_beacons.dm index 00ce37b7..9d595664 100644 --- a/code/modules/mining/equipment/marker_beacons.dm +++ b/code/modules/mining/equipment/marker_beacons.dm @@ -59,6 +59,7 @@ GLOBAL_LIST_INIT(marker_beacon_colors, list( /obj/item/stack/marker_beacon/AltClick(mob/living/user) if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return + . = TRUE var/input_color = input(user, "Choose a color.", "Beacon Color") as null|anything in GLOB.marker_beacon_colors if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return @@ -128,9 +129,10 @@ GLOBAL_LIST_INIT(marker_beacon_colors, list( return ..() /obj/structure/marker_beacon/AltClick(mob/living/user) - ..() + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return + . = TRUE var/input_color = input(user, "Choose a color.", "Beacon Color") as null|anything in GLOB.marker_beacon_colors if(!istype(user) || !user.canUseTopic(src, BE_CLOSE)) return diff --git a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm index b1af34eb..e9f76737 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm @@ -29,6 +29,7 @@ /mob/living/simple_animal/hostile/guardian/bomb/AltClickOn(atom/movable/A) if(!istype(A)) + altclick_listed_turf(A) return if(loc == summoner) to_chat(src, "You must be manifested to create bombs!") diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm index add18b6b..2b1b330d 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/support.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm @@ -105,8 +105,9 @@ /mob/living/simple_animal/hostile/guardian/healer/AltClickOn(atom/movable/A) if(!istype(A)) + altclick_listed_turf(A) return - if(src.loc == summoner) + if(loc == summoner) to_chat(src, "You must be manifested to warp a target!") return if(!beacon) diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm index b1694a61..d2b23d5e 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/drake.dm @@ -267,6 +267,7 @@ Difficulty: Medium /mob/living/simple_animal/hostile/megafauna/dragon/AltClickOn(atom/movable/A) if(!istype(A)) + altclick_listed_turf(A) return if(swoop_cooldown >= world.time) to_chat(src, "You need to wait 20 seconds between swoop attacks!") diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm index d96889cf..397d4092 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/hierophant.dm @@ -442,7 +442,7 @@ Difficulty: Normal /mob/living/simple_animal/hostile/megafauna/hierophant/AltClickOn(atom/A) //player control handler(don't give this to a player holy fuck) if(!istype(A) || get_dist(A, src) <= 2) - return + return altclick_listed_turf(A) blink(A) //Hierophant overlays diff --git a/code/modules/mob/living/ventcrawling.dm b/code/modules/mob/living/ventcrawling.dm index 93065622..8e94a6ff 100644 --- a/code/modules/mob/living/ventcrawling.dm +++ b/code/modules/mob/living/ventcrawling.dm @@ -8,6 +8,7 @@ GLOBAL_LIST_INIT(ventcrawl_machinery, typecacheof(list( /mob/living/proc/handle_ventcrawl(atom/A) if(!ventcrawler || !Adjacent(A)) return + . = TRUE //return value to stop the client from being shown the turf contents stat tab on alt-click. if(stat) to_chat(src, "You must be conscious to do this!") return diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 8a8dbbc0..6aa8fa94 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -126,7 +126,7 @@ portable_drive.verb_pickup() /obj/item/modular_computer/AltClick(mob/user) - ..() + . = ..() if(issilicon(user)) return @@ -142,7 +142,7 @@ return if(ai_slot) ai_slot.try_eject(null, user) - + return TRUE // Gets IDs/access levels from card slot. Would be useful when/if PDAs would become modular PCs. /obj/item/modular_computer/GetAccess() diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm index 4d4dee1b..ce8ab965 100644 --- a/code/modules/modular_computers/computers/item/laptop.dm +++ b/code/modules/modular_computers/computers/item/laptop.dm @@ -86,8 +86,8 @@ /obj/item/modular_computer/laptop/AltClick(mob/user) if(screen_on) // Close it. try_toggle_open(user) - else - return ..() + return TRUE + return ..() /obj/item/modular_computer/laptop/proc/toggle_open(mob/living/user=null) if(screen_on) diff --git a/code/modules/modular_computers/computers/machinery/modular_computer.dm b/code/modules/modular_computers/computers/machinery/modular_computer.dm index 19d3b560..13669cae 100644 --- a/code/modules/modular_computers/computers/machinery/modular_computer.dm +++ b/code/modules/modular_computers/computers/machinery/modular_computer.dm @@ -92,8 +92,9 @@ cpu.eject_card() /obj/machinery/modular_computer/AltClick(mob/user) + . = ..() if(cpu) - cpu.AltClick(user) + return cpu.AltClick(user) //ATTACK HAND IGNORING PARENT RETURN VALUE // On-click handling. Turns on the computer if it's off and opens the GUI. diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 849e8621..47e3f0ce 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -122,6 +122,7 @@ . += "Alt-click [src] to fold it into a paper plane." /obj/item/paper/AltClick(mob/living/carbon/user, obj/item/I) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user), NO_TK)) return to_chat(user, "You fold [src] into the shape of a plane!") @@ -134,3 +135,4 @@ I = new plane_type(user, src) user.put_in_hands(I) + return TRUE diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm index 03d702cf..d1e86891 100644 --- a/code/modules/photography/camera/camera.dm +++ b/code/modules/photography/camera/camera.dm @@ -1,214 +1,215 @@ - -#define CAMERA_PICTURE_SIZE_HARD_LIMIT 21 - -/obj/item/camera - name = "camera" - icon = 'icons/obj/items_and_weapons.dmi' - desc = "A polaroid camera." - icon_state = "camera" - item_state = "camera" - lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' - righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' - light_color = LIGHT_COLOR_WHITE - light_power = FLASH_LIGHT_POWER - w_class = WEIGHT_CLASS_SMALL - flags_1 = CONDUCT_1 - slot_flags = ITEM_SLOT_NECK - materials = list(MAT_METAL = 50, MAT_GLASS = 150) - var/flash_enabled = TRUE - var/state_on = "camera" - var/state_off = "camera_off" - var/pictures_max = 10 - var/pictures_left = 10 - var/on = TRUE - var/cooldown = 64 - var/blending = FALSE //lets not take pictures while the previous is still processing! - var/see_ghosts = CAMERA_NO_GHOSTS //for the spoop of it - var/obj/item/disk/holodisk/disk - var/sound/custom_sound - var/silent = FALSE - var/picture_size_x = 2 - var/picture_size_y = 2 - var/picture_size_x_min = 1 - var/picture_size_y_min = 1 - var/picture_size_x_max = 4 - var/picture_size_y_max = 4 - -/obj/item/camera/attack_self(mob/user) - if(!disk) - return - to_chat(user, "You eject [disk] out the back of [src].") - user.put_in_hands(disk) - disk = null - -/obj/item/camera/examine(mob/user) - . = ..() - . += "Alt-click to change its focusing, allowing you to set how big of an area it will capture." - -/obj/item/camera/AltClick(mob/user) - if(!user.canUseTopic(src, BE_CLOSE)) - return - var/desired_x = input(user, "How high do you want the camera to shoot, between [picture_size_x_min] and [picture_size_x_max]?", "Zoom", picture_size_x) as num - var/desired_y = input(user, "How wide do you want the camera to shoot, between [picture_size_y_min] and [picture_size_y_max]?", "Zoom", picture_size_y) as num - picture_size_x = min(CLAMP(desired_x, picture_size_x_min, picture_size_x_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) - picture_size_y = min(CLAMP(desired_y, picture_size_y_min, picture_size_y_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) - - -/obj/item/camera/attack(mob/living/carbon/human/M, mob/user) - return - -/obj/item/camera/attackby(obj/item/I, mob/user, params) - if(istype(I, /obj/item/camera_film)) - if(pictures_left) - to_chat(user, "[src] still has some film in it!") - return - if(!user.temporarilyRemoveItemFromInventory(I)) - return - to_chat(user, "You insert [I] into [src].") - qdel(I) - pictures_left = pictures_max - return - if(istype(I, /obj/item/disk/holodisk)) - if (!disk) - if(!user.transferItemToLoc(I, src)) - to_chat(user, "[I] is stuck to your hand!") - return TRUE - to_chat(user, "You slide [I] into the back of [src].") - disk = I - else - to_chat(user, "There's already a disk inside [src].") - return TRUE //no afterattack - ..() - -/obj/item/camera/examine(mob/user) - . = ..() - . += "It has [pictures_left] photos left." - -//user can be atom or mob -/obj/item/camera/proc/can_target(atom/target, mob/user, prox_flag) - if(!on || blending || !pictures_left) - return FALSE - var/turf/T = get_turf(target) - if(!T) - return FALSE - if(istype(user)) - if(isAI(user) && !GLOB.cameranet.checkTurfVis(T)) - return FALSE - else if(user.client && !(get_turf(target) in get_hear(user.client.view, user))) - return FALSE - else if(!(get_turf(target) in get_hear(world.view, user))) - return FALSE - else //user is an atom - if(!(get_turf(target) in view(world.view, user))) - return FALSE - return TRUE - -/obj/item/camera/afterattack(atom/target, mob/user, flag) - if (disk) - if(ismob(target)) - if (disk.record) - QDEL_NULL(disk.record) - - disk.record = new - var/mob/M = target - disk.record.caller_name = M.name - disk.record.set_caller_image(M) - else - to_chat(user, "Invalid holodisk target.") - return - - if(!can_target(target, user, flag)) - return - - on = FALSE - - var/realcooldown = cooldown - var/mob/living/carbon/human/H = user - if (HAS_TRAIT(H, TRAIT_PHOTOGRAPHER)) - realcooldown *= 0.5 - addtimer(CALLBACK(src, .proc/cooldown), realcooldown) - - icon_state = state_off - - INVOKE_ASYNC(src, .proc/captureimage, target, user, flag, picture_size_x - 1, picture_size_y - 1) - - -/obj/item/camera/proc/cooldown() - UNTIL(!blending) - icon_state = state_on - on = TRUE - -/obj/item/camera/proc/show_picture(mob/user, datum/picture/selection) - var/obj/item/photo/P = new(src, selection) - P.show(user) - to_chat(user, P.desc) - qdel(P) - -/obj/item/camera/proc/captureimage(atom/target, mob/user, flag, size_x = 1, size_y = 1) - if(flash_enabled) - flash_lighting_fx(8, light_power, light_color) - blending = TRUE - var/turf/target_turf = get_turf(target) - if(!isturf(target_turf)) - blending = FALSE - return FALSE - size_x = CLAMP(size_x, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) - size_y = CLAMP(size_y, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) - var/list/desc = list("This is a photo of an area of [size_x+1] meters by [size_y+1] meters.") - var/ai_user = isAI(user) - var/list/seen - var/list/viewlist = (user && user.client)? getviewsize(user.client.view) : getviewsize(world.view) - var/viewr = max(viewlist[1], viewlist[2]) + max(size_x, size_y) - var/viewc = user.client? user.client.eye : target - seen = get_hear(viewr, viewc) - var/list/turfs = list() - var/list/mobs = list() - var/blueprints = FALSE - var/clone_area = SSmapping.RequestBlockReservation(size_x * 2 + 1, size_y * 2 + 1) - for(var/turf/T in block(locate(target_turf.x - size_x, target_turf.y - size_y, target_turf.z), locate(target_turf.x + size_x, target_turf.y + size_y, target_turf.z))) - if((ai_user && GLOB.cameranet.checkTurfVis(T)) || (T in seen)) - turfs += T - for(var/mob/M in T) - mobs += M - if(locate(/obj/item/areaeditor/blueprints) in T) - blueprints = TRUE - for(var/i in mobs) - var/mob/M = i - desc += M.get_photo_description(src) - - var/psize_x = (size_x * 2 + 1) * world.icon_size - var/psize_y = (size_y * 2 + 1) * world.icon_size - var/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1)) - qdel(clone_area) - var/icon/temp = icon('icons/effects/96x96.dmi',"") - temp.Blend("#000", ICON_OVERLAY) - temp.Scale(psize_x, psize_y) - temp.Blend(get_icon, ICON_OVERLAY) - - var/datum/picture/P = new("picture", desc.Join(" "), temp, null, psize_x, psize_y, blueprints) - after_picture(user, P, flag) - blending = FALSE - -/obj/item/camera/proc/after_picture(mob/user, datum/picture/picture, proximity_flag) - printpicture(user, picture) - -/obj/item/camera/proc/printpicture(mob/user, datum/picture/picture) //Normal camera proc for creating photos - var/obj/item/photo/p = new(get_turf(src), picture) - if(in_range(src, user)) //needed because of TK - user.put_in_hands(p) - pictures_left-- - to_chat(user, "[pictures_left] photos left.") - var/customize = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No") - if(customize == "Yes") - var/name1 = stripped_input(user, "Set a name for this photo, or leave blank. 32 characters max.", "Name", max_length = 32) - var/desc1 = stripped_input(user, "Set a description to add to photo, or leave blank. 128 characters max.", "Caption", max_length = 128) - var/caption = stripped_input(user, "Set a caption for this photo, or leave blank. 256 characters max.", "Caption", max_length = 256) - if(name1) - picture.picture_name = name1 - if(desc1) - picture.picture_desc = "[desc1] - [picture.picture_desc]" - if(caption) - picture.caption = caption - p.set_picture(picture, TRUE, TRUE) - if(CONFIG_GET(flag/picture_logging_camera)) - picture.log_to_file() + +#define CAMERA_PICTURE_SIZE_HARD_LIMIT 21 + +/obj/item/camera + name = "camera" + icon = 'icons/obj/items_and_weapons.dmi' + desc = "A polaroid camera." + icon_state = "camera" + item_state = "camera" + lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi' + righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi' + light_color = LIGHT_COLOR_WHITE + light_power = FLASH_LIGHT_POWER + w_class = WEIGHT_CLASS_SMALL + flags_1 = CONDUCT_1 + slot_flags = ITEM_SLOT_NECK + materials = list(MAT_METAL = 50, MAT_GLASS = 150) + var/flash_enabled = TRUE + var/state_on = "camera" + var/state_off = "camera_off" + var/pictures_max = 10 + var/pictures_left = 10 + var/on = TRUE + var/cooldown = 64 + var/blending = FALSE //lets not take pictures while the previous is still processing! + var/see_ghosts = CAMERA_NO_GHOSTS //for the spoop of it + var/obj/item/disk/holodisk/disk + var/sound/custom_sound + var/silent = FALSE + var/picture_size_x = 2 + var/picture_size_y = 2 + var/picture_size_x_min = 1 + var/picture_size_y_min = 1 + var/picture_size_x_max = 4 + var/picture_size_y_max = 4 + +/obj/item/camera/attack_self(mob/user) + if(!disk) + return + to_chat(user, "You eject [disk] out the back of [src].") + user.put_in_hands(disk) + disk = null + +/obj/item/camera/examine(mob/user) + . = ..() + . += "Alt-click to change its focusing, allowing you to set how big of an area it will capture." + +/obj/item/camera/AltClick(mob/user) + . = ..() + if(!user.canUseTopic(src, BE_CLOSE)) + return + var/desired_x = input(user, "How high do you want the camera to shoot, between [picture_size_x_min] and [picture_size_x_max]?", "Zoom", picture_size_x) as num + var/desired_y = input(user, "How wide do you want the camera to shoot, between [picture_size_y_min] and [picture_size_y_max]?", "Zoom", picture_size_y) as num + picture_size_x = min(CLAMP(desired_x, picture_size_x_min, picture_size_x_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) + picture_size_y = min(CLAMP(desired_y, picture_size_y_min, picture_size_y_max), CAMERA_PICTURE_SIZE_HARD_LIMIT) + return TRUE + +/obj/item/camera/attack(mob/living/carbon/human/M, mob/user) + return + +/obj/item/camera/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/camera_film)) + if(pictures_left) + to_chat(user, "[src] still has some film in it!") + return + if(!user.temporarilyRemoveItemFromInventory(I)) + return + to_chat(user, "You insert [I] into [src].") + qdel(I) + pictures_left = pictures_max + return + if(istype(I, /obj/item/disk/holodisk)) + if (!disk) + if(!user.transferItemToLoc(I, src)) + to_chat(user, "[I] is stuck to your hand!") + return TRUE + to_chat(user, "You slide [I] into the back of [src].") + disk = I + else + to_chat(user, "There's already a disk inside [src].") + return TRUE //no afterattack + ..() + +/obj/item/camera/examine(mob/user) + . = ..() + . += "It has [pictures_left] photos left." + +//user can be atom or mob +/obj/item/camera/proc/can_target(atom/target, mob/user, prox_flag) + if(!on || blending || !pictures_left) + return FALSE + var/turf/T = get_turf(target) + if(!T) + return FALSE + if(istype(user)) + if(isAI(user) && !GLOB.cameranet.checkTurfVis(T)) + return FALSE + else if(user.client && !(get_turf(target) in get_hear(user.client.view, user))) + return FALSE + else if(!(get_turf(target) in get_hear(world.view, user))) + return FALSE + else //user is an atom + if(!(get_turf(target) in view(world.view, user))) + return FALSE + return TRUE + +/obj/item/camera/afterattack(atom/target, mob/user, flag) + if (disk) + if(ismob(target)) + if (disk.record) + QDEL_NULL(disk.record) + + disk.record = new + var/mob/M = target + disk.record.caller_name = M.name + disk.record.set_caller_image(M) + else + to_chat(user, "Invalid holodisk target.") + return + + if(!can_target(target, user, flag)) + return + + on = FALSE + + var/realcooldown = cooldown + var/mob/living/carbon/human/H = user + if (HAS_TRAIT(H, TRAIT_PHOTOGRAPHER)) + realcooldown *= 0.5 + addtimer(CALLBACK(src, .proc/cooldown), realcooldown) + + icon_state = state_off + + INVOKE_ASYNC(src, .proc/captureimage, target, user, flag, picture_size_x - 1, picture_size_y - 1) + + +/obj/item/camera/proc/cooldown() + UNTIL(!blending) + icon_state = state_on + on = TRUE + +/obj/item/camera/proc/show_picture(mob/user, datum/picture/selection) + var/obj/item/photo/P = new(src, selection) + P.show(user) + to_chat(user, P.desc) + qdel(P) + +/obj/item/camera/proc/captureimage(atom/target, mob/user, flag, size_x = 1, size_y = 1) + if(flash_enabled) + flash_lighting_fx(8, light_power, light_color) + blending = TRUE + var/turf/target_turf = get_turf(target) + if(!isturf(target_turf)) + blending = FALSE + return FALSE + size_x = CLAMP(size_x, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) + size_y = CLAMP(size_y, 0, CAMERA_PICTURE_SIZE_HARD_LIMIT) + var/list/desc = list("This is a photo of an area of [size_x+1] meters by [size_y+1] meters.") + var/ai_user = isAI(user) + var/list/seen + var/list/viewlist = (user && user.client)? getviewsize(user.client.view) : getviewsize(world.view) + var/viewr = max(viewlist[1], viewlist[2]) + max(size_x, size_y) + var/viewc = user.client? user.client.eye : target + seen = get_hear(viewr, viewc) + var/list/turfs = list() + var/list/mobs = list() + var/blueprints = FALSE + var/clone_area = SSmapping.RequestBlockReservation(size_x * 2 + 1, size_y * 2 + 1) + for(var/turf/T in block(locate(target_turf.x - size_x, target_turf.y - size_y, target_turf.z), locate(target_turf.x + size_x, target_turf.y + size_y, target_turf.z))) + if((ai_user && GLOB.cameranet.checkTurfVis(T)) || (T in seen)) + turfs += T + for(var/mob/M in T) + mobs += M + if(locate(/obj/item/areaeditor/blueprints) in T) + blueprints = TRUE + for(var/i in mobs) + var/mob/M = i + desc += M.get_photo_description(src) + + var/psize_x = (size_x * 2 + 1) * world.icon_size + var/psize_y = (size_y * 2 + 1) * world.icon_size + var/get_icon = camera_get_icon(turfs, target_turf, psize_x, psize_y, clone_area, size_x, size_y, (size_x * 2 + 1), (size_y * 2 + 1)) + qdel(clone_area) + var/icon/temp = icon('icons/effects/96x96.dmi',"") + temp.Blend("#000", ICON_OVERLAY) + temp.Scale(psize_x, psize_y) + temp.Blend(get_icon, ICON_OVERLAY) + + var/datum/picture/P = new("picture", desc.Join(" "), temp, null, psize_x, psize_y, blueprints) + after_picture(user, P, flag) + blending = FALSE + +/obj/item/camera/proc/after_picture(mob/user, datum/picture/picture, proximity_flag) + printpicture(user, picture) + +/obj/item/camera/proc/printpicture(mob/user, datum/picture/picture) //Normal camera proc for creating photos + var/obj/item/photo/p = new(get_turf(src), picture) + if(in_range(src, user)) //needed because of TK + user.put_in_hands(p) + pictures_left-- + to_chat(user, "[pictures_left] photos left.") + var/customize = alert(user, "Do you want to customize the photo?", "Customization", "Yes", "No") + if(customize == "Yes") + var/name1 = stripped_input(user, "Set a name for this photo, or leave blank. 32 characters max.", "Name", max_length = 32) + var/desc1 = stripped_input(user, "Set a description to add to photo, or leave blank. 128 characters max.", "Caption", max_length = 128) + var/caption = stripped_input(user, "Set a caption for this photo, or leave blank. 256 characters max.", "Caption", max_length = 256) + if(name1) + picture.picture_name = name1 + if(desc1) + picture.picture_desc = "[desc1] - [picture.picture_desc]" + if(caption) + picture.caption = caption + p.set_picture(picture, TRUE, TRUE) + if(CONFIG_GET(flag/picture_logging_camera)) + picture.log_to_file() diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index ad59211f..d3348774 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -748,11 +748,11 @@ return FALSE /obj/machinery/power/apc/AltClick(mob/user) - ..() + . = ..() if(!user.canUseTopic(src, !issilicon(user)) || !isturf(loc)) return - else - togglelock(user) + togglelock(user) + return TRUE /obj/machinery/power/apc/proc/togglelock(mob/living/user) if(obj_flags & EMAGGED) diff --git a/code/modules/projectiles/guns/ballistic/shotgun.dm b/code/modules/projectiles/guns/ballistic/shotgun.dm index 6ca47af1..0105defd 100644 --- a/code/modules/projectiles/guns/ballistic/shotgun.dm +++ b/code/modules/projectiles/guns/ballistic/shotgun.dm @@ -222,10 +222,11 @@ spread = 2 /obj/item/gun/ballistic/shotgun/automatic/combat/compact/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return toggle_stock(user) - . = ..() + return TRUE /obj/item/gun/ballistic/shotgun/automatic/combat/compact/examine(mob/user) . = ..() @@ -286,8 +287,10 @@ to_chat(user, "You switch to tube A.") /obj/item/gun/ballistic/shotgun/automatic/dual_tube/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) return pump() + return TRUE // DOUBLE BARRELED SHOTGUN and IMPROVISED SHOTGUN are in revolver.dm diff --git a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm index ba791f83..f5f0dfb0 100644 --- a/code/modules/reagents/chemistry/machinery/chem_dispenser.dm +++ b/code/modules/reagents/chemistry/machinery/chem_dispenser.dm @@ -442,10 +442,11 @@ return final_list /obj/machinery/chem_dispenser/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) return replace_beaker(user) - return + return TRUE /obj/machinery/chem_dispenser/drinks/Initialize() . = ..() diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index fbd85d24..0900eb52 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -29,10 +29,11 @@ icon_state = "mixer0b" /obj/machinery/chem_heater/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) return replace_beaker(user) - return + return TRUE /obj/machinery/chem_heater/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) if(beaker) diff --git a/code/modules/reagents/chemistry/machinery/pandemic.dm b/code/modules/reagents/chemistry/machinery/pandemic.dm index 1afd8b74..1254932c 100644 --- a/code/modules/reagents/chemistry/machinery/pandemic.dm +++ b/code/modules/reagents/chemistry/machinery/pandemic.dm @@ -237,10 +237,11 @@ return ..() /obj/machinery/computer/pandemic/AltClick(mob/living/user) + . = ..() if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) return replace_beaker(user) - return + return TRUE /obj/machinery/computer/pandemic/proc/replace_beaker(mob/living/user, obj/item/reagent_containers/new_beaker) if(beaker) diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 432a35ea..a5acce99 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -344,8 +344,10 @@ return FALSE /obj/item/hypospray/mkii/AltClick(mob/user) + . = ..() if(vial) vial.attack_self(user) + return TRUE // Gunna allow this for now, still really don't approve - Pooj /obj/item/hypospray/mkii/emag_act(mob/user) diff --git a/code/modules/reagents/reagent_containers/rags.dm b/code/modules/reagents/reagent_containers/rags.dm index 15026312..b0857692 100644 --- a/code/modules/reagents/reagent_containers/rags.dm +++ b/code/modules/reagents/reagent_containers/rags.dm @@ -88,13 +88,25 @@ if(reagents.total_volume && user.canUseTopic(src, BE_CLOSE)) to_chat(user, "You start squeezing the liquids out of \the [src]...") if(do_after(user, action_speed, TRUE, src)) - to_chat(user, "You squeeze \the [src] dry.") - var/atom/react_loc = get_turf(src) - if(ismob(react_loc)) - react_loc = react_loc.loc - if(react_loc) - reagents.reaction(react_loc, TOUCH) - reagents.clear_reagents() + var/msg = "You squeeze \the [src]" + var/obj/item/target + if(Adjacent(user)) //Allows the user to drain the reagents into a beaker if adjacent (no telepathy). + for(var/obj/item/I in user.held_items) + if(I == src) + continue + if(I.is_open_container() && !I.reagents.holder_full()) + target = I + break + if(!target) + msg += " dry" + reagents.reaction(get_turf(src), TOUCH) + reagents.clear_reagents() + else + msg += "'s liquids into \the [target]" + reagents.trans_to(target, reagents.total_volume) + to_chat(user, "[msg].") + return TRUE + /obj/item/reagent_containers/rag/towel name = "towel" @@ -177,4 +189,4 @@ extinguish_efficiency = 5 action_speed = 15 damp_threshold = 0.8 - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 50, "acid" = 50) //items don't provide armor to wearers unlike clothing yet. \ No newline at end of file + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 20, "bio" = 20, "rad" = 20, "fire" = 50, "acid" = 50) //items don't provide armor to wearers unlike clothing yet. diff --git a/code/modules/research/nanites/nanite_hijacker.dm b/code/modules/research/nanites/nanite_hijacker.dm index 88779df4..920c42b4 100644 --- a/code/modules/research/nanites/nanite_hijacker.dm +++ b/code/modules/research/nanites/nanite_hijacker.dm @@ -14,6 +14,7 @@ return if(disk) eject() + return TRUE /obj/item/nanite_hijacker/examine(mob/user) . = ..() diff --git a/code/modules/research/nanites/nanite_remote.dm b/code/modules/research/nanites/nanite_remote.dm index 46d8f297..dcfd3b95 100644 --- a/code/modules/research/nanites/nanite_remote.dm +++ b/code/modules/research/nanites/nanite_remote.dm @@ -35,6 +35,7 @@ update_icon() else to_chat(user, "Access denied.") + return TRUE /obj/item/nanite_remote/emag_act(mob/user) if(obj_flags & EMAGGED) diff --git a/code/modules/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/ruins/spaceruin_code/hilbertshotel.dm index 0d4d2b6a..b0fbd56e 100644 --- a/code/modules/ruins/spaceruin_code/hilbertshotel.dm +++ b/code/modules/ruins/spaceruin_code/hilbertshotel.dm @@ -276,14 +276,15 @@ GLOBAL_VAR_INIT(hhmysteryRoomNumber, 1337) promptExit(user) /turf/closed/indestructible/hoteldoor/AltClick(mob/user) - . = ..() - if(get_dist(get_turf(src), get_turf(user)) <= 1) - to_chat(user, "You peak through the door's bluespace peephole...") - user.reset_perspective(parentSphere) - user.set_machine(src) - var/datum/action/peepholeCancel/PHC = new - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - PHC.Grant(user) + . = ..() + if(get_dist(get_turf(src), get_turf(user)) <= 1) + to_chat(user, "You peak through the door's bluespace peephole...") + user.reset_perspective(parentSphere) + user.set_machine(src) + var/datum/action/peepholeCancel/PHC = new + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + PHC.Grant(user) + return TRUE /turf/closed/indestructible/hoteldoor/check_eye(mob/user) if(get_dist(get_turf(src), get_turf(user)) >= 2) diff --git a/code/modules/vehicles/ridden.dm b/code/modules/vehicles/ridden.dm index 25f54d88..13a139ef 100644 --- a/code/modules/vehicles/ridden.dm +++ b/code/modules/vehicles/ridden.dm @@ -1,78 +1,79 @@ -/obj/vehicle/ridden - name = "ridden vehicle" - can_buckle = TRUE - max_buckled_mobs = 1 - buckle_lying = FALSE - default_driver_move = FALSE - var/legs_required = 1 - var/arms_required = 0 //why not? - -/obj/vehicle/ridden/Initialize() - . = ..() - LoadComponent(/datum/component/riding) - -/obj/vehicle/ridden/examine(mob/user) - . = ..() - if(key_type) - if(!inserted_key) - . += "Put a key inside it by clicking it with the key." - else - . += "Alt-click [src] to remove the key." - -/obj/vehicle/ridden/generate_action_type(actiontype) - var/datum/action/vehicle/ridden/A = ..() - . = A - if(istype(A)) - A.vehicle_ridden_target = src - -/obj/vehicle/ridden/post_unbuckle_mob(mob/living/M) - remove_occupant(M) - return ..() - -/obj/vehicle/ridden/post_buckle_mob(mob/living/M) - add_occupant(M) - if(M.get_num_legs() < legs_required) - to_chat(M, "You don't have enough legs to operate the pedals!") - unbuckle_mob(M) - return ..() - -/obj/vehicle/ridden/attackby(obj/item/I, mob/user, params) - if(key_type && !is_key(inserted_key) && is_key(I)) - if(user.transferItemToLoc(I, src)) - to_chat(user, "You insert \the [I] into \the [src].") - if(inserted_key) //just in case there's an invalid key - inserted_key.forceMove(drop_location()) - inserted_key = I - else - to_chat(user, "[I] seems to be stuck to your hand!") - return - return ..() - -/obj/vehicle/ridden/AltClick(mob/user) - if(inserted_key && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) - if(!is_occupant(user)) - to_chat(user, "You must be riding the [src] to remove [src]'s key!") - return - to_chat(user, "You remove \the [inserted_key] from \the [src].") - inserted_key.forceMove(drop_location()) - user.put_in_hands(inserted_key) - inserted_key = null - return ..() - -/obj/vehicle/ridden/driver_move(mob/user, direction) - if(key_type && !is_key(inserted_key)) - to_chat(user, "[src] has no key inserted!") - return FALSE - var/datum/component/riding/R = GetComponent(/datum/component/riding) - R.handle_ride(user, direction) - return ..() - -/obj/vehicle/ridden/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE) - if(!in_range(user, src) || !in_range(M, src)) - return FALSE - . = ..(M, user, FALSE) - -/obj/vehicle/ridden/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE) - if(!force && occupant_amount() >= max_occupants) - return FALSE - return ..() +/obj/vehicle/ridden + name = "ridden vehicle" + can_buckle = TRUE + max_buckled_mobs = 1 + buckle_lying = FALSE + default_driver_move = FALSE + var/legs_required = 1 + var/arms_required = 0 //why not? + +/obj/vehicle/ridden/Initialize() + . = ..() + LoadComponent(/datum/component/riding) + +/obj/vehicle/ridden/examine(mob/user) + . = ..() + if(key_type) + if(!inserted_key) + . += "Put a key inside it by clicking it with the key." + else + . += "Alt-click [src] to remove the key." + +/obj/vehicle/ridden/generate_action_type(actiontype) + var/datum/action/vehicle/ridden/A = ..() + . = A + if(istype(A)) + A.vehicle_ridden_target = src + +/obj/vehicle/ridden/post_unbuckle_mob(mob/living/M) + remove_occupant(M) + return ..() + +/obj/vehicle/ridden/post_buckle_mob(mob/living/M) + add_occupant(M) + if(M.get_num_legs() < legs_required) + to_chat(M, "You don't have enough legs to operate the pedals!") + unbuckle_mob(M) + return ..() + +/obj/vehicle/ridden/attackby(obj/item/I, mob/user, params) + if(key_type && !is_key(inserted_key) && is_key(I)) + if(user.transferItemToLoc(I, src)) + to_chat(user, "You insert \the [I] into \the [src].") + if(inserted_key) //just in case there's an invalid key + inserted_key.forceMove(drop_location()) + inserted_key = I + else + to_chat(user, "[I] seems to be stuck to your hand!") + return + return ..() + +/obj/vehicle/ridden/AltClick(mob/user) + . = ..() + if(inserted_key && user.canUseTopic(src, BE_CLOSE, ismonkey(user))) + if(!is_occupant(user)) + to_chat(user, "You must be riding the [src] to remove [src]'s key!") + return + to_chat(user, "You remove \the [inserted_key] from \the [src].") + inserted_key.forceMove(drop_location()) + user.put_in_hands(inserted_key) + inserted_key = null + return TRUE + +/obj/vehicle/ridden/driver_move(mob/user, direction) + if(key_type && !is_key(inserted_key)) + to_chat(user, "[src] has no key inserted!") + return FALSE + var/datum/component/riding/R = GetComponent(/datum/component/riding) + R.handle_ride(user, direction) + return ..() + +/obj/vehicle/ridden/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE) + if(!in_range(user, src) || !in_range(M, src)) + return FALSE + . = ..(M, user, FALSE) + +/obj/vehicle/ridden/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE) + if(!force && occupant_amount() >= max_occupants) + return FALSE + return ..() diff --git a/code/modules/vehicles/scooter.dm b/code/modules/vehicles/scooter.dm index 8c21b050..0dd7ff32 100644 --- a/code/modules/vehicles/scooter.dm +++ b/code/modules/vehicles/scooter.dm @@ -94,6 +94,7 @@ qdel(src) /obj/vehicle/ridden/scooter/skateboard/AltClick(mob/user) + . = ..() var/datum/component/riding/R = src.GetComponent(/datum/component/riding) if (!adjusted_speed) R.vehicle_move_delay = 0 @@ -103,6 +104,7 @@ R.vehicle_move_delay = 1 to_chat(user, "You adjust the wheels on [src] to make it go slower.") adjusted_speed = FALSE + return TRUE //CONSTRUCTION /obj/item/scooter_frame diff --git a/modular_citadel/code/modules/arousal/toys/dildos.dm b/modular_citadel/code/modules/arousal/toys/dildos.dm index 55a41acc..17d77071 100644 --- a/modular_citadel/code/modules/arousal/toys/dildos.dm +++ b/modular_citadel/code/modules/arousal/toys/dildos.dm @@ -37,15 +37,11 @@ name = "[sizeword][dildo_shape] [can_customize ? "custom " : ""][dildo_type]" /obj/item/dildo/AltClick(mob/living/user) - if(QDELETED(src)) - return - if(!isliving(user)) - return - if(isAI(user)) - return - if(user.stat > 0)//unconscious or dead + . = ..() + if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) return customize(user) + return TRUE /obj/item/dildo/proc/customize(mob/living/user) if(!can_customize) diff --git a/modular_citadel/code/modules/clothing/clothing.dm b/modular_citadel/code/modules/clothing/clothing.dm index b23e805f..843b7a84 100644 --- a/modular_citadel/code/modules/clothing/clothing.dm +++ b/modular_citadel/code/modules/clothing/clothing.dm @@ -56,7 +56,7 @@ add_overlay(tertiary_overlay) /obj/item/clothing/AltClick(mob/living/user) - ..() + . = ..() if(hasprimary | hassecondary | hastertiary) var/choice = input(user,"polychromic thread options", "Clothing Recolor") as null|anything in list("[hasprimary ? "Primary Color" : ""]", "[hassecondary ? "Secondary Color" : ""]", "[hastertiary ? "Tertiary Color" : ""]") //generates a list depending on the enabled overlays switch(choice) //Lets the list's options actually lead to something @@ -78,6 +78,7 @@ tertiary_color = sanitize_hexcolor(tertiary_color_input, desired_format=6, include_crunch=1) update_icon() user.regenerate_icons() + return TRUE /obj/item/clothing/examine(mob/user) . = ..() diff --git a/modular_citadel/code/modules/clothing/spacesuits/cydonian_armor.dm b/modular_citadel/code/modules/clothing/spacesuits/cydonian_armor.dm deleted file mode 100644 index d85b10fa..00000000 --- a/modular_citadel/code/modules/clothing/spacesuits/cydonian_armor.dm +++ /dev/null @@ -1,176 +0,0 @@ -/* - CYDONIAN ARMOR THAT IS RGB AND STUFF WOOOOOOOOOO -*/ - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight - name = "cydonian helmet" - desc = "A helmet designed with both form and function in mind, it protects the user against physical trauma and hazardous conditions while also having polychromic light strips." - icon = 'modular_citadel/icons/lavaknight/item/head.dmi' - icon_state = "knight_cydonia" - item_state = "knight_yellow" - item_color = null - alternate_worn_icon = 'modular_citadel/icons/lavaknight/mob/head.dmi' - max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | LAVA_PROOF - heat_protection = HEAD - armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100) - brightness_on = 7 - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator) - var/energy_color = "#35FFF0" - var/obj/item/clothing/suit/space/hardsuit/lavaknight/linkedsuit = null - mutantrace_variation = NO_MUTANTRACE_VARIATION - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/New() - ..() - if(istype(loc, /obj/item/clothing/suit/space/hardsuit/lavaknight)) - linkedsuit = loc - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/attack_self(mob/user) - on = !on - - if(on) - set_light(brightness_on) - else - set_light(0) - for(var/X in actions) - var/datum/action/A = X - A.UpdateButtonIcon() - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/update_icon() - var/mutable_appearance/helm_overlay = mutable_appearance('modular_citadel/icons/lavaknight/item/head.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - - if(energy_color) - helm_overlay.color = energy_color - - helm_overlay.plane = LIGHTING_PLANE + 1 //Magic number is used here because we have no ABOVE_LIGHTING_PLANE plane defined. Lighting plane is 15, HUD is 18 - - cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other - - add_overlay(helm_overlay) - - emissivelights() - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/equipped(mob/user, slot) - ..() - if(slot == SLOT_HEAD) - emissivelights() - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/dropped(mob/user) - ..() - emissivelightsoff() - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/proc/emissivelights(mob/user = usr) - var/mutable_appearance/energy_overlay = mutable_appearance('modular_citadel/icons/lavaknight/mob/head.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - energy_overlay.color = energy_color - energy_overlay.plane = LIGHTING_PLANE + 1 - user.cut_overlay(energy_overlay) //honk - user.add_overlay(energy_overlay) //honk - -/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/proc/emissivelightsoff(mob/user = usr) - user.cut_overlay() - linkedsuit.emissivelights() //HONK HONK HONK MAXIMUM SPAGHETTI - user.regenerate_icons() //honk - -/obj/item/clothing/suit/space/hardsuit/lavaknight - icon = 'modular_citadel/icons/lavaknight/item/suit.dmi' - icon_state = "knight_cydonia" - name = "cydonian armor" - desc = "A suit designed with both form and function in mind, it protects the user against physical trauma and hazardous conditions while also having polychromic light strips." - item_state = "swat_suit" - alternate_worn_icon = 'modular_citadel/icons/lavaknight/mob/suit.dmi' - max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT - resistance_flags = FIRE_PROOF | LAVA_PROOF - armor = list(melee = 50, bullet = 10, laser = 10, energy = 10, bomb = 50, bio = 100, rad = 50, fire = 100, acid = 100) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/storage/bag/ore, /obj/item/pickaxe) - helmettype = /obj/item/clothing/head/helmet/space/hardsuit/lavaknight - heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS - actions_types = list(/datum/action/item_action/toggle_helmet) - var/obj/item/clothing/head/helmet/space/hardsuit/lavaknight/linkedhelm - tauric = TRUE //Citadel Add for tauric hardsuits - - var/energy_color = "#35FFF0" - -/obj/item/clothing/suit/space/hardsuit/lavaknight/New() - ..() - if(helmet) - linkedhelm = helmet - light_color = energy_color - set_light(1) - -/obj/item/clothing/suit/space/hardsuit/lavaknight/Initialize() - ..() - update_icon() - -/obj/item/clothing/suit/space/hardsuit/lavaknight/update_icon() - var/mutable_appearance/suit_overlay - - if(taurmode == SNEK_TAURIC) - suit_overlay = mutable_appearance('modular_citadel/icons/mob/taur_naga.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - else if(taurmode == PAW_TAURIC) - suit_overlay = mutable_appearance('modular_citadel/icons/mob/taur_canine.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - else - suit_overlay = mutable_appearance('modular_citadel/icons/lavaknight/item/suit.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - - if(energy_color) - suit_overlay.color = energy_color - - suit_overlay.plane = LIGHTING_PLANE + 1 //Magic number is used here because we have no ABOVE_LIGHTING_PLANE plane defined. Lighting plane is 15. - - cut_overlays() //So that it doesn't keep stacking overlays non-stop on top of each other - - add_overlay(suit_overlay) - -/obj/item/clothing/suit/space/hardsuit/lavaknight/equipped(mob/user, slot) - ..() - if(slot == SLOT_WEAR_SUIT) - emissivelights() - -/obj/item/clothing/suit/space/hardsuit/lavaknight/dropped(mob/user) - ..() - emissivelightsoff() - -/obj/item/clothing/suit/space/hardsuit/lavaknight/proc/emissivelights(mob/user = usr) - var/mutable_appearance/energy_overlay - if(taurmode == SNEK_TAURIC) - energy_overlay = mutable_appearance('modular_citadel/icons/mob/taur_naga.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - else if(taurmode == PAW_TAURIC) - energy_overlay = mutable_appearance('modular_citadel/icons/mob/taur_canine.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - else - energy_overlay = mutable_appearance('modular_citadel/icons/lavaknight/mob/suit.dmi', "knight_cydonia_overlay", LIGHTING_LAYER + 1) - - energy_overlay.color = energy_color - energy_overlay.plane = LIGHTING_PLANE + 1 - user.cut_overlay(energy_overlay) //honk - user.add_overlay(energy_overlay) //honk - -/obj/item/clothing/suit/space/hardsuit/lavaknight/proc/emissivelightsoff(mob/user = usr) - user.cut_overlays() - user.regenerate_icons() //honk - -/obj/item/clothing/suit/space/hardsuit/lavaknight/AltClick(mob/living/user) - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return - if(!in_range(src, user)) - return - if(user.incapacitated() || !istype(user) || !in_range(src, user)) - return - - if(alert("Are you sure you want to recolor your armor stripes?", "Confirm Repaint", "Yes", "No") == "Yes") - var/energy_color_input = input(usr,"","Choose Energy Color",energy_color) as color|null - if(energy_color_input) - energy_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) - user.update_inv_wear_suit() - if(linkedhelm) - linkedhelm.energy_color = sanitize_hexcolor(energy_color_input, desired_format=6, include_crunch=1) - user.update_inv_head() - linkedhelm.update_icon() - update_icon() - user.update_inv_wear_suit() - light_color = energy_color - emissivelights() - update_light() - -/obj/item/clothing/suit/space/hardsuit/lavaknight/examine(mob/user) - . = ..() - . += "Alt-click to recolor it." diff --git a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm index 6918308d..65a407d0 100644 --- a/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm +++ b/modular_citadel/code/modules/mob/living/silicon/robot/dogborg_equipment.dm @@ -1,384 +1,381 @@ -/* -DOG BORG EQUIPMENT HERE -SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! -*/ - -/obj/item/dogborg/jaws - name = "Dogborg jaws" - desc = "The jaws of the debug errors oh god." - icon = 'icons/mob/dogborg.dmi' - flags_1 = CONDUCT_1 - force = 1 - throwforce = 0 - w_class = 3 - hitsound = 'sound/weapons/bite.ogg' - sharpness = IS_SHARP - -/obj/item/dogborg/jaws/big - name = "combat jaws" - desc = "The jaws of the law. Very sharp." - icon_state = "jaws" - force = 10 //Lowered to match secborg. No reason it should be more than a secborg's baton. - attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") - -/obj/item/dogborg/jaws/small - name = "puppy jaws" - desc = "Rubberized teeth designed to protect accidental harm. Sharp enough for specialized tasks however." - icon_state = "smalljaws" - force = 6 - attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") - var/status = 0 - -/obj/item/dogborg/jaws/attack(atom/A, mob/living/silicon/robot/user) - ..() - user.do_attack_animation(A, ATTACK_EFFECT_BITE) - -/obj/item/dogborg/jaws/small/attack_self(mob/user) - var/mob/living/silicon/robot/R = user - if(R.cell && R.cell.charge > 100) - if(R.emagged && status == 0) - name = "combat jaws" - icon_state = "jaws" - desc = "The jaws of the law." - force = 12 - attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") - status = 1 - to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") - else - name = "puppy jaws" - icon_state = "smalljaws" - desc = "The jaws of a small dog." - force = 5 - attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") - status = 0 - if(R.emagged) - to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") - update_icon() - -//Boop - -/obj/item/analyzer/nose - name = "boop module" - icon = 'icons/mob/dogborg.dmi' - icon_state = "nose" - desc = "The BOOP module" - flags_1 = CONDUCT_1 - force = 0 - throwforce = 0 - attack_verb = list("nuzzles", "pushes", "boops") - w_class = 1 - -/obj/item/analyzer/nose/attack_self(mob/user) - user.visible_message("[user] sniffs around the air.", "You sniff the air for gas traces.") - - var/turf/location = user.loc - if(!istype(location)) - return - - var/datum/gas_mixture/environment = location.return_air() - - var/pressure = environment.return_pressure() - var/total_moles = environment.total_moles() - - to_chat(user, "Results:") - if(abs(pressure - ONE_ATMOSPHERE) < 10) - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - else - to_chat(user, "Pressure: [round(pressure,0.1)] kPa") - if(total_moles) - var/list/env_gases = environment.gases - - var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles - var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles - var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles - var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles - GAS_GARBAGE_COLLECT(environment.gases) - - if(abs(n2_concentration - N2STANDARD) < 20) - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") - else - to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") - - if(abs(o2_concentration - O2STANDARD) < 2) - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") - else - to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") - - if(co2_concentration > 0.01) - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") - else - to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") - - if(plasma_concentration > 0.005) - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") - else - to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") - - - for(var/id in env_gases) - if(id in GLOB.hardcoded_gases) - continue - var/gas_concentration = env_gases[id]/total_moles - to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] %") - to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") - -/obj/item/analyzer/nose/AltClick(mob/user) //Barometer output for measuring when the next storm happens - . = ..() - -/obj/item/analyzer/nose/afterattack(atom/target, mob/user, proximity) - . = ..() - if(!proximity) - return - do_attack_animation(target, null, src) - user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") - -//Delivery -/obj/item/storage/bag/borgdelivery - name = "fetching storage" - desc = "Fetch the thing!" - icon = 'icons/mob/dogborg.dmi' - icon_state = "dbag" - w_class = WEIGHT_CLASS_BULKY - -/obj/item/storage/bag/borgdelivery/ComponentInitialize() - . = ..() - var/datum/component/storage/STR = GetComponent(/datum/component/storage) - STR.max_w_class = WEIGHT_CLASS_BULKY - STR.max_combined_w_class = 5 - STR.max_items = 1 - STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear, /obj/item/radio/intercom)) - -//Tongue stuff -/obj/item/soap/tongue - name = "synthetic tongue" - desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." - icon = 'icons/mob/dogborg.dmi' - icon_state = "synthtongue" - hitsound = 'sound/effects/attackblob.ogg' - cleanspeed = 80 - var/status = 0 - -/obj/item/soap/tongue/scrubpup - cleanspeed = 25 //slightly faster than a mop. - -/obj/item/soap/tongue/New() - ..() - item_flags |= NOBLUDGEON //No more attack messages - -/obj/item/soap/tongue/attack_self(mob/user) - var/mob/living/silicon/robot/R = user - if(R.cell && R.cell.charge > 100) - if(R.emagged && status == 0) - status = !status - name = "energized tongue" - desc = "Your tongue is energized for dangerously maximum efficency." - icon_state = "syndietongue" - to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") - cleanspeed = 10 //(nerf'd)tator soap stat - else - status = 0 - name = "synthetic tongue" - desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." - icon_state = "synthtongue" - cleanspeed = initial(cleanspeed) - if(R.emagged) - to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") - update_icon() - -/obj/item/soap/tongue/afterattack(atom/target, mob/user, proximity) - var/mob/living/silicon/robot/R = user - if(!proximity || !check_allowed_items(target)) - return - if(R.client && (target in R.client.screen)) - to_chat(R, "You need to take that [target.name] off before cleaning it!") - else if(is_cleanable(target)) - R.visible_message("[R] begins to lick off \the [target.name].", "You begin to lick off \the [target.name]...") - if(do_after(R, src.cleanspeed, target = target)) - if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. - return //If they moved away, you can't eat them. - to_chat(R, "You finish licking off \the [target.name].") - qdel(target) - R.cell.give(50) - else if(isobj(target)) //hoo boy. danger zone man - if(istype(target,/obj/item/trash)) - R.visible_message("[R] nibbles away at \the [target.name].", "You begin to nibble away at \the [target.name]...") - if(!do_after(R, src.cleanspeed, target = target)) - return //If they moved away, you can't eat them. - to_chat(R, "You finish off \the [target.name].") - qdel(target) - R.cell.give(250) - return - if(istype(target,/obj/item/stock_parts/cell)) - R.visible_message("[R] begins cramming \the [target.name] down its throat.", "You begin cramming \the [target.name] down your throat...") - if(!do_after(R, 50, target = target)) - return //If they moved away, you can't eat them. - to_chat(R, "You finish off \the [target.name].") - var/obj/item/stock_parts/cell/C = target - R.cell.charge = R.cell.charge + (C.charge / 3) //Instant full cell upgrades op idgaf - qdel(target) - return - var/obj/item/I = target //HAHA FUCK IT, NOT LIKE WE ALREADY HAVE A SHITTON OF WAYS TO REMOVE SHIT - if(!I.anchored && R.emagged) - R.visible_message("[R] begins chewing up \the [target.name]. Looks like it's trying to loophole around its diet restriction!", "You begin chewing up \the [target.name]...") - if(!do_after(R, 100, target = I)) //Nerf dat time yo - return //If they moved away, you can't eat them. - visible_message("[R] chews up \the [target.name] and cleans off the debris!") - to_chat(R, "You finish off \the [target.name].") - qdel(I) - R.cell.give(500) - return - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - else if(ishuman(target)) - var/mob/living/L = target - if(status == 0 && check_zone(R.zone_selected) == "head") - R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - if(istype(L) && L.fire_stacks > 0) - L.adjust_fire_stacks(-10) - return - else if(status == 0) - R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") - playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) - if(istype(L) && L.fire_stacks > 0) - L.adjust_fire_stacks(-10) - return - else - if(R.cell.charge <= 800) - to_chat(R, "Insufficent Power!") - return - L.Stun(4) // normal stunbaton is force 7 gimme a break good sir! - L.Knockdown(80) - L.apply_effect(EFFECT_STUTTER, 4) - L.visible_message("[R] has shocked [L] with its tongue!", \ - "[R] has shocked you with its tongue!") - playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) - R.cell.use(666) - log_combat(R, L, "tongue stunned") - - else if(istype(target, /obj/structure/window)) - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - if(do_after(user, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - target.set_opacity(initial(target.opacity)) - else - R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") - if(do_after(user, src.cleanspeed, target = target)) - to_chat(user, "You clean \the [target.name].") - var/obj/effect/decal/cleanable/C = locate() in target - qdel(C) - target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) - SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) - target.wash_cream() - target.wash_cum() - return - -//Dogfood - -/obj/item/trash/rkibble - name = "robo kibble" - desc = "A novelty bowl of assorted mech fabricator byproducts. Mockingly feed this to the sec-dog to help it recharge." - icon = 'icons/mob/dogborg.dmi' - icon_state= "kibble" - -//Defibs - -/obj/item/twohanded/shockpaddles/cyborg/hound - name = "Paws of Life" - desc = "MediHound specific shock paws." - icon = 'icons/mob/dogborg.dmi' - icon_state = "defibpaddles0" - item_state = "defibpaddles0" - -// Pounce stuff for K-9 - -/obj/item/dogborg/pounce - name = "pounce" - icon = 'icons/mob/dogborg.dmi' - icon_state = "pounce" - desc = "Leap at your target to momentarily stun them." - force = 0 - throwforce = 0 - -/obj/item/dogborg/pounce/New() - ..() - item_flags |= NOBLUDGEON - -/mob/living/silicon/robot - var/leaping = 0 - var/pounce_cooldown = 0 - var/pounce_cooldown_time = 20 //Buffed to counter balance changes - var/pounce_spoolup = 1 - var/leap_at - var/disabler - var/laser - var/sleeper_g - var/sleeper_r - var/sleeper_nv - -#define MAX_K9_LEAP_DIST 4 //because something's definitely borked the pounce functioning from a distance. - -/obj/item/dogborg/pounce/afterattack(atom/A, mob/user) - var/mob/living/silicon/robot/R = user - if(R && !R.pounce_cooldown) - R.pounce_cooldown = !R.pounce_cooldown - to_chat(R, "Your targeting systems lock on to [A]...") - addtimer(CALLBACK(R, /mob/living/silicon/robot.proc/leap_at, A), R.pounce_spoolup) - spawn(R.pounce_cooldown_time) - R.pounce_cooldown = !R.pounce_cooldown - else if(R && R.pounce_cooldown) - to_chat(R, "Your leg actuators are still recharging!") - -/mob/living/silicon/robot/proc/leap_at(atom/A) - if(leaping || stat || buckled || lying) - return - - if(!has_gravity(src) || !has_gravity(A)) - to_chat(src,"It is unsafe to leap without gravity!") - //It's also extremely buggy visually, so it's balance+bugfix - return - - if(cell.charge <= 750) - to_chat(src,"Insufficent reserves for jump actuators!") - return - - else - leaping = 1 - weather_immunities += "lava" - pixel_y = 10 - update_icons() - throw_at(A, MAX_K9_LEAP_DIST, 1, spin=0, diagonals_first = 1) - cell.use(750) //Less than a stunbaton since stunbatons hit everytime. - weather_immunities -= "lava" - -/mob/living/silicon/robot/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - - if(!leaping) - return ..() - - if(hit_atom) - if(isliving(hit_atom)) - var/mob/living/L = hit_atom - if(!L.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) - L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") - L.Knockdown(iscarbon(L) ? 225 : 45) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. - playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) - sleep(2)//Runtime prevention (infinite bump() calls on hulks) - step_towards(src,L) - log_combat(src, L, "borg pounced") - else - Knockdown(15, 1, 1) - - pounce_cooldown = !pounce_cooldown - spawn(pounce_cooldown_time) //3s by default - pounce_cooldown = !pounce_cooldown - else if(hit_atom.density && !hit_atom.CanPass(src)) - visible_message("[src] smashes into [hit_atom]!", "You smash into [hit_atom]!") - playsound(src, 'sound/items/trayhit1.ogg', 50, 1) - Knockdown(15, 1, 1) - - if(leaping) - leaping = 0 - pixel_y = initial(pixel_y) - update_icons() - update_canmove() +/* +DOG BORG EQUIPMENT HERE +SLEEPER CODE IS IN game/objects/items/devices/dogborg_sleeper.dm ! +*/ + +/obj/item/dogborg/jaws + name = "Dogborg jaws" + desc = "The jaws of the debug errors oh god." + icon = 'icons/mob/dogborg.dmi' + flags_1 = CONDUCT_1 + force = 1 + throwforce = 0 + w_class = 3 + hitsound = 'sound/weapons/bite.ogg' + sharpness = IS_SHARP + +/obj/item/dogborg/jaws/big + name = "combat jaws" + desc = "The jaws of the law. Very sharp." + icon_state = "jaws" + force = 10 //Lowered to match secborg. No reason it should be more than a secborg's baton. + attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") + +/obj/item/dogborg/jaws/small + name = "puppy jaws" + desc = "Rubberized teeth designed to protect accidental harm. Sharp enough for specialized tasks however." + icon_state = "smalljaws" + force = 6 + attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") + var/status = 0 + +/obj/item/dogborg/jaws/attack(atom/A, mob/living/silicon/robot/user) + ..() + user.do_attack_animation(A, ATTACK_EFFECT_BITE) + +/obj/item/dogborg/jaws/small/attack_self(mob/user) + var/mob/living/silicon/robot/R = user + if(R.cell && R.cell.charge > 100) + if(R.emagged && status == 0) + name = "combat jaws" + icon_state = "jaws" + desc = "The jaws of the law." + force = 12 + attack_verb = list("chomped", "bit", "ripped", "mauled", "enforced") + status = 1 + to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") + else + name = "puppy jaws" + icon_state = "smalljaws" + desc = "The jaws of a small dog." + force = 5 + attack_verb = list("nibbled", "bit", "gnawed", "chomped", "nommed") + status = 0 + if(R.emagged) + to_chat(user, "Your jaws are now [status ? "Combat" : "Pup'd"].") + update_icon() + +//Boop + +/obj/item/analyzer/nose + name = "boop module" + icon = 'icons/mob/dogborg.dmi' + icon_state = "nose" + desc = "The BOOP module" + flags_1 = CONDUCT_1 + force = 0 + throwforce = 0 + attack_verb = list("nuzzles", "pushes", "boops") + w_class = 1 + +/obj/item/analyzer/nose/attack_self(mob/user) + user.visible_message("[user] sniffs around the air.", "You sniff the air for gas traces.") + + var/turf/location = user.loc + if(!istype(location)) + return + + var/datum/gas_mixture/environment = location.return_air() + + var/pressure = environment.return_pressure() + var/total_moles = environment.total_moles() + + to_chat(user, "Results:") + if(abs(pressure - ONE_ATMOSPHERE) < 10) + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + else + to_chat(user, "Pressure: [round(pressure,0.1)] kPa") + if(total_moles) + var/list/env_gases = environment.gases + + var/o2_concentration = env_gases[/datum/gas/oxygen]/total_moles + var/n2_concentration = env_gases[/datum/gas/nitrogen]/total_moles + var/co2_concentration = env_gases[/datum/gas/carbon_dioxide]/total_moles + var/plasma_concentration = env_gases[/datum/gas/plasma]/total_moles + GAS_GARBAGE_COLLECT(environment.gases) + + if(abs(n2_concentration - N2STANDARD) < 20) + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") + else + to_chat(user, "Nitrogen: [round(n2_concentration*100, 0.01)] %") + + if(abs(o2_concentration - O2STANDARD) < 2) + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") + else + to_chat(user, "Oxygen: [round(o2_concentration*100, 0.01)] %") + + if(co2_concentration > 0.01) + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") + else + to_chat(user, "CO2: [round(co2_concentration*100, 0.01)] %") + + if(plasma_concentration > 0.005) + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") + else + to_chat(user, "Plasma: [round(plasma_concentration*100, 0.01)] %") + + + for(var/id in env_gases) + if(id in GLOB.hardcoded_gases) + continue + var/gas_concentration = env_gases[id]/total_moles + to_chat(user, "[GLOB.meta_gas_names[id]]: [round(gas_concentration*100, 0.01)] %") + to_chat(user, "Temperature: [round(environment.temperature-T0C)] °C") + +/obj/item/analyzer/nose/afterattack(atom/target, mob/user, proximity) + . = ..() + if(!proximity) + return + do_attack_animation(target, null, src) + user.visible_message("[user] [pick(attack_verb)] \the [target.name] with their nose!") + +//Delivery +/obj/item/storage/bag/borgdelivery + name = "fetching storage" + desc = "Fetch the thing!" + icon = 'icons/mob/dogborg.dmi' + icon_state = "dbag" + w_class = WEIGHT_CLASS_BULKY + +/obj/item/storage/bag/borgdelivery/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_w_class = WEIGHT_CLASS_BULKY + STR.max_combined_w_class = 5 + STR.max_items = 1 + STR.cant_hold = typecacheof(list(/obj/item/disk/nuclear, /obj/item/radio/intercom)) + +//Tongue stuff +/obj/item/soap/tongue + name = "synthetic tongue" + desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." + icon = 'icons/mob/dogborg.dmi' + icon_state = "synthtongue" + hitsound = 'sound/effects/attackblob.ogg' + cleanspeed = 80 + var/status = 0 + +/obj/item/soap/tongue/scrubpup + cleanspeed = 25 //slightly faster than a mop. + +/obj/item/soap/tongue/New() + ..() + item_flags |= NOBLUDGEON //No more attack messages + +/obj/item/soap/tongue/attack_self(mob/user) + var/mob/living/silicon/robot/R = user + if(R.cell && R.cell.charge > 100) + if(R.emagged && status == 0) + status = !status + name = "energized tongue" + desc = "Your tongue is energized for dangerously maximum efficency." + icon_state = "syndietongue" + to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") + cleanspeed = 10 //(nerf'd)tator soap stat + else + status = 0 + name = "synthetic tongue" + desc = "Useful for slurping mess off the floor before affectionally licking the crew members in the face." + icon_state = "synthtongue" + cleanspeed = initial(cleanspeed) + if(R.emagged) + to_chat(user, "Your tongue is now [status ? "Energized" : "Normal"].") + update_icon() + +/obj/item/soap/tongue/afterattack(atom/target, mob/user, proximity) + var/mob/living/silicon/robot/R = user + if(!proximity || !check_allowed_items(target)) + return + if(R.client && (target in R.client.screen)) + to_chat(R, "You need to take that [target.name] off before cleaning it!") + else if(is_cleanable(target)) + R.visible_message("[R] begins to lick off \the [target.name].", "You begin to lick off \the [target.name]...") + if(do_after(R, src.cleanspeed, target = target)) + if(!in_range(src, target)) //Proximity is probably old news by now, do a new check. + return //If they moved away, you can't eat them. + to_chat(R, "You finish licking off \the [target.name].") + qdel(target) + R.cell.give(50) + else if(isobj(target)) //hoo boy. danger zone man + if(istype(target,/obj/item/trash)) + R.visible_message("[R] nibbles away at \the [target.name].", "You begin to nibble away at \the [target.name]...") + if(!do_after(R, src.cleanspeed, target = target)) + return //If they moved away, you can't eat them. + to_chat(R, "You finish off \the [target.name].") + qdel(target) + R.cell.give(250) + return + if(istype(target,/obj/item/stock_parts/cell)) + R.visible_message("[R] begins cramming \the [target.name] down its throat.", "You begin cramming \the [target.name] down your throat...") + if(!do_after(R, 50, target = target)) + return //If they moved away, you can't eat them. + to_chat(R, "You finish off \the [target.name].") + var/obj/item/stock_parts/cell/C = target + R.cell.charge = R.cell.charge + (C.charge / 3) //Instant full cell upgrades op idgaf + qdel(target) + return + var/obj/item/I = target //HAHA FUCK IT, NOT LIKE WE ALREADY HAVE A SHITTON OF WAYS TO REMOVE SHIT + if(!I.anchored && R.emagged) + R.visible_message("[R] begins chewing up \the [target.name]. Looks like it's trying to loophole around its diet restriction!", "You begin chewing up \the [target.name]...") + if(!do_after(R, 100, target = I)) //Nerf dat time yo + return //If they moved away, you can't eat them. + visible_message("[R] chews up \the [target.name] and cleans off the debris!") + to_chat(R, "You finish off \the [target.name].") + qdel(I) + R.cell.give(500) + return + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + else if(ishuman(target)) + var/mob/living/L = target + if(status == 0 && check_zone(R.zone_selected) == "head") + R.visible_message("\the [R] affectionally licks \the [L]'s face!", "You affectionally lick \the [L]'s face!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + if(istype(L) && L.fire_stacks > 0) + L.adjust_fire_stacks(-10) + return + else if(status == 0) + R.visible_message("\the [R] affectionally licks \the [L]!", "You affectionally lick \the [L]!") + playsound(src.loc, 'sound/effects/attackblob.ogg', 50, 1) + if(istype(L) && L.fire_stacks > 0) + L.adjust_fire_stacks(-10) + return + else + if(R.cell.charge <= 800) + to_chat(R, "Insufficent Power!") + return + L.Stun(4) // normal stunbaton is force 7 gimme a break good sir! + L.Knockdown(80) + L.apply_effect(EFFECT_STUTTER, 4) + L.visible_message("[R] has shocked [L] with its tongue!", \ + "[R] has shocked you with its tongue!") + playsound(loc, 'sound/weapons/Egloves.ogg', 50, 1, -1) + R.cell.use(666) + log_combat(R, L, "tongue stunned") + + else if(istype(target, /obj/structure/window)) + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + if(do_after(user, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + target.set_opacity(initial(target.opacity)) + else + R.visible_message("[R] begins to lick \the [target.name] clean...", "You begin to lick \the [target.name] clean...") + if(do_after(user, src.cleanspeed, target = target)) + to_chat(user, "You clean \the [target.name].") + var/obj/effect/decal/cleanable/C = locate() in target + qdel(C) + target.remove_atom_colour(WASHABLE_COLOUR_PRIORITY) + SEND_SIGNAL(target, COMSIG_COMPONENT_CLEAN_ACT, CLEAN_MEDIUM) + target.wash_cream() + target.wash_cum() + return + +//Dogfood + +/obj/item/trash/rkibble + name = "robo kibble" + desc = "A novelty bowl of assorted mech fabricator byproducts. Mockingly feed this to the sec-dog to help it recharge." + icon = 'icons/mob/dogborg.dmi' + icon_state= "kibble" + +//Defibs + +/obj/item/twohanded/shockpaddles/cyborg/hound + name = "Paws of Life" + desc = "MediHound specific shock paws." + icon = 'icons/mob/dogborg.dmi' + icon_state = "defibpaddles0" + item_state = "defibpaddles0" + +// Pounce stuff for K-9 + +/obj/item/dogborg/pounce + name = "pounce" + icon = 'icons/mob/dogborg.dmi' + icon_state = "pounce" + desc = "Leap at your target to momentarily stun them." + force = 0 + throwforce = 0 + +/obj/item/dogborg/pounce/New() + ..() + item_flags |= NOBLUDGEON + +/mob/living/silicon/robot + var/leaping = 0 + var/pounce_cooldown = 0 + var/pounce_cooldown_time = 20 //Buffed to counter balance changes + var/pounce_spoolup = 1 + var/leap_at + var/disabler + var/laser + var/sleeper_g + var/sleeper_r + var/sleeper_nv + +#define MAX_K9_LEAP_DIST 4 //because something's definitely borked the pounce functioning from a distance. + +/obj/item/dogborg/pounce/afterattack(atom/A, mob/user) + var/mob/living/silicon/robot/R = user + if(R && !R.pounce_cooldown) + R.pounce_cooldown = !R.pounce_cooldown + to_chat(R, "Your targeting systems lock on to [A]...") + addtimer(CALLBACK(R, /mob/living/silicon/robot.proc/leap_at, A), R.pounce_spoolup) + spawn(R.pounce_cooldown_time) + R.pounce_cooldown = !R.pounce_cooldown + else if(R && R.pounce_cooldown) + to_chat(R, "Your leg actuators are still recharging!") + +/mob/living/silicon/robot/proc/leap_at(atom/A) + if(leaping || stat || buckled || lying) + return + + if(!has_gravity(src) || !has_gravity(A)) + to_chat(src,"It is unsafe to leap without gravity!") + //It's also extremely buggy visually, so it's balance+bugfix + return + + if(cell.charge <= 750) + to_chat(src,"Insufficent reserves for jump actuators!") + return + + else + leaping = 1 + weather_immunities += "lava" + pixel_y = 10 + update_icons() + throw_at(A, MAX_K9_LEAP_DIST, 1, spin=0, diagonals_first = 1) + cell.use(750) //Less than a stunbaton since stunbatons hit everytime. + weather_immunities -= "lava" + +/mob/living/silicon/robot/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) + + if(!leaping) + return ..() + + if(hit_atom) + if(isliving(hit_atom)) + var/mob/living/L = hit_atom + if(!L.check_shields(0, "the [name]", src, attack_type = LEAP_ATTACK)) + L.visible_message("[src] pounces on [L]!", "[src] pounces on you!") + L.Knockdown(iscarbon(L) ? 225 : 45) // Temporary. If someone could rework how dogborg pounces work to accomodate for combat changes, that'd be nice. + playsound(src, 'sound/weapons/Egloves.ogg', 50, 1) + sleep(2)//Runtime prevention (infinite bump() calls on hulks) + step_towards(src,L) + log_combat(src, L, "borg pounced") + else + Knockdown(15, 1, 1) + + pounce_cooldown = !pounce_cooldown + spawn(pounce_cooldown_time) //3s by default + pounce_cooldown = !pounce_cooldown + else if(hit_atom.density && !hit_atom.CanPass(src)) + visible_message("[src] smashes into [hit_atom]!", "You smash into [hit_atom]!") + playsound(src, 'sound/items/trayhit1.ogg', 50, 1) + Knockdown(15, 1, 1) + + if(leaping) + leaping = 0 + pixel_y = initial(pixel_y) + update_icons() + update_canmove() diff --git a/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm b/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm index e40ccfe6..02466975 100644 --- a/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm +++ b/modular_citadel/code/modules/projectiles/guns/ballistic/rifles.dm @@ -207,8 +207,10 @@ var/mob/M = loc M.update_inv_hands() /obj/item/gun/ballistic/automatic/AM4B/AltClick(mob/living/user) + . = ..() if(!in_range(src, user)) //Basic checks to prevent abuse return + . = TRUE if(user.incapacitated() || !istype(user)) to_chat(user, "You can't do that right now!") return @@ -217,6 +219,7 @@ if(body_color_input) body_color = sanitize_hexcolor(body_color_input, desired_format=6, include_crunch=1) update_icon() + /obj/item/gun/ballistic/automatic/AM4B/examine(mob/user) . = ..() . += "Alt-click to recolor it." diff --git a/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm b/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm index 0f7db73d..bbe8bd0d 100644 --- a/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm +++ b/modular_citadel/code/modules/projectiles/guns/energy/energy_gun.dm @@ -37,8 +37,10 @@ obj/item/gun/energy/e_gun/cx/update_icon() M.update_inv_hands() obj/item/gun/energy/e_gun/cx/AltClick(mob/living/user) + . = ..() if(!in_range(src, user)) //Basic checks to prevent abuse return + . = TRUE if(user.incapacitated() || !istype(user)) to_chat(user, "You can't do that right now!") return diff --git a/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm b/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm index 14773e19..4d46faa3 100644 --- a/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm +++ b/modular_citadel/code/modules/projectiles/guns/pumpenergy.dm @@ -83,15 +83,17 @@ return 1 /obj/item/gun/energy/pumpaction/AltClick(mob/living/user) //for changing firing modes since attackself is already used for pumping + . = ..() if(!in_range(src, user)) //Basic checks to prevent abuse return - if(user.incapacitated() || !istype(user)) - to_chat(user, "You can't do that right now!") - return if(ammo_type.len > 1) - select_fire(user) - update_icon() + if(user.incapacitated() || !istype(user)) + to_chat(user, "You can't do that right now!") + else + select_fire(user) + update_icon() + return TRUE /obj/item/gun/energy/pumpaction/examine(mob/user) //so people don't ask HOW TO CHANGE FIRING MODE . = ..() diff --git a/tgstation.dme b/tgstation.dme index 96b58569..bd08e84b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3175,7 +3175,6 @@ #include "modular_citadel\code\modules\clothing\neck.dm" #include "modular_citadel\code\modules\clothing\glasses\phantomthief.dm" #include "modular_citadel\code\modules\clothing\head\head.dm" -#include "modular_citadel\code\modules\clothing\spacesuits\cydonian_armor.dm" #include "modular_citadel\code\modules\clothing\spacesuits\flightsuit.dm" #include "modular_citadel\code\modules\clothing\suits\polychromic_cloaks.dm" #include "modular_citadel\code\modules\clothing\suits\polychromic_suit.dm"