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"