diff --git a/code/__HELPERS/traits.dm b/code/__HELPERS/traits.dm index 922200948bb..efe5a33b3da 100644 --- a/code/__HELPERS/traits.dm +++ b/code/__HELPERS/traits.dm @@ -199,6 +199,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Show what machine/door wires do when held. #define TRAIT_SHOW_WIRE_INFO "show_wire_info" #define TRAIT_BUTCHERS_HUMANS "butchers_humans" +#define TRAIT_CMAGGED "cmagged" // // common trait sources diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index fa009ae3102..015cfe38f2a 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -71,7 +71,8 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /obj/item = list( "TRAIT_SHOW_WIRE_INFO" = TRAIT_SHOW_WIRE_INFO, - "TRAIT_BUTCHER_HUMANS" = TRAIT_BUTCHERS_HUMANS + "TRAIT_BUTCHER_HUMANS" = TRAIT_BUTCHERS_HUMANS, + "TRAIT_CMAGGED" = TRAIT_CMAGGED ) )) diff --git a/code/datums/uplink_item.dm b/code/datums/uplink_item.dm index c394b3c37fc..4d61e94048a 100644 --- a/code/datums/uplink_item.dm +++ b/code/datums/uplink_item.dm @@ -182,6 +182,15 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) surplus = 75 job = list("Clown") +/datum/uplink_item/jobspecific/cmag + name = "Jestographic Sequencer" + desc = "The jestographic sequencer, also known as a cmag, is a small card that inverts the access on any door it's used on. Perfect for locking command out of their own departments. Honk!" + reference = "CMG" + item = /obj/item/card/cmag + cost = 4 + surplus = 75 + job = list("Clown") + /datum/uplink_item/jobspecific/trick_revolver name = "Trick Revolver" desc = "A revolver that will fire backwards and kill whoever attempts to use it. Perfect for those pesky vigilante or just a good laugh." diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 90848fc9a12..8e941dfd4bb 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -423,7 +423,7 @@ SHOULD_CALL_PARENT(TRUE) if(updates & NONE) - return // NONE is being sent on purpose, and thus no signal should be sent. + return // NONE is being sent on purpose, and thus no signal should be sent. updates &= ~SEND_SIGNAL(src, COMSIG_ATOM_UPDATE_ICON, updates) if(updates & UPDATE_ICON_STATE) @@ -514,6 +514,9 @@ /atom/proc/unemag() return +/atom/proc/cmag_act() + return + /** * Respond to a radioactive wave hitting this atom * diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 54b52dade9c..9d6f100ad6c 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -605,6 +605,8 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays) . = ..() if(emagged) . += "Its access panel is smoking slightly." + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + . += "The access panel is coated in yellow ooze..." if(note) if(!in_range(user, src)) . += "There's a [note.name] pinned to the front. You can't [note_type() == "note" ? "read" : "see"] it from here." @@ -802,7 +804,7 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays) /obj/machinery/door/airlock/proc/ai_control_check(mob/user) if(!issilicon(user)) return TRUE - if(emagged) + if(emagged || HAS_TRAIT(src, TRAIT_CMAGGED)) to_chat(user, "Unable to interface: Internal error.") return FALSE if(!canAIControl()) @@ -1336,6 +1338,19 @@ GLOBAL_LIST_EMPTY(airlock_emissive_underlays) emagged = TRUE return 1 +/obj/machinery/door/airlock/cmag_act(mob/user) + if(operating || HAS_TRAIT(src, TRAIT_CMAGGED) || !density || !arePowerSystemsOn()) + return + operating = TRUE + update_icon(AIRLOCK_EMAG, 1) + sleep(6) + if(QDELETED(src)) + return + operating = FALSE + update_icon(AIRLOCK_CLOSED, 1) + ADD_TRAIT(src, TRAIT_CMAGGED, "clown_emag") + return TRUE + /obj/machinery/door/airlock/emp_act(severity) . = ..() if(prob(20 / severity)) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 289e0098b21..c6f82284212 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -36,6 +36,9 @@ var/unres_sides = 0 //Multi-tile doors var/width = 1 + //Whether nonstandard door sounds (cmag laughter) are off cooldown. + var/sound_ready = TRUE + var/sound_cooldown = 1 SECONDS /// ID for the window tint button, or another external control var/id @@ -116,8 +119,14 @@ if(world.time - mecha.occupant.last_bumped <= 10) return if(mecha.occupant && allowed(mecha.occupant) || check_access_list(mecha.operation_req_access)) + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(FALSE, mecha.occupant) + return open() else + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(TRUE, mecha.occupant) + return do_animate("deny") return @@ -149,6 +158,9 @@ if(density && !emagged) if(allowed(user)) + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(FALSE, user) + return open() if(isbot(user)) var/mob/living/simple_animal/bot/B = user @@ -156,6 +168,9 @@ else if(pry_open_check(user)) return + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(TRUE, user) + return do_animate("deny") /obj/machinery/door/proc/pry_open_check(mob/user) @@ -204,10 +219,18 @@ return if(requiresID() && (allowed(user) || user.can_advanced_admin_interact())) if(density) + if(HAS_TRAIT(src, TRAIT_CMAGGED) && !user.can_advanced_admin_interact()) //cmag should not prevent admin intervention + cmag_switch(FALSE, user) + return open() else + if(HAS_TRAIT(src, TRAIT_CMAGGED) && !user.can_advanced_admin_interact()) + return close() return TRUE + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(TRUE, user) + return if(density) do_animate("deny") @@ -226,7 +249,28 @@ /obj/machinery/door/proc/try_to_crowbar(mob/user, obj/item/I) return +/obj/machinery/door/proc/clean_cmag_ooze(obj/item/I, mob/user) //Emags are Engineering's problem, cmags are the janitor's problem + var/cleaning = FALSE + if(istype(I, /obj/item/reagent_containers/spray/cleaner)) + var/obj/item/reagent_containers/spray/cleaner/C = I + if(C.reagents.total_volume >= C.amount_per_transfer_from_this) + cleaning = TRUE + else + return + if(istype(I, /obj/item/soap)) + cleaning = TRUE + + if(!cleaning) + return + user.visible_message("[user] starts to clean the ooze off the access panel.", "You start to clean the ooze off the access panel.") + if(do_after(user, 50, target = src)) + user.visible_message("[user] cleans the ooze off [src].", "You clean the ooze off [src].") + REMOVE_TRAIT(src, TRAIT_CMAGGED, "clown_emag") + /obj/machinery/door/attackby(obj/item/I, mob/user, params) + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + clean_cmag_ooze(I, user) + if(user.a_intent != INTENT_HARM && istype(I, /obj/item/twohanded/fireaxe)) try_to_crowbar(user, I) return 1 @@ -235,7 +279,6 @@ return 1 return ..() - /obj/machinery/door/crowbar_act(mob/user, obj/item/I) if(user.a_intent == INTENT_HARM) return @@ -272,6 +315,46 @@ emagged = TRUE return TRUE +/obj/machinery/door/cmag_act(mob/user) + if(!density) + return + flick("door_spark", src) + sleep(6) //The cmag doesn't automatically open doors. It inverts access, not provides it! + ADD_TRAIT(src, TRAIT_CMAGGED, "clown_emag") + return TRUE + +//Proc for inverting access on cmagged doors."canopen" should always return the OPPOSITE of the normal result. +/obj/machinery/door/proc/cmag_switch(canopen, mob/living/user) + if(!canopen || locked || !hasPower()) + if(density) //Windoors can still do their deny animation in unpowered environments, this bugs out if density isn't checked for + do_animate("deny") + if(hasPower() && sound_ready) + playsound(loc, 'sound/machines/honkbot_evil_laugh.ogg', 25, TRUE, ignore_walls = FALSE) + soundcooldown() + return + if(ishuman(user)) + var/mob/living/carbon/human/H = user + var/obj/item/card/id/idcard = H.get_idcard() + if(!idcard?.assignment) //Humans can't game inverted access by taking their ID off or using spare IDs. + if(!density) + return + do_animate("deny") + to_chat(H, "The airlock speaker chuckles: 'What's wrong, pal? Lost your ID? Nyuk nyuk nyuk!'") + if(sound_ready) + playsound(loc, 'sound/machines/honkbot_evil_laugh.ogg', 25, TRUE, ignore_walls = FALSE) + soundcooldown() //Thanks, mechs + return + if(density) + open() + else + close() + +/obj/machinery/door/proc/soundcooldown() + if(!sound_ready) + return + sound_ready = FALSE + addtimer(VARSET_CALLBACK(src, sound_ready, TRUE), sound_cooldown) + /obj/machinery/door/proc/toggle_polarization() return diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 07e4d7a5a26..14b3fdcc979 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -62,6 +62,8 @@ . = ..() if(emagged) . += "Its access panel is smoking slightly." + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + . += "The access panel is coated in yellow ooze..." /obj/machinery/door/window/emp_act(severity) . = ..() @@ -83,8 +85,14 @@ if(ismecha(AM)) var/obj/mecha/mecha = AM if(mecha.occupant && allowed(mecha.occupant)) + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(FALSE) + return open_and_close() else + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(TRUE) + return do_animate("deny") return if(!SSticker) @@ -98,8 +106,14 @@ return add_fingerprint(user) if(!requiresID() || allowed(user)) + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(FALSE, user) + return open_and_close() else + if(HAS_TRAIT(src, TRAIT_CMAGGED)) + cmag_switch(TRUE, user) + return do_animate("deny") /obj/machinery/door/window/unrestricted_side(mob/M) @@ -247,6 +261,17 @@ open(2) return 1 +/obj/machinery/door/window/cmag_act(mob/user, obj/weapon) + if(operating || !density || HAS_TRAIT(src, TRAIT_CMAGGED)) + return + ADD_TRAIT(src, TRAIT_CMAGGED, "clown_emag") + operating = TRUE + flick("[base_state]spark", src) + playsound(src, "sparks", 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + sleep(6) + operating = FALSE + return TRUE + /obj/machinery/door/window/attackby(obj/item/I, mob/living/user, params) //If it's in the process of opening/closing, ignore the click if(operating) diff --git a/code/game/objects/items/control_wand.dm b/code/game/objects/items/control_wand.dm index cb347fe9105..a0235f9f4a7 100644 --- a/code/game/objects/items/control_wand.dm +++ b/code/game/objects/items/control_wand.dm @@ -48,6 +48,9 @@ /obj/item/door_remote/afterattack(obj/machinery/door/airlock/D, mob/user) if(!istype(D)) return + if(HAS_TRAIT(D, TRAIT_CMAGGED)) + to_chat(user, "The door doesn't respond to [src]!") + return if(D.is_special) to_chat(user, "[src] cannot access this kind of door!") return diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm index 62e54e713e8..082ad69ca61 100644 --- a/code/game/objects/items/weapons/cards_ids.dm +++ b/code/game/objects/items/weapons/cards_ids.dm @@ -80,6 +80,27 @@ return A.emag_act(user) +/obj/item/card/cmag + desc = "It's a card coated in a slurry of electromagnetic bananium." + name = "jestographic sequencer" + icon_state = "cmag" + item_state = "card-id" + origin_tech = "magnets=2;syndicate=2" + flags = NOBLUDGEON + flags_2 = NO_MAT_REDEMPTION_2 + +/obj/item/card/cmag/Initialize(mapload) + . = ..() + AddComponent(/datum/component/slippery, src, 16 SECONDS, 100) + +/obj/item/card/cmag/attack() + return + +/obj/item/card/cmag/afterattack(atom/target, mob/user, proximity) + if(!proximity) + return + target.cmag_act(user) + /obj/item/card/id name = "identification card" desc = "A card used to provide ID and determine access across the station." diff --git a/icons/obj/card.dmi b/icons/obj/card.dmi index f51593e0216..41882bb12c0 100644 Binary files a/icons/obj/card.dmi and b/icons/obj/card.dmi differ