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"