mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-01-02 21:11:57 +00:00
* Basic Guardians/Holoparasites (#79473) ## About The Pull Request Fixes #79485 Fixes #77552 Converts Guardians (aka Holoparasites) into Basic Mobs. Changes a bunch of their behaviours into actions or components which we can reuse. Replaces some verbs it would give to you and hide in the status panel with action buttons that you may be able to find more quickly. They _**should**_ work basically like they did before but a bit smoother. It is not unlikely that I made some changes by accident or just by changing framework though. My one creative touch was adding random name suggestions. The Wizard federation have a convention of naming their arcane spirit guardians by combining a colour and a major arcana of the tarot. The Syndicate of course won't truck with any of that mystical claptrap and for their codenames use the much more sensible construction of a colour and a gamepiece. This lets you be randomly assigned such creative names as "Sparkling Hermit", "Bloody Queen", "Blue World", or "Purple Diamond". You can of course still ignore this entirely and type "The Brapmaster" into the box if so desired. I made _one_ other intentional change, which is to swap to Mothblocks' nice leash component instead of instantly teleporting guardians back to you when they are pulled out of the edge of their range. They should now be "dragged" along behind you until they can't path, at which point they will teleport. This should make the experience a bit less disorienting, you have the recall button if you _want_ to instantly catch up. This is unfortunately a bumper-sized PR because it did not seem plausible to not do all of it at once, but I can make a project branch for atomisation if people think this is too much of a pain in the ass to review. Other changes: - Some refactoring to how the charge action works so I could individually override "what you can hit" and "what happens when you hit" instead of those being the same proc - Lightning Guardian damage chain is now a component - Explosive Guardian explosive trap is now a component - Added even more arguments to the Healing Touch component to allow it to heal tox/oxy damage and require a specific click modifier - Life Link component which implements the Guardian behaviour of using another mob as your health bar - Moved some stuff about deciding what guardians look and are described like into a theming datum - Added a generic proc which can return whether your mob is meant to apply some kind of damage multiplier to a certain damage type. It's not perfect because I couldn't figure out how ot cram limb modifiers in there, which is where most of it is on carbons. Oh well. - Riders of vehicles now inherit all movement traits of those vehicles, so riding a charging holoparasite will let you cross chasms. Also works if you piggyback someone with wings, probably. ## Changelog 🆑 refactor: Guardians/Powerminers/Holoparasites now use the basic mob framework. Please report any unexpected changes or behaviour. qol: The verbs used to communicate with, recall, or banish your Guardian are now action buttons. balance: If (as a Guardian) your host moves slightly out of range you will now be dragged back into range if possible, rather than being instantly teleported to them. balance: Protectors now have a shorter leash range rather than a longer one, in order to more easily take advantage of their ability to drag their charge out of danger. balance: Ranged Guardians can now hold down the mouse button to fire automatically. balance: People riding vehicles or other mobs now inherit all of their movement traits, so riding a flying mob (or vehicle, if we have any of those) will allow you to cross chasms and lava safely. /🆑 --------- Co-authored-by: san7890 <the@ san7890.com> * Basic Guardians/Holoparasites * Modular --------- Co-authored-by: Jacquerel <hnevard@gmail.com> Co-authored-by: san7890 <the@ san7890.com> Co-authored-by: Giz <13398309+vinylspiders@users.noreply.github.com>
218 lines
7.7 KiB
Plaintext
218 lines
7.7 KiB
Plaintext
#define MEND_REPLACE_KEY_SOURCE "%SOURCE%"
|
|
#define MEND_REPLACE_KEY_TARGET "%TARGET%"
|
|
|
|
/**
|
|
* # Healing Touch component
|
|
*
|
|
* A mob with this component will be able to heal certain targets by attacking them.
|
|
* This intercepts the attack and starts a do_after if the target is in its allowed type list.
|
|
*/
|
|
/datum/component/healing_touch
|
|
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
|
|
/// How much brute damage to heal
|
|
var/heal_brute
|
|
/// How much burn damage to heal
|
|
var/heal_burn
|
|
/// How much toxin damage to heal
|
|
var/heal_tox
|
|
/// How much oxygen damage to heal
|
|
var/heal_oxy
|
|
/// How much stamina damage to heal
|
|
var/heal_stamina
|
|
/// Interaction will use this key, and be blocked while this key is in use
|
|
var/interaction_key
|
|
/// Any extra conditions which need to be true to permit healing. Returning TRUE permits the healing, FALSE or null cancels it.
|
|
var/datum/callback/extra_checks
|
|
/// Time it takes to perform the healing action
|
|
var/heal_time
|
|
/// Typecache of mobs we can heal
|
|
var/list/valid_targets_typecache
|
|
/// Bitfield for biotypes of mobs we can heal
|
|
var/valid_biotypes
|
|
/// Which kinds of carbon limbs can we heal, has no effect on non-carbon mobs. Set to null if you don't care about excluding prosthetics.
|
|
var/required_bodytype
|
|
/// How targeting yourself works, expects one of HEALING_TOUCH_ANYONE, HEALING_TOUCH_NOT_SELF, or HEALING_TOUCH_SELF_ONLY
|
|
var/self_targeting
|
|
/// Text to print when action starts, replaces %SOURCE% with healer and %TARGET% with healed mob
|
|
var/action_text
|
|
/// Text to print when action completes, replaces %SOURCE% with healer and %TARGET% with healed mob
|
|
var/complete_text
|
|
/// Whether to print the target's remaining health after healing (for non-carbon targets only)
|
|
var/show_health
|
|
/// Color for the healing effect
|
|
var/heal_color
|
|
/// Optional click modifier required
|
|
var/required_modifier
|
|
/// Callback to run after healing a mob
|
|
var/datum/callback/after_healed
|
|
|
|
/datum/component/healing_touch/Initialize(
|
|
heal_brute = 20,
|
|
heal_burn = 20,
|
|
heal_tox = 0,
|
|
heal_oxy = 0,
|
|
heal_stamina = 0,
|
|
heal_time = 2 SECONDS,
|
|
interaction_key = DOAFTER_SOURCE_HEAL_TOUCH,
|
|
datum/callback/extra_checks = null,
|
|
list/valid_targets_typecache = list(),
|
|
valid_biotypes = MOB_ORGANIC | MOB_MINERAL,
|
|
required_bodytype = BODYTYPE_ORGANIC,
|
|
self_targeting = HEALING_TOUCH_NOT_SELF,
|
|
action_text = "%SOURCE% begins healing %TARGET%",
|
|
complete_text = "%SOURCE% finishes healing %TARGET%",
|
|
show_health = FALSE,
|
|
heal_color = COLOR_HEALING_CYAN,
|
|
required_modifier = null,
|
|
datum/callback/after_healed = null,
|
|
)
|
|
if (!isliving(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
src.heal_brute = heal_brute
|
|
src.heal_burn = heal_burn
|
|
src.heal_tox = heal_tox
|
|
src.heal_oxy = heal_oxy
|
|
src.heal_stamina = heal_stamina
|
|
src.heal_time = heal_time
|
|
src.interaction_key = interaction_key
|
|
src.extra_checks = extra_checks
|
|
src.valid_targets_typecache = valid_targets_typecache.Copy()
|
|
src.valid_biotypes = valid_biotypes
|
|
src.required_bodytype = required_bodytype
|
|
src.self_targeting = self_targeting
|
|
src.action_text = action_text
|
|
src.complete_text = complete_text
|
|
src.show_health = show_health
|
|
src.heal_color = heal_color
|
|
src.required_modifier = required_modifier
|
|
src.after_healed = after_healed
|
|
|
|
RegisterSignal(parent, COMSIG_LIVING_UNARMED_ATTACK, PROC_REF(try_healing)) // Players
|
|
RegisterSignal(parent, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(try_healing)) // NPCs
|
|
|
|
// Let's populate this list as we actually use it, this thing has too many args
|
|
/datum/component/healing_touch/InheritComponent(
|
|
datum/component/new_component,
|
|
i_am_original,
|
|
heal_color,
|
|
)
|
|
src.heal_color = heal_color
|
|
|
|
/datum/component/healing_touch/UnregisterFromParent()
|
|
UnregisterSignal(parent, list(COMSIG_LIVING_UNARMED_ATTACK, COMSIG_HOSTILE_PRE_ATTACKINGTARGET))
|
|
return ..()
|
|
|
|
/datum/component/healing_touch/Destroy(force, silent)
|
|
extra_checks = null
|
|
return ..()
|
|
|
|
/// Validate our target, and interrupt the attack chain to start healing it if it is allowed
|
|
/datum/component/healing_touch/proc/try_healing(mob/living/healer, atom/target, proximity, modifiers)
|
|
SIGNAL_HANDLER
|
|
if (!isliving(target))
|
|
return
|
|
|
|
if (!isnull(required_modifier) && !LAZYACCESS(modifiers, required_modifier))
|
|
return
|
|
|
|
if (length(valid_targets_typecache) && !is_type_in_typecache(target, valid_targets_typecache))
|
|
return // Fall back to attacking it
|
|
|
|
if (extra_checks && !extra_checks.Invoke(healer, target))
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
if (DOING_INTERACTION(healer, interaction_key))
|
|
healer.balloon_alert(healer, "busy!")
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
switch (self_targeting)
|
|
if (HEALING_TOUCH_NOT_SELF)
|
|
if (target == healer)
|
|
healer.balloon_alert(healer, "can't heal yourself!")
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
if (HEALING_TOUCH_SELF_ONLY)
|
|
if (target != healer)
|
|
healer.balloon_alert(healer, "can only heal yourself!")
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
var/mob/living/living_target = target
|
|
if (living_target.health >= living_target.maxHealth)
|
|
target.balloon_alert(healer, "not hurt!")
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
if (!has_healable_damage(living_target))
|
|
target.balloon_alert(healer, "can't heal that!")
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
if (living_target.stat == DEAD)
|
|
target.balloon_alert(healer, "they're dead!")
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
INVOKE_ASYNC(src, PROC_REF(heal_target), healer, target)
|
|
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
/// Returns true if the target has a kind of damage which we can heal
|
|
/datum/component/healing_touch/proc/has_healable_damage(mob/living/target)
|
|
if (!isnull(valid_biotypes) && !(valid_biotypes & target.mob_biotypes))
|
|
return FALSE
|
|
if (target.getStaminaLoss() > 0 && heal_stamina)
|
|
return TRUE
|
|
if (target.getOxyLoss() > 0 && heal_oxy)
|
|
return TRUE
|
|
if (target.getToxLoss() > 0 && heal_tox)
|
|
return TRUE
|
|
if (!iscarbon(target))
|
|
return (target.getBruteLoss() > 0 && heal_brute) || (target.getFireLoss() > 0 && heal_burn)
|
|
var/mob/living/carbon/carbon_target = target
|
|
for (var/obj/item/bodypart/part in carbon_target.bodyparts)
|
|
if (!(part.brute_dam && heal_brute) && !(part.burn_dam && heal_burn))
|
|
continue
|
|
if (!isnull(required_bodytype) && !(part.bodytype & required_bodytype))
|
|
continue
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/// Perform a do_after and then heal our target
|
|
/datum/component/healing_touch/proc/heal_target(mob/living/healer, mob/living/target)
|
|
if (action_text)
|
|
healer.visible_message(span_notice("[format_string(action_text, healer, target)]"))
|
|
|
|
if (heal_time && !do_after(healer, heal_time, target = target, interaction_key = interaction_key))
|
|
healer.balloon_alert(healer, "interrupted!")
|
|
return
|
|
|
|
if (complete_text)
|
|
healer.visible_message(span_notice("[format_string(complete_text, healer, target)]"))
|
|
|
|
var/healed = target.heal_overall_damage(
|
|
brute = heal_brute,
|
|
burn = heal_burn,
|
|
stamina = heal_stamina,
|
|
required_bodytype = required_bodytype,
|
|
updating_health = FALSE,
|
|
)
|
|
healed += target.adjustOxyLoss(-heal_oxy, updating_health = FALSE, required_biotype = valid_biotypes)
|
|
healed += target.adjustToxLoss(-heal_tox, updating_health = FALSE, required_biotype = valid_biotypes)
|
|
if (healed <= 0)
|
|
return
|
|
|
|
target.updatehealth()
|
|
new /obj/effect/temp_visual/heal(get_turf(target), heal_color)
|
|
after_healed?.Invoke(target)
|
|
|
|
if(!show_health)
|
|
return
|
|
var/formatted_string = format_string("%TARGET% now has <b>[health_percentage(target)] health.</b>", healer, target)
|
|
to_chat(healer, span_danger(formatted_string))
|
|
|
|
/// Reformats the passed string with the replacetext keys
|
|
/datum/component/healing_touch/proc/format_string(string, atom/source, atom/target)
|
|
var/final_message = replacetext(string, MEND_REPLACE_KEY_SOURCE, "[source]")
|
|
final_message = replacetext(final_message, MEND_REPLACE_KEY_TARGET, "[target]")
|
|
return final_message
|
|
|
|
#undef MEND_REPLACE_KEY_SOURCE
|
|
#undef MEND_REPLACE_KEY_TARGET
|