From f47b29a3922da57d7c42dfb8fee73291a6e669ac Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Wed, 23 Nov 2022 17:40:59 +0000 Subject: [PATCH] Basic Mob Carp Part II: Regenerator (#71437) ## About The Pull Request
Video https://user-images.githubusercontent.com/7483112/203324325-5bf46e0f-b294-4832-9016-aec275036ca9.mp4
Adds a "Regenerator" component, applies it to carp, and removes the previous implementation from megacarp. The function of this component is that any time you take damage a timer is reset, when that timer expires you will begin regenerating health and display an animated border for visual feedback. This was previously a function of mega carp but was extremely obscure because there was _no_ feedback, so it would only be visible that it was happening if you were wearing a medical hud. Additionally because mega carp will pursue a target until it dies you probably wouldn't notice it even then, it had to survive a fight in order to gain any value from this. This will still be true for all carp until AI changes are pushed, so until then this isn't going to do very much for NPCs but might be a small buff to player controlled carp (though those can all retreat to a carp rift for a similar effect). This also modifies a few "defence" procs which were either directly setting HP or neglecting to call signals from a base proc, as they would bypass triggering the effect. This should make them more reliable for other things which rely on those signals too. ## Why It's Good For The Game Gives an iconic creature some more unique behaviour than "moves directly towards you and bites". Gives visual feedback to some behaviour which has existed invisibly for a long time. ## Changelog :cl: add: All Space Carp will now start regenerating health after 6 seconds of not taking any damage, until they're back to full health. This behaviour was previously unique to mega carp. /:cl: --- code/__DEFINES/colors.dm | 3 +- code/datums/components/regenerator.dm | 93 +++++++++++++++++++ .../modules/mob/living/basic/basic_defense.dm | 16 ++-- code/modules/mob/living/living_defense.dm | 1 + .../mob/living/simple_animal/hostile/carp.dm | 16 +--- tgstation.dme | 1 + 6 files changed, 107 insertions(+), 23 deletions(-) create mode 100644 code/datums/components/regenerator.dm diff --git a/code/__DEFINES/colors.dm b/code/__DEFINES/colors.dm index fbfcaededc7..a8b3389e923 100644 --- a/code/__DEFINES/colors.dm +++ b/code/__DEFINES/colors.dm @@ -59,6 +59,7 @@ #define COLOR_GREEN "#008000" #define COLOR_DARK_MODERATE_LIME_GREEN "#44964A" #define COLOR_PAI_GREEN "#00FF88" +#define COLOR_PALE_GREEN "#20e28e" #define COLOR_CYAN "#00FFFF" #define COLOR_DARK_CYAN "#00A2FF" @@ -258,8 +259,6 @@ #define CABLE_COLOR_YELLOW "yellow" #define CABLE_HEX_COLOR_YELLOW COLOR_YELLOW - - GLOBAL_LIST_INIT(cable_colors, list( CABLE_COLOR_BLUE = CABLE_HEX_COLOR_BLUE, CABLE_COLOR_CYAN = CABLE_HEX_COLOR_CYAN, diff --git a/code/datums/components/regenerator.dm b/code/datums/components/regenerator.dm new file mode 100644 index 00000000000..dc219603113 --- /dev/null +++ b/code/datums/components/regenerator.dm @@ -0,0 +1,93 @@ +#define REGENERATION_FILTER "healing_glow" + +/** + * # Regenerator component + * + * A mob with this component will regenerate its health over time, as long as it has not received damage + * in the last X seconds. Taking any damage will reset this cooldown. + */ +/datum/component/regenerator + /// You will only regain health if you haven't been hurt for this many seconds + var/regeneration_delay + /// Health to regenerate per second + var/health_per_second + /// List of damage types we don't care about, in case you want to only remove this with fire damage or something + var/list/ignore_damage_types + /// Colour of regeneration animation, or none if you don't want one + var/outline_colour + /// When this timer completes we start restoring health, it is a timer rather than a cooldown so we can do something on its completion + var/regeneration_start_timer + +/datum/component/regenerator/Initialize(regeneration_delay = 6 SECONDS, health_per_second = 2, ignore_damage_types = list(STAMINA), outline_colour = COLOR_PALE_GREEN) + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + src.regeneration_delay = regeneration_delay + src.health_per_second = health_per_second + src.ignore_damage_types = ignore_damage_types + src.outline_colour = outline_colour + +/datum/component/regenerator/RegisterWithParent() + . = ..() + RegisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE, PROC_REF(on_take_damage)) + +/datum/component/regenerator/UnregisterFromParent() + . = ..() + if(regeneration_start_timer) + deltimer(regeneration_start_timer) + UnregisterSignal(parent, COMSIG_MOB_APPLY_DAMAGE) + stop_regenerating() + +/datum/component/regenerator/Destroy(force, silent) + stop_regenerating() + . = ..() + if(regeneration_start_timer) + deltimer(regeneration_start_timer) + +/// When you take damage, reset the cooldown and start processing +/datum/component/regenerator/proc/on_take_damage(datum/source, damage, damagetype) + SIGNAL_HANDLER + + if (damage <= 0) + return + if (locate(damagetype) in ignore_damage_types) + return + stop_regenerating() + if(regeneration_start_timer) + deltimer(regeneration_start_timer) + regeneration_start_timer = addtimer(CALLBACK(src, PROC_REF(start_regenerating)), regeneration_delay, TIMER_STOPPABLE) + +/// Start processing health regeneration, and show animation if provided +/datum/component/regenerator/proc/start_regenerating() + var/mob/living/living_parent = parent + if (living_parent.stat == DEAD) + return + if (living_parent.health == living_parent.maxHealth) + return + living_parent.visible_message(span_notice("[living_parent]'s wounds begin to knit closed!")) + START_PROCESSING(SSobj, src) + if (!outline_colour) + return + living_parent.add_filter(REGENERATION_FILTER, 2, list("type" = "outline", "color" = outline_colour, "alpha" = 0, "size" = 1)) + var/filter = living_parent.get_filter(REGENERATION_FILTER) + animate(filter, alpha = 200, time = 0.5 SECONDS, loop = -1) + animate(alpha = 0, time = 0.5 SECONDS) + +/datum/component/regenerator/proc/stop_regenerating() + STOP_PROCESSING(SSobj, src) + var/mob/living/living_parent = parent + var/filter = living_parent.get_filter(REGENERATION_FILTER) + animate(filter) + living_parent.remove_filter(REGENERATION_FILTER) + +/datum/component/regenerator/process(delta_time = SSMOBS_DT) + var/mob/living/living_parent = parent + if (living_parent.stat == DEAD) + stop_regenerating() + return + if (living_parent.health == living_parent.maxHealth) + stop_regenerating() + return + living_parent.heal_overall_damage(health_per_second * delta_time) + +#undef REGENERATION_FILTER diff --git a/code/modules/mob/living/basic/basic_defense.dm b/code/modules/mob/living/basic/basic_defense.dm index 60ff41e4bf8..149fc1c6bec 100644 --- a/code/modules/mob/living/basic/basic_defense.dm +++ b/code/modules/mob/living/basic/basic_defense.dm @@ -53,7 +53,7 @@ visible_message(span_danger("[user] punches [src]!"), \ span_userdanger("You're punched by [user]!"), null, COMBAT_MESSAGE_RANGE, user) to_chat(user, span_danger("You punch [src]!")) - adjustBruteLoss(15) + apply_damage(15, damagetype = BRUTE) /mob/living/basic/attack_paw(mob/living/carbon/human/user, list/modifiers) if(..()) //successful monkey bite. @@ -135,10 +135,8 @@ apply_damage(damage, damagetype, null, getarmor(null, armorcheck)) return TRUE -/mob/living/basic/bullet_act(obj/projectile/Proj, def_zone, piercing_hit = FALSE) - apply_damage(Proj.damage, Proj.damage_type) - Proj.on_hit(src, 0, piercing_hit) - return BULLET_ACT_HIT +/mob/living/basic/check_projectile_armor(def_zone, obj/projectile/impacting_projectile, is_silent) + return 0 /mob/living/basic/ex_act(severity, target, origin) if(origin && istype(origin, /datum/spacevine_mutation) && isvineimmune(src)) @@ -151,7 +149,7 @@ switch (severity) if (EXPLODE_DEVASTATE) if(prob(bomb_armor)) - adjustBruteLoss(500) + apply_damage(500, damagetype = BRUTE) else investigate_log("has been gibbed by an explosion.", INVESTIGATE_DEATHS) gib() @@ -160,16 +158,16 @@ var/bloss = 60 if(prob(bomb_armor)) bloss = bloss / 1.5 - adjustBruteLoss(bloss) + apply_damage(bloss, damagetype = BRUTE) if (EXPLODE_LIGHT) var/bloss = 30 if(prob(bomb_armor)) bloss = bloss / 1.5 - adjustBruteLoss(bloss) + apply_damage(bloss, damagetype = BRUTE) /mob/living/basic/blob_act(obj/structure/blob/B) - adjustBruteLoss(20) + apply_damage(20, damagetype = BRUTE) return /mob/living/basic/do_attack_animation(atom/A, visual_effect_icon, used_item, no_effect) diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 803d67e0ed4..3a49a83c5a7 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -231,6 +231,7 @@ return TRUE /mob/living/attack_basic_mob(mob/living/basic/user, list/modifiers) + . = ..() if(user.melee_damage_upper == 0) if(user != src) visible_message(span_notice("\The [user] [user.friendly_verb_continuous] [src]!"), \ diff --git a/code/modules/mob/living/simple_animal/hostile/carp.dm b/code/modules/mob/living/simple_animal/hostile/carp.dm index 1fffeef1145..ea279776b4e 100644 --- a/code/modules/mob/living/simple_animal/hostile/carp.dm +++ b/code/modules/mob/living/simple_animal/hostile/carp.dm @@ -68,6 +68,8 @@ ) /// Is the carp tamed? var/tamed = FALSE + /// What colour is our 'healing' outline? + var/regenerate_colour = COLOR_PALE_GREEN /mob/living/simple_animal/hostile/carp/Initialize(mapload, mob/tamer) AddElement(/datum/element/simple_flying) @@ -77,6 +79,7 @@ . = ..() ADD_TRAIT(src, TRAIT_HEALS_FROM_CARP_RIFTS, INNATE_TRAIT) ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT) + AddComponent(/datum/component/regenerator, outline_colour = regenerate_colour) add_cell_sample() if(ai_controller) ai_controller.blackboard[BB_HOSTILE_ATTACK_WORD] = pick(speak_emote) @@ -115,7 +118,6 @@ can_have_ai = FALSE toggle_ai(AI_OFF) - /mob/living/simple_animal/hostile/carp/add_cell_sample() AddElement(/datum/element/swabable, CELL_LINE_TABLE_CARP, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) @@ -157,6 +159,7 @@ gold_core_spawnable = NO_SPAWN del_on_death = 1 random_color = FALSE + regenerate_colour = COLOR_WHITE /mob/living/simple_animal/hostile/carp/holocarp/add_cell_sample() @@ -183,7 +186,6 @@ melee_damage_lower = 20 melee_damage_upper = 20 butcher_results = list(/obj/item/food/fishmeat/carp = 2, /obj/item/stack/sheet/animalhide/carp = 3) - var/regen_cooldown = 0 /mob/living/simple_animal/hostile/carp/megacarp/Initialize(mapload) . = ..() @@ -197,11 +199,6 @@ /mob/living/simple_animal/hostile/carp/megacarp/add_cell_sample() AddElement(/datum/element/swabable, CELL_LINE_TABLE_MEGACARP, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) -/mob/living/simple_animal/hostile/carp/megacarp/adjustHealth(amount, updating_health = TRUE, forced = FALSE) - . = ..() - if(.) - regen_cooldown = world.time + REGENERATION_DELAY - /mob/living/simple_animal/hostile/carp/megacarp/Login() . = ..() if(!. || !client) @@ -211,11 +208,6 @@ can_buckle = TRUE buckle_lying = 0 -/mob/living/simple_animal/hostile/carp/megacarp/Life(delta_time = SSMOBS_DT, times_fired) - . = ..() - if(regen_cooldown < world.time) - heal_overall_damage(2 * delta_time) - /mob/living/simple_animal/hostile/carp/lia name = "Lia" real_name = "Lia" diff --git a/tgstation.dme b/tgstation.dme index 6e53ed4fdbb..26160fa9005 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -874,6 +874,7 @@ #include "code\datums\components\puzzgrid.dm" #include "code\datums\components\radiation_countdown.dm" #include "code\datums\components\reagent_refiller.dm" +#include "code\datums\components\regenerator.dm" #include "code\datums\components\religious_tool.dm" #include "code\datums\components\remote_materials.dm" #include "code\datums\components\rename.dm"