diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index 8c91bca37a..10294e7b85 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -361,6 +361,11 @@
#define MOB_CLASS_ALL (~MOB_CLASS_NONE)
+// Shorthands for simple mob healing with items; adjust lists as additional healing behavior is added.
+#define MOB_CLASSES_HEALABLE (MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL|MOB_CLASS_SYNTHETIC)
+#define MOB_CLASSES_UNHEALABLE (MOB_CLASS_SLIME|MOB_CLASS_ABERRATION|MOB_CLASS_DEMONIC|MOB_CLASS_ILLUSION|MOB_CLASS_PHOTONIC)
+
+
// For slime commanding. Higher numbers allow for more actions.
#define SLIME_COMMAND_OBEY 1 // When disciplined.
#define SLIME_COMMAND_FACTION 2 // When in the same 'faction'.
diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm
index d9108d5260..24b0608023 100644
--- a/code/game/objects/items/devices/defib.dm
+++ b/code/game/objects/items/devices/defib.dm
@@ -346,6 +346,12 @@
return 0
/obj/item/shockpaddles/attack(mob/living/M, mob/living/user, var/target_zone)
+
+ if(istype(M, /mob/living/simple_mob) && user.a_intent == I_HELP)
+ var/mob/living/simple_mob/critter = M
+ if(critter.attempt_healing(user, src))
+ return TRUE
+
var/mob/living/carbon/human/H = M
if(!istype(H) || user.a_intent == I_HURT)
return ..() //Do a regular attack. Harm intent shocking happens as a hit effect
@@ -353,13 +359,10 @@
if(can_use(user, H))
busy = 1
update_icon()
-
do_revive(H, user)
-
busy = 0
update_icon()
-
- return 1
+ return TRUE
//Since harm-intent now skips the delay for deliberate placement, you have to be able to hit them in combat in order to shock people.
/obj/item/shockpaddles/apply_hit_effect(mob/living/target, mob/living/user, var/hit_zone)
diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm
index 29f64bd9ef..ef4e9a3c43 100644
--- a/code/modules/mob/living/simple_mob/defense.dm
+++ b/code/modules/mob/living/simple_mob/defense.dm
@@ -48,39 +48,15 @@
L.visible_message(SPAN_WARNING("\The [L] [response_harm] \the [src]!"))
L.do_attack_animation(src)
-// When somoene clicks us with an item in hand
+// When somoene clicks us with an item in hand.
/mob/living/simple_mob/attackby(var/obj/item/O, var/mob/user)
- if(istype(O, /obj/item/stack/medical) && (mob_class & (MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL)))
+ // Attempt to apply healing items, nanopaste, etc.
+ if(attempt_healing(user, O))
+ return TRUE
- var/datum/gender/T = gender_datums[get_visible_gender()]
- if(stat == DEAD)
- to_chat(user, SPAN_WARNING("\The [src] is dead, medical items won't bring [T.him] back to life."))
- return
-
- var/obj/item/stack/medical/MED = O
- if(health >= getMaxHealth())
- to_chat(user, SPAN_WARNING("\The [src] does not need medical treatment."))
- return
-
- if(!((MED.heal_burn && getFireLoss()) || (MED.heal_brute && getBruteLoss())))
- to_chat(user, SPAN_WARNING("\The [MED] does not seem appropriate to treat \the [src]."))
- return
-
- if(MED.get_amount() < 1)
- to_chat(user, SPAN_WARNING("You do not have enough of \the [MED] to treat \the [src]."))
- return
-
- if(length(MED.apply_sounds))
- playsound(user, pick(MED.apply_sounds), 25)
-
- if(do_mob(user, src, 2 SECONDS) && MED.get_amount() >= 1 && health < getMaxHealth())
- heal_organ_damage(MED.heal_brute * 25, MED.heal_burn * 25)
- visible_message(SPAN_NOTICE("\The [user] applies \the [MED] to \the [src]."))
- MED.use(1)
- return
-
- if(can_butcher(user, O)) //if the animal can be butchered, do so and return. It's likely to be gibbed.
+ //if the animal can be butchered, do so and return. It's likely to be gibbed.
+ if(can_butcher(user, O))
harvest(user, O)
return
diff --git a/code/modules/mob/living/simple_mob/harvesting.dm b/code/modules/mob/living/simple_mob/harvesting.dm
index 8303fae5ed..dc7f2d5558 100644
--- a/code/modules/mob/living/simple_mob/harvesting.dm
+++ b/code/modules/mob/living/simple_mob/harvesting.dm
@@ -17,11 +17,15 @@
/mob/living/simple_mob/examine(mob/user)
. = ..()
+
if(user && (isobserver(user) || get_dist(user, src) <= 3))
var/datum/gender/G = gender_datums[get_visible_gender()]
if(stat == DEAD)
- . += "[G.He] [G.is] dead."
+ if(mob_class & MOB_CLASS_SYNTHETIC)
+ . += "[G.He] [G.is] offline."
+ else
+ . += "[G.He] [G.is] dead."
return
if(harvest_tool)
@@ -32,14 +36,15 @@
else
. += SPAN_NOTICE("It can be [harvest_verb] now.")
+ var/is_synthetic = isSynthetic()
var/damage_strings = list()
var/percent_brute = getBruteLoss() / getMaxHealth()
if(percent_brute > 0.6)
- damage_strings += SPAN_DANGER("maimed bloody")
+ damage_strings += SPAN_DANGER(is_synthetic ? "falling apart" : "maimed bloody")
else if(percent_brute > 0.3)
- damage_strings += SPAN_WARNING("cut and bruised")
+ damage_strings += SPAN_WARNING(is_synthetic ? "badly dented" : "cut and bruised")
else if(percent_brute > 0)
- damage_strings += "lightly bruised"
+ damage_strings += is_synthetic ? "lightly dented" : "lightly bruised"
var/percent_burn = getFireLoss() / getMaxHealth()
if(percent_burn > 0.6)
@@ -52,13 +57,13 @@
if(!length(damage_strings))
var/percent_health = health / getMaxHealth()
if(percent_health >= 1)
- damage_strings += SPAN_NOTICE("uninjured")
+ damage_strings += SPAN_NOTICE(is_synthetic ? "undamaged" : "uninjured")
else if(percent_health >= 0.7)
- damage_strings += "mildly injured"
+ damage_strings += is_synthetic ? "lightly damaged" : "mildly injured"
else if(percent_health >= 0.4)
- damage_strings += SPAN_WARNING("moderately injured")
+ damage_strings += SPAN_WARNING(is_synthetic ? "moderately damaged" : "moderately injured")
else
- damage_strings += SPAN_DANGER("badly injured")
+ damage_strings += SPAN_DANGER(is_synthetic ? "badly damaged" : "badly injured")
. += "[G.He] [G.is] [english_list(damage_strings)]."
@@ -66,14 +71,9 @@
if(!LAZYLEN(harvest_results)) // Might be a unique interaction of an object using the proc to do something weird, or just someone's a donk.
harvest_recent = world.time
return
-
if(istype(tool, harvest_tool)) // Sanity incase something incorrect is passed in.
harvest_recent = world.time
-
var/max_harvests = rand(1,harvest_per_hit)
-
for(var/I = 1 to max_harvests)
var/new_path = pickweight(harvest_results)
new new_path(get_turf(user))
-
- return
diff --git a/code/modules/mob/living/simple_mob/healing.dm b/code/modules/mob/living/simple_mob/healing.dm
new file mode 100644
index 0000000000..b8702a6705
--- /dev/null
+++ b/code/modules/mob/living/simple_mob/healing.dm
@@ -0,0 +1,182 @@
+// Simple mob healing interactions, moved here to keep attackby() shorter.
+// TODO: Potentially datumize this system a la Bay's movement handlers; have
+// some /decl/mob_heal_method static list on a proc based on mob flags, then
+// iterate it in attackby.
+
+// Checks if a tool can heal us; left as a proc for override in the future.
+/mob/living/simple_mob/proc/item_can_heal_mob(var/obj/item/thing)
+ var/static/list/_item_heals_synthetic_simplemobs = list(
+ /obj/item/borg/upgrade/restart,
+ /obj/item/weldingtool,
+ /obj/item/stack/cable_coil,
+ /obj/item/stack/nanopaste
+ )
+ var/static/list/_items_heals_nonsynthetic_simplemobs = list(
+ /obj/item/stack/medical,
+ /obj/item/shockpaddles
+ )
+ return is_type_in_list(thing, (isSynthetic() ? _item_heals_synthetic_simplemobs : _items_heals_nonsynthetic_simplemobs))
+
+// Make an attempt to heal the mob with a tool supplied by a user.
+// Return value is FALSE if parent attackby should proceed, TRUE
+// if we handled it and it should end.
+/mob/living/simple_mob/proc/attempt_healing(var/mob/user, var/obj/item/tool)
+
+ // Check our base mob_class in case we are currently unhandled (slime, aberration, demon)
+ if(!(mob_class & MOB_CLASSES_HEALABLE) || (mob_class & MOB_CLASSES_UNHEALABLE))
+ return FALSE
+
+ /// Check if the supplied tool can even theoretically be applied to us.
+ if(!item_can_heal_mob(tool))
+ return FALSE
+
+ // Prevent spamming.
+ user.setClickCooldown(user.get_attack_speed(tool))
+
+ var/datum/gender/T = gender_datums[get_visible_gender()]
+
+ // If they're dead, all we can do is revive them, not heal them.
+ // They recover a bit easier than humans since they have no DOT
+ // to kill them immediately after revival.
+ if(stat == DEAD)
+
+ // Too late, too bad, so sad.
+ var/is_synthetic = isSynthetic()
+ if(world.time - timeofdeath >= 15 MINUTES)
+ to_chat(user, SPAN_NOTICE("Unfortunately, \the [src] is beyond recovery."))
+ else if(istype(tool, is_synthetic ? /obj/item/borg/upgrade/restart : /obj/item/shockpaddles))
+ attempt_medical_revive(user, tool)
+ else
+ to_chat(user, SPAN_WARNING("\The [src] is [is_synthetic ? "beyond repair" : "dead"], patching [T.him] up won't bring [T.him] back[is_synthetic ? " online" : ""]."))
+ return TRUE
+
+ // Basic medical item repair.
+ if(istype(tool, /obj/item/stack/medical))
+ var/obj/item/stack/medical/MED = tool
+
+ // Check if they actually need healing with this item.
+ if(health >= getMaxHealth())
+ to_chat(user, SPAN_WARNING("\The [src] does not need medical treatment."))
+ return TRUE
+ if((MED.heal_burn && !MED.heal_brute) && !getFireLoss())
+ to_chat(user, SPAN_WARNING("\The [src] has no burns to treat."))
+ return TRUE
+ if((!MED.heal_burn && MED.heal_brute) && !getBruteLoss())
+ to_chat(user, SPAN_WARNING("\The [src] has no physical injuries to treat."))
+ return TRUE
+
+ // Apply!
+ if(MED.get_amount() < 1)
+ to_chat(user, SPAN_WARNING("You do not have enough of \the [MED] to treat \the [src]."))
+ return TRUE
+ if(length(MED.apply_sounds))
+ playsound(user, pick(MED.apply_sounds), 25)
+ if(do_mob(user, src, 2 SECONDS) && !QDELETED(MED) && MED.get_amount() >= 1 && health < getMaxHealth())
+ heal_organ_damage(MED.heal_brute * 25, MED.heal_burn * 25)
+ visible_message(SPAN_NOTICE("\The [user] applies \the [MED] to \the [src]."))
+ MED.use(1)
+ return TRUE
+
+ // Welding tool repair for robot brute damage.
+ if(istype(tool, /obj/item/weldingtool))
+ if(!getBruteLoss())
+ to_chat(user, SPAN_WARNING("\The [src] has no physical damage to repair."))
+ return TRUE
+ var/obj/item/weldingtool/WT = tool
+ if(!WT.isOn())
+ to_chat(user, SPAN_WARNING("Turn \the [WT] on first!"))
+ return TRUE
+ if(!WT.remove_fuel(0))
+ to_chat(user, SPAN_WARNING("You need more fuel to repair \the [src]."))
+ return TRUE
+ playsound(loc, 'sound/items/Welder2.ogg', 25)
+ if(do_mob(user, src, 2 SECONDS) && !QDELETED(WT) && WT.remove_fuel(0) && getBruteLoss())
+ visible_message(SPAN_NOTICE("\The [user] patches some of the dents and cracks on \the [src]."))
+ heal_organ_damage(rand(5,15), 0)
+ return TRUE
+
+ // Cable repair for robot burn damage.
+ if(istype(tool, /obj/item/stack/cable_coil))
+ if(!getFireLoss())
+ to_chat(user, SPAN_WARNING("\The [src] has no wiring damage to repair."))
+ return TRUE
+ var/obj/item/stack/cable_coil/coil = tool
+ if(coil.get_amount() < 3)
+ to_chat(user, SPAN_WARNING("You need at least three lengths of cable to repair \the [src]."))
+ return TRUE
+ if(do_mob(user, src, 2 SECONDS) && !QDELETED(coil) && coil.use(3) && getFireLoss())
+ visible_message(SPAN_NOTICE("\The [user] patches some of the damaged wiring in \the [src]."))
+ heal_organ_damage(0, rand(5,15))
+ return TRUE
+
+ // Nanopaste for Powerful Robot Repair.
+ if(istype(tool, /obj/item/stack/nanopaste))
+ if(health >= getMaxHealth())
+ to_chat(user, SPAN_WARNING("\The [src] does not need repair."))
+ return TRUE
+ var/obj/item/stack/nanopaste/N = tool
+ if(N.get_amount() < 1)
+ to_chat(user, SPAN_WARNING("You do not have enough of \the [N] to repair \the [src]."))
+ return TRUE
+ if(do_mob(user, src, 2 SECONDS) && !QDELETED(N) && N.get_amount() >= 1 && health < getMaxHealth())
+ heal_organ_damage(rand(10,15), rand(10,15))
+ visible_message(SPAN_NOTICE("\The [user] applies \the [N] to \the [src]."))
+ return TRUE
+
+/mob/living/simple_mob/proc/attempt_medical_revive(var/mob/user, var/obj/item/tool)
+
+ var/mob/observer/dead/ghost = get_ghost()
+ if(ghost)
+ ghost.notify_revive("Someone is trying to resuscitate you. Re-enter your body if you want to be revived!", 'sound/effects/genetics.ogg', source = src)
+
+ user.visible_message(SPAN_NOTICE("\The [user] begins applying \the [tool] to \the [src]..."))
+
+ var/delay_time = 5 SECONDS
+ var/obj/item/shockpaddles/paddles = tool
+ if(istype(paddles))
+ if(!paddles.check_charge(paddles.chargecost))
+ to_chat(user, SPAN_WARNING("\The [src] does not have enough battery."))
+ return FALSE
+ playsound(src, 'sound/machines/defib_charge.ogg', 50, 0)
+ delay_time = paddles.chargetime
+
+ if(!do_after(user, delay_time, src) || stat != DEAD)
+ return FALSE
+
+ if(istype(paddles))
+ if(QDELETED(paddles) || !paddles.checked_use(paddles.chargecost))
+ return FALSE
+ playsound(src, 'sound/machines/defib_zap.ogg', 100, 1, -1)
+
+ if(key && !client && !teleop)
+ visible_message(SPAN_WARNING("\The [src] twitches a bit, but then falls still."))
+ return FALSE
+
+ // Set the health to what it was prior to rejuve, with a bit of wiggle-room for totally dead critters.
+ var/initial_brute = bruteloss
+ var/initial_burn = fireloss
+ rejuvenate()
+
+ // This is a bit clunky, but set brute and burn such
+ // that they remain injured but don't instantly die.
+ var/halfhealth = round(getMaxHealth() * 0.45)
+ bruteloss = min(initial_brute, halfhealth)
+ fireloss = min(initial_burn, halfhealth)
+ updatehealth()
+
+ // Give them some lingering effects.
+ Confuse(20)
+ Weaken(20)
+ Stun(20)
+ Blind(20)
+ Sleeping(20)
+
+ if(!resting)
+ resting = TRUE
+ update_canmove()
+ update_icon()
+
+ if(istype(tool, /obj/item/borg/upgrade/restart))
+ user.drop_from_inventory(tool)
+ qdel(tool)
+ return TRUE
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka/grafadreka_trained.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka/grafadreka_trained.dm
index 5002a5307c..f8e5a4c8a7 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka/grafadreka_trained.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/grafadreka/grafadreka_trained.dm
@@ -7,7 +7,7 @@ Using grab intent you can pick up and drop items by
faction = "station"
ai_holder_type = null // These guys should not exist without players.
gender = PLURAL // Will take gender from prefs = set to non-NEUTER here to avoid randomizing in Initialize().
- movement_cooldown = 1.5 // ~Red~ trained ones go faster.
+ movement_cooldown = 1.5 // ~~Red~~ trained ones go faster.
dexterity = MOB_DEXTERITY_SIMPLE_MACHINES
harness = /obj/item/storage/internal/animal_harness/grafadreka/trained
trained_drake = TRUE
@@ -20,7 +20,8 @@ Using grab intent you can pick up and drop items by
var/static/list/allow_type_to_pass = list(
/obj/item/healthanalyzer,
/obj/item/stack/medical,
- /obj/item/reagent_containers/syringe
+ /obj/item/reagent_containers/syringe,
+ /obj/item/shockpaddles
)
/mob/living/simple_mob/animal/sif/grafadreka/trained/Destroy()
diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm b/code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm
index bdba0033d8..06d3e63d0b 100644
--- a/code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/animal/sif/sif.dm
@@ -24,10 +24,6 @@
// caps the amount drakes can heal with their sap wound tending interaction.
sap_heal_threshold = min(sap_heal_threshold, (round(health / getMaxHealth() / scarification_period)+1) * scarification_period)
-/mob/living/simple_mob/animal/sif/rejuvenate()
- sap_heal_threshold = 1
- . = ..()
-
/mob/living/simple_mob/animal/sif/examine(mob/user)
. = ..()
if(stat != DEAD)
diff --git a/polaris.dme b/polaris.dme
index 8171c7aea4..6fd51e51b4 100644
--- a/polaris.dme
+++ b/polaris.dme
@@ -2456,6 +2456,7 @@
#include "code\modules\mob\living\simple_mob\defense.dm"
#include "code\modules\mob\living\simple_mob\hands.dm"
#include "code\modules\mob\living\simple_mob\harvesting.dm"
+#include "code\modules\mob\living\simple_mob\healing.dm"
#include "code\modules\mob\living\simple_mob\life.dm"
#include "code\modules\mob\living\simple_mob\on_click.dm"
#include "code\modules\mob\living\simple_mob\simple_hud.dm"