mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-24 05:30:05 +01:00
Bane refactor (now uses damage multipliers) (#96003)
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* ## Bane component
|
||||
*
|
||||
* Applied to items and projectiles that modifies damage dealt to certain things.
|
||||
* For example a sword that deals 2x damage to specifically skeletons.
|
||||
*
|
||||
* For items, you are not limited to only dealing more damage. Supports smaller multipliers or removing flat damage.
|
||||
* For projectiles, you are limited to only dealing more damage, at least until further refactors are done.
|
||||
*/
|
||||
/datum/component/bane
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
/// Callback invoked when checking if a target is valid to be baned (arguments: target)
|
||||
/// Return a boolean, FALSE to stop the bane effect or TRUE to allow it
|
||||
VAR_FINAL/datum/callback/should_bane_callback
|
||||
/// Callback invoked before bane damage is applied, allows for modifying the damage or applying other effects. (arguments: target, attacker, damage_modifiers)
|
||||
/// Return value doesn't matter, but you can modify the damage_modifiers list to change the attack's values
|
||||
VAR_FINAL/datum/callback/pre_bane_callback
|
||||
/// Callback invoked after bane damage is applied, allows for applying other effects. (arguments: target, attacker)
|
||||
/// Return value doesn't matter
|
||||
VAR_FINAL/datum/callback/on_bane_callback
|
||||
/// A bitfield of mob biotypes that this bane component applies to.
|
||||
///If NONE, applies to all biotypes. Defaults to NONE.
|
||||
VAR_FINAL/affected_biotypes = NONE
|
||||
/// Multiplier applied to damage when the bane effect applies. Defaults to 1 (no change).
|
||||
VAR_FINAL/damage_multiplier = 1
|
||||
/// Flat damage added when the bane effect applies. Defaults to 0 (no change).
|
||||
VAR_FINAL/added_damage = 0
|
||||
/// Optional text to show in the weapon label readout, if not set it will generate a generic one based on the biotypes
|
||||
VAR_FINAL/label_text = ""
|
||||
|
||||
/datum/component/bane/Initialize(
|
||||
damage_multiplier = 1,
|
||||
added_damage = 0,
|
||||
affected_biotypes = NONE,
|
||||
datum/callback/should_bane_callback,
|
||||
datum/callback/pre_bane_callback,
|
||||
datum/callback/on_bane_callback,
|
||||
label_text = "",
|
||||
)
|
||||
if(!isitem(parent) && !isprojectile(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.damage_multiplier = damage_multiplier
|
||||
src.added_damage = added_damage
|
||||
src.affected_biotypes = affected_biotypes
|
||||
src.should_bane_callback = should_bane_callback
|
||||
src.pre_bane_callback = pre_bane_callback
|
||||
src.on_bane_callback = on_bane_callback
|
||||
src.label_text = label_text
|
||||
|
||||
/datum/component/bane/RegisterWithParent()
|
||||
if(isitem(parent))
|
||||
RegisterSignal(parent, COMSIG_ITEM_WEAPON_LABEL_READOUT, PROC_REF(label_readout))
|
||||
RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(pre_attack))
|
||||
RegisterSignal(parent, COMSIG_ITEM_AFTERATTACK, PROC_REF(after_attack))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE, PROC_REF(on_thrown_hit))
|
||||
if(isprojectile(parent))
|
||||
RegisterSignal(parent, COMSIG_PROJECTILE_SELF_ON_HIT, PROC_REF(projectile_hit))
|
||||
|
||||
/datum/component/bane/UnregisterFromParent()
|
||||
if(isitem(parent))
|
||||
UnregisterSignal(parent, COMSIG_ITEM_WEAPON_LABEL_READOUT)
|
||||
UnregisterSignal(parent, COMSIG_ITEM_PRE_ATTACK)
|
||||
UnregisterSignal(parent, COMSIG_ITEM_AFTERATTACK)
|
||||
UnregisterSignal(parent, COMSIG_MOVABLE_IMPACT_ZONE)
|
||||
if(isprojectile(parent))
|
||||
UnregisterSignal(parent, COMSIG_PROJECTILE_SELF_ON_HIT)
|
||||
|
||||
/datum/component/bane/proc/is_bane_target(atom/target)
|
||||
if(!isliving(target))
|
||||
return FALSE
|
||||
|
||||
var/mob/living/living_target = target
|
||||
if(affected_biotypes && !(living_target.mob_biotypes & affected_biotypes))
|
||||
return FALSE
|
||||
|
||||
return isnull(should_bane_callback) ? TRUE : should_bane_callback.Invoke(target)
|
||||
|
||||
/datum/component/bane/proc/label_readout(obj/item/source, list/readout)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(damage_multiplier == 1 && added_damage == 0 && !label_text)
|
||||
return
|
||||
|
||||
var/label_line = ""
|
||||
if(damage_multiplier > 1 || added_damage > 0)
|
||||
label_line += "It is especially effective against"
|
||||
else if(damage_multiplier < 1 || added_damage < 0)
|
||||
label_line += "It is less effective against"
|
||||
else
|
||||
label_line += "It interacts uniquely with" // for custom behaviors?
|
||||
|
||||
label_line += " "
|
||||
if(label_text)
|
||||
label_line += label_text
|
||||
else if(affected_biotypes)
|
||||
var/list/affected_biotypes_readable = bitfield_to_list(affected_biotypes, MOB_BIOTYPES_READABLE)
|
||||
label_line += "[english_list(affected_biotypes_readable)] enemies"
|
||||
else
|
||||
label_line += "certain enemies"
|
||||
|
||||
if(damage_multiplier > 1 || added_damage > 0)
|
||||
var/magnitude = ""
|
||||
switch(max(damage_multiplier, added_damage / 10))
|
||||
if(3 to INFINITY)
|
||||
magnitude = "massively increased"
|
||||
if(2 to 3)
|
||||
magnitude = "greatly increased"
|
||||
if(1.5 to 2)
|
||||
magnitude = "significantly increased"
|
||||
if(1 to 1.5)
|
||||
magnitude = "slightly increased"
|
||||
|
||||
label_line += ", dealing [span_warning(magnitude)] damage per hit"
|
||||
|
||||
else if(damage_multiplier < 1 || added_damage < 0)
|
||||
var/magnitude = ""
|
||||
switch(min(damage_multiplier, 10 / abs(added_damage || 1)))
|
||||
if(-INFINITY to 0)
|
||||
magnitude = "no"
|
||||
if(0 to 0.3)
|
||||
magnitude = "massively reduced"
|
||||
if(0.3 to 0.6)
|
||||
magnitude = "greatly reduced"
|
||||
if(0.6 to 0.9)
|
||||
magnitude = "significantly reduced"
|
||||
if(0.9 to 1)
|
||||
magnitude = "slightly reduced"
|
||||
|
||||
label_line += ", dealing [span_warning(magnitude)] damage per hit"
|
||||
|
||||
label_line += "."
|
||||
readout += label_line
|
||||
|
||||
// Item attack handling
|
||||
/datum/component/bane/proc/pre_attack(datum/source, atom/target, mob/living/attacker, list/modifiers, list/attack_modifiers)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!is_bane_target(target))
|
||||
return
|
||||
|
||||
if(added_damage != 0)
|
||||
MODIFY_ATTACK_FORCE(attack_modifiers, added_damage)
|
||||
if(damage_multiplier != 1)
|
||||
MODIFY_ATTACK_FORCE_MULTIPLIER(attack_modifiers, damage_multiplier)
|
||||
|
||||
pre_bane_callback?.Invoke(target, attacker, attack_modifiers)
|
||||
|
||||
/datum/component/bane/proc/after_attack(datum/source, atom/target, mob/living/attacker, ...)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!is_bane_target(target))
|
||||
return
|
||||
|
||||
SEND_SIGNAL(target, COMSIG_LIVING_BANED, source, attacker)
|
||||
on_bane_callback?.Invoke(target, attacker)
|
||||
|
||||
// Throw impact handling
|
||||
/datum/component/bane/proc/on_thrown_hit(datum/source, atom/hit_atom, hit_zone, blocked, datum/thrownthing/throwingdatum)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!is_bane_target(hit_atom))
|
||||
return
|
||||
|
||||
var/mob/thrower = throwingdatum?.thrower?.resolve()
|
||||
var/list/damage_modifiers = list("[FORCE_MULTIPLIER]" = damage_multiplier, "[FORCE_MODIFIER]" = added_damage)
|
||||
pre_bane_callback?.Invoke(hit_atom, thrower, damage_modifiers)
|
||||
|
||||
// We're not modifying the throwforce of the item we're just applying more damage as a separate damage event
|
||||
// That's why we do damage_multiplier - 1 (so that a 1.5x multiplier would apply 0.5x damage here for a total of 1.5x)
|
||||
var/obj/item/throwing_item = parent
|
||||
var/extra_damage = (throwing_item.throwforce * max(0, damage_modifiers[FORCE_MULTIPLIER] - 1)) + damage_modifiers[FORCE_MODIFIER]
|
||||
if(extra_damage > 0)
|
||||
var/mob/living/living_target = hit_atom // safe assertion from is_bane_target
|
||||
living_target.apply_damage(extra_damage, throwing_item.damtype, hit_zone, blocked)
|
||||
|
||||
SEND_SIGNAL(hit_atom, COMSIG_LIVING_BANED, thrower, hit_atom)
|
||||
on_bane_callback?.Invoke(hit_atom, thrower)
|
||||
|
||||
// Projectile hit handling
|
||||
/datum/component/bane/proc/projectile_hit(datum/source, atom/firer, atom/target, angle, hit_zone, blocked, ...)
|
||||
SIGNAL_HANDLER
|
||||
|
||||
if(!is_bane_target(target))
|
||||
return
|
||||
|
||||
var/list/damage_modifiers = list("[FORCE_MULTIPLIER]" = damage_multiplier, "[FORCE_MODIFIER]" = added_damage)
|
||||
pre_bane_callback?.Invoke(target, firer, damage_modifiers)
|
||||
|
||||
// We're not modifying the projectile damage we're just applying more damage as a separate damage event
|
||||
// That's why we do damage_multiplier - 1 (so that a 1.5x multiplier would apply 0.5x damage here for a total of 1.5x)
|
||||
var/obj/projectile/projectile_owner = parent
|
||||
var/extra_damage = (projectile_owner.damage * max(0, damage_modifiers[FORCE_MULTIPLIER] - 1)) + damage_modifiers[FORCE_MODIFIER]
|
||||
if(extra_damage > 0)
|
||||
var/mob/living/living_target = target // safe assertion from is_bane_target
|
||||
living_target.apply_damage(extra_damage, projectile_owner.damage_type, hit_zone, blocked)
|
||||
|
||||
SEND_SIGNAL(target, COMSIG_LIVING_BANED, firer, target)
|
||||
on_bane_callback?.Invoke(target, firer)
|
||||
@@ -58,7 +58,6 @@
|
||||
name = "of <mobtype> slaying (random species, carbon or simple animal)"
|
||||
placement = AFFIX_SUFFIX
|
||||
alignment = AFFIX_GOOD
|
||||
var/list/target_types_by_comp = list()
|
||||
|
||||
/datum/fantasy_affix/bane/apply(datum/component/fantasy/comp, newName)
|
||||
. = ..()
|
||||
@@ -83,16 +82,9 @@
|
||||
// This works even with the species picks since we're only accessing the name
|
||||
|
||||
var/obj/item/master = comp.parent
|
||||
master.AddElement(/datum/element/bane, target_type = picked_mobtype)
|
||||
target_types_by_comp[comp] = picked_mobtype
|
||||
comp.appliedComponents += master.AddComponent(/datum/component/bane, affected_biotypes = picked_mobtype, damage_multiplier = 2)
|
||||
return "[newName] of [initial(picked_mobtype.name)] slaying"
|
||||
|
||||
/datum/fantasy_affix/bane/remove(datum/component/fantasy/comp)
|
||||
var/picked_mobtype = target_types_by_comp[comp]
|
||||
var/obj/item/master = comp.parent
|
||||
master.RemoveElement(/datum/element/bane, picked_mobtype)
|
||||
target_types_by_comp -= comp
|
||||
|
||||
/datum/fantasy_affix/summoning
|
||||
name = "of <mobtype> summoning (dangerous, can pick all but megafauna tier stuff)"
|
||||
placement = AFFIX_SUFFIX
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
/// Deals extra damage to mobs of a certain type, species, or biotype.
|
||||
/// This doesn't directly modify the normal damage of the weapon, instead it applies its own damage separately ON TOP of normal damage
|
||||
/// ie. a sword that does 10 damage with a bane element attached that has a 0.5 damage_multiplier will do:
|
||||
/// 10 damage from the swords normal attack + 5 damage (50%) from the bane element
|
||||
/datum/element/bane
|
||||
element_flags = ELEMENT_BESPOKE
|
||||
argument_hash_start_idx = 2
|
||||
/// can be a mob or a species.
|
||||
var/target_type
|
||||
/// multiplier of the extra damage based on the force of the item.
|
||||
var/damage_multiplier
|
||||
/// Added after the above.
|
||||
var/added_damage
|
||||
/// If it requires combat mode on to deal the extra damage or not.
|
||||
var/requires_combat_mode
|
||||
/// if we want it to only affect a certain mob biotype
|
||||
var/mob_biotypes
|
||||
|
||||
/datum/element/bane/Attach(datum/target, target_type = /mob/living, mob_biotypes = NONE, damage_multiplier=1, added_damage = 0, requires_combat_mode = TRUE)
|
||||
. = ..()
|
||||
|
||||
if(!ispath(target_type, /mob/living) && !ispath(target_type, /datum/species))
|
||||
return ELEMENT_INCOMPATIBLE
|
||||
|
||||
src.target_type = target_type
|
||||
src.damage_multiplier = damage_multiplier
|
||||
src.added_damage = added_damage
|
||||
src.requires_combat_mode = requires_combat_mode
|
||||
src.mob_biotypes = mob_biotypes
|
||||
target.AddElementTrait(TRAIT_ON_HIT_EFFECT, REF(src), /datum/element/on_hit_effect)
|
||||
RegisterSignal(target, COMSIG_ON_HIT_EFFECT, PROC_REF(do_bane))
|
||||
|
||||
/datum/element/bane/Detach(datum/source)
|
||||
UnregisterSignal(source, COMSIG_ON_HIT_EFFECT)
|
||||
REMOVE_TRAIT(source, TRAIT_ON_HIT_EFFECT, REF(src))
|
||||
return ..()
|
||||
|
||||
/datum/element/bane/proc/do_bane(datum/element_owner, mob/living/bane_applier, mob/living/baned_target, hit_zone, throw_hit)
|
||||
if(!check_biotype_path(bane_applier, baned_target))
|
||||
return
|
||||
if(SEND_SIGNAL(element_owner, COMSIG_OBJECT_PRE_BANING, baned_target) & COMPONENT_CANCEL_BANING)
|
||||
return
|
||||
|
||||
var/force_boosted
|
||||
var/applied_dam_type
|
||||
|
||||
if(isitem(element_owner))
|
||||
var/obj/item/item_owner = element_owner
|
||||
force_boosted = item_owner.force
|
||||
applied_dam_type = item_owner.damtype
|
||||
else if(isprojectile(element_owner))
|
||||
var/obj/projectile/projectile_owner = element_owner
|
||||
force_boosted = projectile_owner.damage
|
||||
applied_dam_type = projectile_owner.damage_type
|
||||
else if (isliving(element_owner))
|
||||
var/mob/living/living_owner = element_owner
|
||||
force_boosted = (living_owner.melee_damage_lower + living_owner.melee_damage_upper) / 2
|
||||
//commence crying. yes, these really are the same check. FUCK.
|
||||
if(isbasicmob(living_owner))
|
||||
var/mob/living/basic/basic_owner = living_owner
|
||||
applied_dam_type = basic_owner.melee_damage_type
|
||||
else if(isanimal(living_owner))
|
||||
var/mob/living/simple_animal/simple_owner = living_owner
|
||||
applied_dam_type = simple_owner.melee_damage_type
|
||||
else
|
||||
return
|
||||
else
|
||||
return
|
||||
|
||||
var/extra_damage = max(0, (force_boosted * damage_multiplier) + added_damage)
|
||||
baned_target.apply_damage(extra_damage, applied_dam_type, hit_zone)
|
||||
SEND_SIGNAL(baned_target, COMSIG_LIVING_BANED, bane_applier, baned_target) // for extra effects when baned.
|
||||
SEND_SIGNAL(element_owner, COMSIG_OBJECT_ON_BANING, baned_target)
|
||||
|
||||
/**
|
||||
* Checks typepaths and the mob's biotype, returning TRUE if correct and FALSE if wrong.
|
||||
* Additionally checks if combat mode is required, and if so whether it's enabled or not.
|
||||
*/
|
||||
/datum/element/bane/proc/check_biotype_path(atom/bane_applier, atom/target)
|
||||
if(!isliving(target))
|
||||
return FALSE
|
||||
var/mob/living/living_target = target
|
||||
if(isliving(bane_applier) && bane_applier)
|
||||
var/mob/living/living_bane_applier = bane_applier
|
||||
if(requires_combat_mode && !living_bane_applier.combat_mode)
|
||||
return FALSE
|
||||
var/is_correct_biotype = living_target.mob_biotypes & mob_biotypes
|
||||
if(mob_biotypes && !(is_correct_biotype))
|
||||
return FALSE
|
||||
if(ispath(target_type, /mob/living))
|
||||
return istype(living_target, target_type)
|
||||
else //species type
|
||||
return is_species(living_target, target_type)
|
||||
@@ -19,7 +19,7 @@
|
||||
effects_we_clear = list(/obj/effect/rune, /obj/effect/heretic_rune, /obj/effect/cosmic_rune), \
|
||||
)
|
||||
target.AddComponent(/datum/component/cult_kill_tracker)
|
||||
target.AddElement(/datum/element/bane, mob_biotypes = MOB_SPIRIT, damage_multiplier = 0, added_damage = 25, requires_combat_mode = FALSE)
|
||||
target.AddComponent(/datum/component/bane, affected_biotypes = MOB_SPIRIT, added_damage = 25)
|
||||
ADD_TRAIT(target, TRAIT_NULLROD_ITEM, ELEMENT_TRAIT(type))
|
||||
|
||||
if(!PERFORM_ALL_TESTS(focus_only/nullrod_variants) || !chaplain_spawnable)
|
||||
|
||||
@@ -97,6 +97,8 @@
|
||||
if(attached_proc)
|
||||
readout += call(source, attached_proc)()
|
||||
|
||||
SEND_SIGNAL(source, COMSIG_ITEM_WEAPON_LABEL_READOUT, readout)
|
||||
|
||||
// Finally bringing the fields together
|
||||
return readout.Join("\n")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user