From 91d05ae483926b9a98ca0a274d463e4dc6ebe0b2 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:38:46 -0600 Subject: [PATCH 1/3] Compact defibs fit on labcoats and similar medical gear (#368) --- code/game/objects/items/defib.dm | 20 ++++++++--------- .../code/modules/clothing/suits/labcoat.dm | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index 2649a9329380..c1904a6a8553 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -120,18 +120,13 @@ //ATTACK HAND IGNORING PARENT RETURN VALUE /obj/item/defibrillator/attack_hand(mob/user, list/modifiers) if(loc == user) - if(slot_flags & ITEM_SLOT_BACK) - if(user.get_item_by_slot(ITEM_SLOT_BACK) == src) - ui_action_click() - else - to_chat(user, span_warning("Put the defibrillator on your back first!")) - - else if(slot_flags & ITEM_SLOT_BELT) - if(user.get_item_by_slot(ITEM_SLOT_BELT) == src) - ui_action_click() - else - to_chat(user, span_warning("Strap the defibrillator's belt on first!")) + // NON-MODULE CHANGE START + if(user.get_slot_by_item(src) & slot_flags) + ui_action_click() + else + balloon_alert(user, "equip the unit first!") return + // NON-MODULE CHANGE END else if(istype(loc, /obj/machinery/defibrillator_mount)) ui_action_click() //checks for this are handled in defibrillator.mount.dm return ..() @@ -283,9 +278,12 @@ nocell_state = "defibcompact-nocell" emagged_state = "defibcompact-emagged" +/* +// NON-MODULE CHANGE /obj/item/defibrillator/compact/item_action_slot_check(slot, mob/user) if(slot & user.getBeltSlot()) return TRUE +*/ /obj/item/defibrillator/compact/loaded/Initialize(mapload) . = ..() diff --git a/maplestation_modules/code/modules/clothing/suits/labcoat.dm b/maplestation_modules/code/modules/clothing/suits/labcoat.dm index 75bc75f5ed54..ea0fd4af179a 100644 --- a/maplestation_modules/code/modules/clothing/suits/labcoat.dm +++ b/maplestation_modules/code/modules/clothing/suits/labcoat.dm @@ -42,6 +42,28 @@ /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/bag/bio, + /obj/item/defibrillator/compact, ) armor_type = /datum/armor/science_rd species_exception = list(/datum/species/golem) + +// Adding combat defib to allowed list of non-modular labcouts and co. +/obj/item/defibrillator/compact + slot_flags = ITEM_SLOT_BELT|ITEM_SLOT_SUITSTORE|ITEM_SLOT_DEX_STORAGE + worn_icon = 'icons/mob/clothing/belt.dmi' + +/obj/item/clothing/suit/toggle/labcoat/Initialize(mapload) + . = ..() + allowed += /obj/item/defibrillator/compact + +/obj/item/clothing/suit/hooded/wintercoat/medical/Initialize(mapload) + . = ..() + allowed += /obj/item/defibrillator/compact + +/obj/item/clothing/suit/apron/surgical/Initialize(mapload) + . = ..() + allowed += /obj/item/defibrillator/compact + +/datum/mod_theme/medical/New() // Maybe not necessary because the defib MOD exists + . = ..() + allowed_suit_storage += /obj/item/defibrillator/compact From 6d3fe96c9e289c61f27f73afd15066c5146785b7 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:47:20 -0600 Subject: [PATCH 2/3] Re-adds vampire bat form, disallows vampires from entering other departments without permission (#369) --- maplestation.dme | 2 + .../code/__DEFINES/signals.dm | 8 + .../code/game/area/space_station_13_areas.dm | 143 +++++++++++ .../code/modules/events/solar_flare.dm | 45 +--- .../code/modules/jobs/job_types/assistant.dm | 2 + .../code/modules/jobs/job_types/captain.dm | 2 + .../carbon/human/species_types/vampire.dm | 232 ++++++++++++++++++ 7 files changed, 396 insertions(+), 38 deletions(-) create mode 100644 maplestation_modules/code/modules/jobs/job_types/captain.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/species_types/vampire.dm diff --git a/maplestation.dme b/maplestation.dme index c98ea5f2821e..a349d1ad3ffc 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -5558,6 +5558,7 @@ #include "maplestation_modules\code\modules\jobs\job_types\asset_protection.dm" #include "maplestation_modules\code\modules\jobs\job_types\assistant.dm" #include "maplestation_modules\code\modules\jobs\job_types\bridge_officer.dm" +#include "maplestation_modules\code\modules\jobs\job_types\captain.dm" #include "maplestation_modules\code\modules\jobs\job_types\lawyer.dm" #include "maplestation_modules\code\modules\jobs\job_types\ordnance_tech.dm" #include "maplestation_modules\code\modules\jobs\job_types\psychologist.dm" @@ -5653,6 +5654,7 @@ #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\skrell.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\species.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\synths.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\species_types\vampire.dm" #include "maplestation_modules\code\modules\mob\living\silicon\robot\robot_defines.dm" #include "maplestation_modules\code\modules\mob\living\simple_animal\corpse.dm" #include "maplestation_modules\code\modules\mod\mod_control.dm" diff --git a/maplestation_modules/code/__DEFINES/signals.dm b/maplestation_modules/code/__DEFINES/signals.dm index c2aa40b7844e..cd988f205f15 100644 --- a/maplestation_modules/code/__DEFINES/signals.dm +++ b/maplestation_modules/code/__DEFINES/signals.dm @@ -5,3 +5,11 @@ /// Item generating their worn icon #define COMSIG_ITEM_WORN_ICON_MADE "item_worn_icon_made" + +/// Entering or exiting a vent. +#define COMSIG_HANDLE_VENTCRAWLING "handle_ventcrawl" + /// Return to block entrance / exit + #define COMPONENT_NO_VENT (1<<0) + +/// A carbon is being flashed - actually being blinded and taking (eye) damage +#define COMSIG_CARBON_FLASH_ACT "carbon_flash_act" diff --git a/maplestation_modules/code/game/area/space_station_13_areas.dm b/maplestation_modules/code/game/area/space_station_13_areas.dm index 9a5a19e61449..0eccb045ce10 100644 --- a/maplestation_modules/code/game/area/space_station_13_areas.dm +++ b/maplestation_modules/code/game/area/space_station_13_areas.dm @@ -11,6 +11,8 @@ name = "Bridge Officer's Office" icon = 'maplestation_modules/icons/turf/areas.dmi' icon_state = "bo_office" + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SECURITY + associated_department = DEPARTMENT_COMMAND //AP Office, possibly going unused? We're adding it anyway, fuck you /area/station/command/ap_office @@ -20,6 +22,8 @@ /area/station/service/hydroponics/park name = "Park" + associated_department_flags = NONE + associated_department = null /area/station/service/bar/lower name = "Lower Bar" @@ -29,11 +33,15 @@ name = "\improper Abandoned Robotics" icon_state = "abandoned_sci" sound_environment = SOUND_AREA_SMALL_ENCLOSED + associated_department_flags = NONE + associated_department = null /area/station/service/kitchen/abandoned name = "\improper Abandoned Kitchen" icon_state = "kitchen" sound_environment = SOUND_AREA_SMALL_ENCLOSED + associated_department_flags = NONE + associated_department = null /area/station/maintenance/starboard/lower name = "Lower Starboard Maintenance" @@ -78,3 +86,138 @@ name = "\improper Baseball Locker Room" icon = 'maplestation_modules/icons/turf/areas.dmi' icon_state = "baseball_locker" + +/area/station + /// All department flags that are associated with this department + var/associated_department_flags = NONE + /// The PRIMARY department this area may be located in + var/associated_department + +/area/station/security + associated_department_flags = DEPARTMENT_BITFLAG_SECURITY + associated_department = DEPARTMENT_SECURITY + +/area/station/security/checkpoint/medical + associated_department_flags = DEPARTMENT_BITFLAG_SECURITY|DEPARTMENT_BITFLAG_MEDICAL + associated_department = DEPARTMENT_MEDICAL + +/area/station/security/checkpoint/medical/medsci + associated_department_flags = DEPARTMENT_BITFLAG_SECURITY|DEPARTMENT_BITFLAG_MEDICAL|DEPARTMENT_BITFLAG_SCIENCE + +/area/station/security/checkpoint/science + associated_department_flags = DEPARTMENT_BITFLAG_SECURITY|DEPARTMENT_BITFLAG_SCIENCE + associated_department = DEPARTMENT_SCIENCE + +/area/station/security/checkpoint/engineering + associated_department_flags = DEPARTMENT_BITFLAG_SECURITY|DEPARTMENT_BITFLAG_ENGINEERING + associated_department = DEPARTMENT_ENGINEERING + +/area/station/security/checkpoint/supply + associated_department_flags = DEPARTMENT_BITFLAG_SECURITY|DEPARTMENT_BITFLAG_COMMAND + associated_department = DEPARTMENT_COMMAND + +/area/station/medical + associated_department_flags = DEPARTMENT_BITFLAG_MEDICAL + associated_department = DEPARTMENT_MEDICAL + +/area/station/medical/abandoned + associated_department_flags = NONE + associated_department = null + +/area/station/science + associated_department_flags = DEPARTMENT_BITFLAG_SCIENCE + associated_department = DEPARTMENT_SCIENCE + +/area/station/science/research/abandoned + associated_department_flags = NONE + associated_department = null + +/area/station/service + associated_department_flags = DEPARTMENT_BITFLAG_SERVICE + associated_department = DEPARTMENT_SERVICE + +/area/station/service/electronic_marketing_den + associated_department_flags = NONE + associated_department = null + +/area/station/service/abandoned_gambling_den + associated_department_flags = NONE + associated_department = null + +/area/station/service/abandoned_gambling_den/gaming + associated_department_flags = NONE + associated_department = null + +/area/station/service/theater/abandoned + associated_department_flags = NONE + associated_department = null + +/area/station/service/library/abandoned + associated_department_flags = NONE + associated_department = null + +/area/station/service/hydroponics/garden/abandoned + associated_department_flags = NONE + associated_department = null + +/area/station/engineering + associated_department_flags = DEPARTMENT_BITFLAG_ENGINEERING + associated_department = DEPARTMENT_ENGINEERING + +/area/station/supply + associated_department_flags = DEPARTMENT_BITFLAG_CARGO + associated_department = DEPARTMENT_CARGO + +/area/station/command + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND + associated_department = DEPARTMENT_COMMAND + +/area/station/command/heads_quarters/captain + associated_department_flags = DEPARTMENT_BITFLAG_CAPTAIN + +/area/station/command/heads_quarters/cmo + associated_department = DEPARTMENT_MEDICAL + +/area/station/command/heads_quarters/ce + associated_department = DEPARTMENT_ENGINEERING + +/area/station/command/heads_quarters/rd + associated_department = DEPARTMENT_SCIENCE + +/area/station/command/heads_quarters/hos + associated_department = DEPARTMENT_SECURITY + +/area/station/ai_monitored + associated_department_flags = DEPARTMENT_BITFLAG_SILICON + +/area/station/ai_monitored/command + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SILICON + associated_department = DEPARTMENT_COMMAND + +/area/station/ai_monitored/security + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SECURITY + associated_department = DEPARTMENT_BITFLAG_SECURITY + +/area/station/ai_monitored/aisat + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SILICON + associated_department = DEPARTMENT_SILICON + +/area/station/ai_monitored/turret_protected/ai + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SILICON + associated_department = DEPARTMENT_SILICON + +/area/station/ai_monitored/turret_protected/aisat + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SILICON + associated_department = DEPARTMENT_SILICON + +/area/station/ai_monitored/turret_protected/aisat_interior + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SILICON + associated_department = DEPARTMENT_SILICON + +/area/station/ai_monitored/turret_protected/ai_upload + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SILICON + associated_department = DEPARTMENT_SILICON + +/area/station/ai_monitored/turret_protected/ai_upload_foyer + associated_department_flags = DEPARTMENT_BITFLAG_COMMAND|DEPARTMENT_BITFLAG_SILICON + associated_department = DEPARTMENT_SILICON diff --git a/maplestation_modules/code/modules/events/solar_flare.dm b/maplestation_modules/code/modules/events/solar_flare.dm index 5092eb5b4cbc..a35a9908ffed 100644 --- a/maplestation_modules/code/modules/events/solar_flare.dm +++ b/maplestation_modules/code/modules/events/solar_flare.dm @@ -99,45 +99,14 @@ /** * Get all areas associated with a department. */ -/datum/round_event/solar_flare/proc/get_areas(department, area_path) +/datum/round_event/solar_flare/proc/get_areas(department, area/station/area_path) RETURN_TYPE(/list) - . = subtypesof(area_path) - - // There's much more OOP ways to do this, but whatever - switch(department) - if(DEPARTMENT_SECURITY) - . -= typesof(/area/station/security/checkpoint) - . -= /area/station/security/detectives_office/bridge_officer_office - - if(DEPARTMENT_COMMAND) - . -= /area/station/command/gateway - . += /area/station/security/detectives_office/bridge_officer_office - - if(DEPARTMENT_SERVICE) - . -= /area/station/service/electronic_marketing_den - . -= /area/station/service/abandoned_gambling_den - . -= /area/station/service/abandoned_gambling_den/gaming - . -= /area/station/service/theater/abandoned - . -= /area/station/service/library/abandoned - . -= /area/station/service/hydroponics/garden/abandoned - - if(DEPARTMENT_CARGO) - . += /area/station/security/checkpoint/supply - - if(DEPARTMENT_ENGINEERING) - . -= /area/station/engineering/supermatter - . -= /area/station/engineering/supermatter/room - . -= /area/station/engineering/gravity_generator - . += /area/station/security/checkpoint/engineering - - if(DEPARTMENT_SCIENCE) - . -= /area/station/science/research/abandoned - . += /area/station/security/checkpoint/science - . += /area/station/security/checkpoint/science/research - - if(DEPARTMENT_MEDICAL) - . -= /area/station/medical/abandoned - . += /area/station/security/checkpoint/medical + var/list/area_pool = list() + for(var/area/station/area_type as anything in subtypesof(area_path)) + if(initial(area_type.associated_department) != department) + continue + area_pool += area_type + return area_pool // Solar flare. Causes a diamond of fire centered on the initial turf. /obj/effect/solar_flare diff --git a/maplestation_modules/code/modules/jobs/job_types/assistant.dm b/maplestation_modules/code/modules/jobs/job_types/assistant.dm index 3d94b672520b..b335a0aa095d 100644 --- a/maplestation_modules/code/modules/jobs/job_types/assistant.dm +++ b/maplestation_modules/code/modules/jobs/job_types/assistant.dm @@ -1,4 +1,6 @@ // -- Assistant Changes -- +/datum/job/assistant + departments_bitflags = DEPARTMENT_BITFLAG_ASSISTANT // This is done for loadouts, otherwise unique uniforms would be deleted. /datum/outfit/job/assistant diff --git a/maplestation_modules/code/modules/jobs/job_types/captain.dm b/maplestation_modules/code/modules/jobs/job_types/captain.dm new file mode 100644 index 000000000000..2e2382cf9615 --- /dev/null +++ b/maplestation_modules/code/modules/jobs/job_types/captain.dm @@ -0,0 +1,2 @@ +/datum/job/captain + departments_bitflags = DEPARTMENT_BITFLAG_CAPTAIN diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/species_types/vampire.dm b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/vampire.dm new file mode 100644 index 000000000000..38ee4ca015ed --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/species_types/vampire.dm @@ -0,0 +1,232 @@ +// Vampire Business +/datum/species/vampire + mutanteyes = /obj/item/organ/internal/eyes/night_vision/vampire + +// Heart gives you Bat Form, but with a downside +/obj/item/organ/internal/heart/vampire + actions_types = list(/datum/action/cooldown/spell/shapeshift/vampire) + +/obj/item/organ/internal/heart/vampire/on_insert(mob/living/carbon/organ_owner) + . = ..() + organ_owner.AddComponent(/datum/component/verbal_confirmation) + +/obj/item/organ/internal/heart/vampire/on_remove(mob/living/carbon/organ_owner) + . = ..() + qdel(organ_owner.GetComponent(/datum/component/verbal_confirmation)) + +// Vampire eyes, make you vulnerable to the light, but also has nightvision +/obj/item/organ/internal/eyes/night_vision/vampire + name = "blood red eyes" + desc = "A pair of blood red eyes." + flash_protect = FLASH_PROTECTION_SENSITIVE + low_light_cutoff = list(20, 0, 5) + medium_light_cutoff = list(30, 5, 10) + high_light_cutoff = list(40, 10, 20) + +/obj/item/organ/internal/eyes/night_vision/vampire/on_insert(mob/living/carbon/organ_owner, special) + . = ..() + RegisterSignal(organ_owner, COMSIG_CARBON_FLASH_ACT, PROC_REF(do_damage)) + +/obj/item/organ/internal/eyes/night_vision/vampire/on_remove(mob/living/carbon/organ_owner, special) + . = ..() + UnregisterSignal(organ_owner, COMSIG_CARBON_FLASH_ACT) + +/obj/item/organ/internal/eyes/night_vision/vampire/proc/do_damage(mob/living/carbon/source, intensity) + SIGNAL_HANDLER + + var/damage = 3 * (intensity - source.get_eye_protection()) + if(damage <= 0) + return + + source.apply_damage(damage, BURN, BODY_ZONE_HEAD, wound_bonus = -10) + source.visible_message(span_userdanger("The bright flash burns your skin!")) + +// Bat Form transformation my beloved +/datum/action/cooldown/spell/shapeshift/vampire + name = "Bat Form" + desc = "Take on the shape a space bat." + invocation = "Squeak!" + cooldown_time = 5 SECONDS + possible_shapes = list(/mob/living/basic/bat) + button_icon = 'icons/mob/simple/animal.dmi' + button_icon_state = "bat" + antimagic_flags = MAGIC_RESISTANCE_HOLY + spell_requirements = SPELL_REQUIRES_NO_ANTIMAGIC + +// Override to add a signal to handle_ventcrawl +/mob/living/handle_ventcrawl(obj/machinery/atmospherics/components/ventcrawl_target) + if(SEND_SIGNAL(src, COMSIG_HANDLE_VENTCRAWLING, ventcrawl_target) & COMPONENT_NO_VENT) + return + return ..() + +// Override to add a signal to flash act +/mob/living/carbon/flash_act(intensity = 1, override_blindness_check = 0, affect_silicon = 0, visual = 0, type = /atom/movable/screen/fullscreen/flash, length = 25) + . = ..() + if(!. || visual) + return + + SEND_SIGNAL(src, COMSIG_CARBON_FLASH_ACT, intensity) + +/// Component which disallows the mob from entering areas they do not have access to, +/// without getting permission from someone who does +/datum/component/verbal_confirmation + can_transfer = TRUE + + /// Tracks if we've recently asked someone to allow us in + var/recently_asked = FALSE + /// Assoc List of all mobs which have given access (key to department bitflag) + var/list/prior_allowance = list() + /// Regexes for matching asking to be allowed in + var/static/regex/asking_regex + /// Regexes for responses indicating we're allowed in + var/static/regex/allowed_regex + /// Regexes for responses indicating we're explicitly not allowed in + var/static/regex/begone_regex + /// Weakref to the mob's ID card when shapechanging + var/datum/weakref/old_id_card + +/datum/component/verbal_confirmation/Initialize() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + if(!asking_regex) + asking_regex = regex(@"((may|come|let|can|enter)[a-zA-z\s]*)", "i") + if(!allowed_regex) + allowed_regex = regex(@"(sure|okay|ye[sea]*|yes|ok[ay]*|fine|come.+in[a-zA-z\s]*)", "i") + if(!begone_regex) + begone_regex = regex(@"(no[ope]*|begone|go away|shoo|fuck off)", "i") + +/datum/component/verbal_confirmation/RegisterWithParent() + RegisterSignals(parent, list(COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED), PROC_REF(shapechanged)) + RegisterSignal(parent, COMSIG_MOB_SAY, PROC_REF(check_asking)) + RegisterSignal(parent, COMSIG_MOVABLE_HEAR, PROC_REF(check_allowed)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(entering_room)) + RegisterSignal(parent, COMSIG_HANDLE_VENTCRAWLING, PROC_REF(entering_vent)) + +/datum/component/verbal_confirmation/UnregisterFromParent() + UnregisterSignal(parent, list( + COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED, + COMSIG_MOB_SAY, + COMSIG_MOVABLE_HEAR, + COMSIG_MOVABLE_MOVED, + COMSIG_HANDLE_VENTCRAWLING, + )) + +/datum/component/verbal_confirmation/PostTransfer() + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/verbal_confirmation/proc/shapechanged(mob/living/source, mob/living/new_mob) + SIGNAL_HANDLER + new_mob.TakeComponent(src) + old_id_card = WEAKREF(source.get_idcard()) + +/datum/component/verbal_confirmation/proc/get_bitflag_from_mob(mob/living/who) + var/obj/item/card/id/their_id = who.get_idcard() || old_id_card?.resolve() + if(!istype(their_id) || !istype(their_id.trim, /datum/id_trim/job) || get(their_id, /mob/living) != who) + return NONE + + var/datum/id_trim/job/their_trim = their_id.trim + return their_trim.job.departments_bitflags + +/datum/component/verbal_confirmation/proc/is_allowed(mob/living/vampire, area/station/checked_area) + // Non-station areas are always allowed + if(!istype(checked_area) || isnull(checked_area.associated_department)) + return TRUE + // Medbay is always free if you're hurt + if(vampire.stat != CONSCIOUS && (checked_area.associated_department_flags & DEPARTMENT_BITFLAG_MEDICAL)) + return TRUE + + var/all_allowed = get_bitflag_from_mob(vampire) + for(var/ref in prior_allowance) + all_allowed |= prior_allowance[ref] + + if(all_allowed & (DEPARTMENT_BITFLAG_CAPTAIN|checked_area.associated_department_flags)) + return TRUE + if(istype(checked_area, /area/station/service/chapel)) + return TRUE // Go ahead, try waltzing into the chapel + + return FALSE + +/datum/component/verbal_confirmation/proc/check_asking(mob/living/source, list/say_args) + SIGNAL_HANDLER + + var/spoken_message = say_args[SPEECH_MESSAGE] + if(!spoken_message || !asking_regex.Find(spoken_message)) + return + + recently_asked = TRUE + addtimer(VARSET_CALLBACK(src, recently_asked, FALSE), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + say_args[SPEECH_MESSAGE] = asking_regex.Replace(spoken_message, "[span_hierophant("$1")]") + +/datum/component/verbal_confirmation/proc/check_allowed(mob/living/source, list/hear_args) + SIGNAL_HANDLER + + if(!recently_asked && !hear_args[RADIO_EXTENSION]) + return + + var/mob/speaker = hear_args[HEARING_SPEAKER] + if(!ismob(speaker) || speaker == parent) + return + + var/spoken_message = hear_args[HEARING_RAW_MESSAGE] + if(!spoken_message) + return + + if(begone_regex.Find(spoken_message)) + hear_args[HEARING_RAW_MESSAGE] = begone_regex.Replace(spoken_message, "[span_red("$1")]") + recently_asked = FALSE + return + + if(allowed_regex.Find(spoken_message)) + var/speaker_key = REF(speaker) + prior_allowance[speaker_key] = get_bitflag_from_mob(speaker) + hear_args[HEARING_RAW_MESSAGE] = allowed_regex.Replace(spoken_message, "[span_green("$1")]") + addtimer(CALLBACK(src, PROC_REF(clear_allowed), speaker_key), 5 MINUTES, TIMER_UNIQUE|TIMER_OVERRIDE) + // Only give you a notice if you asked + if(recently_asked) + addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(to_chat), source, span_notice("[speaker] allows you into [speaker.p_their()] department. Joyous day.")), 0.2 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) + recently_asked = FALSE + return + +/datum/component/verbal_confirmation/proc/clear_allowed(speaker_key) + prior_allowance -= speaker_key + +/datum/component/verbal_confirmation/proc/entering_room(mob/living/source, turf/old_loc, movement_dir, forced) + SIGNAL_HANDLER + + if(isnull(old_loc) || forced) + return + if(source.pulledby || source.throwing || (source.buckled?.pulledby && source.buckled.pulledby != source)) + return + + var/area/station/old_area = get_area(old_loc) + var/area/station/checked_area = get_area(source) + // Comparing the same area is fine + if(old_area == checked_area) + return + // If we're going from station to station and it's the same department we can skip some checks + if(istype(old_area) && istype(checked_area) && old_area.associated_department == checked_area.associated_department) + return + if(is_allowed(source, checked_area)) + return + + to_chat(source, span_warning("You don't have permission to enter [checked_area].")) + source.Paralyze(1 SECONDS, ignore_canstun = TRUE) + source.throw_at( + target = get_edge_target_turf(source.loc, REVERSE_DIR(movement_dir)), + range = 3, + speed = 4, + gentle = TRUE, + ) + +/datum/component/verbal_confirmation/proc/entering_vent(mob/living/source, obj/machinery/atmospherics/components/ventcrawl_target) + SIGNAL_HANDLER + + var/area/checked_area = get_area(ventcrawl_target) + if(is_allowed(source, checked_area)) + return NONE + + to_chat(source, span_warning("You don't have permission to crawl through that [initial(ventcrawl_target.name)] into [checked_area].")) + source.Immobilize(1 SECONDS, ignore_canstun = TRUE) + return COMPONENT_NO_VENT From 602823b6d3a9bb72105bf1ca4dc461a2ca52241d Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:52:55 -0600 Subject: [PATCH 3/3] Reworks CPR (#367) --- code/modules/mob/living/carbon/human/life.dm | 3 +- .../mob/living/carbon/human/species.dm | 2 +- code/modules/mob/living/carbon/life.dm | 7 +- code/modules/mob/living/status_procs.dm | 2 +- code/modules/surgery/organs/heart.dm | 3 +- maplestation.dme | 4 + .../code/__DEFINES/_module_defines.dm | 5 +- .../code/datums/quirks/neutral.dm | 7 + .../closets/secure/quartermaster.dm | 1 - .../skill_learning/job_skillchips/medbay.dm | 76 +++++++++ .../skill_learning/job_skillchips/mining.dm | 8 +- .../living/carbon/human/heart_rework/cpr.dm | 161 ++++++++++++++++++ .../carbon/human/heart_rework/heart_attack.dm | 81 +++++++++ .../human/heart_rework/heart_overrides.dm | 41 +++++ 14 files changed, 390 insertions(+), 11 deletions(-) create mode 100644 maplestation_modules/code/modules/library/skill_learning/job_skillchips/medbay.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_attack.dm create mode 100644 maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_overrides.dm diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 18033f79479b..1e8a554c95bd 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -36,8 +36,7 @@ HM.on_life(seconds_per_tick, times_fired) if(stat != DEAD) - //heart attack stuff - handle_heart(seconds_per_tick, times_fired) + // handle_heart(seconds_per_tick, times_fired) // NON-MODULE CHANGE handle_liver(seconds_per_tick, times_fired) dna.species.spec_life(src, seconds_per_tick, times_fired) // for mutantraces diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 12d6fa56d181..3d39e161c3bf 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -1121,7 +1121,7 @@ GLOBAL_LIST_EMPTY(features_by_species) if(attacker_style?.help_act(user, target) == MARTIAL_ATTACK_SUCCESS) return TRUE - if(target.body_position == STANDING_UP || target.appears_alive()) + if(!target.undergoing_cardiac_arrest() && (target.body_position == STANDING_UP || target.appears_alive())) // NON-MODULE CHANGE target.help_shake_act(user) if(target != user) log_combat(user, target, "shaken") diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm index 9bf815d529f6..6cb626643de6 100644 --- a/code/modules/mob/living/carbon/life.dm +++ b/code/modules/mob/living/carbon/life.dm @@ -778,4 +778,9 @@ if(!istype(heart)) return - heart.beating = !status + // NON-MODULE CHANGE START + if(status) + heart.Stop() + else + heart.Restart() + // NON-MODULE CHANGE END diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm index c7831346cd63..98eb7b23971f 100644 --- a/code/modules/mob/living/status_procs.dm +++ b/code/modules/mob/living/status_procs.dm @@ -758,6 +758,6 @@ /// Helper to check if we seem to be alive or not /mob/living/proc/appears_alive() - return health >= 0 && !HAS_TRAIT(src, TRAIT_FAKEDEATH) + return stat != DEAD && !HAS_TRAIT(src, TRAIT_FAKEDEATH) #undef IS_STUN_IMMUNE diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 5faa98b24d08..d23957add378 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -56,8 +56,7 @@ /obj/item/organ/internal/heart/OnEatFrom(eater, feeder) . = ..() - beating = FALSE - update_appearance() + Stop() // NON-MODULE CHANGE /obj/item/organ/internal/heart/on_life(seconds_per_tick, times_fired) ..() diff --git a/maplestation.dme b/maplestation.dme index a349d1ad3ffc..9c0d7d64198d 100644 --- a/maplestation.dme +++ b/maplestation.dme @@ -5572,6 +5572,7 @@ #include "maplestation_modules\code\modules\language\japanese.dm" #include "maplestation_modules\code\modules\language\language_holder.dm" #include "maplestation_modules\code\modules\language\skrellian.dm" +#include "maplestation_modules\code\modules\library\skill_learning\job_skillchips\medbay.dm" #include "maplestation_modules\code\modules\library\skill_learning\job_skillchips\mining.dm" #include "maplestation_modules\code\modules\loadouts\loadout_items\_limb_datums.dm" #include "maplestation_modules\code\modules\loadouts\loadout_items\_loadout_categories.dm" @@ -5632,6 +5633,9 @@ #include "maplestation_modules\code\modules\mob\living\carbon\human\human.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\modular_sechud_icons.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\skrell_hair.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\cpr.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\heart_attack.dm" +#include "maplestation_modules\code\modules\mob\living\carbon\human\heart_rework\heart_overrides.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\armwings.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\avian_tails.dm" #include "maplestation_modules\code\modules\mob\living\carbon\human\ornithid_features\plumage.dm" diff --git a/maplestation_modules/code/__DEFINES/_module_defines.dm b/maplestation_modules/code/__DEFINES/_module_defines.dm index d21d3f91e6f9..c8dd1faf9724 100644 --- a/maplestation_modules/code/__DEFINES/_module_defines.dm +++ b/maplestation_modules/code/__DEFINES/_module_defines.dm @@ -27,8 +27,11 @@ #define TOOLTIP_NO_DAMAGE "This item has very low force and is largely cosmetic." #define TOOLTIP_RANDOM_COLOR "This item has a random color and will change every round." -/// Modular traits +// Modular traits +/// Provides additional resistance to contracting diseases. Should be removed next upstream #define TRAIT_DISEASE_RESISTANT "disease_resistant" +/// Does not harm patients when undergoing CPR +#define TRAIT_CPR_CERTIFIED "cpr_certified" /// Bitflags for speech sounds #define SOUND_NORMAL (1<<0) diff --git a/maplestation_modules/code/datums/quirks/neutral.dm b/maplestation_modules/code/datums/quirks/neutral.dm index 24d8ef53833b..7c14930b0e6b 100644 --- a/maplestation_modules/code/datums/quirks/neutral.dm +++ b/maplestation_modules/code/datums/quirks/neutral.dm @@ -27,3 +27,10 @@ //Will add named cardbinder, starting base 2 cards, packbox of 28 Red cards, 4 counters, and a paper with rules. Now in a handy box! give_item_to_holder(card_binder, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) give_item_to_holder(/obj/item/storage/box/tdatet_starter, list(LOCATION_BACKPACK = ITEM_SLOT_BACKPACK, LOCATION_HANDS = ITEM_SLOT_HANDS)) + +/datum/quirk/cpr_certified + name = "CPR Certified" + desc = "You are certified to perform CPR on others independent of your job." + icon = FA_ICON_HEARTBEAT + value = 0 + mob_trait = TRAIT_CPR_CERTIFIED diff --git a/maplestation_modules/code/game/objects/structures/crate_lockers/closets/secure/quartermaster.dm b/maplestation_modules/code/game/objects/structures/crate_lockers/closets/secure/quartermaster.dm index 704fc952c60a..a42c59887207 100644 --- a/maplestation_modules/code/game/objects/structures/crate_lockers/closets/secure/quartermaster.dm +++ b/maplestation_modules/code/game/objects/structures/crate_lockers/closets/secure/quartermaster.dm @@ -1,5 +1,4 @@ // -- Quartermaster locker stuff. -- /obj/structure/closet/secure_closet/quartermaster/PopulateContents() . = ..() - new /obj/item/storage/box/skillchips/cargo(src) new /obj/item/storage/bag/garment/magic/quartermaster(src) // done at the veeeery end for a reason. diff --git a/maplestation_modules/code/modules/library/skill_learning/job_skillchips/medbay.dm b/maplestation_modules/code/modules/library/skill_learning/job_skillchips/medbay.dm new file mode 100644 index 000000000000..561d5d3f83df --- /dev/null +++ b/maplestation_modules/code/modules/library/skill_learning/job_skillchips/medbay.dm @@ -0,0 +1,76 @@ +/obj/item/skillchip/entrails_reader + complexity = 0 // who cares? + +/// Teaches you how to perform CPR without harming the patient +/obj/item/skillchip/job/cpr + name = "CPR-Aid" + skill_name = "CPR Training" + skill_description = "Provides the user with the knowledge of how to safely perform CPR on a patient." + skill_icon = FA_ICON_HEARTBEAT + activate_message = span_notice("You feel more confident in your ability to perform CPR.") + deactivate_message = span_notice("You suddenly feel less confident in your ability to perform CPR.") + auto_traits = list(TRAIT_CPR_CERTIFIED) + complexity = 0 // it's pretty minor, all things considered + +/datum/outfit/job/doctor/New() + . = ..() + LAZYADD(skillchips, /obj/item/skillchip/job/cpr) + +/datum/outfit/job/paramedic/New() + . = ..() + LAZYADD(skillchips, /obj/item/skillchip/job/cpr) + +/datum/outfit/job/cmo/New() + . = ..() + LAZYADD(skillchips, /obj/item/skillchip/job/cpr) + +/obj/item/storage/box/skillchips/medbay + name = "box of medbay skillchips" + desc = "Contains spares of every medical job skillchip." + +/obj/item/storage/box/skillchips/medbay/PopulateContents() + new /obj/item/skillchip/job/cpr(src) + new /obj/item/skillchip/job/cpr(src) + new /obj/item/skillchip/job/cpr(src) + new /obj/item/skillchip/job/cpr(src) + new /obj/item/skillchip/entrails_reader(src) + +/obj/structure/closet/secure_closet/chief_medical/PopulateContents() + . = ..() + new /obj/item/storage/box/skillchips/medbay(src) + +/// Book that grants the same trait as a skillchip, since reasonably there's no need for it to be chip locked +/obj/item/book/granter/cpr + name = "CPR Training Manual" + desc = "A book that teaches you how to perform CPR properly and safely." + icon_state = "book7" + remarks = list( + "Assess the situation for danger... But it's always dangerous on the station!", + "Make sure to check for a pulse and call for help before starting...", + "Thirty compressions followed by two breaths...", + "Match compressions to the beat of 'Staying Alive'... What?", + "Proper hand placement is crucial. Wait, are lizardperson hearts in the same location as humans?", + "Tilt the head back and pinch the nose before delivering breaths.", + "Let the chest return to its normal position after each compression.", + "Follow up with an AED if available.", + ) + pages_to_mastery = 6 + reading_time = 2 SECONDS + uses = INFINITY + +/obj/item/book/granter/cpr/can_learn(mob/living/user) + return !HAS_TRAIT_FROM(user, TRAIT_CPR_CERTIFIED, name) + +/obj/item/book/granter/cpr/on_reading_start(mob/living/user) + . = ..() + if(HAS_TRAIT(user, TRAIT_CPR_CERTIFIED)) + to_chat(user, span_notice("You already know how to perform CPR, but it can't hurt to brush up.")) + +/obj/item/book/granter/cpr/on_reading_finished(mob/living/user) + . = ..() + if(HAS_TRAIT(user, TRAIT_CPR_CERTIFIED)) + to_chat(user, span_green("You remind yourself of the proper way to perform CPR safely.")) + else + to_chat(user, span_green("You feel confident in your ability to perform CPR safely.")) + + ADD_TRAIT(user, TRAIT_CPR_CERTIFIED, name) diff --git a/maplestation_modules/code/modules/library/skill_learning/job_skillchips/mining.dm b/maplestation_modules/code/modules/library/skill_learning/job_skillchips/mining.dm index 872d34fba159..9366382d5da4 100644 --- a/maplestation_modules/code/modules/library/skill_learning/job_skillchips/mining.dm +++ b/maplestation_modules/code/modules/library/skill_learning/job_skillchips/mining.dm @@ -5,8 +5,8 @@ skill_name = "Off Station Pain Resistance" skill_description = "For the adventurous in life, this skillchip provides a reduction in pain received when off the station." skill_icon = "fist-raised" - activate_message = "You feel like you can safely take on the unknown." - deactivate_message = "You feel more vulnerable to the unknown." + activate_message = span_notice("You feel like you can safely take on the unknown.") + deactivate_message = span_notice("You feel more vulnerable to the unknown.") /obj/item/skillchip/job/off_z_pain_resistance/on_activate(mob/living/carbon/user, silent = FALSE) . = ..() @@ -47,3 +47,7 @@ /obj/item/storage/box/skillchips/cargo/PopulateContents() new /obj/item/skillchip/job/off_z_pain_resistance(src) new /obj/item/skillchip/job/off_z_pain_resistance(src) + +/obj/structure/closet/secure_closet/quartermaster/PopulateContents() + . = ..() + new /obj/item/storage/box/skillchips/cargo(src) diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm new file mode 100644 index 000000000000..fd0b7e32f415 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/cpr.dm @@ -0,0 +1,161 @@ + +/mob/living/carbon/human/do_cpr(mob/living/carbon/human/target) + if(target == src) + return FALSE + + if (DOING_INTERACTION_WITH_TARGET(src, target)) + return FALSE + + cpr_process(target, beat = 1) // begin at beat 1, skip the first breath + return TRUE + +/// Number of "beats" per CPR cycle +/// This corresponds to N - 1 compressions and 1 breath +#define BEATS_PER_CPR_CYCLE 16 +// Also I'm kinda cheating here because we do 15 compressions to 1 breath rather than 30 compressions to 2 breaths +// But it's close enough to the real thing (ratio wise) that I'm OK with it + +/mob/living/carbon/human/proc/cpr_process(mob/living/carbon/human/target, beat = 0, panicking = FALSE) + set waitfor = FALSE + + if (!target.appears_alive()) + to_chat(src, span_warning("[target.name] is dead!")) + return + + if(!panicking && target.stat != CONSCIOUS && beat >= BEATS_PER_CPR_CYCLE + 1) + to_chat(src, span_warning("[target] still isn't up[HAS_TRAIT(src, TRAIT_CPR_CERTIFIED) ? " - you pick up the pace." : "! You try harder!"]")) + panicking = TRUE + + var/doafter_mod = panicking ? 0.5 : 1 + + var/doing_a_breath = FALSE + if(beat % BEATS_PER_CPR_CYCLE == 0) + if (is_mouth_covered()) + to_chat(src, span_warning("Your mouth is covered, so you can only perform compressions!")) + + else if (target.is_mouth_covered()) + to_chat(src, span_warning("[p_their(TRUE)] mouth is covered, so you can only perform compressions!")) + + else if (!get_organ_slot(ORGAN_SLOT_LUNGS)) + to_chat(src, span_warning("You have no lungs to breathe with, so you can only perform compressions!")) + + else if (HAS_TRAIT(src, TRAIT_NOBREATH)) + to_chat(src, span_warning("You do not breathe, so you can only perform compressions!")) + + else + doing_a_breath = TRUE + + if(doing_a_breath) + visible_message( + span_notice("[src] attempts to give [target.name] a rescue breath!"), + span_notice("You attempt to give [target.name] a rescue breath as a part of CPR... Hold still!"), + ) + + if(!do_after(user = src, delay = doafter_mod * 6 SECONDS, target = target)) + return + + add_mood_event("saved_life", /datum/mood_event/saved_life) + visible_message( + span_notice("[src] delivers a rescue breath on [target.name]!"), + span_notice("You deliver a rescue breath on [target.name]."), + ) + + else + var/is_first_compression = beat % BEATS_PER_CPR_CYCLE == 1 + if(is_first_compression) + visible_message( + span_notice("[src] attempts to give [target.name] chest compressions!"), + span_notice("You try to perform chest compressions as a part of CPR on [target.name]... Hold still!"), + ) + + if(!do_after(user = src, delay = doafter_mod * 1 SECONDS, target = target)) + return + + if(is_first_compression) + visible_message( + span_notice("[src] delivers chest compressions on [target.name]!"), + span_notice("You deliver chest compressions on [target.name]."), + ) + + target.apply_status_effect(/datum/status_effect/cpr_applied) + + if(doing_a_breath) + if(HAS_TRAIT(target, TRAIT_NOBREATH)) + to_chat(target, span_unconscious("You feel a breath of fresh air... which is a sensation you don't recognise...")) + else if (!target.get_organ_slot(ORGAN_SLOT_LUNGS)) + to_chat(target, span_unconscious("You feel a breath of fresh air... but you don't feel any better...")) + else if(HAS_TRAIT(src, TRAIT_CPR_CERTIFIED)) + target.adjustOxyLoss(-20) + to_chat(target, span_unconscious("You feel a breath of fresh air enter your lungs... It feels good...")) + else + target.adjustOxyLoss(-12) + to_chat(target, span_unconscious("You feel a breath of fresh air enter your lungs...")) + + // Breath relieves some of the pressure on the chest + var/obj/item/bodypart/chest/chest = target.get_bodypart(BODY_ZONE_CHEST) + if(IS_ORGANIC_LIMB(chest)) + to_chat(target, span_notice("You feel the pressure on your chest ease!")) + chest.heal_damage(brute = 3) + target.cause_pain(BODY_ZONE_CHEST, -2) + + log_combat(src, target, "CPRed", addition = "(breath)") + + else if(beat % (BEATS_PER_CPR_CYCLE / 4) == 0 && panicking) + var/obj/item/bodypart/chest/chest = target.get_bodypart(BODY_ZONE_CHEST) + if(IS_ORGANIC_LIMB(chest)) + var/critical_success = prob(1) && target.undergoing_cardiac_arrest() + if(!HAS_TRAIT(src, TRAIT_CPR_CERTIFIED)) + // Apply damage directly to chest. I would use apply damage but I can't, kinda + if(critical_success) + target.set_heartattack(FALSE) + to_chat(target, span_warning("You feel immense pressure on your chest, and a sudden wave of pain... and then relief.")) + chest.receive_damage(brute = 6, wound_bonus = CANT_WOUND, damage_source = "chest compressions") + target.cause_pain(BODY_ZONE_CHEST, 12) + + else + to_chat(target, span_warning("You feel pressure on your chest!")) + chest.receive_damage(brute = 3, wound_bonus = CANT_WOUND, damage_source = "chest compressions") + target.cause_pain(BODY_ZONE_CHEST, 2) + + to_chat(src, span_warning("You bruise [target.name]'s chest with the pressure!")) + + else if(critical_success) + target.set_heartattack(FALSE) + to_chat(target, span_warning("You pressure fade away from your chest... and then relief.")) + target.cause_pain(BODY_ZONE_CHEST, 8) + + log_combat(src, target, "CPRed", addition = "(compression)") + + if(target.body_position != LYING_DOWN) + return + if(target.stat == CONSCIOUS) + return + + cpr_process(target, beat + 1, panicking) + +#undef BEATS_PER_CPR_CYCLE + +/datum/status_effect/cpr_applied + id = "cpr" + alert_type = null + duration = 1 SECONDS + tick_interval = -1 + status_type = STATUS_EFFECT_REFRESH + +/datum/status_effect/cpr_applied/on_apply() + if(!is_effective(owner)) + return FALSE + return TRUE + +/datum/status_effect/cpr_applied/refresh(effect, ...) + if(!is_effective(owner)) + return + return ..() + +/// Checks if CPR is effective against this mob +/datum/status_effect/cpr_applied/proc/is_effective(mob/checking) + if(isnull(checking)) + return FALSE + if(!checking.get_organ_slot(ORGAN_SLOT_HEART)) // A heart is required for CPR to pump your heart + return FALSE + return TRUE diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_attack.dm b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_attack.dm new file mode 100644 index 000000000000..d4eb1a9d04e1 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_attack.dm @@ -0,0 +1,81 @@ +/datum/status_effect/heart_attack + id = "heart_attack" + alert_type = null + tick_interval = 2 SECONDS + /// TimerID for the initial knock out + VAR_FINAL/ko_timer + +/datum/status_effect/heart_attack/on_apply() + if(!iscarbon(owner)) + return FALSE + var/mob/living/carbon/carbon_owner = owner + if(isnull(carbon_owner.dna?.species?.mutantheart)) + return FALSE + + RegisterSignal(owner, COMSIG_SPECIES_GAIN, PROC_REF(species_changed)) + RegisterSignal(owner, COMSIG_CARBON_ATTEMPT_BREATHE, PROC_REF(block_breath)) + + // You get 1 tick of grace before you fall over due to your heart stopping + ko_timer = addtimer(CALLBACK(src, PROC_REF(delayed_ko)), initial(tick_interval), TIMER_STOPPABLE) + return TRUE + +/datum/status_effect/heart_attack/on_remove() + REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + deltimer(ko_timer) + + UnregisterSignal(owner, COMSIG_SPECIES_GAIN) + UnregisterSignal(owner, COMSIG_CARBON_ATTEMPT_BREATHE) + UnregisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOBREATH)) + UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOBREATH)) + + if(!QDELING(owner)) + owner.cause_pain(BODY_ZONE_CHEST, -20) + +/datum/status_effect/heart_attack/proc/delayed_ko() + if(!HAS_TRAIT(owner, TRAIT_NOBREATH)) + ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + ko_timer = null + + RegisterSignal(owner, SIGNAL_ADDTRAIT(TRAIT_NOBREATH), PROC_REF(gained_nobreath)) + RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_NOBREATH), PROC_REF(lost_nobreath)) + +/datum/status_effect/heart_attack/proc/species_changed(datum/source, datum/species/new_species, datum/species/old_species) + SIGNAL_HANDLER + if(isnull(new_species.mutantheart)) + qdel(src) + +/datum/status_effect/heart_attack/proc/gained_nobreath(datum/source) + SIGNAL_HANDLER + REMOVE_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/heart_attack/proc/lost_nobreath(datum/source) + SIGNAL_HANDLER + if(!HAS_TRAIT(owner, TRAIT_NOBREATH)) + ADD_TRAIT(owner, TRAIT_KNOCKEDOUT, TRAIT_STATUS_EFFECT(id)) + +/datum/status_effect/heart_attack/proc/block_breath(datum/source) + SIGNAL_HANDLER + + if(HAS_TRAIT(owner, TRAIT_NOBREATH)) + return NONE + + if(prob(10)) + INVOKE_ASYNC(owner, TYPE_PROC_REF(/mob, emote), "gasp") + + return COMSIG_CARBON_BLOCK_BREATH + +/datum/status_effect/heart_attack/tick(seconds_per_tick, times_fired) + seconds_per_tick = (initial(tick_interval) / 10) // to remove when upstream merge + + if(ko_timer) // Not yet + return + if(owner.stat == DEAD || HAS_TRAIT(owner, TRAIT_STABLEHEART) || HAS_TRAIT(owner, TRAIT_NOBLOOD) || IS_IN_STASIS(owner)) + return + if(owner.has_status_effect(/datum/status_effect/cpr_applied)) + return + + if(!HAS_TRAIT(owner, TRAIT_NOBREATH)) + owner.adjustOxyLoss(4 * seconds_per_tick) + + // Tissues die without blood circulation + owner.adjustBruteLoss(1 * seconds_per_tick) diff --git a/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_overrides.dm b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_overrides.dm new file mode 100644 index 000000000000..05624ad034c0 --- /dev/null +++ b/maplestation_modules/code/modules/mob/living/carbon/human/heart_rework/heart_overrides.dm @@ -0,0 +1,41 @@ +/obj/item/organ/internal/heart/Stop() + if(!beating) + return + + . = ..() + if(!. || !owner) + return + + owner.apply_status_effect(/datum/status_effect/heart_attack) + +/obj/item/organ/internal/heart/Restart() + if(beating) + return + + . = ..() + if(!. || !owner) + return + + owner.remove_status_effect(/datum/status_effect/heart_attack) + +/obj/item/organ/internal/heart/on_insert(mob/living/carbon/organ_owner, special) + . = ..() + if(beating) + organ_owner.remove_status_effect(/datum/status_effect/heart_attack) + +/obj/item/organ/internal/heart/on_remove(mob/living/carbon/organ_owner, special) + . = ..() + if(!special) + organ_owner.apply_status_effect(/datum/status_effect/heart_attack) + +// Glands can't stop beating but they are cringe +/obj/item/organ/internal/heart/gland/Stop() + return FALSE + +/* +// I think this is un-necessary, so I'm commenting it out even if it's SUPPOSED to be a thing +/mob/living/carbon/human/setup_organless_effects() + . = ..() + // You don't spawn with a heart, so, technically... You spawn with a heart attack + apply_status_effect(/datum/status_effect/heart_attack) +*/