mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 12:41:09 +01:00
+37






![tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>](/assets/img/avatar_default.png)


Alexis
GitHub
tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
ArcaneMusic
MrMelbert
Rhials
rageguy505
LemonInTheDark
John Willard
Aliceee2ch
Time-Green
Tsar-Salat
SmArtKar
Maxipat
SyncIt21
deltanedas
SimplyLogan
loganuk
Leland Kemble
FalloutFalcon
Roxy
Lucy
siliconOpossum
Isratosh
TheRyeGuyWhoWillNowDie
Neocloudy
Alexander V.
ElGitificador
Twaticus
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bloop
Cameron Lennox
Tim
Iamgoofball
Layzu666
Arturlang
_0Steven
mrmanlikesbt
Ben10Omintrix
John F. Kennedy
Cursor
Josh
Josh Powell
Yobrocharlie
Hardly3D
shayoki
LT3
21b4095dfd
Upstream 04/17/2026 fixes https://github.com/Bubberstation/Bubberstation/issues/5549 --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com> Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com> Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com> Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com> Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com> Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com> Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com> Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com> Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com> Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com> Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com> Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com> Co-authored-by: loganuk <fakeemail123@aol.com> Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com> Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com> Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com> Co-authored-by: Lucy <lucy@absolucy.moe> Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com> Co-authored-by: Isratosh <Isratosh@hotmail.com> Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com> Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com> Co-authored-by: Alexander V. <volas@ya.ru> Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com> Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com> Co-authored-by: Cameron Lennox <killer65311@gmail.com> Co-authored-by: Tim <timothymtorres@gmail.com> Co-authored-by: Iamgoofball <iamgoofball@gmail.com> Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com> Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com> Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com> Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com> Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com> Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com> Co-authored-by: Josh <josh.adam.powell@gmail.com> Co-authored-by: Josh Powell <josh.powell@softwire.com> Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com> Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com> Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com> Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
353 lines
14 KiB
Plaintext
353 lines
14 KiB
Plaintext
/**
|
|
* A mob which acts as a guardian angel to another mob, sharing health but protecting them using special powers.
|
|
* Usually either obtained in magical form by a wizard, or technological form by a traitor. Sometimes found by miners.
|
|
*/
|
|
/mob/living/basic/guardian
|
|
name = "Guardian Spirit"
|
|
real_name = "Guardian Spirit"
|
|
desc = "A mysterious being that stands by its charge, ever vigilant."
|
|
icon = 'icons/mob/nonhuman-player/guardian.dmi'
|
|
icon_state = "magicbase"
|
|
icon_living = "magicbase"
|
|
icon_dead = "magicbase"
|
|
gender = NEUTER
|
|
basic_mob_flags = DEL_ON_DEATH
|
|
mob_biotypes = MOB_SPECIAL
|
|
sentience_type = SENTIENCE_HUMANOID
|
|
hud_type = /datum/hud/guardian
|
|
speed = 0
|
|
status_flags = CANPUSH
|
|
maxHealth = INFINITY // The spirit itself is invincible and passes damage to its host
|
|
health = INFINITY
|
|
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, STAMINA = 0, OXY = 1)
|
|
unsuitable_atmos_damage = 0
|
|
unsuitable_cold_damage = 0
|
|
unsuitable_heat_damage = 0
|
|
speak_emote = list("hisses")
|
|
bubble_icon = "guardian"
|
|
response_help_continuous = "passes through"
|
|
response_help_simple = "pass through"
|
|
response_disarm_continuous = "flails at"
|
|
response_disarm_simple = "flail at"
|
|
response_harm_continuous = "punches"
|
|
response_harm_simple = "punch"
|
|
attack_sound = 'sound/items/weapons/punch1.ogg'
|
|
attack_verb_continuous = "punches"
|
|
attack_verb_simple = "punch"
|
|
obj_damage = 40
|
|
melee_damage_lower = 15
|
|
melee_damage_upper = 15
|
|
melee_attack_cooldown = CLICK_CD_MELEE
|
|
light_system = OVERLAY_LIGHT
|
|
light_range = 3
|
|
light_on = FALSE
|
|
|
|
/// The summoner of the guardian, we share health with them and can't move too far away (usually)
|
|
var/mob/living/summoner
|
|
/// How far from the summoner the guardian can be.
|
|
var/range = 10
|
|
|
|
/// The guardian's colour, used for their sprite, chat, and some effects.
|
|
var/guardian_colour
|
|
/// Coloured overlay we apply
|
|
var/mutable_appearance/overlay
|
|
|
|
/// Which toggle button the guardian has. Won't get one if it's null.
|
|
var/toggle_button_type = null
|
|
/// Name used by the guardian creator.
|
|
var/creator_name = "Error"
|
|
/// Description used by the guardian creator.
|
|
var/creator_desc = "This shouldn't be here! Report it on GitHub!"
|
|
/// Icon used by the guardian creator.
|
|
var/creator_icon = "fuck"
|
|
|
|
/// What type of guardian are we?
|
|
var/guardian_type = null
|
|
/// How are we themed?
|
|
var/datum/guardian_fluff/theme
|
|
/// A string explaining to the guardian what they can do.
|
|
var/playstyle_string = span_boldholoparasite("You are a Guardian without any type. You shouldn't exist and are an affront to god!")
|
|
|
|
/// Are we forced to not be able to manifest/recall?
|
|
var/locked = FALSE
|
|
/// Cooldown between manifests/recalls.
|
|
COOLDOWN_DECLARE(manifest_cooldown)
|
|
/// Cooldown between the summoner resetting the guardian's client.
|
|
COOLDOWN_DECLARE(resetting_cooldown)
|
|
|
|
/// List of actions we give to our summoner.
|
|
var/static/list/control_actions = list(
|
|
/datum/action/cooldown/mob_cooldown/guardian_comms,
|
|
/datum/action/cooldown/mob_cooldown/recall_guardian,
|
|
/datum/action/cooldown/mob_cooldown/replace_guardian,
|
|
)
|
|
/// List of actions we give to ourselves.
|
|
var/static/list/self_actions = list(
|
|
/datum/action/cooldown/guardian/check_type,
|
|
/datum/action/cooldown/guardian/toggle_light,
|
|
/datum/action/cooldown/guardian/communicate,
|
|
/datum/action/cooldown/guardian/manifest,
|
|
/datum/action/cooldown/guardian/recall,
|
|
)
|
|
|
|
/mob/living/basic/guardian/Initialize(mapload, datum/guardian_fluff/theme)
|
|
. = ..()
|
|
GLOB.parasites += src
|
|
src.theme = theme
|
|
theme?.apply(src)
|
|
AddElement(/datum/element/death_drops, /obj/effect/temp_visual/guardian/phase/out)
|
|
AddElement(/datum/element/simple_flying)
|
|
// life link
|
|
update_appearance(UPDATE_ICON)
|
|
manifest_effects()
|
|
create_actions()
|
|
|
|
/mob/living/basic/guardian/Destroy()
|
|
GLOB.parasites -= src
|
|
if (is_deployed())
|
|
recall_effects()
|
|
cut_summoner(different_person = TRUE)
|
|
return ..()
|
|
|
|
///Creates the guardian's default action buttons and sets them to go in their proper location.
|
|
///Subtypes overwrite this for special ability types and whatnot.
|
|
/mob/living/basic/guardian/proc/create_actions()
|
|
for (var/action_type in self_actions + toggle_button_type)
|
|
if(isnull(action_type)) //no toggle button type
|
|
continue
|
|
if (locate(action_type) in actions)
|
|
continue
|
|
var/datum/action/new_action = new action_type(src)
|
|
new_action.Grant(src)
|
|
update_action_buttons()
|
|
|
|
/mob/living/basic/guardian/update_overlays()
|
|
. = ..()
|
|
. += overlay
|
|
|
|
/mob/living/basic/guardian/Login() //if we have a mind, set its name to ours when it logs in
|
|
. = ..()
|
|
if (!. || isnull(client))
|
|
return FALSE
|
|
if (isnull(summoner))
|
|
to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately."))
|
|
stack_trace("Guardian created with client but no summoner.")
|
|
else
|
|
to_chat(src, span_holoparasite("You are a <b>[theme.name]</b>, bound to serve [summoner.real_name]."))
|
|
to_chat(src, span_holoparasite("You are capable of manifesting or recalling to your master with the buttons on your HUD. You will also find a button to communicate with [summoner.p_them()] privately there."))
|
|
to_chat(src, span_holoparasite("While personally invincible, you will die if [summoner.real_name] does, and any damage dealt to you will have a portion passed on to [summoner.p_them()] as you feed upon [summoner.p_them()] to sustain yourself."))
|
|
to_chat(src, playstyle_string)
|
|
if (!isnull(guardian_colour))
|
|
return // Already set up so we don't need to do it again
|
|
locked = TRUE
|
|
guardian_rename()
|
|
guardian_recolour()
|
|
locked = FALSE
|
|
|
|
/mob/living/basic/guardian/mind_initialize()
|
|
. = ..()
|
|
if (isnull(summoner))
|
|
to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately."))
|
|
return
|
|
mind.enslave_mind_to_creator(summoner) // Once our mind is created, we become enslaved to our summoner. cant be done in the first run of set_summoner, because by then we dont have a mind yet.
|
|
|
|
/// Pick a new colour for our guardian
|
|
/mob/living/basic/guardian/proc/guardian_recolour()
|
|
if (isnull(client))
|
|
return
|
|
var/chosen_guardian_colour = tgui_color_picker(src, "What would you like your colour to be?", "Choose Your Colour", COLOR_WHITE)
|
|
if (isnull(chosen_guardian_colour)) //redo proc until we get a color
|
|
to_chat(src, span_warning("Invalid colour, please try again."))
|
|
return guardian_recolour()
|
|
set_guardian_colour(chosen_guardian_colour)
|
|
|
|
/// Apply a new colour to our guardian
|
|
/mob/living/basic/guardian/proc/set_guardian_colour(colour)
|
|
guardian_colour = colour
|
|
set_light_color(guardian_colour)
|
|
overlay?.color = guardian_colour
|
|
update_appearance(UPDATE_ICON)
|
|
|
|
/mob/living/basic/guardian/proc/guardian_rename()
|
|
if (isnull(client))
|
|
return
|
|
|
|
var/new_name = sanitize_name(reject_bad_text(tgui_input_text(src, "What would you like your name to be?", "Choose Your Name", generate_random_name(), MAX_NAME_LEN)))
|
|
if (!new_name) //redo proc until we get a good name
|
|
to_chat(src, span_warning("Invalid name, please try again."))
|
|
return guardian_rename()
|
|
to_chat(src, span_notice("Your new name [span_name(new_name)] anchors itself in your mind."))
|
|
fully_replace_character_name(null, new_name)
|
|
|
|
/// Picks a random name as a suggestion
|
|
/mob/living/basic/guardian/proc/generate_random_name()
|
|
var/list/surname_options = list("Guardian") // Fallback in case you define a guardian with no theme
|
|
switch(theme?.fluff_type)
|
|
if (GUARDIAN_MAGIC)
|
|
surname_options = GLOB.guardian_fantasy_surnames
|
|
if (GUARDIAN_TECH)
|
|
surname_options = GLOB.guardian_tech_surnames
|
|
|
|
return "[pick(GLOB.guardian_first_names)] [pick(surname_options)]"
|
|
|
|
/mob/living/basic/guardian/melee_attack(atom/target, list/modifiers, ignore_cooldown)
|
|
if (!is_deployed())
|
|
balloon_alert(src, "not tangible!")
|
|
return FALSE
|
|
return ..()
|
|
|
|
/mob/living/basic/guardian/death(gibbed)
|
|
if (!QDELETED(summoner))
|
|
to_chat(summoner, span_bolddanger("Your [name] died somehow!"))
|
|
summoner.dust()
|
|
return ..()
|
|
|
|
/mob/living/basic/guardian/ex_act(severity, target)
|
|
switch(severity)
|
|
if (EXPLODE_DEVASTATE)
|
|
investigate_log("has been gibbed by an explosion.", INVESTIGATE_DEATHS)
|
|
gib()
|
|
return TRUE
|
|
if (EXPLODE_HEAVY)
|
|
adjust_brute_loss(60)
|
|
if (EXPLODE_LIGHT)
|
|
adjust_brute_loss(30)
|
|
|
|
return TRUE
|
|
|
|
/mob/living/basic/guardian/gib()
|
|
death(TRUE)
|
|
|
|
/mob/living/basic/guardian/dust(just_ash, drop_items, give_moodlet, force)
|
|
death(TRUE)
|
|
|
|
/// Link up with a summoner mob.
|
|
/mob/living/basic/guardian/proc/set_summoner(mob/living/to_who, different_person = FALSE)
|
|
if (QDELETED(src))
|
|
return // Just in case
|
|
if (QDELETED(to_who))
|
|
ghostize(FALSE)
|
|
qdel(src) // No life of free invulnerability for you.
|
|
return
|
|
cut_summoner(different_person)
|
|
AddComponent(/datum/component/life_link, to_who, CALLBACK(src, PROC_REF(on_harm)), CALLBACK(src, PROC_REF(on_summoner_death)))
|
|
summoner = to_who
|
|
|
|
for (var/action_type in control_actions)
|
|
if (locate(action_type) in summoner.actions)
|
|
continue
|
|
var/datum/action/new_action = new action_type(summoner)
|
|
new_action.Grant(summoner)
|
|
|
|
if (different_person)
|
|
if (mind)
|
|
mind.enslave_mind_to_creator(to_who)
|
|
else //mindless guardian, manually give them factions
|
|
add_faction(summoner.get_faction())
|
|
add_ally(summoner.allies)
|
|
summoner.add_ally(src)
|
|
remove_all_languages(LANGUAGE_MASTER)
|
|
copy_languages(to_who, LANGUAGE_MASTER) // make sure holoparasites speak same language as master
|
|
RegisterSignal(to_who, COMSIG_QDELETING, PROC_REF(on_summoner_deletion))
|
|
RegisterSignal(to_who, COMSIG_LIVING_ON_WABBAJACKED, PROC_REF(on_summoner_wabbajacked))
|
|
RegisterSignal(to_who, COMSIG_LIVING_SHAPESHIFTED, PROC_REF(on_summoner_shapeshifted))
|
|
RegisterSignal(to_who, COMSIG_LIVING_UNSHAPESHIFTED, PROC_REF(on_summoner_unshapeshifted))
|
|
recall(forced = TRUE)
|
|
leash_to(src, summoner)
|
|
if (to_who.stat == DEAD)
|
|
on_summoner_death(src, to_who)
|
|
summoner.updatehealth()
|
|
|
|
/// Remove all references to our summoner
|
|
/mob/living/basic/guardian/proc/cut_summoner(different_person = FALSE)
|
|
if (isnull(summoner))
|
|
return
|
|
if (is_deployed())
|
|
recall_effects()
|
|
var/summoner_turf = get_turf(summoner)
|
|
if (!isnull(summoner_turf))
|
|
forceMove(summoner_turf)
|
|
unleash()
|
|
UnregisterSignal(summoner, list(COMSIG_QDELETING, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED))
|
|
if (different_person)
|
|
summoner.remove_ally(src)
|
|
remove_faction(summoner.get_faction())
|
|
mind?.remove_all_antag_datums()
|
|
if (!length(summoner.get_all_linked_holoparasites() - src))
|
|
for (var/action_type in control_actions)
|
|
var/datum/action/remove_action = locate(action_type) in summoner.actions
|
|
if (isnull(remove_action))
|
|
continue
|
|
remove_action.Remove(summoner)
|
|
summoner = null
|
|
|
|
/// Connects these two mobs by a leash
|
|
/mob/living/basic/guardian/proc/leash_to(atom/movable/leashed, atom/movable/leashed_to)
|
|
leashed.AddComponent(\
|
|
/datum/component/leash,\
|
|
owner = leashed_to,\
|
|
distance = range,\
|
|
force_teleport_out_effect = /obj/effect/temp_visual/guardian/phase/out,\
|
|
force_teleport_in_effect = /obj/effect/temp_visual/guardian/phase,\
|
|
)
|
|
|
|
/// Removes the leash from this guardian
|
|
/mob/living/basic/guardian/proc/unleash()
|
|
qdel(GetComponent(/datum/component/leash))
|
|
|
|
/// Called when our owner dies. We fucked up, so now neither of us get to exist.
|
|
/mob/living/basic/guardian/proc/on_summoner_death(mob/living/source, mob/living/former_owner)
|
|
cut_summoner()
|
|
if (!isnull(former_owner.loc))
|
|
forceMove(former_owner.loc)
|
|
to_chat(src, span_danger("Your summoner has died!"))
|
|
visible_message(span_bolddanger("\The [src] dies along with its user!"))
|
|
former_owner.visible_message(span_bolddanger("[former_owner]'s body is completely consumed by the strain of sustaining [src]!"))
|
|
former_owner.dust(drop_items = TRUE)
|
|
|
|
/// Called when our health changes, inform our owner of why they are getting hurt (if they are)
|
|
/mob/living/basic/guardian/proc/on_harm(mob/living/source, mob/living/summoner, amount)
|
|
if (QDELETED(src) || QDELETED(summoner) || amount <= 2)
|
|
return
|
|
to_chat(summoner, span_bolddanger("[name] is under attack! You take damage!"))
|
|
summoner.visible_message(span_bolddanger("Blood sprays from [summoner] as [src] takes damage!"))
|
|
if(summoner.stat == UNCONSCIOUS || summoner.stat == HARD_CRIT)
|
|
to_chat(summoner, span_bolddanger("Your head pounds, you can't take the strain of sustaining [src] in this condition!"))
|
|
summoner.adjust_organ_loss(ORGAN_SLOT_BRAIN, amount * 0.5)
|
|
|
|
/// When our owner is deleted, we go too.
|
|
/mob/living/basic/guardian/proc/on_summoner_deletion(mob/living/source)
|
|
SIGNAL_HANDLER
|
|
cut_summoner()
|
|
to_chat(src, span_danger("Your summoner is gone, you feel yourself fading!"))
|
|
ghostize(FALSE)
|
|
qdel(src)
|
|
|
|
/// Signal proc for [COMSIG_LIVING_ON_WABBAJACKED], when our summoner is wabbajacked we should be alerted.
|
|
/mob/living/basic/guardian/proc/on_summoner_wabbajacked(mob/living/source, mob/living/new_mob)
|
|
SIGNAL_HANDLER
|
|
set_summoner(new_mob)
|
|
to_chat(src, span_holoparasite("Your summoner has changed form!"))
|
|
|
|
/// Signal proc for [COMSIG_LIVING_SHAPESHIFTED], when our summoner is shapeshifted we should change to the new mob
|
|
/mob/living/basic/guardian/proc/on_summoner_shapeshifted(mob/living/source, mob/living/new_shape)
|
|
SIGNAL_HANDLER
|
|
set_summoner(new_shape)
|
|
to_chat(src, span_holoparasite("Your summoner has shapeshifted into that of a [new_shape]!"))
|
|
|
|
/// Signal proc for [COMSIG_LIVING_UNSHAPESHIFTED], when our summoner unshapeshifts go back to that mob
|
|
/mob/living/basic/guardian/proc/on_summoner_unshapeshifted(mob/living/source, mob/living/old_summoner)
|
|
SIGNAL_HANDLER
|
|
set_summoner(old_summoner)
|
|
to_chat(src, span_holoparasite("Your summoner has shapeshifted back into their normal form!"))
|
|
|
|
/mob/living/basic/guardian/wabbajack(what_to_randomize, change_flags = WABBAJACK)
|
|
visible_message(span_warning("[src] resists the polymorph!")) // Ha, no
|
|
|
|
/mob/living/basic/guardian/can_suicide()
|
|
return FALSE // You gotta persuade your boss to end it instead, sorry
|
|
|
|
/// Returns true if you are out and about
|
|
/mob/living/basic/guardian/proc/is_deployed()
|
|
return isnull(summoner) || loc != summoner
|