Files
Arthri 494704e323 Set Ore Redemption Machines to use Low Priority Messages (#26444)
* Refactor createMessage out of message server

* Change radio message

* Add reminder

* Fix linting errors

* Fix more linting errors

* Update priorities

* Add low priority icon

* Lower indent

* Fix switch statement

* Add low priority icon

* No reminders for low priority messages

* Update priorities in frontend

* Build and update /tg/ui

* Fix typo

* Clarify purpose of return

* [ci skip]

* Build and update /tg/ui

* [ci skip]

* Build and update /tg/ui

---------

Co-authored-by: Arthri <41360489+a@users.noreply.github.com>
Co-authored-by: /tg/ui Builder <41898282+github-actions[bot]@users.noreply.github.com>
2024-08-17 15:31:33 +00:00

702 lines
22 KiB
Plaintext

/obj/structure/closet/crate
name = "crate"
desc = "A rectangular steel crate."
icon = 'icons/obj/crates.dmi'
icon_state = "crate"
icon_opened = "crate_open"
icon_closed = "crate"
climbable = TRUE
var/rigged = FALSE
open_sound = 'sound/machines/crate_open.ogg'
close_sound = 'sound/machines/crate_close.ogg'
open_sound_volume = 35
close_sound_volume = 50
var/obj/item/paper/manifest/manifest
/// A list of beacon names that the crate will announce the arrival of, when delivered.
var/list/announce_beacons = list()
/// How much this crate is worth if you sell it via the cargo shuttle, needed for balance :)
var/crate_value = DEFAULT_CRATE_VALUE
/obj/structure/closet/crate/update_overlays()
. = ..()
if(manifest)
. += "manifest"
/obj/structure/closet/crate/can_open()
return TRUE
/obj/structure/closet/crate/can_close()
return TRUE
/obj/structure/closet/crate/open(by_hand = FALSE)
if(opened)
return FALSE
if(!can_open())
return FALSE
if(by_hand)
for(var/obj/O in src)
if(O.density)
var/response = tgui_alert(usr, "This crate has been packed with bluespace compression, an item inside won't fit back inside. Are you sure you want to open it?", "Bluespace Compression Warning", list("Yes", "No"))
if(response != "Yes" || !Adjacent(usr))
return FALSE
break
if(rigged && locate(/obj/item/electropack) in src)
if(isliving(usr))
var/mob/living/L = usr
if(L.electrocute_act(17, src))
do_sparks(5, 1, src)
return 2
playsound(loc, open_sound, open_sound_volume, TRUE, -3)
for(var/obj/O in src) //Objects
O.forceMove(loc)
for(var/mob/M in src) //Mobs
M.forceMove(loc)
icon_state = icon_opened
opened = TRUE
if(climbable)
structure_shaken()
return TRUE
/obj/structure/closet/crate/close()
if(!opened)
return FALSE
if(!can_close())
return FALSE
playsound(loc, close_sound, close_sound_volume, TRUE, -3)
var/itemcount = 0
for(var/atom/movable/O in get_turf(src))
if(itemcount >= storage_capacity)
break
if(O.density || O.anchored || istype(O,/obj/structure/closet))
continue
if(ismob(O) && !HAS_TRAIT(O, TRAIT_CONTORTED_BODY))
continue
if(O.has_buckled_mobs()) // You can't put mobs into crates, so naturally if a mob is attached to something, it shouldn't be able to go in the crate
continue
O.forceMove(src)
itemcount++
icon_state = icon_closed
opened = FALSE
return TRUE
/obj/structure/closet/crate/attackby(obj/item/W, mob/user, params)
if(!opened && try_rig(W, user))
return
return ..()
/obj/structure/closet/crate/toggle(mob/user, by_hand = FALSE)
if(!(opened ? close() : open(by_hand)))
to_chat(user, "<span class='notice'>It won't budge!</span>")
/obj/structure/closet/crate/proc/try_rig(obj/item/W, mob/user)
if(istype(W, /obj/item/stack/cable_coil))
var/obj/item/stack/cable_coil/C = W
if(rigged)
to_chat(user, "<span class='notice'>[src] is already rigged!</span>")
return TRUE
if(C.use(15))
to_chat(user, "<span class='notice'>You rig [src].</span>")
rigged = TRUE
else
to_chat(user, "<span class='warning'>You need at least 15 wires to rig [src]!</span>")
return TRUE
if(istype(W, /obj/item/electropack))
if(rigged)
if(!user.drop_item())
to_chat(user, "<span class='warning'>[W] seems to be stuck to your hand!</span>")
return TRUE
to_chat(user, "<span class='notice'>You attach [W] to [src].</span>")
W.forceMove(src)
return TRUE
/obj/structure/closet/crate/wirecutter_act(mob/living/user, obj/item/I)
if(opened)
return
if(!rigged)
return
if(I.use_tool(src, user))
to_chat(user, "<span class='notice'>You cut away the wiring.</span>")
playsound(loc, I.usesound, 100, 1)
rigged = FALSE
return TRUE
/obj/structure/closet/crate/welder_act()
return
/obj/structure/closet/crate/attack_hand(mob/user)
if(manifest)
to_chat(user, "<span class='notice'>You tear the manifest off of the crate.</span>")
playsound(loc, 'sound/items/poster_ripped.ogg', 75, TRUE)
manifest.forceMove(loc)
if(ishuman(user))
user.put_in_hands(manifest)
manifest = null
update_icon()
return
else
if(rigged && locate(/obj/item/electropack) in src)
if(isliving(user))
var/mob/living/L = user
if(L.electrocute_act(17, src))
do_sparks(5, 1, src)
return
add_fingerprint(user)
toggle(user, by_hand = TRUE)
/obj/structure/closet/crate/shove_impact(mob/living/target, mob/living/attacker)
return FALSE
// Called when a crate is delivered by MULE at a location, for notifying purposes
/obj/structure/closet/crate/proc/notifyRecipient(destination)
var/list/msg = list("[capitalize(name)] has arrived at [destination].")
if(destination in announce_beacons)
for(var/obj/machinery/requests_console/D in GLOB.allRequestConsoles)
if(D.department in announce_beacons[destination])
D.createMessage(name, "Your Crate has Arrived!", msg, RQ_NORMALPRIORITY)
/obj/structure/closet/crate/secure
desc = "A secure crate."
name = "Secure crate"
icon_state = "securecrate"
icon_opened = "securecrate_open"
icon_closed = "securecrate"
max_integrity = 500
armor = list(MELEE = 30, BULLET = 50, LASER = 50, ENERGY = 100, BOMB = 0, RAD = 0, FIRE = 80, ACID = 80)
damage_deflection = 25
broken = FALSE
locked = TRUE
can_be_emaged = TRUE
crate_value = 25 // rarer and cannot be crafted, bonus credits for exporting them
var/redlight = "securecrater"
var/greenlight = "securecrateg"
var/emag = "securecrateemag"
var/tamperproof = FALSE
/obj/structure/closet/crate/secure/update_overlays()
. = ..()
if(broken)
. += emag
return
if(locked)
. += redlight
else
. += greenlight
/obj/structure/closet/crate/secure/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1)
if(prob(tamperproof) && damage_amount >= DAMAGE_PRECISION)
boom()
else
return ..()
/obj/structure/closet/crate/secure/proc/boom(mob/user)
if(user)
to_chat(user, "<span class='danger'>The crate's anti-tamper system activates!</span>")
investigate_log("[key_name(user)] has detonated a [src]", INVESTIGATE_BOMB)
add_attack_logs(user, src, "has detonated", ATKLOG_MOST)
for(var/atom/movable/AM in src)
qdel(AM)
explosion(get_turf(src), 0, 1, 5, 5)
qdel(src)
/obj/structure/closet/crate/secure/can_open()
return !locked
/obj/structure/closet/crate/secure/proc/togglelock(mob/user)
if(opened)
to_chat(user, "<span class='notice'>Close the crate first.</span>")
return FALSE
if(broken)
to_chat(user, "<span class='warning'>The crate appears to be broken.</span>")
return FALSE
if(allowed(user))
locked = !locked
visible_message("<span class='notice'>The crate has been [locked ? null : "un"]locked by [user].</span>")
update_icon()
return TRUE
else
to_chat(user, "<span class='notice'>Access Denied.</span>")
return FALSE
/obj/structure/closet/crate/secure/AltClick(mob/user)
if(Adjacent(user) && !opened)
if(HAS_TRAIT(user, TRAIT_HANDS_BLOCKED) || user.stat) // Don't use it if you're not able to! Checks for stuns, ghost and restrain
return
togglelock(user)
return
. = ..()
/obj/structure/closet/crate/secure/attack_hand(mob/user)
if(manifest)
to_chat(user, "<span class='notice'>You tear the manifest off of the crate.</span>")
playsound(loc, 'sound/items/poster_ripped.ogg', 75, 1)
manifest.forceMove(loc)
if(ishuman(user))
user.put_in_hands(manifest)
manifest = null
update_icon()
return
if(locked)
togglelock(user)
else
toggle(user, by_hand = TRUE)
/obj/structure/closet/crate/secure/closed_item_click(mob/user)
togglelock(user)
/obj/structure/closet/crate/secure/emag_act(mob/user)
if(locked)
locked = FALSE
broken = TRUE
update_icon()
do_sparks(2, TRUE, src)
to_chat(user, "<span class='notice'>You unlock \the [src].</span>")
return TRUE
/obj/structure/closet/crate/secure/emp_act(severity)
for(var/obj/O in src)
O.emp_act(severity)
if(!broken && !opened && prob(50/severity))
if(!locked)
locked = TRUE
else
do_sparks(2, 1, src)
locked = FALSE
update_icon()
if(!opened && prob(20/severity))
if(!locked)
open()
else
req_access = list()
req_access += pick(get_all_accesses())
..()
/obj/structure/closet/crate/secure/personal
name = "personal crate"
desc = "The crate version of Nanotrasen's famous personal locker, ideal for shipping booze, food, or drugs to CC without letting Cargo consume it. This one has not been configured by CC, and the first card swiped gains control."
req_access = list(ACCESS_ALL_PERSONAL_LOCKERS)
/// The name of the person this crate is registered to.
var/registered_name = null
// Unlike most secure crates, personal crates are easily obtained.
crate_value = DEFAULT_CRATE_VALUE
/obj/structure/closet/crate/secure/personal/allowed(mob/user)
if(..())
return TRUE
var/obj/item/card/id/id = user.get_id_card()
if(is_usable_id(id))
return id.registered_name == registered_name
return FALSE
/// Returns whether the object is a usable ID card (not guest pass, has name).
/obj/structure/closet/crate/secure/personal/proc/is_usable_id(obj/item/card/id/id)
if(!istype(id))
return FALSE
if(istype(id, /obj/item/card/id/guest) || !id.registered_name)
return FALSE
return TRUE
/obj/structure/closet/crate/secure/personal/attackby(obj/item/I, mob/user, params)
if(opened || !istype(I, /obj/item/card/id))
return ..()
if(broken)
to_chat(user, "<span class='warning'>It appears to be broken.</span>")
return FALSE
var/obj/item/card/id/id = I
if(!is_usable_id(id))
to_chat(user, "<span class='warning'>Invalid identification card.</span>")
return FALSE
if(registered_name && allowed(user))
return ..()
if(!registered_name)
registered_name = id.registered_name
desc = "Owned by [id.registered_name]."
to_chat(user, "<span class='notice'>Crate reserved</span>")
return TRUE
if(registered_name == id.registered_name)
return ..()
return FALSE
/obj/structure/closet/crate/plastic
name = "plastic crate"
desc = "A rectangular plastic crate."
icon_state = "plasticcrate"
icon_opened = "plasticcrate_open"
icon_closed = "plasticcrate"
material_drop = /obj/item/stack/sheet/plastic
material_drop_amount = 4
crate_value = 3 // You can mass produce plastic crates, this is needed to prevent cargo from making tons of money too easily
/obj/structure/closet/crate/internals
desc = "A internals crate."
name = "internals crate"
icon_state = "o2crate"
icon_opened = "o2crate_open"
icon_closed = "o2crate"
/obj/structure/closet/crate/trashcart
desc = "A heavy, metal trashcart with wheels."
name = "trash Cart"
icon_state = "trashcart"
icon_opened = "trashcart_open"
icon_closed = "trashcart"
/obj/structure/closet/crate/medical
desc = "A medical crate."
name = "medical crate"
icon_state = "medicalcrate"
icon_opened = "medicalcrate_open"
icon_closed = "medicalcrate"
/obj/structure/closet/crate/rcd
desc = "A crate for the storage of the RCD."
name = "\improper RCD crate"
/obj/structure/closet/crate/rcd/populate_contents()
new /obj/item/rcd_ammo(src)
new /obj/item/rcd_ammo(src)
new /obj/item/rcd_ammo(src)
new /obj/item/rcd(src)
/obj/structure/closet/crate/freezer
name = "Freezer"
desc = "A freezer for keeping food and organs fresh."
icon_state = "freezer"
icon_opened = "freezer_open"
icon_closed = "freezer"
var/target_temp = T0C - 40
var/cooling_power = 40
/obj/structure/closet/crate/freezer/return_obj_air()
RETURN_TYPE(/datum/gas_mixture)
var/datum/gas_mixture/gas = ..()
if(!gas)
var/turf/T = get_turf(src)
gas = T.get_readonly_air()
var/datum/gas_mixture/newgas = new/datum/gas_mixture()
newgas.set_oxygen(gas.oxygen())
newgas.set_carbon_dioxide(gas.carbon_dioxide())
newgas.set_nitrogen(gas.nitrogen())
newgas.set_toxins(gas.toxins())
newgas.volume = gas.volume
newgas.set_temperature(gas.temperature())
if(newgas.temperature() <= target_temp) return
if((newgas.temperature() - cooling_power) > target_temp)
newgas.set_temperature(newgas.temperature() - cooling_power)
else
newgas.set_temperature(target_temp)
return newgas
/obj/structure/closet/crate/freezer/iv_storage
name = "IV storage freezer"
desc = "A freezer used to store IV bags containing various blood types."
/obj/structure/closet/crate/freezer/iv_storage/populate_contents()
new /obj/item/reagent_containers/iv_bag/blood/OMinus(src)
new /obj/item/reagent_containers/iv_bag/blood/OPlus(src)
new /obj/item/reagent_containers/iv_bag/blood/AMinus(src)
new /obj/item/reagent_containers/iv_bag/blood/APlus(src)
new /obj/item/reagent_containers/iv_bag/blood/BMinus(src)
new /obj/item/reagent_containers/iv_bag/blood/BPlus(src)
new /obj/item/reagent_containers/iv_bag/blood/random(src)
new /obj/item/reagent_containers/iv_bag/blood/random(src)
new /obj/item/reagent_containers/iv_bag/blood/random(src)
new /obj/item/reagent_containers/iv_bag/salglu(src)
new /obj/item/reagent_containers/iv_bag/slime(src)
new /obj/item/reagent_containers/iv_bag/blood/vox(src)
/obj/structure/closet/crate/can
desc = "A large can, looks like a bin to me."
name = "garbage can"
icon_state = "largebin"
icon_opened = "largebin_open"
icon_closed = "largebin"
anchored = TRUE
open_sound = 'sound/effects/bin_open.ogg'
close_sound = 'sound/effects/bin_close.ogg'
/obj/structure/closet/crate/can/wrench_act(mob/user, obj/item/I)
. = TRUE
if(!I.tool_use_check(user, 0))
return
default_unfasten_wrench(user, I, 40)
/obj/structure/closet/crate/radiation
desc = "A crate with a radiation sign on it."
name = "radioactive gear crate"
icon_state = "radiation"
icon_opened = "radiation_open"
icon_closed = "radiation"
/obj/structure/closet/crate/secure/weapon
desc = "A secure weapons crate."
name = "weapons crate"
icon_state = "weaponcrate"
icon_opened = "weaponcrate_open"
icon_closed = "weaponcrate"
/obj/structure/closet/crate/secure/plasma
desc = "A secure plasma crate."
name = "plasma crate"
icon_state = "plasmacrate"
icon_opened = "plasmacrate_open"
icon_closed = "plasmacrate"
/obj/structure/closet/crate/secure/gear
desc = "A secure gear crate."
name = "gear crate"
icon_state = "secgearcrate"
icon_opened = "secgearcrate_open"
icon_closed = "secgearcrate"
/obj/structure/closet/crate/secure/hydrosec
desc = "A crate with a lock on it, painted in the scheme of the station's botanists."
name = "secure hydroponics crate"
icon_state = "hydrosecurecrate"
icon_opened = "hydrosecurecrate_open"
icon_closed = "hydrosecurecrate"
/obj/structure/closet/crate/secure/bin
desc = "A secure bin."
name = "secure bin"
icon_state = "largebins"
icon_opened = "largebins_open"
icon_closed = "largebins"
redlight = "largebinr"
greenlight = "largebing"
emag = "largebinemag"
open_sound = 'sound/effects/bin_open.ogg'
close_sound = 'sound/effects/bin_close.ogg'
/obj/structure/closet/crate/hydroponics
name = "hydroponics crate"
desc = "All you need to destroy those pesky weeds and pests."
icon_state = "hydrocrate"
icon_opened = "hydrocrate_open"
icon_closed = "hydrocrate"
/obj/structure/closet/crate/hydroponics/prespawned
//This exists so the prespawned hydro crates spawn with their contents.
// Do I need the definition above? Who knows!
/obj/structure/closet/crate/hydroponics/prespawned/populate_contents()
new /obj/item/reagent_containers/glass/bucket(src)
new /obj/item/reagent_containers/glass/bucket(src)
new /obj/item/screwdriver(src)
new /obj/item/screwdriver(src)
new /obj/item/wrench(src)
new /obj/item/wrench(src)
new /obj/item/wirecutters(src)
new /obj/item/wirecutters(src)
new /obj/item/shovel/spade(src)
new /obj/item/shovel/spade(src)
new /obj/item/storage/box/beakers(src)
new /obj/item/storage/box/beakers(src)
new /obj/item/hand_labeler(src)
new /obj/item/hand_labeler(src)
/obj/structure/closet/crate/sci
name = "science crate"
desc = "A science crate."
icon_state = "scicrate"
icon_opened = "scicrate_open"
icon_closed = "scicrate"
/obj/structure/closet/crate/sci/robo
desc = "A science crate. Contain various mech parts."
icon_state = "scicrate_mech"
icon_opened = "scicrate_mech_open"
icon_closed = "scicrate_mech"
/obj/structure/closet/crate/secure/scisec
name = "secure science crate"
desc = "A crate with a lock on it, painted in the scheme of the station's scientists."
icon_state = "scisecurecrate"
icon_opened = "scisecurecrate_open"
icon_closed = "scisecurecrate"
/obj/structure/closet/crate/engineering
name = "engineering crate"
desc = "An engineering crate."
icon_state = "engicrate"
icon_opened = "engicrate_open"
icon_closed = "engicrate"
/obj/structure/closet/crate/secure/engineering
name = "secure engineering crate"
desc = "A crate with a lock on it, painted in the scheme of the station's engineers."
icon_state = "engisecurecrate"
icon_opened = "engisecurecrate_open"
icon_closed = "engisecurecrate"
/obj/structure/closet/crate/engineering/electrical
name = "electrical engineering crate"
desc = "An electrical engineering crate."
icon_state = "electricalcrate"
icon_opened = "electricalcrate_open"
icon_closed = "electricalcrate"
/obj/structure/closet/crate/mail
name = "mail crate"
desc = "A plastic crate for mail delivery."
icon = 'icons/obj/bureaucracy.dmi'
icon_state = "mailsealed"
icon_opened = "mailopen"
icon_closed = "mailsealed"
material_drop = /obj/item/stack/sheet/plastic
material_drop_amount = 4
var/list/possible_contents = list(/obj/item/envelope/security,
/obj/item/envelope/science,
/obj/item/envelope/supply,
/obj/item/envelope/medical,
/obj/item/envelope/engineering,
/obj/item/envelope/bread,
/obj/item/envelope/circuses,
/obj/item/envelope/command,
/obj/item/envelope/misc)
/obj/structure/closet/crate/mail/populate_contents()
. = ..()
for(var/i in 1 to rand(5, 10))
var/item = pick(possible_contents)
new item(src)
/obj/structure/closet/crate/tape/populate_contents()
if(prob(10))
new /obj/item/bikehorn/rubberducky(src)
//crates of gear in the free golem ship
/obj/structure/closet/crate/golemgear/populate_contents()
new /obj/item/storage/backpack/industrial(src)
new /obj/item/shovel(src)
new /obj/item/pickaxe(src)
new /obj/item/t_scanner/adv_mining_scanner/lesser(src)
new /obj/item/storage/bag/ore(src)
new /obj/item/clothing/glasses/meson(src)
new /obj/item/card/id/golem(src)
new /obj/item/flashlight/lantern(src)
#define RECURSION_PANIC_AMOUNT 10
/obj/structure/closet/crate/surplus
/obj/structure/closet/crate/surplus/Initialize(mapload, obj/item/uplink/U, crate_value, cost, mob/user)
. = ..()
var/list/temp_uplink_list = get_uplink_items(U, user)
var/list/buyable_items = list()
for(var/category in temp_uplink_list)
buyable_items += temp_uplink_list[category]
for(var/datum/uplink_item/uplink_item in buyable_items)
if(!uplink_item.surplus) // Otherwise we'll just have an element with a weight of 0 in our weighted list
continue
buyable_items[uplink_item] = uplink_item.surplus
if(!length(buyable_items)) // UH OH - Will almost always happen when an admin will try to spawn a crate
fucked_shit_up_alert(loc, "[src] spawning failed: had no buyable items on purchase which would have caused an infinite loop, refunding [cost] telecrystals instead. (Original cost of the crate). Report this to coders please.")
generate_refund(cost, loc)
return
var/remaining_TC = crate_value
var/list/bought_items = list()
var/list/itemlog = list()
var/datum/uplink_item/uplink_item
var/danger_counter = 0 // lets make sure we dont get into an infinite loop...
while(remaining_TC)
if(danger_counter > RECURSION_PANIC_AMOUNT)
fucked_shit_up_alert(loc, "[src] spawning failed: approached an infinite loop by cost checking, giving the remaining [remaining_TC] telecrystals instead.")
generate_refund(remaining_TC, loc)
break
if(!length(buyable_items)) // UH OH V.2
fucked_shit_up_alert(loc, "[src] spawning failed: ran out of buyable items while looping, refunding [remaining_TC] telecrystals and cancelling crate. (Original cost of the crate). Report this to coders please.")
generate_refund(remaining_TC, loc)
bought_items.Cut()
break
uplink_item = pickweight(buyable_items)
if(uplink_item.cost > remaining_TC)
danger_counter++
buyable_items -= uplink_item
continue
bought_items += uplink_item.item
remaining_TC -= uplink_item.cost
buyable_items[uplink_item] *= 0.66 // To prevent people from getting the same thing over and over again
itemlog += uplink_item.name // To make the name more readable for the log compared to just uplink_item.item
danger_counter = 0
U.purchase_log += "<BIG>[bicon(src)]</BIG>"
for(var/item in bought_items)
var/obj/purchased = new item(src)
U.purchase_log += "<BIG>[bicon(purchased)]</BIG>"
log_game("[key_name(usr)] purchased a surplus crate with [jointext(itemlog, ", ")]")
/obj/structure/closet/crate/surplus/proc/generate_refund(amount)
var/changing_amount = amount
var/prohibitor = 0
var/given_out_TC = 0
while(changing_amount >= 1)
var/obj/item/stack/telecrystal/TC = new /obj/item/stack/telecrystal(src)
var/give_amount = min(changing_amount, TC.max_amount)
TC.amount = give_amount
changing_amount -= give_amount
given_out_TC += give_amount
if(prohibitor > RECURSION_PANIC_AMOUNT) // idk how they got 1000+ tc, dont ask me
new /obj/item/stack/telecrystal(src, changing_amount)
// Return of Bogdanoff: doomp it
var/turf/T = get_turf(loc)
given_out_TC += changing_amount
message_admins("While refunding telecrystals, [src] went over the expected limit, for a total of [amount] TC. Expected refund is likely [given_out_TC]. [ADMIN_COORDJMP(T)]")
break
prohibitor++
/obj/structure/closet/crate/surplus/proc/fucked_shit_up_alert(turf/loc, msg) // yeah just fuckin tell everyone, this shit is bad
stack_trace(msg)
message_admins("[msg] [ADMIN_COORDJMP(loc)]")
log_admin(msg)
#undef RECURSION_PANIC_AMOUNT
/obj/structure/closet/crate/secure/sec_shuttle_ruin
name = "locked crate"
desc = "The side of the crate has a slot for a keycard to be swiped."
can_be_emaged = FALSE
/obj/structure/closet/crate/secure/sec_shuttle_ruin/allowed(mob/user)
if(!user)
return
var/obj/item/card/sec_shuttle_ruin/keycard = user.get_active_hand()
if(!istype(keycard))
return
to_chat(user, "<span class='notice'>You swipe [keycard] in [src]'s keycard slot.</span>")
return TRUE
/obj/item/card/sec_shuttle_ruin
name = "warden's card"
desc = "A card used by the warden to unlock their crate."
icon_state = "data"