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)
+*/