From 6bdf052a84c07ff54065dd7906d9c9da540a07b8 Mon Sep 17 00:00:00 2001 From: lizardqueenlexi <105025397+lizardqueenlexi@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:43:25 -0500 Subject: [PATCH] Converts cursed heart effect into a component. (#78554) ## About The Pull Request Fixes #58401 Fixes #58799 Fixes #58800 Converts the manual heart-beating effect of the cursed heart into a component. Behavior has mostly been maintained, but polished a bit as compared to the original cursed heart. Most notably, the action for beating your heart is now a cooldown action - providing a visual indicator of when you can beat it again, rather than leaving you guessing. Some better checks have also been put in place for edge cases such as having your species changed. Implementation inspired by the existing "manual blinking" and "manual breathing" components. Currently only used by the cursed heart and the (now majorly simplified) effect of corazargh. My first component, so hopefully I didn't miss anything. ## Why It's Good For The Game The cursed heart was kind of unusably bad - which may have been part of the intent, but having to count in your head or spam-click the button is just annoying. With a visual indicator of when you should beat your heart, hopefully it will be slightly less awful for the cursed. The real motivation here was that corazargh's implementation was kind of a travesty - summoning a cursed heart inside of your body while it was in your system, then restoring your old heart afterward. This was error-prone as well as just being ridiculous. Making this effect a component gets rid of these problems, and leaves space open for new uses of manual heart beating if anyone feels like being cruel. ## Changelog :cl: fix: Your heart will no longer be deleted if an admin heals you while you have corazargh in your system. refactor: The cursed heart has been streamlined a bit, and now gives you a visual cooldown for when you can beat your heart again. /:cl: --- code/__DEFINES/dcs/signals/signals_action.dm | 5 +- code/datums/components/manual_heart.dm | 179 ++++++++++++++++++ .../antagonists/wizard/equipment/artefact.dm | 2 +- code/modules/client/client_colour.dm | 4 + .../impure_medicine_reagents.dm | 78 +------- .../surgery/organs/internal/heart/_heart.dm | 76 +------- tgstation.dme | 1 + 7 files changed, 201 insertions(+), 144 deletions(-) create mode 100644 code/datums/components/manual_heart.dm diff --git a/code/__DEFINES/dcs/signals/signals_action.dm b/code/__DEFINES/dcs/signals/signals_action.dm index 27206e483c5..6fbf5372acd 100644 --- a/code/__DEFINES/dcs/signals/signals_action.dm +++ b/code/__DEFINES/dcs/signals/signals_action.dm @@ -40,8 +40,11 @@ /// From base of /datum/action/cooldown/mob_cooldown/lava_swoop/proc/swoop_attack(): () #define COMSIG_LAVA_ARENA_FAILED "mob_lava_arena_failed" -///From /datum/action/vehicle/sealed/mecha/mech_toggle_safeties/proc/update_action_icon(): () +/// From /datum/action/vehicle/sealed/mecha/mech_toggle_safeties/proc/update_action_icon(): () #define COMSIG_MECH_SAFETIES_TOGGLE "mech_safeties_toggle" /// From /datum/action/cooldown/mob_cooldown/assume_form/proc/assume_appearances(), sent to the action owner: (atom/movable/target) #define COMSIG_ACTION_DISGUISED_APPEARANCE "mob_ability_disguise_appearance" + +/// From /datum/action/cooldown/manual_heart/Activate(): () +#define COMSIG_HEART_MANUAL_PULSE "heart_manual_pulse" diff --git a/code/datums/components/manual_heart.dm b/code/datums/components/manual_heart.dm new file mode 100644 index 00000000000..09448acfc67 --- /dev/null +++ b/code/datums/components/manual_heart.dm @@ -0,0 +1,179 @@ +/// If a beat is missed, how long to give before the next is missed +#define MANUAL_HEART_GRACE_PERIOD 2 SECONDS + +/** + * Manual heart pumping component. Requires the holder to pump their heart manually every + * so often or die. + * + * Mainly used by the cursed heart. + */ +/datum/component/manual_heart + /// The action for pumping your heart + var/datum/action/cooldown/manual_heart/pump_action + /// Cooldown before harm is caused to the owner + COOLDOWN_DECLARE(heart_timer) + /// If true, add a screen tint on the next process + var/add_colour = TRUE + /// How long between needed pumps; you can pump one second early + var/pump_delay = 3 SECONDS + /// How much blood volume you lose every missed pump, this is a flat amount not a percentage! + var/blood_loss = BLOOD_VOLUME_NORMAL * 0.2 // 20% of normal volume, missing five pumps is instant death + + //How much to heal per pump - negative numbers harm the owner instead + /// The amount of brute damage to heal per pump + var/heal_brute = 0 + /// The amount of burn damage to heal per pump + var/heal_burn = 0 + /// The amount of oxygen damage to heal per pump + var/heal_oxy = 0 + +/datum/component/manual_heart/Initialize(pump_delay = 3 SECONDS, blood_loss = BLOOD_VOLUME_NORMAL * 0.2, heal_brute = 0, heal_burn = 0, heal_oxy = 0) + //Non-Carbon mobs can't have hearts, and should never receive this component. + if (!iscarbon(parent)) + stack_trace("Manual Heart component added to [parent] ([parent?.type]) which is not a /mob/living/carbon subtype.") + return COMPONENT_INCOMPATIBLE + + src.pump_delay = pump_delay + src.blood_loss = blood_loss + src.heal_brute = heal_brute + src.heal_burn = heal_burn + src.heal_oxy = heal_oxy + + pump_action = new(src) + +/datum/component/manual_heart/Destroy() + QDEL_NULL(pump_action) + return ..() + +/datum/component/manual_heart/RegisterWithParent() + RegisterSignal(parent, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(check_removed_organ)) + RegisterSignal(parent, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(check_added_organ)) + RegisterSignal(parent, COMSIG_HEART_MANUAL_PULSE, PROC_REF(on_pump)) + RegisterSignals(parent, list(COMSIG_LIVING_DEATH, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD)), PROC_REF(pause)) + RegisterSignals(parent, list(COMSIG_LIVING_REVIVE, SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD)), PROC_REF(restart)) + + pump_action.cooldown_time = pump_delay - (1 SECONDS) //you can pump up to a second early + pump_action.Grant(parent) + + var/mob/living/carbon/carbon_parent = parent + var/obj/item/organ/internal/heart/parent_heart = carbon_parent.get_organ_slot(ORGAN_SLOT_HEART) + if(parent_heart && !HAS_TRAIT(carbon_parent, TRAIT_NOBLOOD) && carbon_parent.stat != DEAD) + START_PROCESSING(SSdcs, src) + COOLDOWN_START(src, heart_timer, pump_delay) + + to_chat(parent, span_userdanger("Your heart no longer beats automatically! You have to pump it manually - otherwise you'll die!")) + +/datum/component/manual_heart/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_CARBON_GAIN_ORGAN, COMSIG_CARBON_LOSE_ORGAN, COMSIG_HEART_MANUAL_PULSE, COMSIG_LIVING_REVIVE, COMSIG_LIVING_DEATH, SIGNAL_ADDTRAIT(TRAIT_NOBLOOD), SIGNAL_REMOVETRAIT(TRAIT_NOBLOOD))) + + to_chat(parent, span_userdanger("You feel your heart start beating normally again!")) + var/mob/living/carbon/carbon_parent = parent + if(istype(carbon_parent)) + carbon_parent.remove_client_colour(/datum/client_colour/manual_heart_blood) + +/datum/component/manual_heart/proc/restart() + SIGNAL_HANDLER + + if(!check_valid()) + return + COOLDOWN_START(src, heart_timer, pump_delay) + pump_action.build_all_button_icons(UPDATE_BUTTON_STATUS) //make sure the action button always shows as available when it is + START_PROCESSING(SSdcs, src) + +/datum/component/manual_heart/proc/pause() + SIGNAL_HANDLER + pump_action.build_all_button_icons(UPDATE_BUTTON_STATUS) + var/mob/living/carbon/carbon_parent = parent + if(istype(carbon_parent)) + carbon_parent.remove_client_colour(/datum/client_colour/manual_heart_blood) //prevents red overlay from getting stuck + STOP_PROCESSING(SSdcs, src) + +/// Worker proc that checks logic for if a pump can happen, and applies effects from doing so +/datum/component/manual_heart/proc/on_pump(mob/owner) + COOLDOWN_START(src, heart_timer, pump_delay) + playsound(owner,'sound/effects/singlebeat.ogg', 40, TRUE) + + var/mob/living/carbon/carbon_owner = owner + + if(HAS_TRAIT(carbon_owner, TRAIT_NOBLOOD)) + return + carbon_owner.blood_volume = min(carbon_owner.blood_volume + (blood_loss * 0.5), BLOOD_VOLUME_MAXIMUM) + carbon_owner.remove_client_colour(/datum/client_colour/manual_heart_blood) + add_colour = TRUE + carbon_owner.adjustBruteLoss(-heal_brute) + carbon_owner.adjustFireLoss(-heal_burn) + carbon_owner.adjustOxyLoss(-heal_oxy) + +/datum/component/manual_heart/process() + var/mob/living/carbon/carbon_parent = parent + + //If they aren't connected, don't kill them. + if(!carbon_parent.client) + COOLDOWN_START(src, heart_timer, pump_delay) + return + + if(!COOLDOWN_FINISHED(src, heart_timer)) + return + + carbon_parent.blood_volume = max(carbon_parent.blood_volume - blood_loss, 0) + to_chat(carbon_parent, span_userdanger("You have to keep pumping your blood!")) + COOLDOWN_START(src, heart_timer, MANUAL_HEART_GRACE_PERIOD) //give two full seconds before losing more blood + if(add_colour) + carbon_parent.add_client_colour(/datum/client_colour/manual_heart_blood) + add_colour = FALSE + +///If a new heart is added, start processing. +/datum/component/manual_heart/proc/check_added_organ(mob/organ_owner, obj/item/organ/new_organ) + SIGNAL_HANDLER + + var/obj/item/organ/internal/heart/new_heart = new_organ + + if(!istype(new_heart) || !check_valid()) + return + COOLDOWN_START(src, heart_timer, pump_delay) + pump_action.build_all_button_icons(UPDATE_BUTTON_STATUS) + var/mob/living/carbon/carbon_parent = parent + if(istype(carbon_parent)) + carbon_parent.remove_client_colour(/datum/client_colour/manual_heart_blood) //prevents red overlay from getting stuck + START_PROCESSING(SSdcs, src) + +///If the heart is removed, stop processing. +/datum/component/manual_heart/proc/check_removed_organ(mob/organ_owner, obj/item/organ/removed_organ) + SIGNAL_HANDLER + + var/obj/item/organ/internal/heart/removed_heart = removed_organ + + if(istype(removed_heart)) + pump_action.build_all_button_icons(UPDATE_BUTTON_STATUS) + STOP_PROCESSING(SSdcs, src) + +///Helper proc to check if processing can be restarted. +/datum/component/manual_heart/proc/check_valid() + var/mob/living/carbon/carbon_parent = parent + var/obj/item/organ/internal/heart/parent_heart = carbon_parent.get_organ_slot(ORGAN_SLOT_HEART) + return !isnull(parent_heart) && !HAS_TRAIT(carbon_parent, TRAIT_NOBLOOD) && carbon_parent.stat != DEAD + +///Action to pump your heart. Cooldown will always be set to 1 second less than the pump delay. +/datum/action/cooldown/manual_heart + name = "Pump your blood" + cooldown_time = 2 SECONDS + check_flags = NONE + button_icon = 'icons/obj/medical/organs/organs.dmi' + button_icon_state = "cursedheart-off" + +/datum/action/cooldown/manual_heart/Activate(atom/atom_target) + . = ..() + + SEND_SIGNAL(owner, COMSIG_HEART_MANUAL_PULSE) + +///The action button is only available when you're a living carbon with blood and a heart. +/datum/action/cooldown/manual_heart/IsAvailable(feedback = FALSE) + var/mob/living/carbon/heart_haver = owner + if(!istype(heart_haver) || HAS_TRAIT(heart_haver, TRAIT_NOBLOOD) || heart_haver.stat == DEAD) + return FALSE + var/obj/item/organ/internal/heart/heart_havers_heart = heart_haver.get_organ_slot(ORGAN_SLOT_HEART) + if(isnull(heart_havers_heart)) + return FALSE + return ..() + +#undef MANUAL_HEART_GRACE_PERIOD diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm index 3e4cf0766f5..4783daffb8b 100644 --- a/code/modules/antagonists/wizard/equipment/artefact.dm +++ b/code/modules/antagonists/wizard/equipment/artefact.dm @@ -311,7 +311,7 @@ //Provides a decent heal, need to pump every 6 seconds /obj/item/organ/internal/heart/cursed/wizard - pump_delay = 60 + pump_delay = 6 SECONDS heal_brute = 25 heal_burn = 25 heal_oxy = 25 diff --git a/code/modules/client/client_colour.dm b/code/modules/client/client_colour.dm index 88573c78e23..9ac64851a68 100644 --- a/code/modules/client/client_colour.dm +++ b/code/modules/client/client_colour.dm @@ -224,6 +224,10 @@ override = TRUE colour = list(0.8,0,0,0, 0,0,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0) +/datum/client_colour/manual_heart_blood + priority = PRIORITY_ABSOLUTE + colour = COLOR_RED + #undef PRIORITY_ABSOLUTE #undef PRIORITY_HIGH #undef PRIORITY_NORMAL diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm index 85cdc4c3922..e2b703a9c13 100644 --- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm @@ -686,7 +686,7 @@ Basically, we fill the time between now and 2s from now with hands based off the affected_mob.cure_trauma_type(temp_trauma, resilience = TRAUMA_RESILIENCE_MAGIC) /datum/reagent/inverse/corazargh - name = "Corazargh" //It's what you yell! Though, if you've a better name feel free. Also an omage to an older chem + name = "Corazargh" //It's what you yell! Though, if you've a better name feel free. Also an homage to an older chem description = "Interferes with the body's natural pacemaker, forcing the patient to manually beat their heart." color = "#5F5F5F" self_consuming = TRUE @@ -695,83 +695,23 @@ Basically, we fill the time between now and 2s from now with hands based off the metabolization_rate = REM chemical_flags = REAGENT_DEAD_PROCESS tox_damage = 0 - ///Weakref to the old heart we're swapping for - var/datum/weakref/original_heart_ref - ///Weakref to the new heart that's temp added - var/datum/weakref/manual_heart_ref -///Creates a new cursed heart and puts the old inside of it, then replaces the position of the old +///Give the victim the manual heart beating component. /datum/reagent/inverse/corazargh/on_mob_metabolize(mob/living/affected_mob) . = ..() if(!iscarbon(affected_mob)) return var/mob/living/carbon/carbon_mob = affected_mob - var/obj/item/organ/internal/heart/original_heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART) - if(!original_heart) + var/obj/item/organ/internal/heart/affected_heart = carbon_mob.get_organ_slot(ORGAN_SLOT_HEART) + if(isnull(affected_heart)) return - original_heart_ref = WEAKREF(original_heart) + carbon_mob.AddComponent(/datum/component/manual_heart) + return ..() - var/obj/item/organ/internal/heart/cursed/manual_heart = new(null, src) - manual_heart_ref = WEAKREF(manual_heart) - original_heart.Remove(carbon_mob, special = TRUE) //So we don't suddenly die - original_heart.forceMove(manual_heart) - original_heart.organ_flags |= ORGAN_FROZEN //Not actually frozen, but we want to pause decay - manual_heart.Insert(carbon_mob, special = TRUE) - //these last so instert doesn't call them - RegisterSignal(carbon_mob, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_gained_organ)) - RegisterSignal(carbon_mob, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_removed_organ)) - to_chat(affected_mob, span_userdanger("You feel your heart suddenly stop beating on it's own - you'll have to manually beat it!")) - -///Intercepts the new heart and creates a new cursed heart - putting the old inside of it -/datum/reagent/inverse/corazargh/proc/on_gained_organ(mob/affected_mob, obj/item/organ/organ) - SIGNAL_HANDLER - if(!istype(organ, /obj/item/organ/internal/heart)) - return - // DO NOT REACT TO YOUR OWN HEART ADDITION I SWEAR TO CHRIST - var/obj/item/organ/internal/heart/cursed/manual_heart = manual_heart_ref?.resolve() - if(organ == manual_heart) - return - - var/mob/living/carbon/affected_carbon = affected_mob - var/obj/item/organ/internal/heart/original_heart = organ - original_heart_ref = WEAKREF(original_heart) - original_heart.Remove(affected_carbon, special = TRUE) - if(!manual_heart) - manual_heart = new(null, src) - manual_heart_ref = WEAKREF(manual_heart) - original_heart.forceMove(manual_heart) - original_heart.organ_flags |= ORGAN_FROZEN //Not actually frozen, but we want to pause decay - manual_heart.Insert(affected_carbon, special = TRUE) - -///If we're ejecting out the organ - replace it with the original -/datum/reagent/inverse/corazargh/proc/on_removed_organ(mob/prev_owner, obj/item/organ/organ) - SIGNAL_HANDLER - var/obj/item/organ/internal/heart/cursed/manual_heart = manual_heart_ref?.resolve() - if(organ != manual_heart) - return - var/obj/item/organ/internal/heart/original_heart = original_heart_ref?.resolve() - if(!original_heart) - return - - original_heart.forceMove(manual_heart.loc) - original_heart.organ_flags &= ~ORGAN_FROZEN //enable decay again - QDEL_NULL(manual_heart_ref) - -///We're done - remove the curse and restore the old one +///We're done - remove the curse /datum/reagent/inverse/corazargh/on_mob_end_metabolize(mob/living/affected_mob) - . = ..() - //Do these first so Insert doesn't call them - UnregisterSignal(affected_mob, COMSIG_CARBON_LOSE_ORGAN) - UnregisterSignal(affected_mob, COMSIG_CARBON_GAIN_ORGAN) - if(!iscarbon(affected_mob)) - return - var/mob/living/carbon/affected_carbon = affected_mob - var/obj/item/organ/internal/heart/original_heart = original_heart_ref?.resolve() - if(original_heart) //Mostly a just in case - original_heart.organ_flags &= ~ORGAN_FROZEN //enable decay again - original_heart.Insert(affected_carbon, special = TRUE) - QDEL_NULL(manual_heart_ref) - to_chat(affected_mob, span_userdanger("You feel your heart start beating normally again!")) + qdel(affected_mob.GetComponent(/datum/component/manual_heart)) + ..() /datum/reagent/inverse/antihol name = "Prohol" diff --git a/code/modules/surgery/organs/internal/heart/_heart.dm b/code/modules/surgery/organs/internal/heart/_heart.dm index 307d3e478a5..fb97ca7eda0 100644 --- a/code/modules/surgery/organs/internal/heart/_heart.dm +++ b/code/modules/surgery/organs/internal/heart/_heart.dm @@ -104,20 +104,12 @@ icon_state = "cursedheart-off" base_icon_state = "cursedheart" decay_factor = 0 - actions_types = list(/datum/action/item_action/organ_action/cursed_heart) - var/last_pump = 0 - var/add_colour = TRUE //So we're not constantly recreating colour datums - /// How long between needed pumps; you can pump one second early var/pump_delay = 3 SECONDS - /// How much blood volume you lose every missed pump, this is a flat amount not a percentage! - var/blood_loss = (BLOOD_VOLUME_NORMAL / 5) // 20% of normal volume, missing five pumps is instant death - - //How much to heal per pump, negative numbers would HURT the player + var/blood_loss = BLOOD_VOLUME_NORMAL * 0.2 var/heal_brute = 0 var/heal_burn = 0 var/heal_oxy = 0 - /obj/item/organ/internal/heart/cursed/attack(mob/living/carbon/human/accursed, mob/living/carbon/human/user, obj/target) if(accursed == user && istype(accursed)) playsound(user,'sound/effects/singlebeat.ogg',40,TRUE) @@ -126,75 +118,13 @@ else return ..() -/// Worker proc that checks logic for if a pump can happen, and applies effects/notifications from doing so -/obj/item/organ/internal/heart/cursed/proc/on_pump(mob/owner) - var/next_pump = last_pump + pump_delay - (1 SECONDS) // pump a second early - if(world.time < next_pump) - to_chat(owner, span_userdanger("Too soon!")) - return - - last_pump = world.time - playsound(owner,'sound/effects/singlebeat.ogg', 40, TRUE) - to_chat(owner, span_notice("Your heart beats.")) - - if(!ishuman(owner)) - return - var/mob/living/carbon/human/accursed = owner - - if(HAS_TRAIT(accursed, TRAIT_NOBLOOD) || !accursed.dna) - return - accursed.blood_volume = min(accursed.blood_volume + (blood_loss * 0.5), BLOOD_VOLUME_MAXIMUM) - accursed.remove_client_colour(/datum/client_colour/cursed_heart_blood) - add_colour = TRUE - accursed.adjustBruteLoss(-heal_brute) - accursed.adjustFireLoss(-heal_burn) - accursed.adjustOxyLoss(-heal_oxy) - -/obj/item/organ/internal/heart/cursed/on_life(seconds_per_tick, times_fired) - if(!owner.client || !ishuman(owner)) // Let's be fair, if you're not here to pump, you're not here to suffer. - last_pump = world.time - return - - if(world.time <= (last_pump + pump_delay)) - return - - var/mob/living/carbon/human/accursed = owner - if(HAS_TRAIT(accursed, TRAIT_NOBLOOD) || !accursed.dna) - return - - accursed.blood_volume = max(accursed.blood_volume - blood_loss, 0) - to_chat(accursed, span_userdanger("You have to keep pumping your blood!")) - if(add_colour) - accursed.add_client_colour(/datum/client_colour/cursed_heart_blood) //bloody screen so real - add_colour = FALSE - /obj/item/organ/internal/heart/cursed/on_insert(mob/living/carbon/accursed) . = ..() - last_pump = world.time // give them time to react - to_chat(accursed, span_userdanger("Your heart has been replaced with a cursed one, you have to pump this one manually otherwise you'll die!")) + accursed.AddComponent(/datum/component/manual_heart, pump_delay = pump_delay, blood_loss = blood_loss, heal_brute = heal_brute, heal_burn = heal_burn, heal_oxy = heal_oxy) /obj/item/organ/internal/heart/cursed/Remove(mob/living/carbon/accursed, special = FALSE) . = ..() - accursed.remove_client_colour(/datum/client_colour/cursed_heart_blood) - -/datum/action/item_action/organ_action/cursed_heart - name = "Pump your blood" - check_flags = NONE - -//You are now brea- pumping blood manually -/datum/action/item_action/organ_action/cursed_heart/Trigger(trigger_flags) - . = ..() - if(!.) - return - - var/obj/item/organ/internal/heart/cursed/cursed_heart = target - if(!istype(cursed_heart)) - CRASH("Cursed heart pump action created on non-cursed heart!") - cursed_heart.on_pump(owner) - -/datum/client_colour/cursed_heart_blood - priority = 100 //it's an indicator you're dying, so it's very high priority - colour = "#FF0000" + qdel(accursed.GetComponent(/datum/component/manual_heart)) /obj/item/organ/internal/heart/cybernetic name = "basic cybernetic heart" diff --git a/tgstation.dme b/tgstation.dme index e3a512be234..e6f65e3dd38 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1023,6 +1023,7 @@ #include "code\datums\components\magnet.dm" #include "code\datums\components\manual_blinking.dm" #include "code\datums\components\manual_breathing.dm" +#include "code\datums\components\manual_heart.dm" #include "code\datums\components\mind_linker.dm" #include "code\datums\components\mirv.dm" #include "code\datums\components\mob_harvest.dm"