diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm index 8eda251d1ae..bed3a0b019c 100644 --- a/code/datums/wounds/_wounds.dm +++ b/code/datums/wounds/_wounds.dm @@ -79,6 +79,7 @@ /// How much we're contributing to this limb's bleed_rate var/blood_flow /// Surgical states we're applying to our limb + /// Note: This var is mutated after it is applied, to only states that were successfully applied var/surgery_states = NONE /// How much having this wound will add to all future check_wounding() rolls on this limb, to allow progression to worse injuries with repeated damage @@ -304,15 +305,6 @@ limb.update_wounds(replaced) if (disabling) limb.remove_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) - - if(surgery_states) - for (var/state in exclusive_surgery_states) - if (!(limb.surgery_state & exclusive_surgery_states[state])) - continue - var/actual_state = text2num(state) - if (actual_state & surgery_states) - surgery_states &= ~actual_state - if (surgery_states) limb.remove_surgical_state(surgery_states) @@ -323,11 +315,10 @@ if (limb) RegisterSignal(limb, COMSIG_QDELETING, PROC_REF(source_died)) RegisterSignals(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_UNGAUZED), PROC_REF(gauze_state_changed)) - RegisterSignal(limb, COMSIG_BODYPART_UPDATING_SURGERY_STATE, PROC_REF(on_surgery_state_change)) if (disabling) limb.add_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src)) - if(surgery_states) + if(surgery_states) // first check filters invalid states for (var/state in exclusive_surgery_states) if (!(limb.surgery_state & exclusive_surgery_states[state])) continue @@ -335,8 +326,10 @@ if (actual_state & surgery_states) surgery_states &= ~actual_state - if (surgery_states) + if (surgery_states) // second check applies any remaining valid states limb.add_surgical_state(surgery_states) + // NB: don't check state changes until AFTER we finish apply our own, or we'll just undo ourselves + RegisterSignal(limb, COMSIG_BODYPART_UPDATING_SURGERY_STATE, PROC_REF(on_surgery_state_change)) if (victim) start_limping_if_we_should() // the status effect already handles removing itself @@ -345,8 +338,9 @@ update_inefficiencies(replaced) /// Used to remove states applied or removed by operations from ourselves as to not remove them if we heal mid-surgery -/datum/wound/proc/on_surgery_state_change(old_state, surgery_state, changed_states) +/datum/wound/proc/on_surgery_state_change(datum/source, old_state, current_state, changed_states) SIGNAL_HANDLER + // Any state that changes, adding or removing, should henceforth be untouched by us. Let the surgeon handle it. surgery_states &= ~changed_states /datum/wound/proc/add_or_remove_actionspeed_mod() diff --git a/code/modules/surgery/operations/operation_generic.dm b/code/modules/surgery/operations/operation_generic.dm index 1f08ccd90e4..c670c09c873 100644 --- a/code/modules/surgery/operations/operation_generic.dm +++ b/code/modules/surgery/operations/operation_generic.dm @@ -131,7 +131,9 @@ /datum/surgery_operation/limb/retract_skin/on_success(obj/item/bodypart/limb) . = ..() - limb.add_surgical_state(SURGERY_SKIN_OPEN) + // the limb SHOULD either have unclamped or clamped vessels if we're retracting skin + // if it doesn't, some shenanigans happened (likely due to wounds), so we add unclamped if needed - just to be thorough + limb.add_surgical_state(SURGERY_SKIN_OPEN | (LIMB_HAS_SURGERY_STATE(limb, SURGERY_VESSELS_CLAMPED) ? NONE : SURGERY_VESSELS_UNCLAMPED)) limb.remove_surgical_state(SURGERY_SKIN_CUT) /datum/surgery_operation/limb/retract_skin/abductor diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm index deb154742f8..69bc29fb148 100644 --- a/code/modules/unit_tests/surgeries.dm +++ b/code/modules/unit_tests/surgeries.dm @@ -244,3 +244,103 @@ var/obj/item/clothing/under/jumpsuit = test_mob.get_item_by_slot(ITEM_SLOT_ICLOTHING) jumpsuit.adjust_to_alt() TEST_ASSERT(test_mob.is_location_accessible(BODY_ZONE_CHEST), "Chest should be accessible after rolling jumpsuit down") + +/// Tests surgeries which just modify basic surgical states +/datum/unit_test/state_surgeries + +/datum/unit_test/state_surgeries/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) + var/mob/living/carbon/human/surgeon = allocate(/mob/living/carbon/human/consistent) + var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel) + var/obj/item/retractor/retractor = allocate(/obj/item/retractor) + var/obj/item/circular_saw/saw = allocate(/obj/item/circular_saw) + var/obj/item/hemostat/hemostat = allocate(/obj/item/hemostat) + var/obj/item/cautery/cautery = allocate(/obj/item/cautery) + var/obj/item/bodypart/chest/chest = patient.get_bodypart(BODY_ZONE_CHEST) + + var/datum/surgery_operation/limb/incise_skin/isurgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(isurgery.success(chest, surgeon, scalpel, list())) + + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_CUT), "Making an incision did not apply the skin cut surgical state") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_UNCLAMPED), "Making an incision did not apply the vessels unclamped surgical state") + + var/datum/surgery_operation/limb/retract_skin/rsurgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(rsurgery.success(chest, surgeon, retractor, list())) + + TEST_ASSERT(!LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_CUT), "Retracting skin did not remove the skin cut surgical state") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_OPEN), "Retracting skin did not apply the skin open surgical state") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_UNCLAMPED), "Retracting skin removed the vessels unclamped surgical state unexpectedly") + + var/datum/surgery_operation/limb/clamp_bleeders/csurgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(csurgery.success(chest, surgeon, hemostat, list())) + + TEST_ASSERT(!LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_UNCLAMPED), "Clamping bleeders did not remove the vessels unclamped surgical state") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_CLAMPED), "Clamping bleeders did not apply the vessels clamped surgical state") + + var/datum/surgery_operation/limb/saw_bones/ssurgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(ssurgery.success(chest, surgeon, saw, list())) + + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_BONE_SAWED), "Sawing bones did not apply the bone sawed surgical state") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_CLAMPED), "Sawing bones removed the vessels clamped surgical state unexpectedly") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_OPEN), "Sawing bones removed the skin open surgical state unexpectedly") + + var/datum/surgery_operation/limb/incise_organs/osurgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(osurgery.success(chest, surgeon, scalpel, list())) + + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_ORGANS_CUT), "Incising organs did not apply the organs incision surgical state") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_BONE_SAWED), "Incising organs removed the bone sawed surgical state unexpectedly") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_CLAMPED), "Incising organs removed the vessels clamped surgical state unexpectedly") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_OPEN), "Incising organs removed the skin open surgical state unexpectedly") + + var/datum/surgery_operation/limb/close_skin/msurgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + UNLINT(msurgery.success(chest, surgeon, cautery, list())) + + TEST_ASSERT(!LIMB_HAS_ANY_SURGERY_STATE(chest, ALL), "Closing surgery did not remove all surgical states applied during surgery") + +/// Checks wounds apply surgical state and remove surgical state when healed +/datum/unit_test/wound_state + +/datum/unit_test/wound_state/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) + var/obj/item/bodypart/chest/chest = patient.get_bodypart(BODY_ZONE_CHEST) + + var/datum/wound/slash/flesh/severe/wound = new() + wound.surgery_states = SURGERY_SKIN_CUT | SURGERY_VESSELS_UNCLAMPED + wound.apply_wound(chest) + + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_CUT|SURGERY_VESSELS_UNCLAMPED), "Wound did not apply correct surgery states to limb") + + qdel(wound) + + TEST_ASSERT(!LIMB_HAS_ANY_SURGERY_STATE(chest, SURGERY_SKIN_CUT|SURGERY_VESSELS_UNCLAMPED), "Wound did not remove surgery states from limb upon healing") + +/// Checks surgical states applied by surgery while wounded don't get removed when the wound heals +/datum/unit_test/wound_state_with_surgery + +/datum/unit_test/wound_state_with_surgery/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human/consistent) + var/obj/item/bodypart/chest/chest = patient.get_bodypart(BODY_ZONE_CHEST) + + var/datum/wound/slash/flesh/severe/wound = new() + wound.surgery_states = SURGERY_SKIN_CUT | SURGERY_VESSELS_UNCLAMPED | SURGERY_BONE_DRILLED + wound.apply_wound(chest) + + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_CUT|SURGERY_VESSELS_UNCLAMPED|SURGERY_BONE_DRILLED), "Wound did not apply correct surgery states to limb") + + var/datum/surgery_operation/limb/retract_skin/surgery = GLOB.operations.operations_by_typepath[__IMPLIED_TYPE__] + var/mob/living/carbon/human/surgeon = allocate(/mob/living/carbon/human/consistent) + var/obj/item/retractor/retractor = allocate(/obj/item/retractor) + + UNLINT(surgery.success(chest, surgeon, retractor, list())) + + TEST_ASSERT(!LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_CUT), "Surgical state for skin cut was not removed after surgery opened the cut") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_OPEN), "Surgical state for skin open was not applied after surgery opened the cut") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_UNCLAMPED), "Surgical state for unclamped vessels was incorrectly removed by surgery that only opened the skin") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_BONE_DRILLED), "Surgical state for bone drilled was incorrectly removed by surgery that only opened the skin") + + qdel(wound) + + TEST_ASSERT(!LIMB_HAS_ANY_SURGERY_STATE(chest, SURGERY_SKIN_CUT), "Surgical state for skin cut was somehow re-applied upon wound healing") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_SKIN_OPEN), "Surgical state for skin open was incorrectly removed upon wound healing") + TEST_ASSERT(LIMB_HAS_SURGERY_STATE(chest, SURGERY_VESSELS_UNCLAMPED), "Surgical state for unclamped vessels was incorrectly removed upon wound healing") + TEST_ASSERT(!LIMB_HAS_SURGERY_STATE(chest, SURGERY_BONE_DRILLED), "Surgical state for bone drilled was not correctly removed upon wound healing")