fkKXT
zo?qjB6=>QtT0yR)j_2Ft%M@4hLocaJ>BCh(firuXTEX-#|M})Eu>Po*AE-6vAb_l^
z;Russ8-g
z1eEBGAYaI_f^rz(9thLb=W
Date: Sun, 1 Feb 2026 01:08:55 -0600
Subject: [PATCH 002/128] Automatic changelog generation for PR #5842 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5842.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5842.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5842.yml b/html/changelogs/AutoChangeLog-pr-5842.yml
new file mode 100644
index 0000000000..50d348b6ee
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5842.yml
@@ -0,0 +1,4 @@
+author: thearbiber
+changes:
+ - {rscadd: resprites nightvision}
+delete-after: true
From d105186d603e2bf6f617076251299d6f14e2630b Mon Sep 17 00:00:00 2001
From: firebudgy <153147550+firebudgy@users.noreply.github.com>
Date: Sun, 1 Feb 2026 12:04:53 -0800
Subject: [PATCH 003/128] APC Control Console Removal (From Ships & Ruins)
(#5852)
## About The Pull Request
Removes the power flow control console from ships and ruins. For
context, you can remotely disable APCs no matter if they are ruin or
ship-attached, including other ships.
## Why It's Good For The Game
It's cruft, also I encouraged someone to use it to delam a supermatter
last round so this is my community service to Shiptest.
## Changelog
:cl:
balance: APC Control Console removed from all player-facing
environments.
/:cl:
---
.../IceRuins/icemoon_training_center.dmm | 23 +++++++++++--------
.../JungleRuins/jungle_interceptor.dmm | 2 +-
_maps/RandomRuins/SpaceRuins/spacemall.dmm | 2 +-
.../shuttles/nanotrasen/nanotrasen_ranger.dmm | 2 +-
.../syndicate/syndicate_ngr_kaliandhi.dmm | 2 +-
5 files changed, 17 insertions(+), 14 deletions(-)
diff --git a/_maps/RandomRuins/IceRuins/icemoon_training_center.dmm b/_maps/RandomRuins/IceRuins/icemoon_training_center.dmm
index e89fef9cfe..1500440185 100644
--- a/_maps/RandomRuins/IceRuins/icemoon_training_center.dmm
+++ b/_maps/RandomRuins/IceRuins/icemoon_training_center.dmm
@@ -2150,6 +2150,7 @@
pixel_x = 0;
pixel_y = -5
},
+/obj/item/reagent_containers/food/drinks/mead,
/turf/open/floor/wood,
/area/ruin/icemoon/training_facility/office_2)
"kA" = (
@@ -4342,7 +4343,7 @@
/turf/open/floor/plating,
/area/ruin/icemoon/training_facility/classroom)
"vC" = (
-/obj/machinery/computer/apc_control{
+/obj/machinery/computer/monitor{
dir = 1
},
/obj/effect/spawner/random/trash/crushed_can{
@@ -5369,11 +5370,6 @@
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plasteel/dark,
/area/ruin/icemoon/training_facility/classroom)
-"Ay" = (
-/obj/structure/table/greyscale,
-/obj/item/attachment/gun/flamethrower,
-/turf/open/floor/plating/asteroid/icerock/cracked,
-/area/ruin/icemoon/training_facility)
"AA" = (
/obj/effect/turf_decal/techfloor{
dir = 5
@@ -6371,6 +6367,10 @@
"Ff" = (
/turf/open/floor/plasteel/grimy,
/area/ruin/icemoon/training_facility/dorms)
+"Fi" = (
+/mob/living/simple_animal/hostile/human/ramzi/melee/sawbones,
+/turf/open/floor/plating/rust,
+/area/ruin/icemoon/training_facility/medbay)
"Fj" = (
/obj/effect/turf_decal/techfloor{
dir = 8
@@ -7673,7 +7673,6 @@
/area/ruin/icemoon/training_facility/dorms)
"Lz" = (
/obj/effect/decal/cleanable/dirt,
-/mob/living/simple_animal/hostile/human/ramzi/melee/sawbones,
/turf/open/floor/plasteel/mono/white,
/area/ruin/icemoon/training_facility/medbay)
"LA" = (
@@ -8516,6 +8515,11 @@
},
/turf/open/floor/plating/rust,
/area/ruin/icemoon/training_facility/classroom)
+"PO" = (
+/obj/structure/table/greyscale,
+/obj/item/attachment/gun/flamethrower,
+/turf/open/floor/plating/asteroid/icerock/cracked,
+/area/ruin/icemoon/training_facility)
"PP" = (
/obj/structure/table/reinforced,
/obj/effect/decal/cleanable/dirt,
@@ -9244,7 +9248,6 @@
pixel_x = 0;
pixel_y = 12
},
-/obj/item/reagent_containers/food/drinks/mead,
/turf/open/floor/wood,
/area/ruin/icemoon/training_facility/office_2)
"SW" = (
@@ -13943,7 +13946,7 @@ qr
tx
Cd
wr
-ZU
+Fi
ZU
rW
qP
@@ -14822,7 +14825,7 @@ kk
wN
Bv
kk
-Ay
+PO
xj
xj
tB
diff --git a/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm b/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm
index cd235131ec..a86034f870 100644
--- a/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm
+++ b/_maps/RandomRuins/JungleRuins/jungle_interceptor.dmm
@@ -3607,7 +3607,7 @@
/turf/open/floor/plating/rust,
/area/ruin/jungle/interceptor/forehall)
"Fs" = (
-/obj/machinery/computer/apc_control{
+/obj/machinery/computer/monitor{
dir = 4
},
/turf/open/floor/plating/rust,
diff --git a/_maps/RandomRuins/SpaceRuins/spacemall.dmm b/_maps/RandomRuins/SpaceRuins/spacemall.dmm
index f2582aea14..65f58a9551 100644
--- a/_maps/RandomRuins/SpaceRuins/spacemall.dmm
+++ b/_maps/RandomRuins/SpaceRuins/spacemall.dmm
@@ -10039,7 +10039,7 @@
/turf/open/floor/plasteel/white,
/area/ruin/space/has_grav/spacemall/dorms)
"Me" = (
-/obj/machinery/computer/apc_control,
+/obj/machinery/computer/monitor,
/obj/structure/spider/stickyweb,
/turf/open/floor/plasteel/dark,
/area/ruin/space/has_grav/spacemall/maint)
diff --git a/_maps/shuttles/nanotrasen/nanotrasen_ranger.dmm b/_maps/shuttles/nanotrasen/nanotrasen_ranger.dmm
index 0a63fbe0f1..68efacfb22 100644
--- a/_maps/shuttles/nanotrasen/nanotrasen_ranger.dmm
+++ b/_maps/shuttles/nanotrasen/nanotrasen_ranger.dmm
@@ -1746,7 +1746,7 @@
/turf/open/floor/plasteel/dark,
/area/ship/hallway/port)
"lD" = (
-/obj/machinery/computer/apc_control{
+/obj/machinery/computer/monitor{
dir = 4;
icon_state = "computer-left"
},
diff --git a/_maps/shuttles/syndicate/syndicate_ngr_kaliandhi.dmm b/_maps/shuttles/syndicate/syndicate_ngr_kaliandhi.dmm
index c67f1ef720..1412680986 100644
--- a/_maps/shuttles/syndicate/syndicate_ngr_kaliandhi.dmm
+++ b/_maps/shuttles/syndicate/syndicate_ngr_kaliandhi.dmm
@@ -3117,7 +3117,7 @@
/turf/open/floor/plasteel/tech,
/area/ship/security/armory)
"qy" = (
-/obj/machinery/computer/apc_control{
+/obj/machinery/computer/monitor{
icon_state = "computer-right"
},
/obj/effect/turf_decal/techfloor{
From 172f4b0a12ba8f7b0f9c498bda14ce7ea0bcbefa Mon Sep 17 00:00:00 2001
From: Changelogs
Date: Sun, 1 Feb 2026 14:26:40 -0600
Subject: [PATCH 004/128] Automatic changelog generation for PR #5852 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5852.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5852.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5852.yml b/html/changelogs/AutoChangeLog-pr-5852.yml
new file mode 100644
index 0000000000..a036642442
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5852.yml
@@ -0,0 +1,4 @@
+author: firebudgy
+changes:
+ - {balance: APC Control Console removed from all player-facing environments.}
+delete-after: true
From 310e35afd4d618ea2a884002527a36f9a589b684 Mon Sep 17 00:00:00 2001
From: Sarah C <93578146+SapphicOverload@users.noreply.github.com>
Date: Sun, 1 Feb 2026 15:18:05 -0500
Subject: [PATCH 005/128] Robot Wounds (#5676)
## About The Pull Request
This adds several new wound types applicable to robotic limbs, and ports
some wound refactoring from /tg/ to do so.
I've also made some additional code changes to allow rolling for and
applying multiple wound types at once, and applying slash/pierce wounds
with burn damage weapons that have the correct sharpness.
New wound types:
- Buckling - The limb is dented, buckled inward, or sheared, making
movement more difficult. Caused by brute damage, more commonly by blunt
weaponry.
- Heat Warping - The limb has been warped by thermal stress, reducing
the limb's maximum integrity. Caused by burn damage.
- Electrical - The limb's internal electronics have been damaged,
causing loss of limb function and damaging internal components. Caused
by all damage but especially bullets and lasers.
Treatments:
BUCKLING:
- Dented Plating (Moderate Buckling) - Apply a crowbar to bend the
plating back into place.
- Buckled Chassis (Severe Buckling) - 1) Cut off the external plating
with a welding tool, angle grinder, or plasma cutter. 2) Add new
titanium, plasteel, or plastitanium plating. 3) Weld the new plating on
with a welding tool.
- Sheared Frame (Critical Buckling) - 1) Cut off the external plating.
2) Disconnect the internal frame with a wrench. 3) Add new rods to
replace the damaged frame. 4) Secure the new frame with a wrench. 5) Add
the new plating. 6) Weld the new plating on.
HEAT-WARPING:
- Surface Oxidization (Moderate Heat-Warping) - Apply a welding tool or
similarly hot object to burn off the oxidized layer.
- Warped Plating (Severe Heat-Warping) - Apply a welding tool or other
source of high heat to re-heat the plating, then bend it back into shape
with a crowbar.
- Deformed Chassis (Critical Heat-Warping) - 1) Cut off the external
plating. 2) Disconnect the internal frame with a wrench. 3) Add new rods
to replace the damaged frame. 4) Secure the new frame with a wrench. 5)
Add the new plating. 6) Weld the new plating on.
ELECTRICAL:
- Damaged Electronics (Severe Electrical) - 1) Unscrew the part's shell.
2) Open the hatch. 3) Replace the internal wiring. 4) Re-heat the solder
with a welding tool, or apply liquid solder. 5) Close the hatch. 6)
Screw the shell back together.
- Short Circuit (Critical Electrical) - 1) Unscrew the part's shell. 2)
Open the hatch. 3) Prepare the electronics with a multitool. 4) Replace
the capacitor in the electronics. 5) Replace the internal wiring. 6)
Re-heat the solder with a welding tool, or apply liquid solder. 7) Close
the hatch. 8) Screw the shell back together.
TODO:
- [x] More backend refactoring
- [x] Document repairs for each wound
- [x] Improve in-game descriptions
- [ ] Test on live
## Why It's Good For The Game
Robotic limbs ignoring the wounds system puts them in a very strange
position balance-wise, while also making them a lot less interesting.
This corrects both.
## Changelog
:cl:
add: Added several new wound types for robotic limbs.
balance: Fire stacks now apply wounds normally instead of instantly
applying the highest one possible.
code: Multiple wound types can be rolled for and applied simultaneously.
code: Burn damage weapons can now apply slash/pierce wounds with the
correct sharpness.
fix: Fixed IPCs taking far more damage in crit than intended.
/:cl:
---
code/__DEFINES/DNA.dm | 5 -
.../signals/signals_mob/signals_mob_carbon.dm | 2 +
code/__DEFINES/dcs/signals/signals_reagent.dm | 2 +-
code/__DEFINES/mobs.dm | 2 +-
code/__DEFINES/subsystems.dm | 1 +
code/__DEFINES/traits/traits.dm | 7 +
code/__DEFINES/wounds.dm | 170 +++---
code/__HELPERS/_lists.dm | 1 +
code/_onclick/item_attack.dm | 42 +-
code/controllers/subsystem/wounds.dm | 167 ++++++
code/datums/components/butchering.dm | 6 +-
code/datums/components/embedded.dm | 3 +-
code/datums/components/pellet_cloud.dm | 8 +-
code/datums/elements/kneecapping.dm | 7 +-
code/datums/elements/robotic_heal.dm | 93 ++++
code/datums/status_effects/debuffs.dm | 30 +-
.../status_effects/debuffs/fire_stacks.dm | 24 +-
code/datums/status_effects/wound_effects.dm | 103 ++--
code/datums/wounds/_wound_static_data.dm | 188 +++++++
code/datums/wounds/_wounds.dm | 404 ++++++++++----
code/datums/wounds/blunt.dm | 3 +
code/datums/wounds/bones.dm | 256 ++++-----
code/datums/wounds/burns.dm | 82 ++-
code/datums/wounds/dismember.dm | 68 ++-
code/datums/wounds/muscle.dm | 55 +-
code/datums/wounds/pierce.dm | 104 ++--
code/datums/wounds/robotic/buckling.dm | 213 ++++++++
code/datums/wounds/robotic/electrical.dm | 130 +++++
code/datums/wounds/robotic/heat_warping.dm | 180 +++++++
code/datums/wounds/slash.dm | 136 +++--
code/game/atoms.dm | 28 +-
code/game/objects/items/devices/flashlight.dm | 2 +-
code/game/objects/items/melee/energy.dm | 6 +-
code/game/objects/items/stacks/stack.dm | 1 +
code/game/objects/items/stacks/tape.dm | 47 +-
code/game/objects/items/tools/weldingtool.dm | 18 +-
code/modules/admin/verbs/randomverbs.dm | 16 +-
code/modules/mob/living/carbon/carbon.dm | 25 +-
.../mob/living/carbon/carbon_defense.dm | 62 ++-
.../mob/living/carbon/carbon_defines.dm | 3 +
.../modules/mob/living/carbon/damage_procs.dm | 8 +-
code/modules/mob/living/carbon/human/human.dm | 6 +-
.../mob/living/carbon/human/species.dm | 10 -
.../living/carbon/human/species_types/IPC.dm | 4 +-
.../carbon/human/species_types/abductors.dm | 2 +-
.../carbon/human/species_types/ethereal.dm | 2 +-
.../carbon/human/species_types/flypeople.dm | 2 +-
.../carbon/human/species_types/humans.dm | 2 +-
.../carbon/human/species_types/jellypeople.dm | 2 +-
.../carbon/human/species_types/kepori.dm | 2 +-
.../human/species_types/lizardpeople.dm | 2 +-
.../carbon/human/species_types/mothmen.dm | 3 +-
.../carbon/human/species_types/plasmamen.dm | 2 +-
.../human/species_types/shadowpeople.dm | 2 +-
.../carbon/human/species_types/skeletons.dm | 2 +-
.../carbon/human/species_types/snail.dm | 2 +-
.../living/carbon/human/species_types/vox.dm | 1 -
code/modules/mob/living/life.dm | 4 +-
code/modules/mob/living/living.dm | 3 +
code/modules/mob/mob_helpers.dm | 20 -
code/modules/power/cable.dm | 24 +-
.../projectiles/guns/energy/special.dm | 18 +-
.../reagents/medical_reagents/ipc_reagents.dm | 5 +-
.../medical_reagents/medicine_reagents.dm | 2 +-
.../medical_reagents/wound_reagents.dm | 2 +-
.../chemistry/reagents/other_reagents.dm | 3 +
.../chemistry/reagents/trickwine_reagents.dm | 4 +-
.../reagents/chemistry/recipes/medicine.dm | 4 -
.../reagents/chemistry/recipes/others.dm | 4 +
code/modules/surgery/bodyparts/bodyparts.dm | 498 ++++++++++++------
.../surgery/bodyparts/dismemberment.dm | 110 ++--
code/modules/surgery/bodyparts/head.dm | 15 +-
code/modules/surgery/bodyparts/parts.dm | 7 +-
.../surgery/bodyparts/robot_bodyparts.dm | 6 +
.../bodyparts/species_parts/ipc_bodyparts.dm | 7 +
.../bodyparts/species_parts/misc_bodyparts.dm | 24 +-
.../species_parts/plasmaman_bodyparts.dm | 6 +
.../species_parts/rachnid_bodyparts.dm | 6 +
code/modules/surgery/bone_fractures.dm | 12 +-
code/modules/surgery/buckling_repair.dm | 38 ++
code/modules/surgery/debride.dm | 16 +-
code/modules/surgery/electrical_repair.dm | 159 ++++++
code/modules/surgery/heatwarp_repair.dm | 20 +
code/modules/surgery/mechanic_steps.dm | 204 +++++++
code/modules/surgery/organs/augments_arms.dm | 2 +-
.../surgery/organs/augments_internal.dm | 6 +-
code/modules/surgery/organs/ears.dm | 7 +-
code/modules/surgery/organs/eyes.dm | 20 +-
code/modules/surgery/organs/organ_internal.dm | 14 +-
code/modules/surgery/repair_puncture.dm | 12 +-
code/modules/surgery/surgery_helpers.dm | 2 +-
code/modules/surgery/surgery_step.dm | 2 +-
code/modules/unit_tests/medical_wounds.dm | 36 +-
code/modules/vending/_vending.dm | 8 +-
shiptest.dme | 10 +
95 files changed, 3076 insertions(+), 1018 deletions(-)
create mode 100644 code/controllers/subsystem/wounds.dm
create mode 100644 code/datums/elements/robotic_heal.dm
create mode 100644 code/datums/wounds/_wound_static_data.dm
create mode 100644 code/datums/wounds/blunt.dm
create mode 100644 code/datums/wounds/robotic/buckling.dm
create mode 100644 code/datums/wounds/robotic/electrical.dm
create mode 100644 code/datums/wounds/robotic/heat_warping.dm
create mode 100644 code/modules/surgery/buckling_repair.dm
create mode 100644 code/modules/surgery/electrical_repair.dm
create mode 100644 code/modules/surgery/heatwarp_repair.dm
diff --git a/code/__DEFINES/DNA.dm b/code/__DEFINES/DNA.dm
index 344b903488..6773b2cb12 100644
--- a/code/__DEFINES/DNA.dm
+++ b/code/__DEFINES/DNA.dm
@@ -132,11 +132,6 @@
#define MUTCOLORS_SECONDARY 25
///Human skintones
#define SKINCOLORS 26
-///Used for determining which wounds are applicable to this species.
-///if we have flesh (can suffer slash/piercing/burn wounds, requires they don't have NOBLOOD)
-#define HAS_FLESH 27
-///if we have bones (can suffer bone wounds)
-#define HAS_BONE 28
//organ slots
#define ORGAN_SLOT_BRAIN "brain"
diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
index e3d88ac3d6..8968a85c37 100644
--- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
+++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_carbon.dm
@@ -18,6 +18,8 @@
// /mob/living/carbon physiology signals
#define COMSIG_CARBON_GAIN_WOUND "carbon_gain_wound" //from /datum/wound/proc/apply_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L)
#define COMSIG_CARBON_LOSE_WOUND "carbon_lose_wound" //from /datum/wound/proc/remove_wound() (/mob/living/carbon/C, /datum/wound/W, /obj/item/bodypart/L)
+/// Called after limb AND victim has been unset
+#define COMSIG_CARBON_POST_LOSE_WOUND "carbon_post_lose_wound" //from /datum/wound/proc/remove_wound() (/datum/wound/lost_wound, /obj/item/bodypart/part, ignore_limb, replaced)
///from base of /obj/item/bodypart/proc/attach_limb(): (new_limb, special) allows you to fail limb attachment
#define COMSIG_CARBON_ATTACH_LIMB "carbon_attach_limb"
#define COMPONENT_NO_ATTACH (1<<0)
diff --git a/code/__DEFINES/dcs/signals/signals_reagent.dm b/code/__DEFINES/dcs/signals/signals_reagent.dm
index 2f9688aab6..d7cc4b336c 100644
--- a/code/__DEFINES/dcs/signals/signals_reagent.dm
+++ b/code/__DEFINES/dcs/signals/signals_reagent.dm
@@ -9,7 +9,7 @@
/// Prevents the atom from being exposed to reagents if returned on [COMSIG_ATOM_EXPOSE_REAGENTS]
#define COMPONENT_NO_EXPOSE_REAGENTS (1<<0)
///from base of atom/expose_reagents(): (/list, /datum/reagents, methods, volume_modifier, show_message)
-//#define COMSIG_ATOM_AFTER_EXPOSE_REAGENTS "atom_after_expose_reagents"
+#define COMSIG_ATOM_AFTER_EXPOSE_REAGENTS "atom_after_expose_reagents"
///from base of [/datum/reagent/proc/expose_atom]: (/datum/reagent, reac_volume)
#define COMSIG_ATOM_EXPOSE_REAGENT "atom_expose_reagent"
///from base of [/datum/reagent/proc/expose_atom]: (/atom, reac_volume)
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 5123acf1dc..1e301971fd 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -187,7 +187,7 @@
#define TRAUMA_RESILIENCE_BASIC 1 //Curable with chems
#define TRAUMA_RESILIENCE_SURGERY 2 //Curable with brain surgery
#define TRAUMA_RESILIENCE_LOBOTOMY 3 //Curable with lobotomy
-#define TRAUMA_RESILIENCE_WOUND 4 //Curable by healing the head wound
+#define TRAUMA_RESILIENCE_WOUND 4 //Curable by healing the relevant wound
#define TRAUMA_RESILIENCE_MAGIC 5 //Curable only with magic
#define TRAUMA_RESILIENCE_ABSOLUTE 6 //This is here to stay
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 4a139c2e56..609a65e52d 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -115,6 +115,7 @@
#define INIT_ORDER_SOUND_CACHE 84
#define INIT_ORDER_SOUNDS 83
#define INIT_ORDER_INSTRUMENTS 82
+#define INIT_ORDER_WOUNDS 81
#define INIT_ORDER_VIS 80
#define INIT_ORDER_ACHIEVEMENTS 77
#define INIT_ORDER_RESEARCH 75
diff --git a/code/__DEFINES/traits/traits.dm b/code/__DEFINES/traits/traits.dm
index 1ac5ddf420..204171951a 100644
--- a/code/__DEFINES/traits/traits.dm
+++ b/code/__DEFINES/traits/traits.dm
@@ -216,6 +216,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define TRAIT_NOMETABOLISM "no_metabolism"
#define TRAIT_NOCLONELOSS "no_cloneloss"
#define TRAIT_TOXIMMUNE "toxin_immune"
+#define TRAIT_NOBLOOD "noblood"
#define TRAIT_EASYDISMEMBER "easy_dismember"
#define TRAIT_LIMBATTACHMENT "limb_attach"
#define TRAIT_NOLIMBDISABLE "no_limb_disable"
@@ -436,6 +437,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define MAPPING_HELPER_TRAIT "mapping-helper" //obtained from mapping helper
/// Trait associated to wearing a suit
#define SUIT_TRAIT "suit"
+/// Trait associated with being EMPed
+#define EMP_TRAIT "emp"
+/// Trait associated with damage, whatever that means in the datum's context
+#define DAMAGE_TRAIT "damage"
/// Trait associated to lying down (having a [lying_angle] of a different value than zero).
#define LYING_DOWN_TRAIT "lying-down"
/// Trait associated to lacking electrical power.
@@ -488,6 +493,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai
#define LACKING_MANIPULATION_APPENDAGES_TRAIT "lacking-manipulation-appengades" //trait associated to not having fine manipulation appendages such as hands
#define HANDCUFFED_TRAIT "handcuffed"
#define ORBITED_TRAIT "orbited"
+/// Trait applied to organs when they are unable to function.
+#define TRAIT_ORGAN_FAILING "organ_failing"
/// Trait granted by [/obj/item/warpwhistle]
#define WARPWHISTLE_TRAIT "warpwhistle"
///Turf trait for when a turf is transparent
diff --git a/code/__DEFINES/wounds.dm b/code/__DEFINES/wounds.dm
index 8ed9aee087..0832fc160f 100644
--- a/code/__DEFINES/wounds.dm
+++ b/code/__DEFINES/wounds.dm
@@ -13,6 +13,12 @@
/// set wound_bonus on an item or attack to this to disable checking wounding for the attack
#define CANT_WOUND -100
+/// If there are multiple possible and valid wounds for the same type and severity, weight will be used to pick among them. See _wound_pregen_data.dm for more details
+/// This is used in pick_weight, so use integers
+#define WOUND_DEFAULT_WEIGHT 50
+/// Chance to roll a muscle wound from brute damage
+#define WOUND_MUSCLE_WEIGHT 15
+
// ~wound severities
#define WOUND_SEVERITY_TRIVIAL 0
#define WOUND_SEVERITY_MODERATE 1
@@ -22,18 +28,18 @@
#define WOUND_SEVERITY_LOSS 4
-// ~wound categories
-/// any brute weapon/attack that doesn't have sharpness. rolls for blunt bone wounds
-#define WOUND_BLUNT "blunt"
+// ~wound categories: wounding_types
+/// any brute weapon/attack that doesn't have sharpness. rolls for blunt bone and metal buckling wounds
+#define WOUND_BLUNT "wound_blunt"
/// any brute weapon/attack with sharpness = SHARP_EDGED. rolls for slash wounds
-#define WOUND_SLASH "slash"
-/// any brute weapon/attack with sharpness = SHARP_POINTY. rolls for piercing wounds
-#define WOUND_PIERCE "pierce"
-/// any concentrated burn attack (lasers really). rolls for burning wounds
-#define WOUND_BURN "burn"
-/// any brute attacks, rolled on a chance
-#define WOUND_MUSCLE "muscle"
+#define WOUND_SLASH "wound_slash"
+/// any brute weapon/attack with sharpness = SHARP_POINTY. rolls for piercing and electrical wounds
+#define WOUND_PIERCE "wound_pierce"
+/// any concentrated burn attack (lasers really). rolls for burning, heat-warping, and electrical wounds
+#define WOUND_BURN "wound_burn"
+/// Mainly a define used for wound_pregen_data, if a pregen data instance expects this, it will accept any and all wound types, even none at all
+#define WOUND_ALL "wound_all"
// ~determination second wind defines
// How much determination reagent to add each time someone gains a new wound in [/datum/wound/proc/second_wind]
@@ -47,22 +53,10 @@
/// While someone has determination in their system, their bleed rate is slightly reduced
#define WOUND_DETERMINATION_BLEED_MOD 0.85
-// ~wound global lists
-// list in order of highest severity to lowest
-GLOBAL_LIST_INIT(global_wound_types, list(WOUND_BLUNT = list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate),
- WOUND_SLASH = list(/datum/wound/slash/critical, /datum/wound/slash/critical, /datum/wound/slash/moderate),
- WOUND_PIERCE = list(/datum/wound/pierce/critical, /datum/wound/pierce/severe, /datum/wound/pierce/moderate),
- WOUND_BURN = list(/datum/wound/burn/critical, /datum/wound/burn/severe, /datum/wound/burn/moderate),
- WOUND_MUSCLE = list(/datum/wound/muscle/severe, /datum/wound/muscle/moderate)
- ))
-
-// every single type of wound that can be rolled naturally, in case you need to pull a random one
-GLOBAL_LIST_INIT(global_all_wound_types, list(/datum/wound/blunt/critical, /datum/wound/blunt/severe, /datum/wound/blunt/moderate,
- /datum/wound/slash/critical, /datum/wound/slash/critical, /datum/wound/slash/moderate,
- /datum/wound/pierce/critical, /datum/wound/pierce/severe, /datum/wound/pierce/moderate,
- /datum/wound/burn/critical, /datum/wound/burn/severe, /datum/wound/burn/moderate,
- /datum/wound/muscle/severe, /datum/wound/muscle/moderate))
-
+/// Wounds using this competition mode will remove any wounds of a greater severity than itself in a random wound roll. In most cases, you dont want to use this.
+#define WOUND_COMPETITION_OVERPOWER_GREATERS "wound_submit"
+/// Wounds using this competition mode will remove any wounds of a lower severity than itself in a random wound roll. Used for ensuring the worse case scenario of a given injury_roll.
+#define WOUND_COMPETITION_OVERPOWER_LESSERS "wound_dominate"
// ~burn wound infection defines
// Thresholds for infection for burn wounds, once infestation hits each threshold, things get steadily worse
@@ -86,52 +80,108 @@ GLOBAL_LIST_INIT(global_all_wound_types, list(/datum/wound/blunt/critical, /datu
#define WOUND_SLASH_DEAD_CLOT_MIN 0.05
/// if we suffer a bone wound to the head that creates brain traumas, the timer for the trauma cycle is +/- by this percent (0-100)
#define WOUND_BONE_HEAD_TIME_VARIANCE 20
-/// Chance to roll a muscle wound from brute damage
-#define MUSCLE_WOUND_CHANCE 20
-
-
-// ~mangling defines
-// With the wounds pt. 2 update, general dismemberment now requires 2 things for a limb to be dismemberable (bone only creatures just need the second):
-// 1. Skin is mangled: A critical slash or pierce wound on that limb
-// 2. Bone is mangled: At least a severe bone wound on that limb
-// see [/obj/item/bodypart/proc/get_mangled_state] for more information
-#define BODYPART_MANGLED_NONE 0
-#define BODYPART_MANGLED_BONE 1
-#define BODYPART_MANGLED_FLESH 2
-#define BODYPART_MANGLED_BOTH 3
-
+/// charge drain per severity level
+#define WOUND_ELECTRIC_POWER_DRAIN 0.05
// ~biology defines
-// What kind of biology we have, and what wounds we can suffer, mostly relies on the HAS_FLESH and HAS_BONE species traits on human species
-/// golems and androids, cannot suffer any wounds
-#define BIO_INORGANIC 0
-/// skeletons and plasmemes, can only suffer bone wounds, only needs mangled bone to be able to dismember
-#define BIO_JUST_BONE 1
-/// nothing right now, maybe slimepeople in the future, can only suffer slashing, piercing, and burn wounds
-#define BIO_JUST_FLESH 2
-/// standard humanoids, can suffer all wounds, needs mangled bone and flesh to dismember. conveniently, what you get when you combine BIO_JUST_BONE and BIO_JUST_FLESH
-#define BIO_FLESH_BONE 3
-
+// What kind of biology we have, and what wounds we can suffer, relies on the biological_state var on bodyparts.
+/// Has absolutely fucking nothing, no wounds
+#define BIO_INORGANIC NONE
+/// Has bone - allows the victim to suffer T2-T3 bone blunt wounds
+#define BIO_BONE (1<<0)
+/// Has flesh - allows the victim to suffer fleshy slash pierce and burn wounds
+#define BIO_FLESH (1<<1)
+/// Has metal - allows the victim to suffer buckling and heat-warping wounds
+#define BIO_METAL (1<<2)
+/// Is wired internally - allows the victim to suffer electrical wounds (robotic T1-T3 slash/pierce)
+#define BIO_WIRED (1<<3)
+/// Has bloodflow - can suffer bleeding wounds and can bleed
+#define BIO_BLOODED (1<<4)
+/// Is connected by a joint - can suffer T1 bone blunt wounds (dislocation)
+#define BIO_JOINTED (1<<5)
+
+/// Robotic - can suffer all metal/wired wounds, such as: UNIMPLEMENTED PLEASE UPDATE ONCE SYNTH WOUNDS 9/5/2023 ~Niko
+#define BIO_ROBOTIC (BIO_METAL|BIO_WIRED)
+/// Has flesh and bone - See BIO_BONE and BIO_FLESH
+#define BIO_FLESH_BONE (BIO_BONE|BIO_FLESH)
+/// Standard humanoid - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt, except dislocations. Think human heads/chests
+#define BIO_STANDARD_UNJOINTED (BIO_FLESH_BONE|BIO_BLOODED)
+/// Standard humanoid limbs - can bleed and suffer all flesh/bone wounds, such as: T1-3 slash/pierce/burn/blunt. Can also bleed, and be dislocated. Think human arms and legs
+#define BIO_STANDARD_JOINTED (BIO_STANDARD_UNJOINTED|BIO_JOINTED)
+
+// "Where" a specific biostate is within a given limb
+// Interior is hard shit, the last line, shit like bones
+// Exterior is soft shit, targetted by slashes and pierces (usually), protects exterior
+// A limb needs both mangled interior and exterior to be dismembered, but slash/pierce must mangle exterior to attack the interior
+// Not having exterior/interior counts as mangled exterior/interior for the purposes of dismemberment
+/// The given biostate is on the "interior" of the limb - hard shit, protected by exterior
+#define ANATOMY_INTERIOR (1<<0)
+/// The given biostate is on the "exterior" of the limb - soft shit, protects interior
+#define ANATOMY_EXTERIOR (1<<1)
+#define ANATOMY_EXTERIOR_AND_INTERIOR (ANATOMY_EXTERIOR|ANATOMY_INTERIOR)
+
+// Wound series
+// A "wound series" is just a family of wounds that logically follow eachother
+// Multiple wounds in a single series cannot be on a limb - the highest severity will always be prioritized, and lower ones will be skipped
+
+/// T1-T3 Bleeding slash wounds. Requires flesh. Can cause bleeding, but doesn't require it. From: slash.dm
+#define WOUND_SERIES_FLESH_SLASH_BLEED "wound_series_flesh_slash_bled"
+/// T1-T3 Basic blunt wounds. T1 requires jointed, but 2-3 require bone. From: bone.dm
+#define WOUND_SERIES_BONE_BLUNT_BASIC "wound_series_bone_blunt_basic"
+/// T1-T3 Basic burn wounds. Requires flesh. From: burns.dm
+#define WOUND_SERIES_FLESH_BURN_BASIC "wound_series_flesh_burn_basic"
+/// T1-3 Bleeding puncture wounds. Requires flesh. Can cause bleeding, but doesn't require it. From: pierce.dm
+#define WOUND_SERIES_FLESH_PUNCTURE_BLEED "wound_series_flesh_puncture_bleed"
+/// T1-3 Buckling wounds. Requires metal. From: buckling.dm
+#define WOUND_SERIES_METAL_BUCKLING "wound_series_metal_buckling"
+/// T1-3 Heat-warping wounds. Requires metal. From: heat_warping.dm
+#define WOUND_SERIES_METAL_HEAT_WARPING "wound_series_metal_heat_warping"
+/// T1-3 Electrical wounds. Requires wired. From: electrical.dm
+#define WOUND_SERIES_WIRED_ELECTRICAL "wound_series_wired_electrical"
+/// T1-3 Muscle wounds. Requires flesh. From: muscle.dm
+#define WOUND_SERIES_FLESH_MUSCLE "wound_series_flesh_muscle"
+/// Generic loss wounds. See loss.dm
+#define WOUND_SERIES_LOSS_BASIC "wound_series_loss_basic"
+
+/// Used in get_corresponding_wound_type(): Will pick the highest severity wound out of severity_min and severity_max
+#define WOUND_PICK_HIGHEST_SEVERITY 1
+/// Used in get_corresponding_wound_type(): Will pick the lowest severity wound out of severity_min and severity_max
+#define WOUND_PICK_LOWEST_SEVERITY 2
+
+// With the wounds pt. 2 update, general dismemberment now requires 2 things for a limb to be dismemberable (exterior/bone only creatures just need the second):
+// 1. Exterior is mangled: A critical slash or pierce wound on that limb
+// 2. Interior is mangled: At least a severe bone wound on that limb
+// Lack of exterior or interior count as mangled exterior/interior respectively
+// see [/obj/item/bodypart/proc/get_mangled_state] for more information, as well as GLOB.bio_state_anatomy
+#define BODYPART_MANGLED_NONE NONE
+#define BODYPART_MANGLED_INTERIOR (1<<0)
+#define BODYPART_MANGLED_EXTERIOR (1<<1)
+#define BODYPART_MANGLED_BOTH (BODYPART_MANGLED_INTERIOR | BODYPART_MANGLED_EXTERIOR)
// ~wound flag defines
-/// If this wound requires having the HAS_FLESH flag for humanoids
-#define FLESH_WOUND (1<<0)
-/// If this wound requires having the HAS_BONE flag for humanaoids
-#define BONE_WOUND (1<<1)
-/// If having this wound counts as mangled flesh for dismemberment
-#define MANGLES_FLESH (1<<2)
-/// If having this wound counts as mangled bone for dismemberment
-#define MANGLES_BONE (1<<3)
+/// If having this wound counts as mangled exterior for dismemberment
+#define MANGLES_EXTERIOR (1<<0)
+/// If having this wound counts as mangled interior for dismemberment
+#define MANGLES_INTERIOR (1<<1)
/// If this wound marks the limb as being allowed to have gauze applied
-#define ACCEPTS_GAUZE (1<<4)
+#define ACCEPTS_GAUZE (1<<2)
/// If this wound marks the limb as being allowed to have splints applied
-#define ACCEPTS_SPLINT (1<<5)
+#define ACCEPTS_SPLINT (1<<3)
+/// Whether this wound is fixed when replacing the external plating
+#define PLATING_DAMAGE (1<<4)
+/// If this wound allows the victim to grasp it
+#define CAN_BE_GRASPED (1<<5)
+/// This causes the wound to numb the affected limb
+#define NUMBS_BODYPART (1<<6)
/// When a wound is staining the gauze with blood
#define GAUZE_STAIN_BLOOD 1
/// When a wound is staining the gauze with pus
#define GAUZE_STAIN_PUS 2
+/// Limb integrity is reduced to this before being used to calculate how much integrity loss it should have.
+#define WOUND_MAX_INTEGRITY_CONSIDERED 50
+
// ~blood_flow rates of change, these are used by [/datum/wound/proc/get_bleed_rate_of_change] from [/mob/living/carbon/proc/bleed_warn] to let the player know if their bleeding is getting better/worse/the same
/// Our wound is clotting and will eventually stop bleeding if this continues
#define BLOOD_FLOW_DECREASING -1
diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm
index 72236dc21a..7ebb379a4f 100644
--- a/code/__HELPERS/_lists.dm
+++ b/code/__HELPERS/_lists.dm
@@ -70,6 +70,7 @@
#define LAZYACCESSASSOC(L, I, K) L ? L[I] ? L[I][K] ? L[I][K] : null : null : null
#define LAZYNULL(L) L = null
#define QDEL_LAZYLIST(L) for(var/I in L) qdel(I); L = null;
+#define QDEL_LAZYASSOCLIST(L) for(var/K in L) qdel(L[K]); L = null;
/// ORs two lazylists together without inserting errant nulls, returning a new list and not modifying the existing lists.
#define LAZY_LISTS_OR(left_list, right_list)\
(length(left_list)\
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 55e823bb6f..275ed0aa9d 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -137,23 +137,32 @@
/obj/attackby(obj/item/I, mob/living/user, params)
return ..() || ((obj_flags & CAN_BE_HIT) && I.attack_obj(src, user))
-/mob/living/attackby(obj/item/I, mob/living/user, params)
+/mob/living/attackby(obj/item/weapon, mob/living/user, params)
if(..())
return TRUE
- if(user.a_intent == INTENT_HELP || user.a_intent == INTENT_DISARM)
- for(var/datum/surgery/S in surgeries)
- if(body_position != LYING_DOWN && S.lying_required)
+
+ if(handle_tool_treatment(weapon, user, params2list(params)))
+ return TRUE
+
+ // MUST be done AFTER wound treatment
+ if((weapon.item_flags & SURGICAL_TOOL) && user.a_intent == INTENT_HELP && attempt_initiate_surgery(weapon, src, user))
+ return TRUE
+
+ //This should really be in attack but 2 much logic doesnt call parent
+ user.changeNext_move(weapon.attack_cooldown)
+ return weapon.attack(src, user, params)
+
+/// This handles treating wounds and performing surgeries with items. It is also a hack to avoid refactoring the entire attack chain (for now)
+/mob/living/proc/handle_tool_treatment(obj/item/tool, mob/living/user, list/modifiers)
+ if(user.a_intent == INTENT_HELP)
+ for(var/datum/surgery/active_surgery in surgeries)
+ if(body_position != LYING_DOWN && active_surgery.lying_required)
continue
- if(!S.self_operable && user == src)
+ if(!active_surgery.self_operable && user == src)
continue
- if(S.next_step(user, params2list(params)))
+ if(active_surgery.next_step(user, modifiers))
return TRUE
- if((I.item_flags & SURGICAL_TOOL) && user.a_intent == INTENT_HELP)
- if(attempt_initiate_surgery(I, src, user))
- return TRUE
- //This should really be in attack but 2 much logic doesnt call parent
- user.changeNext_move(I.attack_cooldown)
- return I.attack(src, user, params)
+ return FALSE
/mob/living/attack_hand(mob/living/user, list/modifiers)
if(..())
@@ -175,9 +184,12 @@
* * params - Click params of this attack
*/
/obj/item/proc/attack(mob/living/target_mob, mob/living/user, params)
- if(SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, target_mob, user, params) & COMPONENT_ITEM_NO_ATTACK)
- return
- SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, target_mob, user, params)
+ var/signal_return = SEND_SIGNAL(src, COMSIG_ITEM_ATTACK, target_mob, user, params) | SEND_SIGNAL(user, COMSIG_MOB_ITEM_ATTACK, target_mob, user, params)
+ if(signal_return & COMPONENT_CANCEL_ATTACK_CHAIN)
+ return TRUE
+ if(signal_return & COMPONENT_SKIP_ATTACK)
+ return FALSE
+
if(item_flags & NOBLUDGEON)
return
diff --git a/code/controllers/subsystem/wounds.dm b/code/controllers/subsystem/wounds.dm
new file mode 100644
index 0000000000..dfe8e43832
--- /dev/null
+++ b/code/controllers/subsystem/wounds.dm
@@ -0,0 +1,167 @@
+SUBSYSTEM_DEF(wounds)
+ name = "Wounds"
+ init_order = INIT_ORDER_WOUNDS
+ flags = SS_NO_FIRE
+
+ /// A "chronological" list of wound severities, starting at the least severe.
+ var/static/list/severities_chronological = list(
+ "[WOUND_SEVERITY_TRIVIAL]",
+ "[WOUND_SEVERITY_MODERATE]",
+ "[WOUND_SEVERITY_SEVERE]",
+ "[WOUND_SEVERITY_CRITICAL]"
+ )
+
+ /// A assoc list of BIO_ define to EXTERIOR/INTERIOR defines.
+ /// This is where the interior/exterior state of a given biostate is set.
+ /// Note that not all biostates are guaranteed to be one of these - and in fact, many are not
+ /// IMPORTANT NOTE: All keys are stored as text and must be converted via text2num
+ var/static/list/bio_state_anatomy = list(
+ "[BIO_WIRED]" = ANATOMY_EXTERIOR,
+ "[BIO_METAL]" = ANATOMY_INTERIOR,
+ "[BIO_FLESH]" = ANATOMY_EXTERIOR,
+ "[BIO_BONE]" = ANATOMY_INTERIOR,
+ )
+
+ /// Associated list of wound types and their pregen data.
+ var/list/datum/wound_pregen_data/pregen_data
+
+ // A wound series "collection" is merely a way for us to track what is in what series, and what their types are.
+ // Without this, we have no centralized way to determine what type is in what series outside of iterating over every pregen data.
+
+ /// A branching assoc list of (series -> list(severity -> list(typepath -> weight))). Allows you to say "I want a generic slash wound",
+ /// then "Of severity 2", and get a wound of that description - via get_corresponding_wound_type()
+ /// Series: A generic wound_series, such as WOUND_SERIES_BONE_BLUNT_BASIC
+ /// Severity: Any wounds held within this will be of this severity.
+ /// Typepath, Weight: Merely a pairing of a given typepath to its weight, held for convenience in pickweight.
+ var/list/series_collection
+
+ /// A branching assoc list of (wounding_type -> list(wound_series)).
+ /// Allows for determining of which wound series are caused by what.
+ var/static/list/types_to_series = list(
+ WOUND_BLUNT = list(
+ WOUND_SERIES_BONE_BLUNT_BASIC,
+ WOUND_SERIES_METAL_BUCKLING,
+ WOUND_SERIES_FLESH_MUSCLE,
+ ),
+ WOUND_SLASH = list(
+ WOUND_SERIES_FLESH_SLASH_BLEED,
+ WOUND_SERIES_WIRED_ELECTRICAL,
+ ),
+ WOUND_BURN = list(
+ WOUND_SERIES_FLESH_BURN_BASIC,
+ WOUND_SERIES_METAL_HEAT_WARPING,
+ WOUND_SERIES_WIRED_ELECTRICAL,
+ ),
+ WOUND_PIERCE = list(
+ WOUND_SERIES_FLESH_PUNCTURE_BLEED,
+ WOUND_SERIES_WIRED_ELECTRICAL,
+ ),
+ )
+
+/datum/controller/subsystem/wounds/Initialize(timeofday)
+ generate_wound_static_data()
+ generate_wound_series_collection()
+ return ..()
+
+/// Constructs [all_wound_pregen_data] by iterating through a typecache of pregen data, ignoring abstract types, and instantiating the rest.
+/datum/controller/subsystem/wounds/proc/generate_wound_static_data()
+ var/list/datum/wound_pregen_data/all_pregen_data = list()
+
+ for (var/datum/wound_pregen_data/iterated_path as anything in typecacheof(path = /datum/wound_pregen_data, ignore_root_path = TRUE))
+ if (initial(iterated_path.abstract))
+ continue
+
+ if (!isnull(all_pregen_data[initial(iterated_path.wound_path_to_generate)]))
+ stack_trace("pre-existing pregen data for [initial(iterated_path.wound_path_to_generate)] when [iterated_path] was being considered: [all_pregen_data[initial(iterated_path.wound_path_to_generate)]]. \
+ this is definitely a bug, and is probably because one of the two pregen data have the wrong wound typepath defined. [iterated_path] will not be instantiated")
+
+ continue
+
+ var/datum/wound_pregen_data/new_data = new iterated_path
+ LAZYSET(pregen_data, new_data.wound_path_to_generate, new_data)
+
+// Series -> severity -> type -> weight
+/// Generates [wound_series_collections] by iterating through all pregen_data. Refer to the mentioned list for documentation
+/datum/controller/subsystem/wounds/proc/generate_wound_series_collection()
+ for (var/datum/wound/wound_typepath as anything in typecacheof(/datum/wound, FALSE, TRUE))
+ var/datum/wound_pregen_data/data = pregen_data[wound_typepath]
+ if (!data)
+ continue
+
+ if (data.abstract)
+ stack_trace("somehow, a abstract wound_pregen_data instance ([data.type]) was instantiated and made it to generate_wound_series_collection()! \
+ i literally have no idea how! please fix this!")
+ continue
+
+ var/series = data.wound_series
+ var/list/datum/wound/series_list = series_collection[series]
+ if (isnull(series_list))
+ series_collection[series] = list()
+ series_list = series_collection[series]
+
+ var/severity = "[(initial(wound_typepath.severity))]"
+ var/list/datum/wound/severity_list = series_list[severity]
+ if (isnull(severity_list))
+ series_list[severity] = list()
+ severity_list = series_list[severity]
+
+ severity_list[wound_typepath] = data.weight
+
+/**
+ * Searches through all wounds for any of proper type, series, and biostate, and then returns a single one via pickweight.
+ * Is able to discern between, say, a flesh slash wound, and a metallic slash wound, and will return the respective one for the provided limb.
+ *
+ * The severity_max and severity_pick_mode args mostly exist in case you want a wound in a series that may not have your ideal severity wound, as it lets you
+ * essentially set a "fallback", where if your ideal wound doesnt exist, it'll still return something, trying to get closest to your ideal severity.
+ *
+ * Generally speaking, if you want a critical/severe/moderate wound, you should set severity_min to WOUND_SEVERITY_MODERATE, severity_max to your ideal wound,
+ * and severity_pick_mode to WOUND_PICK_HIGHEST_SEVERITY - UNLESS you for some reason want the LOWEST severity, in which case you should set
+ * severity_max to the highest wound you're willing to tolerate, and severity_pick_mode to WOUND_PICK_LOWEST_SEVERITY.
+ *
+ * Args:
+ * * list/wounding_types: A list of wounding_types. Only wounds that accept these wound types will be considered.
+ * * obj/item/bodypart/part: The limb we are considering. Extremely important for biostates.
+ * * severity_min: The minimum wound severity we will search for.
+ * * severity_max = severity_min: The maximum wound severity we will search for.
+ * * severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY: The "pick mode" we will use when considering multiple wounds of acceptable severity. See the above defines.
+ * * random_roll = TRUE: If this is considered a "random" consideration. If true, only wounds that can be randomly generated will be considered.
+ * * duplicates_allowed = FALSE: If exact duplicates of a given wound on part are tolerated. Useful for simply getting a path and not instantiating.
+ * * care_about_existing_wounds = TRUE: If we iterate over wounds to see if any are above or at a given wounds severity, and disregard it if any are. Useful for simply getting a path and not instantiating.
+ *
+ * Returns:
+ * A randomly picked wound typepath meeting all the above criteria and being applicable to the part's biotype - or null if there were none.
+ */
+/datum/controller/subsystem/wounds/proc/get_corresponding_wound_type(list/wounding_types, obj/item/bodypart/part, severity_min, severity_max = severity_min, severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY, random_roll = TRUE, duplicates_allowed = FALSE, care_about_existing_wounds = TRUE)
+ RETURN_TYPE(/datum/wound) // note that just because its set to return this doesnt mean its non-nullable
+
+ var/list/wounding_type_list = list()
+ for (var/wounding_type as anything in wounding_types)
+ wounding_type_list |= SSwounds.types_to_series[wounding_type]
+ if (!length(wounding_type_list))
+ return null
+
+ var/list/datum/wound/paths_to_pick_from = list()
+ for (var/series as anything in shuffle(wounding_type_list))
+ var/list/severity_list = series_collection[series]
+ if (!length(severity_list))
+ continue
+
+ var/picked_severity
+ for (var/severity_text as anything in shuffle(severities_chronological))
+ var/severity = text2num(severity_text)
+ if (severity > severity_min || severity < severity_max)
+ continue
+
+ if (isnull(picked_severity) || ((severity_pick_mode == WOUND_PICK_HIGHEST_SEVERITY && severity > picked_severity) || (severity_pick_mode == WOUND_PICK_LOWEST_SEVERITY && severity < picked_severity)))
+ picked_severity = severity
+
+ var/list/wound_typepaths = severity_list["[picked_severity]"]
+ if (!length(wound_typepaths))
+ continue
+
+ for (var/datum/wound/iterated_path as anything in wound_typepaths)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[iterated_path]
+ if (pregen_data.can_be_applied_to(part, wounding_types, random_roll = random_roll, duplicates_allowed = duplicates_allowed, care_about_existing_wounds = care_about_existing_wounds))
+ paths_to_pick_from[iterated_path] = wound_typepaths[iterated_path]
+
+ return pick_weight(paths_to_pick_from) // we found our winners!
diff --git a/code/datums/components/butchering.dm b/code/datums/components/butchering.dm
index f0686579e4..ec0c0f958b 100644
--- a/code/datums/components/butchering.dm
+++ b/code/datums/components/butchering.dm
@@ -97,10 +97,8 @@
H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD, wound_bonus = CANT_WOUND)
var/obj/item/bodypart/slit_throat = H.get_bodypart(BODY_ZONE_HEAD)
- if(slit_throat)
- var/datum/wound/slash/critical/screaming_through_a_slit_throat = new
- screaming_through_a_slit_throat.apply_wound(slit_throat)
- H.apply_status_effect(/datum/status_effect/neck_slice)
+ if (H.cause_wound_of_type_and_severity(WOUND_SLASH, slit_throat, WOUND_SEVERITY_CRITICAL))
+ H.apply_status_effect(/datum/status_effect/neck_slice)
/datum/component/butchering/proc/Butcher(mob/living/butcher, mob/living/meat)
var/turf/T = meat.drop_location()
diff --git a/code/datums/components/embedded.dm b/code/datums/components/embedded.dm
index 70fc4308f4..ecda3afc45 100644
--- a/code/datums/components/embedded.dm
+++ b/code/datums/components/embedded.dm
@@ -97,7 +97,8 @@
if(harmful)
victim.throw_alert("embeddedobject", /atom/movable/screen/alert/embeddedobject)
playsound(victim,'sound/weapons/bladeslice.ogg', 40)
- weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
+ if(limb.can_bleed())
+ weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
damage += weapon.w_class * impact_pain_mult
SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm
index 5ceffc3cb7..feedd27667 100644
--- a/code/datums/components/pellet_cloud.dm
+++ b/code/datums/components/pellet_cloud.dm
@@ -298,13 +298,13 @@
if(isbodypart(target))
hit_part = target
target = hit_part.owner
- if(wound_info_by_part[hit_part] && (initial(P.damage_type) == BRUTE || initial(P.damage_type) == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds)
+ var/damage_type = initial(P.damage_type)
+ if(wound_info_by_part[hit_part] && (damage_type == BRUTE || damage_type == BURN)) // so a cloud of disablers that deal stamina don't inadvertently end up causing burn wounds)
var/damage_dealt = wound_info_by_part[hit_part][CLOUD_POSITION_DAMAGE]
var/w_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_W_BONUS]
var/bw_bonus = wound_info_by_part[hit_part][CLOUD_POSITION_BW_BONUS]
- var/wound_type = (initial(P.damage_type) == BRUTE) ? WOUND_BLUNT : WOUND_BURN // sharpness is handled in the wound rolling
- wound_info_by_part[hit_part] = null
- hit_part.painless_wound_roll(wound_type, damage_dealt, w_bonus, bw_bonus, initial(P.sharpness))
+ wound_info_by_part -= hit_part
+ hit_part.wound_roll((damage_type == BRUTE) ? damage_dealt : 0, (damage_type == BURN) ? damage_dealt : 0, w_bonus, bw_bonus, initial(P.sharpness))
if(num_hits > 1)
target.visible_message(span_danger("[target] is hit by [num_hits] [proj_name]s[hit_part ? " in the [hit_part.name]" : ""]!"), null, null, COMBAT_MESSAGE_RANGE, target)
diff --git a/code/datums/elements/kneecapping.dm b/code/datums/elements/kneecapping.dm
index 9df4782a0f..975161c0d6 100644
--- a/code/datums/elements/kneecapping.dm
+++ b/code/datums/elements/kneecapping.dm
@@ -90,9 +90,10 @@
span_danger("You swing \the [weapon] at [target]'s kneecaps!"),
)
- var/datum/wound/blunt/severe/severe_wound_type = /datum/wound/blunt/severe
- var/datum/wound/blunt/critical/critical_wound_type = /datum/wound/blunt/critical
- leg.receive_damage(brute = weapon.force, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10))
+ var/min_wound = leg.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = weapon)
+ var/max_wound = leg.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = weapon)
+
+ leg.receive_damage(brute = weapon.force, wound_bonus = rand(min_wound, max_wound + 10))
log_combat(attacker, target, "broke the kneecaps of", weapon)
target.update_damage_overlays()
attacker.do_attack_animation(target, used_item = weapon)
diff --git a/code/datums/elements/robotic_heal.dm b/code/datums/elements/robotic_heal.dm
new file mode 100644
index 0000000000..978efd5220
--- /dev/null
+++ b/code/datums/elements/robotic_heal.dm
@@ -0,0 +1,93 @@
+/datum/element/robotic_heal
+ element_flags = ELEMENT_BESPOKE
+ argument_hash_start_idx = 2
+ /// Brute damage healed by the attached item.
+ var/brute_heal
+ /// Burn damage healed by the attached item.
+ var/burn_heal
+ /// Delay when self-repairing with this item.
+ var/self_delay
+ /// Delay when repairing others with this item.
+ var/other_delay
+ /// The message when using this item to heal.
+ var/heal_message
+
+/datum/element/robotic_heal/Attach(datum/target, brute_heal = 0, burn_heal = 0, self_delay = 3 SECONDS, other_delay = 1 SECONDS)
+ if(!isitem(target))
+ return ELEMENT_INCOMPATIBLE
+ src.brute_heal = brute_heal
+ src.burn_heal = burn_heal
+ src.self_delay = self_delay
+ src.other_delay = other_delay
+ if(!heal_message) // This only needs to be set once for its first attached item
+ if(brute_heal && burn_heal)
+ heal_message = "dents and burnt wires in"
+ else if(brute_heal)
+ heal_message = "dents on"
+ else
+ heal_message = "burnt wires in"
+ RegisterSignal(target, COMSIG_ITEM_ATTACK, PROC_REF(on_item_attack))
+ return ..()
+
+/datum/element/robotic_heal/Detach(datum/source, ...)
+ UnregisterSignal(source, COMSIG_ITEM_ATTACK)
+ return ..()
+
+/// Intercepts [mob/living/attack()] and tries to heal a robotic limb if possible.
+/datum/element/robotic_heal/proc/on_item_attack(obj/item/tool, mob/living/patient, mob/user, params)
+ SIGNAL_HANDLER
+
+ if(user.a_intent != INTENT_HELP)
+ return NONE
+
+ if(!iscarbon(patient))
+ return NONE
+
+ var/obj/item/bodypart/part_to_repair = patient.get_bodypart(user.zone_selected)
+ if(!part_to_repair)
+ to_chat(user, span_warning("[patient]'s [parse_zone(user.zone_selected)] is missing!"))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(!IS_ROBOTIC_LIMB(part_to_repair))
+ to_chat(user, span_warning("[tool] can't repair this!"))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(!(brute_heal && part_to_repair.brute_dam > 0) && !(burn_heal && part_to_repair.burn_dam > 0))
+ to_chat(user, span_warning("[patient]'s [part_to_repair.plaintext_zone] is already in good condition!"))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(part_to_repair.get_damage() <= part_to_repair.wound_integrity_loss)
+ to_chat(user, span_warning("[patient]'s [part_to_repair.plaintext_zone] cannot be repaired any further!"))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(!patient.is_exposed(user))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ if(!tool.tool_start_check(user, patient, amount = 1))
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+ user.visible_message(
+ span_notice("[user] starts to fix some of the [heal_message] [patient]'s [part_to_repair.plaintext_zone]"),
+ span_notice("You start to fix some of the [heal_message] [patient]'s [part_to_repair.plaintext_zone]."),
+ )
+
+ INVOKE_ASYNC(src, PROC_REF(item_heal_robotic), tool, patient, user, part_to_repair, patient == user ? self_delay : other_delay)
+ return COMPONENT_CANCEL_ATTACK_CHAIN
+
+/**
+ * Heal a robotic body part on a mob
+ */
+/datum/element/robotic_heal/proc/item_heal_robotic(obj/item/tool, mob/living/carbon/patient, mob/user, obj/item/bodypart/part_to_repair, delay)
+ if(!tool.use_tool(patient, user, delay, amount = 1, volume = 50, extra_checks = CALLBACK(patient, TYPE_PROC_REF(/mob/living, is_exposed), user, user.zone_selected)))
+ return
+
+ if(QDELETED(part_to_repair))
+ to_chat(user, span_warning("[patient]'s [part_to_repair.plaintext_zone] is gone!"))
+ return
+
+ part_to_repair.heal_damage(brute_heal, burn_heal, FALSE, BODYTYPE_ROBOTIC)
+ patient.update_damage_overlays()
+ user.visible_message(
+ span_notice("[user] fixes some of the [heal_message] [patient]'s [part_to_repair.plaintext_zone]."),
+ span_notice("You fix some of the [heal_message] [patient]'s [part_to_repair.plaintext_zone]"),
+ )
diff --git a/code/datums/status_effects/debuffs.dm b/code/datums/status_effects/debuffs.dm
index f8fdf90045..f0455e32ee 100644
--- a/code/datums/status_effects/debuffs.dm
+++ b/code/datums/status_effects/debuffs.dm
@@ -318,24 +318,34 @@
id = "neck_slice"
status_type = STATUS_EFFECT_UNIQUE
alert_type = null
- duration = -1
+ duration = STATUS_EFFECT_PERMANENT
-/datum/status_effect/neck_slice/tick()
- var/mob/living/carbon/human/H = owner
- var/obj/item/bodypart/throat = H.get_bodypart(BODY_ZONE_HEAD)
- if(H.stat == DEAD || !throat)
- H.remove_status_effect(/datum/status_effect/neck_slice)
+/datum/status_effect/neck_slice/on_apply()
+ if(!ishuman(owner))
+ return FALSE
+ if(!owner.get_bodypart(BODY_ZONE_HEAD))
+ return FALSE
+ return TRUE
+
+/datum/status_effect/neck_slice/tick(seconds_between_ticks)
+ var/obj/item/bodypart/throat = owner.get_bodypart(BODY_ZONE_HEAD)
+ if(owner.stat == DEAD || !throat) // they can lose their head while it's going.
+ qdel(src)
+ return
var/still_bleeding = FALSE
- for(var/datum/wound/W as anything in throat.wounds)
- if(W.wound_type == WOUND_SLASH && W.severity > WOUND_SEVERITY_MODERATE)
+ for(var/datum/wound/bleeding_thing as anything in throat.wounds)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[bleeding_thing.type]
+
+ if(pregen_data.wounding_types_valid(throat, list(WOUND_SLASH)) && bleeding_thing.severity > WOUND_SEVERITY_MODERATE && bleeding_thing.blood_flow > 0)
still_bleeding = TRUE
break
if(!still_bleeding)
- H.remove_status_effect(/datum/status_effect/neck_slice)
+ qdel(src)
+ return
if(prob(10))
- H.emote(pick("gasp", "gag", "choke"))
+ owner.emote(pick("gasp", "gag", "choke"))
/mob/living/proc/apply_necropolis_curse(set_curse)
var/datum/status_effect/necropolis_curse/C = has_status_effect(STATUS_EFFECT_NECROPOLIS_CURSE)
diff --git a/code/datums/status_effects/debuffs/fire_stacks.dm b/code/datums/status_effects/debuffs/fire_stacks.dm
index b7aee1f2c5..f2e34a986a 100644
--- a/code/datums/status_effects/debuffs/fire_stacks.dm
+++ b/code/datums/status_effects/debuffs/fire_stacks.dm
@@ -180,28 +180,20 @@
if(!no_protection)
if(thermal_protection >= FIRE_IMMUNITY_MAX_TEMP_PROTECT)
+ SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "on_fire")
return
if(thermal_protection >= FIRE_SUIT_MAX_TEMP_PROTECT)
+ SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire)
victim.adjust_bodytemperature(5.5 * seconds_between_ticks)
return
victim.adjust_bodytemperature((victim.dna.species.bodytemp_heating_rate_max + (stacks * 12)) * 0.5 * seconds_between_ticks)
- victim.apply_damage((stacks * 0.5), FIRE, blocked = victim.run_armor_check(null, "fire", armour_penetration=stacks*5, silent=TRUE), spread_damage = TRUE)
- if(SPT_PROB(20, seconds_between_ticks))
- var/obj/item/bodypart/it_burns = victim.get_bodypart(pick(BODY_ZONE_L_ARM,BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG, BODY_ZONE_CHEST, BODY_ZONE_HEAD))
- if(it_burns)
- var/datum/wound/burn_injury
- switch(stacks)
- if(1 to 3)
- EMPTY_BLOCK_GUARD
- if(3 to 7)
- burn_injury = new /datum/wound/burn/moderate
- if(7 to 14)
- burn_injury = new /datum/wound/burn/severe
- if(14 to 20)
- burn_injury = new /datum/wound/burn/critical
- if(burn_injury)
- burn_injury.apply_wound(it_burns)
+ if(!victim.apply_damage((stacks * 0.5), BURN, blocked = victim.run_armor_check(null, FIRE, armour_penetration=stacks*5, silent=TRUE), spread_damage = TRUE))
+ return
+ if(SPT_PROB(50, seconds_between_ticks))
+ var/obj/item/bodypart/it_burns = victim.get_random_bodypart()
+ if(it_burns) // apply_damage doesn't cause wounds without a selected bodypart, so we do this manually here
+ it_burns.wound_roll(0, stacks * min(victim.bodytemperature / FIRE_MINIMUM_TEMPERATURE_TO_EXIST, 2), no_dismember = TRUE)
SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "on_fire", /datum/mood_event/on_fire)
/**
diff --git a/code/datums/status_effects/wound_effects.dm b/code/datums/status_effects/wound_effects.dm
index 47e43ffc23..4455424202 100644
--- a/code/datums/status_effects/wound_effects.dm
+++ b/code/datums/status_effects/wound_effects.dm
@@ -11,21 +11,39 @@
/datum/status_effect/determined/on_apply()
. = ..()
- owner.visible_message(
- span_danger("[owner]'s body tenses up noticeably, gritting against [owner.p_their()] pain!"),
- span_notice("Your senses sharpen as your body tenses up from the wounds you've sustained!"),
- vision_distance = COMBAT_MESSAGE_RANGE,
- )
+ if(owner.mob_biotypes & MOB_ROBOTIC)
+ owner.visible_message(
+ span_danger("[owner]'s cooling fans spin up far louder than usual."),
+ span_notice("Acceptable damage threshold exceeded. Emergency self-preservation protocol initiated."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ )
+ else
+ owner.visible_message(
+ span_danger("[owner]'s body tenses up noticeably, gritting against [owner.p_their()] pain!"),
+ span_notice("Your senses sharpen as your body tenses up from the wounds you've sustained!"),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ )
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
human_owner.physiology.bleed_mod *= WOUND_DETERMINATION_BLEED_MOD
/datum/status_effect/determined/on_remove()
- owner.visible_message(
- span_danger("[owner]'s body slackens noticeably!"),
- span_warning("Your adrenaline rush dies off, and the pain from your wounds come aching back in..."),
- vision_distance = COMBAT_MESSAGE_RANGE,
- )
+ if(owner.mob_biotypes & MOB_ROBOTIC)
+ var/mob/living/carbon/carbon_owner = owner
+ if(!iscarbon(owner))
+ stack_trace("Determination status effect applied to non-carbon [owner] of type [owner.type]")
+ carbon_owner = null
+ owner.visible_message(
+ span_danger("[owner]'s cooling fans suddenly quiet down."),
+ span_notice("Emergency self-preservation protocol concluded. [rand(2, 100 * LAZYLEN(carbon_owner?.all_wounds))] new errors to report."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ )
+ else
+ owner.visible_message(
+ span_danger("[owner]'s body slackens noticeably!"),
+ span_warning("Your adrenaline rush dies off, and the pain from your wounds come aching back in..."),
+ vision_distance = COMBAT_MESSAGE_RANGE,
+ )
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
human_owner.physiology.bleed_mod /= WOUND_DETERMINATION_BLEED_MOD
@@ -144,9 +162,9 @@
alert_type = NONE
/datum/status_effect/wound/on_creation(mob/living/new_owner, incoming_wound)
- . = ..()
linked_wound = incoming_wound
linked_limb = linked_wound.limb
+ return ..()
/datum/status_effect/wound/on_remove()
linked_wound = null
@@ -166,67 +184,68 @@
if(W == linked_wound)
qdel(src)
-// bones
-/datum/status_effect/wound/blunt
+/datum/status_effect/wound/nextmove_modifier()
+ var/mob/living/carbon/status_owner = owner
+
+ if(status_owner.get_active_hand() == linked_limb)
+ return linked_wound.get_action_delay_mult()
+
+ return ..()
-/datum/status_effect/wound/blunt/on_apply()
+// bones
+/datum/status_effect/wound/blunt/bone
+/*
+/datum/status_effect/wound/blunt/bone/on_apply()
. = ..()
- RegisterSignal(owner, COMSIG_MOB_SWAP_HANDS, PROC_REF(on_swap_hands))
- on_swap_hands()
+ if(.)
+ RegisterSignal(owner, COMSIG_MOB_SWAP_HANDS, PROC_REF(on_swap_hands))
+ on_swap_hands()
-/datum/status_effect/wound/blunt/on_remove()
+/datum/status_effect/wound/blunt/bone/on_remove()
. = ..()
UnregisterSignal(owner, COMSIG_MOB_SWAP_HANDS)
var/mob/living/carbon/wound_owner = owner
- wound_owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/blunt_wound)
+ wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/blunt_wound)
-/datum/status_effect/wound/blunt/proc/on_swap_hands()
+/datum/status_effect/wound/blunt/bone/proc/on_swap_hands()
SIGNAL_HANDLER
var/mob/living/carbon/wound_owner = owner
if(wound_owner.get_active_hand() == linked_limb)
- wound_owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/blunt_wound, (linked_wound.interaction_efficiency_penalty - 1))
+ wound_owner.add_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/blunt_wound, (linked_wound.interaction_efficiency_penalty - 1))
else
- wound_owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/blunt_wound)
-
-/datum/status_effect/wound/blunt/nextmove_modifier()
- var/mob/living/carbon/C = owner
-
- if(C.get_active_hand() == linked_limb)
- return linked_wound.interaction_efficiency_penalty
-
- return 1
-
+ wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/status_effect/blunt_wound)
+*/
// blunt
-/datum/status_effect/wound/blunt/moderate
+/datum/status_effect/wound/blunt/bone/moderate
id = "disjoint"
-/datum/status_effect/wound/blunt/severe
+/datum/status_effect/wound/blunt/bone/severe
id = "hairline"
-/datum/status_effect/wound/blunt/critical
+/datum/status_effect/wound/blunt/bone/critical
id = "compound"
// slash
-/datum/status_effect/wound/slash/moderate
+/datum/status_effect/wound/slash/flesh/moderate
id = "abrasion"
-/datum/status_effect/wound/slash/severe
+/datum/status_effect/wound/slash/flesh/severe
id = "laceration"
-/datum/status_effect/wound/slash/critical
+/datum/status_effect/wound/slash/flesh/critical
id = "avulsion"
// pierce
-/datum/status_effect/wound/pierce/moderate
+/datum/status_effect/wound/pierce/bleed/moderate
id = "breakage"
-/datum/status_effect/wound/pierce/severe
+/datum/status_effect/wound/pierce/bleed/severe
id = "puncture"
-/datum/status_effect/wound/pierce/critical
+/datum/status_effect/wound/pierce/bleed/critical
id = "rupture"
// burns
-/datum/status_effect/wound/burn/moderate
+/datum/status_effect/wound/burn/flesh/moderate
id = "seconddeg"
-/datum/status_effect/wound/burn/severe
+/datum/status_effect/wound/burn/flesh/severe
id = "thirddeg"
-/datum/status_effect/wound/burn/critical
+/datum/status_effect/wound/burn/flesh/critical
id = "fourthdeg"
// muscle
diff --git a/code/datums/wounds/_wound_static_data.dm b/code/datums/wounds/_wound_static_data.dm
new file mode 100644
index 0000000000..df5a146d16
--- /dev/null
+++ b/code/datums/wounds/_wound_static_data.dm
@@ -0,0 +1,188 @@
+/// A singleton datum that holds pre-gen and static data about a wound. Each wound datum should have a corresponding wound_pregen_data.
+/datum/wound_pregen_data
+ /// The typepath of the wound we will be handling and storing data of. NECESSARY IF THIS IS A NON-ABSTRACT TYPE!
+ var/datum/wound/wound_path_to_generate
+
+ /// Will this be instantiated?
+ var/abstract = FALSE
+
+ /// If true, our wound can be selected in ordinary wound rolling. If this is set to false, our wound can only be directly instantiated by use of specific typepath.
+ var/can_be_randomly_generated = TRUE
+
+ /// A list of biostates a limb must have to receive our wound, in wounds.dm.
+ var/required_limb_biostate
+ /// If false, we will check if the limb has all of our required biostates instead of just any.
+ var/require_any_biostate = FALSE
+
+ /// If false, we will iterate through wounds on a given limb, and if any match our type, we wont add our wound.
+ var/duplicates_allowed = FALSE
+
+ /// If we require BIO_BLOODED, we will not add our wound if this is true and the limb cannot bleed.
+ var/ignore_cannot_bleed = TRUE // a lot of bleed wounds should still be applied for purposes of mangling flesh
+
+ /// A list of bodyzones this is incompatible with.
+ var/list/excluded_zones = list()
+
+ /// The types of attack that can generate this wound. E.g. WOUND_SLASH = A sharp attack can cause this, WOUND_BLUNT = an attack with no sharpness/an attack with sharpness against a limb with mangled exterior can cause this.
+ var/list/required_wounding_types
+ /// [required_wounding_types] but requires the associated anatomy to be mangled first.
+ var/list/mangled_wounding_types
+ /// If true, this wound can only be generated by all [required_wounding_types] at once, not just any.
+ var/match_all_wounding_types = FALSE
+
+ /// The weight that will be used if, by the end of wound selection, there are multiple valid wounds. This will be inserted into pick_weight, so use integers.
+ var/weight = WOUND_DEFAULT_WEIGHT
+
+ /// The minimum injury roll a attack must get to generate us. Affected by our wound's threshold_penalty and series_threshold_penalty, as well as the attack's wound_bonus. See check_wounding_mods().
+ var/threshold_minimum
+
+ /// The series of wounds this is in. See wounds.dm (the defines file) for a more detailed explanation - but tldr is that no 2 wounds of the same series can be on a limb.
+ var/wound_series
+
+ /// If true, we will attempt to, during a random wound roll, overpower and remove other wound typepaths from the possible wounds list using [competition_mode] and [overpower_wounds_of_even_severity].
+ var/compete_for_wounding = TRUE
+ /// The competition mode with which we will remove other wounds from a possible wound roll assuming [compete_for_wounding] is TRUE. See wounds.dm, the defines file, for more information on what these do.
+ var/competition_mode = WOUND_COMPETITION_OVERPOWER_LESSERS
+ /// If this and [compete_for_wounding] is true, we will remove wounds of an even severity to us during a random wound roll.
+ var/overpower_wounds_of_even_severity = FALSE
+
+
+/datum/wound_pregen_data/New()
+ . = ..()
+
+ if (!abstract)
+ if (required_limb_biostate == null)
+ stack_trace("required_limb_biostate null - please set it! occured on: [src.type]")
+ if (wound_path_to_generate == null)
+ stack_trace("wound_path_to_generate null - please set it! occured on: [src.type]")
+
+// this proc is the primary reason this datum exists - a singleton instance so we can always run this proc even without the wound existing
+/**
+ * Args:
+ * * obj/item/bodypart/limb: The limb we are considering.
+ * * wound_type: The type of the "wound acquisition attempt". Example: A slashing attack cannot proc a blunt wound, so wound_type = WOUND_SLASH would
+ * fail if we expect WOUND_BLUNT. Defaults to the wound type we expect.
+ * * datum/wound/old_wound: If we would replace a wound, this would be said wound. Nullable.
+ * * random_roll = FALSE: If this is in the context of a random wound generation, and this wound wasn't specifically checked.
+ *
+ * Returns:
+ * FALSE if the limb cannot be wounded, if wound_type is not ours, if we have a higher severity wound already in our series,
+ * if we have a biotype mismatch, if the limb isnt in a viable zone, or if theres any duplicate wound types.
+ * TRUE otherwise.
+ */
+/datum/wound_pregen_data/proc/can_be_applied_to(obj/item/bodypart/limb, list/suggested_wounding_types = required_wounding_types, datum/wound/old_wound, random_roll = FALSE, duplicates_allowed = src.duplicates_allowed, care_about_existing_wounds = TRUE)
+ SHOULD_BE_PURE(TRUE)
+
+ if(!istype(limb) || !limb.owner)
+ return FALSE
+
+ if(random_roll && !can_be_randomly_generated)
+ return FALSE
+
+ if(HAS_TRAIT(limb.owner, TRAIT_NEVER_WOUNDED) || (limb.owner.status_flags & GODMODE))
+ return FALSE
+
+ if(!wounding_types_valid(limb, suggested_wounding_types))
+ return FALSE
+
+ if(care_about_existing_wounds)
+ for(var/datum/wound/preexisting_wound as anything in limb.wounds)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[preexisting_wound.type]
+ if(pregen_data.wound_series == wound_series)
+ if(preexisting_wound.severity >= wound_path_to_generate::severity)
+ return FALSE
+
+ if(!ignore_cannot_bleed && ((required_limb_biostate & BIO_BLOODED) && !limb.can_bleed()))
+ return FALSE
+
+ if(!biostate_valid(limb.biological_state))
+ return FALSE
+
+ if(limb.body_zone in excluded_zones)
+ return FALSE
+
+ // we accept promotions and demotions, but no point in redundancy. This should have already been checked wherever the wound was rolled and applied for (see: bodypart damage code), but we do an extra check
+ // in case we ever directly add wounds
+ if(!duplicates_allowed)
+ for (var/datum/wound/preexisting_wound as anything in limb.wounds)
+ if (preexisting_wound.type == wound_path_to_generate && (preexisting_wound != old_wound))
+ return FALSE
+ return TRUE
+
+/// Returns true if we have the given biostates, or any biostate in it if check_for_any is true. False otherwise.
+/datum/wound_pregen_data/proc/biostate_valid(biostate)
+ if (require_any_biostate)
+ if (!(biostate & required_limb_biostate))
+ return FALSE
+ else if (!((biostate & required_limb_biostate) == required_limb_biostate)) // check for all
+ return FALSE
+
+ return TRUE
+
+/**
+ * A simple getter for [weight], with arguments supplied to allow custom behavior.
+ *
+ * Args:
+ * * obj/item/bodypart/limb: The limb we are contemplating being added to. Nullable.
+ * * list/wounding_types: The amount of damage for each wounding type. Nullable.
+ * * attack_direction: The direction of the attack that'd cause us. Nullable.
+ * * damage_source: The entity that would cause us. Nullable.
+ *
+ * Returns:
+ * Our weight.
+ */
+/datum/wound_pregen_data/proc/get_weight(obj/item/bodypart/limb, list/wounding_types, attack_direction, damage_source)
+ return weight
+
+/// Returns TRUE if we use WOUND_ALL, or we require all types and have all/if we require any and have any, FALSE otherwise.
+/datum/wound_pregen_data/proc/wounding_types_valid(obj/item/bodypart/limb, list/suggested_wounding_types)
+ if (WOUND_ALL in required_wounding_types)
+ return TRUE
+ if (!length(suggested_wounding_types))
+ return FALSE
+
+ for(var/iter_wounding_type as anything in suggested_wounding_types)
+ if(!(iter_wounding_type in required_wounding_types))
+ if(match_all_wounding_types)
+ return FALSE
+ else
+ if(!match_all_wounding_types)
+ return TRUE
+
+ if(LAZYLEN(mangled_wounding_types))
+ var/mangled_state = limb.get_mangled_state()
+ for(var/iter_mangled_type as anything in mangled_wounding_types)
+ if(mangled_state & mangled_wounding_types[iter_mangled_type])
+ return TRUE
+
+ return match_all_wounding_types // if we get here, we've matched everything
+
+/**
+ * A simple getter for [threshold_minimum], with arguments supplied to allow custom behavior.
+ *
+ * Args:
+ * * obj/item/bodypart/part: The limb we are contemplating being added to.
+ * * attack_direction: The direction of the attack that'd generate us. Nullable.
+ * * damage_source: The source of the damage that'd cause us. Nullable.
+ */
+/datum/wound_pregen_data/proc/get_threshold_for(obj/item/bodypart/part, attack_direction, damage_source)
+ return threshold_minimum
+
+/// Returns a new instance of our wound datum.
+/datum/wound_pregen_data/proc/generate_instance(obj/item/bodypart/limb, ...)
+ RETURN_TYPE(/datum/wound)
+
+ return new wound_path_to_generate
+
+/datum/wound_pregen_data/Destroy(force, ...)
+ var/error_message = "[src], a singleton wound pregen data instance, was destroyed! This should not happen!"
+ if (force)
+ error_message += " NOTE: This Destroy() was called with force == TRUE. This instance will be deleted and replaced with a new one."
+ stack_trace(error_message)
+
+ if (!force)
+ return QDEL_HINT_LETMELIVE
+
+ . = ..()
+
+ SSwounds.pregen_data[wound_path_to_generate] = new src.type //recover
diff --git a/code/datums/wounds/_wounds.dm b/code/datums/wounds/_wounds.dm
index 88480101f5..5e35e6aab1 100644
--- a/code/datums/wounds/_wounds.dm
+++ b/code/datums/wounds/_wounds.dm
@@ -13,6 +13,13 @@
deciding what specific wound will be applied. I'd like to have a few different types of wounds for at least some of the choices, but I'm just doing rough generals for now. Expect polishing
*/
+#define WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS 15
+
+// Applied into wounds when they're scanned with the wound analyzer, halves time to treat them manually.
+#define TRAIT_WOUND_SCANNED "wound_scanned"
+// I dunno lol
+#define ANALYZER_TRAIT "analyzer_trait"
+
/datum/wound
/// What it's named
var/name = "Wound"
@@ -29,25 +36,25 @@
var/occur_text = ""
/// This sound will be played upon the wound being applied
var/sound_effect
+ /// The volume of [sound_effect]
+ var/sound_volume = 70
- /// Either WOUND_SEVERITY_TRIVIAL, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL, WOUND_SEVERITY_LOSS
- var/severity = WOUND_SEVERITY_MODERATE
- /// The list of wounds it belongs in, WOUND_BLUNT, WOUND_SLASH, WOUND_BURN, WOUND_MUSCLE
- var/wound_type
+ /// Either WOUND_SEVERITY_TRIVIAL, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL, WOUND_SEVERITY_LOSS. FALSE prevents it from rolling.
+ var/severity = FALSE
- /// What body zones can we affect
- var/list/viable_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST, BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ /// What body zones can we NOT affect
+ var/list/excluded_zones = list()
/// Who owns the body part that we're wounding
var/mob/living/carbon/victim = null
- /// The bodypart we're parented to
+ /// The bodypart we're parented to. Not guaranteed to be non-null, especially after/during removal or if we haven't been applied
var/obj/item/bodypart/limb = null
/// Specific items such as bandages or sutures that can try directly treating this wound
var/list/treatable_by
/// Specific items such as bandages or sutures that can try directly treating this wound only if the user has the victim in an aggressive grab or higher
var/list/treatable_by_grabbed
- /// Tools with the specified tool flag will also be able to try directly treating this wound
- var/treatable_tool
+ /// Any tools with any of the flags in this list will be usable to try directly treating this wound
+ var/list/treatable_tools
/// How long it will take to treat this wound with a standard effective tool, assuming it doesn't need surgery
var/base_treat_time = 3 SECONDS
@@ -55,6 +62,8 @@
var/interaction_efficiency_penalty = 1
/// Incoming damage on this limb will be multiplied by this, to simulate tenderness and vulnerability (mostly burns).
var/damage_mulitplier_penalty = 1
+ /// The proportion of damage on this limb that cannot be healed until this wound is removed (0-1).
+ var/limb_integrity_penalty = 0
/// If set and this wound is applied to a leg, we take this many deciseconds extra per step on this leg
var/limp_slowdown
/// If this wound has a limp_slowdown and is applied to a leg, it has this chance to limp each step
@@ -62,10 +71,10 @@
/// How much we're contributing to this limb's bleed_rate
var/blood_flow
- /// The minimum we need to roll on [/obj/item/bodypart/proc/check_wounding] to begin suffering this wound, see check_wounding_mods() for more
- var/threshold_minimum
/// 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
var/threshold_penalty
+ /// How much having this wound will add to all future check_wounding() rolls on this limb, but only for wounds of its own series
+ var/series_threshold_penalty = 0
/// If we need to process each life tick
var/processes = FALSE
@@ -78,23 +87,25 @@
var/datum/status_effect/linked_status_effect
/// If we're operating on this wound and it gets healed, we'll nix the surgery too
var/datum/surgery/attached_surgery
- /// if you're a lazy git and just throw them in cryo, the wound will go away after accumulating severity * 25 power
- var/cryo_progress
+ /// if you're a lazy git and just throw them in cryo, the wound will go away after accumulating severity * [base_regen_progress_to_qdel] power
+ var/regen_progress
+
+ /// The base amount of [regen_progress] required to have ourselves fully healed by cryo. Multiplied against severity.
+ var/base_regen_progress_to_qdel = 33
/// If we forced this wound through badmin smite, we won't count it towards the round totals
var/from_smite
+ /// The biological state required for this wound to be applied
+ var/bio_state_required = BIO_BONE | BIO_FLESH
/// What flags apply to this wound
- var/wound_flags = (FLESH_WOUND | BONE_WOUND | ACCEPTS_GAUZE)
+ var/wound_flags = ACCEPTS_GAUZE
/datum/wound/Destroy()
- if(attached_surgery)
- QDEL_NULL(attached_surgery)
+ QDEL_NULL(attached_surgery)
// destroy can call remove_wound() and remove_wound() calls qdel, so we check to make sure there's anything to remove first
- if(limb?.wounds && (src in limb.wounds))
+ if(limb)
remove_wound()
- set_limb(null)
- victim = null
return ..()
/**
@@ -109,25 +120,11 @@
* * attack_direction: For bloodsplatters, if relevant
*/
/datum/wound/proc/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null)
- if(!istype(L) || !L.owner || !(L.body_zone in viable_zones) || !IS_ORGANIC_LIMB(L) || HAS_TRAIT(L.owner, TRAIT_NEVER_WOUNDED))
+ if(!can_be_applied_to(L, old_wound))
qdel(src)
- return
+ return FALSE
- if(ishuman(L.owner))
- var/mob/living/carbon/human/H = L.owner
- if(((wound_flags & BONE_WOUND) && !(HAS_BONE in H.dna.species.species_traits)) || ((wound_flags & FLESH_WOUND) && !(HAS_FLESH in H.dna.species.species_traits)))
- qdel(src)
- return
-
- // we accept promotions and demotions, but no point in redundancy. This should have already been checked wherever the wound was rolled and applied for (see: bodypart damage code), but we do an extra check
- // in case we ever directly add wounds
- for(var/i in L.wounds)
- var/datum/wound/preexisting_wound = i
- if((preexisting_wound.type == type) && (preexisting_wound != old_wound))
- qdel(src)
- return
-
- victim = L.owner
+ set_victim(L.owner)
set_limb(L)
LAZYADD(victim.all_wounds, src)
LAZYADD(limb.wounds, src)
@@ -152,7 +149,7 @@
var/msg = span_danger("[victim]'s [limb.name] [occur_text]!")
var/vis_dist = COMBAT_MESSAGE_RANGE
- if(severity != WOUND_SEVERITY_MODERATE)
+ if(severity > WOUND_SEVERITY_MODERATE)
msg = "[msg]"
vis_dist = DEFAULT_MESSAGE_RANGE
@@ -162,27 +159,119 @@
vision_distance = vis_dist,
)
if(sound_effect)
- playsound(L.owner, sound_effect, 70 + 20 * severity, TRUE)
+ playsound(L.owner, sound_effect, sound_volume + (20 * severity), TRUE)
wound_injury(old_wound, attack_direction = attack_direction)
if(!demoted)
second_wind()
+ return TRUE
+
+/// Returns TRUE if we can be applied to the limb.
+/datum/wound/proc/can_be_applied_to(obj/item/bodypart/limb, datum/wound/old_wound)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[type]
+
+ // We assume we aren't being randomly applied - we have no reason to believe we are
+ // And, besides, if we were, you could just as easily check our pregen data rather than run this proc
+ // Generally speaking this proc is called in apply_wound, which is called when the caller is already confidant in its ability to be applied
+ return pregen_data.can_be_applied_to(limb, old_wound = old_wound)
+
+/// Returns the zones we can be applied to.
+/datum/wound/proc/get_excluded_zones()
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[type]
+
+ return pregen_data.excluded_zones
+
+/// Returns the biostate we require to be applied.
+/datum/wound/proc/get_required_biostate()
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[type]
+
+ return pregen_data.required_limb_biostate
+
+/datum/wound/proc/null_victim()
+ SIGNAL_HANDLER
+ set_victim(null)
+
+/// Setter for [victim]. Should completely transfer signals, attributes, etc. To the new victim - if there is any, as it can be null.
+/datum/wound/proc/set_victim(new_victim)
+ if(victim)
+ UnregisterSignal(victim, COMSIG_QDELETING)
+
+ remove_wound_from_victim()
+ victim = new_victim
+ if(victim)
+ RegisterSignal(victim, COMSIG_QDELETING, PROC_REF(null_victim))
+
+/// Proc called to change the variable `limb` and react to the event.
+/datum/wound/proc/set_limb(obj/item/bodypart/new_value, replaced = FALSE)
+ if(limb == new_value)
+ return FALSE //Limb can either be a reference to something or `null`. Returning the number variable makes it clear no change was made.
+ . = limb
+ if(limb) // if we're nulling limb, we're basically detaching from it, so we should remove ourselves in that case
+ UnregisterSignal(limb, COMSIG_QDELETING)
+ if(wound_flags & ACCEPTS_GAUZE)
+ UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED))
+ if(wound_flags & ACCEPTS_SPLINT)
+ UnregisterSignal(limb, list(COMSIG_BODYPART_SPLINTED, COMSIG_BODYPART_SPLINT_DESTROYED))
+ LAZYREMOVE(limb.wounds, src)
+ limb.update_wounds(replaced)
+ if (disabling)
+ limb.remove_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src))
+
+ limb = new_value
+
+ // POST-CHANGE
+
+ if (limb)
+ RegisterSignal(limb, COMSIG_QDELETING, PROC_REF(source_died))
+ if(wound_flags & ACCEPTS_GAUZE)
+ RegisterSignals(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED), PROC_REF(gauze_state_changed))
+ if(wound_flags & ACCEPTS_SPLINT)
+ RegisterSignals(limb, list(COMSIG_BODYPART_SPLINTED, COMSIG_BODYPART_SPLINT_DESTROYED), PROC_REF(splint_state_changed))
+ if (disabling)
+ limb.add_traits(list(TRAIT_PARALYSIS, TRAIT_DISABLED_BY_WOUND), REF(src))
+
+ if (victim)
+ start_limping_if_we_should() // the status effect already handles removing itself
+
+ update_inefficiencies()
+
+/datum/wound/proc/start_limping_if_we_should()
+ if ((limb.body_zone == BODY_ZONE_L_LEG || limb.body_zone == BODY_ZONE_R_LEG) && limp_slowdown > 0 && limp_chance > 0)
+ victim.apply_status_effect(/datum/status_effect/limp)
+
+/// Deletes the wound if its attached limb is deleted.
+/datum/wound/proc/source_died()
+ SIGNAL_HANDLER
+ qdel(src)
+
/// Remove the wound from whatever it's afflicting, and cleans up whateverstatus effects it had or modifiers it had on interaction times. ignore_limb is used for detachments where we only want to forget the victim
/datum/wound/proc/remove_wound(ignore_limb, replaced = FALSE)
//TODO: have better way to tell if we're getting removed without replacement (full heal)
+ var/old_victim = victim
+ var/old_limb = limb
+
set_disabling(FALSE)
if(victim)
- LAZYREMOVE(victim.all_wounds, src)
- if(!victim.all_wounds)
- victim.clear_alert("wound")
- SEND_SIGNAL(victim, COMSIG_CARBON_LOSE_WOUND, src, limb)
+ remove_wound_from_victim()
if(limb && !ignore_limb)
- LAZYREMOVE(limb.wounds, src)
- limb.update_wounds(replaced)
+ set_limb(null, replaced) // since we're removing limb's ref to us, we should do the same
+ // if you want to keep the ref, do it externally, theres no reason for us to remember it
+
+ if (ismob(old_victim))
+ var/mob/mob_victim = old_victim
+ SEND_SIGNAL(mob_victim, COMSIG_CARBON_POST_LOSE_WOUND, src, old_limb, ignore_limb, replaced)
+
+/datum/wound/proc/remove_wound_from_victim()
+ if(!victim)
+ return
+ LAZYREMOVE(victim.all_wounds, src)
+ if(!victim.all_wounds)
+ victim.clear_alert("wound")
+ SEND_SIGNAL(victim, COMSIG_CARBON_LOSE_WOUND, src, limb)
/**
* replace_wound() is used when you want to replace the current wound with a new wound, presumably of the same category, just of a different severity (either up or down counts)
@@ -190,13 +279,13 @@
* This proc actually instantiates the new wound based off the specific type path passed, then returns the new instantiated wound datum.
*
* Arguments:
- * * new_type - The TYPE PATH of the wound you want to replace this, like /datum/wound/slash/severe
+ * * new_wound - The wound instance you want to replace this
* * smited - If this is a smite, we don't care about this wound for stat tracking purposes (not yet implemented)
*/
-/datum/wound/proc/replace_wound(new_type, smited = FALSE, attack_direction = attack_direction)
- var/datum/wound/new_wound = new new_type
- remove_wound(replaced=TRUE)
- new_wound.apply_wound(limb, old_wound = src, smited = smited, attack_direction = attack_direction)
+/datum/wound/proc/replace_wound(datum/wound/new_wound, smited = FALSE, attack_direction = attack_direction)
+ var/obj/item/bodypart/cached_limb = limb // remove_wound() nulls limb so we have to track it locally
+ remove_wound(replaced = new_wound)
+ new_wound.apply_wound(cached_limb, old_wound = src, smited = smited, attack_direction = attack_direction)
. = new_wound
qdel(src)
@@ -204,21 +293,6 @@
/datum/wound/proc/wound_injury(datum/wound/old_wound = null, attack_direction = null)
return
-/// Proc called to change the variable `limb` and react to the event.
-/datum/wound/proc/set_limb(new_value)
- if(limb == new_value)
- return FALSE //Limb can either be a reference to something or `null`. Returning the number variable makes it clear no change was made.
- . = limb
- limb = new_value
- if(. && disabling)
- var/obj/item/bodypart/old_limb = .
- REMOVE_TRAIT(old_limb, TRAIT_PARALYSIS, REF(src))
- REMOVE_TRAIT(old_limb, TRAIT_DISABLED_BY_WOUND, REF(src))
- if(limb)
- if(disabling)
- ADD_TRAIT(limb, TRAIT_PARALYSIS, REF(src))
- ADD_TRAIT(limb, TRAIT_DISABLED_BY_WOUND, REF(src))
-
/// Proc called to change the variable `disabling` and react to the event.
/datum/wound/proc/set_disabling(new_value)
if(disabling == new_value)
@@ -235,6 +309,65 @@
if(limb?.can_be_disabled)
limb.update_disabled()
+/// Setter for [interaction_efficiency_penalty]. Updates the actionspeed of our actionspeed mod.
+/datum/wound/proc/set_interaction_efficiency_penalty(new_value)
+ //var/should_update = (new_value != interaction_efficiency_penalty)
+
+ interaction_efficiency_penalty = new_value
+
+ /*if (should_update)
+ update_actionspeed_modifier()*/
+
+/// Returns a "adjusted" interaction_efficiency_penalty that will be used for the actionspeed mod.
+/datum/wound/proc/get_effective_actionspeed_modifier()
+ return interaction_efficiency_penalty - 1
+
+/// Returns the decisecond multiplier of any click interactions, assuming our limb is being used.
+/datum/wound/proc/get_action_delay_mult()
+ SHOULD_BE_PURE(TRUE)
+
+ return interaction_efficiency_penalty
+
+/// Returns the decisecond increment of any click interactions, assuming our limb is being used.
+/datum/wound/proc/get_action_delay_increment()
+ SHOULD_BE_PURE(TRUE)
+
+ return 0
+
+/// Signal proc for if gauze has been applied or removed from our limb.
+/datum/wound/proc/gauze_state_changed()
+ SIGNAL_HANDLER
+
+/// Signal proc for if a splint has been applied or removed from our limb.
+/datum/wound/proc/splint_state_changed()
+ SIGNAL_HANDLER
+
+ if (wound_flags & ACCEPTS_SPLINT)
+ update_inefficiencies()
+
+/// Updates our limping and interaction penalties in accordance with our gauze.
+/datum/wound/proc/update_inefficiencies()
+ if (wound_flags & ACCEPTS_SPLINT)
+ if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
+ if(limb.current_splint?.splint_factor)
+ limp_slowdown = initial(limp_slowdown) * limb.current_splint.splint_factor
+ limp_chance = initial(limp_chance) * limb.current_splint.splint_factor
+ else
+ limp_slowdown = initial(limp_slowdown)
+ limp_chance = initial(limp_chance)
+ else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
+ if(limb.current_splint?.splint_factor)
+ set_interaction_efficiency_penalty(1 + ((get_effective_actionspeed_modifier()) * limb.current_splint.splint_factor))
+ else
+ set_interaction_efficiency_penalty(initial(interaction_efficiency_penalty))
+
+ if(initial(disabling))
+ set_disabling(!limb.current_splint)
+
+ limb.update_wounds()
+
+ start_limping_if_we_should()
+
/// Additional beneficial effects when the wound is gained, in case you want to give a temporary boost to allow the victim to try an escape or last stand
/datum/wound/proc/second_wind()
switch(severity)
@@ -268,37 +401,39 @@
if(QDELETED(I) || limb.body_zone != user.zone_selected || (I.force && user.a_intent != INTENT_HELP))
return FALSE
- var/allowed = FALSE
-
- // check if we have a valid treatable tool
- if(I.tool_behaviour == treatable_tool)
- allowed = TRUE
- else if(treatable_tool == TOOL_CAUTERY && I.get_temperature() && user == victim) // allow improvised cauterization on yourself without an aggro grab
- allowed = TRUE
- // failing that, see if we're aggro grabbing them and if we have an item that works for aggro grabs only
- else if(user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE && check_grab_treatments(I, user))
- allowed = TRUE
- // failing THAT, we check if we have a generally allowed item
- else
- for(var/allowed_type in treatable_by)
- if(istype(I, allowed_type))
- allowed = TRUE
- break
-
- // if none of those apply, we return false to avoid interrupting
- if(!allowed)
+ if(!item_can_treat(I, user))
return FALSE
+ // now that we've determined we have a valid attempt at treating, we can stomp on their dreams if we're already interacting with the patient or if their part is obscured
+ if(DOING_INTERACTION_WITH_TARGET(user, victim))
+ to_chat(user, span_warning("You're already interacting with [victim]!"))
+ return TRUE
+
// next we check if the bodypart in actually accessible (not under thick clothing). We skip the species trait check since skellies
// & such may need to use bone gel but may be wearing a space suit for..... whatever reason a skeleton would wear a space suit for
if(ishuman(victim))
var/mob/living/carbon/human/victim_human = victim
- if(!victim_human.can_inject(user, injection_flags = INJECT_CHECK_IGNORE_SPECIES))
+ if(!victim_human.try_inject(user, injection_flags = INJECT_CHECK_IGNORE_SPECIES | INJECT_TRY_SHOW_ERROR_MESSAGE))
return TRUE
// lastly, treat them
- treat(I, user)
- return TRUE
+ return treat(I, user) // we allow treat to return a value so it can control if the item does its normal interaction or not
+
+/// Returns TRUE if the item can be used to treat our wounds. Hooks into treat() - only things that return TRUE here may be used there.
+/datum/wound/proc/item_can_treat(obj/item/potential_treater, mob/user)
+ // check if we have a valid treatable tool
+ if(potential_treater.tool_behaviour in treatable_tools)
+ return TRUE
+ if((TOOL_CAUTERY in treatable_tools) && potential_treater.get_temperature() && user == victim) // allow improvised cauterization on yourself without an aggro grab
+ return TRUE
+ // failing that, see if we're aggro grabbing them and if we have an item that works for aggro grabs only
+ if(user.pulling == victim && user.grab_state >= GRAB_AGGRESSIVE && check_grab_treatments(potential_treater, user))
+ return TRUE
+ // failing THAT, we check if we have a generally allowed item
+ for(var/allowed_type in treatable_by)
+ if(istype(potential_treater, allowed_type))
+ return TRUE
+ return FALSE
/// Return TRUE if we have an item that can only be used while aggro grabbed (unhanded aggro grab treatments go in [/datum/wound/proc/try_handling]). Treatment is still is handled in [/datum/wound/proc/treat]
/datum/wound/proc/check_grab_treatments(obj/item/I, mob/user)
@@ -321,15 +456,25 @@
return (!QDELETED(src) && limb)
/// When our parent bodypart is hurt
-/datum/wound/proc/receive_damage(wounding_type, wounding_dmg, wound_bonus, attack_direction)
+/datum/wound/proc/receive_damage(list/wounding_types, total_wound_dmg, wound_bonus, attack_direction, damage_source)
return
/// Called from cryoxadone and pyroxadone when they're proc'ing. Wounds will slowly be fixed separately from other methods when these are in effect. crappy name but eh
/datum/wound/proc/on_xadone(power)
- cryo_progress += power
- if(cryo_progress > 33 * severity)
+ regen_progress += power
+ return handle_regen_progress()
+
+/// Does various actions based on [regen_progress]. By default, qdeletes the wound past a certain threshold.
+/datum/wound/proc/handle_regen_progress()
+ if(regen_progress > get_regen_progress_to_qdel())
qdel(src)
+/// Returns the amount of [regen_progress] we need to be qdeleted.
+/datum/wound/proc/get_regen_progress_to_qdel()
+ SHOULD_BE_PURE(TRUE)
+
+ return base_regen_progress_to_qdel * severity
+
/// When synthflesh is applied to the victim, we call this. No sense in setting up an entire chem reaction system for wounds when we only care for a few chems. Probably will change in the future
/datum/wound/proc/on_synthflesh(power)
return
@@ -377,8 +522,59 @@
* * mob/user: The user examining the wound's owner, if that matters
*/
/datum/wound/proc/get_examine_description(mob/user)
- . = "[victim.p_their(TRUE)] [limb.name] [examine_desc]"
- . = severity <= WOUND_SEVERITY_MODERATE ? "[.]." : "[.]!"
+ var/desc
+
+ if((wound_flags & ACCEPTS_SPLINT) && limb.current_splint)
+ desc = "[victim.p_their()] [limb.name] is [get_sling_condition()] fastened with a [limb.current_splint.name]"
+ else if ((wound_flags & ACCEPTS_GAUZE) && limb.current_gauze)
+ desc = "[victim.p_their()] [limb.name] is [get_gauze_condition()] fastened in a sling of [limb.current_gauze.name]"
+ else
+ desc = "[victim.p_their()] [limb.name] [examine_desc]"
+
+ desc = modify_desc_before_span(desc, user)
+
+ return get_desc_intensity(desc)
+
+/// A hook proc used to modify desc before it is spanned via [get_desc_intensity]. Useful for inserting spans yourself.
+/datum/wound/proc/modify_desc_before_span(desc, mob/user)
+ return desc
+
+/datum/wound/proc/get_gauze_condition()
+ SHOULD_BE_PURE(TRUE)
+ if (!limb.current_gauze)
+ return null
+
+ switch(limb.current_gauze.absorption_capacity)
+ if(0 to 1.25)
+ return "just barely"
+ if(1.25 to 2.75)
+ return "loosely"
+ if(2.75 to 4)
+ return "mostly"
+ if(4 to INFINITY)
+ return "tightly"
+
+/datum/wound/proc/get_sling_condition()
+ SHOULD_BE_PURE(TRUE)
+ if (!limb.current_splint)
+ return null
+
+ switch(limb.current_splint.sling_condition)
+ if(0 to 1.25)
+ return "just barely"
+ if(1.25 to 2.75)
+ return "loosely"
+ if(2.75 to 4)
+ return "mostly"
+ if(4 to INFINITY)
+ return "tightly"
+
+/// Spans [desc] based on our severity.
+/datum/wound/proc/get_desc_intensity(desc)
+ SHOULD_BE_PURE(TRUE)
+ if (severity > WOUND_SEVERITY_MODERATE)
+ return span_bold("[desc]!")
+ return "[desc]."
/datum/wound/proc/get_scanner_description(mob/user)
return "Type: [name]\nSeverity: [severity_text()]\nDescription: [desc]\nRecommended Treatment: [treat_text]"
@@ -394,6 +590,14 @@
if(WOUND_SEVERITY_CRITICAL)
return "Critical"
+/// Returns TRUE if our limb is the head or chest, FALSE otherwise.
+/// Essential in the sense of "we cannot live without it".
+/datum/wound/proc/limb_essential()
+ var/obj/item/organ/brain/victim_brain = victim?.getorganslot(ORGAN_SLOT_BRAIN)
+ if(victim_brain && limb.body_zone == victim_brain.zone) // IPCs don't need their head to live
+ return TRUE
+ return (limb.body_zone == BODY_ZONE_CHEST)
+
/// Whether we should show an interactable topic in examines of the wound. href_list["wound_topic"]
/datum/wound/proc/show_wound_topic(mob/user)
return FALSE
@@ -401,3 +605,21 @@
/// Gets the name of the wound with any interactable topic if possible
/datum/wound/proc/get_topic_name(mob/user)
return show_wound_topic(user) ? "[lowertext(name)]" : lowertext(name)
+
+/// Gets the flat percentage chance increment of a dismember occuring, if a dismember is attempted (requires mangled flesh and bone). returning 15 = +15%.
+/datum/wound/proc/get_dismember_chance_bonus(existing_chance)
+ SHOULD_BE_PURE(TRUE)
+
+ var/datum/wound_pregen_data/pregen_data = get_pregen_data()
+ if((WOUND_BLUNT in pregen_data.required_wounding_types) && severity >= WOUND_SEVERITY_CRITICAL)
+ return WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS // we only require mangled bone (T2 blunt), but if there's a critical blunt, we'll add 15% more
+
+/// Returns our pregen data, which is practically guaranteed to exist, so this proc can safely be used raw.
+/// In fact, since it's RETURN_TYPEd to wound_pregen_data, you can even directly access the variables without having to store the value of this proc in a typed variable.
+/// Ex. get_pregen_data().wound_series
+/datum/wound/proc/get_pregen_data()
+ RETURN_TYPE(/datum/wound_pregen_data)
+
+ return SSwounds.pregen_data[type]
+
+#undef WOUND_CRITICAL_BLUNT_DISMEMBER_BONUS
diff --git a/code/datums/wounds/blunt.dm b/code/datums/wounds/blunt.dm
new file mode 100644
index 0000000000..219b7dd880
--- /dev/null
+++ b/code/datums/wounds/blunt.dm
@@ -0,0 +1,3 @@
+/datum/wound/blunt
+ name = "Blunt Wound"
+ sound_effect = 'sound/effects/wounds/crack1.ogg'
diff --git a/code/datums/wounds/bones.dm b/code/datums/wounds/bones.dm
index 14b3ac2ffd..efcc402ac2 100644
--- a/code/datums/wounds/bones.dm
+++ b/code/datums/wounds/bones.dm
@@ -2,11 +2,17 @@
Blunt/Bone wounds
*/
-/datum/wound/blunt
+/datum/wound_pregen_data/bone
+ abstract = TRUE
+ required_limb_biostate = BIO_BONE
+
+ required_wounding_types = list(WOUND_BLUNT)
+
+ wound_series = WOUND_SERIES_BONE_BLUNT_BASIC
+
+/datum/wound/blunt/bone
name = "Blunt (Bone) Wound"
- sound_effect = 'sound/effects/wounds/crack1.ogg'
- wound_type = WOUND_BLUNT
- wound_flags = (BONE_WOUND | ACCEPTS_SPLINT)
+ wound_flags = ACCEPTS_SPLINT
///Have we been bone gel'd?
var/gelled
@@ -30,16 +36,12 @@
/*
Overwriting of base procs
*/
-/datum/wound/blunt/wound_injury(datum/wound/old_wound = null, attack_direction = null)
- // hook into gaining/losing gauze so crit bone wounds can re-enable/disable depending if they're slung or not
- RegisterSignals(limb, list(COMSIG_BODYPART_SPLINTED, COMSIG_BODYPART_SPLINT_DESTROYED), PROC_REF(update_inefficiencies))
-
+/datum/wound/blunt/bone/wound_injury(datum/wound/old_wound = null, attack_direction = null)
if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group)
processes = TRUE
active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND)
next_trauma_cycle = world.time + (rand(100 - WOUND_BONE_HEAD_TIME_VARIANCE, 100 + WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown)
- RegisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand))
if(limb.held_index && victim.get_item_for_held_index(limb.held_index) && (disabling || prob(30 * severity)))
var/obj/item/I = victim.get_item_for_held_index(limb.held_index)
if(istype(I, /obj/item/offhand))
@@ -53,19 +55,27 @@
)
update_inefficiencies()
+ return ..()
+
+/datum/wound/blunt/bone/set_victim(new_victim)
+ if(victim)
+ UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+ if(new_victim)
+ RegisterSignal(new_victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand))
+ return ..()
-/datum/wound/blunt/remove_wound(ignore_limb, replaced)
+/datum/wound/blunt/bone/remove_wound(ignore_limb, replaced)
limp_slowdown = 0
limp_chance = 0
QDEL_NULL(active_trauma)
- if(limb)
- UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED))
- if(victim)
- UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
return ..()
-/datum/wound/blunt/handle_process(delta_time, times_fired)
+/datum/wound/blunt/bone/handle_process(delta_time, times_fired)
. = ..()
+
+ if(!victim || IS_IN_STASIS(victim))
+ return
+
if(limb.body_zone == BODY_ZONE_HEAD && brain_trauma_group && world.time > next_trauma_cycle)
if(active_trauma)
QDEL_NULL(active_trauma)
@@ -73,8 +83,7 @@
active_trauma = victim.gain_trauma_type(brain_trauma_group, TRAUMA_RESILIENCE_WOUND)
next_trauma_cycle = world.time + (rand(100-WOUND_BONE_HEAD_TIME_VARIANCE, 100+WOUND_BONE_HEAD_TIME_VARIANCE) * 0.01 * trauma_cycle_cooldown)
- var/is_bone_creature = victim.get_biological_state() == BIO_JUST_BONE
- if(!gelled || (!taped && !is_bone_creature))
+ if(!gelled || (!taped && limb.biological_state != BIO_BONE))
return
regen_ticks_current++
@@ -92,7 +101,7 @@
remove_wound()
/// If we're a human who's punching something with a broken arm, we might hurt ourselves doing so
-/datum/wound/blunt/proc/attack_with_hurt_hand(mob/M, atom/target, proximity)
+/datum/wound/blunt/bone/proc/attack_with_hurt_hand(mob/M, atom/target, proximity)
SIGNAL_HANDLER
if(victim.get_active_hand() != limb || victim.a_intent == INTENT_HELP || !ismob(target) || severity <= WOUND_SEVERITY_MODERATE)
@@ -117,16 +126,16 @@
return COMPONENT_NO_ATTACK_HAND
-/datum/wound/blunt/receive_damage(wounding_type, wounding_dmg, wound_bonus)
- if(!victim || wounding_dmg < WOUND_MINIMUM_DAMAGE)
+/datum/wound/blunt/bone/receive_damage(list/wounding_types, total_wound_dmg, wound_bonus)
+ if(!victim || total_wound_dmg < WOUND_MINIMUM_DAMAGE)
return
if(ishuman(victim))
var/mob/living/carbon/human/human_victim = victim
if(NOBLOOD in human_victim.dna?.species.species_traits)
return
- if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg))
- var/blood_bled = rand(1, wounding_dmg * (severity == WOUND_SEVERITY_CRITICAL ? 2 : 1.5)) // 12 brute toolbox can cause up to 18/24 bleeding with a severe/critical chest wound
+ if(limb.body_zone == BODY_ZONE_CHEST && victim.blood_volume && prob(internal_bleeding_chance + total_wound_dmg))
+ var/blood_bled = rand(1, total_wound_dmg * (severity == WOUND_SEVERITY_CRITICAL ? 2 : 1.5)) // 12 brute toolbox can cause up to 18/24 bleeding with a severe/critical chest wound
switch(blood_bled)
if(1 to 6)
victim.bleed(blood_bled, TRUE)
@@ -155,97 +164,65 @@
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.add_splatter_floor(get_step(victim.loc, victim.dir))
-
-/datum/wound/blunt/get_examine_description(mob/user)
- if(!limb.current_splint && !gelled && !taped)
- return ..()
-
- var/list/msg = list()
- if(!limb.current_splint)
- msg += "[victim.p_their(TRUE)] [limb.name] [examine_desc]"
- else
- var/sling_condition = ""
- // how much life we have left in these bandages
- switch(limb.current_splint.sling_condition)
- if(0 to 1.25)
- sling_condition = "just barely"
- if(1.25 to 2.75)
- sling_condition = "loosely"
- if(2.75 to 4)
- sling_condition = "mostly"
- if(4 to INFINITY)
- sling_condition = "tightly"
-
- msg += "[victim.p_their(TRUE)] [limb.name] is [sling_condition] fastened with a [limb.current_splint.name]"
+/datum/wound/blunt/bone/modify_desc_before_span(desc)
+ . = ..()
if(taped)
- msg += ", and appears to be reforming itself under some surgical tape!"
+ . += ", [span_notice("and appears to be reforming itself under some surgical tape!")]"
else if(gelled)
- msg += ", with fizzing flecks of blue bone gel sparking off the bone!"
- else
- msg += "!"
- return "[msg.Join()]"
+ . += ", [span_notice("with fizzing flecks of blue bone gel sparking off the bone!")]"
/*
- New common procs for /datum/wound/blunt/
+ New common procs for /datum/wound/blunt/bone/
*/
-/datum/wound/blunt/proc/update_inefficiencies()
- SIGNAL_HANDLER
-
- if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
- if(limb.current_splint?.splint_factor)
- limp_slowdown = initial(limp_slowdown) * limb.current_splint.splint_factor
- limp_chance = initial(limp_chance) * limb.current_splint.splint_factor
- else
- limp_slowdown = initial(limp_slowdown)
- limp_chance = initial(limp_chance)
- victim.apply_status_effect(STATUS_EFFECT_LIMP)
-
- else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
- if(limb.current_splint?.splint_factor)
- interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * limb.current_splint.splint_factor)
- else
- interaction_efficiency_penalty = interaction_efficiency_penalty
- interaction_efficiency_penalty = initial(interaction_efficiency_penalty)
-
- if(initial(disabling))
- if(limb.current_splint && limb.current_splint.helps_disabled)
- set_disabling(FALSE)
- else
- set_disabling(TRUE)
-
- limb.update_wounds()
-
/// Joint Dislocation (Moderate Blunt)
-/datum/wound/blunt/moderate
+/datum/wound/blunt/bone/moderate
name = "Joint Dislocation"
- desc = "Patient's bone has been unset from socket, causing pain and reduced motor function."
- treat_text = "Recommended application of bonesetter to affected limb, though manual relocation by applying an aggressive grab to the patient and helpfully interacting with afflicted limb may suffice."
+ desc = "Patient's limb has been unset from socket, causing pain and reduced motor function."
+ treat_text = "Recommended application of bonesetter or wrench to affected limb, though manual relocation by applying an aggressive grab to the patient and helpfully interacting with afflicted limb may suffice."
examine_desc = "is awkwardly janked out of place"
occur_text = "janks violently and becomes unseated"
severity = WOUND_SEVERITY_MODERATE
- viable_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ wound_flags = ACCEPTS_SPLINT | PLATING_DAMAGE
+ excluded_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST)
interaction_efficiency_penalty = 1.2
limp_slowdown = 2.25
limp_chance = 50
- threshold_minimum = 35
threshold_penalty = 15
- treatable_tool = TOOL_BONESET
- wound_flags = (BONE_WOUND)
- status_effect_type = /datum/status_effect/wound/blunt/moderate
+ treatable_tools = list(TOOL_BONESET, TOOL_WRENCH)
+ wound_flags = NONE
+ status_effect_type = /datum/status_effect/wound/blunt/bone/moderate
+
+/datum/wound_pregen_data/bone/dislocate
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/blunt/bone/moderate
+
+ required_limb_biostate = BIO_JOINTED
+
+ threshold_minimum = 35
+
+/datum/wound_pregen_data/bone/dislocate/get_threshold_for(obj/item/bodypart/part, attack_direction, damage_source)
+ if(part.biological_state & BIO_METAL)
+ return threshold_minimum * 2
+ return ..()
-/datum/wound/blunt/moderate/Destroy()
+/datum/wound/blunt/bone/moderate/Destroy()
if(victim)
UnregisterSignal(victim, COMSIG_LIVING_DOORCRUSHED)
return ..()
-/datum/wound/blunt/moderate/wound_injury(datum/wound/old_wound, attack_direction = null)
- . = ..()
- RegisterSignal(victim, COMSIG_LIVING_DOORCRUSHED, PROC_REF(door_crush))
+/datum/wound/blunt/bone/moderate/set_victim(new_victim)
+ if(victim)
+ UnregisterSignal(victim, COMSIG_LIVING_DOORCRUSHED)
+ if(new_victim)
+ RegisterSignal(new_victim, COMSIG_LIVING_DOORCRUSHED, PROC_REF(door_crush))
+
+ return ..()
/// Getting smushed in an airlock/firelock is a last-ditch attempt to try relocating your limb
-/datum/wound/blunt/moderate/proc/door_crush()
+/datum/wound/blunt/bone/moderate/proc/door_crush()
if(prob(40))
victim.visible_message(
span_danger("[victim]'s dislocated [limb.name] pops back into place!"),
@@ -253,7 +230,13 @@
)
remove_wound()
-/datum/wound/blunt/moderate/try_handling(mob/living/carbon/human/user, modifiers)
+/datum/wound/blunt/bone/moderate/treat(obj/item/treatment, mob/user)
+ if((limb.biological_state & BIO_BONE) && treatment.tool_behaviour == TOOL_BONESET)
+ return boneset_limb(treatment, user)
+ if((limb.biological_state & BIO_METAL) && treatment.tool_behaviour == TOOL_WRENCH)
+ return wrench_limb(treatment, user)
+
+/datum/wound/blunt/bone/moderate/try_handling(mob/living/carbon/human/user, modifiers)
if(user.pulling != victim || user.zone_selected != limb.body_zone || user.a_intent == INTENT_GRAB)
return FALSE
@@ -276,7 +259,7 @@
return TRUE
/// If someone is snapping our dislocated joint back into place by hand with an aggro grab and help intent
-/datum/wound/blunt/moderate/proc/chiropractice(mob/living/carbon/human/user)
+/datum/wound/blunt/bone/moderate/proc/chiropractice(mob/living/carbon/human/user)
var/time = base_treat_time
if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
return
@@ -302,7 +285,7 @@
chiropractice(user)
/// If someone is snapping our dislocated joint into a fracture by hand with an aggro grab and harm or disarm intent
-/datum/wound/blunt/moderate/proc/malpractice(mob/living/carbon/human/user)
+/datum/wound/blunt/bone/moderate/proc/malpractice(mob/living/carbon/human/user)
var/time = base_treat_time
if(!do_after(user, time, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
@@ -327,20 +310,20 @@
limb.receive_damage(brute = 10, wound_bonus = 10)
malpractice(user)
-/datum/wound/blunt/moderate/treat(obj/item/I, mob/user)
+/datum/wound/blunt/bone/moderate/proc/boneset_limb(obj/item/treatment, mob/user)
if(victim == user)
victim.visible_message(
- span_danger("[user] begins resetting [victim.p_their()] [limb.name] with [I]."),
- span_warning("You begin resetting your [limb.name] with [I]..."),
+ span_danger("[user] begins resetting [victim.p_their()] [limb.name] with [treatment]."),
+ span_warning("You begin resetting your [limb.name] with [treatment]..."),
)
else
user.visible_message(
- span_danger("[user] begins resetting [victim]'s [limb.name] with [I]."),
- span_notice("You begin resetting [victim]'s [limb.name] with [I]..."),
+ span_danger("[user] begins resetting [victim]'s [limb.name] with [treatment]."),
+ span_notice("You begin resetting [victim]'s [limb.name] with [treatment]..."),
)
if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
if(victim == user)
limb.receive_damage(brute = 5, wound_bonus = CANT_WOUND)
@@ -358,11 +341,28 @@
to_chat(victim, span_userdanger("[user] resets your [limb.name]!"))
qdel(src)
+ return TRUE
+
+/datum/wound/blunt/bone/moderate/proc/wrench_limb(obj/item/wrench, mob/user)
+ if(!victim)
+ return FALSE
+ victim.visible_message(
+ span_notice("[user] starts tightening the bolts on [victim]'s [limb.name]..."),
+ span_notice("[user] starts tightening the bolts on your [limb.name].")
+ )
+ if(!wrench.use_tool(victim, user, 3 SECONDS, volume = 50))
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] wrenches [victim]'s [limb.name] back into place."),
+ span_notice("[user] wrenches your [limb.name] back into place.")
+ )
+ qdel(src)
+ return TRUE
/*
Severe (Hairline Fracture)
*/
-/datum/wound/blunt/severe
+/datum/wound/blunt/bone/severe
name = "Hairline Fracture"
desc = "Patient's bone has suffered a crack in the foundation, causing serious pain and reduced limb functionality."
treat_text = "Recommended light surgical application of bone gel, though a sling of medical gauze will prevent worsening situation."
@@ -373,18 +373,24 @@
interaction_efficiency_penalty = 2
limp_slowdown = 6
limp_chance = 60
- threshold_minimum = 70
threshold_penalty = 30
treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel)
- status_effect_type = /datum/status_effect/wound/blunt/severe
+ status_effect_type = /datum/status_effect/wound/blunt/bone/severe
brain_trauma_group = BRAIN_TRAUMA_MILD
trauma_cycle_cooldown = 5 MINUTES
internal_bleeding_chance = 40
- wound_flags = (BONE_WOUND | ACCEPTS_SPLINT | MANGLES_BONE)
+ wound_flags = ACCEPTS_SPLINT | MANGLES_INTERIOR
regen_ticks_needed = 120 // ticks every 2 seconds, 240 seconds, so roughly 4 minutes default
+/datum/wound_pregen_data/bone/hairline
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/blunt/bone/severe
+
+ threshold_minimum = 70
+
/// Compound Fracture (Critical Blunt)
-/datum/wound/blunt/critical
+/datum/wound/blunt/bone/critical
name = "Compound Fracture"
desc = "Patient's bones have suffered multiple gruesome fractures, causing significant pain and near uselessness of limb."
treat_text = "Immediate binding of affected limb, followed by surgical intervention ASAP."
@@ -397,31 +403,37 @@
limp_chance = 70
limp_slowdown = 9
sound_effect = 'sound/effects/wounds/crack2.ogg'
- threshold_minimum = 115
threshold_penalty = 50
disabling = TRUE
treatable_by = list(/obj/item/stack/sticky_tape/surgical, /obj/item/stack/medical/bone_gel)
- status_effect_type = /datum/status_effect/wound/blunt/critical
+ status_effect_type = /datum/status_effect/wound/blunt/bone/critical
brain_trauma_group = BRAIN_TRAUMA_SEVERE
trauma_cycle_cooldown = 5 MINUTES
internal_bleeding_chance = 60
- wound_flags = (BONE_WOUND | ACCEPTS_SPLINT | MANGLES_BONE)
+ wound_flags = ACCEPTS_SPLINT | MANGLES_INTERIOR
regen_ticks_needed = 240 // ticks every 2 seconds, 480 seconds, so roughly 8 minutes default
+/datum/wound_pregen_data/bone/compound
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/blunt/bone/critical
+
+ threshold_minimum = 115
+
// doesn't make much sense for "a" bone to stick out of your head
-/datum/wound/blunt/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null)
+/datum/wound/blunt/bone/critical/apply_wound(obj/item/bodypart/L, silent = FALSE, datum/wound/old_wound = null, smited = FALSE, attack_direction = null)
if(L.body_zone == BODY_ZONE_HEAD)
occur_text = "splits open, exposing a bare, cracked skull through the flesh and blood"
examine_desc = "has an unsettling indent, with bits of skull poking out"
. = ..()
/// if someone is using bone gel on our wound
-/datum/wound/blunt/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user)
+/datum/wound/blunt/bone/proc/gel(obj/item/stack/medical/bone_gel/I, mob/user)
// skellies get treated nicer with bone gel since their "reattach dismembered limbs by hand" ability sucks when it's still critically wounded
// i hate you
if(gelled)
to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.name] is already coated with bone gel!"))
- return
+ return TRUE
user.visible_message(
span_danger("[user] begins hastily applying [I] to [victim]'s' [limb.name]..."),
@@ -429,7 +441,7 @@
)
if(!do_after(user, base_treat_time * 1.5 * (user == victim ? 1.5 : 1), target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
I.use(1)
victim.force_pain_noise(60)
@@ -460,7 +472,7 @@
span_notice("You pass out from the pain of applying [I] to your [limb.name] before you can finish!"),
)
victim.AdjustUnconscious(5 SECONDS)
- return
+ return TRUE
victim.visible_message(
span_notice("[victim] finishes applying [I] to [victim.p_their()] [limb.name], grimacing from the pain!"),
span_notice("You finish applying [I] to your [limb.name], and your bones explode in pain!"),
@@ -469,15 +481,16 @@
limb.receive_damage(25, stamina = 100, wound_bonus = CANT_WOUND)
gelled = TRUE
processes = TRUE
+ return TRUE
/// if someone is using surgical tape on our wound
-/datum/wound/blunt/proc/tape(obj/item/stack/sticky_tape/surgical/I, mob/user)
+/datum/wound/blunt/bone/proc/tape(obj/item/stack/sticky_tape/surgical/I, mob/user)
if(!gelled)
to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.name] must be coated with bone gel to perform this emergency operation!"))
- return
+ return TRUE
if(taped)
to_chat(user, span_warning("[user == victim ? "Your" : "[victim]'s"] [limb.name] is already wrapped in [I.name]."))
- return
+ return TRUE
user.visible_message(
span_danger("[user] begins applying [I] to [victim]'s' [limb.name]..."),
@@ -485,7 +498,7 @@
)
if(!do_after(user, base_treat_time * (user == victim ? 1.5 : 1), target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
if(victim == user)
regen_ticks_needed *= 1.5
@@ -506,20 +519,21 @@
taped = TRUE
processes = TRUE
+ return TRUE
-/datum/wound/blunt/treat(obj/item/I, mob/user)
+/datum/wound/blunt/bone/treat(obj/item/I, mob/user)
if(istype(I, /obj/item/stack/medical/bone_gel))
- gel(I, user)
+ return gel(I, user)
else if(istype(I, /obj/item/stack/sticky_tape/surgical))
- tape(I, user)
+ return tape(I, user)
-/datum/wound/blunt/get_scanner_description(mob/user)
+/datum/wound/blunt/bone/get_scanner_description(mob/user)
. = ..()
. += ""
if(severity > WOUND_SEVERITY_MODERATE)
- if(victim.get_biological_state() == BIO_JUST_BONE)
+ if((limb.biological_state & BIO_BONE) && !(limb.biological_state & BIO_FLESH))
if(!gelled)
. += "Recommended Treatment: Apply bone gel directly to injured limb. Creatures of pure bone don't seem to mind bone gel application nearly as much as fleshed individuals. Surgical tape will also be unnecessary.\n"
else
diff --git a/code/datums/wounds/burns.dm b/code/datums/wounds/burns.dm
index 56d4d42157..e56691f17d 100644
--- a/code/datums/wounds/burns.dm
+++ b/code/datums/wounds/burns.dm
@@ -6,10 +6,12 @@
/datum/wound/burn
name = "Burn Wound"
a_or_from = "from"
- wound_type = WOUND_BURN
- processes = TRUE
sound_effect = 'sound/effects/wounds/sizzle1.ogg'
- wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
+
+/datum/wound/burn/flesh
+ name = "Burn (Flesh) Wound"
+ a_or_from = "from"
+ processes = TRUE
treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh) // sterilizer and alcohol will require reagent treatments, coming soon
@@ -30,8 +32,18 @@
/// Once we reach infestation beyond WOUND_INFESTATION_SEPSIS, we get this many warnings before the limb is completely paralyzed (you'd have to ignore a really bad burn for a really long time for this to happen)
var/strikes_to_lose_limb = 3
+/datum/wound_pregen_data/flesh_burn
+ abstract = TRUE
+
+ required_wounding_types = list(WOUND_BURN)
+ required_limb_biostate = BIO_FLESH
+
+ wound_series = WOUND_SERIES_FLESH_BURN_BASIC
+
+/datum/wound/burn/flesh/handle_process(seconds_per_tick, times_fired)
+ if (!victim || IS_IN_STASIS(victim))
+ return
-/datum/wound/burn/handle_process()
. = ..()
if(strikes_to_lose_limb == 0) // we've already hit sepsis, nothing more to do
victim.adjustToxLoss(0.5)
@@ -124,7 +136,7 @@
set_disabling(TRUE)
strikes_to_lose_limb--
-/datum/wound/burn/get_examine_description(mob/user)
+/datum/wound/burn/flesh/get_examine_description(mob/user)
if(strikes_to_lose_limb <= 0)
return span_deadsay("
[victim.p_their(TRUE)] [limb.name] has locked up completely and is non-functional.")
@@ -157,7 +169,7 @@
return "
[condition.Join()]"
-/datum/wound/burn/get_scanner_description(mob/user)
+/datum/wound/burn/flesh/get_scanner_description(mob/user)
if(strikes_to_lose_limb == 0)
var/oopsie = "Type: [name]\nSeverity: [severity_text()]"
oopsie += "
Infection Level: The bodypart has suffered complete sepsis and must be removed. Amputate or augment limb immediately.
"
@@ -190,15 +202,15 @@
*/
/// if someone is using ointment or mesh on our burns
-/datum/wound/burn/proc/ointmentmesh(obj/item/stack/medical/I, mob/user)
+/datum/wound/burn/flesh/proc/ointmentmesh(obj/item/stack/medical/I, mob/user)
user.visible_message(
span_notice("[user] begins applying [I] to [victim]'s [limb.name]..."),
span_notice("You begin applying [I] to [user == victim ? "your" : "[victim]'s"] [limb.name]..."),
)
if (I.amount <= 0)
- return
+ return TRUE
if(!do_after(user, (user == victim ? I.self_delay : I.other_delay), extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
limb.heal_damage(I.heal_brute, I.heal_burn)
user.visible_message(
@@ -213,25 +225,26 @@
// in this fashion rather than have the cut immediately clear up. maybe bleed wounds could downgrade into muscle wounds...?
if((infestation <= 0 || sanitization >= infestation) && (flesh_damage <= 0 || flesh_healing > flesh_damage))
to_chat(user, span_notice("You've done all you can with [I], [victim]'s [limb.name] can't be treated further."))
+ return TRUE
else
- try_treating(I, user)
+ return try_treating(I, user)
-/datum/wound/burn/treat(obj/item/I, mob/user)
+/datum/wound/burn/flesh/treat(obj/item/I, mob/user)
if(istype(I, /obj/item/stack/medical/ointment))
- ointmentmesh(I, user)
+ return ointmentmesh(I, user)
else if(istype(I, /obj/item/stack/medical/mesh))
var/obj/item/stack/medical/mesh/mesh_check = I
if(!mesh_check.is_open)
to_chat(user, span_warning("You need to open [mesh_check] first."))
return
- ointmentmesh(mesh_check, user)
+ return ointmentmesh(mesh_check, user)
-/datum/wound/burn/on_synthflesh(amount)
+/datum/wound/burn/flesh/on_synthflesh(amount)
flesh_healing += amount * 0.5 // 20u patch will heal 10 flesh standard
/// When a -tane chem is applied to the victim, we call this.
-/datum/wound/burn/on_tane(amount)
+/datum/wound/burn/flesh/on_tane(amount)
if(amount > 10 && severity <= WOUND_SEVERITY_SEVERE)
qdel(src)
return
@@ -241,7 +254,7 @@
return
//crystal reagent lets you fully clear burns because they're rare chemicals and burns suck ass with no cryo
-/datum/wound/burn/on_crystal(power)
+/datum/wound/burn/flesh/on_crystal(power)
if(power>=5)
to_chat(victim, span_green("The burns on your [limb.name] have been regenerated, leaving only minor necrosis."))
victim.adjustCloneLoss(5)
@@ -249,7 +262,7 @@
return
//So does rezadone
-/datum/wound/burn/on_rezadone(power)
+/datum/wound/burn/flesh/on_rezadone(power)
if(power>=10)
// Rapidly regenerating burns isn't so clean, especially when there's an infection to purge
to_chat(victim, span_green("The burns on your [limb.name] clear up, leaving you with an ill feeling."))
@@ -268,7 +281,7 @@
return
// we don't even care about first degree burns, straight to second
-/datum/wound/burn/moderate
+/datum/wound/burn/flesh/moderate
name = "Second Degree Burns"
desc = "Patient is suffering considerable burns with mild skin penetration, weakening limb integrity and increased burning sensations."
treat_text = "Recommended application of topical ointment or regenerative mesh to affected region."
@@ -276,27 +289,38 @@
occur_text = "breaks out with violent red burns"
severity = WOUND_SEVERITY_MODERATE
damage_mulitplier_penalty = 1.05
- threshold_minimum = 40
threshold_penalty = 20
- status_effect_type = /datum/status_effect/wound/burn/moderate
+ status_effect_type = /datum/status_effect/wound/burn/flesh/moderate
flesh_damage = 5
-/datum/wound/burn/severe
+/datum/wound_pregen_data/flesh_burn/second_degree
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/burn/flesh/moderate
+ threshold_minimum = 40
+
+/datum/wound/burn/flesh/severe
name = "Third Degree Burns"
desc = "Patient is suffering extreme burns with full skin penetration, creating serious risk of infection and greatly reduced limb integrity."
treat_text = "Recommended immediate disinfection and excision of any infected skin, followed by bandaging and ointment."
examine_desc = "appears seriously charred, with aggressive red splotches"
occur_text = "chars rapidly, spreading angry red burns"
severity = WOUND_SEVERITY_SEVERE
+ wound_flags = ACCEPTS_GAUZE | NUMBS_BODYPART
damage_mulitplier_penalty = 1.1
- threshold_minimum = 80
threshold_penalty = 30
- status_effect_type = /datum/status_effect/wound/burn/severe
+ status_effect_type = /datum/status_effect/wound/burn/flesh/severe
treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
infestation_rate = 0.03
flesh_damage = 12.5
-/datum/wound/burn/critical
+/datum/wound_pregen_data/flesh_burn/third_degree
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/burn/flesh/severe
+ threshold_minimum = 80
+
+/datum/wound/burn/flesh/critical
name = "Catastrophic Burns"
desc = "Patient is suffering near complete loss of tissue and significantly charred muscle and bone, creating life-threatening risk of infection and negligible limb integrity."
treat_text = "Immediate surgical debriding of any infected skin, followed by potent tissue regeneration formula and bandaging."
@@ -305,9 +329,15 @@
severity = WOUND_SEVERITY_CRITICAL
damage_mulitplier_penalty = 1.15
sound_effect = 'sound/effects/wounds/sizzle2.ogg'
- threshold_minimum = 140
threshold_penalty = 80
- status_effect_type = /datum/status_effect/wound/burn/critical
+ status_effect_type = /datum/status_effect/wound/burn/flesh/critical
treatable_by = list(/obj/item/stack/medical/ointment, /obj/item/stack/medical/mesh)
infestation_rate = 0.07
flesh_damage = 20
+ wound_flags = ACCEPTS_GAUZE | MANGLES_EXTERIOR | NUMBS_BODYPART
+
+/datum/wound_pregen_data/flesh_burn/fourth_degree
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/burn/flesh/critical
+ threshold_minimum = 140
diff --git a/code/datums/wounds/dismember.dm b/code/datums/wounds/dismember.dm
index df516429c4..faf3a8115b 100644
--- a/code/datums/wounds/dismember.dm
+++ b/code/datums/wounds/dismember.dm
@@ -1,16 +1,31 @@
+/datum/wound_pregen_data/loss
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/loss
+ required_limb_biostate = NONE
+ require_any_biostate = TRUE
+
+ required_wounding_types = list(WOUND_ALL)
+
+ wound_series = WOUND_SERIES_LOSS_BASIC
+
+ threshold_minimum = WOUND_DISMEMBER_OUTRIGHT_THRESH // not actually used since dismembering is handled differently, but may as well assign it since we got it
+
/datum/wound/loss
name = "Dismemberment Wound"
desc = "Tis but a flesh wound."
sound_effect = 'sound/effects/wounds/dismember.ogg'
severity = WOUND_SEVERITY_LOSS
- threshold_minimum = WOUND_DISMEMBER_OUTRIGHT_THRESH // not actually used since dismembering is handled differently, but may as well assign it since we got it
status_effect_type = null
wound_flags = null
+ /// The wounding_type of the attack that caused us. Used to generate the description of our scar. Currently unused, but primarily exists in case non-biological wounds are added.
+ var/loss_wounding_type
+
/// Our special proc for our special dismembering, the wounding type only matters for what text we have
/datum/wound/loss/proc/apply_dismember(obj/item/bodypart/dismembered_part, wounding_type = WOUND_SLASH, outright = FALSE, attack_direction)
- if(!istype(dismembered_part) || !dismembered_part.owner || !(dismembered_part.body_zone in viable_zones) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember())
+ if(!istype(dismembered_part) || !dismembered_part.owner || (dismembered_part.body_zone in get_excluded_zones()) || isalien(dismembered_part.owner) || !dismembered_part.can_dismember())
qdel(src)
return
@@ -18,10 +33,31 @@
if(dismembered_part.body_zone == BODY_ZONE_CHEST)
occur_text = "is split open, causing [victim.p_their()] internals organs to spill out!"
- else if(outright)
+ else
+ occur_text = dismembered_part.get_dismember_message(wounding_type, outright)
+
+ var/msg = span_bolddanger("[victim]'s [dismembered_part.name] [occur_text]!")
+
+ victim.visible_message(msg, span_userdanger("Your [dismembered_part.name] [occur_text]!"))
+
+ loss_wounding_type = wounding_type
+
+ set_limb(dismembered_part)
+ second_wind()
+ log_wound(victim, src)
+ if(dismembered_part.can_bleed() && wounding_type != WOUND_BURN && victim.blood_volume)
+ victim.spray_blood(attack_direction, severity)
+ dismembered_part.dismember(dam_type = (wounding_type == WOUND_BURN ? BURN : BRUTE))
+ qdel(src)
+ return TRUE
+
+/obj/item/bodypart/proc/get_dismember_message(wounding_type, outright)
+ var/occur_text
+
+ if(outright)
switch(wounding_type)
if(WOUND_BLUNT)
- occur_text = "is outright smashed to a gross pulp, severing it completely!"
+ occur_text = "is outright smashed to [(biological_state & BIO_METAL) ? "pieces" : "a gross pulp"], severing it completely!"
if(WOUND_SLASH)
occur_text = "is outright slashed off, severing it completely!"
if(WOUND_PIERCE)
@@ -29,25 +65,17 @@
if(WOUND_BURN)
occur_text = "is outright incinerated, falling to dust!"
else
+ var/bone_text = get_internal_description()
+ var/tissue_text = get_external_description()
+
switch(wounding_type)
if(WOUND_BLUNT)
- occur_text = "is shattered through the last bone holding it together, severing it completely!"
+ occur_text = "is shattered through the last [bone_text] holding it together, severing it completely!"
if(WOUND_SLASH)
- occur_text = "is slashed through the last tissue holding it together, severing it completely!"
+ occur_text = "is slashed through the last [tissue_text] holding it together, severing it completely!"
if(WOUND_PIERCE)
- occur_text = "is pierced through the last tissue holding it together, severing it completely!"
+ occur_text = "is pierced through the last [tissue_text] holding it together, severing it completely!"
if(WOUND_BURN)
- occur_text = "is completely incinerated, falling to dust!"
-
- var/msg = span_bolddanger("[victim]'s [dismembered_part.name] [occur_text]!")
+ occur_text = "is completely incinerated, [(biological_state & BIO_METAL) ? "melting to slag" : "falling to dust"]!"
- victim.visible_message(msg, span_userdanger("Your [dismembered_part.name] [occur_text]!"))
-
- set_limb(dismembered_part)
- second_wind()
- log_wound(victim, src)
- if(wounding_type != WOUND_BURN && victim.blood_volume)
- victim.spray_blood(attack_direction, severity)
- dismembered_part.dismember(wounding_type == WOUND_BURN ? BURN : BRUTE)
- qdel(src)
- return TRUE
+ return occur_text
diff --git a/code/datums/wounds/muscle.dm b/code/datums/wounds/muscle.dm
index 88b7093ab6..30dcbfe423 100644
--- a/code/datums/wounds/muscle.dm
+++ b/code/datums/wounds/muscle.dm
@@ -3,21 +3,27 @@
*/
/datum/wound/muscle
name = "Muscle Wound"
- wound_type = WOUND_MUSCLE
- wound_flags = (FLESH_WOUND | ACCEPTS_SPLINT)
- viable_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
+ wound_flags = ACCEPTS_SPLINT
+ bio_state_required = BIO_FLESH
+ excluded_zones = list(BODY_ZONE_HEAD, BODY_ZONE_CHEST)
processes = TRUE
///How much do we need to regen. Will regen faster if we're splinted and or laying down
var/regen_ticks_needed
///Our current counter for healing
var/regen_ticks_current = 0
+/datum/wound_pregen_data/muscle
+ abstract = TRUE
+ required_limb_biostate = BIO_FLESH
+
+ required_wounding_types = list(WOUND_BLUNT)
+ wound_series = WOUND_SERIES_FLESH_MUSCLE
+ weight = WOUND_MUSCLE_WEIGHT
+
/*
Overwriting of base procs
*/
/datum/wound/muscle/wound_injury(datum/wound/old_wound = null, attack_direction = null)
- //hook into gaining/losing gauze so crit muscle wounds can re-enable/disable depending if they're slung or not
- RegisterSignals(limb, list(COMSIG_BODYPART_SPLINTED, COMSIG_BODYPART_SPLINT_DESTROYED), PROC_REF(update_inefficiencies))
RegisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand))
if(limb.held_index && victim.get_item_for_held_index(limb.held_index) && (disabling || prob(10 * severity)))
@@ -111,31 +117,6 @@
return "
[msg.Join()]"
-/*
- Common procs mostly copied from bone wounds, as their behaviour is very similar
-*/
-/datum/wound/muscle/proc/update_inefficiencies()
- if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
- if(limb.current_splint)
- limp_slowdown = initial(limp_slowdown) * limb.current_splint.splint_factor
- else
- limp_slowdown = initial(limp_slowdown)
- victim.apply_status_effect(STATUS_EFFECT_LIMP)
-
- else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
- if(limb.current_splint)
- interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * limb.current_splint.splint_factor)
- else
- interaction_efficiency_penalty = interaction_efficiency_penalty
-
- if(initial(disabling))
- if(limb.current_splint && limb.current_splint.helps_disabled)
- set_disabling(FALSE)
- else
- set_disabling(TRUE)
-
- limb.update_wounds()
-
//Moderate (Muscle Tear)
/datum/wound/muscle/moderate
name = "Muscle Tear"
@@ -146,11 +127,16 @@
severity = WOUND_SEVERITY_MODERATE
interaction_efficiency_penalty = 1.15
limp_slowdown = 1
- threshold_minimum = 40
threshold_penalty = 10
status_effect_type = /datum/status_effect/wound/muscle/moderate
regen_ticks_needed = 500
+/datum/wound_pregen_data/muscle/tear
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/muscle/moderate
+ threshold_minimum = 40
+
//Severe (Ruptured Tendon)
/datum/wound/muscle/severe
name = "Ruptured Tendon"
@@ -162,12 +148,17 @@
severity = WOUND_SEVERITY_SEVERE
interaction_efficiency_penalty = 1.25
limp_slowdown = 5
- threshold_minimum = 90
threshold_penalty = 35
disabling = TRUE
status_effect_type = /datum/status_effect/wound/muscle/severe
regen_ticks_needed = 1500 //takes a while
+/datum/wound_pregen_data/muscle/rupture
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/muscle/severe
+ threshold_minimum = 90
+
/datum/status_effect/wound/muscle
/datum/status_effect/wound/muscle/on_apply()
diff --git a/code/datums/wounds/pierce.dm b/code/datums/wounds/pierce.dm
index a7ddf660d0..acc9b97ae1 100644
--- a/code/datums/wounds/pierce.dm
+++ b/code/datums/wounds/pierce.dm
@@ -3,14 +3,15 @@
*/
/datum/wound/pierce
+
+/datum/wound/pierce/bleed
name = "Piercing Wound"
sound_effect = 'sound/weapons/slice.ogg'
processes = TRUE
- wound_type = WOUND_PIERCE
treatable_by = list(/obj/item/stack/medical/suture)
- treatable_tool = TOOL_CAUTERY
+ treatable_tools = list(TOOL_CAUTERY)
base_treat_time = 3 SECONDS
- wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | ACCEPTS_SPLINT)
+ wound_flags = ACCEPTS_GAUZE | CAN_BE_GRASPED
/// How much blood we start losing when this wound is first applied
var/initial_flow
@@ -22,30 +23,38 @@
/// If we let off blood when hit, the max blood lost is this * the incoming damage
var/internal_bleeding_coefficient
-/datum/wound/pierce/show_wound_topic(mob/user)
+/datum/wound_pregen_data/flesh_pierce
+ abstract = TRUE
+
+ required_limb_biostate = (BIO_FLESH)
+ required_wounding_types = list(WOUND_PIERCE)
+
+ wound_series = WOUND_SERIES_FLESH_PUNCTURE_BLEED
+
+/datum/wound/pierce/bleed/show_wound_topic(mob/user)
return (user == victim && blood_flow)
-/datum/wound/pierce/Topic(href, href_list)
+/datum/wound/pierce/bleed/Topic(href, href_list)
. = ..()
if(href_list["wound_topic"])
if(!usr == victim)
return
victim.self_grasp_bleeding_limb(limb)
-/datum/wound/pierce/wound_injury(datum/wound/old_wound = null, attack_direction = null)
+/datum/wound/pierce/bleed/wound_injury(datum/wound/old_wound = null, attack_direction = null)
blood_flow = initial_flow
- if(attack_direction && victim.blood_volume > BLOOD_VOLUME_BAD)
+ if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_BAD)
victim.spray_blood(attack_direction, severity)
-/datum/wound/pierce/receive_damage(wounding_type, wounding_dmg, wound_bonus)
- if(isnull(victim) || victim.stat == DEAD || wounding_dmg < WOUND_MINIMUM_DAMAGE)
+/datum/wound/pierce/bleed/receive_damage(list/wounding_types, total_wound_dmg, wound_bonus)
+ if(isnull(victim) || victim.stat == DEAD || total_wound_dmg < WOUND_MINIMUM_DAMAGE)
return
- if(victim.blood_volume && prob(internal_bleeding_chance + wounding_dmg))
+ if(victim.blood_volume && limb.can_bleed() && prob(internal_bleeding_chance + total_wound_dmg))
if(limb.current_splint?.splint_factor)
- wounding_dmg *= (1 - limb.current_splint.splint_factor)
+ total_wound_dmg *= (1 - limb.current_splint.splint_factor)
- var/blood_bled = rand(1, wounding_dmg * internal_bleeding_coefficient)
+ var/blood_bled = rand(1, total_wound_dmg * internal_bleeding_coefficient)
switch(blood_bled)
if(1 to 6)
victim.bleed(blood_bled, TRUE)
@@ -74,14 +83,16 @@
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.add_splatter_floor(get_step(victim.loc, victim.dir))
-/datum/wound/pierce/get_bleed_rate_of_change()
+/datum/wound/pierce/bleed/get_bleed_rate_of_change()
+ if(!limb.can_bleed())
+ return BLOOD_FLOW_STEADY
if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS))
return BLOOD_FLOW_INCREASING
if(limb.current_gauze && limb.current_gauze.seep_gauze(limb.current_gauze.absorption_rate, GAUZE_STAIN_BLOOD))
return BLOOD_FLOW_DECREASING
return BLOOD_FLOW_STEADY
-/datum/wound/pierce/handle_process()
+/datum/wound/pierce/bleed/handle_process()
blood_flow = min(blood_flow, WOUND_SLASH_MAX_BLOODFLOW)
if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS))
@@ -93,30 +104,30 @@
if(blood_flow <= 0)
qdel(src)
-/datum/wound/pierce/on_stasis()
+/datum/wound/pierce/bleed/on_stasis()
. = ..()
if(blood_flow <= 0)
qdel(src)
-/datum/wound/pierce/check_grab_treatments(obj/item/I, mob/user)
+/datum/wound/pierce/bleed/check_grab_treatments(obj/item/I, mob/user)
if(I.get_temperature()) // if we're using something hot but not a cautery, we need to be aggro grabbing them first, so we don't try treating someone we're eswording
return TRUE
-/datum/wound/pierce/treat(obj/item/I, mob/user)
+/datum/wound/pierce/bleed/treat(obj/item/I, mob/user)
if(istype(I, /obj/item/stack/medical/suture))
- suture(I, user)
+ return suture(I, user)
else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature())
- tool_cauterize(I, user)
+ return tool_cauterize(I, user)
-/datum/wound/pierce/on_xadone(power)
+/datum/wound/pierce/bleed/on_xadone(power)
. = ..()
blood_flow -= 0.03 * power // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick is pretty good for 0 effort
-/datum/wound/pierce/on_synthflesh(power)
+/datum/wound/pierce/bleed/on_synthflesh(power)
. = ..()
blood_flow -= 0.05 * power // 20u * 0.05 = -1 blood flow, less than with slashes but still good considering smaller bleed rates
-/datum/wound/pierce/on_silfrine(power)
+/datum/wound/pierce/bleed/on_silfrine(power)
switch(power)
if(0 to 3)
EMPTY_BLOCK_GUARD
@@ -129,14 +140,14 @@
blood_flow -= 0.05 * power
/// If someone is using a suture to close this puncture
-/datum/wound/pierce/proc/suture(obj/item/stack/medical/suture/I, mob/user)
+/datum/wound/pierce/bleed/proc/suture(obj/item/stack/medical/suture/I, mob/user)
var/self_penalty_mult = (user == victim ? 1 : 1)
user.visible_message(
span_notice("[user] begins stitching [victim]'s [limb.name] with [I]..."),
span_notice("You begin stitching [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]..."),
)
if(!do_after(user, base_treat_time * self_penalty_mult, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
user.visible_message(
span_green("[user] stitches up some of the bleeding on [victim]."),
span_green("You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"]."),
@@ -147,12 +158,13 @@
I.use(1)
if(blood_flow > 0)
- try_treating(I, user)
+ return try_treating(I, user)
else
to_chat(user, span_green("You successfully close the hole in [user == victim ? "your" : "[victim]'s"] [limb.name]."))
+ return TRUE
/// If someone is using either a cautery tool or something with heat to cauterize this pierce
-/datum/wound/pierce/proc/tool_cauterize(obj/item/I, mob/user)
+/datum/wound/pierce/bleed/proc/tool_cauterize(obj/item/I, mob/user)
var/improv_penalty_mult = (I.tool_behaviour == TOOL_CAUTERY ? 1 : 1.25) // 25% longer and less effective if you don't use a real cautery
var/self_penalty_mult = (user == victim ? 1.1 : 1)
@@ -161,7 +173,7 @@
span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]..."),
)
if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
playsound(user, 'sound/surgery/cautery2.ogg', 20)
user.visible_message(
@@ -175,9 +187,10 @@
blood_flow -= blood_cauterized
if(blood_flow > 0)
- try_treating(I, user)
+ return try_treating(I, user)
+ return TRUE
-/datum/wound/pierce/moderate
+/datum/wound/pierce/bleed/moderate
name = "Minor Breakage"
desc = "Patient's skin has been broken open, causing severe bruising and minor internal bleeding in affected area."
treat_text = "Application of clean bandages and sutures or cauterization."
@@ -189,11 +202,16 @@
gauzed_clot_rate = 0.8
internal_bleeding_chance = 30
internal_bleeding_coefficient = 1.25
- threshold_minimum = 40
threshold_penalty = 20
- status_effect_type = /datum/status_effect/wound/pierce/moderate
+ status_effect_type = /datum/status_effect/wound/pierce/bleed/moderate
+
+/datum/wound_pregen_data/flesh_pierce/breakage
+ abstract = FALSE
-/datum/wound/pierce/severe
+ wound_path_to_generate = /datum/wound/pierce/bleed/moderate
+ threshold_minimum = 40
+
+/datum/wound/pierce/bleed/severe
name = "Open Puncture"
desc = "Patient's internal tissue is penetrated, causing sizeable internal bleeding and reduced limb stability."
treat_text = "Application of clean bandages and sutures or cauterization."
@@ -205,11 +223,16 @@
gauzed_clot_rate = 0.6
internal_bleeding_chance = 60
internal_bleeding_coefficient = 1.5
- threshold_minimum = 60
threshold_penalty = 35
- status_effect_type = /datum/status_effect/wound/pierce/severe
+ status_effect_type = /datum/status_effect/wound/pierce/bleed/severe
+
+/datum/wound_pregen_data/flesh_pierce/open_puncture
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/pierce/bleed/severe
+ threshold_minimum = 60
-/datum/wound/pierce/critical
+/datum/wound/pierce/bleed/critical
name = "Ruptured Cavity"
desc = "Patient's internal tissue and circulatory system is shredded, causing significant internal bleeding and damage to internal organs."
treat_text = "Bandaging, cauterization, or surgical repair followed by potential transfusion."
@@ -221,7 +244,12 @@
gauzed_clot_rate = 0.5
internal_bleeding_chance = 80
internal_bleeding_coefficient = 1.75
- threshold_minimum = 115
threshold_penalty = 50
- status_effect_type = /datum/status_effect/wound/pierce/critical
- wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | ACCEPTS_SPLINT | MANGLES_FLESH)
+ status_effect_type = /datum/status_effect/wound/pierce/bleed/critical
+ wound_flags = ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED
+
+/datum/wound_pregen_data/flesh_pierce/cavity
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/pierce/bleed/critical
+ threshold_minimum = 115
diff --git a/code/datums/wounds/robotic/buckling.dm b/code/datums/wounds/robotic/buckling.dm
new file mode 100644
index 0000000000..87aa3b1d74
--- /dev/null
+++ b/code/datums/wounds/robotic/buckling.dm
@@ -0,0 +1,213 @@
+#define MAX_TAPE_STACKS 5
+#define DAMAGE_PER_STACK 10
+#define TAPE_SCALE 50
+
+#define TAPE_FACTOR_CALC(amount, strength) (TAPE_SCALE / (TAPE_SCALE + (amount * strength)**2))
+
+/datum/wound/blunt/buckling
+ name = "Blunt (Metal) Wound"
+ sound_effect = 'sound/machines/clockcult/integration_cog_install.ogg'
+ wound_flags = PLATING_DAMAGE
+ bio_state_required = BIO_METAL
+
+ /// The tape applied to this wound.
+ var/obj/item/stack/tape/applied_tape
+
+/datum/wound_pregen_data/buckling
+ abstract = TRUE
+ required_limb_biostate = BIO_METAL
+
+ required_wounding_types = list(WOUND_BLUNT)
+
+ wound_series = WOUND_SERIES_METAL_BUCKLING
+
+/datum/wound/blunt/buckling/moderate
+ name = "Bent Plating"
+ desc = "Patient's external plating is bent out of shape."
+ treat_text = "Recommend prying the external plate back into place."
+ examine_desc = "is bent out of shape"
+ occur_text = "suddenly bends out of shape"
+ sound_effect = 'sound/effects/bin_open.ogg'
+ treatable_tools = list(TOOL_CROWBAR, TOOL_WIRECUTTER)
+ treatable_by = list(/obj/item/stack/tape)
+ severity = WOUND_SEVERITY_MODERATE
+ interaction_efficiency_penalty = 1.2
+ threshold_penalty = 20
+ limp_slowdown = 2.25
+ limp_chance = 50
+
+/datum/wound_pregen_data/buckling/dented
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/blunt/buckling/moderate
+ threshold_minimum = 35
+
+/datum/wound/blunt/buckling/severe
+ name = "Buckled Chassis"
+ desc = "Patient's chassis is buckled inwards, causing disruption to mobility. Applying duct tape can temporarily secure the limb until proper repairs."
+ treat_text = "Recommend replacement of external plating."
+ examine_desc = "is buckled inwards"
+ occur_text = "creaks and buckles inwards"
+ severity = WOUND_SEVERITY_SEVERE
+ wound_flags = MANGLES_INTERIOR | PLATING_DAMAGE
+ treatable_tools = list(TOOL_WIRECUTTER) // for tape removal
+ treatable_by = list(/obj/item/stack/tape)
+ interaction_efficiency_penalty = 2
+ threshold_penalty = 40
+ limp_slowdown = 6
+ limp_chance = 60
+
+/datum/wound_pregen_data/buckling/buckled
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/blunt/buckling/severe
+ threshold_minimum = 70
+
+/datum/wound/blunt/buckling/critical
+ name = "Sheared Frame"
+ desc = "Patient's limb is sheared, rendering it inoperable."
+ treat_text = "Recommend replacement of internal frame and external plating. Applying duct tape can temporarily secure the limb until proper repairs."
+ examine_desc = "is sheared off, barely hanging on by the wires"
+ occur_text = "violently snaps as its frame shears apart"
+ wound_flags = MANGLES_INTERIOR | PLATING_DAMAGE
+ severity = WOUND_SEVERITY_CRITICAL
+ treatable_tools = list(TOOL_WIRECUTTER) // for tape removal
+ treatable_by = list(/obj/item/stack/tape) // duct tape will fix anything
+ disabling = TRUE
+ interaction_efficiency_penalty = 2.5
+ threshold_penalty = 50
+ limp_slowdown = 9
+ limp_chance = 70
+
+/datum/wound_pregen_data/buckling/sheared
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/blunt/buckling/critical
+ threshold_minimum = 125
+
+/datum/wound/blunt/buckling/remove_wound(ignore_limb, replaced)
+ limp_slowdown = 0
+ limp_chance = 0
+ if(applied_tape)
+ QDEL_NULL(applied_tape)
+ return ..()
+
+/datum/wound/blunt/buckling/treat(obj/item/treatment, mob/user)
+ if(istype(treatment, /obj/item/stack/tape))
+ return apply_tape(treatment, user)
+ if(treatment.tool_behaviour == TOOL_WIRECUTTER)
+ return remove_tape(treatment, user)
+ if(treatment.tool_behaviour == TOOL_CROWBAR)
+ return pry_chassis(treatment, user)
+
+/// Applies tape to the wound.
+/datum/wound/blunt/buckling/proc/apply_tape(obj/item/stack/tape/new_tape, mob/user)
+ if(applied_tape?.amount >= MAX_TAPE_STACKS)
+ to_chat(user, span_notice("[limb] has too much tape on it already!"))
+ return TRUE
+ if(new_tape.amount < 1)
+ to_chat(user, span_notice("[new_tape] does not contain enough tape!"))
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] starts applying [new_tape.name] to [victim]'s [limb]."),
+ span_notice("[user] starts applying [new_tape.name] to your [limb]."),
+ )
+ while(applied_tape?.amount < MAX_TAPE_STACKS && new_tape.use_tool(victim, user, 2 SECONDS, amount = 1, volume = 50))
+ if(!applied_tape)
+ applied_tape = new new_tape.type(null, 1)
+ else
+ applied_tape.add(1)
+ update_inefficiencies()
+ if(applied_tape.amount >= MAX_TAPE_STACKS)
+ victim.visible_message(
+ span_notice("[user] finishes applying [applied_tape.name] to [victim]'s [limb]."),
+ span_notice("[user] finishes applying [applied_tape.name] to your [limb]."),
+ )
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] applies some [applied_tape.name] to [victim]'s [limb]."),
+ span_notice("[user] applies some [applied_tape.name] to your [limb]."),
+ )
+ return TRUE
+
+/// Removes tape from the round.
+/datum/wound/blunt/buckling/proc/remove_tape(obj/item/tool, mob/user)
+ victim.visible_message(
+ span_notice("[user] tries to remove the [applied_tape] from [victim]'s [limb.name]."),
+ span_danger("[user] tries to remove the [applied_tape] from your [limb.name]!"),
+ )
+ if(!tool.use_tool(victim, user, 3 SECONDS, volume = 50))
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] removes the [applied_tape.name] from [victim]'s [limb.name]."),
+ span_danger("[user] removes the [applied_tape.name] from your [limb]."),
+ )
+ QDEL_NULL(applied_tape)
+ return TRUE
+
+/// Pries moderately buckled limbs back into shape
+/datum/wound/blunt/buckling/proc/pry_chassis(obj/item/tool, mob/user)
+ victim.visible_message(
+ span_notice("[user] starts prying the plating on [victim]'s [limb.name] back into place."),
+ span_notice("[user] starts prying the plating on your [limb.name] back into place."),
+ )
+ if(!tool.use_tool(victim, user, 5 SECONDS, volume = 50))
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] pries the plating on [victim]'s [limb.name] back into place."),
+ span_notice("[user] pries the plating on your [limb.name] back into place."),
+ )
+ qdel(src)
+ return TRUE
+
+/datum/wound/blunt/buckling/receive_damage(list/wounding_types, total_wound_dmg, wound_bonus, attack_direction)
+ if(!applied_tape)
+ return
+ applied_tape.use(round(total_wound_dmg / DAMAGE_PER_STACK))
+ if(QDELETED(applied_tape))
+ if(victim)
+ victim.visible_message(
+ span_warning("The [applied_tape.name] on [victim]'s [limb.name] falls apart!"),
+ span_danger("The [applied_tape.name] on your [limb.name] falls apart!")
+ )
+ applied_tape = null
+ update_inefficiencies()
+
+/datum/wound/blunt/buckling/update_inefficiencies()
+ if(applied_tape)
+ var/tape_factor = TAPE_FACTOR_CALC(applied_tape.amount, applied_tape.nonorganic_heal)
+ interaction_efficiency_penalty = src::interaction_efficiency_penalty * tape_factor
+ limp_slowdown = src::limp_slowdown * tape_factor
+ if(applied_tape.amount >= MAX_TAPE_STACKS)
+ set_disabling(FALSE)
+ else if(applied_tape.amount < CEILING(MAX_TAPE_STACKS / 2, 1))
+ set_disabling(src::disabling)
+ else
+ interaction_efficiency_penalty = src::interaction_efficiency_penalty
+ limp_slowdown = src::limp_slowdown
+ set_disabling(src::disabling)
+
+ limb.update_wounds()
+ start_limping_if_we_should()
+
+/datum/wound/blunt/buckling/modify_desc_before_span(desc, mob/user)
+ if(!applied_tape)
+ return desc
+ var/tape_msg
+ var/tape_strength = 1 - TAPE_FACTOR_CALC(applied_tape.amount, applied_tape.nonorganic_heal)
+ switch(tape_strength)
+ if(0.75 to INFINITY)
+ tape_msg = "tightly"
+ if(0.5 to 0.75)
+ tape_msg = "somewhat"
+ if(0.25 to 0.5)
+ tape_msg = "loosely"
+ else
+ tape_msg = "just barely"
+ return "[victim.p_their(TRUE)] [limb.name] is [tape_msg] held together with [applied_tape.name]"
+
+#undef TAPE_FACTOR_CALC
+
+#undef TAPE_SCALE
+#undef DAMAGE_PER_STACK
+#undef MAX_TAPE_STACKS
diff --git a/code/datums/wounds/robotic/electrical.dm b/code/datums/wounds/robotic/electrical.dm
new file mode 100644
index 0000000000..8fa71647e6
--- /dev/null
+++ b/code/datums/wounds/robotic/electrical.dm
@@ -0,0 +1,130 @@
+// Electrical wounds are special and can be applied from any physical damage type, they get to be their own thing
+
+/datum/wound/electric
+ name = "Electrical Wound"
+ sound_effect = 'sound/effects/light_flicker.ogg'
+ wound_flags = NUMBS_BODYPART
+ bio_state_required = BIO_METAL
+
+ /// The organ currently being affected by this wound.
+ var/obj/item/organ/affected_organ
+ /// The brain trauma linked to this wound, if on the brain's body part.
+ var/datum/brain_trauma/linked_trauma
+ /// The group of brain traumas that can be inflicted.
+ var/trauma_group = BRAIN_TRAUMA_MILD
+
+/datum/wound_pregen_data/electric
+ abstract = TRUE
+ required_limb_biostate = BIO_WIRED
+
+ required_wounding_types = list(WOUND_PIERCE, WOUND_BURN)
+ mangled_wounding_types = list(WOUND_SLASH = ANATOMY_INTERIOR)
+
+ wound_series = WOUND_SERIES_WIRED_ELECTRICAL
+
+/datum/wound/electric/severe
+ name = "Damaged Electronics"
+ desc = "Patient's electronics are damaged, preventing movement and damaging internal components."
+ treat_text = "Recommend replacement of internal wiring."
+ examine_desc = "occasionally sparks"
+ occur_text = "emits a shower of sparks"
+ threshold_penalty = 20
+ severity = WOUND_SEVERITY_SEVERE
+ disabling = TRUE
+
+/datum/wound_pregen_data/electric/damaged
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/electric/severe
+ threshold_minimum = 70
+
+/datum/wound/electric/critical
+ name = "Short Circuit"
+ desc = "Patient's internal circuitry is shorted, causing significant power drain and loss of function."
+ treat_text = "Recommend replacement of internal electronics and wiring."
+ examine_desc = "is twitching and emitting electrical arcs"
+ occur_text = "arcs as its electronics short out"
+ threshold_penalty = 40
+ sound_effect = 'sound/machines/defib_zap.ogg'
+ disabling = TRUE
+ processes = TRUE
+ wound_flags = MANGLES_EXTERIOR | NUMBS_BODYPART
+ severity = WOUND_SEVERITY_CRITICAL
+ trauma_group = BRAIN_TRAUMA_SEVERE
+
+/datum/wound_pregen_data/electric/shorted
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/electric/critical
+ threshold_minimum = 110
+
+/datum/wound/electric/wound_injury(datum/wound/old_wound, attack_direction)
+ if(!affected_organ)
+ affect_organ()
+
+
+/datum/wound/electric/replace_wound(datum/wound/electric/new_wound, smited, attack_direction)
+ if(istype(new_wound, /datum/wound/electric))
+ new_wound.affected_organ = affected_organ
+ return ..()
+
+/datum/wound/electric/remove_wound(ignore_limb, replaced)
+ if(!replaced)
+ restore_organ()
+ else
+ QDEL_NULL(linked_trauma)
+ return ..()
+
+/datum/wound/electric/set_victim(new_victim)
+ if(victim)
+ UnregisterSignal(victim, list(COMSIG_CARBON_LOSE_ORGAN, COMSIG_CARBON_GAIN_ORGAN))
+ if(!new_victim)
+ restore_organ()
+ if(new_victim)
+ RegisterSignal(victim, COMSIG_CARBON_LOSE_ORGAN, PROC_REF(on_organ_loss))
+ RegisterSignal(victim, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_organ_gain))
+ return ..()
+
+/datum/wound/electric/proc/affect_organ()
+ if(!affected_organ)
+ var/obj/item/organ/brain/victim_brain = victim.getorganslot(ORGAN_SLOT_BRAIN)
+ if(victim_brain && limb.body_zone == victim_brain.zone && victim_brain.status == ORGAN_ROBOTIC)
+ affected_organ = victim_brain
+ else
+ affected_organ = pick(limb.get_organs(ORGAN_ROBOTIC))
+ if(!affected_organ)
+ return
+ if(affected_organ.slot == ORGAN_SLOT_BRAIN)
+ if(linked_trauma)
+ QDEL_NULL(linked_trauma)
+ to_chat(victim, span_userdanger(Gibberish("Warning: Power loss to central processing core detected!", TRUE, 40)))
+ linked_trauma = victim.gain_trauma_type(trauma_group, TRAUMA_RESILIENCE_WOUND)
+ else
+ to_chat(victim, span_userdanger("Your [affected_organ.name] suddenly shuts down as it loses power!"))
+ ADD_TRAIT(affected_organ, TRAIT_ORGAN_FAILING, POWER_LACK_TRAIT) // power is not reaching this organ
+
+/datum/wound/electric/proc/restore_organ()
+ SIGNAL_HANDLER
+ if(!affected_organ)
+ return
+ if(linked_trauma)
+ QDEL_NULL(linked_trauma)
+ else
+ REMOVE_TRAIT(affected_organ, TRAIT_ORGAN_FAILING, POWER_LACK_TRAIT)
+
+/datum/wound/electric/proc/on_organ_loss(datum/source, obj/item/organ/lost_organ)
+ SIGNAL_HANDLER
+ if(lost_organ == affected_organ)
+ restore_organ()
+ affect_organ()
+
+/datum/wound/electric/proc/on_organ_gain(datum/source, obj/item/organ/new_organ)
+ SIGNAL_HANDLER
+ if(!affected_organ && new_organ.zone == limb.body_zone)
+ affect_organ()
+
+/datum/wound/electric/handle_process(seconds_per_tick, times_fired)
+ if(!victim)
+ return
+ if(victim.mob_biotypes & MOB_ROBOTIC)
+ victim.adjust_nutrition(severity * -WOUND_ELECTRIC_POWER_DRAIN)
diff --git a/code/datums/wounds/robotic/heat_warping.dm b/code/datums/wounds/robotic/heat_warping.dm
new file mode 100644
index 0000000000..f0d716cea6
--- /dev/null
+++ b/code/datums/wounds/robotic/heat_warping.dm
@@ -0,0 +1,180 @@
+/// The flame temperature required to re-heat the chassis.
+#define CHASSIS_MELTING_POINT 1900
+
+/datum/wound/burn/heat_warping
+ name = "Heat-Warping Wound"
+ sound_effect = 'sound/machines/clockcult/steam_whoosh.ogg'
+
+ wound_flags = PLATING_DAMAGE
+ bio_state_required = BIO_METAL
+
+ /// Whether the limb has been re-heated, allowing it to be bent back into shape
+ var/re_heated = FALSE
+
+/datum/wound_pregen_data/heat_warping
+ abstract = TRUE
+ required_limb_biostate = BIO_METAL
+
+ required_wounding_types = list(WOUND_BURN)
+
+ wound_series = WOUND_SERIES_METAL_HEAT_WARPING
+
+/datum/wound/burn/heat_warping/set_limb(obj/item/bodypart/new_value, replaced)
+ var/obj/item/bodypart/old_limb = ..()
+ if(old_limb && !replaced && !QDELETED(old_limb))
+ old_limb.heal_damage(burn = min(old_limb.max_damage, WOUND_MAX_INTEGRITY_CONSIDERED) * limb_integrity_penalty)
+ if(new_value)
+ var/limb_damage = limb.get_damage()
+ if(limb_damage < limb.wound_integrity_loss)
+ limb.set_burn_dam(CEILING(limb.burn_dam + limb.wound_integrity_loss - limb_damage, DAMAGE_PRECISION))
+ return old_limb
+
+/datum/wound/burn/heat_warping/treat(obj/item/tool, mob/user)
+ if(tool.tool_behaviour == TOOL_WELDER)
+ return heat_chassis(tool, user)
+ if(tool.tool_behaviour == TOOL_CROWBAR)
+ return bend_chassis(tool, user)
+ return ..()
+
+/datum/wound/burn/heat_warping/check_grab_treatments(obj/item/tool, mob/user)
+ if(tool.get_temperature() > CHASSIS_MELTING_POINT)
+ INVOKE_ASYNC(src, PROC_REF(heat_chassis), tool, user)
+ return TRUE
+ return FALSE
+
+/datum/wound/burn/heat_warping/get_examine_description(mob/user)
+ . = ..()
+ if(re_heated)
+ . += span_notice("
It has been re-heated and can be bent back into shape.")
+
+/datum/wound/burn/heat_warping/proc/heat_chassis(obj/item/tool, mob/user)
+ if(re_heated)
+ to_chat(user, span_warning("[victim]'s [limb.name] is already hot!"))
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] begins re-heating [victim]'s [limb.name]..."),
+ span_notice("[user] begins re-heating your [limb.name]..."),
+ )
+ if(!tool.use_tool(victim, user, 5 SECONDS, volume = 50))
+ return TRUE
+ victim.visible_message(
+ span_notice("[victim]'s [limb.name] glows red-hot, ready to be reformed."),
+ span_notice("Your [limb.name] glows red-hot, ready to be reformed."),
+ )
+ re_heated = TRUE
+ addtimer(CALLBACK(src, PROC_REF(cool_down)), 1 MINUTES, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_DELETE_ME)
+ return TRUE
+
+/datum/wound/burn/heat_warping/proc/bend_chassis(obj/item/tool, mob/user)
+ if(!re_heated)
+ return FALSE
+ victim.visible_message(
+ span_notice("[user] starts to bend [victim]'s [limb.name] back into shape..."),
+ span_notice("[user] starts to bend your [limb.name] back into shape..."),
+ )
+ if(!tool.use_tool(victim, user, 4 SECONDS, volume = 50))
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] starts to bend [victim]'s [limb.name] back into shape..."),
+ span_notice("[user] starts to bend your [limb.name] back into shape..."),
+ )
+ qdel(src)
+ return TRUE
+
+/datum/wound/burn/heat_warping/proc/cool_down()
+ victim.visible_message(
+ span_warning("[victim]'s [limb.name] cools back down."),
+ span_warning("Your [limb.name] cools back down."),
+ )
+ re_heated = FALSE
+
+/datum/wound/burn/heat_warping/moderate
+ name = "Surface Oxidization"
+ desc = "Patient's external plating has been oxidized by high temperature."
+ treat_text = "Recommend applying a cleaning agent to remove the oxidized layer, or burning it off with a welding tool."
+ examine_desc = "is oxidized across much of its surface"
+ occur_text = "starts to become discolored"
+ severity = WOUND_SEVERITY_MODERATE
+ treatable_tools = list(TOOL_WELDER)
+ threshold_penalty = 20
+ limb_integrity_penalty = 0.1
+
+/datum/wound_pregen_data/heat_warping/oxidation
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/burn/heat_warping/moderate
+ threshold_minimum = 30
+
+/datum/wound/burn/heat_warping/oxidation/moderate/set_victim(new_victim)
+ if(victim)
+ UnregisterSignal(victim, COMSIG_ATOM_EXPOSE_REAGENTS)
+ if(new_victim)
+ RegisterSignal(victim, COMSIG_ATOM_EXPOSE_REAGENTS, PROC_REF(on_expose))
+ return ..()
+
+/datum/wound/burn/heat_warping/moderate/heat_chassis(obj/item/tool, mob/user)
+ victim.visible_message(
+ span_notice("[user] starts to burn the oxidized layer off of [victim]'s [limb.name]..."),
+ span_notice("[user] starts to burn the oxidized layer off of your [limb.name]..."),
+ )
+ if(!tool.use_tool(victim, user, 4 SECONDS, volume = 50))
+ return TRUE
+ victim.visible_message(
+ span_notice("[user] burns the oxidized layer off of [victim]'s [limb.name]."),
+ span_notice("[user] burns the oxidized layer off of your [limb.name]."),
+ )
+ qdel(src)
+ return TRUE
+
+/datum/wound/burn/heat_warping/oxidation/moderate/proc/on_expose(atom/source, list/reagents, datum/reagents/source_reagents, methods, volume_modifier, show_message)
+ SIGNAL_HANDLER
+
+ if(!(methods & (TOUCH|VAPOR|PATCH)))
+ return
+ var/total_clean_power = 0
+ for(var/datum/reagent/space_cleaner/cleaner in reagents)
+ total_clean_power += cleaner.volume * cleaner.robot_clean_power * volume_modifier
+ if (total_clean_power)
+ source.visible_message(
+ span_notice("The surface of [victim]'s [limb.name] begins to bubble."),
+ span_notice("The surface of your [limb.name] begins to bubble."),
+ )
+ playsound(victim, 'sound/effects/bubbles.ogg', 25 + total_clean_power * 2)
+ handle_regen_progress()
+
+/datum/wound/burn/heat_warping/severe
+ name = "Warped Plating"
+ desc = "Patient's external plating has been warped by thermal stress, threatening its structural integrity."
+ treat_text = "Recommend re-heating the external plating and bending it back into shape."
+ examine_desc = "is heat-warped and charred"
+ occur_text = "warps from the high temperature"
+ severity = WOUND_SEVERITY_SEVERE
+ treatable_tools = list(TOOL_WELDER, TOOL_CROWBAR)
+ threshold_penalty = 30
+ limb_integrity_penalty = 0.2
+
+/datum/wound_pregen_data/heat_warping/thermal_stress
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/burn/heat_warping/severe
+ threshold_minimum = 75
+
+/datum/wound/burn/heat_warping/critical
+ name = "Deformed Chassis"
+ desc = "Patient's chassis has been severely deformed from temperatures close to its melting point and can no longer function."
+ treat_text = "Recommend replacement of the warped external plating."
+ examine_desc = "is a deformed mass of metal and slag"
+ occur_text = "glows red-hot and begins to deform"
+ severity = WOUND_SEVERITY_CRITICAL
+ wound_flags = PLATING_DAMAGE | MANGLES_INTERIOR
+ disabling = TRUE
+ threshold_penalty = 40
+ limb_integrity_penalty = 0.3
+
+/datum/wound_pregen_data/heat_warping/deformed_slag
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/burn/heat_warping/critical
+ threshold_minimum = 130
+
+#undef CHASSIS_MELTING_POINT
diff --git a/code/datums/wounds/slash.dm b/code/datums/wounds/slash.dm
index a3003c22a6..b982ab2adc 100644
--- a/code/datums/wounds/slash.dm
+++ b/code/datums/wounds/slash.dm
@@ -3,15 +3,26 @@
*/
/datum/wound/slash
- name = "Slashing Wound"
+ name = "Slashing (Cut) Wound"
+ sound_effect = 'sound/weapons/slice.ogg'
+
+/datum/wound_pregen_data/flesh_slash
+ abstract = TRUE
+
+ required_wounding_types = list(WOUND_SLASH)
+ required_limb_biostate = BIO_FLESH
+
+ wound_series = WOUND_SERIES_FLESH_SLASH_BLEED
+
+/datum/wound/slash/flesh
+ name = "Slashing (Cut) Flesh Wound"
sound_effect = 'sound/weapons/slice.ogg'
processes = TRUE
- wound_type = WOUND_SLASH
treatable_by = list(/obj/item/stack/medical/suture)
treatable_by_grabbed = list(/obj/item/gun/energy/laser)
- treatable_tool = TOOL_CAUTERY
+ treatable_tools = list(TOOL_CAUTERY)
base_treat_time = 3 SECONDS
- wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE)
+ wound_flags = ACCEPTS_GAUZE | CAN_BE_GRASPED
/// How much blood we start losing when this wound is first applied
var/initial_flow
@@ -24,26 +35,26 @@
/// The maximum flow we've had so far
var/highest_flow
-/datum/wound/slash/show_wound_topic(mob/user)
+/datum/wound/slash/flesh/show_wound_topic(mob/user)
return (user == victim && blood_flow)
-/datum/wound/slash/Topic(href, href_list)
+/datum/wound/slash/flesh/Topic(href, href_list)
. = ..()
if(href_list["wound_topic"])
if(!usr == victim)
return
victim.self_grasp_bleeding_limb(limb)
-/datum/wound/slash/wound_injury(datum/wound/slash/old_wound = null, attack_direction = null)
+/datum/wound/slash/flesh/wound_injury(datum/wound/slash/flesh/old_wound = null, attack_direction = null)
blood_flow = initial_flow
if(old_wound)
blood_flow = max(old_wound.blood_flow, initial_flow)
- else if(attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY)
+ else if(limb.can_bleed() && attack_direction && victim.blood_volume > BLOOD_VOLUME_OKAY)
victim.spray_blood(attack_direction, severity)
-/datum/wound/slash/get_examine_description(mob/user)
+/datum/wound/slash/flesh/get_examine_description(mob/user)
if(!limb.current_gauze)
return ..()
@@ -62,11 +73,14 @@
return "
[msg.Join()]"
-/datum/wound/slash/receive_damage(wounding_type, wounding_dmg, wound_bonus)
- if(victim.stat != DEAD && wound_bonus != CANT_WOUND && wounding_type == WOUND_SLASH) // can't stab dead bodies to make it bleed faster this way
- blood_flow += 0.05 * wounding_dmg
+/datum/wound/slash/flesh/receive_damage(list/wounding_types, total_wound_dmg, wound_bonus)
+ if(!victim) // if we are dismembered, we can still take damage, its fine to check here
+ return
+
+ if(victim.stat != DEAD && wound_bonus != CANT_WOUND && wounding_types[WOUND_SLASH] < WOUND_MINIMUM_DAMAGE) // can't stab dead bodies to make it bleed faster this way
+ blood_flow += 0.05 * wounding_types[WOUND_SLASH]
-/datum/wound/slash/drag_bleed_amount()
+/datum/wound/slash/flesh/drag_bleed_amount()
// say we have 3 severe cuts with 3 blood flow each, pretty reasonable
// compare with being at 100 brute damage before, where you bled (brute/100 * 2), = 2 blood per tile
// 3 * 3 * 0.1 = 0.9 blood total, less than before! the share here is .3 blood of course.
@@ -78,15 +92,21 @@
return bleed_amt
-/datum/wound/slash/get_bleed_rate_of_change()
+/datum/wound/slash/flesh/get_bleed_rate_of_change()
+ if(!limb.can_bleed())
+ return BLOOD_FLOW_STEADY
if(HAS_TRAIT(victim, TRAIT_BLOODY_MESS))
return BLOOD_FLOW_INCREASING
if(limb.current_gauze || clot_rate > 0)
return BLOOD_FLOW_DECREASING
if(clot_rate < 0)
return BLOOD_FLOW_INCREASING
+ return BLOOD_FLOW_STEADY
+
+/datum/wound/slash/flesh/handle_process()
+ if(!victim || IS_IN_STASIS(victim))
+ return
-/datum/wound/slash/handle_process()
if(victim.stat == DEAD)
blood_flow -= max(clot_rate, WOUND_SLASH_DEAD_CLOT_MIN)
if(blood_flow < minimum_flow)
@@ -114,12 +134,12 @@
if(blood_flow < minimum_flow)
if(demotes_to)
- replace_wound(demotes_to)
+ replace_wound(new demotes_to)
else
to_chat(victim, span_green("The cut on your [limb.name] has stopped bleeding!"))
qdel(src)
-/datum/wound/slash/on_stasis()
+/datum/wound/slash/flesh/on_stasis()
if(blood_flow >= minimum_flow)
return
if(demotes_to)
@@ -129,29 +149,29 @@
/* BEWARE, THE BELOW NONSENSE IS MADNESS. bones.dm looks more like what I have in mind and is sufficiently clean, don't pay attention to this messiness */
// what do you mean dont pay attention. what??
-/datum/wound/slash/check_grab_treatments(obj/item/I, mob/user)
+/datum/wound/slash/flesh/check_grab_treatments(obj/item/I, mob/user)
if(istype(I, /obj/item/gun/energy/laser))
return TRUE
if(I.get_temperature()) // if we're using something hot but not a cautery, we need to be aggro grabbing them first, so we don't try treating someone we're eswording
return TRUE
-/datum/wound/slash/treat(obj/item/I, mob/user)
+/datum/wound/slash/flesh/treat(obj/item/I, mob/user)
if(istype(I, /obj/item/gun/energy/laser))
- las_cauterize(I, user)
+ return las_cauterize(I, user)
else if(I.tool_behaviour == TOOL_CAUTERY || I.get_temperature())
- tool_cauterize(I, user)
+ return tool_cauterize(I, user)
else if(istype(I, /obj/item/stack/medical/suture))
- suture(I, user)
+ return suture(I, user)
-/datum/wound/slash/on_xadone(power) //this is for cryo, check and maybe remove later
+/datum/wound/slash/flesh/on_xadone(power) //this is for cryo, check and maybe remove later
. = ..()
blood_flow -= 0.03 * power // i think it's like a minimum of 3 power, so .09 blood_flow reduction per tick
-/datum/wound/slash/on_synthflesh(power)
+/datum/wound/slash/flesh/on_synthflesh(power)
. = ..()
blood_flow -= 0.075 * power // 20u * 0.075 = -1.5 blood flow
-/datum/wound/slash/on_silfrine(power)
+/datum/wound/slash/flesh/on_silfrine(power)
switch(power)
if(0 to 5)
EMPTY_BLOCK_GUARD
@@ -164,7 +184,7 @@
blood_flow -= 0.05 * power
/// If someone's putting a laser gun up to our cut to cauterize it
-/datum/wound/slash/proc/las_cauterize(obj/item/gun/energy/laser/lasgun, mob/user)
+/datum/wound/slash/flesh/proc/las_cauterize(obj/item/gun/energy/laser/lasgun, mob/user)
var/self_penalty_mult = (user == victim ? 1.25 : 1)
user.visible_message(
span_warning("[user] begins aiming [lasgun] directly at [victim]'s [limb.name]..."),
@@ -172,20 +192,21 @@
)
if(!do_after(user, base_treat_time * self_penalty_mult, target=victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
var/damage = lasgun.chambered.BB.damage
lasgun.chambered.BB.wound_bonus -= 30
lasgun.chambered.BB.damage *= self_penalty_mult
if(!lasgun.process_fire(victim, victim, TRUE, null, limb.body_zone))
- return
+ return TRUE
victim.force_scream()
blood_flow -= damage / (5 * self_penalty_mult) // 20 / 5 = 4 bloodflow removed, p good
victim.visible_message(span_warning("The cuts on [victim]'s [limb.name] scar over!"))
+ return TRUE
/// If someone is using either a cautery tool or something with heat to cauterize this cut
-/datum/wound/slash/proc/tool_cauterize(obj/item/I, mob/user)
+/datum/wound/slash/flesh/proc/tool_cauterize(obj/item/I, mob/user)
var/improv_penalty_mult = (I.tool_behaviour == TOOL_CAUTERY ? 1 : 1.25) // 25% longer and less effective if you don't use a real cautery
var/self_penalty_mult = (user == victim ? 1.2 : 1)
user.visible_message(
@@ -193,7 +214,7 @@
span_warning("You begin cauterizing [user == victim ? "your" : "[victim]'s"] [limb.name] with [I]..."),
)
if(!do_after(user, base_treat_time * self_penalty_mult * improv_penalty_mult, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
playsound(user, 'sound/surgery/cautery2.ogg', 20)
user.visible_message(
@@ -206,12 +227,13 @@
blood_flow -= blood_cauterized
if(blood_flow > minimum_flow)
- try_treating(I, user)
+ return try_treating(I, user)
else if(demotes_to)
to_chat(user, span_green("You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts."))
+ return TRUE
/// If someone is using a suture to close this cut
-/datum/wound/slash/proc/suture(obj/item/stack/medical/suture/I, mob/user)
+/datum/wound/slash/flesh/proc/suture(obj/item/stack/medical/suture/I, mob/user)
var/self_penalty_mult = (user == victim ? 1 : 1)
user.visible_message(
span_notice("[user] begins stitching [victim]'s [limb.name] with [I]..."),
@@ -219,7 +241,7 @@
)
if(!do_after(user, base_treat_time * self_penalty_mult, target = victim, extra_checks = CALLBACK(src, PROC_REF(still_exists))))
- return
+ return TRUE
user.visible_message(
span_green("[user] stitches up some of the bleeding on [victim]."),
span_green("You stitch up some of the bleeding on [user == victim ? "yourself" : "[victim]"]."),
@@ -230,11 +252,13 @@
I.use(1)
if(blood_flow > minimum_flow)
- try_treating(I, user)
+ return try_treating(I, user)
else if(demotes_to)
to_chat(user, span_green("You successfully lower the severity of [user == victim ? "your" : "[victim]'s"] cuts."))
+ return TRUE
+ return FALSE
-/datum/wound/slash/moderate
+/datum/wound/slash/flesh/moderate
name = "Rough Abrasion"
desc = "Patient's flesh has been badly scraped, generating moderate blood loss."
treat_text = "Application of clean bandages and sutures."
@@ -245,11 +269,16 @@
initial_flow = 1.5
minimum_flow = 0.1
clot_rate = 0.015
- threshold_minimum = 30
threshold_penalty = 10
- status_effect_type = /datum/status_effect/wound/slash/moderate
+ status_effect_type = /datum/status_effect/wound/slash/flesh/moderate
+
+/datum/wound_pregen_data/flesh_slash/abrasion
+ abstract = FALSE
-/datum/wound/slash/severe
+ wound_path_to_generate = /datum/wound/slash/flesh/moderate
+ threshold_minimum = 30
+
+/datum/wound/slash/flesh/severe
name = "Open Laceration"
desc = "Patient's flesh is ripped clean open, allowing significant blood loss."
treat_text = "Application of clean bandages and sutures or cauterization."
@@ -260,12 +289,17 @@
initial_flow = 2
minimum_flow = 1.5
clot_rate = 0.025
- threshold_minimum = 50
threshold_penalty = 20
- demotes_to = /datum/wound/slash/moderate
- status_effect_type = /datum/status_effect/wound/slash/severe
+ demotes_to = /datum/wound/slash/flesh/moderate
+ status_effect_type = /datum/status_effect/wound/slash/flesh/severe
+
+/datum/wound_pregen_data/flesh_slash/laceration
+ abstract = FALSE
-/datum/wound/slash/critical
+ wound_path_to_generate = /datum/wound/slash/flesh/severe
+ threshold_minimum = 50
+
+/datum/wound/slash/flesh/critical
name = "Weeping Avulsion"
desc = "Patient's flesh is completely torn open, along with significant loss of tissue. Extreme blood loss will lead to quick death without intervention."
treat_text = "Bandage immediately and apply pressure, then apply sutures or cauterization."
@@ -276,8 +310,18 @@
initial_flow = 3
minimum_flow = 2
clot_rate = -0.005 // critical cuts actively get worse instead of better
- threshold_minimum = 100
threshold_penalty = 50
- demotes_to = /datum/wound/slash/severe
- status_effect_type = /datum/status_effect/wound/slash/critical
- wound_flags = (FLESH_WOUND | ACCEPTS_GAUZE | MANGLES_FLESH)
+ demotes_to = /datum/wound/slash/flesh/severe
+ status_effect_type = /datum/status_effect/wound/slash/flesh/critical
+ wound_flags = ACCEPTS_GAUZE | MANGLES_EXTERIOR | CAN_BE_GRASPED
+
+/datum/wound_pregen_data/flesh_slash/avulsion
+ abstract = FALSE
+
+ wound_path_to_generate = /datum/wound/slash/flesh/critical
+ threshold_minimum = 100
+
+/datum/wound/slash/flesh/critical/receive_damage(list/wounding_types, total_wound_dmg, wound_bonus)
+ . = ..()
+ if(victim && wounding_types[WOUND_BLUNT])
+ playsound(victim, "sound/effects/wounds/crackandbleed.ogg", 100)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index d99f2ecb09..bdc24d3d4d 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -506,12 +506,14 @@
* - show_message: Whether to display anything to mobs when they are exposed.
*/
/atom/proc/expose_reagents(list/reagents, datum/reagents/source, method=TOUCH, volume_modifier=1, show_message=TRUE)
- if((. = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, method, volume_modifier, show_message)) & COMPONENT_NO_EXPOSE_REAGENTS)
+ . = SEND_SIGNAL(src, COMSIG_ATOM_EXPOSE_REAGENTS, reagents, source, method, volume_modifier, show_message)
+ if(. & COMPONENT_NO_EXPOSE_REAGENTS)
return
for(var/reagent in reagents)
var/datum/reagent/R = reagent
. |= R.expose_atom(src, reagents[R])
+ SEND_SIGNAL(src, COMSIG_ATOM_AFTER_EXPOSE_REAGENTS, reagents, source, method, volume_modifier, show_message)
/// Are you allowed to drop this atom
/atom/proc/AllowDrop()
@@ -1737,16 +1739,22 @@
* * dealt_bare_wound_bonus- The bare_wound_bonus, if one was specified *and applied*, of the wounding attack. Not shown if armor was present
* * base_roll- Base wounding ability of an attack is a random number from 1 to (dealt_damage ** WOUND_DAMAGE_EXPONENT). This is the number that was rolled in there, before mods
*/
-/proc/log_wound(atom/victim, datum/wound/suffered_wound, dealt_damage, dealt_wound_bonus, dealt_bare_wound_bonus, base_roll)
- if(QDELETED(victim) || !suffered_wound)
+/proc/log_wound(atom/victim, obj/item/bodypart/limb, list/datum/wound/suffered_wounds, list/wounding_types, dealt_wound_bonus, dealt_bare_wound_bonus, list/base_rolls)
+ if(QDELETED(victim) || !LAZYLEN(suffered_wounds))
return
- var/message = "has suffered: [suffered_wound][suffered_wound.limb ? " to [suffered_wound.limb.name]" : null]"// maybe indicate if it's a promote/demote?
-
- if(dealt_damage)
- message += " | Damage: [dealt_damage]"
- // The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut
- if(base_roll)
- message += " (rolled [base_roll]/[dealt_damage ** WOUND_DAMAGE_EXPONENT])"
+ var/message = "has suffered: [english_list(suffered_wounds)][limb ? " to [limb.name]" : null]"// maybe indicate if it's a promote/demote?
+
+ if(wounding_types)
+ var/damage_text
+ for(var/wounding_type in wounding_types)
+ if(damage_text)
+ damage_text += ", "
+ var/damage_dealt = wounding_types[wounding_type]
+ damage_text = "[damage_dealt] [wounding_type]"
+ // The base roll is useful since it can show how lucky someone got with the given attack. For example, dealing a cut
+ if(base_rolls)
+ damage_text += "(rolled [base_rolls[wounding_type]]/[damage_dealt ** WOUND_DAMAGE_EXPONENT])"
+ message += " | Damage: [damage_text]"
if(dealt_wound_bonus)
message += " | WB: [dealt_wound_bonus]"
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index cb152b13f0..e19896545b 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -186,7 +186,7 @@
holo_cooldown = world.time + 10 SECONDS
return
-// see: [/datum/wound/burn/proc/uv()]
+// see: [/datum/wound/burn/flesh/proc/uv()]
/obj/item/flashlight/pen/paramedic
name = "paramedic penlight"
desc = "A high-powered UV penlight intended to help stave off infection in the field on serious burned patients. Probably really bad to look into."
diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm
index b018f26534..1e25f0bd9b 100644
--- a/code/game/objects/items/melee/energy.dm
+++ b/code/game/objects/items/melee/energy.dm
@@ -14,6 +14,8 @@
var/sword_color
/// The heat given off when active.
var/active_heat = 3500
+ /// Damage type used while active.
+ var/active_damtype = BURN
/// Force while active.
var/active_force = 30
@@ -56,11 +58,13 @@
if(active)
heat = active_heat
+ damtype = active_damtype
START_PROCESSING(SSobj, src)
if(sword_color)
icon_state = "[base_icon_state][sword_color]"
else
heat = initial(heat)
+ damtype = initial(damtype)
STOP_PROCESSING(SSobj, src)
tool_behaviour = (active ? TOOL_SAW : NONE) //Lets energy weapons cut trees. Also lets them do bonecutting surgery, which is kinda metal!
@@ -386,11 +390,9 @@
if(active)
icon_state = "[base_icon_state]-on"
item_state = "[base_icon_state]-on"
- damtype = FIRE
armour_penetration = 60
else
icon_state = base_icon_state
item_state = base_icon_state
- damtype = BRUTE
armour_penetration = -20
. = ..()
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index cac867cab9..ef7dbd2cf3 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -12,6 +12,7 @@
/obj/item/stack
icon = 'icons/obj/stack_objects.dmi'
gender = PLURAL
+ item_flags = NOBLUDGEON
material_modifier = 0.01
max_integrity = 100
var/list/datum/stack_recipe/recipes
diff --git a/code/game/objects/items/stacks/tape.dm b/code/game/objects/items/stacks/tape.dm
index f7fb72bb1a..68d5efcc3e 100644
--- a/code/game/objects/items/stacks/tape.dm
+++ b/code/game/objects/items/stacks/tape.dm
@@ -92,12 +92,16 @@
var/lifespan = 300
var/nonorganic_heal = 5
- var/self_delay = 30 //! Also used for the tapecuff delay
- var/other_delay = 10
+ var/self_delay = 3 SECONDS //! Also used for the tapecuff delay
+ var/other_delay = 1 SECONDS
var/prefix = "sticky"
var/list/conferred_embed = EMBED_HARMLESS
var/overwrite_existing = FALSE
+/obj/item/stack/tape/Initialize(mapload, new_amount, merge)
+ . = ..()
+ AddElement(/datum/element/robotic_heal, brute_heal = nonorganic_heal, self_delay = self_delay, other_delay = other_delay)
+
/obj/item/stack/tape/merge(obj/item/stack/S) //Because we have unique children, we need to add an additional fail case
if(src.type != S.type)
return
@@ -107,10 +111,6 @@
if(!istype(C))
return
- //Bootleg bandage
- if(user.a_intent == INTENT_HELP)
- try_heal(C, user)
-
//Relatable suffering
if((HAS_TRAIT(user, TRAIT_CLUMSY) && prob(25)))
to_chat(user, span_warning("Uh... where did the tape edge go?!"))
@@ -157,39 +157,7 @@
to_chat(user, span_warning("There isn't enough tape left!"))
else
to_chat(user, span_warning("[C] doesn't have two hands..."))
-
-/obj/item/stack/tape/proc/try_heal(mob/living/carbon/C, mob/user)
- if(C == user)
- playsound(loc, usesound, 30, TRUE, -2)
- user.visible_message(span_notice("[user] starts to apply \the [src] on [user.p_them()]self..."), span_notice("You begin applying \the [src] on yourself..."))
- if(!do_after(user, self_delay, C, extra_checks=CALLBACK(C, TYPE_PROC_REF(/mob/living, can_inject), user)))
- return
- else if(other_delay)
- user.visible_message(span_notice("[user] starts to apply \the [src] on [C]."), span_notice("You begin applying \the [src] on [C]..."))
- if(!do_after(user, other_delay, C, extra_checks=CALLBACK(C, TYPE_PROC_REF(/mob/living, can_inject), user)))
- return
-
- if(heal(C, user))
- log_combat(user, C, "tape bandaged", src.name)
- use(1)
-
-/obj/item/stack/tape/proc/heal(mob/living/carbon/C, mob/user)
- if(C.stat == DEAD)
- to_chat(user, span_notice("There isn't enough [src] in the universe to fix that..."))
- return
- if(!iscarbon(C))
- return
- var/obj/item/bodypart/affecting = C.get_bodypart(check_zone(user.zone_selected))
- if(!affecting) //Missing limb?
- to_chat(user, span_warning("[C] doesn't have \a [parse_zone(user.zone_selected)]!"))
- return
- if(IS_ROBOTIC_LIMB(affecting)) //Robotic patch-up
- if(affecting.brute_dam)
- user.visible_message(span_notice("[user] applies \the [src] on [C]'s [affecting.name]."), span_green("You apply \the [src] on [C]'s [affecting.name]."))
- if(affecting.heal_damage(nonorganic_heal))
- C.update_damage_overlays()
- return TRUE
- to_chat(user, span_warning("[src] can't patch what [C] has..."))
+ return ..()
/obj/item/stack/tape/proc/apply_gag(mob/living/carbon/target, mob/user)
if(target.is_muzzled() || target.is_mouth_covered())
@@ -306,6 +274,7 @@
nonorganic_heal = 10
prefix = "insulated sticky"
siemens_coefficient = 0
+ w_class = WEIGHT_CLASS_SMALL
/obj/item/stack/tape/industrial/electrical/wrap_item(obj/item/I, mob/living/user)
if(istype(I, /obj/item/clothing/gloves/color))
diff --git a/code/game/objects/items/tools/weldingtool.dm b/code/game/objects/items/tools/weldingtool.dm
index 686fb6092e..b7ffbada0d 100644
--- a/code/game/objects/items/tools/weldingtool.dm
+++ b/code/game/objects/items/tools/weldingtool.dm
@@ -59,6 +59,7 @@
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
AddElement(/datum/element/tool_flash, light_range)
+ AddElement(/datum/element/robotic_heal, brute_heal = 15, self_delay = 5 SECONDS)
/obj/item/weldingtool/update_icon_state()
if(welding)
@@ -116,23 +117,6 @@
. = ..()
target.cut_overlay(GLOB.welding_sparks)
-/obj/item/weldingtool/attack(mob/living/carbon/human/target, mob/user)
- if(!istype(target))
- return ..()
- var/obj/item/bodypart/attackedLimb = target.get_bodypart(check_zone(user.zone_selected))
- if(!attackedLimb || IS_ORGANIC_LIMB(attackedLimb) || (user.a_intent == INTENT_HARM))
- return ..()
- if(!target.is_exposed(user, TRUE, user.zone_selected))
- return TRUE
- if(!tool_start_check(user, amount = 1))
- return TRUE
- user.visible_message(span_notice("[user] starts to fix some of the dents on [target]'s [parse_zone(attackedLimb.body_zone)]."),
- span_notice("You start fixing some of the dents on [target == user ? "your" : "[target]'s"] [parse_zone(attackedLimb.body_zone)]."))
- if(!use_tool(target, user, delay = (target == user ? 5 SECONDS : 0.5 SECONDS), amount = 1, volume = 25))
- return TRUE
- item_heal_robotic(target, user, brute_heal = 15, burn_heal = 0)
- return TRUE
-
/obj/item/weldingtool/afterattack(atom/O, mob/user, proximity)
. = ..()
if(!proximity)
diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm
index cd45856e9d..240dc27638 100644
--- a/code/modules/admin/verbs/randomverbs.dm
+++ b/code/modules/admin/verbs/randomverbs.dm
@@ -1172,8 +1172,14 @@
squish_part = C.bodyparts[zone]
if(!squish_part)
continue
- var/type_wound = pick(list(/datum/wound/blunt/severe, /datum/wound/blunt/severe, /datum/wound/blunt/moderate))
- squish_part.force_wound_upwards(type_wound, smited=TRUE)
+ var/severity = pick(list(
+ "[WOUND_SEVERITY_MODERATE]",
+ "[WOUND_SEVERITY_SEVERE]",
+ "[WOUND_SEVERITY_SEVERE]",
+ "[WOUND_SEVERITY_CRITICAL]",
+ "[WOUND_SEVERITY_CRITICAL]",
+ ))
+ C.cause_wound_of_type_and_severity(WOUND_BLUNT, squish_part, severity)
if(ADMIN_PUNISHMENT_BLEED)
if(!iscarbon(target))
@@ -1185,11 +1191,11 @@
slice_part = C.bodyparts[zone]
if(!slice_part)
continue
- var/type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/moderate))
+ var/type_wound = pick(list(/datum/wound/slash/flesh/critical, /datum/wound/slash/flesh/moderate))
slice_part.force_wound_upwards(type_wound, smited=TRUE)
- type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/moderate))
+ type_wound = pick(list(/datum/wound/slash/flesh/critical, /datum/wound/slash/flesh/moderate))
slice_part.force_wound_upwards(type_wound, smited=TRUE)
- type_wound = pick(list(/datum/wound/slash/critical, /datum/wound/slash/moderate))
+ type_wound = pick(list(/datum/wound/slash/flesh/critical, /datum/wound/slash/flesh/moderate))
slice_part.force_wound_upwards(type_wound, smited=TRUE)
if(ADMIN_PUNISHMENT_PERFORATE)
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 20b5172014..3b4f46be80 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -61,16 +61,13 @@
else
mode() // Activate held item
-/mob/living/carbon/attackby(obj/item/I, mob/user, params)
- if(!all_wounds || !(user.a_intent == INTENT_HELP || user == src))
- return ..()
-
- for(var/i in shuffle(all_wounds))
- var/datum/wound/W = i
- if(W.try_treating(I, user))
- return 1
-
- return ..()
+/mob/living/carbon/handle_tool_treatment(obj/item/tool, mob/living/user, list/modifiers)
+ . = ..()
+ if(. || !all_wounds || !(user.a_intent == INTENT_HELP || user == src))
+ return
+ for(var/datum/wound/iterated_wound as anything in shuffle(all_wounds))
+ if(iterated_wound.try_treating(tool, user))
+ return TRUE
/mob/living/carbon/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
. = ..()
@@ -1277,14 +1274,6 @@
/mob/living/carbon/is_face_visible()
return !(wear_mask?.flags_inv & HIDEFACE) && !(head?.flags_inv & HIDEFACE)
-/**
- * get_biological_state is a helper used to see what kind of wounds we roll for. By default we just assume carbons (read:monkeys) are flesh and bone, but humans rely on their species datums
- *
- * go look at the species def for more info [/datum/species/proc/get_biological_state]
- */
-/mob/living/carbon/proc/get_biological_state() //todo: silicon wounds for ipcs
- return BIO_FLESH_BONE
-
/// Modifies the handcuffed value if a different value is passed, returning FALSE otherwise. The variable should only be changed through this proc.
/mob/living/carbon/proc/set_handcuffed(new_value)
if(handcuffed == new_value)
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 5ab473dcb4..94e26d8eca 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -84,7 +84,7 @@
if(I.force)
var/attack_direction = get_dir(user, src)
apply_damage(I.force, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness(), attack_direction = attack_direction)
- if(I.damtype == BRUTE && (IS_ORGANIC_LIMB(affecting)))
+ if(I.damtype == BRUTE && affecting.can_bleed())
if(prob(33))
I.add_mob_blood(src)
var/turf/location = get_turf(src)
@@ -113,15 +113,38 @@
return
var/extra_wound_details = ""
- if(I.damtype == BRUTE && hit_bodypart.can_dismember())
+
+ if(I.damtype != STAMINA && hit_bodypart.can_dismember())
+
var/mangled_state = hit_bodypart.get_mangled_state()
- var/bio_state = get_biological_state()
- if(mangled_state == BODYPART_MANGLED_BOTH)
+
+ var/bio_status = hit_bodypart.get_bio_state_status()
+
+ var/has_exterior = ((bio_status & ANATOMY_EXTERIOR))
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
+
+ var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR)))
+ var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR)))
+
+ var/dismemberable = ((hit_bodypart.dismemberable_by_wound()) || hit_bodypart.dismemberable_by_total_damage())
+ if (dismemberable)
extra_wound_details = ", threatening to sever it entirely"
- else if((mangled_state == BODYPART_MANGLED_FLESH && I.get_sharpness()) || (mangled_state & BODYPART_MANGLED_BONE && bio_state == BIO_JUST_BONE))
- extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the bone"
- else if((mangled_state == BODYPART_MANGLED_BONE && I.get_sharpness()) || (mangled_state & BODYPART_MANGLED_FLESH && bio_state == BIO_JUST_FLESH))
- extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining tissue"
+ else
+ var/sharpness = I.get_sharpness()
+ var/sharpness_text
+ switch(sharpness)
+ if(SHARP_EDGED)
+ sharpness_text = "slicing"
+ if(SHARP_POINTY)
+ sharpness_text = "piercing"
+ else
+ sharpness_text = "smashing"
+ if((has_interior && (has_exterior && exterior_ready_to_dismember) && I.get_sharpness()))
+ var/bone_text = hit_bodypart.get_internal_description()
+ extra_wound_details = ", [sharpness_text] through to the [bone_text]"
+ else if(has_exterior && ((has_interior && interior_ready_to_dismember) && I.get_sharpness()))
+ var/tissue_text = hit_bodypart.get_external_description()
+ extra_wound_details = ", [sharpness_text] at the remaining [tissue_text]"
var/message_hit_area = ""
if(hit_area)
@@ -134,7 +157,7 @@
attack_message_local = "[user] [message_verb] you[message_hit_area] with [I][extra_wound_details]!"
if(user == src)
- attack_message_local = "You [message_verb] yourself[message_hit_area] with [I][extra_wound_details]"
+ attack_message_local = "You [message_verb] yourself[message_hit_area] with [I][extra_wound_details]!"
visible_message(
span_danger("[attack_message]"),
span_userdanger("[attack_message_local]"), null, COMBAT_MESSAGE_RANGE,
@@ -730,7 +753,7 @@
self_grasp_bleeding_limb(grasped_part, supress_message)
/mob/living/carbon/proc/self_grasp_bleeding_limb(obj/item/bodypart/grasped_part, supress_message = FALSE)
- if(!grasped_part?.get_part_bleed_rate())
+ if(!grasped_part?.can_be_grasped())
return
var/starting_hand_index = active_hand_index
@@ -738,7 +761,9 @@
to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!"))
return
- to_chat(src, span_warning("You try grasping at your [grasped_part.name], trying to stop the bleeding..."))
+ var/bleed_rate = grasped_part.get_part_bleed_rate()
+ var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "")
+ to_chat(src, span_warning("You try grasping at your [grasped_part.name][bleeding_text]..."))
if(!do_after(src, 0.5 SECONDS))
to_chat(src, span_danger("You fail to grasp your [grasped_part.name]."))
return
@@ -750,6 +775,17 @@
return
grasp.grasp_limb(grasped_part)
+/// If TRUE, the owner of this bodypart can try grabbing it to slow bleeding, as well as various other effects.
+/obj/item/bodypart/proc/can_be_grasped()
+ if (get_part_bleed_rate())
+ return TRUE
+
+ for (var/datum/wound/iterated_wound as anything in wounds)
+ if (iterated_wound.wound_flags & CAN_BE_GRASPED)
+ return TRUE
+
+ return FALSE
+
/// an abstract item representing you holding your own limb to staunch the bleeding, see [/mob/living/carbon/proc/grabbedby] will probably need to find somewhere else to put this.
/obj/item/self_grasp
name = "self-grasp"
@@ -795,8 +831,10 @@
RegisterSignal(user, COMSIG_QDELETING, PROC_REF(qdel_void))
RegisterSignal(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_QDELETING), PROC_REF(qdel_void))
+ var/bleed_rate = grasped_part.get_part_bleed_rate()
+ var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "")
user.visible_message(
- span_danger("[user] grasps at [user.p_their()] [grasped_part.name], trying to stop the bleeding."),
+ span_danger("[user] grasps at [user.p_their()] [grasped_part.name][bleeding_text]."),
span_notice("You grab hold of your [grasped_part.name] tightly."),
vision_distance=COMBAT_MESSAGE_RANGE,
)
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index 09f59ebd27..b8d9c80fe2 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -91,6 +91,9 @@
/// All of the wounds a carbon has afflicted throughout their limbs
var/list/all_wounds
+ /// Assoc list of BODY_ZONE -> wounding_type. Set when a limb is dismembered, unset when one is attached. Used for determining what scar to add when it comes time to generate them.
+ var/list/body_zone_dismembered_by
+
/// Levels of moth dust
var/mothdust
diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm
index 899783042f..4fd2e46f80 100644
--- a/code/modules/mob/living/carbon/damage_procs.dm
+++ b/code/modules/mob/living/carbon/damage_procs.dm
@@ -88,11 +88,11 @@
heal_overall_damage(abs(amount), 0, 0, required_status, updating_health)
return amount
-/mob/living/carbon/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status)
+/mob/living/carbon/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE, required_status, ignore_reduction = 0)
if(!forced && (status_flags & GODMODE))
return FALSE
if(amount > 0)
- take_overall_damage(0, amount, 0, updating_health, required_status)
+ take_overall_damage(0, amount, 0, updating_health, required_status, ignore_reduction)
else
if(!required_status)
required_status = forced ? null : BODYTYPE_ORGANIC
@@ -273,7 +273,7 @@
update_damage_overlays()
/// damage MANY bodyparts, in random order
-/mob/living/carbon/take_overall_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status)
+/mob/living/carbon/take_overall_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status, ignore_reduction = 0)
if(status_flags & GODMODE)
return //godmode
@@ -290,7 +290,7 @@
var/stamina_was = picked.stamina_dam
- update |= picked.receive_damage(brute_per_part, burn_per_part, stamina_per_part, FALSE, required_status, wound_bonus = CANT_WOUND)
+ update |= picked.receive_damage(brute_per_part, burn_per_part, stamina_per_part, FALSE, required_status, wound_bonus = CANT_WOUND, ignore_reduction = ignore_reduction)
brute = round(brute - (picked.brute_dam - brute_was), DAMAGE_PRECISION)
burn = round(burn - (picked.burn_dam - burn_was), DAMAGE_PRECISION)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 396c0fd988..5186f4dc87 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -502,7 +502,7 @@
//ohh god this'll need to be reworked into a zone-by-zone selection, rather than just "are yuor jorts thick"
-/mob/living/carbon/human/proc/is_exposed(mob/user, error_msg, target_zone)
+/mob/living/carbon/human/is_exposed(mob/user, target_zone, error_msg)
. = TRUE // Default to returning true.
if(user && !target_zone)
target_zone = user.zone_selected
@@ -894,8 +894,8 @@
if(!body_part)
continue
var/numbing_wound = FALSE
- for(var/datum/wound/W in body_part.wounds)
- if(W.wound_type == WOUND_BURN)
+ for(var/datum/wound/iterated_wound in body_part.wounds)
+ if(iterated_wound.wound_flags & NUMBS_BODYPART)
numbing_wound = TRUE
var/damage = body_part.burn_dam + body_part.brute_dam
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index 1b17cb307c..6d8e572da9 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -2368,14 +2368,4 @@ GLOBAL_LIST_EMPTY(roundstart_races)
/datum/species/proc/get_harm_descriptors()
return
-/**
- * The human species version of [/mob/living/carbon/proc/get_biological_state]. Depends on the HAS_FLESH and HAS_BONE species traits, having bones lets you have bone wounds, having flesh lets you have burn, slash, and piercing wounds
- */
-/datum/species/proc/get_biological_state(mob/living/carbon/human/H)
- . = BIO_INORGANIC
- if(HAS_FLESH in species_traits)
- . |= BIO_JUST_FLESH
- if(HAS_BONE in species_traits)
- . |= BIO_JUST_BONE
-
#undef MINIMUM_MOLS_TO_HARM
diff --git a/code/modules/mob/living/carbon/human/species_types/IPC.dm b/code/modules/mob/living/carbon/human/species_types/IPC.dm
index 3e1729d7ff..853d1327e9 100644
--- a/code/modules/mob/living/carbon/human/species_types/IPC.dm
+++ b/code/modules/mob/living/carbon/human/species_types/IPC.dm
@@ -3,7 +3,7 @@
id = SPECIES_IPC
species_age_min = 0
species_age_max = 300
- species_traits = list(HAIR,NOTRANSSTING,NO_DNA_COPY,TRAIT_EASYDISMEMBER,NOZOMBIE,MUTCOLORS,REVIVESBYHEALING,NOHUSK,NOMOUTH) //all of these + whatever we inherit from the real species
+ species_traits = list(HAIR,NOTRANSSTING,NO_DNA_COPY,NOZOMBIE,MUTCOLORS,REVIVESBYHEALING,NOHUSK,NOMOUTH) //all of these + whatever we inherit from the real species
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_LIMBATTACHMENT)
inherent_biotypes = MOB_ROBOTIC|MOB_HUMANOID
mutantbrain = /obj/item/organ/brain/mmi_holder/posibrain
@@ -217,7 +217,7 @@
/datum/species/ipc/spec_life(mob/living/carbon/human/H)
. = ..()
if(H.health <= HEALTH_THRESHOLD_CRIT && H.stat != DEAD) // So they die eventually instead of being stuck in crit limbo.
- H.adjustFireLoss(6) // After BODYTYPE_ROBOTIC resistance this is ~2/second
+ H.adjustFireLoss(2, ignore_reduction = INFINITY)
if(prob(5))
to_chat(H, span_warning("Alert: Internal temperature regulation systems offline; thermal damage sustained. Shutdown imminent."))
H.visible_message("[H]'s cooling system fans stutter and stall. There is a faint, yet rapid beeping coming from inside their chassis.")
diff --git a/code/modules/mob/living/carbon/human/species_types/abductors.dm b/code/modules/mob/living/carbon/human/species_types/abductors.dm
index 6c43162737..a6e9916c00 100644
--- a/code/modules/mob/living/carbon/human/species_types/abductors.dm
+++ b/code/modules/mob/living/carbon/human/species_types/abductors.dm
@@ -2,7 +2,7 @@
name = "\improper Abductor"
id = SPECIES_ABDUCTOR
species_traits = list(NOBLOOD)
- inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH)
+ inherent_traits = list(TRAIT_VIRUSIMMUNE,TRAIT_CHUNKYFINGERS,TRAIT_NOHUNGER,TRAIT_NOBREATH,TRAIT_NODISMEMBER,TRAIT_NEVER_WOUNDED)
mutanttongue = /obj/item/organ/tongue/abductor
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN
ass_image = 'icons/ass/assgrey.png'
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index b8b91fe1fc..eb5ec08339 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -16,7 +16,7 @@
attack_type = BURN //burn bish
exotic_bloodtype = "E"
species_age_max = 300
- species_traits = list(DYNCOLORS, HAIR, FACEHAIR, HAS_FLESH, HAS_BONE)
+ species_traits = list(DYNCOLORS, HAIR, FACEHAIR)
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN
species_language_holder = /datum/language_holder/ethereal
inherent_traits = list(TRAIT_NOHUNGER)
diff --git a/code/modules/mob/living/carbon/human/species_types/flypeople.dm b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
index 9efc99b8ce..a7c6b5236b 100644
--- a/code/modules/mob/living/carbon/human/species_types/flypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/flypeople.dm
@@ -1,7 +1,7 @@
/datum/species/fly
name = "\improper Flyperson"
id = SPECIES_FLYPERSON
- species_traits = list(TRAIT_ANTENNAE, HAS_FLESH, HAS_BONE)
+ inherent_traits = list(TRAIT_ANTENNAE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
mutanttongue = /obj/item/organ/tongue/fly
mutantliver = /obj/item/organ/liver/fly
diff --git a/code/modules/mob/living/carbon/human/species_types/humans.dm b/code/modules/mob/living/carbon/human/species_types/humans.dm
index 2d5bf7b4f9..70b6d66ffa 100644
--- a/code/modules/mob/living/carbon/human/species_types/humans.dm
+++ b/code/modules/mob/living/carbon/human/species_types/humans.dm
@@ -2,7 +2,7 @@
name = "\improper Human"
id = SPECIES_HUMAN
default_color = "FFFFFF"
- species_traits = list(HAIR,FACEHAIR,LIPS,SCLERA,EMOTE_OVERLAY,SKINCOLORS,HAS_FLESH,HAS_BONE)
+ species_traits = list(HAIR,FACEHAIR,LIPS,SCLERA,EMOTE_OVERLAY,SKINCOLORS)
default_features = list("mcolor" = "FFF", "tail_human" = "None", "ears" = "None", "wings" = "None")
mutant_bodyparts = list("ears", "tail_human")
use_skintones = TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index f2347aa142..41ee147d4c 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -3,7 +3,7 @@
name = "\improper Jellyperson"
id = SPECIES_JELLYPERSON
default_color = "00FF90"
- species_traits = list(MUTCOLORS,NOBLOOD,HAIR,FACEHAIR,HAS_FLESH)
+ species_traits = list(MUTCOLORS,NOBLOOD,HAIR,FACEHAIR)
inherent_traits = list(TRAIT_TOXINLOVER)
hair_color = "mutcolor"
hair_alpha = 150
diff --git a/code/modules/mob/living/carbon/human/species_types/kepori.dm b/code/modules/mob/living/carbon/human/species_types/kepori.dm
index 5edff12d87..43fbc21896 100644
--- a/code/modules/mob/living/carbon/human/species_types/kepori.dm
+++ b/code/modules/mob/living/carbon/human/species_types/kepori.dm
@@ -2,7 +2,7 @@
name = "\improper Kepori"
id = SPECIES_KEPORI
default_color = "6060FF"
- species_traits = list(SCLERA, MUTCOLORS, MUTCOLORS_SECONDARY, HAS_FLESH, HAS_BONE)
+ species_traits = list(SCLERA, MUTCOLORS, MUTCOLORS_SECONDARY)
inherent_traits = list(TRAIT_SCOOPABLE)
mutant_bodyparts = list("kepori_body_feathers", "kepori_head_feathers", "kepori_tail_feathers", "kepori_feathers")
default_features = list("mcolor" = "0F0", "wings" = "None", "kepori_feathers" = "None", "kepori_head_feathers" = "None", "kepori_body_feathers" = "None", "kepori_tail_feathers" = "None")
diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
index f5014a2310..df65b80c20 100644
--- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm
@@ -4,7 +4,7 @@
id = SPECIES_SARATHI
default_color = "00FF00"
species_age_max = 175
- species_traits = list(MUTCOLORS, LIPS, SCLERA, EMOTE_OVERLAY, MUTCOLORS_SECONDARY, HAS_FLESH, HAS_BONE)
+ species_traits = list(MUTCOLORS, LIPS, SCLERA, EMOTE_OVERLAY, MUTCOLORS_SECONDARY)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_REPTILE
mutant_bodyparts = list("tail_lizard", "face_markings", "frills", "horns", "spines", "body_markings")
mutanttongue = /obj/item/organ/tongue/lizard
diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
index a8b5f5cc5f..2e20ab6f5d 100644
--- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm
@@ -2,7 +2,8 @@
name = "\improper Moth"
id = SPECIES_MOTH
default_color = "00FF00"
- species_traits = list(LIPS, TRAIT_ANTENNAE, HAIR, EMOTE_OVERLAY, HAS_FLESH, HAS_BONE)
+ species_traits = list(LIPS, HAIR, EMOTE_OVERLAY)
+ inherent_traits = list(TRAIT_ANTENNAE)
inherent_biotypes = MOB_ORGANIC|MOB_HUMANOID|MOB_BUG
mutant_bodyparts = list("moth_wings", "moth_fluff", "moth_markings")
default_features = list("moth_wings" = "Plain", "moth_fluff" = "Plain", "moth_markings" = "None")
diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
index ffd8e37e8d..32976b827c 100644
--- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
+++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm
@@ -2,7 +2,7 @@
name = "\improper Phorid"
id = SPECIES_PLASMAMAN
meat = /obj/item/stack/sheet/mineral/plasma
- species_traits = list(NOBLOOD, NOTRANSSTING, HAS_BONE)
+ species_traits = list(NOBLOOD, NOTRANSSTING)
// plasmemes get hard to wound since they only need a severe bone wound to dismember, but unlike skellies, they can't pop their bones back into place
inherent_traits = list(TRAIT_RESISTCOLD,TRAIT_RADIMMUNE,TRAIT_GENELESS,TRAIT_NOHUNGER,TRAIT_ALWAYS_CLEAN, TRAIT_HARDLY_WOUNDED)
inherent_biotypes = MOB_HUMANOID|MOB_MINERAL
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index 31bbb3c60f..5430f531ea 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -6,7 +6,7 @@
name = "???"
id = SPECIES_SHADOW
species_traits = list(NOBLOOD)
- inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH)
+ inherent_traits = list(TRAIT_RADIMMUNE,TRAIT_VIRUSIMMUNE,TRAIT_NOBREATH,TRAIT_NODISMEMBER,TRAIT_NEVER_WOUNDED)
inherent_factions = list("faithless")
changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC
mutanteyes = /obj/item/organ/eyes/night_vision
diff --git a/code/modules/mob/living/carbon/human/species_types/skeletons.dm b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
index ea7f4cda2e..fcd689ed14 100644
--- a/code/modules/mob/living/carbon/human/species_types/skeletons.dm
+++ b/code/modules/mob/living/carbon/human/species_types/skeletons.dm
@@ -2,7 +2,7 @@
// 2spooky
name = "\improper Skeleton"
id = SPECIES_SKELETON
- species_traits = list(NOBLOOD, HAS_BONE, NOHUSK)
+ species_traits = list(NOBLOOD, NOHUSK)
inherent_traits = list(TRAIT_NOMETABOLISM,TRAIT_TOXIMMUNE,TRAIT_RESISTHEAT,TRAIT_NOBREATH,TRAIT_RESISTCOLD,TRAIT_RESISTHIGHPRESSURE,TRAIT_RESISTLOWPRESSURE,TRAIT_RADIMMUNE,\
TRAIT_GENELESS,TRAIT_PIERCEIMMUNE,TRAIT_NOHUNGER,TRAIT_EASYDISMEMBER,TRAIT_LIMBATTACHMENT,TRAIT_FAKEDEATH,TRAIT_XENO_IMMUNE,TRAIT_NOCLONELOSS)
inherent_biotypes = MOB_UNDEAD|MOB_HUMANOID
diff --git a/code/modules/mob/living/carbon/human/species_types/snail.dm b/code/modules/mob/living/carbon/human/species_types/snail.dm
index bf96159129..e42b824530 100644
--- a/code/modules/mob/living/carbon/human/species_types/snail.dm
+++ b/code/modules/mob/living/carbon/human/species_types/snail.dm
@@ -2,7 +2,7 @@
name = "\improper Snailperson"
id = SPECIES_SNAIL
default_color = "336600" //vomit green
- species_traits = list(MUTCOLORS, NO_UNDERWEAR, HAS_FLESH, HAS_BONE)
+ species_traits = list(MUTCOLORS, NO_UNDERWEAR)
inherent_traits = list(TRAIT_ALWAYS_CLEAN, TRAIT_NOSLIPALL)
attack_verb = "slap"
coldmod = 0.5 //snails only come out when its cold and wet
diff --git a/code/modules/mob/living/carbon/human/species_types/vox.dm b/code/modules/mob/living/carbon/human/species_types/vox.dm
index 16dc505b6a..c3f250f9b9 100644
--- a/code/modules/mob/living/carbon/human/species_types/vox.dm
+++ b/code/modules/mob/living/carbon/human/species_types/vox.dm
@@ -4,7 +4,6 @@
id = SPECIES_VOX
default_color = "6060FF"
species_age_max = 280
- species_traits = list(HAS_BONE, HAS_FLESH)
mutant_bodyparts = list("vox_head_quills", "vox_neck_quills")
default_features = list("mcolor" = "0F0", "wings" = "None", "vox_head_quills" = "None", "vox_neck_quills" = "None")
meat = /obj/item/food/meat/slab/chicken
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 5a93527958..7a9f42099d 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -34,8 +34,6 @@
handle_diseases(seconds_per_tick, times_fired)// DEAD check is in the proc itself; we want it to spread even if the mob is dead, but to handle its disease-y properties only if you're not.
- handle_wounds()
-
if (QDELETED(src)) // diseases can qdel the mob via transformations
return
@@ -56,6 +54,8 @@
handle_traits() // eye, ear, brain damages
handle_status_effects() //all special effects, stun, knockdown, jitteryness, hallucination, sleeping, etc
+ handle_wounds(seconds_per_tick, times_fired)
+
if(machine)
machine.check_eye(src)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 93cf5a5d65..87382771f0 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -561,6 +561,9 @@
/mob/living/proc/can_inject(mob/user, target_zone, injection_flags)
return TRUE
+/mob/living/proc/is_exposed(mob/user, target_zone, error_msg)
+ return TRUE
+
/**
* Like can_inject, but it can perform side effects.
*
diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm
index 92e468af4d..e36754e620 100644
--- a/code/modules/mob/mob_helpers.dm
+++ b/code/modules/mob/mob_helpers.dm
@@ -446,26 +446,6 @@
alert_overlay.plane = FLOAT_PLANE
A.add_overlay(alert_overlay)
-/**
- * Heal a robotic body part on a mob
- */
-/proc/item_heal_robotic(mob/living/carbon/human/H, mob/user, brute_heal, burn_heal)
- var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected))
- if(affecting && (!IS_ORGANIC_LIMB(affecting)))
- var/dam //changes repair text based on how much brute/burn was supplied
- if(brute_heal > burn_heal)
- dam = 1
- else
- dam = 0
- if((brute_heal > 0 && affecting.brute_dam > 0) || (burn_heal > 0 && affecting.burn_dam > 0))
- if(affecting.heal_damage(brute_heal, burn_heal, 0, BODYTYPE_ROBOTIC))
- H.update_damage_overlays()
- user.visible_message("[user] has fixed some of the [dam ? "dents on" : "burnt wires in"] [H]'s [parse_zone(affecting.body_zone)].", \
- span_notice("You fix some of the [dam ? "dents on" : "burnt wires in"] [H == user ? "your" : "[H]'s"] [parse_zone(affecting.body_zone)]."))
- return 1 //successful heal
- else
- to_chat(user, span_warning("[affecting] is already in good condition!"))
-
///Is the passed in mob a ghost with admin powers, doesn't check for AI interact like isAdminGhost() used to
/proc/isAdminObserver(mob/user)
if(!user) //Are they a mob? Auto interface updates call this with a null src
diff --git a/code/modules/power/cable.dm b/code/modules/power/cable.dm
index 4e6f10fd7b..ea7e968252 100644
--- a/code/modules/power/cable.dm
+++ b/code/modules/power/cable.dm
@@ -525,35 +525,13 @@ GLOBAL_LIST_INIT(cable_coil_recipes, list(new/datum/stack_recipe("cable restrain
pixel_x = base_pixel_x + rand(-2,2)
pixel_y = base_pixel_y + rand(-2,2)
update_appearance()
+ AddElement(/datum/element/robotic_heal, burn_heal = 15)
recipes = GLOB.cable_coil_recipes
-
///////////////////////////////////
// General procedures
///////////////////////////////////
-
-//you can use wires to heal robotics
-/obj/item/stack/cable_coil/attack(mob/living/carbon/human/H, mob/user)
- if(!istype(H))
- return ..()
-
- if(!H.is_exposed(user, TRUE, user.zone_selected))
- return TRUE
-
- var/obj/item/bodypart/affecting = H.get_bodypart(check_zone(user.zone_selected))
- if(affecting && (!IS_ORGANIC_LIMB(affecting)))
- if(user == H)
- user.visible_message(span_notice("[user] starts to fix some of the wires in [H]'s [parse_zone(affecting.body_zone)]."), span_notice("You start fixing some of the wires in [H == user ? "your" : "[H]'s"] [parse_zone(affecting.body_zone)]."))
- if(!do_after(user, 0.5 SECONDS, H))
- return
- if(item_heal_robotic(H, user, 0, 15))
- use(1)
- return
- else
- return ..()
-
-
/obj/item/stack/cable_coil/update_appearance()
. = ..()
icon_state = "[base_icon_state][amount < 3 ? amount : ""]"
diff --git a/code/modules/projectiles/guns/energy/special.dm b/code/modules/projectiles/guns/energy/special.dm
index 9c92e94e99..63f5a72874 100644
--- a/code/modules/projectiles/guns/energy/special.dm
+++ b/code/modules/projectiles/guns/energy/special.dm
@@ -153,6 +153,7 @@
AddComponent(/datum/component/butchering, 25, 105, 0, 'sound/weapons/plasma_cutter.ogg')
AddElement(/datum/element/update_icon_blocker)
AddElement(/datum/element/tool_flash, 1)
+ AddElement(/datum/element/robotic_heal, brute_heal = 15)
/obj/item/gun/energy/plasmacutter/examine(mob/user)
. = ..()
@@ -190,23 +191,6 @@
return TRUE
-/obj/item/gun/energy/plasmacutter/attack(mob/living/carbon/human/target, mob/user)
- if(!istype(target))
- return ..()
- var/obj/item/bodypart/attackedLimb = target.get_bodypart(check_zone(user.zone_selected))
- if(!attackedLimb || IS_ORGANIC_LIMB(attackedLimb) || (user.a_intent == INTENT_HARM))
- return ..()
- if(!target.is_exposed(user, TRUE, user.zone_selected))
- return TRUE
- if(!tool_start_check(user, amount = 1))
- return TRUE
- user.visible_message(span_notice("[user] starts to fix some of the dents on [target]'s [parse_zone(attackedLimb.body_zone)]."),
- span_notice("You start fixing some of the dents on [target == user ? "your" : "[target]'s"] [parse_zone(attackedLimb.body_zone)]."))
- if(!use_tool(target, user, delay = (target == user ? 5 SECONDS : 0.5 SECONDS), amount = 1, volume = 25))
- return TRUE
- item_heal_robotic(target, user, brute_heal = 15, burn_heal = 0)
- return TRUE
-
/obj/item/gun/energy/plasmacutter/use(amount)
return (!QDELETED(cell) && cell.use(amount ? amount * charge_cut : charge_cut))
diff --git a/code/modules/reagents/chemistry/reagents/medical_reagents/ipc_reagents.dm b/code/modules/reagents/chemistry/reagents/medical_reagents/ipc_reagents.dm
index 06fe6fdd90..76f368956d 100644
--- a/code/modules/reagents/chemistry/reagents/medical_reagents/ipc_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medical_reagents/ipc_reagents.dm
@@ -2,15 +2,16 @@
** for some reason
*/
-/datum/reagent/medicine/system_cleaner
+/datum/reagent/space_cleaner/system_cleaner
name = "System Cleaner"
description = "Neutralizes harmful chemical compounds inside synthetic systems."
reagent_state = LIQUID
color = "#F1C40F"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
process_flags = SYNTHETIC
+ robot_clean_power = 10
-/datum/reagent/medicine/system_cleaner/on_mob_life(mob/living/M)
+/datum/reagent/space_cleaner/system_cleaner/on_mob_life(mob/living/M)
M.adjustToxLoss(-2*REM, 0)
. = 1
for(var/datum/reagent/R in M.reagents.reagent_list)
diff --git a/code/modules/reagents/chemistry/reagents/medical_reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medical_reagents/medicine_reagents.dm
index 96a420d9d9..887d5ced8f 100644
--- a/code/modules/reagents/chemistry/reagents/medical_reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medical_reagents/medicine_reagents.dm
@@ -57,7 +57,7 @@
if(prob(5))
var/obj/item/bodypart/open_sore = victim.get_random_bodypart()
if(IS_ORGANIC_LIMB(open_sore))
- open_sore.force_wound_upwards(/datum/wound/slash/moderate)
+ open_sore.force_wound_upwards(/datum/wound/slash/flesh/moderate)
M.emote("gasps")
/datum/reagent/medicine/indomide/on_mob_end_metabolize(mob/living/L)
diff --git a/code/modules/reagents/chemistry/reagents/medical_reagents/wound_reagents.dm b/code/modules/reagents/chemistry/reagents/medical_reagents/wound_reagents.dm
index fa644a7a25..14da07350a 100644
--- a/code/modules/reagents/chemistry/reagents/medical_reagents/wound_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medical_reagents/wound_reagents.dm
@@ -19,7 +19,7 @@
if(31 to INFINITY)
C.AdjustSleeping(40)
//formerly everything-fixing juice
- for(var/datum/wound/blunt/broken_bone in C.all_wounds)
+ for(var/datum/wound/blunt/bone/broken_bone in C.all_wounds)
broken_bone.remove_wound()
for(var/obj/item/organ/O in C.internal_organs)
O.damage = 0
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 41246e0caa..96eafdb2f9 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -875,6 +875,7 @@
color = "#A5F0EE" // rgb: 165, 240, 238
taste_description = "sourness"
reagent_weight = 0.6 //so it sprays further
+ var/robot_clean_power = 2
var/clean_types = CLEAN_WASH
/datum/reagent/space_cleaner/expose_obj(obj/O, reac_volume)
@@ -900,6 +901,7 @@
description = "A powerful, acidic cleaner sold by Waffle Co. Affects organic matter while leaving other objects unaffected."
metabolization_rate = 1.5 * REAGENTS_METABOLISM
taste_description = "acid"
+ robot_clean_power = 15
/datum/reagent/space_cleaner/ez_clean/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(3.33)
@@ -2200,6 +2202,7 @@
self_consuming = TRUE
taste_description = "pure determination"
overdose_threshold = 45
+ process_flags = ALL
/// Whether we've had at least WOUND_DETERMINATION_SEVERE (2.5u) of determination at any given time. No damage slowdown immunity or indication we're having a second wind if it's just a single moderate wound
var/significant = FALSE
diff --git a/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm b/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm
index c48352567a..803c8ffdf4 100644
--- a/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/trickwine_reagents.dm
@@ -356,9 +356,9 @@
var/mob/living/carbon/guy_who_probably_got_shot = M
if(prob(20) && length(guy_who_probably_got_shot.all_wounds))
to_chat(M, span_warning("Your cuts and punctures sear for a second, before ceasing their bloody flow!"))
- for(var/datum/wound/slash/cut in guy_who_probably_got_shot.all_wounds)
+ for(var/datum/wound/slash/flesh/cut in guy_who_probably_got_shot.all_wounds)
cut.remove_wound()
- for(var/datum/wound/pierce/hole in guy_who_probably_got_shot.all_wounds)
+ for(var/datum/wound/pierce/bleed/hole in guy_who_probably_got_shot.all_wounds)
hole.remove_wound()
if(prob(10) && length(guy_who_probably_got_shot.all_wounds))
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index 221c20ee2d..1350c6b66e 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -257,10 +257,6 @@
required_temp = 207
is_cold_recipe = TRUE
-/datum/chemical_reaction/system_cleaner
- results = list(/datum/reagent/medicine/system_cleaner = 4)
- required_reagents = list(/datum/reagent/consumable/ethanol = 1, /datum/reagent/chlorine = 1, /datum/reagent/phenol = 2, /datum/reagent/potassium = 1)
-
/datum/chemical_reaction/liquid_solder
results = list(/datum/reagent/medicine/liquid_solder = 3)
required_reagents = list(/datum/reagent/consumable/ethanol = 1, /datum/reagent/copper = 1, /datum/reagent/silver = 1)
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index 897c833214..039ad16e5c 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -340,6 +340,10 @@
results = list(/datum/reagent/space_cleaner = 2)
required_reagents = list(/datum/reagent/ammonia = 1, /datum/reagent/water = 1)
+/datum/chemical_reaction/system_cleaner
+ results = list(/datum/reagent/space_cleaner/system_cleaner = 4)
+ required_reagents = list(/datum/reagent/consumable/ethanol = 1, /datum/reagent/chlorine = 1, /datum/reagent/phenol = 2, /datum/reagent/potassium = 1)
+
/datum/chemical_reaction/plantbgone
results = list(/datum/reagent/toxin/plantbgone = 5)
required_reagents = list(/datum/reagent/toxin = 1, /datum/reagent/water = 4)
diff --git a/code/modules/surgery/bodyparts/bodyparts.dm b/code/modules/surgery/bodyparts/bodyparts.dm
index f8fba4a545..427e38d4bc 100644
--- a/code/modules/surgery/bodyparts/bodyparts.dm
+++ b/code/modules/surgery/bodyparts/bodyparts.dm
@@ -15,6 +15,9 @@
///List of bodytypes flags, important for fitting clothing. If you'd like to know if a bodypart is organic, please use is_organic_limb()
var/bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC
+ ///The types of wounds this bodypart is capable of receiving.
+ var/biological_state = BIO_STANDARD_UNJOINTED
+
///Whether the clothing being worn forces the limb into being "squished" to plantigrade/standard humanoid compliance
var/plantigrade_forced = FALSE
///Whether the limb is husked
@@ -120,6 +123,8 @@
var/list/scars
/// Our current stored wound damage multiplier
var/wound_damage_multiplier = 1
+ /// The amount of damage on this limb that cannot be healed until the wounds causing it are fixed
+ var/wound_integrity_loss = 0
/// This number is subtracted from all wound rolls on this bodypart, higher numbers mean more defense, negative means easier to wound
var/wound_resistance = 0
@@ -142,6 +147,16 @@
//band-aid for blood overlays & other external overlays until they get refactored
var/stored_icon_state
+ /// In the case we dont have dismemberable features, or literally cant get wounds, we will use this percent to determine when we can be dismembered.
+ /// Compared to our ABSOLUTE maximum. Stored in decimal; 0.8 = 80%.
+ var/hp_percent_to_dismemberable = 0.8
+ /// If true, we will use [hp_percent_to_dismemberable] even if we are dismemberable via wounds. Useful for things with extreme wound resistance.
+ var/use_alternate_dismemberment_calc_even_if_mangleable = FALSE
+ /// If false, no wound that can be applied to us can mangle our exterior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment.
+ var/any_existing_wound_can_mangle_our_exterior
+ /// If false, no wound that can be applied to us can mangle our interior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment.
+ var/any_existing_wound_can_mangle_our_interior
+
/obj/item/bodypart/Initialize()
. = ..()
if(dynamic_rename)
@@ -238,12 +253,14 @@
I.forceMove(T)
///since organs aren't actually stored in the bodypart themselves while attached to a person, we have to query the owner for what we should have
-/obj/item/bodypart/proc/get_organs()
+/obj/item/bodypart/proc/get_organs(required_status)
if(!owner)
return FALSE
var/list/bodypart_organs
for(var/obj/item/organ/organ_check as anything in owner.internal_organs) //internal organs inside the dismembered limb are dropped.
+ if(required_status && required_status != organ_check.status)
+ continue
if(check_zone(organ_check.zone) == body_zone)
LAZYADD(bodypart_organs, organ_check) // this way if we don't have any, it'll just return null
@@ -260,7 +277,7 @@
//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all.
//Damage will not exceed max_damage using this proc
//Cannot apply negative damage
-/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null)
+/obj/item/bodypart/proc/receive_damage(brute = 0, burn = 0, stamina = 0, blocked = 0, updating_health = TRUE, required_status = null, wound_bonus = 0, bare_wound_bonus = 0, sharpness = SHARP_NONE, attack_direction = null, ignore_reduction = 0)
var/hit_percent = (100-blocked)/100
if((!brute && !burn && !stamina) || hit_percent <= 0)
return FALSE
@@ -275,8 +292,8 @@
brute = round(max(brute * dmg_mlt, 0),DAMAGE_PRECISION)
burn = round(max(burn * dmg_mlt, 0),DAMAGE_PRECISION)
stamina = round(max(stamina * dmg_mlt, 0),DAMAGE_PRECISION)
- brute = max(0, brute - brute_reduction)
- burn = max(0, burn - burn_reduction)
+ brute = max(0, brute - max(brute_reduction - ignore_reduction, 0))
+ burn = max(0, burn - max(burn_reduction - ignore_reduction, 0))
if(!brute && !burn && !stamina)
return FALSE
@@ -288,69 +305,8 @@
if(ALIEN_BODYPART,LARVA_BODYPART) //aliens take double burn //nothing can burn with so much snowflake code around
burn *= 2
- //START WOUND HANDLING
-
- // what kind of wounds we're gonna roll for, take the greater between brute and burn, then if it's brute, we subdivide based on sharpness
- var/wounding_type = (brute > burn ? WOUND_BLUNT : WOUND_BURN)
- var/wounding_dmg = max(brute, burn)
-
- var/mangled_state = get_mangled_state()
- var/bio_state = owner.get_biological_state()
- var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
-
- if(wounding_type == WOUND_BLUNT && sharpness)
- wounding_type = (sharpness == SHARP_EDGED ? WOUND_SLASH : WOUND_PIERCE)
-
- //Handling for bone only/flesh only(none right now)/flesh and bone targets
- switch(bio_state)
- // if we're bone only, all cutting attacks go straight to the bone
- if(BIO_JUST_BONE)
- if(wounding_type == WOUND_SLASH)
- wounding_type = WOUND_BLUNT
- wounding_dmg *= (easy_dismember ? 1 : 0.6)
- else if(wounding_type == WOUND_PIERCE)
- wounding_type = WOUND_BLUNT
- wounding_dmg *= (easy_dismember ? 1 : 0.75)
- if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
- return
- // if we're flesh only, all blunt attacks become weakened slashes in terms of wound damage
- if(BIO_JUST_FLESH)
- if(wounding_type == WOUND_BLUNT)
- wounding_type = WOUND_SLASH
- wounding_dmg *= (easy_dismember ? 1 : 0.3)
- if((mangled_state & BODYPART_MANGLED_FLESH) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
- return
- // standard humanoids
- if(BIO_FLESH_BONE)
- // If the bodypart is not mangled, and its a limb, we have a chance we hit a muscle
- if(mangled_state != BODYPART_MANGLED_BOTH && body_zone != BODY_ZONE_CHEST && body_zone != BODY_ZONE_HEAD && prob(MUSCLE_WOUND_CHANCE))
- wounding_type = WOUND_MUSCLE
- // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
- // So a big sharp weapon is still all you need to destroy a limb
- else if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
- playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
- if(wounding_type == WOUND_SLASH && !easy_dismember)
- wounding_dmg *= 0.4
- if(wounding_type == WOUND_PIERCE && !easy_dismember)
- wounding_dmg *= 0.6
- wounding_type = WOUND_BLUNT
- else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
- return
-
- // now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage
- if(owner && wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND)
- if(current_gauze)
- current_gauze.take_damage()
- if(current_splint)
- current_splint.take_damage()
- check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus, attack_direction)
-
- for(var/datum/wound/iter_wound as anything in wounds)
- iter_wound.receive_damage(wounding_type, wounding_dmg, wound_bonus)
-
- /*
- // END WOUND HANDLING
- */
+ if(wound_roll(brute, burn, wound_bonus, bare_wound_bonus, sharpness, attack_direction))
+ return // stop here if dismembered
//back to our regularly scheduled program, we now actually apply damage if there's room below limb damage cap
var/can_inflict = max_damage - get_damage()
@@ -381,46 +337,49 @@
. = TRUE
return update_bodypart_damage_state() || .
-/// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds
-/obj/item/bodypart/proc/painless_wound_roll(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=SHARP_NONE)
- if(!owner || phantom_wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND)
+/// Rolls for wounds, returning TRUE if the limb was dismembered.
+/obj/item/bodypart/proc/wound_roll(brute, burn, wound_bonus = 0, bare_wound_bonus = 0, sharpness=SHARP_NONE, attack_direction, no_dismember = FALSE)
+ if(wound_bonus == CANT_WOUND)
return
- var/mangled_state = get_mangled_state()
- var/bio_state = owner.get_biological_state()
- var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
-
- if(wounding_type == WOUND_BLUNT && sharpness)
- wounding_type = (sharpness == SHARP_EDGED ? WOUND_SLASH : WOUND_PIERCE)
-
- //Handling for bone only/flesh only(none right now)/flesh and bone targets
- switch(bio_state)
- // if we're bone only, all cutting attacks go straight to the bone
- if(BIO_JUST_BONE)
- if(wounding_type == WOUND_SLASH)
- wounding_type = WOUND_BLUNT
- phantom_wounding_dmg *= (easy_dismember ? 1 : 0.6)
- else if(wounding_type == WOUND_PIERCE)
- wounding_type = WOUND_BLUNT
- phantom_wounding_dmg *= (easy_dismember ? 1 : 0.75)
- if((mangled_state & BODYPART_MANGLED_BONE) && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
- return
- // note that there's no handling for BIO_JUST_FLESH since we don't have any that are that right now (slimepeople maybe someday)
- // standard humanoids
- if(BIO_FLESH_BONE)
- // if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
- // So a big sharp weapon is still all you need to destroy a limb
- if(mangled_state == BODYPART_MANGLED_FLESH && sharpness)
- playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
- if(wounding_type == WOUND_SLASH && !easy_dismember)
- phantom_wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area
- if(wounding_type == WOUND_PIERCE && !easy_dismember)
- phantom_wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
- wounding_type = WOUND_BLUNT
- else if(mangled_state == BODYPART_MANGLED_BOTH && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
- return
+ // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
+ var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) || HAS_TRAIT(src, TRAIT_EASYDISMEMBER)
+
+ /// Associated list of each wound type and how much effective wounding damage can be done of that type.
+ var/list/wounding_types
+ switch(sharpness)
+ if(SHARP_NONE)
+ if(brute)
+ LAZYSET(wounding_types, WOUND_BLUNT, brute)
+ if(SHARP_EDGED)
+ LAZYSET(wounding_types, WOUND_SLASH, brute + burn)
+ if(brute)
+ LAZYSET(wounding_types, WOUND_BLUNT, brute * (easy_dismember ? 1 : 0.6))
+ if(SHARP_POINTY)
+ LAZYSET(wounding_types, WOUND_PIERCE, brute + burn)
+ if(brute)
+ LAZYSET(wounding_types, WOUND_BLUNT, brute * (easy_dismember ? 1 : 0.6))
+ if(burn)
+ LAZYSET(wounding_types, WOUND_BURN, burn)
- check_wounding(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)
+ if(!no_dismember && (dismemberable_by_wound() || dismemberable_by_total_damage()) && try_dismember(wounding_types, wound_bonus, bare_wound_bonus))
+ return TRUE
+
+ var/highest_damage = 0
+ // now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage
+ if(LAZYLEN(wounding_types))
+ if(current_gauze)
+ current_gauze.take_damage()
+ if(current_splint)
+ current_splint.take_damage()
+ for(var/wound_type in wounding_types)
+ if(!owner)
+ break
+ highest_damage = max(highest_damage, wounding_types[wound_type])
+ check_wounding(wounding_types, wound_bonus, bare_wound_bonus, attack_direction, no_dismember)
+
+ for(var/datum/wound/iter_wound as anything in wounds)
+ iter_wound.receive_damage(wounding_types, highest_damage, wound_bonus)
/**
* check_wounding() is where we handle rolling for, selecting, and applying a wound if we meet the criteria
@@ -434,76 +393,198 @@
* * wound_bonus- The wound_bonus of an attack
* * bare_wound_bonus- The bare_wound_bonus of an attack
*/
-/obj/item/bodypart/proc/check_wounding(woundtype, damage, wound_bonus, bare_wound_bonus, attack_direction)
+/obj/item/bodypart/proc/check_wounding(list/wounding_types, wound_bonus, bare_wound_bonus, attack_direction, no_dismember = FALSE)
+ var/damage_mult = 1
+
// note that these are fed into an exponent, so these are magnified
if(HAS_TRAIT(owner, TRAIT_EASILY_WOUNDED))
- damage *= 1.5
- else
- damage = min(damage, WOUND_MAX_CONSIDERED_DAMAGE)
+ damage_mult *= 1.5
if(HAS_TRAIT(owner,TRAIT_HARDLY_WOUNDED))
- damage *= 0.85
+ damage_mult *= 0.85
if(HAS_TRAIT(owner, TRAIT_VERY_HARDLY_WOUNDED))
- damage *= 0.6
+ damage_mult *= 0.6
if(HAS_TRAIT(owner, TRAIT_EASYDISMEMBER))
- damage *= 1.1
-
- var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT))
- var/injury_roll = base_roll
- injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus)
- var/list/wounds_checking = GLOB.global_wound_types[woundtype]
-
- if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100))
+ damage_mult *= 1.1
+
+ var/wounding_mods = check_woundings_mods(wound_bonus, bare_wound_bonus)
+ var/highest_roll = 0
+ var/highest_wound_type
+ var/list/wound_rolls = list()
+ for(var/wounding_type in wounding_types)
+ for(var/wounding_series in SSwounds.types_to_series[wounding_type])
+ var/injury_roll = rand(1, round(min(wounding_types[wounding_type], WOUND_MAX_CONSIDERED_DAMAGE) * damage_mult) ** WOUND_DAMAGE_EXPONENT) + wounding_mods
+ if(injury_roll > highest_roll)
+ highest_wound_type = wounding_type
+ highest_roll = highest_roll
+ wound_rolls[wounding_series] = max(injury_roll, wound_rolls[wounding_series])
+ if(!wound_rolls.len)
+ CRASH("check_wounding called with a null wounding_types list!")
+ if(!no_dismember && highest_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100))
var/datum/wound/loss/dismembering = new
- dismembering.apply_dismember(src, woundtype, outright = TRUE, attack_direction = attack_direction)
+ dismembering.apply_dismember(src, highest_wound_type, outright = TRUE, attack_direction = attack_direction)
return
+ var/list/series_wounding_mods = check_series_wounding_mods()
+
+ var/list/datum/wound/possible_wounds = list()
+ for(var/datum/wound/wound_type as anything in SSwounds.pregen_data)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[wound_type]
+ if(pregen_data.can_be_applied_to(src, wounding_types, random_roll = TRUE))
+ possible_wounds[wound_type] = pregen_data.get_weight(src, wounding_types, attack_direction)
+
// quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow
if(ishuman(owner))
var/mob/living/carbon/human/human_wearer = owner
var/list/clothing = human_wearer.get_clothing_on_part(src)
for(var/obj/item/clothing/clothes_check as anything in clothing)
// unlike normal armor checks, we tabluate these piece-by-piece manually so we can also pass on appropriate damage the clothing's limbs if necessary
- if(clothes_check.armor.getRating("wound"))
+ if(clothes_check.armor.getRating(WOUND))
bare_wound_bonus = 0
break
- //cycle through the wounds of the relevant category from the most severe down
- for(var/PW in wounds_checking)
- var/datum/wound/possible_wound = PW
+ for(var/datum/wound/iterated_path as anything in possible_wounds)
+ for(var/datum/wound/existing_wound as anything in wounds)
+ if(iterated_path == existing_wound.type)
+ possible_wounds -= iterated_path
+ break // breaks out of the nested loop
+
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[iterated_path]
+ var/specific_injury_roll = (wound_rolls[pregen_data.wound_series] + series_wounding_mods[pregen_data.wound_series])
+ if(pregen_data.get_threshold_for(src, attack_direction) > specific_injury_roll)
+ possible_wounds -= iterated_path
+ continue
+
+ if(pregen_data.compete_for_wounding)
+ for(var/datum/wound/other_path as anything in possible_wounds)
+ if(other_path == iterated_path)
+ continue
+ if(iterated_path::severity == other_path::severity && pregen_data.overpower_wounds_of_even_severity)
+ possible_wounds -= other_path
+ continue
+ else if(pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_LESSERS)
+ if(iterated_path::severity > other_path::severity)
+ possible_wounds -= other_path
+ continue
+ else if(pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_GREATERS)
+ if(iterated_path::severity < other_path::severity)
+ possible_wounds -= other_path
+ continue
+
+ var/list/datum/wound/applied_wounds
+ while(length(possible_wounds))
+ var/datum/wound/possible_wound = pick_weight(possible_wounds)
+ var/datum/wound_pregen_data/possible_pregen_data = SSwounds.pregen_data[possible_wound]
+ possible_wounds -= possible_wound
+
+ // this makes muscle wounds show up less consistently at the same time as others
+ if(LAZYLEN(applied_wounds) && !prob(possible_wounds[possible_wound]))
+ continue
+
var/datum/wound/replaced_wound
for(var/i in wounds)
var/datum/wound/existing_wound = i
- if(existing_wound.type in wounds_checking)
- if(existing_wound.severity >= initial(possible_wound.severity))
- return
+ var/datum/wound_pregen_data/existing_pregen_data = SSwounds.pregen_data[existing_wound.type]
+ if(existing_pregen_data.wound_series == possible_pregen_data.wound_series)
+ if(existing_wound.severity >= possible_wound::severity)
+ continue
else
replaced_wound = existing_wound
- if(initial(possible_wound.threshold_minimum) < injury_roll)
- var/datum/wound/new_wound
- if(replaced_wound)
- new_wound = replaced_wound.replace_wound(possible_wound, attack_direction = attack_direction)
- log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned
- else
- new_wound = new possible_wound
- new_wound.apply_wound(src, attack_direction = attack_direction)
- log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll)
- return new_wound
+ // if we get through this whole loop without continuing, we found our winner
+ var/datum/wound/new_wound = new possible_wound
+ if(replaced_wound)
+ new_wound = replaced_wound.replace_wound(new_wound, attack_direction = attack_direction)
+ else
+ new_wound.apply_wound(src, attack_direction = attack_direction)
+ LAZYADD(applied_wounds, new_wound)
+
+ if(LAZYLEN(applied_wounds)) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned
+ log_wound(owner, src, applied_wounds, wounding_types, wound_bonus, bare_wound_bonus, wound_rolls)
+ return applied_wounds
// try forcing a specific wound, but only if there isn't already a wound of that severity or greater for that type on this bodypart
-/obj/item/bodypart/proc/force_wound_upwards(specific_woundtype, smited = FALSE)
- var/datum/wound/potential_wound = specific_woundtype
+/obj/item/bodypart/proc/force_wound_upwards(datum/wound/potential_wound, smited = FALSE, wound_source)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ if (isnull(potential_wound))
+ return
+
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[potential_wound]
for(var/datum/wound/existing_wound as anything in wounds)
- if(existing_wound.wound_type == initial(potential_wound.wound_type))
- if(existing_wound.severity < initial(potential_wound.severity)) // we only try if the existing one is inferior to the one we're trying to force
- existing_wound.replace_wound(potential_wound, smited)
+ var/datum/wound_pregen_data/existing_pregen_data = existing_wound.get_pregen_data()
+ if(existing_pregen_data.wound_series == pregen_data.wound_series)
+ if(existing_wound.severity < potential_wound::severity) // we only try if the existing one is inferior to the one we're trying to force
+ existing_wound.replace_wound(new potential_wound, smited)
return
var/datum/wound/new_wound = new potential_wound
new_wound.apply_wound(src, smited = smited)
+ return new_wound
+
+/**
+ * A simple proc to force a type of wound onto this mob. If you just want to force a specific mainline (fractures, bleeding, etc.) wound, you only need to care about the first 3 args.
+ *
+ * Args:
+ * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list.
+ * * obj/item/bodypart/limb: The limb we wil be applying the wound to. If null, a random bodypart will be picked.
+ * * min_severity: The minimum severity that will be considered.
+ * * max_severity: The maximum severity that will be considered.
+ * * severity_pick_mode: The "pick mode" to be used. See get_corresponding_wound_type's documentation
+ * * wound_source: The source of the wound to be applied. Nullable.
+ *
+ * For the rest of the args, refer to get_corresponding_wound_type().
+ *
+ * Returns:
+ * A new wound instance if the application was successful, null otherwise.
+*/
+/mob/living/carbon/proc/cause_wound_of_type_and_severity(wounding_type, obj/item/bodypart/limb, min_severity, max_severity = min_severity, severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY, wound_source)
+ if(isnull(limb))
+ limb = pick(bodyparts)
+
+ var/list/type_list = wounding_type
+ if(!islist(type_list))
+ type_list = list(type_list)
+
+ var/datum/wound/corresponding_typepath = SSwounds.get_corresponding_wound_type(type_list, limb, min_severity, max_severity, severity_pick_mode)
+ if(corresponding_typepath)
+ return limb.force_wound_upwards(corresponding_typepath, wound_source = wound_source)
+
+/// Limb is nullable, but picks a random one. Defers to limb.get_wound_threshold_of_wound_type, see it for documentation.
+/mob/living/carbon/proc/get_wound_threshold_of_wound_type(wounding_type, severity, default, obj/item/bodypart/limb, wound_source)
+ if(isnull(limb))
+ limb = pick(bodyparts)
+
+ if(!limb)
+ return default
+
+ return limb.get_wound_threshold_of_wound_type(wounding_type, severity, default, wound_source)
+
+/**
+ * A simple proc that gets the best wound to fit the criteria laid out, then returns its wound threshold.
+ *
+ * Args:
+ * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list of wounding_types.
+ * * severity: The severity that will be considered.
+ * * return_value_if_no_wound: If no wound is found, we will return this instead. (It is reccomended to use named args for this one, as its unclear what it is without)
+ * * wound_source: The theoretical source of the wound. Nullable.
+ *
+ * Returns:
+ * return_value_if_no_wound if no wound is found - if one IS found, the wound threshold for that wound.
+ */
+/obj/item/bodypart/proc/get_wound_threshold_of_wound_type(wounding_type, severity, return_value_if_no_wound, wound_source)
+ var/list/type_list = wounding_type
+ if(!islist(type_list))
+ type_list = list(type_list)
+
+ var/datum/wound/wound_path = SSwounds.get_corresponding_wound_type(type_list, src, severity, duplicates_allowed = TRUE, care_about_existing_wounds = FALSE)
+ if(wound_path)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[wound_path]
+ return pregen_data.get_threshold_for(src, damage_source = wound_source)
+
+ return return_value_if_no_wound
/**
* check_wounding_mods() is where we handle the various modifiers of a wound roll
@@ -515,7 +596,7 @@
* Arguments:
* * It's the same ones on [/obj/item/bodypart/proc/receive_damage]
*/
-/obj/item/bodypart/proc/check_woundings_mods(wounding_type, damage, wound_bonus, bare_wound_bonus)
+/obj/item/bodypart/proc/check_woundings_mods(wound_bonus, bare_wound_bonus)
var/armor_ablation = 0
var/injury_mod = 0
@@ -532,28 +613,104 @@
injury_mod -= armor_ablation
injury_mod += wound_bonus
- for(var/datum/wound/W as anything in wounds)
- if(W.wound_type == wounding_type)
- injury_mod += W.threshold_penalty
+ for(var/datum/wound/wound as anything in wounds)
+ injury_mod += wound.threshold_penalty
var/part_mod = -wound_resistance
- if(get_damage(TRUE) >= max_damage)
+ if(bodypart_disabled >= max_damage)
part_mod += disabled_wound_penalty
injury_mod += part_mod
return injury_mod
+/// Returns a bitflag using ANATOMY_EXTERIOR or ANATOMY_INTERIOR. Used to determine if we as a whole have a interior or exterior biostate, or both.
+/obj/item/bodypart/proc/get_bio_state_status()
+ SHOULD_BE_PURE(TRUE)
+
+ var/bio_status = NONE
+
+ for (var/state as anything in SSwounds.bio_state_anatomy)
+ var/flag = text2num(state)
+ if (!(biological_state & flag))
+ continue
+
+ var/value = SSwounds.bio_state_anatomy[state]
+ if (value & ANATOMY_EXTERIOR)
+ bio_status |= ANATOMY_EXTERIOR
+ if (value & ANATOMY_INTERIOR)
+ bio_status |= ANATOMY_INTERIOR
+
+ if ((bio_status & ANATOMY_EXTERIOR_AND_INTERIOR) == ANATOMY_EXTERIOR_AND_INTERIOR)
+ break
+
+ return bio_status
+
+/// Returns if our current mangling status allows us to be dismembered. Requires both no exterior/mangled exterior and no interior/mangled interior.
+/obj/item/bodypart/proc/dismemberable_by_wound()
+ SHOULD_BE_PURE(TRUE)
+
+ var/mangled_state = get_mangled_state()
+
+ var/bio_status = get_bio_state_status()
+
+ var/has_exterior = ((bio_status & ANATOMY_EXTERIOR))
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
+
+ var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR)))
+ var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR)))
+
+ return (exterior_ready_to_dismember && interior_ready_to_dismember)
+
+/// Returns TRUE if our total percent damage is more or equal to our dismemberable percentage, but FALSE if a wound can cause us to be dismembered.
+/obj/item/bodypart/proc/dismemberable_by_total_damage()
+
+ update_wound_theory()
+
+ var/bio_status = get_bio_state_status()
+
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
+ var/can_theoretically_be_dismembered_by_wound = (any_existing_wound_can_mangle_our_interior || (any_existing_wound_can_mangle_our_exterior && has_interior))
+
+ var/wound_dismemberable = dismemberable_by_wound()
+ var/ready_to_use_alternate_formula = (use_alternate_dismemberment_calc_even_if_mangleable || (!wound_dismemberable && !can_theoretically_be_dismembered_by_wound))
+
+ if (ready_to_use_alternate_formula)
+ var/percent_to_total_max = (get_damage() / max_damage)
+ if (percent_to_total_max >= hp_percent_to_dismemberable)
+ return TRUE
+
+ return FALSE
+
+/// Updates our "can be theoretically dismembered by wounds" variables by iterating through all wound static data.
+/obj/item/bodypart/proc/update_wound_theory()
+ // We put this here so we dont increase init time by doing this all at once on initialization
+ // Effectively, we "lazy load"
+ if (isnull(any_existing_wound_can_mangle_our_interior) || isnull(any_existing_wound_can_mangle_our_exterior))
+ any_existing_wound_can_mangle_our_interior = FALSE
+ any_existing_wound_can_mangle_our_exterior = FALSE
+ for (var/datum/wound/wound_type as anything in SSwounds.pregen_data)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[wound_type]
+ if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific
+ continue
+ if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_EXTERIOR)
+ any_existing_wound_can_mangle_our_exterior = TRUE
+ if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_INTERIOR)
+ any_existing_wound_can_mangle_our_interior = TRUE
+
+ if (any_existing_wound_can_mangle_our_interior && any_existing_wound_can_mangle_our_exterior)
+ break
+
//Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all.
-//Damage cannot go below zero.
+//Damage cannot go below zero, or min_damage.
//Cannot remove negative damage (i.e. apply damage)
/obj/item/bodypart/proc/heal_damage(brute, burn, stamina, required_status, updating_health = TRUE)
if(required_status && !(bodytype & required_status)) //So we can only heal certain kinds of limbs, ie robotic vs organic.
return
if(brute)
- set_brute_dam(round(max(brute_dam - brute, 0), DAMAGE_PRECISION))
+ set_brute_dam(round(max(brute_dam - brute, wound_integrity_loss - burn_dam, 0), DAMAGE_PRECISION))
if(burn)
- set_burn_dam(round(max(burn_dam - burn, 0), DAMAGE_PRECISION))
+ set_burn_dam(round(max(burn_dam - burn, wound_integrity_loss - brute_dam, 0), DAMAGE_PRECISION))
if(stamina)
set_stamina_dam(round(max(stamina_dam - stamina, 0), DAMAGE_PRECISION))
if(owner)
@@ -979,6 +1136,19 @@
drop_organs()
qdel(src)
+/// Should return an assoc list of (wound_series -> penalty). Will be used in determining series-specific penalties for wounding.
+/obj/item/bodypart/proc/check_series_wounding_mods()
+ RETURN_TYPE(/list)
+
+ var/list/series_mods = list()
+
+ for (var/datum/wound/iterated_wound as anything in wounds)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[iterated_wound.type]
+
+ series_mods[pregen_data.wound_series] += iterated_wound.series_threshold_penalty
+
+ return series_mods
+
/// Get whatever wound of the given type is currently attached to this limb, if any
/obj/item/bodypart/proc/get_wound_type(checking_type)
if(isnull(wounds))
@@ -998,12 +1168,15 @@
*/
/obj/item/bodypart/proc/update_wounds(replaced = FALSE)
var/dam_mul = 1 //initial(wound_damage_multiplier)
+ var/integrity_mul = 0
-// we can (normally) only have one wound per type, but remember there's multiple types (smites like :B:loodless can generate multiple cuts on a limb)
+ // we can (normally) only have one wound per type, but remember there's multiple types (smites like :B:loodless can generate multiple cuts on a limb)
for(var/datum/wound/iter_wound as anything in wounds)
dam_mul *= iter_wound.damage_mulitplier_penalty
+ integrity_mul += iter_wound.limb_integrity_penalty
wound_damage_multiplier = dam_mul
+ wound_integrity_loss = min(max_damage, WOUND_MAX_INTEGRITY_CONSIDERED) * integrity_mul
/**
* Calculates how much blood this limb is losing per life tick
@@ -1083,6 +1256,11 @@
#undef BLEED_OVERLAY_MED
#undef BLEED_OVERLAY_GUSH
+/obj/item/bodypart/proc/can_bleed()
+ SHOULD_BE_PURE(TRUE)
+
+ return ((biological_state & BIO_BLOODED) && (!owner || !HAS_TRAIT(owner, TRAIT_NOBLOOD)))
+
/**
* apply_gauze() is used to- well, apply gauze to a bodypart
*
@@ -1113,3 +1291,23 @@
return
current_splint = new new_splint.splint_type(src)
new_splint.use(1)
+
+/* NOTE: it makes absolutely NO sense for wires to be "external," it should likely be renamed to hard/soft materials but i'm too lazy to do that right now */
+
+/// Returns the generic description of our BIO_EXTERNAL feature(s), prioritizing certain ones over others. Returns error on failure.
+/obj/item/bodypart/proc/get_external_description()
+ if (biological_state & BIO_FLESH)
+ return "flesh"
+ if (biological_state & BIO_WIRED)
+ return "wiring"
+
+ return "error"
+
+/// Returns the generic description of our BIO_INTERNAL feature(s), prioritizing certain ones over others. Returns error on failure.
+/obj/item/bodypart/proc/get_internal_description()
+ if (biological_state & BIO_BONE)
+ return "bone"
+ if (biological_state & BIO_METAL)
+ return "metal"
+
+ return "error"
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 9aaea11e67..e182f2ae68 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -1,36 +1,41 @@
/obj/item/bodypart/proc/can_dismember()
+ if(owner && HAS_TRAIT(owner, TRAIT_NODISMEMBER))
+ return FALSE
return dismemberable
//Dismember a limb
-/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE)
+/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE, wounding_type)
if(!owner || !dismemberable)
return FALSE
- var/mob/living/carbon/C = owner
- if(C.status_flags & GODMODE)
+ var/mob/living/carbon/limb_owner = owner
+ if(limb_owner.status_flags & GODMODE)
return FALSE
- if(HAS_TRAIT(C, TRAIT_NODISMEMBER))
+ if(HAS_TRAIT(limb_owner, TRAIT_NODISMEMBER))
return FALSE
- var/obj/item/bodypart/affecting = C.get_bodypart(BODY_ZONE_CHEST)
+ var/obj/item/bodypart/affecting = limb_owner.get_bodypart(BODY_ZONE_CHEST)
affecting.receive_damage(clamp(brute_dam/2 * affecting.body_damage_coeff, 15, 50), clamp(burn_dam/2 * affecting.body_damage_coeff, 0, 50), wound_bonus=CANT_WOUND) //Damage the chest based on limb's existing damage
if(!silent)
- C.visible_message(span_danger("
[C]'s [name] sails off in a bloody arc!"))
+ limb_owner.visible_message(span_danger("
[limb_owner]'s [name] sails off in a bloody arc!"))
- if(C.stat <= SOFT_CRIT)//No more screaming while unconsious
+ if(limb_owner.stat <= SOFT_CRIT)//No more screaming while unconsious
if(IS_ORGANIC_LIMB(affecting))//Chest is a good indicator for if a carbon is robotic in nature or not.
- if(!HAS_TRAIT(C, TRAIT_ANALGESIA)) //and do we actually feel pain?
- INVOKE_ASYNC(C, TYPE_PROC_REF(/mob, emote), "scream")
+ if(!HAS_TRAIT(limb_owner, TRAIT_ANALGESIA)) //and do we actually feel pain?
+ INVOKE_ASYNC(limb_owner, TYPE_PROC_REF(/mob, emote), "scream")
- playsound(get_turf(C), 'sound/effects/wounds/dismember.ogg', 80, TRUE)
- SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "dismembered", /datum/mood_event/dismembered)
+ playsound(get_turf(limb_owner), 'sound/effects/wounds/dismember.ogg', 80, TRUE)
+ SEND_SIGNAL(limb_owner, COMSIG_ADD_MOOD_EVENT, "dismembered", /datum/mood_event/dismembered)
+
+ if(wounding_type)
+ LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wounding_type)
drop_limb()
- C.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment
- var/turf/location = C.loc
- if(istype(location))
- C.add_splatter_floor(location)
+ limb_owner.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment
+ var/turf/location = limb_owner.loc
+ if(wounding_type != WOUND_BURN && istype(location) && can_bleed())
+ limb_owner.add_splatter_floor(location)
if(QDELETED(src)) //Could have dropped into lava/explosion/chasm/whatever
return TRUE
@@ -38,8 +43,9 @@
burn()
return TRUE
- add_mob_blood(C)
- C.bleed(rand(20, 40))
+ if(can_bleed())
+ add_mob_blood(limb_owner)
+ limb_owner.bleed(rand(20, 40))
var/direction = pick(GLOB.cardinals)
var/t_range = rand(2,max(throw_range/2, 2))
@@ -56,27 +62,27 @@
return TRUE
-/obj/item/bodypart/chest/dismember()
+/obj/item/bodypart/chest/dismember(dam_type = BRUTE, silent = TRUE, wound_type)
if(!owner)
return FALSE
- var/mob/living/carbon/C = owner
+ var/mob/living/carbon/chest_owner = owner
if(!dismemberable)
return FALSE
- if(HAS_TRAIT(C, TRAIT_NODISMEMBER))
+ if(HAS_TRAIT(chest_owner, TRAIT_NODISMEMBER))
return FALSE
. = list()
- var/turf/T = get_turf(C)
- C.add_splatter_floor(T)
- playsound(get_turf(C), 'sound/misc/splort.ogg', 80, TRUE)
- for(var/obj/item/organ/O as anything in C.internal_organs)
+ if(wound_type != WOUND_BURN && isturf(chest_owner.loc) && can_bleed())
+ chest_owner.add_splatter_floor(chest_owner.loc)
+ playsound(get_turf(chest_owner), 'sound/misc/splort.ogg', 80, TRUE)
+ for(var/obj/item/organ/O as anything in chest_owner.internal_organs)
var/org_zone = check_zone(O.zone)
if(org_zone != BODY_ZONE_CHEST)
continue
- O.Remove(C)
- O.forceMove(T)
+ O.Remove(chest_owner)
+ O.forceMove(chest_owner.loc)
. += O
if(cavity_item)
- cavity_item.forceMove(T)
+ cavity_item.forceMove(chest_owner.loc)
. += cavity_item
cavity_item = null
@@ -149,23 +155,15 @@
forceMove(Tsec)
-/**
- * get_mangled_state() is relevant for flesh and bone bodyparts, and returns whether this bodypart has mangled skin, mangled bone, or both (or neither i guess)
- *
- * Dismemberment for flesh and bone requires the victim to have the skin on their bodypart destroyed (either a critical cut or piercing wound), and at least a hairline fracture
- * (severe bone), at which point we can start rolling for dismembering. The attack must also deal at least 10 damage, and must be a brute attack of some kind (sorry for now, cakehat, maybe later)
- *
- * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_FLESH if our skin is broken, BODYPART_MANGLED_BONE if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering
- */
+/// Returns which of this bodypart's biological states have been sufficiently mangled.
/obj/item/bodypart/proc/get_mangled_state()
- . = BODYPART_MANGLED_NONE
-
- for(var/i in wounds)
- var/datum/wound/iter_wound = i
- if((iter_wound.wound_flags & MANGLES_BONE))
- . |= BODYPART_MANGLED_BONE
- if((iter_wound.wound_flags & MANGLES_FLESH))
- . |= BODYPART_MANGLED_FLESH
+ var/mangled_states = NONE
+ for(var/datum/wound/iter_wound as anything in wounds)
+ if((iter_wound.wound_flags & MANGLES_INTERIOR))
+ mangled_states |= BODYPART_MANGLED_INTERIOR
+ if((iter_wound.wound_flags & MANGLES_EXTERIOR))
+ mangled_states |= BODYPART_MANGLED_EXTERIOR
+ return mangled_states
/**
* try_dismember() is used, once we've confirmed that a flesh and bone bodypart has both the skin and bone mangled, to actually roll for it
@@ -175,20 +173,29 @@
* Lastly, we choose which kind of dismember we want based on the wounding type we hit with. Note we don't care about all the normal mods or armor for this
*
* Arguments:
- * * wounding_type: Either WOUND_BLUNT, WOUND_SLASH, or WOUND_PIERCE, basically only matters for the dismember message
- * * wounding_dmg: The damage of the strike that prompted this roll, higher damage = higher chance
+ * * wounding_types: An associated list of wounding types and how much effective damage each one has
* * wound_bonus: Not actually used right now, but maybe someday
* * bare_wound_bonus: ditto above
*/
-/obj/item/bodypart/proc/try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)
+/obj/item/bodypart/proc/try_dismember(list/wounding_types, wound_bonus, bare_wound_bonus)
+ if(!can_dismember())
+ return
+
+ var/wounding_type
+ var/wounding_dmg = 0
+ for(var/wound in wounding_types)
+ if(wounding_types[wound] > wounding_dmg)
+ wounding_dmg = wounding_types[wound]
+ wounding_type = wound
+
if(wounding_dmg < DISMEMBER_MINIMUM_DAMAGE)
return
var/base_chance = wounding_dmg
base_chance += (get_damage() / max_damage * 50) // how much damage we dealt with this blow, + 50% of the damage percentage we already had on this bodypart
- if(locate(/datum/wound/blunt/critical) in wounds) // we only require a severe bone break, but if there's a critical bone break, we'll add 15% more
- base_chance += 15
+ for(var/datum/wound/iterated_wound as anything in wounds)
+ base_chance += iterated_wound.get_dismember_chance_bonus(base_chance)
if(prob(base_chance))
var/datum/wound/loss/dismembering = new
@@ -327,6 +334,7 @@
moveToNullspace()
set_owner(C)
C.add_bodypart(src)
+ LAZYREMOVE(C.body_zone_dismembered_by, body_zone)
if(held_index)
if(held_index > C.hand_bodyparts.len)
C.hand_bodyparts.len = held_index
@@ -452,9 +460,9 @@
return
/mob/living/carbon/regenerate_limb(limb_zone, noheal, robotic = FALSE)
- var/obj/item/bodypart/L
+ var/obj/item/bodypart/limb
if(get_bodypart(limb_zone))
return FALSE
- L = new_body_part(limb_zone, robotic, FALSE)
- L.replace_limb(src, TRUE, TRUE)
+ limb = new_body_part(limb_zone, robotic, FALSE)
+ limb.replace_limb(src, TRUE, TRUE)
return TRUE
diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm
index 44618e94c0..93ba95941f 100644
--- a/code/modules/surgery/bodyparts/head.dm
+++ b/code/modules/surgery/bodyparts/head.dm
@@ -6,6 +6,7 @@
max_damage = 200
body_zone = BODY_ZONE_HEAD
body_part = HEAD
+ plaintext_zone = "head"
w_class = WEIGHT_CLASS_BULKY //Quite a hefty load
slowdown = 1 //Balancing measure
throw_range = 2 //No head bowling
@@ -134,22 +135,22 @@
tongue = null
/obj/item/bodypart/head/update_limb(dropping_limb, mob/living/carbon/source, is_creating)
- var/mob/living/carbon/C
+ var/mob/living/carbon/limb_owner
if(source)
- C = source
+ limb_owner = source
else
- C = owner
+ limb_owner = owner
- real_name = C.real_name
- if(HAS_TRAIT(C, TRAIT_HUSK))
+ real_name = limb_owner.real_name
+ if(HAS_TRAIT(limb_owner, TRAIT_HUSK))
real_name = "Unknown"
hairstyle = "Bald"
facial_hairstyle = "Shaved"
lip_style = null
stored_lipstick_trait = null
- else if(!animal_origin && ishuman(C))
- var/mob/living/carbon/human/H = C
+ else if(!animal_origin && ishuman(limb_owner))
+ var/mob/living/carbon/human/H = limb_owner
var/datum/species/S = H.dna.species
//Facial hair
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index 58c594e089..92b7a0b654 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -28,7 +28,7 @@
if(cavity_item)
cavity_item.forceMove(drop_location())
cavity_item = null
- ..()
+ return ..()
/obj/item/bodypart/chest/monkey
icon = 'icons/mob/animal_parts.dmi'
@@ -70,6 +70,7 @@
px_x = -6
px_y = 0
can_be_disabled = TRUE
+ biological_state = BIO_STANDARD_JOINTED
/obj/item/bodypart/l_arm/set_owner(new_owner)
. = ..()
@@ -161,6 +162,7 @@
px_y = 0
max_stamina_damage = 50
can_be_disabled = TRUE
+ biological_state = BIO_STANDARD_JOINTED
/obj/item/bodypart/r_arm/set_owner(new_owner)
. = ..()
@@ -234,6 +236,9 @@
max_damage = 100
animal_origin = ALIEN_BODYPART
+/obj/item/bodypart/leg
+ biological_state = BIO_STANDARD_JOINTED
+
/obj/item/bodypart/leg/left
name = "left leg"
desc = "Some athletes prefer to tie their left shoelaces first for good \
diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm
index 02ed7d0223..773cc5bc95 100644
--- a/code/modules/surgery/bodyparts/robot_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm
@@ -23,6 +23,7 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
brute_reduction = 5
burn_reduction = 4
@@ -48,6 +49,7 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
brute_reduction = 5
burn_reduction = 4
@@ -73,6 +75,7 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
brute_reduction = 5
burn_reduction = 4
@@ -98,6 +101,7 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
brute_reduction = 5
burn_reduction = 4
@@ -122,6 +126,7 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = BIO_ROBOTIC
brute_reduction = 5
burn_reduction = 4
@@ -224,6 +229,7 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = BIO_ROBOTIC
brute_reduction = 5
burn_reduction = 4
diff --git a/code/modules/surgery/bodyparts/species_parts/ipc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/ipc_bodyparts.dm
index 1cb466e2e9..84029624b5 100644
--- a/code/modules/surgery/bodyparts/species_parts/ipc_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/species_parts/ipc_bodyparts.dm
@@ -8,6 +8,8 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC | BODYTYPE_BOXHEAD
+ biological_state = BIO_ROBOTIC
+
light_brute_msg = "scratched"
medium_brute_msg = "dented"
heavy_brute_msg = "sheared"
@@ -25,6 +27,7 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = BIO_ROBOTIC
light_brute_msg = "scratched"
medium_brute_msg = "dented"
@@ -42,6 +45,7 @@
dynamic_rename = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
light_brute_msg = "scratched"
medium_brute_msg = "dented"
@@ -59,6 +63,7 @@
dynamic_rename = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
light_brute_msg = "scratched"
medium_brute_msg = "dented"
@@ -76,6 +81,7 @@
dynamic_rename = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
light_brute_msg = "scratched"
medium_brute_msg = "dented"
@@ -93,6 +99,7 @@
dynamic_rename = FALSE
should_draw_greyscale = FALSE
bodytype = BODYTYPE_HUMANOID | BODYTYPE_ROBOTIC
+ biological_state = (BIO_ROBOTIC|BIO_JOINTED)
light_brute_msg = "scratched"
medium_brute_msg = "dented"
diff --git a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm
index 70d663b8f0..c8a800c495 100644
--- a/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/species_parts/misc_bodyparts.dm
@@ -50,24 +50,30 @@
///JELLY
/obj/item/bodypart/head/jelly
- limb_id = "jelly"
+ limb_id = SPECIES_JELLYPERSON
is_dimorphic = TRUE
+ biological_state = (BIO_FLESH|BIO_BLOODED)
/obj/item/bodypart/chest/jelly
- limb_id = "jelly"
+ limb_id = SPECIES_JELLYPERSON
is_dimorphic = TRUE
+ biological_state = (BIO_FLESH|BIO_BLOODED)
/obj/item/bodypart/l_arm/jelly
- limb_id = "jelly"
+ limb_id = SPECIES_JELLYPERSON
+ biological_state = (BIO_FLESH|BIO_BLOODED)
/obj/item/bodypart/r_arm/jelly
- limb_id = "jelly"
+ limb_id = SPECIES_JELLYPERSON
+ biological_state = (BIO_FLESH|BIO_BLOODED)
/obj/item/bodypart/leg/left/jelly
- limb_id = "jelly"
+ limb_id = SPECIES_JELLYPERSON
+ biological_state = (BIO_FLESH|BIO_BLOODED)
/obj/item/bodypart/leg/right/jelly
- limb_id = "jelly"
+ limb_id = SPECIES_JELLYPERSON
+ biological_state = (BIO_FLESH|BIO_BLOODED)
///SLIME
/obj/item/bodypart/head/slime
@@ -201,24 +207,30 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
greyscale_eyes = FALSE
+ biological_state = BIO_BONE
/obj/item/bodypart/chest/skeleton
limb_id = "skeleton"
is_dimorphic = FALSE
should_draw_greyscale = FALSE
+ biological_state = BIO_BONE
/obj/item/bodypart/l_arm/skeleton
limb_id = "skeleton"
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
/obj/item/bodypart/r_arm/skeleton
limb_id = "skeleton"
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
/obj/item/bodypart/leg/left/skeleton
limb_id = "skeleton"
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
/obj/item/bodypart/leg/right/skeleton
limb_id = "skeleton"
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
diff --git a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm
index 974075a31d..7b52ea7b62 100644
--- a/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/species_parts/plasmaman_bodyparts.dm
@@ -4,29 +4,35 @@
is_dimorphic = FALSE
should_draw_greyscale = FALSE
greyscale_eyes = FALSE
+ biological_state = BIO_BONE
/obj/item/bodypart/chest/plasmaman
static_icon = 'icons/mob/species/plasmaman/bodyparts.dmi'
limb_id = SPECIES_PLASMAMAN
is_dimorphic = FALSE
should_draw_greyscale = FALSE
+ biological_state = BIO_BONE
/obj/item/bodypart/l_arm/plasmaman
static_icon = 'icons/mob/species/plasmaman/bodyparts.dmi'
limb_id = SPECIES_PLASMAMAN
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
/obj/item/bodypart/r_arm/plasmaman
static_icon = 'icons/mob/species/plasmaman/bodyparts.dmi'
limb_id = SPECIES_PLASMAMAN
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
/obj/item/bodypart/leg/left/plasmaman
static_icon = 'icons/mob/species/plasmaman/bodyparts.dmi'
limb_id = SPECIES_PLASMAMAN
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
/obj/item/bodypart/leg/right/plasmaman
static_icon = 'icons/mob/species/plasmaman/bodyparts.dmi'
limb_id = SPECIES_PLASMAMAN
should_draw_greyscale = FALSE
+ biological_state = (BIO_BONE|BIO_JOINTED)
diff --git a/code/modules/surgery/bodyparts/species_parts/rachnid_bodyparts.dm b/code/modules/surgery/bodyparts/species_parts/rachnid_bodyparts.dm
index 00690d92e7..440b0b7236 100644
--- a/code/modules/surgery/bodyparts/species_parts/rachnid_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/species_parts/rachnid_bodyparts.dm
@@ -3,6 +3,7 @@
limb_id = SPECIES_RACHNID
should_draw_greyscale = FALSE
overlay_icon_state = TRUE
+ biological_state = (BIO_FLESH|BIO_BLOODED)
draw_eyes = FALSE
/obj/item/bodypart/chest/rachnid
@@ -10,26 +11,31 @@
limb_id = SPECIES_RACHNID
is_dimorphic = FALSE
should_draw_greyscale = FALSE
+ biological_state = (BIO_FLESH|BIO_BLOODED)
overlay_icon_state = TRUE
/obj/item/bodypart/l_arm/rachnid
static_icon = 'icons/mob/species/rachnid/bodyparts.dmi'
limb_id = SPECIES_RACHNID
should_draw_greyscale = FALSE
+ biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED)
/obj/item/bodypart/r_arm/rachnid
static_icon = 'icons/mob/species/rachnid/bodyparts.dmi'
limb_id = SPECIES_RACHNID
should_draw_greyscale = FALSE
+ biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED)
/obj/item/bodypart/leg/left/rachnid
static_icon = 'icons/mob/species/rachnid/bodyparts.dmi'
limb_id = SPECIES_RACHNID
should_draw_greyscale = FALSE
overlay_icon_state = TRUE
+ biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED)
/obj/item/bodypart/leg/right/rachnid
static_icon = 'icons/mob/species/rachnid/bodyparts.dmi'
limb_id = SPECIES_RACHNID
should_draw_greyscale = FALSE
overlay_icon_state = TRUE
+ biological_state = (BIO_FLESH|BIO_BLOODED|BIO_JOINTED)
diff --git a/code/modules/surgery/bone_fractures.dm b/code/modules/surgery/bone_fractures.dm
index eb05b60fd1..36aa6422fc 100644
--- a/code/modules/surgery/bone_fractures.dm
+++ b/code/modules/surgery/bone_fractures.dm
@@ -7,7 +7,7 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/blunt/severe
+ targetable_wound = /datum/wound/blunt/bone/severe
/datum/surgery/repair_hairline_fracture/can_start(mob/living/user, mob/living/carbon/target)
if(..())
@@ -21,7 +21,7 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/blunt/critical
+ targetable_wound = /datum/wound/blunt/bone/critical
/datum/surgery/reset_compound_fracture/can_start(mob/living/user, mob/living/carbon/target)
if(..())
@@ -53,7 +53,8 @@
"
[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)] with [tool]!",
"
[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)]!")
log_combat(user, target, "repaired a hairline fracture in", addition="INTENT: [uppertext(user.a_intent)]")
- qdel(surgery.operated_wound)
+ surgery.operated_wound.attached_surgery = null
+ QDEL_NULL(surgery.operated_wound)
else
to_chat(user, "
[target] has no hairline fracture there!")
return ..()
@@ -87,7 +88,8 @@
"
[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)] with [tool]!",
"
[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)]!")
log_combat(user, target, "repaired a compound fracture in", addition="INTENT: [uppertext(user.a_intent)]")
- qdel(surgery.operated_wound)
+ surgery.operated_wound.attached_surgery = null
+ QDEL_NULL(surgery.operated_wound)
else
to_chat(user, "
[target] has no compound fracture there!")
return ..()
@@ -126,7 +128,7 @@
"
[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)] with [tool]!",
"
[user] successfully repairs the fracture in [target]'s [parse_zone(target_zone)]!")
log_combat(user, target, "repaired a compound fracture in", addition="INTENT: [uppertext(user.a_intent)]")
- qdel(surgery.operated_wound)
+ QDEL_NULL(surgery.operated_wound)
else
to_chat(user, "
[target] has no compound fracture there!")
return ..()
diff --git a/code/modules/surgery/buckling_repair.dm b/code/modules/surgery/buckling_repair.dm
new file mode 100644
index 0000000000..61c3befe28
--- /dev/null
+++ b/code/modules/surgery/buckling_repair.dm
@@ -0,0 +1,38 @@
+/datum/surgery/repair_buckled_frame
+ name = "Repair Buckled Plating"
+ desc = "Replaces a buckled external plating on a robotic limb."
+ steps = list(
+ /datum/surgery_step/cut_plating,
+ /datum/surgery_step/add_plating,
+ /datum/surgery_step/weld_plating,
+ )
+ requires_bodypart_type = BODYTYPE_ROBOTIC
+ self_operable = TRUE
+ targetable_wound = /datum/wound/blunt/buckling/severe
+
+/datum/surgery/repair_buckled_frame/can_start(mob/user, mob/living/patient)
+ if(!..())
+ return FALSE
+ var/obj/item/bodypart/targeted_bodypart = patient.get_bodypart(user.zone_selected)
+ return !isnull(targeted_bodypart.get_wound_type(targetable_wound))
+
+/datum/surgery/repair_sheared_frame
+ name = "Repair Sheared Frame"
+ desc = "Replaces the internal frame and external plating of a robotic limb."
+ steps = list(
+ /datum/surgery_step/cut_plating,
+ /datum/surgery_step/mechanic_unwrench,
+ /datum/surgery_step/replace_frame,
+ /datum/surgery_step/mechanic_wrench,
+ /datum/surgery_step/add_plating,
+ /datum/surgery_step/weld_plating,
+ )
+ requires_bodypart_type = BODYTYPE_ROBOTIC
+ self_operable = TRUE
+ targetable_wound = /datum/wound/blunt/buckling/critical
+
+/datum/surgery/repair_sheared_frame/can_start(mob/user, mob/living/patient)
+ if(!..())
+ return FALSE
+ var/obj/item/bodypart/targeted_bodypart = patient.get_bodypart(user.zone_selected)
+ return !isnull(targeted_bodypart.get_wound_type(targetable_wound))
diff --git a/code/modules/surgery/debride.dm b/code/modules/surgery/debride.dm
index 69947b5b09..2c1266ee39 100644
--- a/code/modules/surgery/debride.dm
+++ b/code/modules/surgery/debride.dm
@@ -8,14 +8,14 @@
target_mobtypes = list(/mob/living/carbon/human)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/burn
+ targetable_wound = /datum/wound/burn/flesh
/datum/surgery/debride/can_start(mob/living/user, mob/living/carbon/target)
if(!istype(target))
return FALSE
if(..())
var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected)
- var/datum/wound/burn/burn_wound = targeted_bodypart.get_wound_type(targetable_wound)
+ var/datum/wound/burn/flesh/burn_wound = targeted_bodypart.get_wound_type(targetable_wound)
return(burn_wound && burn_wound.infestation > 0)
//SURGERY STEPS
@@ -32,7 +32,7 @@
var/infestation_removed = 0.5
/// To give the surgeon a heads up how much work they have ahead of them
-/datum/surgery_step/debride/proc/get_progress(mob/user, mob/living/carbon/target, datum/wound/burn/burn_wound)
+/datum/surgery_step/debride/proc/get_progress(mob/user, mob/living/carbon/target, datum/wound/burn/flesh/burn_wound)
if(!burn_wound?.infestation || !infestation_removed)
return
var/estimated_remaining_steps = burn_wound.infestation / infestation_removed
@@ -52,7 +52,7 @@
/datum/surgery_step/debride/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
if(surgery.operated_wound)
- var/datum/wound/burn/burn_wound = surgery.operated_wound
+ var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound
if(burn_wound.infestation <= 0)
to_chat(user, "
[target]'s [parse_zone(user.zone_selected)] has no infected flesh to remove!")
surgery.status++
@@ -65,7 +65,7 @@
user.visible_message("
[user] looks for [target]'s [parse_zone(user.zone_selected)].", "
You look for [target]'s [parse_zone(user.zone_selected)]...")
/datum/surgery_step/debride/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
- var/datum/wound/burn/burn_wound = surgery.operated_wound
+ var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound
if(burn_wound)
var/progress_text = get_progress(user, target, burn_wound)
display_results(user, target, "
You successfully excise some of the infected flesh from [target]'s [parse_zone(target_zone)][progress_text].",
@@ -91,7 +91,7 @@
/datum/surgery_step/debride/initiate(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, try_to_fail = FALSE)
if(!..())
return
- var/datum/wound/burn/burn_wound = surgery.operated_wound
+ var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound
while(burn_wound && burn_wound.infestation > 0.25)
if(!..())
break
@@ -108,7 +108,7 @@
/datum/surgery_step/dress/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
- var/datum/wound/burn/burn_wound = surgery.operated_wound
+ var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound
if(burn_wound)
display_results(user, target, "
You begin to dress the burns on [target]'s [parse_zone(user.zone_selected)]...",
"
[user] begins to dress the burns on [target]'s [parse_zone(user.zone_selected)] with [tool].",
@@ -117,7 +117,7 @@
user.visible_message("
[user] looks for [target]'s [parse_zone(user.zone_selected)].", "
You look for [target]'s [parse_zone(user.zone_selected)]...")
/datum/surgery_step/dress/success(mob/living/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
- var/datum/wound/burn/burn_wound = surgery.operated_wound
+ var/datum/wound/burn/flesh/burn_wound = surgery.operated_wound
if(burn_wound)
display_results(user, target, "
You successfully wrap [target]'s [parse_zone(target_zone)] with [tool].",
"
[user] successfully wraps [target]'s [parse_zone(target_zone)] with [tool]!",
diff --git a/code/modules/surgery/electrical_repair.dm b/code/modules/surgery/electrical_repair.dm
new file mode 100644
index 0000000000..1f36af3d47
--- /dev/null
+++ b/code/modules/surgery/electrical_repair.dm
@@ -0,0 +1,159 @@
+/// Minimum temperature to melt the solder.
+#define SOLDER_MELTING_POINT 600
+
+/datum/surgery/repair_electrical_damage
+ name = "Repair Damaged Electronics"
+ desc = "Repairs damaged electronics inside a robotic limb."
+ requires_bodypart_type = BODYTYPE_ROBOTIC
+ steps = list(
+ /datum/surgery_step/mechanic_open,
+ /datum/surgery_step/open_hatch,
+ /datum/surgery_step/replace_wiring,
+ /datum/surgery_step/solder_wiring,
+ /datum/surgery_step/close_hatch,
+ /datum/surgery_step/mechanic_close,
+ )
+ lying_required = FALSE
+ self_operable = TRUE
+ targetable_wound = /datum/wound/electric/severe
+
+/datum/surgery/repair_electrical_damage/can_start(mob/user, mob/living/patient)
+ if(!..())
+ return FALSE
+ var/obj/item/bodypart/targeted_bodypart = patient.get_bodypart(user.zone_selected)
+ var/datum/wound/electric/targeted_wound = targeted_bodypart.get_wound_type(targetable_wound)
+ if(isnull(targeted_wound))
+ return FALSE
+ if(user == patient && targeted_wound.affected_organ?.slot == ORGAN_SLOT_BRAIN)
+ return FALSE // can't operate on your own brain
+ return TRUE
+
+/datum/surgery/repair_short_circuit
+ name = "Repair Short Circuit"
+ desc = "Repairs short-circuiting electronics inside a robotic limb."
+ requires_bodypart_type = BODYTYPE_ROBOTIC
+ steps = list(
+ /datum/surgery_step/mechanic_open,
+ /datum/surgery_step/open_hatch,
+ /datum/surgery_step/prepare_electronics,
+ /datum/surgery_step/replace_capacitor,
+ /datum/surgery_step/replace_wiring,
+ /datum/surgery_step/solder_wiring,
+ /datum/surgery_step/close_hatch,
+ /datum/surgery_step/mechanic_close,
+ )
+ lying_required = FALSE
+ self_operable = TRUE
+ targetable_wound = /datum/wound/electric/critical
+
+/datum/surgery/repair_short_circuit/can_start(mob/user, mob/living/patient)
+ if(!..())
+ return FALSE
+ var/obj/item/bodypart/targeted_bodypart = patient.get_bodypart(user.zone_selected)
+ var/datum/wound/electric/targeted_wound = targeted_bodypart.get_wound_type(targetable_wound)
+ if(isnull(targeted_wound))
+ return FALSE
+ if(user == patient && targeted_wound.affected_organ?.slot == ORGAN_SLOT_BRAIN)
+ return FALSE
+ return TRUE
+
+/datum/surgery_step/replace_capacitor
+ name = "replace capacitor"
+ implements = list(
+ /obj/item/stock_parts/capacitor = 100,
+ )
+ preop_sound = 'sound/items/tape_flip.ogg'
+ success_sound = 'sound/items/taperecorder_close.ogg'
+ failure_sound = 'sound/machines/defib_zap.ogg'
+ time = 3 SECONDS
+
+/datum/surgery_step/replace_capacitor/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ display_results(user, target,
+ span_notice("You start replacing the electronic components inside [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] starts replacing the electronic components inside [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] starts replacing the electronic components inside [target]'s [parse_zone(target_zone)]...")
+ )
+/datum/surgery_step/replace_capacitor/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
+ if(surgery.operated_wound)
+ qdel(tool)
+ return ..()
+
+/datum/surgery_step/replace_wiring
+ name = "replace wiring"
+ implements = list(
+ /obj/item/stack/cable_coil = 100,
+ )
+ time = 3 SECONDS
+
+/datum/surgery_step/replace_wiring/tool_check(mob/user, obj/item/tool)
+ if(isstack(tool))
+ var/obj/item/stack/new_wiring = tool
+ if(new_wiring.amount < 5)
+ to_chat(user, span_warning("You need 5 lengths of cable to replace the wiring!"))
+ return FALSE
+ return ..()
+
+/datum/surgery_step/replace_wiring/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ display_results(user, target,
+ span_notice("You start replacing the wires inside [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] starts replacing the wires inside [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] starts replacing the wires inside [target]'s [parse_zone(target_zone)]...")
+ )
+
+/datum/surgery_step/replace_wiring/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
+ . = ..()
+ tool.use(5)
+
+/datum/surgery_step/solder_wiring
+ name = "solder wiring"
+ implements = list(
+ TOOL_WELDER = 100,
+ TOOL_CAUTERY = 100,
+ /obj/item/reagent_containers = 100,
+ /obj/item/gun/energy/plasmacutter = 70,
+ /obj/item = 40,
+ )
+ time = 5 SECONDS
+
+/datum/surgery_step/solder_wiring/tool_check(mob/user, obj/item/tool)
+ if(tool.type == /obj/item && tool.get_temperature() < SOLDER_MELTING_POINT)
+ return FALSE
+ if(istype(tool, /obj/item/reagent_containers) && tool.reagents?.get_reagent_amount(/datum/reagent/medicine/liquid_solder) < 2)
+ to_chat(user, span_warning("You need more liquid solder to repair the wiring!"))
+ return FALSE
+ if(tool.usesound)
+ preop_sound = pick(tool.usesound)
+ success_sound = pick(tool.usesound)
+ return ..()
+
+/datum/surgery_step/solder_wiring/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(istype(tool, /obj/item/reagent_containers))
+ display_results(user, target,
+ span_notice("You start adding new solder to [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] starts adding new solder to [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] starts adding new solder to [target]'s [parse_zone(target_zone)]...")
+ )
+ else
+ display_results(user, target,
+ span_notice("You start heating the solder inside [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] starts heating the solder inside [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] starts heating the solder inside [target]'s [parse_zone(target_zone)]...")
+ )
+
+/datum/surgery_step/solder_wiring/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
+ if(surgery.operated_wound)
+ display_results(user, target,
+ span_notice("You solder the new wiring inside [target]'s [parse_zone(target_zone)]."),
+ span_notice("[user] solders the new wiring inside [target]'s [parse_zone(target_zone)] with [tool]."),
+ span_notice("[user] solders the new wiring inside [target]'s [parse_zone(target_zone)].")
+ )
+ surgery.operated_wound.attached_surgery = null
+ QDEL_NULL(surgery.operated_wound)
+ return ..()
+
+/datum/surgery_step/solder_wiring/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob)
+ if(surgery.operated_bodypart)
+ surgery.operated_bodypart.wound_roll(0, rand(20, 40))
+ return ..()
+
+#undef SOLDER_MELTING_POINT
diff --git a/code/modules/surgery/heatwarp_repair.dm b/code/modules/surgery/heatwarp_repair.dm
new file mode 100644
index 0000000000..9ecb910013
--- /dev/null
+++ b/code/modules/surgery/heatwarp_repair.dm
@@ -0,0 +1,20 @@
+/datum/surgery/repair_heat_warp
+ name = "Repair Deformed Chassis"
+ desc = "Replaces the heat-warped plating and frame of a robotic limb."
+ steps = list(
+ /datum/surgery_step/cut_plating,
+ /datum/surgery_step/mechanic_unwrench,
+ /datum/surgery_step/replace_frame,
+ /datum/surgery_step/mechanic_wrench,
+ /datum/surgery_step/add_plating,
+ /datum/surgery_step/weld_plating,
+ )
+ requires_bodypart_type = BODYTYPE_ROBOTIC
+ self_operable = TRUE
+ targetable_wound = /datum/wound/burn/heat_warping/critical
+
+/datum/surgery/repair_sheared_frame/can_start(mob/user, mob/living/patient)
+ if(!..())
+ return FALSE
+ var/obj/item/bodypart/targeted_bodypart = patient.get_bodypart(user.zone_selected)
+ return !isnull(targeted_bodypart.get_wound_type(targetable_wound))
diff --git a/code/modules/surgery/mechanic_steps.dm b/code/modules/surgery/mechanic_steps.dm
index f707232d1a..06adabcd29 100644
--- a/code/modules/surgery/mechanic_steps.dm
+++ b/code/modules/surgery/mechanic_steps.dm
@@ -204,3 +204,207 @@
span_notice("[user] successfully replaces [target]'s [parse_zone(target_zone)] with [tool]!"),
span_notice("[user] successfully replaces [target]'s [parse_zone(target_zone)]!"))
return ..()
+
+// Repair of specific robotic wounds.
+
+/datum/surgery_step/cut_plating
+ name = "cut plating"
+ implements = list(
+ TOOL_DECONSTRUCT = 100,
+ TOOL_WELDER = 100,
+ TOOL_SAW = 50,
+ )
+ time = 4 SECONDS
+
+/datum/surgery_step/cut_plating/tool_check(mob/user, obj/item/tool)
+ if(tool.usesound)
+ preop_sound = pick(tool.usesound)
+ success_sound = pick(tool.usesound)
+ return ..()
+
+/datum/surgery_step/cut_plating/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(surgery.operated_wound)
+ display_results(user, target,
+ "You begin cutting the plating off of [target]'s [parse_zone(target_zone)]...",
+ "[user] begins cutting the plating off of [target]'s [parse_zone(target_zone)] with [tool]...",
+ "[user] begins cutting the plating off of [target]'s [parse_zone(target_zone)]...",
+ )
+
+/datum/surgery_step/add_plating
+ name = "add new plating"
+ implements = list(
+ /obj/item/construction/rcd = 100,
+ /obj/item/stack/sheet/plasteel = 100,
+ /obj/item/stack/sheet/mineral/titanium = 100,
+ /obj/item/stack/sheet/mineral/plastitanium = 100,
+ )
+ preop_sound = 'sound/machines/pda_button1.ogg'
+ success_sound = 'sound/machines/doorclick.ogg'
+ time = 2 SECONDS
+
+/datum/surgery_step/add_plating/tool_check(mob/user, obj/item/tool)
+ if(isstack(tool))
+ var/obj/item/stack/new_plating = tool
+ if(new_plating.amount < 2)
+ to_chat(user, span_warning("You need 2 sheets to replace the plating!"))
+ return FALSE
+ if(istype(tool, /obj/item/construction/rcd))
+ var/obj/item/construction/rcd/constructor = tool
+ if(constructor.matter < 20)
+ to_chat(user, constructor.no_ammo_message)
+ return FALSE
+ return ..()
+
+/datum/surgery_step/add_plating/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(!surgery.operated_wound)
+ return
+ if(istype(tool, /obj/item/construction/rcd))
+ display_results(user, target,
+ span_notice("You begin reconstructing the plating on [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] begins reconstructing the plating on [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] begins reconstructing the plating on [target]'s [parse_zone(target_zone)]..."),
+ )
+ else
+ display_results(user, target,
+ span_notice("You begin replacing the plating on [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] begins replacing the plating on [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] begins replacing the plating on [target]'s [parse_zone(target_zone)]..."),
+ )
+
+/datum/surgery_step/add_plating/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
+ if(surgery.operated_wound)
+ if(isstack(tool))
+ var/obj/item/stack/used_stack = tool
+ used_stack.use(2)
+ if(istype(tool, /obj/item/construction/rcd))
+ var/obj/item/construction/rcd/used_rcd = tool
+ used_rcd.useResource(20, user)
+ display_results(user, target,
+ span_notice("You reconstruct the plating on [target]'s [parse_zone(target_zone)]"),
+ span_notice("[user] reconstructs the plating on [target]'s [parse_zone(target_zone)] with [tool]"),
+ span_notice("[user] reconstructs the plating on [target]'s [parse_zone(target_zone)]"),
+ )
+ else
+ display_results(user, target,
+ span_notice("You replace the plating on [target]'s [parse_zone(target_zone)]"),
+ span_notice("[user] replace the plating on [target]'s [parse_zone(target_zone)] with [tool]"),
+ span_notice("[user] replace the plating on [target]'s [parse_zone(target_zone)]"),
+ )
+ return ..()
+
+/datum/surgery_step/add_plating/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob)
+ . = ..()
+ if(isstack(tool))
+ var/obj/item/stack/used_stack = tool
+ used_stack.use(2)
+ if(istype(tool, /obj/item/construction/rcd))
+ var/obj/item/construction/rcd/used_rcd = tool
+ used_rcd.useResource(20, user)
+
+/datum/surgery_step/replace_frame
+ name = "replace frame"
+ implements = list(
+ /obj/item/stack/rods = 100,
+ /obj/item/construction/rcd = 100,
+ )
+ preop_sound = 'sound/items/tape_flip.ogg'
+ success_sound = 'sound/items/taperecorder_close.ogg'
+ time = 2 SECONDS
+
+/datum/surgery_step/replace_frame/tool_check(mob/user, obj/item/tool)
+ if(isstack(tool))
+ var/obj/item/stack/new_plating = tool
+ if(new_plating.amount < 4)
+ to_chat(user, span_warning("You need 4 rods to replace the frame!"))
+ return FALSE
+ if(istype(tool, /obj/item/construction/rcd))
+ var/obj/item/construction/rcd/constructor = tool
+ if(constructor.matter < 20)
+ to_chat(user, constructor.no_ammo_message)
+ return FALSE
+ return ..()
+
+/datum/surgery_step/replace_frame/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ if(!surgery.operated_wound)
+ return
+ if(istype(tool, /obj/item/construction/rcd))
+ display_results(user, target,
+ span_notice("You begin reconstructing the frame inside [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] begins reconstructing the frame inside [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] begins reconstructing the frame inside [target]'s [parse_zone(target_zone)]..."),
+ )
+ else
+ display_results(user, target,
+ span_notice("You begin replacing the frame inside [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] begins replacing the frame inside [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] begins replacing the frame inside [target]'s [parse_zone(target_zone)]..."),
+ )
+
+/datum/surgery_step/replace_frame/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
+ if(surgery.operated_wound)
+ if(isstack(tool))
+ var/obj/item/stack/used_stack = tool
+ used_stack.use(4)
+ if(istype(tool, /obj/item/construction/rcd))
+ var/obj/item/construction/rcd/used_rcd = tool
+ used_rcd.useResource(20, user)
+ display_results(user, target,
+ span_notice("You reconstruct the frame inside [target]'s [parse_zone(target_zone)]."),
+ span_notice("[user] reconstructs the frame inside [target]'s [parse_zone(target_zone)] with [tool]."),
+ span_notice("[user] reconstructs the frame inside [target]'s [parse_zone(target_zone)]."),
+ )
+ else
+ display_results(user, target,
+ span_notice("You replace the frame inside [target]'s [parse_zone(target_zone)]."),
+ span_notice("[user] replace the frame inside [target]'s [parse_zone(target_zone)] with [tool]."),
+ span_notice("[user] replace the frame inside [target]'s [parse_zone(target_zone)]."),
+ )
+ return ..()
+
+/datum/surgery_step/replace_frame/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, fail_prob)
+ . = ..()
+ if(isstack(tool))
+ var/obj/item/stack/used_stack = tool
+ used_stack.use(4)
+ if(istype(tool, /obj/item/construction/rcd))
+ var/obj/item/construction/rcd/used_rcd = tool
+ used_rcd.useResource(20, user)
+
+/datum/surgery_step/weld_plating
+ name = "weld plating"
+ implements = list(
+ TOOL_WELDER = 100,
+ /obj/item/gun/energy/plasmacutter = 75,
+ )
+ time = 3 SECONDS
+
+/datum/surgery_step/weld_plating/tool_check(mob/user, obj/item/tool)
+ if(tool.usesound)
+ preop_sound = pick(tool.usesound)
+ success_sound = pick(tool.usesound)
+ return ..()
+
+/datum/surgery_step/weld_plating/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ display_results(user, target,
+ span_notice("You start welding the plating onto [target]'s [parse_zone(target_zone)]..."),
+ span_notice("[user] starts welding the plating onto [target]'s [parse_zone(target_zone)] with [tool]..."),
+ span_notice("[user] starts welding the plating onto [target]'s [parse_zone(target_zone)]..."),
+ )
+
+/datum/surgery_step/weld_plating/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
+ if(surgery.operated_wound)
+ display_results(user, target,
+ span_notice("You weld the plating onto [target]'s [parse_zone(target_zone)]."),
+ span_notice("[user] welds the plating onto [target]'s [parse_zone(target_zone)] with [tool]."),
+ span_notice("[user] welds the plating onto [target]'s [parse_zone(target_zone)]."),
+ )
+ var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected)
+ for(var/datum/wound/plating_wound as anything in targeted_bodypart.wounds) // might have more than one wound solved by replacing the plating
+ if(!(plating_wound.wound_flags & PLATING_DAMAGE))
+ continue
+ if(plating_wound.attached_surgery == surgery)
+ plating_wound.attached_surgery = null // detach the wound from this surgery so that it can be completed properly
+ qdel(plating_wound)
+ if(QDELETED(surgery.operated_wound))
+ surgery.operated_wound = null
+ return ..()
diff --git a/code/modules/surgery/organs/augments_arms.dm b/code/modules/surgery/organs/augments_arms.dm
index 8f9b1d9b9b..58bc3c93ba 100644
--- a/code/modules/surgery/organs/augments_arms.dm
+++ b/code/modules/surgery/organs/augments_arms.dm
@@ -198,7 +198,7 @@
owner.adjust_fire_stacks(20)
owner.ignite_mob()
owner.adjustFireLoss(25)
- organ_flags |= ORGAN_FAILING
+ ADD_TRAIT(src, TRAIT_ORGAN_FAILING, DAMAGE_TRAIT)
/obj/item/organ/cyberimp/arm/gun/laser
diff --git a/code/modules/surgery/organs/augments_internal.dm b/code/modules/surgery/organs/augments_internal.dm
index bdd938d5ee..32203468da 100644
--- a/code/modules/surgery/organs/augments_internal.dm
+++ b/code/modules/surgery/organs/augments_internal.dm
@@ -131,11 +131,11 @@
. = ..()
if((organ_flags & ORGAN_FAILING) || . & EMP_PROTECT_SELF)
return
- organ_flags |= ORGAN_FAILING
+ ADD_TRAIT(src, TRAIT_ORGAN_FAILING, EMP_TRAIT)
addtimer(CALLBACK(src, PROC_REF(reboot)), 90 / severity)
/obj/item/organ/cyberimp/brain/anti_stun/proc/reboot()
- organ_flags &= ~ORGAN_FAILING
+ REMOVE_TRAIT(src, TRAIT_ORGAN_FAILING, EMP_TRAIT)
/obj/item/organ/cyberimp/brain/joywire
name = "\improper Midi-Sed pleasure vivifier"
@@ -152,7 +152,7 @@
. = ..()
if(!owner || . & EMP_PROTECT_SELF)
return
- organ_flags |= ORGAN_FAILING
+ ADD_TRAIT(src, TRAIT_ORGAN_FAILING, DAMAGE_TRAIT)
SEND_SIGNAL(owner, COMSIG_CLEAR_MOOD_EVENT, "joywire")
SEND_SIGNAL(owner, COMSIG_ADD_MOOD_EVENT, "joywire_emp", /datum/mood_event/joywire_emp)
to_chat(owner, span_boldwarning("That feeling of dream-like, distilled joy is suddenly diluted. Misery sets in..."))
diff --git a/code/modules/surgery/organs/ears.dm b/code/modules/surgery/organs/ears.dm
index dc742f411b..8dae323502 100644
--- a/code/modules/surgery/organs/ears.dm
+++ b/code/modules/surgery/organs/ears.dm
@@ -32,8 +32,6 @@
return
..()
var/mob/living/carbon/C = owner
- if((damage < maxHealth) && (organ_flags & ORGAN_FAILING)) //ear damage can be repaired from the failing condition
- organ_flags &= ~ORGAN_FAILING
// genetic deafness prevents the body from using the ears, even if healthy
if(HAS_TRAIT(C, TRAIT_DEAF))
deaf = max(deaf, 1)
@@ -48,8 +46,7 @@
/obj/item/organ/ears/proc/restoreEars()
deaf = 0
- damage = 0
- organ_flags &= ~ORGAN_FAILING
+ setOrganDamage(0)
var/mob/living/carbon/C = owner
@@ -57,7 +54,7 @@
deaf = 1
/obj/item/organ/ears/proc/adjustEarDamage(ddmg, ddeaf)
- damage = max(damage + (ddmg*damage_multiplier), 0)
+ applyOrganDamage(-ddmg * damage_multiplier)
deaf = max(deaf + (ddeaf*damage_multiplier), 0)
/obj/item/organ/ears/proc/minimumDeafTicks(value)
diff --git a/code/modules/surgery/organs/eyes.dm b/code/modules/surgery/organs/eyes.dm
index 7fa2b4258a..19dc7ae0b9 100644
--- a/code/modules/surgery/organs/eyes.dm
+++ b/code/modules/surgery/organs/eyes.dm
@@ -50,6 +50,8 @@
sclera_color = human_owner.sclera_color
M.update_tint()
+ if(organ_flags & ORGAN_FAILING)
+ M.become_blind(EYE_DAMAGE)
owner.update_sight()
if(M.has_dna() && ishuman(M))
M.dna.species.handle_body(M) //updates eye icon
@@ -70,16 +72,10 @@
/obj/item/organ/eyes/on_life()
..()
var/mob/living/carbon/C = owner
- //since we can repair fully damaged eyes, check if healing has occurred
- if((organ_flags & ORGAN_FAILING) && (damage < maxHealth))
- organ_flags &= ~ORGAN_FAILING
- C.cure_blind(EYE_DAMAGE)
//various degrees of "oh fuck my eyes", from "point a laser at your eye" to "staring at the Sun" intensities
if(damage > 20)
damaged = TRUE
- if((organ_flags & ORGAN_FAILING))
- C.become_blind(EYE_DAMAGE)
- else if(damage > 30)
+ if(damage > 30)
C.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/impaired, 2)
else
C.overlay_fullscreen("eye_damage", /atom/movable/screen/fullscreen/impaired, 1)
@@ -89,6 +85,16 @@
C.clear_fullscreen("eye_damage")
return
+/obj/item/organ/eyes/on_organ_fail()
+ . = ..()
+ if(owner)
+ owner.become_blind(EYE_DAMAGE)
+
+/obj/item/organ/eyes/on_organ_restore()
+ . = ..()
+ if(owner)
+ owner.cure_blind(EYE_DAMAGE)
+
/obj/item/organ/eyes/lizard
name = "lizard eyes"
desc = "Very similar to human eyes in functionality, only visible difference being the different shade of white."
diff --git a/code/modules/surgery/organs/organ_internal.dm b/code/modules/surgery/organs/organ_internal.dm
index 7f1d9d4796..ac758ec538 100644
--- a/code/modules/surgery/organs/organ_internal.dm
+++ b/code/modules/surgery/organs/organ_internal.dm
@@ -49,6 +49,8 @@
pre_eat = CALLBACK(src, PROC_REF(pre_eat)),\
on_compost = CALLBACK(src, PROC_REF(pre_compost)),\
after_eat = CALLBACK(src, PROC_REF(on_eat_from)))
+ RegisterSignal(src, SIGNAL_ADDTRAIT(TRAIT_ORGAN_FAILING), PROC_REF(on_organ_fail))
+ RegisterSignal(src, SIGNAL_REMOVETRAIT(TRAIT_ORGAN_FAILING), PROC_REF(on_organ_restore))
///When you take a bite you cant jam it in for surgery anymore.
/obj/item/organ/proc/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
@@ -191,6 +193,7 @@
var/delta = damage - prev_damage
if(delta > 0)
if(damage >= maxHealth)
+ ADD_TRAIT(src, TRAIT_ORGAN_FAILING, DAMAGE_TRAIT)
organ_flags |= ORGAN_FAILING
return now_failing
if(damage > high_threshold && prev_damage <= high_threshold)
@@ -198,7 +201,7 @@
if(damage > low_threshold && prev_damage <= low_threshold)
return low_threshold_passed
else
- organ_flags &= ~ORGAN_FAILING
+ REMOVE_TRAIT(src, TRAIT_ORGAN_FAILING, DAMAGE_TRAIT)
if(prev_damage > low_threshold && damage <= low_threshold)
return low_threshold_cleared
if(prev_damage > high_threshold && damage <= high_threshold)
@@ -248,3 +251,12 @@
*/
/obj/item/organ/proc/get_availability(datum/species/S)
return TRUE
+
+/obj/item/organ/proc/on_organ_fail()
+ SIGNAL_HANDLER
+ organ_flags |= ORGAN_FAILING
+
+/obj/item/organ/proc/on_organ_restore()
+ SIGNAL_HANDLER
+ organ_flags &= ~ORGAN_FAILING
+
diff --git a/code/modules/surgery/repair_puncture.dm b/code/modules/surgery/repair_puncture.dm
index 43a48f1936..adf618426f 100644
--- a/code/modules/surgery/repair_puncture.dm
+++ b/code/modules/surgery/repair_puncture.dm
@@ -12,13 +12,13 @@
target_mobtypes = list(/mob/living/carbon)
possible_locs = list(BODY_ZONE_R_ARM,BODY_ZONE_L_ARM,BODY_ZONE_R_LEG,BODY_ZONE_L_LEG,BODY_ZONE_CHEST,BODY_ZONE_HEAD)
requires_real_bodypart = TRUE
- targetable_wound = /datum/wound/pierce
+ targetable_wound = /datum/wound/pierce/bleed
/datum/surgery/repair_puncture/can_start(mob/living/user, mob/living/carbon/target)
. = ..()
if(.)
var/obj/item/bodypart/targeted_bodypart = target.get_bodypart(user.zone_selected)
- var/datum/wound/burn/pierce_wound = targeted_bodypart.get_wound_type(targetable_wound)
+ var/datum/wound/pierce/bleed/pierce_wound = targeted_bodypart.get_wound_type(targetable_wound)
return(pierce_wound && pierce_wound.blood_flow > 0)
//SURGERY STEPS
@@ -30,7 +30,7 @@
time = 3 SECONDS
/datum/surgery_step/repair_innards/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
- var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound
if(!pierce_wound)
user.visible_message("
[user] looks for [target]'s [parse_zone(user.zone_selected)].", "
You look for [target]'s [parse_zone(user.zone_selected)]...")
return
@@ -45,7 +45,7 @@
"
[user] begins to realign the torn blood vessels in [target]'s [parse_zone(user.zone_selected)].")
/datum/surgery_step/repair_innards/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
- var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound
if(!pierce_wound)
to_chat(user, "
[target] has no puncture wound there!")
return ..()
@@ -78,7 +78,7 @@
return TRUE
/datum/surgery_step/seal_veins/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
- var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound
if(!pierce_wound)
user.visible_message("
[user] looks for [target]'s [parse_zone(user.zone_selected)].", "
You look for [target]'s [parse_zone(user.zone_selected)]...")
return
@@ -87,7 +87,7 @@
"
[user] begins to meld some of the split blood vessels in [target]'s [parse_zone(user.zone_selected)].")
/datum/surgery_step/seal_veins/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results = FALSE)
- var/datum/wound/pierce/pierce_wound = surgery.operated_wound
+ var/datum/wound/pierce/bleed/pierce_wound = surgery.operated_wound
if(!pierce_wound)
to_chat(user, "
[target] has no puncture there!")
return ..()
diff --git a/code/modules/surgery/surgery_helpers.dm b/code/modules/surgery/surgery_helpers.dm
index db8ff31664..6207f972bc 100644
--- a/code/modules/surgery/surgery_helpers.dm
+++ b/code/modules/surgery/surgery_helpers.dm
@@ -21,7 +21,7 @@
var/list/available_surgeries = list()
for(var/datum/surgery/S in all_surgeries)
- if(!S.possible_locs.Find(selected_zone))
+ if(LAZYLEN(S.possible_locs) && !S.possible_locs.Find(selected_zone))
continue
if(affecting)
if(!S.requires_bodypart)
diff --git a/code/modules/surgery/surgery_step.dm b/code/modules/surgery/surgery_step.dm
index 8e152bae18..8f9e753f8d 100644
--- a/code/modules/surgery/surgery_step.dm
+++ b/code/modules/surgery/surgery_step.dm
@@ -112,7 +112,7 @@
var/was_sleeping = (target.stat != DEAD && target.IsSleeping())
- if(do_after(user, modded_time, target = target))
+ if(tool ? tool.use_tool(target, user, modded_time) : do_after(user, modded_time, target = target))
var/chem_check_result = chem_check(target)
if((prob(100-fail_prob) || (iscyborg(user) && !silicons_obey_prob)) && chem_check_result && !try_to_fail)
diff --git a/code/modules/unit_tests/medical_wounds.dm b/code/modules/unit_tests/medical_wounds.dm
index 987d04c8ac..34105e04ed 100644
--- a/code/modules/unit_tests/medical_wounds.dm
+++ b/code/modules/unit_tests/medical_wounds.dm
@@ -12,24 +12,25 @@
var/i = 1
var/list/iter_test_wound_list
- for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe),\
- list(/datum/wound/slash/moderate, /datum/wound/slash/critical),\
- list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe),\
- list(/datum/wound/burn/moderate, /datum/wound/burn/severe)))
+ for(iter_test_wound_list in list(list(/datum/wound/blunt/bone/moderate, /datum/wound/blunt/bone/severe, /datum/wound/blunt/bone/critical),\
+ list(/datum/wound/slash/flesh/moderate, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/critical),\
+ list(/datum/wound/pierce/bleed/moderate, /datum/wound/pierce/bleed/severe, /datum/wound/pierce/bleed/critical),\
+ list(/datum/wound/burn/flesh/moderate, /datum/wound/burn/flesh/severe, /datum/wound/burn/flesh/critical)))
TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
var/datum/wound/iter_test_wound
+ var/datum/wound_pregen_data/iter_pregen_data = SSwounds.pregen_data[iter_test_wound]
var/threshold_penalty = 0
for(iter_test_wound in iter_test_wound_list)
- var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
if(dam_types[i] == BRUTE)
tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
else if(dam_types[i] == BURN)
tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i])
TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]")
- TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ //TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
var/datum/wound/actual_wound = victim.all_wounds[1]
TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]")
threshold_penalty = actual_wound.threshold_penalty
@@ -50,36 +51,39 @@
var/i = 1
var/list/iter_test_wound_list
- victim.dna.species.species_traits &= HAS_FLESH // take away the base human's flesh (ouchie!) ((not actually ouchie, this just affects their wounds and dismemberment handling))
+ tested_part.biological_state &= ~BIO_FLESH // take away the base limb's flesh (ouchie!) ((not actually ouchie, this just affects their wounds and dismemberment handling))
- for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe),\
- list(/datum/wound/slash/moderate, /datum/wound/slash/critical),\
- list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe),\
- list(/datum/wound/burn/moderate, /datum/wound/burn/severe)))
+ for(iter_test_wound_list in list(list(/datum/wound/blunt/bone/moderate, /datum/wound/blunt/bone/severe, /datum/wound/blunt/bone/critical),\
+ list(/datum/wound/slash/flesh/moderate, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/critical),\
+ list(/datum/wound/pierce/bleed/moderate, /datum/wound/pierce/bleed/severe, /datum/wound/pierce/bleed/critical),\
+ list(/datum/wound/burn/flesh/moderate, /datum/wound/burn/flesh/severe, /datum/wound/burn/flesh/critical)))
TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
var/datum/wound/iter_test_wound
+ var/datum/wound_pregen_data/iter_pregen_data = SSwounds.pregen_data[iter_test_wound]
var/threshold_penalty = 0
for(iter_test_wound in iter_test_wound_list)
- var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
if(dam_types[i] == BRUTE)
tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
else if(dam_types[i] == BURN)
tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i])
// so if we just tried to deal a flesh wound, make sure we didn't actually suffer it. We may have suffered a bone wound instead, but we just want to make sure we don't have a flesh wound
- if(initial(iter_test_wound.wound_flags) & FLESH_WOUND)
+ var/datum/wound_pregen_data/pregen_data = SSwounds.pregen_data[iter_test_wound]
+ if (pregen_data.required_limb_biostate & BIO_FLESH)
if(!length(victim.all_wounds)) // not having a wound is good news
continue
else // we have to check that it's actually a bone wound and not the intended wound type
- TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ //TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
var/datum/wound/actual_wound = victim.all_wounds[1]
- TEST_ASSERT((actual_wound.wound_flags & ~FLESH_WOUND), "Patient has flesh wound despite no HAS_FLESH flag, expected either no wound or bone wound. Offending wound: [actual_wound]")
+ var/datum/wound_pregen_data/actual_pregen_data = SSwounds.pregen_data[actual_wound.type]
+ TEST_ASSERT((actual_pregen_data.required_limb_biostate & ~BIO_FLESH), "Limb has flesh wound despite no BIO_FLESH biological_state, expected either no wound or bone wound. Offending wound: [actual_wound]")
threshold_penalty = actual_wound.threshold_penalty
else // otherwise if it's a bone wound, check that we have it per usual
TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]")
- TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
+ //TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]")
var/datum/wound/actual_wound = victim.all_wounds[1]
TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]")
threshold_penalty = actual_wound.threshold_penalty
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 83b3c06a47..253c56eede 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -548,11 +548,9 @@ IF YOU MODIFY THE PRODUCTS LIST OF A MACHINE, MAKE SURE TO UPDATE ITS RESUPPLY C
squish_part = C.bodyparts[zone]
if(!squish_part)
continue
- if(IS_ORGANIC_LIMB(squish_part))
- var/type_wound = pick(list(/datum/wound/blunt/severe, /datum/wound/blunt/moderate))
- squish_part.force_wound_upwards(type_wound)
- else
- squish_part.receive_damage(brute=30)
+ var/severity = pick(WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL)
+ if(!C.cause_wound_of_type_and_severity(WOUND_BLUNT, squish_part, severity, wound_source = "crushed by [src]"))
+ squish_part.receive_damage(brute = 30)
C.visible_message(
span_userdanger("[C]'s body is maimed underneath the mass of [src]!"),
span_userdanger("Your body is maimed underneath the mass of [src]!"),
diff --git a/shiptest.dme b/shiptest.dme
index bbc10c7224..e10bc121df 100644
--- a/shiptest.dme
+++ b/shiptest.dme
@@ -443,6 +443,7 @@
#include "code\controllers\subsystem\vis_overlays.dm"
#include "code\controllers\subsystem\vote.dm"
#include "code\controllers\subsystem\weather.dm"
+#include "code\controllers\subsystem\wounds.dm"
#include "code\controllers\subsystem\processing\ai_basic_avoidance.dm"
#include "code\controllers\subsystem\processing\ai_behaviors.dm"
#include "code\controllers\subsystem\processing\ai_movement.dm"
@@ -791,6 +792,7 @@
#include "code\datums\elements\point_of_interest.dm"
#include "code\datums\elements\relay_attackers.dm"
#include "code\datums\elements\renamemob.dm"
+#include "code\datums\elements\robotic_heal.dm"
#include "code\datums\elements\selfknockback.dm"
#include "code\datums\elements\shatters_when_thrown.dm"
#include "code\datums\elements\snail_crawl.dm"
@@ -1012,13 +1014,18 @@
#include "code\datums\wires\syndicatebomb.dm"
#include "code\datums\wires\tesla_coil.dm"
#include "code\datums\wires\vending.dm"
+#include "code\datums\wounds\_wound_static_data.dm"
#include "code\datums\wounds\_wounds.dm"
+#include "code\datums\wounds\blunt.dm"
#include "code\datums\wounds\bones.dm"
#include "code\datums\wounds\burns.dm"
#include "code\datums\wounds\dismember.dm"
#include "code\datums\wounds\muscle.dm"
#include "code\datums\wounds\pierce.dm"
#include "code\datums\wounds\slash.dm"
+#include "code\datums\wounds\robotic\buckling.dm"
+#include "code\datums\wounds\robotic\electrical.dm"
+#include "code\datums\wounds\robotic\heat_warping.dm"
#include "code\game\alternate_appearance.dm"
#include "code\game\atoms.dm"
#include "code\game\atoms_movable.dm"
@@ -3627,14 +3634,17 @@
#include "code\modules\surgery\blood_filter.dm"
#include "code\modules\surgery\bone_fractures.dm"
#include "code\modules\surgery\brain_surgery.dm"
+#include "code\modules\surgery\buckling_repair.dm"
#include "code\modules\surgery\cavity_implant.dm"
#include "code\modules\surgery\coronary_bypass.dm"
#include "code\modules\surgery\debride.dm"
#include "code\modules\surgery\dental_implant.dm"
+#include "code\modules\surgery\electrical_repair.dm"
#include "code\modules\surgery\experimental_dissection.dm"
#include "code\modules\surgery\eye_surgery.dm"
#include "code\modules\surgery\gastrectomy.dm"
#include "code\modules\surgery\healing.dm"
+#include "code\modules\surgery\heatwarp_repair.dm"
#include "code\modules\surgery\hepatectomy.dm"
#include "code\modules\surgery\implant_removal.dm"
#include "code\modules\surgery\ipc_revive.dm"
From ca762771dd42bd970e7a5c46668ede3a979cc201 Mon Sep 17 00:00:00 2001
From: Changelogs
Date: Sun, 1 Feb 2026 14:46:22 -0600
Subject: [PATCH 006/128] Automatic changelog generation for PR #5676 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5676.yml | 11 +++++++++++
1 file changed, 11 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5676.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5676.yml b/html/changelogs/AutoChangeLog-pr-5676.yml
new file mode 100644
index 0000000000..40493deae0
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5676.yml
@@ -0,0 +1,11 @@
+author: SapphicOverload
+changes:
+ - {rscadd: Added several new wound types for robotic limbs.}
+ - {balance: Fire stacks now apply wounds normally instead of instantly
+ applying the highest one possible.}
+ - {code_imp: Multiple wound types can be rolled for and applied
+ simultaneously.}
+ - {code_imp: Burn damage weapons can now apply slash/pierce wounds with the
+ correct sharpness.}
+ - {bugfix: Fixed IPCs taking far more damage in crit than intended.}
+delete-after: true
From 2d22d74469faf12894f541cb663025df6312f1a2 Mon Sep 17 00:00:00 2001
From: Changelogs
Date: Mon, 2 Feb 2026 01:49:04 +0000
Subject: [PATCH 007/128] Automatic changelog compile [ci skip]
---
html/changelogs/AutoChangeLog-pr-5676.yml | 11 -----------
html/changelogs/AutoChangeLog-pr-5842.yml | 4 ----
html/changelogs/AutoChangeLog-pr-5852.yml | 4 ----
html/changelogs/archive/2026-02.yml | 13 +++++++++++++
4 files changed, 13 insertions(+), 19 deletions(-)
delete mode 100644 html/changelogs/AutoChangeLog-pr-5676.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5842.yml
delete mode 100644 html/changelogs/AutoChangeLog-pr-5852.yml
create mode 100644 html/changelogs/archive/2026-02.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5676.yml b/html/changelogs/AutoChangeLog-pr-5676.yml
deleted file mode 100644
index 40493deae0..0000000000
--- a/html/changelogs/AutoChangeLog-pr-5676.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-author: SapphicOverload
-changes:
- - {rscadd: Added several new wound types for robotic limbs.}
- - {balance: Fire stacks now apply wounds normally instead of instantly
- applying the highest one possible.}
- - {code_imp: Multiple wound types can be rolled for and applied
- simultaneously.}
- - {code_imp: Burn damage weapons can now apply slash/pierce wounds with the
- correct sharpness.}
- - {bugfix: Fixed IPCs taking far more damage in crit than intended.}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-5842.yml b/html/changelogs/AutoChangeLog-pr-5842.yml
deleted file mode 100644
index 50d348b6ee..0000000000
--- a/html/changelogs/AutoChangeLog-pr-5842.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: thearbiber
-changes:
- - {rscadd: resprites nightvision}
-delete-after: true
diff --git a/html/changelogs/AutoChangeLog-pr-5852.yml b/html/changelogs/AutoChangeLog-pr-5852.yml
deleted file mode 100644
index a036642442..0000000000
--- a/html/changelogs/AutoChangeLog-pr-5852.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: firebudgy
-changes:
- - {balance: APC Control Console removed from all player-facing environments.}
-delete-after: true
diff --git a/html/changelogs/archive/2026-02.yml b/html/changelogs/archive/2026-02.yml
new file mode 100644
index 0000000000..773dcd8df2
--- /dev/null
+++ b/html/changelogs/archive/2026-02.yml
@@ -0,0 +1,13 @@
+2026-02-02:
+ SapphicOverload:
+ - rscadd: Added several new wound types for robotic limbs.
+ - balance: Fire stacks now apply wounds normally instead of instantly applying the
+ highest one possible.
+ - code_imp: Multiple wound types can be rolled for and applied simultaneously.
+ - code_imp: Burn damage weapons can now apply slash/pierce wounds with the correct
+ sharpness.
+ - bugfix: Fixed IPCs taking far more damage in crit than intended.
+ firebudgy:
+ - balance: APC Control Console removed from all player-facing environments.
+ thearbiber:
+ - rscadd: resprites nightvision
From d2338c7cf2412949e7426ee705aeac3fc1fd999d Mon Sep 17 00:00:00 2001
From: firebudgy <153147550+firebudgy@users.noreply.github.com>
Date: Mon, 2 Feb 2026 08:27:48 -0800
Subject: [PATCH 008/128] Changes a mushroom description (#5856)
## About The Pull Request
Changes the description of the Plump Helmet mushroom to 'Plumus
Hellmus; Named for it's helmet-shaped cap and purple coloration.'
## Why It's Good For The Game
What the fuck man.
## Changelog
:cl:
code: Description change of the Plump Helmet Mushroom
/:cl:
---
code/modules/hydroponics/grown/mushrooms.dm | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/hydroponics/grown/mushrooms.dm b/code/modules/hydroponics/grown/mushrooms.dm
index 948f855b56..379b18e2f5 100644
--- a/code/modules/hydroponics/grown/mushrooms.dm
+++ b/code/modules/hydroponics/grown/mushrooms.dm
@@ -136,7 +136,7 @@
/obj/item/food/grown/mushroom/plumphelmet
seed = /obj/item/seeds/plump
name = "plump-helmet"
- desc = "Plumus Hellmus: Plump, soft and s-so inviting~"
+ desc = "Plumus Hellmus; Named for it's helmet-shaped cap and purple coloration."
icon_state = "plumphelmet"
filling_color = "#9370DB"
distill_reagent = /datum/reagent/consumable/ethanol/manly_dorf
From 0b9a21363962db07c0fbf21ba9131989d2beac38 Mon Sep 17 00:00:00 2001
From: Changelogs
Date: Mon, 2 Feb 2026 10:48:18 -0600
Subject: [PATCH 009/128] Automatic changelog generation for PR #5856 [ci skip]
---
html/changelogs/AutoChangeLog-pr-5856.yml | 4 ++++
1 file changed, 4 insertions(+)
create mode 100644 html/changelogs/AutoChangeLog-pr-5856.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5856.yml b/html/changelogs/AutoChangeLog-pr-5856.yml
new file mode 100644
index 0000000000..24566a2449
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-5856.yml
@@ -0,0 +1,4 @@
+author: firebudgy
+changes:
+ - {code_imp: Description change of the Plump Helmet Mushroom}
+delete-after: true
From 55e7a91f7ffdfc55e88df983f9a49f9ba3544e93 Mon Sep 17 00:00:00 2001
From: Changelogs
Date: Tue, 3 Feb 2026 01:48:03 +0000
Subject: [PATCH 010/128] Automatic changelog compile [ci skip]
---
html/changelogs/AutoChangeLog-pr-5856.yml | 4 ----
html/changelogs/archive/2026-02.yml | 3 +++
2 files changed, 3 insertions(+), 4 deletions(-)
delete mode 100644 html/changelogs/AutoChangeLog-pr-5856.yml
diff --git a/html/changelogs/AutoChangeLog-pr-5856.yml b/html/changelogs/AutoChangeLog-pr-5856.yml
deleted file mode 100644
index 24566a2449..0000000000
--- a/html/changelogs/AutoChangeLog-pr-5856.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: firebudgy
-changes:
- - {code_imp: Description change of the Plump Helmet Mushroom}
-delete-after: true
diff --git a/html/changelogs/archive/2026-02.yml b/html/changelogs/archive/2026-02.yml
index 773dcd8df2..fd46b4f96d 100644
--- a/html/changelogs/archive/2026-02.yml
+++ b/html/changelogs/archive/2026-02.yml
@@ -11,3 +11,6 @@
- balance: APC Control Console removed from all player-facing environments.
thearbiber:
- rscadd: resprites nightvision
+2026-02-03:
+ firebudgy:
+ - code_imp: Description change of the Plump Helmet Mushroom
From 814c2953de60fcba45aaf200b85fd55e501769a4 Mon Sep 17 00:00:00 2001
From: thearbiber <135081923+thearbiber@users.noreply.github.com>
Date: Tue, 3 Feb 2026 14:33:22 -0500
Subject: [PATCH 011/128] digi sprite for old bloodred (#5819)
## About The Pull Request
## Why It's Good For The Game
spirt
## Changelog
:cl:
add: digi sprites for old bloodred
/:cl:
---
code/modules/clothing/spacesuits/hardsuit.dm | 2 +-
icons/mob/species/misc/digitigrade_suits.dmi | Bin 74428 -> 76947 bytes
2 files changed, 1 insertion(+), 1 deletion(-)
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index 5088401b8a..97ae986e94 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -461,7 +461,7 @@
armor = list("melee" = 35, "bullet" = 40, "laser" = 20,"energy" = 40, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 75, "acid" = 75, "wound" = 20)
slowdown = 0.5
jetpack = null
- supports_variations = KEPORI_VARIATION
+ supports_variations = KEPORI_VARIATION | DIGITIGRADE_VARIATION
/obj/item/clothing/head/helmet/space/hardsuit/syndi/old
name = "worn blood-red hardsuit helmet"
diff --git a/icons/mob/species/misc/digitigrade_suits.dmi b/icons/mob/species/misc/digitigrade_suits.dmi
index d0be30334c318e4251f312e79c6ab8f37851e05e..5070bf0682aaf160bc3bd901c4e346178b668d0e 100644
GIT binary patch
delta 57707
zcmbrlWn5HY_bxm`io~F(bho0QbPggR4bmdrlG44A4(Sp}=>|!qmF{jS>CT~N=4_t-
z^S-@u60VjyI_y;4FUUs+&ZSb8;}MLSRG9xob>ZvFU&XG+XQ
zify%lo!n#Wz~D6}5`eJ;XBDM!Ind+|Vy1;)nVS#DJh(jueuW>6$8z`!RN1CRnX0C}*Xp&`m31mV
z;63x&=bHLbbnq%R85U?u__wyS-C8lD_V!V%~EspV3FXco8tZ!kVu=F5?g*|d!e)9;ZFym3P!qmNs}iLBjcInf>URvT7n
z{lX5Df^>!)oPU5vWhMfR4ds&yMg^|XHyf~1RSr*bF%sA611OII;1(Ue+Hitw4f+z3
zMM;6z;4yAUaV9~>q!ahUMknbDEx9mMI4oV@^i*Kg-ENP!H*!-cA@M45HRUnVT$)@l
z$%mDO`BzF9$<_O~#4r+g%z8m!0LN#h7YZQe!p3DH8n1y
z&G9&SVRkX0;_{FidRi$9C3D+fU{x9x67R60Fa$vxu|w15tX+eEBHMvwg{HGF+X?
zw*arjWcNp*SLzXQp%I#nKO4D9S|+Kc%U`8N5*ue@L~r2;v?P3C^d0T1Jn)(%y4;(k
zNfVuK@1o|P8~AC~{%WT5SnG1IB!ogw?C)FdKqn^h)YDF@_#8%7neR%Tz7-adg_wP@
zrZzPxDW{`5@gy5g>Je+w)qtJxiwvE5{E#IpC}#54-4S3guw>{CfZG2y$2*V}00D5JR?nLNV91Ybu_ciS-dQ_iCH1NPllI;v;t
ze8@G+-kN=Xp{12m^HJ()ovlGIsgg*<`R~~>bmM!Z#Tr@DAEk;_d<lWpSPEiGR4JHTO4;^z)aTroJ|ED&g2%_12zVnaA#c{yV**DOe*U!pXX1Hdw;Ni
zioesp%@9n8!ZG}rR(^Yu>F@1xP5SFLaC*b9h^nPom_f-*QmX1yh9;>!e~2FVo^%V&>`xjo
zV9=7Rs1(ataL?yC|2HJ;K$K&YiIl<~v#o1+>t~^;st0nLxTs<>W!s4<)R~;%ro_ek
znhK9WFSxmaHfad)bO`z3kS-PZ7nqnLLOpi4`r3rE8K)cknIC_GOT^Yi0*8zy$bP2d%66v-S4+QV{HO!NR$|U;KUnX{6j<+
zQ+URb*yjgKi~PK(l*|NG?lKsh+j*y0z_BICodjZb_24itC^lY?ZWAEg?4OgwU7Bgp
zN2OM5H(s^b^l%O(vy;9&{s*ItM{iiW^=4CZO=4&5X*0c7#y8mfzVjL@7#T3bo!swz
zPh|~$Tbr%??fvr>j#$(Ds?c&+JL-G?L)ndS)Gu0*NM?GN}a#M@P5=h025TJ