diff --git a/__DEFINES/_macros.dm b/__DEFINES/_macros.dm index 7ac9f615add..45ad52a9293 100644 --- a/__DEFINES/_macros.dm +++ b/__DEFINES/_macros.dm @@ -162,6 +162,8 @@ #define isfood(A) istype(A, /obj/item/weapon/reagent_containers/food) +#define ismuzzle(A) A.is_muzzle + #define iswirecutter(A) istype(A, /obj/item/tool/wirecutters) #define iswiretool(A) (iswirecutter(A) || ismultitool(A) || issignaler(A)) diff --git a/__DEFINES/setup.dm b/__DEFINES/setup.dm index cf861e4214f..2290bce6ba3 100644 --- a/__DEFINES/setup.dm +++ b/__DEFINES/setup.dm @@ -1065,9 +1065,10 @@ var/default_colour_matrix = list(1,0,0,0,\ //Language flags. -#define WHITELISTED 1 // Language is available if the speaker is whitelisted. -#define RESTRICTED 2 // Language can only be accquired by spawning or an admin. -#define CAN_BE_SECONDARY_LANGUAGE 4 // Language is available on character setup as secondary language. +#define WHITELISTED (1<<0) // Language is available if the speaker is whitelisted. +#define RESTRICTED (1<<1) // Language can only be accquired by spawning or an admin. +#define CAN_BE_SECONDARY_LANGUAGE (1<<2) // Language is available on character setup as secondary language. +#define NONORAL (1<<3) //Language is spoken without using the mouth, so can be spoken while muzzled. // Hairstyle flags #define HAIRSTYLE_CANTRIP 1 // 5% chance of tripping your stupid ass if you're running. @@ -1824,3 +1825,7 @@ var/list/weekend_days = list("Friday", "Saturday", "Sunday") #define COIN_HEADS "heads-up." #define COIN_TAILS "tails-up." #define COIN_SIDE "on the side!" + +//Muzzles +#define MUZZLE_SOFT 1 //Muzzle causes muffled speech. +#define MUZZLE_HARD 2 //Muzzle prevents speech. diff --git a/code/datums/gamemode/factions/bloodcult/bloodcult_runes.dm b/code/datums/gamemode/factions/bloodcult/bloodcult_runes.dm index f6cf998da79..1d2f1ad6002 100644 --- a/code/datums/gamemode/factions/bloodcult/bloodcult_runes.dm +++ b/code/datums/gamemode/factions/bloodcult/bloodcult_runes.dm @@ -368,7 +368,7 @@ var/list/rune_appearances_cache = list() return if(!user.checkTattoo(TATTOO_SILENT)) - if(user.is_wearing_item(/obj/item/clothing/mask/muzzle, slot_wear_mask)) + if(ismuzzle(user.wear_mask)) to_chat(user, "You are unable to speak the words of the rune because of \the [user.wear_mask].") return diff --git a/code/datums/gamemode/factions/legacy_cult/ritual.dm b/code/datums/gamemode/factions/legacy_cult/ritual.dm index 7a5b4a17d04..4badb07d45d 100644 --- a/code/datums/gamemode/factions/legacy_cult/ritual.dm +++ b/code/datums/gamemode/factions/legacy_cult/ritual.dm @@ -83,7 +83,7 @@ var/runedec = 0 // Rune cap ? if(!islegacycultist(user)) to_chat(user, "You can't mouth the arcane scratchings without fumbling over them.") return - if(istype(user.wear_mask, /obj/item/clothing/mask/muzzle)) + if(ismuzzle(user.wear_mask)) to_chat(user, "You are unable to speak the words of the rune.") return if(!word1 || !word2 || !word3 || prob(user.getBrainLoss())) diff --git a/code/datums/gamemode/factions/legacy_cult/runes.dm b/code/datums/gamemode/factions/legacy_cult/runes.dm index d68fa5917b3..12170e91449 100644 --- a/code/datums/gamemode/factions/legacy_cult/runes.dm +++ b/code/datums/gamemode/factions/legacy_cult/runes.dm @@ -1078,7 +1078,7 @@ possible_targets += cultistarget else if (cultistarget.legcuffed) possible_targets += cultistarget - else if (istype(cultistarget.wear_mask, /obj/item/clothing/mask/muzzle)) + else if (ismuzzle(cultistarget.wear_mask)) possible_targets += cultistarget else if (istype(cultistarget.loc, /obj/structure/closet)) var/obj/structure/closet/closet = cultistarget.loc @@ -1105,7 +1105,7 @@ return if(!(cultist.locked_to || \ cultist.handcuffed || \ - istype(cultist.wear_mask, /obj/item/clothing/mask/muzzle) || \ + ismuzzle(cultist.wear_mask) || \ (istype(cultist.loc, /obj/structure/closet)&&cultist.loc:welded) || \ (istype(cultist.loc, /obj/structure/closet/secure_closet)&&cultist.loc:locked) || \ (istype(cultist.loc, /obj/machinery/dna_scannernew)&&cultist.loc:locked) \ @@ -1117,7 +1117,7 @@ cultist.drop_from_inventory(cultist.handcuffed) if (cultist.legcuffed) cultist.drop_from_inventory(cultist.legcuffed) - if (istype(cultist.wear_mask, /obj/item/clothing/mask/muzzle)) + if (ismuzzle(cultist.wear_mask)) cultist.u_equip(cultist.wear_mask, 1) if(istype(cultist.loc, /obj/structure/closet)) var/obj/structure/closet/closet = cultist.loc diff --git a/code/datums/gamemode/role/vampire_role.dm b/code/datums/gamemode/role/vampire_role.dm index 5142f02f316..10dee9c4faa 100644 --- a/code/datums/gamemode/role/vampire_role.dm +++ b/code/datums/gamemode/role/vampire_role.dm @@ -164,8 +164,14 @@ if(ishuman(M)) var/mob/living/carbon/human/vamp_H = M + + if(vamp_H.is_muzzled()) + to_chat(M, " The [vamp_H.wear_mask] prevents you from biting!") + return FALSE + if(vamp_H.check_body_part_coverage(MOUTH)) to_chat(M, "With practiced ease, you shift aside your mask for each gulp of blood.") + return TRUE diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index c3b89426ad7..2aed903a6fb 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -63,6 +63,8 @@ var/restraint_resist_time = 0 //When set, allows the item to be applied as restraints, which take this amount of time to resist out of var/restraint_apply_time = 3 SECONDS var/icon/wear_override = null //Worn state override used when wearing this object on your head/uniform/glasses/etc slot, for making a more procedurally generated icon + var/goes_in_mouth //Whether or not the item is described as "on his/her face" or "in his/her mouth" when worn on the face slot. + var/is_muzzle //Whether or not the item is a muzzle, and how strong the muzzling effect is. See setup.dm. var/hides_identity = HIDES_IDENTITY_DEFAULT var/datum/daemon/daemon @@ -493,6 +495,11 @@ return CAN_EQUIP_BUT_SLOT_TAKEN else return CANNOT_EQUIP + + if(goes_in_mouth && !H.hasmouth()) //Item is equipped to the mouth but the species has no mouth. + to_chat(H, "You have no mouth.") + return CANNOT_EQUIP + return CAN_EQUIP if(slot_back) if( !(slot_flags & SLOT_BACK) ) diff --git a/code/game/objects/items/weapons/cigs_lighters.dm b/code/game/objects/items/weapons/cigs_lighters.dm index 6556af39191..45f163ca031 100644 --- a/code/game/objects/items/weapons/cigs_lighters.dm +++ b/code/game/objects/items/weapons/cigs_lighters.dm @@ -181,6 +181,7 @@ MATCHBOXES ARE ALSO IN FANCY.DM source_temperature = TEMPERATURE_FLAME light_color = LIGHT_COLOR_FIRE slot_flags = SLOT_MASK|SLOT_EARS + goes_in_mouth = TRUE var/lit = 0 var/overlay_on = "ciglit" //Apparently not used var/type_butt = /obj/item/trash/cigbutt @@ -558,13 +559,6 @@ MATCHBOXES ARE ALSO IN FANCY.DM // CIGARS // //////////// -/obj/item/clothing/mask/cigarette/mob_can_equip(mob/M, slot, disable_warning = 0, automatic = 0) - var/mob/living/carbon/C = M - if(!istype(C) || !C.hasmouth()) - to_chat(C, "You have no mouth.") - return CANNOT_EQUIP - . = ..() - /obj/item/clothing/mask/cigarette/cigar name = "Premium Cigar" desc = "A large roll of tobacco and... well, you're not quite sure. This thing's huge!" diff --git a/code/game/objects/structures/bedsheet_bin.dm b/code/game/objects/structures/bedsheet_bin.dm index f940a3e83bc..d4af6204bf0 100644 --- a/code/game/objects/structures/bedsheet_bin.dm +++ b/code/game/objects/structures/bedsheet_bin.dm @@ -27,11 +27,11 @@ LINEN BINS if(I.is_sharp()) cut_time = 60 / I.sharpness if(cut_time) - to_chat(user, "You begin cutting the [src].") + to_chat(user, "You begin cutting up [src].") if(do_after(user, src, cut_time)) if(!src) return - to_chat(user, "You have cut the [src] into rags.") + to_chat(user, "You finish cutting [src] into rags.") var/turf/location = get_turf(src) for(var/x=0; x<=8; x++) var/obj/item/weapon/reagent_containers/glass/rag/S = new/obj/item/weapon/reagent_containers/glass/rag/(location) @@ -45,7 +45,6 @@ LINEN BINS //todo: more cutting tools? //todo: sharp thing code/game/objects/objs.dm - /obj/item/weapon/bedsheet/blue icon_state = "sheetblue" _color = "blue" diff --git a/code/modules/clothing/masks/miscellaneous.dm b/code/modules/clothing/masks/miscellaneous.dm index 7038c95e27f..4d5f7de937f 100644 --- a/code/modules/clothing/masks/miscellaneous.dm +++ b/code/modules/clothing/masks/miscellaneous.dm @@ -10,6 +10,7 @@ species_fit = list(VOX_SHAPED, INSECT_SHAPED, GREY_SHAPED) origin_tech = Tc_BIOTECH + "=2" body_parts_covered = MOUTH + is_muzzle = MUZZLE_HARD //Monkeys can not take the muzzle off of themself! Call PETA! /obj/item/clothing/mask/muzzle/attack_paw(mob/user as mob) diff --git a/code/modules/detectivework/footprints_and_rag.dm b/code/modules/detectivework/footprints_and_rag.dm index e0b322b04cd..377c852f661 100644 --- a/code/modules/detectivework/footprints_and_rag.dm +++ b/code/modules/detectivework/footprints_and_rag.dm @@ -15,10 +15,15 @@ w_class = W_CLASS_TINY icon = 'icons/obj/toy.dmi' icon_state = "rag" + item_state = new/icon("icon" = 'icons/mob/mask.dmi', "icon_state" = "rag") amount_per_transfer_from_this = 5 possible_transfer_amounts = list(5) volume = 5 can_be_placed_into = null + slot_flags = SLOT_MASK + body_parts_covered = MOUTH + goes_in_mouth = TRUE + is_muzzle = MUZZLE_SOFT var/mob/current_target = null /obj/item/weapon/reagent_containers/glass/rag/attack_self(mob/user as mob) @@ -99,3 +104,25 @@ qdel(O) reagents.remove_any(1) user.visible_message("[user] finishes wiping down \the [target].", "You have finished wiping down \the [target]!") + +/obj/item/weapon/reagent_containers/glass/rag/process() + //Reagents in the rag gradually get transferred into the wearer. Copied from cigs_lighters.dm. + var/mob/living/M = get_holder_of_type(src, /mob/living) + if(reagents && reagents.total_volume) //Check if it has any reagents at all + if(iscarbon(M) && ((src == M.wear_mask) || (loc == M.wear_mask))) //If it's in the human/monkey mouth, transfer reagents to the mob + if(M.reagents.has_any_reagents(LEXORINS) || istype(M.loc, /obj/machinery/atmospherics/unary/cryo_cell)) + reagents.remove_any(REAGENTS_METABOLISM) + else + reagents.reaction(M, INGEST) + reagents.trans_to(M, 0.5) + else + processing_objects.Remove(src) + +/obj/item/weapon/reagent_containers/glass/rag/equipped(mob/living/carbon/human/H, equipped_slot) + ..() + if(istype(H) && H.get_item_by_slot(slot_wear_mask) == src && equipped_slot != null && equipped_slot == slot_wear_mask) + processing_objects.Add(src) + +/obj/item/weapon/reagent_containers/glass/rag/unequipped(mob/living/carbon/human/user, from_slot = null) + ..() + processing_objects.Remove(src) diff --git a/code/modules/mob/language.dm b/code/modules/mob/language.dm index 3b52fd89af7..a9df254bae8 100644 --- a/code/modules/mob/language.dm +++ b/code/modules/mob/language.dm @@ -138,6 +138,7 @@ exclaim_verb = "rustles" colour = "soghun" key = "q" + flags = NONORAL syllables = list("hs","zt","kr","st","sh") /datum/language/common @@ -221,6 +222,7 @@ colour = "sinister" native=1 space_chance = 95 + flags = NONORAL syllables = list("CLICK", "CLACK") /datum/language/golem @@ -233,6 +235,7 @@ colour = "golem" native = 1 key = "8" + flags = NONORAL syllables = list("oa","ur","ae","um","tu","gor","an","lo","ag","oon","po") /datum/language/slime @@ -245,6 +248,7 @@ colour = "slime" native = 1 key = "f" + flags = NONORAL syllables = list("ba","ab","be","eb","bi","ib","bo","ob","bu","ub") /datum/language/skellington/say_misunderstood(mob/M, message) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 019ab80e729..fe3904fecdb 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -462,8 +462,8 @@ return borers_in_mob /mob/living/carbon/is_muzzled() - return(istype(get_item_by_slot(slot_wear_mask), /obj/item/clothing/mask/muzzle)) - + var/obj/item/M = get_item_by_slot(slot_wear_mask) + return M?.is_muzzle /mob/living/carbon/proc/isInCrit() // Health is in deep shit and we're not already dead diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index d7cf174d5b3..52f61dcb790 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -131,9 +131,9 @@ //mask if(wear_mask && !(slot_wear_mask in obscured) && wear_mask.is_visible()) if(wear_mask.blood_DNA && wear_mask.blood_DNA.len) - msg += "[t_He] [t_has] [bicon(wear_mask)] [wear_mask.gender==PLURAL?"some":"a"] blood-stained [wear_mask.name] on [t_his] face! [format_examine(wear_mask, "Examine")][wear_mask.description_accessories()]\n" + msg += "[t_He] [t_has] [bicon(wear_mask)] [wear_mask.gender==PLURAL?"some":"a"] blood-stained [wear_mask.name] [wear_mask.goes_in_mouth ? "in" : "on"] [t_his] [wear_mask.goes_in_mouth ? "mouth" : "face"]! [format_examine(wear_mask, "Examine")][wear_mask.description_accessories()]\n" else - msg += "[t_He] [t_has] [bicon(wear_mask)] \a [wear_mask] on [t_his] face. [format_examine(wear_mask, "Examine")][wear_mask.description_accessories()]\n" + msg += "[t_He] [t_has] [bicon(wear_mask)] \a [wear_mask] [wear_mask.goes_in_mouth ? "in" : "on"] [t_his] [wear_mask.goes_in_mouth ? "mouth" : "face"]. [format_examine(wear_mask, "Examine")][wear_mask.description_accessories()]\n" //eyes if(glasses && !(slot_glasses in obscured) && glasses.is_visible()) diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 0209272aa2a..0b10e2bfd3b 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -90,7 +90,7 @@ emp_act var/siemens_coefficient = 1.0 var/list/clothing_items = list(head, wear_mask, wear_suit, w_uniform, gloves, shoes) // What all are we checking? - for(var/obj/item/clothing/C in clothing_items) + for(var/obj/item/C in clothing_items) if(istype(C) && (C.body_parts_covered & def_zone.body_part)) // Is that body part being targeted covered? siemens_coefficient *= C.siemens_coefficient @@ -136,7 +136,7 @@ emp_act if(!body_part_flags) return 0 var/parts_to_check = body_part_flags - for(var/obj/item/clothing/C in get_clothing_items()) + for(var/obj/item/C in get_clothing_items()) if(!C) continue if(ignored && C == ignored) @@ -151,7 +151,7 @@ emp_act /mob/living/carbon/human/proc/get_body_part_coverage(var/body_part_flags=0) if(!body_part_flags) return null - for(var/obj/item/clothing/C in get_clothing_items()) + for(var/obj/item/C in get_clothing_items()) if(!C) continue //Check if this piece of clothing contains ALL of the flags we want to check. @@ -163,7 +163,7 @@ emp_act //Because get_body_part_coverage(FULL_BODY) would only return true if the human has one piece of clothing that covers their whole body by itself. var/body_coverage = FULL_BODY | FULL_HEAD - for(var/obj/item/clothing/C in get_clothing_items()) + for(var/obj/item/C in get_clothing_items()) if(!C) continue body_coverage &= ~(C.body_parts_covered) diff --git a/code/modules/mob/living/carbon/monkey/combat.dm b/code/modules/mob/living/carbon/monkey/combat.dm index 894c056c738..5180b8f84cb 100644 --- a/code/modules/mob/living/carbon/monkey/combat.dm +++ b/code/modules/mob/living/carbon/monkey/combat.dm @@ -11,7 +11,7 @@ return 2 /mob/living/carbon/monkey/unarmed_attack_mob(mob/living/) - if(istype(wear_mask, /obj/item/clothing/mask/muzzle)) + if(ismuzzle(wear_mask)) to_chat(src, "You can't do this with \the [wear_mask] on!") return diff --git a/code/modules/mob/living/carbon/monkey/examine.dm b/code/modules/mob/living/carbon/monkey/examine.dm index 8fda822970d..7355efedbad 100644 --- a/code/modules/mob/living/carbon/monkey/examine.dm +++ b/code/modules/mob/living/carbon/monkey/examine.dm @@ -7,7 +7,8 @@ if (src.hat && hat.is_visible()) msg += "It is wearing [bicon(src.hat)] \a [src.hat] on its head.[hat.description_accessories()][hat.description_hats()]\n" if (src.wear_mask && !(wear_mask.invisibility || wear_mask.alpha <= 1)) - msg += "It has [bicon(src.wear_mask)] \a [src.wear_mask] over its face.\n" + msg += "It has [bicon(src.wear_mask)] \a [src.wear_mask] [wear_mask.goes_in_mouth ? "in" : "over"] its [wear_mask.goes_in_mouth ? "mouth" : "face"].\n" + if (src.glasses && glasses.is_visible()) msg += "It is wearing [bicon(src.glasses)] \a [src.glasses] over its eyes.\n" diff --git a/code/modules/mob/living/carbon/stripping.dm b/code/modules/mob/living/carbon/stripping.dm index 3e0b88256d9..d7992a86ae4 100644 --- a/code/modules/mob/living/carbon/stripping.dm +++ b/code/modules/mob/living/carbon/stripping.dm @@ -104,7 +104,10 @@ return if(!pickpocket) - visible_message("\The [user] is trying to put \a [held] on \the [src]!") + if(held.goes_in_mouth && slot == SLOT_MASK) + visible_message("\The [user] is trying to put \a [held] in \the [src]'s mouth!") + else + visible_message("\The [user] is trying to put \a [held] on \the [src]!") if(reversestrip_into_slot(user, slot, pickpocket)) user.attack_log += text("\[[time_stamp()]\] Has put \a [held] into [src.name]'s [src.slotID2slotname(slot)] ([src.ckey])") diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 185eca65379..581dbc0e884 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -163,6 +163,7 @@ var/list/headset_modes = list( speech.language = parse_language(speech.message) say_testing(src, "Getting speaking language, got [istype(speech.language) ? speech.language.name : "null"]") if(istype(speech.language)) + #ifdef SAY_DEBUG var/oldmsg = message #endif @@ -180,6 +181,17 @@ var/list/headset_modes = list( speech.language = get_default_language() say_testing(src, "Didnt have a language, get_default_language() gave us [speech.language ? speech.language.name : "null"]") speech.message = trim_left(speech.message) + + //Handle speech muffling by muzzles. + if(!(speech?.language?.flags & NONORAL)) + var/mob/living/carbon/C = src + switch(C.is_muzzled()) + if(MUZZLE_SOFT) + speech.message = muffle(speech.message) + if(MUZZLE_HARD) + qdel(speech) + return + if(handle_inherent_channels(speech, message_mode)) say_testing(src, "Handled by inherent channel") qdel(speech) @@ -377,9 +389,6 @@ var/list/headset_modes = list( if(is_mute()) return - if(is_muzzled()) - return - if(!IsVocal()) return @@ -727,3 +736,30 @@ var/list/headset_modes = list( /obj/effect/speech_bubble var/mob/parent + +//Muffles a message for when muzzled. +/proc/muffle(var/message) + var/muffle_syllables = list("mh","mph","mm","mgh","mg") + var/unmuffled = list(" ", "-", ",", ".", "!", "?") + var/output = "" + var/i = 1 + var/current_char + while(i <= length(message)) + current_char = message[i] + if(current_char in unmuffled) + output += current_char + i += 1 + else + var/length_to_add = 1 + var/allcaps = uppertext(message[i]) == message[i] + while((i + length_to_add <= length(message)) && (length_to_add < 3)) + if(message[i + length_to_add] in unmuffled) + break + allcaps &= uppertext(message[i + length_to_add]) == message[i + length_to_add] + length_to_add += 1 + i += length_to_add + if(allcaps) + output += uppertext(pick(muffle_syllables)) + else + output += pick(muffle_syllables) + return output \ No newline at end of file diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm index 1013c98f20b..86aebbf20ba 100644 --- a/code/modules/mob/say.dm +++ b/code/modules/mob/say.dm @@ -189,4 +189,4 @@ var/list/global_deadchat_listeners = list() temp += pick(append) say(temp) - winset(client, "input", "text=[null]") + winset(client, "input", "text=[null]") \ No newline at end of file diff --git a/code/modules/reagents/reagent_containers/food/snacks.dm b/code/modules/reagents/reagent_containers/food/snacks.dm index 6fba62f68d7..0281e5bd99c 100644 --- a/code/modules/reagents/reagent_containers/food/snacks.dm +++ b/code/modules/reagents/reagent_containers/food/snacks.dm @@ -5243,6 +5243,7 @@ icon = 'icons/obj/candymachine.dmi' bitesize = 5 slot_flags = SLOT_MASK //No, really, suck on this. + goes_in_mouth = TRUE attack_verb = list("taps", "pokes") eatverb = "crunch" valid_utensils = 0 diff --git a/code/modules/spells/spell_code.dm b/code/modules/spells/spell_code.dm index 3fcded0def8..ca8c15f17ad 100644 --- a/code/modules/spells/spell_code.dm +++ b/code/modules/spells/spell_code.dm @@ -401,7 +401,7 @@ var/list/spells = typesof(/spell) //needed for the badmin verb for now return 0 if((ishuman(user) || ismonkey(user)) && !(invocation_type in list(SpI_EMOTE, SpI_NONE))) - if(istype(user.wear_mask, /obj/item/clothing/mask/muzzle)) + if(ismuzzle(user.wear_mask)) to_chat(user, "Mmmf mrrfff!") return 0 diff --git a/icons/mob/mask.dmi b/icons/mob/mask.dmi index 185d506294b..1724cbd6054 100644 Binary files a/icons/mob/mask.dmi and b/icons/mob/mask.dmi differ