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"