|
| 1 | +/obj/item/stack/medical/suture/proc/try_mute(mob/living/mutedone, mob/living/user, healed_zone) |
| 2 | + var/x = mutedone.staminaloss //I skipped my math classes |
| 3 | + var/stamina_modifier = 5 / (x + 1) // trying some fancy math here, for the first time. |
| 4 | + var/obj/item/stack/medical/suture/sew_thing = user.get_active_held_item() |
| 5 | + if(user.zone_selected == BODY_ZONE_PRECISE_MOUTH) |
| 6 | + if(sew_thing.amount < 2) |
| 7 | + to_chat(user, span_danger("You don't have enough of [sew_thing] to do that!")) |
| 8 | + return ITEM_INTERACT_BLOCKING |
| 9 | + |
| 10 | + if(mutedone.has_status_effect(/datum/status_effect/mouth_sewed_up)) |
| 11 | + to_chat(user, span_alert(pick("You can't...", "Their mouth is already sewed..", "Hurr-durr... they are already..", "No...", "This isn't right.."))) |
| 12 | + return ITEM_INTERACT_BLOCKING |
| 13 | + |
| 14 | + user.visible_message( |
| 15 | + span_danger("[user] is trying to sew [mutedone]'s mouth shut with [sew_thing]"), |
| 16 | + span_danger("You begin trying to suture up [mutedone]'s mouth") |
| 17 | + ) |
| 18 | + balloon_alert(user, "sewing mouth shut...") |
| 19 | + playsound(mutedone, heal_begin_sound, 100) |
| 20 | + to_chat(mutedone, span_userdanger("[user] is trying to sew your mouth shut with [sew_thing]")) |
| 21 | + |
| 22 | + if(do_after(user, (7 SECONDS + stamina_modifier SECONDS) / mouth_sewing_force, mutedone)) |
| 23 | + user.visible_message( |
| 24 | + span_danger("[user] sews [mutedone]'s mouth shut"), |
| 25 | + span_danger("You succesfully sew [mutedone]'s mouth with the [sew_thing]") |
| 26 | + ) |
| 27 | + to_chat(mutedone, span_userdanger("[user] has sewed your mouth shut with the [sew_thing]!")) |
| 28 | + mutedone.emote("scream", forced = TRUE) |
| 29 | + mutedone.apply_status_effect(/datum/status_effect/mouth_sewed_up) |
| 30 | + //to_chat(mutedone, span_danger("mutedforreal")) |
| 31 | + playsound(mutedone, heal_end_sound, 100) |
| 32 | + |
| 33 | + log_combat(user, mutedone, "[user] has used [sew_thing] on [mutedone] to prevent them from speaking", sew_thing) |
| 34 | + sew_thing.amount -= 2 |
| 35 | + sew_thing.update_appearance() |
| 36 | + return |
| 37 | + |
| 38 | +/obj/item/stack/medical/suture/interact_with_atom_secondary(atom/interacting_with, mob/living/user, list/modifiers) |
| 39 | + if(!isliving(interacting_with)) |
| 40 | + return NONE |
| 41 | + if(!try_mute(interacting_with, user)) |
| 42 | + return NONE |
| 43 | + return ITEM_INTERACT_SUCCESS |
| 44 | + |
| 45 | +/datum/status_effect/mouth_sewed_up |
| 46 | + id = "mouth_sewed_up" |
| 47 | + duration = STATUS_EFFECT_PERMANENT |
| 48 | + tick_interval = STATUS_EFFECT_NO_TICK |
| 49 | + alert_type = /atom/movable/screen/alert/status_effect/mouth_sewed_up |
| 50 | + |
| 51 | +/datum/status_effect/mouth_sewed_up/get_examine_text() |
| 52 | + . = ..() |
| 53 | + return span_danger("[owner.p_Their()] mouth appears to be sewed shut with some suture!") |
| 54 | + |
| 55 | +/atom/movable/screen/alert/status_effect/mouth_sewed_up |
| 56 | + name = "Mute!" |
| 57 | + desc = "Whoops! Someone has sewed your mouth shut, \ |
| 58 | + press resist to try break the suture by your mouth \ |
| 59 | + or use any sharp item to tear it away!" |
| 60 | + overlay_state = "mind_control" |
| 61 | + attached_effect = /datum/status_effect/mouth_sewed_up |
| 62 | + |
| 63 | +/datum/status_effect/mouth_sewed_up/on_apply() |
| 64 | + if(ishuman(owner)) |
| 65 | + // owner.add_traits(list(TRAIT_MUTE), REF(src)) |
| 66 | + RegisterSignal(owner, COMSIG_MOB_SAY, PROC_REF(muzzle_talk)) |
| 67 | + // RegisterSignal(owner, COMSIG_MOB_PRE_EMOTED, PROC_REF(emote_override)) // Wait, why did we ctrl+c ctrl+v this blindly? Does having your mouth sewed prevent you from moving your hands or emoting? - No. |
| 68 | + RegisterSignal(owner, COMSIG_LIVING_RESIST, PROC_REF(handle_resist)) |
| 69 | + RegisterSignal(owner, COMSIG_ATOM_ATTACKBY, PROC_REF(someone_removing_sutures)) |
| 70 | + return TRUE |
| 71 | + |
| 72 | +/datum/status_effect/mouth_sewed_up/on_remove() |
| 73 | + if(ishuman(owner)) |
| 74 | + owner.remove_traits(list(TRAIT_MUTE), REF(src)) |
| 75 | + UnregisterSignal(owner, list(COMSIG_MOB_SAY, COMSIG_MOB_PRE_EMOTED, COMSIG_LIVING_RESIST, COMSIG_ATOM_ATTACKBY)) |
| 76 | + return TRUE |
| 77 | + |
| 78 | + |
| 79 | +/* see comment on on_apply() |
| 80 | +/datum/status_effect/mouth_sewed_up/proc/emote_override(mob/living/source, key, params, type_override, intentional, datum/emote/emote) //thx to whoever made muffles_speech.dm |
| 81 | + SIGNAL_HANDLER |
| 82 | + if(!emote.hands_use_check && (emote.emote_type & EMOTE_AUDIBLE)) |
| 83 | + source.audible_message("makes a [pick("strong ", "weak ", "")]noise.", audible_message_flags = EMOTE_MESSAGE|ALWAYS_SHOW_SELF_MESSAGE) |
| 84 | + return COMPONENT_CANT_EMOTE |
| 85 | + return NONE |
| 86 | +*/ |
| 87 | + |
| 88 | +/datum/status_effect/mouth_sewed_up/proc/muzzle_talk(datum/source, list/speech_args) //thx to whoever made muffles_speech.dm |
| 89 | + SIGNAL_HANDLER |
| 90 | + if(HAS_TRAIT(source, TRAIT_SIGN_LANG)) |
| 91 | + return |
| 92 | + var/spoken_message = speech_args[SPEECH_MESSAGE] |
| 93 | + if(spoken_message) |
| 94 | + var/list/words = splittext(spoken_message, " ") |
| 95 | + var/yell_suffix = copytext(spoken_message, findtext(spoken_message, "!")) |
| 96 | + spoken_message = "" |
| 97 | + |
| 98 | + for(var/ind = 1 to length(words)) |
| 99 | + var/new_word = "" |
| 100 | + for(var/i = 1 to length(words[ind]) + rand(-1,1)) |
| 101 | + new_word += "m" |
| 102 | + new_word += "f" |
| 103 | + words[ind] = yell_suffix ? uppertext(new_word) : new_word |
| 104 | + spoken_message = "[jointext(words, " ")][yell_suffix]" |
| 105 | + speech_args[SPEECH_MESSAGE] = spoken_message |
| 106 | + |
| 107 | +/datum/status_effect/mouth_sewed_up/proc/handle_resist(datum/source) |
| 108 | + SIGNAL_HANDLER |
| 109 | + INVOKE_ASYNC(src, PROC_REF(handle_resist_async), source) |
| 110 | + return COMPONENT_BLOCK_MISC_HELP |
| 111 | + |
| 112 | +// resist out of the, uhh.. sutures? |
| 113 | +/datum/status_effect/mouth_sewed_up/proc/handle_resist_async(mob/living/carbon/muteman) |
| 114 | + muteman.visible_message( |
| 115 | + message = span_warning("[muteman] starts tearing at the sutures on [muteman.p_their()] mouth!"), |
| 116 | + self_message = span_danger("You start tearing at the sutures on your mouth... This will hurt!") |
| 117 | + ) |
| 118 | + if(do_after(muteman, 20 SECONDS, target = muteman)) //Yeah, those take some time.. those are surgery grade. |
| 119 | + if(!muteman.has_status_effect(/datum/status_effect/mouth_sewed_up)) |
| 120 | + return |
| 121 | + playsound(muteman, 'sound/items/weapons/slice.ogg', 55, TRUE) |
| 122 | + muteman.visible_message( |
| 123 | + message = span_danger("[muteman] tears the sutures off [muteman.p_their()] mouth!"), |
| 124 | + self_message = span_userdanger("You tear the sutures off your mouth! AGH!") |
| 125 | + ) |
| 126 | + if(ishuman(muteman)) |
| 127 | + var/obj/item/bodypart/golova = muteman.get_bodypart(BODY_ZONE_HEAD) |
| 128 | + if(golova) |
| 129 | + muteman.apply_damage(10, BRUTE, BODY_ZONE_HEAD) |
| 130 | + muteman.cause_wound_of_type_and_severity(WOUND_SLASH, golova, WOUND_SEVERITY_TRIVIAL, WOUND_SEVERITY_MODERATE) |
| 131 | + muteman.emote("scream", forced = TRUE) |
| 132 | + qdel(src) // delete status effect |
| 133 | + |
| 134 | +//TODO: Forbid felinids to use their mouth/bite attack as if restrained or as if their mouth has been covered by a mask. |
| 135 | + |
| 136 | +/datum/status_effect/mouth_sewed_up/proc/someone_removing_sutures(atom/source, obj/item/weapon, mob/living/user, list/modifiers) |
| 137 | + SIGNAL_HANDLER |
| 138 | + // what are we targeting? |
| 139 | + if(user.zone_selected != BODY_ZONE_PRECISE_MOUTH) |
| 140 | + return NONE |
| 141 | + // is it sharp? |
| 142 | + if(!weapon) |
| 143 | + return NONE |
| 144 | + |
| 145 | + var/sharpness = weapon.get_sharpness() |
| 146 | + if(sharpness != SHARP_EDGED && sharpness != SHARP_POINTY) |
| 147 | + return NONE |
| 148 | + |
| 149 | + INVOKE_ASYNC(src, PROC_REF(try_remove_sutures_async), user, weapon) |
| 150 | + return COMPONENT_CANCEL_ATTACK_CHAIN |
| 151 | + |
| 152 | +/datum/status_effect/mouth_sewed_up/proc/try_remove_sutures_async(mob/living/helper, obj/item/tool) |
| 153 | + if(DOING_INTERACTION(helper, id)) |
| 154 | + return |
| 155 | + helper.visible_message( |
| 156 | + span_warning("[helper] starts cutting the sutures on [owner] mouth with [tool]!"), |
| 157 | + span_notice("You start cutting the sutures on [owner] mouth with [tool]!"), |
| 158 | + ) |
| 159 | + |
| 160 | + if(do_after(helper, 5.5 SECONDS, owner, extra_checks = CALLBACK(src, PROC_REF(try_remove_sutures_checks)), interaction_key = id)) |
| 161 | + playsound(owner, 'sound/items/weapons/slice.ogg', 55, TRUE) |
| 162 | + owner.visible_message( |
| 163 | + span_danger("[helper] cuts the sutures from [owner]'s mouth!"), |
| 164 | + span_danger("You cut the sutures from [owner]'s mouth!"), |
| 165 | + ) |
| 166 | + qdel(src) |
| 167 | + else |
| 168 | + var/mob/living/carbon/carbon_owner = owner // cause_wound_of_type_and_severity() works only for carbons and carbons only |
| 169 | + var/obj/item/bodypart/bashka = carbon_owner.get_bodypart(BODY_ZONE_HEAD) |
| 170 | + if(bashka) |
| 171 | + carbon_owner.apply_damage(3, BRUTE, BODY_ZONE_HEAD) |
| 172 | + playsound(owner, 'sound/effects/wounds/pierce1.ogg', 50, TRUE) |
| 173 | + if(prob(50)) |
| 174 | + carbon_owner.cause_wound_of_type_and_severity(WOUND_SLASH, bashka, WOUND_SEVERITY_TRIVIAL, WOUND_SEVERITY_MODERATE) |
| 175 | + owner.visible_message( |
| 176 | + span_danger("[helper] cuts the sutures from [owner]'s mouth, but [helper]'s hand slips, leaving their face with a nasty cut in process!"), |
| 177 | + span_danger("You cut open [owner]'s mouth free. But your hands slips and cuts their face, leaving a nasty cut!") |
| 178 | + ) |
| 179 | + return |
| 180 | + |
| 181 | +/datum/status_effect/mouth_sewed_up/proc/try_remove_sutures_checks() |
| 182 | + return !QDELETED(src) // code\modules\projectiles\projectile\energy\stun.dm ln. 339-340 - I dunno what it's really for, but I suppose it's there for a reason |
| 183 | + |
| 184 | +/// Override is_mouth_covered() to prevent felinid bite attacks when mouth is sewed |
| 185 | +/mob/living/carbon/human/is_mouth_covered(check_flags = ALL) |
| 186 | + if(has_status_effect(/datum/status_effect/mouth_sewed_up)) |
| 187 | + return src |
| 188 | + return ..() |
0 commit comments