From f4352073762d6d4d24615aeac6a942768fdad6cb Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Mon, 27 Feb 2023 23:27:50 +0000 Subject: [PATCH] Replaces Netherworld mob health scaling component (#73629) ## About The Pull Request The netherworld mob refactor PR added a component called `damage_buffs` which... was both kind of a little bit pointless and also didn't do what the name said it did. This adds a new component which actually does what that component promised to do, and does it more reliably. The previous system had sort of odd behaviour where a Migo would start moving faster as it took damage but _only_ if it took damage with an identifiable mob, so damage from the environment or lava or an explosion or anything else which doesn't come with someone to blame wouldn't update the mob, and neither would its health going back _up_... until someone punched it at which point anything it should have been doing before then would suddenly update. This adds a new component which simply listens to `COMSIG_LIVING_HEALTH_UPDATE` instead of `COMSIG_ATOM_WAS_ATTACKED` and then interpolates the provided maximum and minimum values smoothly based on how close you are to a maximum or minimum health threshold. This is slightly different from the original behaviour which didn't necessarily have those things change linearly and only did it in increments of 25%... but this is cleaner. ## Why It's Good For The Game Fixes some odd behaviour with these mobs. Hopefully makes the code both cleaner and more reusable in the future. ## Changelog :cl: refactor: Migos, Creatures, and Blank Bodies use a new shared component to update their stats which scales smoothly as they take damage rather than in stages. fix: Migos, Creatures and Blank Bodies will all update their stats when taking or losing damage regardless of it was from an identifiable enemy. /:cl: --- code/__DEFINES/maths.dm | 6 ++ code/datums/components/damage_buffs.dm | 25 ------ .../components/health_scaling_effects.dm | 84 +++++++++++++++++++ .../space_fauna/netherworld/blankbody.dm | 29 +------ .../basic/space_fauna/netherworld/creature.dm | 39 ++------- .../basic/space_fauna/netherworld/migo.dm | 31 +------ code/modules/movespeed/modifiers/mobs.dm | 2 +- tgstation.dme | 2 +- 8 files changed, 104 insertions(+), 114 deletions(-) delete mode 100644 code/datums/components/damage_buffs.dm create mode 100644 code/datums/components/health_scaling_effects.dm diff --git a/code/__DEFINES/maths.dm b/code/__DEFINES/maths.dm index f5b44b0f00f..3820512db45 100644 --- a/code/__DEFINES/maths.dm +++ b/code/__DEFINES/maths.dm @@ -99,6 +99,12 @@ // amount=0.5 returns the mean of a and b. #define LERP(a, b, amount) ( amount ? ((a) + ((b) - (a)) * (amount)) : a ) +/** + * Performs an inverse linear interpolation between a, b, and a provided value between a and b + * This returns the amount that you would need to feed into a lerp between A and B to return the third value + */ +#define INVERSE_LERP(a, b, value) ((value - a) / (b - a)) + // Returns the nth root of x. #define ROOT(n, x) ((x) ** (1 / (n))) diff --git a/code/datums/components/damage_buffs.dm b/code/datums/components/damage_buffs.dm deleted file mode 100644 index f13e8f1f64b..00000000000 --- a/code/datums/components/damage_buffs.dm +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Attached to a mob so it can change or do actions based on the fact it got attacked. - */ -/datum/component/damage_buffs - /// Callback to a mob for health changes - var/datum/callback/health_callback - -/datum/component/damage_buffs/Initialize(datum/callback/health_callback) - if(!ismob(parent)) - return ELEMENT_INCOMPATIBLE - - src.health_callback = health_callback - parent.AddElement(/datum/element/relay_attackers) - -/datum/component/damage_buffs/RegisterWithParent() - RegisterSignal(parent, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) - -/datum/component/damage_buffs/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_ATOM_WAS_ATTACKED) - -/// Add an attacking atom to a blackboard list of things which attacked us -/datum/component/damage_buffs/proc/on_attacked(mob/victim, atom/attacker) - SIGNAL_HANDLER - - health_callback?.InvokeAsync(attacker) diff --git a/code/datums/components/health_scaling_effects.dm b/code/datums/components/health_scaling_effects.dm new file mode 100644 index 00000000000..ecfb9ceb1e5 --- /dev/null +++ b/code/datums/components/health_scaling_effects.dm @@ -0,0 +1,84 @@ +/** + * ### Enrage buffs component + * + * Scales some statistics of a living mob (speed or attack power or such) based on how hurt it is. + */ +/datum/component/health_scaling_effects + /// Health percentage (between 0 and 1) at which you are considered to get the full "max" effect + var/max_health_threshold + /// Health percentage (between 0 and 1) at which you are considered to get the full "min" effect + var/min_health_threshold + /// Modification to apply to the lower bound of your attack while your health is at or above the max threshold + var/max_health_attack_modifier_lower + /// Modification to apply to the lower bound of your attack while your health is at or above the min threshold + var/min_health_attack_modifier_lower + /// Modification to apply to the upper bound of your attack while your health is at or above the max threshold + var/max_health_attack_modifier_upper + /// Modification to apply to the upper bound of your attack while your health is at or above the min threshold + var/min_health_attack_modifier_upper + /// Modification to movement speed to apply while your health is at or above the max threshold + var/max_health_slowdown + /// Modification to movement speed to apply while your health is at or above the min threshold + var/min_health_slowdown + /// A callback which is sent the mob's current ratio between the max and min values, for updating mob-specific effects + var/datum/callback/additional_status_callback + +/datum/component/health_scaling_effects/Initialize( + max_health_threshold = 1, + min_health_threshold = 0.25, + max_health_attack_modifier_lower = 0, + min_health_attack_modifier_lower = 0, + max_health_attack_modifier_upper = 0, + min_health_attack_modifier_upper = 0, + max_health_slowdown = 0, + min_health_slowdown = 0, + additional_status_callback = null, +) + if (!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + src.max_health_threshold = max_health_threshold + src.min_health_threshold = min_health_threshold + src.max_health_attack_modifier_lower = max_health_attack_modifier_lower + src.min_health_attack_modifier_lower = min_health_attack_modifier_lower + src.max_health_attack_modifier_upper = max_health_attack_modifier_upper + src.min_health_attack_modifier_upper = min_health_attack_modifier_upper + src.max_health_slowdown = max_health_slowdown + src.min_health_slowdown = min_health_slowdown + src.additional_status_callback = additional_status_callback + + RegisterSignal(parent, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_health_changed)) + +/datum/component/health_scaling_effects/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_LIVING_HEALTH_UPDATE) + return ..() + +/datum/component/health_scaling_effects/Destroy(force, silent) + QDEL_NULL(additional_status_callback) + return ..() + +/// Called when mob health changes, recalculates the ratio between maximum and minimum +/datum/component/health_scaling_effects/proc/on_health_changed(mob/living/source) + SIGNAL_HANDLER + var/current_health_percentage = source.health / source.maxHealth + var/max_min_ratio = clamp(INVERSE_LERP(min_health_threshold, max_health_threshold, current_health_percentage), 0, 1) + + INVOKE_ASYNC(src, PROC_REF(update_stats), source, max_min_ratio) + +/// Update statistics based on provided interpolator between maximum and minimum values +/datum/component/health_scaling_effects/proc/update_stats(mob/living/source, max_min_ratio) + if (max_health_attack_modifier_lower != 0 || min_health_attack_modifier_lower != 0) + var/lower_modifier = LERP(min_health_attack_modifier_lower, max_health_attack_modifier_lower, max_min_ratio) + source.melee_damage_lower = initial(source.melee_damage_lower) + lower_modifier + if (max_health_attack_modifier_upper != 0 || min_health_attack_modifier_upper != 0) + var/upper_modifier = LERP(min_health_attack_modifier_upper, max_health_attack_modifier_upper, max_min_ratio) + source.melee_damage_upper = initial(source.melee_damage_upper) + upper_modifier + + if (max_health_slowdown != 0 || min_health_slowdown != 0) + source.add_or_update_variable_movespeed_modifier( + /datum/movespeed_modifier/health_scaling_speed_buff, + multiplicative_slowdown = LERP(min_health_slowdown, max_health_slowdown, max_min_ratio), + ) + + if (additional_status_callback) + additional_status_callback.Invoke(max_min_ratio) diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm index 32ce7717b94..52dbebc8cf7 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm @@ -29,35 +29,8 @@ /mob/living/basic/blankbody/Initialize(mapload) . = ..() - var/datum/callback/health_changes_callback = CALLBACK(src, PROC_REF(health_check)) AddElement(/datum/element/swabable, CELL_LINE_TABLE_NETHER, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 0) - AddComponent(/datum/component/damage_buffs, health_changes_callback) - -/mob/living/basic/blankbody/proc/health_check(mob/living/attacker) - if(health < maxHealth * 0.25) - health_low_behaviour() - else if (health < maxHealth * 0.5) - health_medium_behaviour() - else if (health < maxHealth * 0.75) - health_high_behaviour() - else - health_full_behaviour() - -/mob/living/basic/blankbody/proc/health_full_behaviour() - melee_damage_lower = 2 - melee_damage_upper = 6 - -/mob/living/basic/blankbody/proc/health_high_behaviour() - melee_damage_lower = 4 - melee_damage_upper = 8 - -/mob/living/basic/blankbody/proc/health_medium_behaviour() - melee_damage_lower = 8 - melee_damage_upper = 12 - -/mob/living/basic/blankbody/proc/health_low_behaviour() - melee_damage_lower = 10 - melee_damage_upper = 20 + AddComponent(/datum/component/health_scaling_effects, min_health_attack_modifier_lower = 8, min_health_attack_modifier_upper = 14) /datum/ai_controller/basic_controller/blankbody blackboard = list( diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm index 061dee20f73..d3fd3462b3a 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm @@ -30,42 +30,17 @@ /mob/living/basic/creature/Initialize(mapload) . = ..() - var/datum/callback/health_changes_callback = CALLBACK(src, PROC_REF(health_check)) AddElement(/datum/element/swabable, CELL_LINE_TABLE_NETHER, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 0) - AddComponent(/datum/component/damage_buffs, health_changes_callback) + AddComponent( + /datum/component/health_scaling_effects,\ + min_health_attack_modifier_lower = 15,\ + min_health_attack_modifier_upper = 30,\ + min_health_slowdown = -1.5,\ + ) + var/datum/action/cooldown/spell/jaunt/creature_teleport/teleport = new(src) teleport.Grant(src) -/mob/living/basic/creature/proc/health_check(mob/living/attacker) - if(health < maxHealth * 0.25) - health_low_behaviour() - else if (health < maxHealth * 0.5) - health_medium_behaviour() - else if (health < maxHealth * 0.75) - health_high_behaviour() - else - health_full_behaviour() - -/mob/living/basic/creature/proc/health_full_behaviour() - melee_damage_lower = 20 - melee_damage_upper = 30 - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = 0) - -/mob/living/basic/creature/proc/health_high_behaviour() - melee_damage_lower = 25 - melee_damage_upper = 40 - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = -0.5) - -/mob/living/basic/creature/proc/health_medium_behaviour() - melee_damage_lower = 30 - melee_damage_upper = 50 - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = -1) - -/mob/living/basic/creature/proc/health_low_behaviour() - melee_damage_lower = 35 - melee_damage_upper = 60 - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = -1.5) - /mob/living/basic/creature/proc/can_be_seen(turf/location) // Check for darkness if(location?.lighting_object) diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm index 651ff8bdada..d6ba8ab0fd4 100644 --- a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm +++ b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm @@ -35,35 +35,12 @@ /mob/living/basic/migo/Initialize(mapload) . = ..() migo_sounds = list('sound/items/bubblewrap.ogg', 'sound/items/change_jaws.ogg', 'sound/items/crowbar.ogg', 'sound/items/drink.ogg', 'sound/items/deconstruct.ogg', 'sound/items/carhorn.ogg', 'sound/items/change_drill.ogg', 'sound/items/dodgeball.ogg', 'sound/items/eatfood.ogg', 'sound/items/megaphone.ogg', 'sound/items/screwdriver.ogg', 'sound/items/weeoo1.ogg', 'sound/items/wirecutter.ogg', 'sound/items/welder.ogg', 'sound/items/zip.ogg', 'sound/items/rped.ogg', 'sound/items/ratchet.ogg', 'sound/items/polaroid1.ogg', 'sound/items/pshoom.ogg', 'sound/items/airhorn.ogg', 'sound/items/geiger/high1.ogg', 'sound/items/geiger/high2.ogg', 'sound/voice/beepsky/creep.ogg', 'sound/voice/beepsky/iamthelaw.ogg', 'sound/voice/ed209_20sec.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss6.ogg', 'sound/voice/medbot/patchedup.ogg', 'sound/voice/medbot/feelbetter.ogg', 'sound/voice/human/manlaugh1.ogg', 'sound/voice/human/womanlaugh.ogg', 'sound/weapons/sear.ogg', 'sound/ambience/antag/clockcultalr.ogg', 'sound/ambience/antag/ling_alert.ogg', 'sound/ambience/antag/tatoralert.ogg', 'sound/ambience/antag/monkey.ogg', 'sound/mecha/nominal.ogg', 'sound/mecha/weapdestr.ogg', 'sound/mecha/critdestr.ogg', 'sound/mecha/imag_enh.ogg', 'sound/effects/adminhelp.ogg', 'sound/effects/alert.ogg', 'sound/effects/attackblob.ogg', 'sound/effects/bamf.ogg', 'sound/effects/blobattack.ogg', 'sound/effects/break_stone.ogg', 'sound/effects/bubbles.ogg', 'sound/effects/bubbles2.ogg', 'sound/effects/clang.ogg', 'sound/effects/clockcult_gateway_disrupted.ogg', 'sound/effects/clownstep2.ogg', 'sound/effects/curse1.ogg', 'sound/effects/dimensional_rend.ogg', 'sound/effects/doorcreaky.ogg', 'sound/effects/empulse.ogg', 'sound/effects/explosion_distant.ogg', 'sound/effects/explosionfar.ogg', 'sound/effects/explosion1.ogg', 'sound/effects/grillehit.ogg', 'sound/effects/genetics.ogg', 'sound/effects/heart_beat.ogg', 'sound/runtime/hyperspace/hyperspace_begin.ogg', 'sound/runtime/hyperspace/hyperspace_end.ogg', 'sound/effects/his_grace_awaken.ogg', 'sound/effects/pai_boot.ogg', 'sound/effects/phasein.ogg', 'sound/effects/picaxe1.ogg', 'sound/effects/ratvar_reveal.ogg', 'sound/effects/sparks1.ogg', 'sound/effects/smoke.ogg', 'sound/effects/splat.ogg', 'sound/effects/snap.ogg', 'sound/effects/tendril_destroyed.ogg', 'sound/effects/supermatter.ogg', 'sound/misc/desecration-01.ogg', 'sound/misc/desecration-02.ogg', 'sound/misc/desecration-03.ogg', 'sound/misc/bloblarm.ogg', 'sound/misc/airraid.ogg', 'sound/misc/bang.ogg','sound/misc/highlander.ogg', 'sound/misc/interference.ogg', 'sound/misc/notice1.ogg', 'sound/misc/notice2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/misc/slip.ogg', 'sound/misc/splort.ogg', 'sound/weapons/armbomb.ogg', 'sound/weapons/beam_sniper.ogg', 'sound/weapons/chainsawhit.ogg', 'sound/weapons/emitter.ogg', 'sound/weapons/emitter2.ogg', 'sound/weapons/blade1.ogg', 'sound/weapons/bladeslice.ogg', 'sound/weapons/blastcannon.ogg', 'sound/weapons/blaster.ogg', 'sound/weapons/bulletflyby3.ogg', 'sound/weapons/circsawhit.ogg', 'sound/weapons/cqchit2.ogg', 'sound/weapons/drill.ogg', 'sound/weapons/genhit1.ogg', 'sound/weapons/gun/pistol/shot_suppressed.ogg', 'sound/weapons/gun/pistol/shot.ogg', 'sound/weapons/handcuffs.ogg', 'sound/weapons/homerun.ogg', 'sound/weapons/kenetic_accel.ogg', 'sound/machines/clockcult/steam_whoosh.ogg', 'sound/machines/fryer/deep_fryer_emerge.ogg', 'sound/machines/airlock.ogg', 'sound/machines/airlock_alien_prying.ogg', 'sound/machines/airlockclose.ogg', 'sound/machines/airlockforced.ogg', 'sound/machines/airlockopen.ogg', 'sound/machines/alarm.ogg', 'sound/machines/blender.ogg', 'sound/machines/boltsdown.ogg', 'sound/machines/boltsup.ogg', 'sound/machines/buzz-sigh.ogg', 'sound/machines/buzz-two.ogg', 'sound/machines/chime.ogg', 'sound/machines/cryo_warning.ogg', 'sound/machines/defib_charge.ogg', 'sound/machines/defib_failed.ogg', 'sound/machines/defib_ready.ogg', 'sound/machines/defib_zap.ogg', 'sound/machines/deniedbeep.ogg', 'sound/machines/ding.ogg', 'sound/machines/disposalflush.ogg', 'sound/machines/door_close.ogg', 'sound/machines/door_open.ogg', 'sound/machines/engine_alert1.ogg', 'sound/machines/engine_alert2.ogg', 'sound/machines/hiss.ogg', 'sound/machines/honkbot_evil_laugh.ogg', 'sound/machines/juicer.ogg', 'sound/machines/ping.ogg', 'sound/ambience/signal.ogg', 'sound/machines/synth_no.ogg', 'sound/machines/synth_yes.ogg', 'sound/machines/terminal_alert.ogg', 'sound/machines/triple_beep.ogg', 'sound/machines/twobeep.ogg', 'sound/machines/ventcrawl.ogg', 'sound/machines/warning-buzzer.ogg', 'sound/ai/default/outbreak5.ogg', 'sound/ai/default/outbreak7.ogg', 'sound/ai/default/poweroff.ogg', 'sound/ai/default/radiation.ogg', 'sound/ai/default/shuttlecalled.ogg', 'sound/ai/default/shuttledock.ogg', 'sound/ai/default/shuttlerecalled.ogg', 'sound/ai/default/aimalf.ogg') //hahahaha fuck you code divers - var/datum/callback/health_changes_callback = CALLBACK(src, PROC_REF(health_check)) AddElement(/datum/element/swabable, CELL_LINE_TABLE_NETHER, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 0) - AddComponent(/datum/component/damage_buffs, health_changes_callback) + AddComponent(/datum/component/health_scaling_effects, min_health_slowdown = -1.5, additional_status_callback = CALLBACK(src, PROC_REF(update_dodge_chance))) -/mob/living/basic/migo/proc/health_check(mob/living/attacker) - if(health < maxHealth * 0.25) - health_low_behaviour() - else if (health < maxHealth * 0.5) - health_medium_behaviour() - else if (health < maxHealth * 0.75) - health_high_behaviour() - else - health_full_behaviour() - -/mob/living/basic/migo/proc/health_full_behaviour() - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = 0) - dodge_prob = 10 - -/mob/living/basic/migo/proc/health_high_behaviour() - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = -0.5) - dodge_prob = 20 - -/mob/living/basic/migo/proc/health_medium_behaviour() - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = -1) - dodge_prob = 30 - -/mob/living/basic/migo/proc/health_low_behaviour() - add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/netherworld_enrage, multiplicative_slowdown = -1.5) - dodge_prob = 50 +/// Makes the migo more likely to dodge around the more damaged it is +/mob/living/basic/migo/proc/update_dodge_chance(health_ratio) + dodge_prob = LERP(50, 10, health_ratio) /mob/living/basic/migo/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null, message_range = 7, datum/saymode/saymode = null) ..() diff --git a/code/modules/movespeed/modifiers/mobs.dm b/code/modules/movespeed/modifiers/mobs.dm index 675e01f1e53..6eb1020d13e 100644 --- a/code/modules/movespeed/modifiers/mobs.dm +++ b/code/modules/movespeed/modifiers/mobs.dm @@ -135,5 +135,5 @@ /datum/movespeed_modifier/player_spider_modifier variable = TRUE -/datum/movespeed_modifier/netherworld_enrage +/datum/movespeed_modifier/health_scaling_speed_buff variable = TRUE diff --git a/tgstation.dme b/tgstation.dme index d05e7162325..48e6fbead43 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -863,7 +863,6 @@ #include "code\datums\components\cult_ritual_item.dm" #include "code\datums\components\curse_of_hunger.dm" #include "code\datums\components\customizable_reagent_holder.dm" -#include "code\datums\components\damage_buffs.dm" #include "code\datums\components\deadchat_control.dm" #include "code\datums\components\dejavu.dm" #include "code\datums\components\deployable.dm" @@ -894,6 +893,7 @@ #include "code\datums\components\gunpoint.dm" #include "code\datums\components\hazard_area.dm" #include "code\datums\components\healing_touch.dm" +#include "code\datums\components\health_scaling_effects.dm" #include "code\datums\components\heirloom.dm" #include "code\datums\components\hide_highest_offset.dm" #include "code\datums\components\holderloving.dm"