From e38eee6dee1942b41b39297105ead79a218237df Mon Sep 17 00:00:00 2001 From: Zonespace <41448081+Zonespace27@users.noreply.github.com> Date: Mon, 9 Jan 2023 02:39:54 -0800 Subject: [PATCH] holopara qol update (#72042) (#18438) holoparasite types have improved code admins can now give someone a holoparasite through a new menu in vv dropdown alt click holopara abilities were moved to right click (support's beacon wasn't, but support's heal mode was) holoparas have less hardcoded stuff so admins can edit them easier holoparasites now get their light color from their guardian color holoparasites no longer have the hostile faction, things will attack them holoparasites now have a damage overlay, so you can see how much your summoner is damaged holoparasite health updating is now event based rather than running on life, so you'll see health changes everytime they happen, rather than every 2 seconds holoparasites fly properly again (they cant spacewalk, but count as flying for stuff like chasms) holoparasite creation now uses a radial menu with tooltips for each subtype. it also shows ghosts which type you picked holoparasites can no longer be fugu'd adds support for ownerless holoparasites fixes mildly related bugs along the way Co-authored-by: Fikou <23585223+Fikou@users.noreply.github.com> Co-authored-by: Tastyfish --- code/__DEFINES/guardian_defines.dm | 8 + code/__DEFINES/span.dm | 1 + code/__DEFINES/vv.dm | 1 + code/_onclick/hud/guardian.dm | 34 +- code/_onclick/hud/radial.dm | 1 + code/_onclick/hud/screen_objects.dm | 2 +- code/datums/components/overlay_lighting.dm | 3 + code/datums/mind/antag.dm | 2 +- code/game/atoms.dm | 13 +- .../equipment/spellbook_entries/assistance.dm | 8 - code/modules/mining/lavaland/tendril_loot.dm | 2 +- code/modules/mob/living/living.dm | 57 ++ .../living/simple_animal/guardian/guardian.dm | 802 +++++++----------- .../guardian/guardian_creator.dm | 211 +++++ .../simple_animal/guardian/types/assassin.dm | 96 ++- .../simple_animal/guardian/types/charger.dm | 79 +- .../simple_animal/guardian/types/dextrous.dm | 45 +- .../simple_animal/guardian/types/explosive.dm | 80 +- .../simple_animal/guardian/types/gaseous.dm | 42 +- .../guardian/types/gravitokinetic.dm | 108 ++- .../simple_animal/guardian/types/lightning.dm | 56 +- .../simple_animal/guardian/types/protector.dm | 75 +- .../simple_animal/guardian/types/ranged.dm | 149 ++-- .../simple_animal/guardian/types/standard.dm | 40 +- .../simple_animal/guardian/types/support.dm | 172 ++-- .../simple_animal/hostile/wumborian_fugu.dm | 10 +- code/modules/mob/mob_helpers.dm | 8 + .../unit_tests/simple_animal_freeze.dm | 8 +- code/modules/uplink/uplink_items/dangerous.dm | 2 +- icons/hud/guardian.dmi | Bin 10717 -> 13153 bytes icons/mob/nonhuman-player/guardian.dmi | Bin 24412 -> 24409 bytes tgstation.dme | 2 + 32 files changed, 1142 insertions(+), 975 deletions(-) create mode 100644 code/__DEFINES/guardian_defines.dm create mode 100644 code/modules/mob/living/simple_animal/guardian/guardian_creator.dm diff --git a/code/__DEFINES/guardian_defines.dm b/code/__DEFINES/guardian_defines.dm new file mode 100644 index 00000000000..d7aae6965a7 --- /dev/null +++ b/code/__DEFINES/guardian_defines.dm @@ -0,0 +1,8 @@ +#define GUARDIAN_THEME_TECH "tech" +#define GUARDIAN_THEME_MAGIC "magic" +#define GUARDIAN_THEME_CARP "carp" +#define GUARDIAN_THEME_MINER "miner" + +#define GUARDIAN_COLOR_LAYER 2 +#define GUARDIAN_HANDS_LAYER 1 +#define GUARDIAN_TOTAL_LAYERS 2 diff --git a/code/__DEFINES/span.dm b/code/__DEFINES/span.dm index b813ee4e34c..ad6e46e03b5 100644 --- a/code/__DEFINES/span.dm +++ b/code/__DEFINES/span.dm @@ -55,6 +55,7 @@ #define span_highlight(str) ("" + str + "") #define span_his_grace(str) ("" + str + "") #define span_holoparasite(str) ("" + str + "") +#define span_boldholoparasite(str) ("" + str + "") #define span_hypnophrase(str) ("" + str + "") #define span_icon(str) ("" + str + "") #define span_info(str) ("" + str + "") diff --git a/code/__DEFINES/vv.dm b/code/__DEFINES/vv.dm index 3837cc6c0aa..9b75660c55d 100644 --- a/code/__DEFINES/vv.dm +++ b/code/__DEFINES/vv.dm @@ -130,6 +130,7 @@ #define VV_HK_REMOVE_MOOD "removemood" #define VV_HK_GIVE_HALLUCINATION "give_hallucination" #define VV_HK_GIVE_DELUSION_HALLUCINATION "give_hallucination_delusion" +#define VV_HK_GIVE_GUARDIAN_SPIRIT "give_guardian_spirit" // /mob/living/carbon #define VV_HK_MAKE_AI "aiify" diff --git a/code/_onclick/hud/guardian.dm b/code/_onclick/hud/guardian.dm index f30045c35da..91da0f9fdb8 100644 --- a/code/_onclick/hud/guardian.dm +++ b/code/_onclick/hud/guardian.dm @@ -69,7 +69,7 @@ static_inventory += using pull_icon = new /atom/movable/screen/pull() - pull_icon.icon = 'icons/hud/guardian.dmi' + pull_icon.icon = ui_style pull_icon.update_appearance() pull_icon.screen_loc = ui_living_pull pull_icon.hud = src @@ -103,15 +103,15 @@ if(!mymob) return if(istype(mymob, /mob/living/simple_animal/hostile/guardian/dextrous)) - var/mob/living/simple_animal/hostile/guardian/dextrous/D = mymob + var/mob/living/simple_animal/hostile/guardian/dextrous/dex_guardian = mymob if(hud_shown) - if(D.internal_storage) - D.internal_storage.screen_loc = ui_id - D.client.screen += D.internal_storage + if(dex_guardian.internal_storage) + dex_guardian.internal_storage.screen_loc = ui_id + dex_guardian.client.screen += dex_guardian.internal_storage else - if(D.internal_storage) - D.internal_storage.screen_loc = null + if(dex_guardian.internal_storage) + dex_guardian.internal_storage.screen_loc = null ..() @@ -125,8 +125,8 @@ /atom/movable/screen/guardian/manifest/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/G = usr - G.Manifest() + var/mob/living/simple_animal/hostile/guardian/user = usr + user.manifest() /atom/movable/screen/guardian/recall @@ -136,8 +136,8 @@ /atom/movable/screen/guardian/recall/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/G = usr - G.Recall() + var/mob/living/simple_animal/hostile/guardian/user = usr + user.recall() /atom/movable/screen/guardian/toggle_mode icon_state = "toggle" @@ -146,8 +146,8 @@ /atom/movable/screen/guardian/toggle_mode/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/G = usr - G.ToggleMode() + var/mob/living/simple_animal/hostile/guardian/user = usr + user.toggle_modes() /atom/movable/screen/guardian/toggle_mode/inactive icon_state = "notoggle" //greyed out so it doesn't look like it'll work @@ -169,8 +169,8 @@ /atom/movable/screen/guardian/communicate/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/G = usr - G.Communicate() + var/mob/living/simple_animal/hostile/guardian/user = usr + user.communicate() /atom/movable/screen/guardian/toggle_light @@ -180,5 +180,5 @@ /atom/movable/screen/guardian/toggle_light/Click() if(isguardian(usr)) - var/mob/living/simple_animal/hostile/guardian/G = usr - G.ToggleLight() + var/mob/living/simple_animal/hostile/guardian/user = usr + user.toggle_light() diff --git a/code/_onclick/hud/radial.dm b/code/_onclick/hud/radial.dm index 44c143eed51..56c94398dff 100644 --- a/code/_onclick/hud/radial.dm +++ b/code/_onclick/hud/radial.dm @@ -200,6 +200,7 @@ GLOBAL_LIST_EMPTY(radial_menus) /datum/radial_menu/proc/HideElement(atom/movable/screen/radial/slice/E) E.cut_overlays() + E.vis_contents.Cut() E.alpha = 0 E.name = "None" E.maptext = null diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index 8f029dcab9d..95175edd60a 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -581,7 +581,7 @@ /atom/movable/screen/healths/guardian name = "summoner health" - icon = 'icons/mob/nonhuman-player/guardian.dmi' + icon = 'icons/hud/guardian.dmi' icon_state = "base" mouse_opacity = MOUSE_OPACITY_TRANSPARENT diff --git a/code/datums/components/overlay_lighting.dm b/code/datums/components/overlay_lighting.dm index 0048cab7aea..f24d25c6e6e 100644 --- a/code/datums/components/overlay_lighting.dm +++ b/code/datums/components/overlay_lighting.dm @@ -136,6 +136,7 @@ clean_old_turfs() UnregisterSignal(parent, list( COMSIG_MOVABLE_MOVED, + COMSIG_MOVABLE_Z_CHANGED, COMSIG_ATOM_UPDATE_LIGHT_RANGE, COMSIG_ATOM_UPDATE_LIGHT_POWER, COMSIG_ATOM_UPDATE_LIGHT_COLOR, @@ -277,6 +278,8 @@ ///Called when the current_holder is qdeleted, to remove the light effect. /datum/component/overlay_lighting/proc/on_holder_qdel(atom/movable/source, force) SIGNAL_HANDLER + if(QDELETED(current_holder)) + return UnregisterSignal(current_holder, list(COMSIG_PARENT_QDELETING, COMSIG_MOVABLE_MOVED)) if(directional) UnregisterSignal(current_holder, COMSIG_ATOM_DIR_CHANGE) diff --git a/code/datums/mind/antag.dm b/code/datums/mind/antag.dm index 9b5fcc8d384..048ab6ca787 100644 --- a/code/datums/mind/antag.dm +++ b/code/datums/mind/antag.dm @@ -203,7 +203,7 @@ current.faction |= creator.faction creator.faction |= current.faction - if(creator.mind.special_role) + if(creator.mind?.special_role) message_admins("[ADMIN_LOOKUPFLW(current)] has been created by [ADMIN_LOOKUPFLW(creator)], an antagonist.") to_chat(current, span_userdanger("Despite your creator's current allegiances, your true master remains [creator.real_name]. If their loyalties change, so do yours. This will never change unless your creator's body is destroyed.")) diff --git a/code/game/atoms.dm b/code/game/atoms.dm index c66cb9218f2..b4cbc3ce3d5 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1901,18 +1901,15 @@ return 0 var/list/forced_gravity = list() - if(SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity)) - if(!length(forced_gravity)) - SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) + SEND_SIGNAL(src, COMSIG_ATOM_HAS_GRAVITY, gravity_turf, forced_gravity) + SEND_SIGNAL(gravity_turf, COMSIG_TURF_HAS_GRAVITY, src, forced_gravity) + if(length(forced_gravity)) + var/positive_grav = max(forced_gravity) + var/negative_grav = min(min(forced_gravity), 0) //negative grav needs to be below or equal to 0 - var/positive_grav = 0 - var/negative_grav = 0 //our gravity is sum of the most massive positive and negative numbers returned by the signal //so that adding two forced_gravity elements with an effect size of 1 each doesnt add to 2 gravity //but negative force gravity effects can cancel out positive ones - for(var/gravity_influence in forced_gravity) - positive_grav = max(positive_grav, gravity_influence) - negative_grav = min(negative_grav, gravity_influence) return (positive_grav + negative_grav) diff --git a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm index beec27fc99d..9b084b90a64 100644 --- a/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm +++ b/code/modules/antagonists/wizard/equipment/spellbook_entries/assistance.dm @@ -72,14 +72,6 @@ item_path = /obj/item/guardiancreator/choose/wizard category = "Assistance" -/datum/spellbook_entry/item/guardian/buy_spell(mob/living/carbon/human/user, obj/item/spellbook/book) - . = ..() - if(!.) - return - - new /obj/item/paper/guides/antag/guardian/wizard(get_turf(user)) - to_chat(user, span_notice("If you are not experienced in the ways of wizardly guardians, a guide has been summoned at your feet.")) - /datum/spellbook_entry/item/bloodbottle name = "Bottle of Blood" desc = "A bottle of magically infused blood, the smell of which will \ diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index 69ae5c99d27..1b017ffc124 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -209,7 +209,7 @@ /obj/item/clothing/neck/necklace/memento_mori/proc/regurgitate_guardian(mob/living/simple_animal/hostile/guardian/guardian) guardian.locked = FALSE - guardian.Recall(TRUE) + guardian.recall(forced = TRUE) to_chat(guardian, span_notice("You have been returned back from your summoner's pendant!")) guardian.playsound_local(get_turf(guardian), 'sound/magic/repulse.ogg', 50, TRUE) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 0ea3f164726..e3d12151a10 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1857,6 +1857,7 @@ GLOBAL_LIST_EMPTY(fire_appearances) VV_DROPDOWN_OPTION(VV_HK_REMOVE_MOOD, "Remove Mood Event") VV_DROPDOWN_OPTION(VV_HK_GIVE_HALLUCINATION, "Give Hallucination") VV_DROPDOWN_OPTION(VV_HK_GIVE_DELUSION_HALLUCINATION, "Give Delusion Hallucination") + VV_DROPDOWN_OPTION(VV_HK_GIVE_GUARDIAN_SPIRIT, "Give Guardian Spirit") /mob/living/vv_do_topic(list/href_list) . = ..() @@ -1879,6 +1880,10 @@ GLOBAL_LIST_EMPTY(fire_appearances) if(!check_rights(NONE)) return admin_give_delusion(usr) + if(href_list[VV_HK_GIVE_GUARDIAN_SPIRIT]) + if(!check_rights(NONE)) + return + admin_give_guardian(usr) /mob/living/proc/move_to_error_room() var/obj/effect/landmark/error/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list @@ -2512,3 +2517,55 @@ GLOBAL_LIST_EMPTY(fire_appearances) log_admin("[key_name(admin)] gave [src] a delusion hallucination. (Type: [delusion_args[1]])") // Not using the wrapper here because we already have a list / arglist _cause_hallucination(delusion_args) + +/mob/living/proc/admin_give_guardian(mob/admin) + if(!admin || !check_rights(NONE)) + return + var/del_mob = FALSE + var/mob/old_mob + var/ai_control = FALSE + var/list/possible_players = list("None", "Poll Ghosts") + sort_list(GLOB.clients) + var/client/guardian_client = tgui_input_list(admin, "Pick the player to put in control.", "Guardian Controller", possible_players) + if(!guardian_client) + return + else if(guardian_client == "None") + guardian_client = null + ai_control = (tgui_alert(admin, "Do you want to give the spirit AI control?", "Guardian Controller", list("Yes", "No")) == "Yes") + else if(guardian_client == "Poll Ghosts") + var/list/candidates = poll_ghost_candidates("Do you want to play as an admin created Guardian Spirit of [real_name]?", ROLE_PAI, FALSE, 100, POLL_IGNORE_HOLOPARASITE) + if(LAZYLEN(candidates)) + var/mob/dead/observer/candidate = pick(candidates) + guardian_client = candidate.client + else + tgui_alert(admin, "No ghost candidates.", "Guardian Controller") + return + else + old_mob = guardian_client.mob + if(isobserver(old_mob) || tgui_alert(admin, "Do you want to delete [guardian_client]'s old mob?", "Guardian Controller", list("Yes"," No")) == "Yes") + del_mob = TRUE + var/picked_type = tgui_input_list(admin, "Pick the guardian type.", "Guardian Controller", subtypesof(/mob/living/simple_animal/hostile/guardian)) + var/picked_theme = tgui_input_list(admin, "Pick the guardian theme.", "Guardian Controller", list(GUARDIAN_THEME_TECH, GUARDIAN_THEME_MAGIC, GUARDIAN_THEME_CARP, GUARDIAN_THEME_MINER, "Random")) + if(picked_theme == "Random") + picked_theme = null //holopara code handles not having a theme by giving a random one + var/picked_name = tgui_input_text(admin, "Name the guardian, leave empty to let player name it.", "Guardian Controller") + var/picked_color = input(admin, "Set the guardian's color, cancel to let player set it.", "Guardian Controller", "#ffffff") as color|null + if(tgui_alert(admin, "Confirm creation.", "Guardian Controller", list("Yes", "No")) != "Yes") + return + var/mob/living/simple_animal/hostile/guardian/summoned_guardian = new picked_type(src, picked_theme) + summoned_guardian.set_summoner(src, different_person = TRUE) + if(picked_name) + summoned_guardian.fully_replace_character_name(null, picked_name) + if(picked_color) + summoned_guardian.set_guardian_color(picked_color) + summoned_guardian.key = guardian_client?.key + guardian_client?.init_verbs() + if(del_mob) + qdel(old_mob) + if(ai_control) + summoned_guardian.can_have_ai = TRUE + summoned_guardian.toggle_ai(AI_ON) + summoned_guardian.manifest() + message_admins(span_adminnotice("[key_name_admin(admin)] gave a guardian spirit controlled by [guardian_client || "AI"] to [src].")) + log_admin("[key_name(admin)] gave a guardian spirit controlled by [guardian_client] to [src].") + SSblackbox.record_feedback("tally", "admin_verb", 1, "Give Guardian Spirit") + diff --git a/code/modules/mob/living/simple_animal/guardian/guardian.dm b/code/modules/mob/living/simple_animal/guardian/guardian.dm index e8742da0551..acf18a8642b 100644 --- a/code/modules/mob/living/simple_animal/guardian/guardian.dm +++ b/code/modules/mob/living/simple_animal/guardian/guardian.dm @@ -1,9 +1,5 @@ - GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians -#define GUARDIAN_HANDS_LAYER 1 -#define GUARDIAN_TOTAL_LAYERS 1 - /mob/living/simple_animal/hostile/guardian name = "Guardian Spirit" real_name = "Guardian Spirit" @@ -39,64 +35,134 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians obj_damage = 40 melee_damage_lower = 15 melee_damage_upper = 15 - butcher_results = list(/obj/item/ectoplasm = 1) + del_on_death = TRUE + loot = list(/obj/effect/temp_visual/guardian/phase/out) AIStatus = AI_OFF + can_have_ai = FALSE light_system = MOVABLE_LIGHT light_range = 3 light_on = FALSE hud_type = /datum/hud/guardian dextrous_hud_type = /datum/hud/dextrous/guardian //if we're set to dextrous, account for it. - var/mutable_appearance/cooloverlay - var/guardiancolor - var/recolorentiresprite - var/theme + faction = list() + + /// The guardian's color, used for their sprite, chat, and some effects made by it. + var/guardian_color + /// List of overlays we use. var/list/guardian_overlays[GUARDIAN_TOTAL_LAYERS] - var/reset = 0 //if the summoner has reset the guardian already - var/cooldown = 0 + + /// The summoner of the guardian, the one it's intended to guard! var/mob/living/summoner - var/range = 10 //how far from the user the spirit can be - var/toggle_button_type = /atom/movable/screen/guardian/toggle_mode/inactive //what sort of toggle button the hud uses - var/playstyle_string = "You are a Guardian without any type. You shouldn't exist!" - var/magic_fluff_string = "You draw the Coder, symbolizing bugs and errors. This shouldn't happen! Submit a bug report!" - var/tech_fluff_string = "BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!" - var/carp_fluff_string = "CARP CARP CARP SOME SORT OF HORRIFIC BUG BLAME THE CODERS CARP CARP CARP" - var/miner_fluff_string = "You encounter... Mythril, it shouldn't exist... Submit a bug report!" - ///Are we forced to not be able to manifest/recall? + /// How far from the summoner the guardian can be. + var/range = 10 + + /// Which toggle button the HUD uses. + var/toggle_button_type = /atom/movable/screen/guardian/toggle_mode/inactive + /// 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" + + /// 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!") + /// The fluff string we actually use. + var/used_fluff_string + /// Fluff string from tarot cards. + var/magic_fluff_string = span_holoparasite("You draw the Coder, symbolizing bugs and errors. This shouldn't happen! Submit a bug report!") + /// Fluff string from holoparasite injectors. + var/tech_fluff_string = span_holoparasite("BOOT SEQUENCE COMPLETE. ERROR MODULE LOADED. THIS SHOULDN'T HAPPEN. Submit a bug report!") + /// Fluff string from holocarp fishsticks. + var/carp_fluff_string = span_holoparasite("CARP CARP CARP SOME SORT OF HORRIFIC BUG BLAME THE CODERS CARP CARP CARP") + /// Fluff string from the dusty shard. + var/miner_fluff_string = span_holoparasite("You encounter... Mythril, it shouldn't exist... Submit a bug report!") + + /// 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) /mob/living/simple_animal/hostile/guardian/Initialize(mapload, theme) + . = ..() GLOB.parasites += src - updatetheme(theme) + update_theme(theme) AddElement(/datum/element/simple_flying) + +/mob/living/simple_animal/hostile/guardian/Destroy() //if deleted by admins or something random, cut from the summoner + if(!QDELETED(summoner)) + cut_summoner(different_person = TRUE) return ..() /// Setter for our summoner mob. -/mob/living/simple_animal/hostile/guardian/proc/set_summoner(mob/to_who) +/mob/living/simple_animal/hostile/guardian/proc/set_summoner(mob/to_who, different_person = FALSE) + if(QDELETED(to_who)) + qdel(src) //no gettin off scot-free pal......... + return if(summoner) - UnregisterSignal(summoner, list(COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED)) - + cut_summoner(different_person) summoner = to_who - Recall(TRUE) - RegisterSignal(to_who, COMSIG_LIVING_ON_WABBAJACKED, PROC_REF(on_owner_wabbajacked)) - RegisterSignal(to_who, COMSIG_LIVING_SHAPESHIFTED, PROC_REF(on_owner_shapeshifted)) - RegisterSignal(to_who, COMSIG_LIVING_UNSHAPESHIFTED, PROC_REF(on_owner_unshapeshifted)) + update_health_hud() + med_hud_set_health() + med_hud_set_status() + add_verb(to_who, list( + /mob/living/proc/guardian_comm, + /mob/living/proc/guardian_recall, + /mob/living/proc/guardian_reset, + )) + if(different_person) + if(mind) + mind.enslave_mind_to_creator(to_who) + else //mindless guardian, manually give them factions + faction += summoner.faction + summoner.faction += "[REF(src)]" + remove_all_languages(LANGUAGE_MASTER) + copy_languages(to_who, LANGUAGE_MASTER) // make sure holoparasites speak same language as master + update_atom_languages() + RegisterSignal(to_who, COMSIG_MOVABLE_MOVED, PROC_REF(check_distance)) + RegisterSignal(to_who, COMSIG_PARENT_QDELETING, PROC_REF(on_summoner_deletion)) + RegisterSignal(to_who, COMSIG_LIVING_DEATH, PROC_REF(on_summoner_death)) + RegisterSignal(to_who, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_summoner_health_update)) + 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) + +/mob/living/simple_animal/hostile/guardian/proc/cut_summoner(different_person = FALSE) + if(is_deployed()) + recall_effects() + forceMove(get_turf(src)) + UnregisterSignal(summoner, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING, COMSIG_LIVING_DEATH, COMSIG_LIVING_HEALTH_UPDATE, COMSIG_LIVING_ON_WABBAJACKED, COMSIG_LIVING_SHAPESHIFTED, COMSIG_LIVING_UNSHAPESHIFTED)) + if(different_person) + summoner.faction -= "[REF(src)]" + faction -= summoner.faction + mind?.remove_all_antag_datums() + if(!length(summoner.get_all_linked_holoparasites() - src)) + remove_verb(summoner, list( + /mob/living/proc/guardian_comm, + /mob/living/proc/guardian_recall, + /mob/living/proc/guardian_reset, + )) + summoner = null /// Signal proc for [COMSIG_LIVING_ON_WABBAJACKED], when our summoner is wabbajacked we should be alerted. -/mob/living/simple_animal/hostile/guardian/proc/on_owner_wabbajacked(mob/living/source, mob/living/new_mob) +/mob/living/simple_animal/hostile/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/simple_animal/hostile/guardian/proc/on_owner_shapeshifted(mob/living/source, mob/living/new_shape) +/mob/living/simple_animal/hostile/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/simple_animal/hostile/guardian/proc/on_owner_unshapeshifted(mob/living/source, mob/living/old_summoner) +/mob/living/simple_animal/hostile/guardian/proc/on_summoner_unshapeshifted(mob/living/source, mob/living/old_summoner) SIGNAL_HANDLER set_summoner(old_summoner) @@ -106,204 +172,241 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians /mob/living/simple_animal/hostile/guardian/wabbajack(what_to_randomize, change_flags = WABBAJACK) visible_message(span_warning("[src] resists the polymorph!")) +/mob/living/simple_animal/hostile/guardian/proc/on_summoner_health_update(mob/living/source) + SIGNAL_HANDLER + + update_health_hud() + med_hud_set_health() + med_hud_set_status() + /mob/living/simple_animal/hostile/guardian/med_hud_set_health() - if(summoner) - var/image/holder = hud_list[HEALTH_HUD] - holder.icon_state = "hud[RoundHealth(summoner)]" + var/image/holder = hud_list?[HEALTH_HUD] + if(isnull(holder)) + return + holder.icon_state = "hud[RoundHealth(summoner || src)]" + var/icon/size_check = icon(icon, icon_state, dir) + holder.pixel_y = size_check.Height() - world.icon_size /mob/living/simple_animal/hostile/guardian/med_hud_set_status() - if(summoner) - var/image/holder = hud_list[STATUS_HUD] - var/icon/I = icon(icon, icon_state, dir) - holder.pixel_y = I.Height() - world.icon_size - if(summoner.stat == DEAD) - holder.icon_state = "huddead" - else - holder.icon_state = "hudhealthy" + var/image/holder = hud_list?[STATUS_HUD] + if(isnull(holder)) + return + var/icon/size_check = icon(icon, icon_state, dir) + holder.pixel_y = size_check.Height() - world.icon_size + var/mob/living/checking_mob = summoner || src + if(checking_mob.stat == DEAD || HAS_TRAIT(checking_mob, TRAIT_FAKEDEATH)) + holder.icon_state = "huddead" + else + holder.icon_state = "hudhealthy" /mob/living/simple_animal/hostile/guardian/Destroy() GLOB.parasites -= src return ..() -/mob/living/simple_animal/hostile/guardian/proc/updatetheme(theme) //update the guardian's theme +/mob/living/simple_animal/hostile/guardian/proc/update_theme(theme) //update the guardian's theme if(!theme) - theme = pick("magic", "tech", "carp", "miner") + theme = pick(GUARDIAN_THEME_MAGIC, GUARDIAN_THEME_TECH, GUARDIAN_THEME_CARP, GUARDIAN_THEME_MINER) switch(theme)//should make it easier to create new stand designs in the future if anyone likes that - if("magic") + if(GUARDIAN_THEME_MAGIC) name = "Guardian Spirit" real_name = "Guardian Spirit" bubble_icon = "guardian" icon_state = "magicbase" icon_living = "magicbase" icon_dead = "magicbase" - if("tech") + used_fluff_string = magic_fluff_string + if(GUARDIAN_THEME_TECH) name = "Holoparasite" real_name = "Holoparasite" bubble_icon = "holo" icon_state = "techbase" icon_living = "techbase" icon_dead = "techbase" - if("miner") + used_fluff_string = tech_fluff_string + if(GUARDIAN_THEME_MINER) name = "Power Miner" real_name = "Power Miner" bubble_icon = "guardian" icon_state = "minerbase" icon_living = "minerbase" icon_dead = "minerbase" - if("carp") + used_fluff_string = miner_fluff_string + if(GUARDIAN_THEME_CARP) name = "Holocarp" real_name = "Holocarp" bubble_icon = "holo" - icon_state = "holocarp" - icon_living = "holocarp" - icon_dead = "holocarp" + icon_state = null //entirely handled by overlays + icon_living = null + icon_dead = null speak_emote = string_list(list("gnashes")) desc = "A mysterious fish that stands by its charge, ever vigilant." attack_verb_continuous = "bites" attack_verb_simple = "bite" attack_sound = 'sound/weapons/bite.ogg' attack_vis_effect = ATTACK_EFFECT_BITE - recolorentiresprite = TRUE - if(!recolorentiresprite) //we want this to proc before stand logs in, so the overlay isn't gone for some reason - cooloverlay = mutable_appearance(icon, theme) - add_overlay(cooloverlay) + used_fluff_string = carp_fluff_string + guardian_overlays[GUARDIAN_COLOR_LAYER] = mutable_appearance(icon, theme) + apply_overlay(GUARDIAN_COLOR_LAYER) /mob/living/simple_animal/hostile/guardian/Login() //if we have a mind, set its name to ours when it logs in . = ..() if(!. || !client) return FALSE - if(mind) - mind.name = "[real_name]" if(!summoner) - to_chat(src, "For some reason, somehow, you have no summoner. Please report this bug immediately.") + to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately.")) return to_chat(src, span_holoparasite("You are a [real_name], 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(!guardiancolor) - guardianrename() - guardianrecolor() + if(!guardian_color) + locked = TRUE + guardian_rename() + guardian_recolor() + locked = FALSE -/mob/living/simple_animal/hostile/guardian/proc/guardianrecolor() - guardiancolor = input(src,"What would you like your color to be?","Choose Your Color","#ffffff") as color|null - if(!guardiancolor) //redo proc until we get a color - to_chat(src, span_warning("Not a valid color, please try again.")) - guardianrecolor() +/mob/living/simple_animal/hostile/guardian/mind_initialize() + . = ..() + if(!summoner) + to_chat(src, span_boldholoparasite("For some reason, somehow, you have no summoner. Please report this bug immediately.")) return - if(!recolorentiresprite) - cooloverlay.color = guardiancolor - cut_overlay(cooloverlay) //we need to get our new color - add_overlay(cooloverlay) - else - add_atom_colour(guardiancolor, FIXED_COLOUR_PRIORITY) + 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. -/mob/living/simple_animal/hostile/guardian/proc/guardianrename() +/mob/living/simple_animal/hostile/guardian/proc/guardian_recolor() + if(!client) + return + var/chosen_guardian_color = input(src, "What would you like your color to be?","Choose Your Color","#ffffff") as color|null + if(!chosen_guardian_color) //redo proc until we get a color + to_chat(src, span_warning("Not a valid color, please try again.")) + guardian_recolor() + return + set_guardian_color(chosen_guardian_color) + +/mob/living/simple_animal/hostile/guardian/proc/set_guardian_color(colour) + guardian_color = colour + set_light_color(guardian_color) + var/mutable_appearance/guardian_color_overlay = guardian_overlays[GUARDIAN_COLOR_LAYER] + remove_overlay(GUARDIAN_COLOR_LAYER) + guardian_color_overlay.color = guardian_color + guardian_overlays[GUARDIAN_COLOR_LAYER] = guardian_color_overlay + apply_overlay(GUARDIAN_COLOR_LAYER) + +/mob/living/simple_animal/hostile/guardian/proc/guardian_rename() + if(!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", real_name, MAX_NAME_LEN))) if(!new_name) //redo proc until we get a good name to_chat(src, span_warning("Not a valid name, please try again.")) - guardianrename() + guardian_rename() return to_chat(src, span_notice("Your new name [span_name("[new_name]")] anchors itself in your mind.")) fully_replace_character_name(null, new_name) -/mob/living/simple_animal/hostile/guardian/Life(delta_time = SSMOBS_DT, times_fired) //Dies if the summoner dies - . = ..() - update_health_hud() //we need to update all of our health displays to match our summoner and we can't practically give the summoner a hook to do it - med_hud_set_health() - med_hud_set_status() - if(!QDELETED(summoner)) - if(summoner.stat == DEAD) - forceMove(summoner.loc) - to_chat(src, span_danger("Your summoner has died!")) - visible_message(span_danger("\The [src] dies along with its user!")) - summoner.visible_message(span_danger("[summoner]'s body is completely consumed by the strain of sustaining [src]!")) - for(var/obj/item/W in summoner) - if(!summoner.dropItemToGround(W)) - qdel(W) - summoner.dust() - death(TRUE) - qdel(src) - else - to_chat(src, span_danger("Your summoner has died!")) - visible_message(span_danger("[src] dies along with its user!")) - death(TRUE) - qdel(src) - snapback() +/mob/living/simple_animal/hostile/guardian/proc/on_summoner_death(mob/living/source) + SIGNAL_HANDLER + + cut_summoner() + forceMove(source.loc) + to_chat(src, span_danger("Your summoner has died!")) + visible_message(span_bolddanger("\The [src] dies along with its user!")) + source.visible_message(span_bolddanger("[source]'s body is completely consumed by the strain of sustaining [src]!")) + source.dust(drop_items = TRUE) + death(TRUE) + +/mob/living/simple_animal/hostile/guardian/proc/on_summoner_deletion(mob/living/source) + SIGNAL_HANDLER + + cut_summoner() + to_chat(src, span_danger("Your summoner is gone!")) + qdel(src) /mob/living/simple_animal/hostile/guardian/get_status_tab_items() . += ..() if(summoner) - var/resulthealth - if(iscarbon(summoner)) - resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - summoner.health) / abs(HEALTH_THRESHOLD_DEAD - summoner.maxHealth)) * 100) - else - resulthealth = round((summoner.health / summoner.maxHealth) * 100, 0.5) - . += "Summoner Health: [resulthealth]%" - if(cooldown >= world.time) - . += "Manifest/Recall Cooldown Remaining: [DisplayTimeText(cooldown - world.time)]" + var/healthpercent = health_percentage(summoner) + . += "Summoner Health: [round(healthpercent, 0.5)]%" + if(!COOLDOWN_FINISHED(src, manifest_cooldown)) + . += "Manifest/Recall Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, manifest_cooldown))]" /mob/living/simple_animal/hostile/guardian/Move() //Returns to summoner if they move out of range . = ..() - snapback() + check_distance() -/mob/living/simple_animal/hostile/guardian/proc/snapback() - if(summoner) - if(get_dist(get_turf(summoner),get_turf(src)) <= range) - return - else - to_chat(src, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")) - visible_message(span_danger("\The [src] jumps back to its user.")) - if(istype(summoner.loc, /obj/effect)) - Recall(TRUE) - else - new /obj/effect/temp_visual/guardian/phase/out(loc) - forceMove(summoner.loc) - new /obj/effect/temp_visual/guardian/phase(loc) +/mob/living/simple_animal/hostile/guardian/proc/check_distance() + SIGNAL_HANDLER + + if(!summoner) + return + if(get_dist(get_turf(summoner), get_turf(src)) <= range) + return + to_chat(src, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")) + visible_message(span_danger("\The [src] jumps back to its user.")) + if(istype(summoner.loc, /obj/effect)) + recall(forced = TRUE) + else + new /obj/effect/temp_visual/guardian/phase/out(loc) + forceMove(summoner.loc) + new /obj/effect/temp_visual/guardian/phase(loc) /mob/living/simple_animal/hostile/guardian/canSuicide() return FALSE /mob/living/simple_animal/hostile/guardian/proc/is_deployed() - return loc != summoner + return loc != summoner || !summoner -/mob/living/simple_animal/hostile/guardian/AttackingTarget() +/mob/living/simple_animal/hostile/guardian/AttackingTarget(atom/attacked_target) if(!is_deployed()) - to_chat(src, "[span_danger("You must be manifested to attack!")]") + to_chat(src, span_bolddanger("You must be manifested to attack!")) return FALSE else return ..() -/mob/living/simple_animal/hostile/guardian/death() - drop_all_held_items() - ..() - if(summoner) - to_chat(summoner, "[span_danger("Your [name] died somehow!")]") +/mob/living/simple_animal/hostile/guardian/death(gibbed) + . = ..() + if(!QDELETED(summoner)) + to_chat(summoner, span_bolddanger("Your [name] died somehow!")) summoner.dust() /mob/living/simple_animal/hostile/guardian/update_health_hud() - if(summoner && hud_used?.healths) - var/resulthealth - if(iscarbon(summoner)) - resulthealth = round((abs(HEALTH_THRESHOLD_DEAD - summoner.health) / abs(HEALTH_THRESHOLD_DEAD - summoner.maxHealth)) * 100) + var/severity = 0 + var/healthpercent = health_percentage(summoner || src) + switch(healthpercent) + if(100 to INFINITY) + severity = 0 + if(85 to 100) + severity = 1 + if(70 to 85) + severity = 2 + if(55 to 70) + severity = 3 + if(40 to 55) + severity = 4 + if(25 to 40) + severity = 5 else - resulthealth = round((summoner.health / summoner.maxHealth) * 100, 0.5) - hud_used.healths.maptext = MAPTEXT("
[resulthealth]%
") + severity = 6 + if(severity > 0) + overlay_fullscreen("brute", /atom/movable/screen/fullscreen/brute, severity) + else + clear_fullscreen("brute") + if(hud_used?.healths) + hud_used.healths.maptext = MAPTEXT("
[round(healthpercent, 0.5)]%
") /mob/living/simple_animal/hostile/guardian/adjustHealth(amount, updating_health = TRUE, forced = FALSE) //The spirit is invincible, but passes on damage to the summoner . = amount - if(summoner) - if(loc == summoner) - return FALSE - summoner.adjustBruteLoss(amount) - if(amount > 0) - to_chat(summoner, "[span_danger("Your [name] is under attack! You take damage!")]") - summoner.visible_message(span_danger("Blood sprays from [summoner] as [src] takes damage!")) - switch(summoner.stat) - if(UNCONSCIOUS, HARD_CRIT) - to_chat(summoner, "[span_danger("Your body can't take the strain of sustaining [src] in this condition, it begins to fall apart!")]") - summoner.adjustCloneLoss(amount * 0.5) //dying hosts take 50% bonus damage as cloneloss - update_health_hud() + if(!summoner) + return ..() + if(!is_deployed()) + return FALSE + summoner.adjustBruteLoss(amount) + if(amount < 0 || QDELETED(summoner)) + return + to_chat(summoner, span_bolddanger("Your [name] is under attack! You take damage!")) + summoner.visible_message(span_bolddanger("Blood sprays from [summoner] as [src] takes damage!")) + switch(summoner.stat) + if(UNCONSCIOUS, HARD_CRIT) + to_chat(summoner, span_bolddanger("Your body can't take the strain of sustaining [src] in this condition, it begins to fall apart!")) + summoner.adjustCloneLoss(amount * 0.5) //dying hosts take 50% bonus damage as cloneloss /mob/living/simple_animal/hostile/guardian/ex_act(severity, target) switch(severity) @@ -317,12 +420,10 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians adjustBruteLoss(30) /mob/living/simple_animal/hostile/guardian/gib() - if(summoner) - to_chat(summoner, "[span_danger("Your [src] was blown up!")]") - summoner.investigate_log("has been gibbed by an explosion.", INVESTIGATE_DEATHS) - summoner.gib() - ghostize() - qdel(src) + death(TRUE) + +/mob/living/simple_animal/hostile/guardian/dust(just_ash, drop_items, force) + death(TRUE) //HAND HANDLING @@ -387,31 +488,35 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians //MANIFEST, RECALL, TOGGLE MODE/LIGHT, SHOW TYPE -/mob/living/simple_animal/hostile/guardian/proc/Manifest(forced) - if(istype(summoner.loc, /obj/effect) || (cooldown > world.time && !forced) || locked) +/mob/living/simple_animal/hostile/guardian/proc/manifest(forced) + if(is_deployed() || istype(summoner.loc, /obj/effect) || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) return FALSE - if(loc == summoner) - forceMove(summoner.loc) - new /obj/effect/temp_visual/guardian/phase(loc) - cooldown = world.time + 10 - reset_perspective() - return TRUE - return FALSE - -/mob/living/simple_animal/hostile/guardian/proc/Recall(forced) - if(!summoner || loc == summoner || (cooldown > world.time && !forced) || locked) - return FALSE - new /obj/effect/temp_visual/guardian/phase/out(loc) - - forceMove(summoner) - cooldown = world.time + 10 + forceMove(summoner.loc) + new /obj/effect/temp_visual/guardian/phase(loc) + COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) + reset_perspective() + manifest_effects() return TRUE -/mob/living/simple_animal/hostile/guardian/proc/ToggleMode() - to_chat(src, "[span_danger("You don't have another mode!")]") +/mob/living/simple_animal/hostile/guardian/proc/recall(forced) + if(!is_deployed() || !summoner || (!COOLDOWN_FINISHED(src, manifest_cooldown) && !forced) || locked) + return FALSE + new /obj/effect/temp_visual/guardian/phase/out(loc) + forceMove(summoner) + COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) + recall_effects() + return TRUE +/mob/living/simple_animal/hostile/guardian/proc/manifest_effects() + return -/mob/living/simple_animal/hostile/guardian/proc/ToggleLight() +/mob/living/simple_animal/hostile/guardian/proc/recall_effects() + return + +/mob/living/simple_animal/hostile/guardian/proc/toggle_modes() + to_chat(src, span_bolddanger("You don't have another mode!")) + +/mob/living/simple_animal/hostile/guardian/proc/toggle_light() if(!light_on) to_chat(src, span_notice("You activate your light.")) set_light_on(TRUE) @@ -420,7 +525,7 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians set_light_on(FALSE) -/mob/living/simple_animal/hostile/guardian/verb/ShowType() +/mob/living/simple_animal/hostile/guardian/verb/check_type() set name = "Check Guardian Type" set category = "Guardian" set desc = "Check what type you are." @@ -428,25 +533,26 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians //COMMUNICATION -/mob/living/simple_animal/hostile/guardian/proc/Communicate() - if(summoner) - var/sender_key = key - var/input = tgui_input_text(src, "Enter a message to tell your summoner", "Guardian") - if(sender_key != key || !input) //guardian got reset, or did not enter anything - return +/mob/living/simple_animal/hostile/guardian/proc/communicate() + if(!summoner) + return + var/sender_key = key + var/input = tgui_input_text(src, "Enter a message to tell your summoner", "Guardian") + if(sender_key != key || !input) //guardian got reset, or did not enter anything + return - var/preliminary_message = "[input]" //apply basic color/bolding - var/my_message = "[src]: [preliminary_message]" //add source, color source with the guardian's color + var/preliminary_message = span_boldholoparasite("[input]") //apply basic color/bolding + var/my_message = "[src]: [preliminary_message]" //add source, color source with the guardian's color - to_chat(summoner, "[my_message]") - var/list/guardians = summoner.get_all_linked_holoparasites() - for(var/para in guardians) - to_chat(para, "[my_message]") - for(var/M in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [my_message]") + to_chat(summoner, "[my_message]") + var/list/guardians = summoner.get_all_linked_holoparasites() + for(var/guardian in guardians) + to_chat(guardian, "[my_message]") + for(var/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, src) + to_chat(dead_mob, "[link] [my_message]") - src.log_talk(input, LOG_SAY, tag="guardian") + src.log_talk(input, LOG_SAY, tag="guardian") /mob/living/proc/guardian_comm() set name = "Communicate" @@ -456,17 +562,16 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians if(!input) return - var/preliminary_message = "[input]" //apply basic color/bolding - var/my_message = "[src]: [preliminary_message]" //add source, color source with default grey... + var/preliminary_message = span_boldholoparasite("[input]") //apply basic color/bolding + var/my_message = span_boldholoparasite("[src]: [preliminary_message]") //add source, color source with default grey... to_chat(src, "[my_message]") var/list/guardians = get_all_linked_holoparasites() - for(var/para in guardians) - var/mob/living/simple_animal/hostile/guardian/G = para - to_chat(G, "[src]: [preliminary_message]" ) - for(var/M in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [my_message]") + for(var/mob/living/simple_animal/hostile/guardian/guardian as anything in guardians) + to_chat(guardian, "[src]: [preliminary_message]" ) + for(var/dead_mob in GLOB.dead_mob_list) + var/link = FOLLOW_LINK(dead_mob, src) + to_chat(dead_mob, "[link] [my_message]") src.log_talk(input, LOG_SAY, tag="guardian") @@ -477,56 +582,39 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians set category = "Guardian" set desc = "Forcibly recall your guardian." var/list/guardians = get_all_linked_holoparasites() - for(var/para in guardians) - var/mob/living/simple_animal/hostile/guardian/G = para - G.Recall() + for(var/mob/living/simple_animal/hostile/guardian/guardian in guardians) + guardian.recall() /mob/living/proc/guardian_reset() - set name = "Reset Guardian Player (One Use)" + set name = "Reset Guardian Player (5 Minute Cooldown)" set category = "Guardian" - set desc = "Re-rolls which ghost will control your Guardian. One use per Guardian." + set desc = "Re-rolls which ghost will control your Guardian. Can be used once per 5 minutes." var/list/guardians = get_all_linked_holoparasites() - for(var/para in guardians) - var/mob/living/simple_animal/hostile/guardian/P = para - if(P.reset) - guardians -= P //clear out guardians that are already reset - if(!length(guardians)) - remove_verb(src, /mob/living/proc/guardian_reset) - return + for(var/mob/living/simple_animal/hostile/guardian/resetting_guardian as anything in guardians) + if(!COOLDOWN_FINISHED(resetting_guardian, resetting_cooldown)) + guardians -= resetting_guardian //clear out guardians that are already reset var/mob/living/simple_animal/hostile/guardian/chosen_guardian = tgui_input_list(src, "Pick the guardian you wish to reset", "Guardian Reset", sort_names(guardians)) if(isnull(chosen_guardian)) to_chat(src, span_holoparasite("You decide not to reset [length(guardians) > 1 ? "any of your guardians":"your guardian"].")) return - to_chat(src, span_holoparasite("You attempt to reset [chosen_guardian.real_name]'s personality...")) - var/list/mob/dead/observer/ghost_candidates = poll_ghost_candidates("Do you want to play as [src.real_name]'s [chosen_guardian.real_name]?", ROLE_PAI, FALSE, 100) + to_chat(src, span_holoparasite("You attempt to reset [chosen_guardian.real_name]'s personality...")) + var/list/mob/dead/observer/ghost_candidates = poll_ghost_candidates("Do you want to play as [src.real_name]'s Guardian Spirit?", ROLE_PAI, FALSE, 100) if(!LAZYLEN(ghost_candidates)) - to_chat(src, span_holoparasite("There were no ghosts willing to take control of [chosen_guardian.real_name]. Looks like you're stuck with it for now.")) + to_chat(src, span_holoparasite("There were no ghosts willing to take control of [chosen_guardian.real_name]. Looks like you're stuck with it for now.")) return var/mob/dead/observer/candidate = pick(ghost_candidates) to_chat(chosen_guardian, span_holoparasite("Your user reset you, and your body was taken over by a ghost. Looks like they weren't happy with your performance.")) - to_chat(src, "Your [chosen_guardian.real_name] has been successfully reset.") + to_chat(src, span_boldholoparasite("Your [chosen_guardian.real_name] has been successfully reset.")) message_admins("[key_name_admin(candidate)] has taken control of ([ADMIN_LOOKUPFLW(chosen_guardian)])") chosen_guardian.ghostize(FALSE) - chosen_guardian.guardianrecolor() - chosen_guardian.guardianrename() //give it a new color and name, to show it's a new person chosen_guardian.key = candidate.key - chosen_guardian.reset = 1 - switch(chosen_guardian.theme) - if("tech") - to_chat(src, span_holoparasite("[chosen_guardian.real_name] is now online!")) - if("magic") - to_chat(src, span_holoparasite("[chosen_guardian.real_name] has been summoned!")) - if("carp") - to_chat(src, span_holoparasite("[chosen_guardian.real_name] has been caught!")) - if("miner") - to_chat(src, span_holoparasite("[chosen_guardian.real_name] has appeared!")) - guardians -= chosen_guardian - if(!length(guardians)) - remove_verb(src, /mob/living/proc/guardian_reset) + COOLDOWN_START(chosen_guardian, resetting_cooldown, 5 MINUTES) + chosen_guardian.guardian_rename() //give it a new color and name, to show it's a new person + chosen_guardian.guardian_recolor() ////////parasite tracking/finding procs @@ -543,265 +631,3 @@ GLOBAL_LIST_EMPTY(parasites) //all currently existing/living guardians /// Returns true if this holoparasite has the same summoner as the passed holoparasite. /mob/living/simple_animal/hostile/guardian/proc/hasmatchingsummoner(mob/living/simple_animal/hostile/guardian/other_guardian) return istype(other_guardian) && other_guardian.summoner == summoner - -////////Creation - -/obj/item/guardiancreator - name = "enchanted deck of tarot cards" - desc = "An enchanted deck of tarot cards, rumored to be a source of unimaginable power." - icon = 'icons/obj/toys/playing_cards.dmi' - icon_state = "deck_tarot_full" - var/used = FALSE - var/theme = "magic" - var/mob_name = "Guardian Spirit" - var/use_message = "You shuffle the deck..." - var/used_message = "All the cards seem to be blank now." - var/failure_message = "..And draw a card! It's...blank? Maybe you should try again later." - var/ling_failure = "The deck refuses to respond to a souless creature such as you." - var/list/possible_guardians = list("Assassin", "Charger", "Explosive", "Gaseous", "Lightning", "Protector", "Ranged", "Standard", "Support", "Gravitokinetic") - var/random = TRUE - var/allowmultiple = FALSE - var/allowling = TRUE - var/allowguardian = FALSE - var/datum/antagonist/antag_datum_to_give - -/obj/item/guardiancreator/attack_self(mob/living/user) - if(isguardian(user) && !allowguardian) - to_chat(user, span_holoparasite("[mob_name] chains are not allowed.")) - return - var/list/guardians = user.get_all_linked_holoparasites() - if(length(guardians) && !allowmultiple) - to_chat(user, span_holoparasite("You already have a [mob_name]!")) - return - if(user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling) && !allowling) - to_chat(user, "[ling_failure]") - return - if(used == TRUE) - to_chat(user, "[used_message]") - return - used = TRUE - to_chat(user, "[use_message]") - var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as the [mob_name] of [user.real_name]?", ROLE_PAI, FALSE, 100, POLL_IGNORE_HOLOPARASITE) - - if(LAZYLEN(candidates)) - var/mob/dead/observer/candidate = pick(candidates) - spawn_guardian(user, candidate) - else - to_chat(user, "[failure_message]") - used = FALSE - - -/obj/item/guardiancreator/proc/spawn_guardian(mob/living/user, mob/dead/candidate) - var/guardiantype = "Standard" - if(random) - guardiantype = pick(possible_guardians) - else - guardiantype = tgui_input_list(user, "Pick the type of [mob_name]", "Guardian Creation", sort_list(possible_guardians)) - if(isnull(guardiantype) || !candidate.client) - to_chat(user, "[failure_message]" ) - used = FALSE - return - var/pickedtype = /mob/living/simple_animal/hostile/guardian/punch - switch(guardiantype) - - if("Standard") - pickedtype = /mob/living/simple_animal/hostile/guardian/punch - - if("Ranged") - pickedtype = /mob/living/simple_animal/hostile/guardian/ranged - - if("Support") - pickedtype = /mob/living/simple_animal/hostile/guardian/healer - - if("Explosive") - pickedtype = /mob/living/simple_animal/hostile/guardian/bomb - - if("Lightning") - pickedtype = /mob/living/simple_animal/hostile/guardian/beam - - if("Protector") - pickedtype = /mob/living/simple_animal/hostile/guardian/protector - - if("Charger") - pickedtype = /mob/living/simple_animal/hostile/guardian/charger - - if("Assassin") - pickedtype = /mob/living/simple_animal/hostile/guardian/assassin - - if("Dextrous") - pickedtype = /mob/living/simple_animal/hostile/guardian/dextrous - - if("Gravitokinetic") - pickedtype = /mob/living/simple_animal/hostile/guardian/gravitokinetic - - if("Gaseous") - pickedtype = /mob/living/simple_animal/hostile/guardian/gaseous - - var/list/guardians = user.get_all_linked_holoparasites() - if(length(guardians) && !allowmultiple) - to_chat(user, span_holoparasite("You already have a [mob_name]!") ) - used = FALSE - return - var/mob/living/simple_animal/hostile/guardian/G = new pickedtype(user, theme) - G.name = mob_name - G.set_summoner(user) - G.key = candidate.key - G.mind.enslave_mind_to_creator(user) - G.copy_languages(user, LANGUAGE_MASTER) // make sure holoparasites speak same language as master - G.update_atom_languages() - user.log_message("has summoned [key_name(G)], a [guardiantype] holoparasite.", LOG_GAME) - G.log_message("was summoned as a [guardiantype] holoparsite.", LOG_GAME) - switch(theme) - if("tech") - to_chat(user, "[G.tech_fluff_string]") - to_chat(user, span_holoparasite("[G.real_name] is now online!")) - if("magic") - to_chat(user, "[G.magic_fluff_string]") - to_chat(user, span_holoparasite("[G.real_name] has been summoned!")) - if("carp") - to_chat(user, "[G.carp_fluff_string]") - to_chat(user, span_holoparasite("[G.real_name] has been caught!")) - if("miner") - to_chat(user, "[G.miner_fluff_string]") - to_chat(user, span_holoparasite("[G.real_name] has appeared!")) - add_verb(user, list(/mob/living/proc/guardian_comm, \ - /mob/living/proc/guardian_recall, \ - /mob/living/proc/guardian_reset)) - G?.client.init_verbs() - return G - -/obj/item/guardiancreator/choose - random = FALSE - -/obj/item/guardiancreator/choose/dextrous - possible_guardians = list("Assassin", "Charger", "Dextrous", "Explosive", "Gaseous", "Lightning", "Protector", "Ranged", "Standard", "Support", "Gravitokinetic") - -/obj/item/guardiancreator/choose/wizard - possible_guardians = list("Assassin", "Charger", "Dextrous", "Explosive", "Gaseous", "Lightning", "Protector", "Ranged", "Standard", "Gravitokinetic") - allowmultiple = TRUE - -/obj/item/guardiancreator/choose/wizard/spawn_guardian(mob/living/user, mob/dead/candidate) - . = ..() - var/mob/guardian = . - if(!guardian) - return - - var/datum/antagonist/wizard/antag_datum = user.mind.has_antag_datum(/datum/antagonist/wizard) - if(antag_datum) - if(!antag_datum.wiz_team) - antag_datum.create_wiz_team() - guardian.mind.add_antag_datum(/datum/antagonist/wizard_minion, antag_datum.wiz_team) - -/obj/item/guardiancreator/tech - name = "holoparasite injector" - desc = "It contains an alien nanoswarm of unknown origin. Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, it requires an organic host as a home base and source of fuel." - icon = 'icons/obj/medical/syringe.dmi' - icon_state = "combat_hypo" - theme = "tech" - mob_name = "Holoparasite" - use_message = "You start to power on the injector..." - used_message = "The injector has already been used." - failure_message = "...ERROR. BOOT SEQUENCE ABORTED. AI FAILED TO INTIALIZE. PLEASE CONTACT SUPPORT OR TRY AGAIN LATER." - ling_failure = "The holoparasites recoil in horror. They want nothing to do with a creature like you." - -/obj/item/guardiancreator/tech/choose/traitor - possible_guardians = list("Assassin", "Charger", "Explosive", "Gaseous", "Lightning", "Protector", "Ranged", "Standard", "Support", "Gravitokinetic") - allowling = FALSE - -/obj/item/guardiancreator/tech/choose - random = FALSE - -/obj/item/guardiancreator/tech/choose/dextrous - possible_guardians = list("Assassin", "Charger", "Dextrous", "Explosive", "Gaseous", "Lightning", "Protector", "Ranged", "Standard", "Support", "Gravitokinetic") - -/obj/item/paper/guides/antag/guardian - name = "Holoparasite Guide" - default_raw_text = {"A list of Holoparasite Types
- -
-Assassin: Does medium damage and takes full damage, but can enter stealth, causing its next attack to do massive damage and ignore armor. However, it becomes briefly unable to recall after attacking from stealth.
-
-Charger: Moves extremely fast, does medium damage on attack, and can charge at targets, damaging the first target hit and forcing them to drop any items they are holding.
-
-Explosive: High damage resist and medium power attack that may explosively teleport targets. Can turn any object, including objects too large to pick up, into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered or after a delay.
-
-Gaseous: Creates sparks on touch and continuously expels a gas of its choice. Automatically extinguishes the user if they catch on fire.
-
-Lightning: Attacks apply lightning chains to targets. Has a lightning chain to the user. Lightning chains shock everything near them, doing constant damage.
-
-Protector: Causes you to teleport to it when out of range, unlike other parasites. Has two modes; Combat, where it does and takes medium damage, and Protection, where it does and takes almost no damage but moves slightly slower.
-
-Ranged: Has two modes. Ranged; which fires a constant stream of weak, armor-ignoring projectiles. Scout; Cannot attack, but can move through walls and is quite hard to see. Can lay surveillance snares, which alert it when crossed, in either mode.
-
-Standard: Devastating close combat attacks and high damage resist. Can smash through weak walls.
-
-Gravitokinetic: Attacks will apply crushing gravity to the target. Can target the ground as well to slow targets advancing on you, but this will affect the user.
-
-"} - -/obj/item/paper/guides/antag/guardian/wizard - name = "Guardian Guide" - default_raw_text = {"A list of Guardian Types
- -
-Assassin: Does medium damage and takes full damage, but can enter stealth, causing its next attack to do massive damage and ignore armor. However, it becomes briefly unable to recall after attacking from stealth.
-
-Charger: Moves extremely fast, does medium damage on attack, and can charge at targets, damaging the first target hit and forcing them to drop any items they are holding.
-
-Dexterous: Does low damage on attack, but is capable of holding items and storing a single item within it. It will drop items held in its hands when it recalls, but it will retain the stored item.
-
-Explosive: High damage resist and medium power attack that may explosively teleport targets. Can turn any object, including objects too large to pick up, into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered or after a delay.
-
-Gaseous: Creates sparks on touch and continuously expels a gas of its choice. Automatically extinguishes the user if they catch on fire.
-
-Lightning: Attacks apply lightning chains to targets. Has a lightning chain to the user. Lightning chains shock everything near them, doing constant damage.
-
-Protector: Causes you to teleport to it when out of range, unlike other parasites. Has two modes; Combat, where it does and takes medium damage, and Protection, where it does and takes almost no damage but moves slightly slower.
-
-Ranged: Has two modes. Ranged; which fires a constant stream of weak, armor-ignoring projectiles. Scout; Cannot attack, but can move through walls and is quite hard to see. Can lay surveillance snares, which alert it when crossed, in either mode.
-
-Standard: Devastating close combat attacks and high damage resist. Can smash through weak walls.
-
-Gravitokinetic: Attacks will apply crushing gravity to the target. Can target the ground as well to slow targets advancing on you, but this will affect the user.
-
-"} - - -/obj/item/storage/box/syndie_kit/guardian - name = "holoparasite injector kit" - -/obj/item/storage/box/syndie_kit/guardian/PopulateContents() - new /obj/item/guardiancreator/tech/choose/traitor(src) - new /obj/item/paper/guides/antag/guardian(src) - -/obj/item/guardiancreator/carp - name = "holocarp fishsticks" - desc = "Using the power of Carp'sie, you can catch a carp from byond the veil of Carpthulu, and bind it to your fleshy flesh form." - icon = 'icons/obj/food/meat.dmi' - icon_state = "fishfingers" - theme = "carp" - mob_name = "Holocarp" - use_message = "You put the fishsticks in your mouth..." - used_message = "Someone's already taken a bite out of these fishsticks! Ew." - failure_message = "You couldn't catch any carp spirits from the seas of Lake Carp. Maybe there are none, maybe you fucked up." - ling_failure = "Carp'sie seems to not have taken you as the chosen one. Maybe it's because of your horrifying origin." - allowmultiple = TRUE - -/obj/item/guardiancreator/carp/choose - random = FALSE - -/obj/item/guardiancreator/miner - name = "dusty shard" - desc = "Seems to be a very old rock, may have originated from a strange meteor." - icon = 'icons/obj/lavaland/artefacts.dmi' - icon_state = "dustyshard" - theme = "miner" - mob_name = "Power Miner" - use_message = "You pierce your skin with the shard..." - used_message = "This shard seems to have lost all its' power..." - failure_message = "The shard hasn't reacted at all. Maybe try again later..." - ling_failure = "The power of the shard seems to not react with your horrifying, mutated body." - possible_guardians = list("Charger", "Protector", "Ranged", "Standard", "Support") - -/obj/item/guardiancreator/miner/choose - random = FALSE diff --git a/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm b/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm new file mode 100644 index 00000000000..a94fa7fd522 --- /dev/null +++ b/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm @@ -0,0 +1,211 @@ +GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial()) + +/proc/setup_guardian_radial() + . = list() + for(var/mob/living/simple_animal/hostile/guardian/guardian_path as anything in subtypesof(/mob/living/simple_animal/hostile/guardian)) + var/datum/radial_menu_choice/option = new() + option.name = initial(guardian_path.creator_name) + option.image = image(icon = 'icons/hud/guardian.dmi', icon_state = initial(guardian_path.creator_icon)) + option.info = span_boldnotice(initial(guardian_path.creator_desc)) + .[guardian_path] = option + +/obj/item/guardiancreator + name = "enchanted deck of tarot cards" + desc = "An enchanted deck of tarot cards, rumored to be a source of unimaginable power." + icon = 'icons/obj/toys/playing_cards.dmi' + icon_state = "deck_tarot_full" + /// Are we used or in the process of being used? If yes, then we can't be used. + var/used = FALSE + /// The visuals we give to the guardian we spawn. + var/theme = GUARDIAN_THEME_MAGIC + /// The name of the guardian, for UI/message stuff. + var/mob_name = "Guardian Spirit" + /// Message sent when you use it. + var/use_message = span_holoparasite("You shuffle the deck...") + /// Message sent when it's already used. + var/used_message = span_holoparasite("All the cards seem to be blank now.") + /// Failure message if no ghost picks the holopara. + var/failure_message = span_boldholoparasite("..And draw a card! It's... blank? Maybe you should try again later.") + /// Failure message if we don't allow lings. + var/ling_failure = span_boldholoparasite("The deck refuses to respond to a souless creature such as you.") + /// Message sent if we successfully get a guardian. + var/success_message = span_holoparasite("%GUARDIAN has been summoned!") + /// If true, you are given a random guardian rather than picking from a selection. + var/random = TRUE + /// If true, you can have multiple guardians at the same time. + var/allowmultiple = FALSE + /// If true, lings can get guardians from this. + var/allowling = TRUE + /// If true, a dextrous guardian can get their own guardian, infinite chain! + var/allowguardian = FALSE + /// List of all the guardians this type can spawn. + var/list/possible_guardians = list( //default, has everything but dextrous + /mob/living/simple_animal/hostile/guardian/assassin, + /mob/living/simple_animal/hostile/guardian/charger, + /mob/living/simple_animal/hostile/guardian/explosive, + /mob/living/simple_animal/hostile/guardian/gaseous, + /mob/living/simple_animal/hostile/guardian/gravitokinetic, + /mob/living/simple_animal/hostile/guardian/lightning, + /mob/living/simple_animal/hostile/guardian/protector, + /mob/living/simple_animal/hostile/guardian/ranged, + /mob/living/simple_animal/hostile/guardian/standard, + /mob/living/simple_animal/hostile/guardian/support, + ) + +/obj/item/guardiancreator/attack_self(mob/living/user) + if(isguardian(user) && !allowguardian) + to_chat(user, span_holoparasite("[mob_name] chains are not allowed.")) + return + var/list/guardians = user.get_all_linked_holoparasites() + if(length(guardians) && !allowmultiple) + to_chat(user, span_holoparasite("You already have a [mob_name]!")) + return + if(user.mind && user.mind.has_antag_datum(/datum/antagonist/changeling) && !allowling) + to_chat(user, ling_failure) + return + if(used) + to_chat(user, used_message) + return + var/list/radial_options = GLOB.guardian_radial_images.Copy() + for(var/possible_guardian in radial_options) + if(possible_guardian in possible_guardians) + continue + radial_options -= possible_guardian + var/mob/living/simple_animal/hostile/guardian/guardian_path + if(random) + guardian_path = pick(possible_guardians) + else + guardian_path = show_radial_menu(user, src, radial_options, custom_check = CALLBACK(src, PROC_REF(check_menu), user), radius = 42, require_near = TRUE) + if(!guardian_path) + return + used = TRUE + to_chat(user, use_message) + var/guardian_type_name = "a random" + if(!random) + guardian_type_name = "the " + lowertext(initial(guardian_path.creator_name)) + var/list/mob/dead/observer/candidates = poll_ghost_candidates("Do you want to play as [guardian_type_name] [mob_name] of [user.real_name]?", ROLE_PAI, FALSE, 100, POLL_IGNORE_HOLOPARASITE) + if(LAZYLEN(candidates)) + var/mob/dead/observer/candidate = pick(candidates) + spawn_guardian(user, candidate, guardian_path) + else + to_chat(user, failure_message) + used = FALSE + +/obj/item/guardiancreator/proc/spawn_guardian(mob/living/user, mob/dead/candidate, guardian_path) + var/list/guardians = user.get_all_linked_holoparasites() + if(length(guardians) && !allowmultiple) + to_chat(user, span_holoparasite("You already have a [mob_name]!") ) + used = FALSE + return + var/mob/living/simple_animal/hostile/guardian/summoned_guardian = new guardian_path(user, theme) + summoned_guardian.set_summoner(user, different_person = TRUE) + summoned_guardian.key = candidate.key + user.log_message("has summoned [key_name(summoned_guardian)], a [summoned_guardian.creator_name] holoparasite.", LOG_GAME) + summoned_guardian.log_message("was summoned as a [summoned_guardian.creator_name] holoparasite.", LOG_GAME) + to_chat(user, summoned_guardian.used_fluff_string) + to_chat(user, replacetext(success_message, "%GUARDIAN", mob_name)) + summoned_guardian.client?.init_verbs() + return summoned_guardian + +/obj/item/guardiancreator/proc/check_menu(mob/living/user) + if(!istype(user)) + return FALSE + if(user.incapacitated() || !user.is_holding(src) || used) + return FALSE + return TRUE + +/obj/item/guardiancreator/choose + random = FALSE + +/obj/item/guardiancreator/choose/all/Initialize(mapload) + . = ..() + possible_guardians = subtypesof(/mob/living/simple_animal/hostile/guardian) + +/obj/item/guardiancreator/choose/wizard + allowmultiple = TRUE + possible_guardians = list( //no support, but dextrous + /mob/living/simple_animal/hostile/guardian/assassin, + /mob/living/simple_animal/hostile/guardian/charger, + /mob/living/simple_animal/hostile/guardian/dextrous, + /mob/living/simple_animal/hostile/guardian/explosive, + /mob/living/simple_animal/hostile/guardian/gaseous, + /mob/living/simple_animal/hostile/guardian/gravitokinetic, + /mob/living/simple_animal/hostile/guardian/lightning, + /mob/living/simple_animal/hostile/guardian/protector, + /mob/living/simple_animal/hostile/guardian/ranged, + /mob/living/simple_animal/hostile/guardian/standard, + ) + +/obj/item/guardiancreator/choose/wizard/spawn_guardian(mob/living/user, mob/dead/candidate) + . = ..() + var/mob/guardian = . + if(!guardian) + return + var/datum/antagonist/wizard/antag_datum = user.mind.has_antag_datum(/datum/antagonist/wizard) + if(antag_datum) + if(!antag_datum.wiz_team) + antag_datum.create_wiz_team() + guardian.mind.add_antag_datum(/datum/antagonist/wizard_minion, antag_datum.wiz_team) + +/obj/item/guardiancreator/tech + name = "holoparasite injector" + desc = "It contains an alien nanoswarm of unknown origin. Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, it requires an organic host as a home base and source of fuel." + icon = 'icons/obj/medical/syringe.dmi' + icon_state = "combat_hypo" + theme = GUARDIAN_THEME_TECH + mob_name = "Holoparasite" + use_message = span_holoparasite("You start to power on the injector...") + used_message = span_holoparasite("The injector has already been used.") + failure_message = span_boldholoparasite("...ERROR. BOOT SEQUENCE ABORTED. AI FAILED TO INTIALIZE. PLEASE CONTACT SUPPORT OR TRY AGAIN LATER.") + ling_failure = span_boldholoparasite("The holoparasites recoil in horror. They want nothing to do with a creature like you.") + success_message = span_holoparasite("%GUARDIAN is now online!") + +/obj/item/guardiancreator/tech/choose + random = FALSE + +/obj/item/guardiancreator/tech/choose/all/Initialize(mapload) + . = ..() + possible_guardians = subtypesof(/mob/living/simple_animal/hostile/guardian) + +/obj/item/guardiancreator/tech/choose/traitor + allowling = FALSE + +/obj/item/guardiancreator/carp + name = "holocarp fishsticks" + desc = "Using the power of Carp'sie, you can catch a carp from byond the veil of Carpthulu, and bind it to your fleshy flesh form." + icon = 'icons/obj/food/meat.dmi' + icon_state = "fishfingers" + theme = GUARDIAN_THEME_CARP + mob_name = "Holocarp" + use_message = span_holoparasite("You put the fishsticks in your mouth...") + used_message = span_holoparasite("Someone's already taken a bite out of these fishsticks! Ew.") + failure_message = span_boldholoparasite("You couldn't catch any carp spirits from the seas of Lake Carp. Maybe there are none, maybe you fucked up.") + ling_failure = span_boldholoparasite("Carp'sie seems to not have taken you as the chosen one. Maybe it's because of your horrifying origin.") + success_message = span_holoparasite("%GUARDIAN has been caught!") + allowmultiple = TRUE + +/obj/item/guardiancreator/carp/choose + random = FALSE + +/obj/item/guardiancreator/miner + name = "dusty shard" + desc = "Seems to be a very old rock, may have originated from a strange meteor." + icon = 'icons/obj/lavaland/artefacts.dmi' + icon_state = "dustyshard" + theme = GUARDIAN_THEME_MINER + mob_name = "Power Miner" + use_message = span_holoparasite("You pierce your skin with the shard...") + used_message = span_holoparasite("This shard seems to have lost all its power...") + failure_message = span_boldholoparasite("The shard hasn't reacted at all. Maybe try again later...") + ling_failure = span_boldholoparasite("The power of the shard seems to not react with your horrifying, mutated body.") + success_message = span_holoparasite("%GUARDIAN has appeared!") + possible_guardians = list( //limited to ones useful on lavaland + /mob/living/simple_animal/hostile/guardian/charger, + /mob/living/simple_animal/hostile/guardian/protector, + /mob/living/simple_animal/hostile/guardian/ranged, + /mob/living/simple_animal/hostile/guardian/standard, + /mob/living/simple_animal/hostile/guardian/support, + ) + +/obj/item/guardiancreator/miner/choose + random = FALSE diff --git a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm b/code/modules/mob/living/simple_animal/guardian/types/assassin.dm index 4518df42ccf..e9b218d7805 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/assassin.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/assassin.dm @@ -7,95 +7,99 @@ attack_sound = 'sound/weapons/bladeslice.ogg' attack_vis_effect = ATTACK_EFFECT_SLASH damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) - playstyle_string = "As an assassin type you do medium damage and have no damage resistance, but can enter stealth, massively increasing the damage of your next attack and causing it to ignore armor. Stealth is broken when you attack or take damage." - magic_fluff_string = "..And draw the Space Ninja, a lethal, invisible assassin." - tech_fluff_string = "Boot sequence complete. Assassin modules loaded. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! Caught one! It's an assassin carp! Just when you thought it was safe to go back to the water... which is unhelpful, because we're in space." - miner_fluff_string = "You encounter... Glass, a sharp, fragile attacker." + playstyle_string = span_holoparasite("As an assassin type you do medium damage and have no damage resistance, but can enter stealth, massively increasing the damage of your next attack and causing it to ignore armor. Stealth is broken when you attack or take damage.") + magic_fluff_string = span_holoparasite("..And draw the Space Ninja, a lethal, invisible assassin.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Assassin modules loaded. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's an assassin carp! Just when you thought it was safe to go back to the water... which is unhelpful, because we're in space.") + miner_fluff_string = span_holoparasite("You encounter... Glass, a sharp, fragile attacker.") + creator_name = "Assassin" + creator_desc = "Does medium damage and takes full damage, but can enter stealth, causing its next attack to do massive damage and ignore armor. However, it becomes briefly unable to recall after attacking from stealth." + creator_icon = "assassin" toggle_button_type = /atom/movable/screen/guardian/toggle_mode/assassin + /// Is it in stealth mode? var/toggle = FALSE - var/stealthcooldown = 160 + /// Time between going in stealth. + var/stealth_cooldown_time = 16 SECONDS + /// Damage added in stealth mode. + var/damage_bonus = 35 + /// Screen alert given when we are able to stealth. var/atom/movable/screen/alert/canstealthalert + /// Screen alert given when we are in stealth. var/atom/movable/screen/alert/instealthalert - -/mob/living/simple_animal/hostile/guardian/assassin/Initialize(mapload) - . = ..() - stealthcooldown = 0 + /// Cooldown for the stealth toggle. + COOLDOWN_DECLARE(stealth_cooldown) /mob/living/simple_animal/hostile/guardian/assassin/Life(delta_time = SSMOBS_DT, times_fired) . = ..() updatestealthalert() - if(loc == summoner && toggle) - ToggleMode(0) /mob/living/simple_animal/hostile/guardian/assassin/get_status_tab_items() . = ..() - if(stealthcooldown >= world.time) - . += "Stealth Cooldown Remaining: [DisplayTimeText(stealthcooldown - world.time)]" + if(!COOLDOWN_FINISHED(src, stealth_cooldown)) + . += "Stealth Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, stealth_cooldown))]" -/mob/living/simple_animal/hostile/guardian/assassin/AttackingTarget() +/mob/living/simple_animal/hostile/guardian/assassin/AttackingTarget(atom/attacked_target) . = ..() if(.) if(toggle && (isliving(target) || istype(target, /obj/structure/window) || istype(target, /obj/structure/grille))) - ToggleMode(1) + toggle_modes(forced = TRUE) /mob/living/simple_animal/hostile/guardian/assassin/adjustHealth(amount, updating_health = TRUE, forced = FALSE) . = ..() if(. > 0 && toggle) - ToggleMode(1) + toggle_modes(forced = TRUE) -/mob/living/simple_animal/hostile/guardian/assassin/Recall() - if(..() && toggle) - ToggleMode(0) - -/mob/living/simple_animal/hostile/guardian/assassin/ToggleMode(forced = 0) +/mob/living/simple_animal/hostile/guardian/assassin/recall_effects() if(toggle) - melee_damage_lower = initial(melee_damage_lower) - melee_damage_upper = initial(melee_damage_upper) + toggle_modes(forced = TRUE) + +/mob/living/simple_animal/hostile/guardian/assassin/toggle_modes(forced = FALSE) + if(toggle) + melee_damage_lower -= damage_bonus + melee_damage_upper -= damage_bonus armour_penetration = initial(armour_penetration) obj_damage = initial(obj_damage) environment_smash = initial(environment_smash) alpha = initial(alpha) if(!forced) - to_chat(src, "[span_danger("You exit stealth.")]") + to_chat(src, span_bolddanger("You exit stealth.")) else visible_message(span_danger("\The [src] suddenly appears!")) - stealthcooldown = world.time + initial(stealthcooldown) //we were forced out of stealth and go on cooldown - cooldown = world.time + 40 //can't recall for 4 seconds + COOLDOWN_START(src, stealth_cooldown, stealth_cooldown_time) //we were forced out of stealth and go on cooldown + COOLDOWN_START(src, manifest_cooldown, 4 SECONDS) //can't recall for 4 seconds updatestealthalert() toggle = FALSE - else if(stealthcooldown <= world.time) - if(src.loc == summoner) - to_chat(src, "[span_danger("You have to be manifested to enter stealth!")]") + else if(COOLDOWN_FINISHED(src, stealth_cooldown)) + if(!is_deployed()) + to_chat(src, span_bolddanger("You have to be manifested to enter stealth!")) return - melee_damage_lower = 50 - melee_damage_upper = 50 + melee_damage_lower += damage_bonus + melee_damage_upper += damage_bonus armour_penetration = 100 obj_damage = 0 environment_smash = ENVIRONMENT_SMASH_NONE new /obj/effect/temp_visual/guardian/phase/out(get_turf(src)) alpha = 15 if(!forced) - to_chat(src, "[span_danger("You enter stealth, empowering your next attack.")]") + to_chat(src, span_bolddanger("You enter stealth, empowering your next attack.")) updatestealthalert() toggle = TRUE else if(!forced) - to_chat(src, "[span_danger("You cannot yet enter stealth, wait another [DisplayTimeText(stealthcooldown - world.time)]!")]") + to_chat(src, span_bolddanger("You cannot yet enter stealth, wait another [DisplayTimeText(COOLDOWN_TIMELEFT(src, stealth_cooldown))]!")) /mob/living/simple_animal/hostile/guardian/assassin/proc/updatestealthalert() - if(stealthcooldown <= world.time) - if(toggle) - if(!instealthalert) - instealthalert = throw_alert("instealth", /atom/movable/screen/alert/instealth) - clear_alert("canstealth") - canstealthalert = null - else - if(!canstealthalert) - canstealthalert = throw_alert("canstealth", /atom/movable/screen/alert/canstealth) - clear_alert("instealth") - instealthalert = null - else + if(!COOLDOWN_FINISHED(src, stealth_cooldown)) clear_alert("instealth") instealthalert = null clear_alert("canstealth") canstealthalert = null + return + if(toggle && !instealthalert) + instealthalert = throw_alert("instealth", /atom/movable/screen/alert/instealth) + clear_alert("canstealth") + canstealthalert = null + else if(!toggle && !canstealthalert) + canstealthalert = throw_alert("canstealth", /atom/movable/screen/alert/canstealth) + clear_alert("instealth") + instealthalert = null + diff --git a/code/modules/mob/living/simple_animal/guardian/types/charger.dm b/code/modules/mob/living/simple_animal/guardian/types/charger.dm index 8da5a278948..2d61e4154cd 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/charger.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/charger.dm @@ -2,73 +2,70 @@ /mob/living/simple_animal/hostile/guardian/charger melee_damage_lower = 15 melee_damage_upper = 15 - ranged = 1 //technically + ranged = TRUE //technically ranged_message = "charges" - ranged_cooldown_time = 40 + ranged_cooldown_time = 4 SECONDS speed = -1 damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6) - playstyle_string = "As a charger type you do medium damage, have medium damage resistance, move very fast, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding." - magic_fluff_string = "..And draw the Hunter, an alien master of rapid assault." - tech_fluff_string = "Boot sequence complete. Charge modules loaded. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! Caught one! It's a charger carp, that likes running at people. But it doesn't have any legs..." - miner_fluff_string = "You encounter... Titanium, a lightweight, agile fighter." - var/charging = 0 + playstyle_string = span_holoparasite("As a charger type you do medium damage, have medium damage resistance, move very fast, and can charge at a location, damaging any target hit and forcing them to drop any items they are holding.") + magic_fluff_string = span_holoparasite("..And draw the Hunter, an alien master of rapid assault.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Charge modules loaded. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's a charger carp, that likes running at people. But it doesn't have any legs...") + miner_fluff_string = span_holoparasite("You encounter... Titanium, a lightweight, agile fighter.") + creator_name = "Charger" + creator_desc = "Moves extremely fast, does medium damage on attack, and can charge at targets, damaging the first target hit and forcing them to drop any items they are holding." + creator_icon = "charger" + var/charging = FALSE var/atom/movable/screen/alert/chargealert /mob/living/simple_animal/hostile/guardian/charger/Life(delta_time = SSMOBS_DT, times_fired) . = ..() - if(ranged_cooldown <= world.time) + if(!COOLDOWN_FINISHED(src, ranged_cooldown)) if(!chargealert) chargealert = throw_alert(ALERT_CHARGE, /atom/movable/screen/alert/cancharge) else clear_alert(ALERT_CHARGE) chargealert = null -/mob/living/simple_animal/hostile/guardian/charger/OpenFire(atom/A) - if(!charging) - visible_message(span_danger("[src] [ranged_message] at [A]!")) - ranged_cooldown = world.time + ranged_cooldown_time - clear_alert(ALERT_CHARGE) - chargealert = null - Shoot(A) +/mob/living/simple_animal/hostile/guardian/charger/OpenFire(atom/target) + if(charging) + return + visible_message(span_danger("[src] [ranged_message] at [target]!")) + COOLDOWN_START(src, ranged_cooldown, ranged_cooldown_time) + clear_alert(ALERT_CHARGE) + chargealert = null + Shoot(target) /mob/living/simple_animal/hostile/guardian/charger/Shoot(atom/targeted_atom) - charging = 1 + charging = TRUE throw_at(targeted_atom, range, 1, src, FALSE, TRUE, callback = CALLBACK(src, PROC_REF(charging_end))) /mob/living/simple_animal/hostile/guardian/charger/proc/charging_end() - charging = 0 + charging = FALSE /mob/living/simple_animal/hostile/guardian/charger/Move() if(charging) - new /obj/effect/temp_visual/decoy/fading(loc,src) - . = ..() + new /obj/effect/temp_visual/decoy/fading(loc, src) + return ..() -/mob/living/simple_animal/hostile/guardian/charger/snapback() +/mob/living/simple_animal/hostile/guardian/charger/check_distance() if(!charging) ..() /mob/living/simple_animal/hostile/guardian/charger/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) if(!charging) return ..() - - else if(hit_atom) - if(isliving(hit_atom) && hit_atom != summoner) - var/mob/living/L = hit_atom - var/blocked = FALSE - if(hasmatchingsummoner(hit_atom)) //if the summoner matches don't hurt them - blocked = TRUE - if(ishuman(hit_atom)) - var/mob/living/carbon/human/H = hit_atom - if(H.check_shields(src, 90, "[name]", attack_type = THROWN_PROJECTILE_ATTACK)) - blocked = TRUE - if(!blocked) - L.drop_all_held_items() - L.visible_message(span_danger("[src] slams into [L]!"), span_userdanger("[src] slams into you!")) - L.apply_damage(20, BRUTE) - playsound(get_turf(L), 'sound/effects/meteorimpact.ogg', 100, TRUE) - shake_camera(L, 4, 3) - shake_camera(src, 2, 3) - - charging = 0 + if(!isliving(hit_atom) || hit_atom == summoner || hasmatchingsummoner(hit_atom)) + return + var/mob/living/hit_mob = hit_atom + if(ishuman(hit_mob)) + var/mob/living/carbon/human/hit_human = hit_mob + if(hit_human.check_shields(src, 90, name, attack_type = THROWN_PROJECTILE_ATTACK)) + return + hit_mob.drop_all_held_items() + hit_mob.visible_message(span_danger("[src] slams into [hit_mob]!"), span_userdanger("[src] slams into you!")) + hit_mob.apply_damage(20, BRUTE) + playsound(hit_mob, 'sound/effects/meteorimpact.ogg', 100, TRUE) + shake_camera(hit_mob, 4, 3) + shake_camera(src, 2, 3) diff --git a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm index 140702ce8c3..5a706ecef6c 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/dextrous.dm @@ -3,54 +3,55 @@ melee_damage_lower = 10 melee_damage_upper = 10 damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) - playstyle_string = "As a dextrous type you can hold items, store an item within yourself, and have medium damage resistance, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!" - magic_fluff_string = "..And draw the Drone, a dextrous master of construction and repair." - tech_fluff_string = "Boot sequence complete. Dextrous combat modules loaded. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! You caught one! It can hold stuff in its fins, sort of." - miner_fluff_string = "You encounter... Gold, a malleable constructor." + playstyle_string = span_holoparasite("As a dextrous type you can hold items, store an item within yourself, and have medium damage resistance, but do low damage on attacks. Recalling and leashing will force you to drop unstored items!") + magic_fluff_string = span_holoparasite("..And draw the Drone, a dextrous master of construction and repair.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Dextrous combat modules loaded. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! It can hold stuff in its fins, sort of.") + miner_fluff_string = span_holoparasite("You encounter... Gold, a malleable constructor.") + creator_name = "Dextrous" + creator_desc = "Does low damage on attack, but is capable of holding items and storing a single item within it. It will drop items held in its hands when it recalls, but it will retain the stored item." + creator_icon = "dextrous" dextrous = TRUE held_items = list(null, null) var/obj/item/internal_storage //what we're storing within ourself /mob/living/simple_animal/hostile/guardian/dextrous/death(gibbed) - ..() + . = ..() if(internal_storage) dropItemToGround(internal_storage) /mob/living/simple_animal/hostile/guardian/dextrous/examine(mob/user) if(dextrous) . = list("This is [icon2html(src)] \a [src]!\n[desc]", EXAMINE_SECTION_BREAK) //SKYRAT EDIT CHANGE - for(var/obj/item/I in held_items) - if(!(I.item_flags & ABSTRACT)) - . += "It has [I.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(I))]." + for(var/obj/item/held_item in held_items) + if(!(held_item.item_flags & ABSTRACT)) + . += "It has [held_item.get_examine_string(user)] in its [get_held_index_name(get_held_index_of_item(held_item))]." if(internal_storage && !(internal_storage.item_flags & ABSTRACT)) . += "It is holding [internal_storage.get_examine_string(user)] in its internal storage." . += "" else return ..() -/mob/living/simple_animal/hostile/guardian/dextrous/Recall(forced) - if(!summoner || loc == summoner || (cooldown > world.time && !forced)) - return FALSE +/mob/living/simple_animal/hostile/guardian/dextrous/recall_effects() drop_all_held_items() - return ..() //lose items, then return -/mob/living/simple_animal/hostile/guardian/dextrous/snapback() - if(summoner && !(get_dist(get_turf(summoner),get_turf(src)) <= range)) - drop_all_held_items() - ..() //lose items, then return +/mob/living/simple_animal/hostile/guardian/dextrous/check_distance() + if(!summoner || get_dist(get_turf(summoner), get_turf(src)) <= range) + return + drop_all_held_items() + ..() //lose items, then return //SLOT HANDLING BULLSHIT FOR INTERNAL STORAGE -/mob/living/simple_animal/hostile/guardian/dextrous/doUnEquip(obj/item/I, force, newloc, no_move, invdrop = TRUE, silent = FALSE) +/mob/living/simple_animal/hostile/guardian/dextrous/doUnEquip(obj/item/equipped_item, force, newloc, no_move, invdrop = TRUE, silent = FALSE) if(..()) update_held_items() - if(I == internal_storage) + if(equipped_item == internal_storage) internal_storage = null update_inv_internal_storage() return TRUE return FALSE -/mob/living/simple_animal/hostile/guardian/dextrous/can_equip(obj/item/I, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) +/mob/living/simple_animal/hostile/guardian/dextrous/can_equip(obj/item/equipped_item, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE) switch(slot) if(ITEM_SLOT_DEX_STORAGE) if(internal_storage) @@ -68,13 +69,13 @@ return ITEM_SLOT_DEX_STORAGE return ..() -/mob/living/simple_animal/hostile/guardian/dextrous/equip_to_slot(obj/item/I, slot) +/mob/living/simple_animal/hostile/guardian/dextrous/equip_to_slot(obj/item/equipped_item, slot) if(!..()) return switch(slot) if(ITEM_SLOT_DEX_STORAGE) - internal_storage = I + internal_storage = equipped_item update_inv_internal_storage() else to_chat(src, span_danger("You are trying to equip this item to an unsupported inventory slot. Report this to a coder!")) diff --git a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm index d54cf3a7e63..8434e5d60ce 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/explosive.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/explosive.dm @@ -4,61 +4,69 @@ UnregisterSignal(A, COMSIG_PARENT_EXAMINE); \ } while (0) -//Bomb -/mob/living/simple_animal/hostile/guardian/bomb +//Explosive +/mob/living/simple_animal/hostile/guardian/explosive melee_damage_lower = 15 melee_damage_upper = 15 damage_coeff = list(BRUTE = 0.6, BURN = 0.6, TOX = 0.6, CLONE = 0.6, STAMINA = 0, OXY = 0.6) range = 13 - playstyle_string = "As an explosive type, you have moderate close combat abilities and are capable of converting nearby items and objects into disguised bombs via alt click." - magic_fluff_string = "..And draw the Scientist, master of explosive death." - tech_fluff_string = "Boot sequence complete. Explosive modules active. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! Caught one! It's an explosive carp! Boom goes the fishy." - miner_fluff_string = "You encounter... Gibtonite, an explosive fighter." - var/bomb_cooldown = 0 + playstyle_string = span_holoparasite("As an explosive type, you have moderate close combat abilities and are capable of converting nearby items and objects into disguised bombs via right-click.") + magic_fluff_string = span_holoparasite("..And draw the Scientist, master of explosive death.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Explosive modules active. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's an explosive carp! Boom goes the fishy.") + miner_fluff_string = span_holoparasite("You encounter... Gibtonite, an explosive fighter.") + creator_name = "Explosive" + creator_desc = "High damage resist and medium power attack that may explosively teleport targets. Can turn any object, including objects too large to pick up, into a bomb, dealing explosive damage to the next person to touch it. The object will return to normal after the trap is triggered or after a delay." + creator_icon = "explosive" + /// Static list of signals that activate the boom. var/static/list/boom_signals = list(COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_BUMPED, COMSIG_ATOM_ATTACK_HAND) + /// After this amount of time passses, boom deactivates. + var/decay_time = 1 MINUTES + /// Time between bombs. + var/bomb_cooldown_time = 20 SECONDS + /// The cooldown timer between bombs. + COOLDOWN_DECLARE(bomb_cooldown) -/mob/living/simple_animal/hostile/guardian/bomb/get_status_tab_items() +/mob/living/simple_animal/hostile/guardian/explosive/get_status_tab_items() . = ..() - if(bomb_cooldown >= world.time) - . += "Bomb Cooldown Remaining: [DisplayTimeText(bomb_cooldown - world.time)]" + if(!COOLDOWN_FINISHED(src, bomb_cooldown)) + . += "Bomb Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, bomb_cooldown))]" -/mob/living/simple_animal/hostile/guardian/bomb/AltClickOn(atom/movable/A) - if(!istype(A)) +/mob/living/simple_animal/hostile/guardian/explosive/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + if(LAZYACCESS(modifiers, RIGHT_CLICK) && proximity_flag && isobj(attack_target)) + plant_bomb(attack_target) return - if(loc == summoner) - to_chat(src, span_danger("You must be manifested to create bombs!")) - return - if(isobj(A) && Adjacent(A)) - if(bomb_cooldown <= world.time && !stat) - to_chat(src, span_danger("Success! Bomb armed!")) - bomb_cooldown = world.time + 200 - RegisterSignal(A, COMSIG_PARENT_EXAMINE, PROC_REF(display_examine)) - RegisterSignal(A, boom_signals, PROC_REF(kaboom)) - addtimer(CALLBACK(src, PROC_REF(disable), A), 600, TIMER_UNIQUE|TIMER_OVERRIDE) - else - to_chat(src, span_danger("Your powers are on cooldown! You must wait 20 seconds between bombs.")) + return ..() -/mob/living/simple_animal/hostile/guardian/bomb/proc/kaboom(atom/source, mob/living/explodee) +/mob/living/simple_animal/hostile/guardian/explosive/proc/plant_bomb(obj/planting_on) + if(!COOLDOWN_FINISHED(src, bomb_cooldown)) + to_chat(src, span_bolddanger("Your powers are on cooldown! You must wait [DisplayTimeText(COOLDOWN_TIMELEFT(src, bomb_cooldown))] between bombs.")) + return + to_chat(src, span_bolddanger("Success! Bomb armed!")) + COOLDOWN_START(src, bomb_cooldown, bomb_cooldown_time) + RegisterSignal(planting_on, COMSIG_PARENT_EXAMINE, PROC_REF(display_examine)) + RegisterSignal(planting_on, boom_signals, PROC_REF(kaboom)) + addtimer(CALLBACK(src, PROC_REF(disable), planting_on), decay_time, TIMER_UNIQUE|TIMER_OVERRIDE) + +/mob/living/simple_animal/hostile/guardian/explosive/proc/kaboom(atom/source, mob/living/explodee) SIGNAL_HANDLER if(!istype(explodee)) return if(explodee == src || explodee == summoner || hasmatchingsummoner(explodee)) return - to_chat(explodee, span_danger("[source] was boobytrapped!")) - to_chat(src, span_danger("Success! Your trap caught [explodee]")) - var/turf/T = get_turf(source) - playsound(T,'sound/effects/explosion2.ogg', 200, TRUE) - new /obj/effect/temp_visual/explosion(T) + to_chat(explodee, span_bolddanger("[source] was boobytrapped!")) + to_chat(src, span_bolddanger("Success! Your trap caught [explodee]")) + playsound(source, 'sound/effects/explosion2.ogg', 200, TRUE) + new /obj/effect/temp_visual/explosion(get_turf(source)) EX_ACT(explodee, EXPLODE_HEAVY) UNREGISTER_BOMB_SIGNALS(source) -/mob/living/simple_animal/hostile/guardian/bomb/proc/disable(atom/A) - to_chat(src, span_danger("Failure! Your trap didn't catch anyone this time.")) - UNREGISTER_BOMB_SIGNALS(A) +/mob/living/simple_animal/hostile/guardian/explosive/proc/disable(obj/rigged_obj) + to_chat(src, span_bolddanger("Failure! Your trap didn't catch anyone this time.")) + UNREGISTER_BOMB_SIGNALS(rigged_obj) -/mob/living/simple_animal/hostile/guardian/bomb/proc/display_examine(datum/source, mob/user, text) +/mob/living/simple_animal/hostile/guardian/explosive/proc/display_examine(datum/source, mob/user, text) SIGNAL_HANDLER - text += span_holoparasite("It glows with a strange light!") + text += span_holoparasite("It glows with a strange light!") #undef UNREGISTER_BOMB_SIGNALS diff --git a/code/modules/mob/living/simple_animal/guardian/types/gaseous.dm b/code/modules/mob/living/simple_animal/guardian/types/gaseous.dm index 13675b1a991..5baf58bf99e 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/gaseous.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/gaseous.dm @@ -9,20 +9,23 @@ tech_fluff_string = span_holoparasite("Boot sequence complete. Atmospheric modules activated. Holoparasite swarm online.") carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! OH GOD, EVERYTHING'S ON FIRE. Except you and the fish.") miner_fluff_string = span_holoparasite("You encounter... Plasma, the bringer of fire.") + creator_name = "Gaseous" + creator_desc = "Creates sparks on touch and continuously expels a gas of its choice. Automatically extinguishes the user if they catch on fire." + creator_icon = "gaseous" toggle_button_type = /atom/movable/screen/guardian/toggle_mode/gases /// Gas being expelled. var/expelled_gas = null - /// How much gas we expel per second. - var/gas_rate = 10 - /// Possible gases to expel, with their expelling multiplier. + /// Rate of temperature stabilization per second. + var/temp_stabilization_rate = 0.1 + /// Possible gases to expel, with how much moles they create. var/static/list/possible_gases = list( - /datum/gas/oxygen = 5, - /datum/gas/nitrogen = 75, //yes 75 times. overpressurizing is hard!. - /datum/gas/water_vapor = 0.1, //you need incredibly little water vapor for the effects to kick in - /datum/gas/nitrous_oxide = 1.5, - /datum/gas/carbon_dioxide = 5, - /datum/gas/plasma = 0.25, - /datum/gas/bz = 1, + /datum/gas/oxygen = 50, + /datum/gas/nitrogen = 750, //overpressurizing is hard!. + /datum/gas/water_vapor = 1, //you need incredibly little water vapor for the effects to kick in + /datum/gas/nitrous_oxide = 15, + /datum/gas/carbon_dioxide = 50, + /datum/gas/plasma = 3, + /datum/gas/bz = 10, ) /// Gas colors, used for the particles. var/static/list/gas_colors = list( @@ -45,33 +48,34 @@ return do_sparks(1, TRUE, target) -/mob/living/simple_animal/hostile/guardian/gaseous/Recall(forced) +/mob/living/simple_animal/hostile/guardian/gaseous/recall(forced) expelled_gas = null QDEL_NULL(particles) //need to delete before putting in another object . = ..() - if(.) + if(. && summoner) UnregisterSignal(summoner, COMSIG_ATOM_PRE_PRESSURE_PUSH) -/mob/living/simple_animal/hostile/guardian/gaseous/Manifest(forced) +/mob/living/simple_animal/hostile/guardian/gaseous/manifest(forced) . = ..() - if(.) + if(. && summoner) RegisterSignal(summoner, COMSIG_ATOM_PRE_PRESSURE_PUSH, PROC_REF(stop_pressure)) /mob/living/simple_animal/hostile/guardian/gaseous/Life(delta_time, times_fired) . = ..() - summoner.extinguish_mob() - summoner.adjust_fire_stacks(-10 * delta_time) - summoner.adjust_bodytemperature(get_temp_change_amount((summoner.get_body_temp_normal() - summoner.bodytemperature), 0.1 * delta_time)) + if(summoner) + summoner.extinguish_mob() + summoner.set_fire_stacks(0, remove_wet_stacks = FALSE) + summoner.adjust_bodytemperature(get_temp_change_amount((summoner.get_body_temp_normal() - summoner.bodytemperature), temp_stabilization_rate * delta_time)) if(!expelled_gas) return var/datum/gas_mixture/mix_to_spawn = new() mix_to_spawn.add_gas(expelled_gas) - mix_to_spawn.gases[expelled_gas][MOLES] = gas_rate * possible_gases[expelled_gas] * delta_time + mix_to_spawn.gases[expelled_gas][MOLES] = possible_gases[expelled_gas] * delta_time mix_to_spawn.temperature = T20C var/turf/open/our_turf = get_turf(src) our_turf.assume_air(mix_to_spawn) -/mob/living/simple_animal/hostile/guardian/gaseous/ToggleMode() +/mob/living/simple_animal/hostile/guardian/gaseous/toggle_modes() var/list/gases = list("None") for(var/datum/gas/gas as anything in possible_gases) gases[initial(gas.name)] = gas diff --git a/code/modules/mob/living/simple_animal/guardian/types/gravitokinetic.dm b/code/modules/mob/living/simple_animal/guardian/types/gravitokinetic.dm index 767b1ea25db..fe6a0ec5856 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/gravitokinetic.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/gravitokinetic.dm @@ -3,73 +3,89 @@ melee_damage_lower = 15 melee_damage_upper = 15 damage_coeff = list(BRUTE = 0.75, BURN = 0.75, TOX = 0.75, CLONE = 0.75, STAMINA = 0, OXY = 0.75) - playstyle_string = "As a gravitokinetic type, you can alt click to make the gravity on the ground stronger, and punching applies this effect to a target." - magic_fluff_string = "..And draw the Singularity, an anomalous force of terror." - tech_fluff_string = "Boot sequence complete. Gravitokinetic modules loaded. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! Caught one! It's a gravitokinetic carp! Now do you understand the gravity of the situation?" - miner_fluff_string = "You encounter... Bananium, a master of gravity business." - var/list/gravito_targets = list() - var/gravity_power_range = 10 //how close the stand must stay to the target to keep the heavy gravity + playstyle_string = span_holoparasite("As a gravitokinetic type, you can right-click to make the gravity on the ground stronger, and punching applies this effect to a target.") + magic_fluff_string = span_holoparasite("..And draw the Singularity, an anomalous force of terror.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Gravitokinetic modules loaded. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's a gravitokinetic carp! Now do you understand the gravity of the situation?") + miner_fluff_string = span_holoparasite("You encounter... Bananium, a master of gravity business.") + creator_name = "Gravitokinetic" + creator_desc = "Attacks will apply crushing gravity to the target. Can target the ground as well to slow targets advancing on you, but this will affect the user." + creator_icon = "gravitokinetic" + /// Targets we have applied our effects on. + var/list/gravity_targets = list() + /// Distance in which our ability works + var/gravity_power_range = 10 + /// Gravity added on punches. + var/punch_gravity = 5 + /// Gravity added to turfs. + var/turf_gravity = 3 + +/mob/living/simple_animal/hostile/guardian/gravitokinetic/Initialize(mapload, theme) + . = ..() + AddElement(/datum/element/forced_gravity, 1) + +/mob/living/simple_animal/hostile/guardian/gravitokinetic/set_summoner(mob/to_who, different_person) + . = ..() + to_who.AddElement(/datum/element/forced_gravity, 1) + +/mob/living/simple_animal/hostile/guardian/gravitokinetic/cut_summoner(different_person) + summoner.RemoveElement(/datum/element/forced_gravity, 1) + return ..() ///Removes gravity from affected mobs upon guardian death to prevent permanent effects /mob/living/simple_animal/hostile/guardian/gravitokinetic/death() . = ..() - for(var/i in gravito_targets) - remove_gravity(i) + for(var/gravity_target in gravity_targets) + remove_gravity(gravity_target) -/mob/living/simple_animal/hostile/guardian/gravitokinetic/AttackingTarget() +/mob/living/simple_animal/hostile/guardian/gravitokinetic/AttackingTarget(atom/attacked_target) . = ..() - if(isliving(target) && target != src && target != summoner) - to_chat(src, "[span_danger("Your punch has applied heavy gravity to [target]!")]") - add_gravity(target, 5) + if(isliving(target) && !hasmatchingsummoner(attacked_target) && target != src && target != summoner && !gravity_targets[target]) + to_chat(src, span_bolddanger("Your punch has applied heavy gravity to [target]!")) + add_gravity(target, punch_gravity) to_chat(target, span_userdanger("Everything feels really heavy!")) -/mob/living/simple_animal/hostile/guardian/gravitokinetic/AltClickOn(atom/A) - if(isopenturf(A) && is_deployed() && stat != DEAD && in_range(src, A) && !incapacitated()) - var/turf/T = A - if(isspaceturf(T)) - to_chat(src, span_warning("You cannot add gravity to space!")) - return - visible_message(span_danger("[src] slams their fist into the [T]!"), span_notice("You modify the gravity of the [T].")) - do_attack_animation(T) - add_gravity(T, 3) +/mob/living/simple_animal/hostile/guardian/gravitokinetic/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + if(LAZYACCESS(modifiers, RIGHT_CLICK) && proximity_flag && !gravity_targets[target]) + slam_turf(attack_target) return return ..() -/mob/living/simple_animal/hostile/guardian/gravitokinetic/Recall(forced) - . = ..() - to_chat(src, "[span_danger("You have released your gravitokinetic powers!")]") - for(var/i in gravito_targets) - remove_gravity(i) +/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/slam_turf(turf/open/slammed) + if(!isopenturf(slammed) || isgroundlessturf(slammed)) + to_chat(src, span_warning("You cannot add gravity to this!")) + return + visible_message(span_danger("[src] slams their fist into the [slammed]!"), span_notice("You modify the gravity of the [slammed].")) + do_attack_animation(slammed) + add_gravity(slammed, turf_gravity) -/mob/living/simple_animal/hostile/guardian/gravitokinetic/Manifest(forced) - . = ..() - //just make sure to reapply a gravity immunity wherever you summon. it can be overridden but not by you at least - summoner.AddElement(/datum/element/forced_gravity, 1) - AddElement(/datum/element/forced_gravity, 1) +/mob/living/simple_animal/hostile/guardian/gravitokinetic/recall_effects() + to_chat(src, span_bolddanger("You have released your gravitokinetic powers!")) + for(var/gravity_target in gravity_targets) + remove_gravity(gravity_target) /mob/living/simple_animal/hostile/guardian/gravitokinetic/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE) . = ..() - for(var/i in gravito_targets) - if(get_dist(src, i) > gravity_power_range) - remove_gravity(i) + for(var/gravity_target in gravity_targets) + if(get_dist(src, gravity_target) > gravity_power_range) + remove_gravity(gravity_target) -/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/add_gravity(atom/A, new_gravity = 3) - if(gravito_targets[A]) +/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/add_gravity(atom/target, new_gravity = 3) + if(gravity_targets[target]) return - A.AddElement(/datum/element/forced_gravity, new_gravity) - gravito_targets[A] = new_gravity - RegisterSignal(A, COMSIG_MOVABLE_MOVED, PROC_REF(__distance_check)) + target.AddElement(/datum/element/forced_gravity, new_gravity) + gravity_targets[target] = new_gravity + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(distance_check)) playsound(src, 'sound/effects/gravhit.ogg', 100, TRUE) /mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/remove_gravity(atom/target) - if(isnull(gravito_targets[target])) + if(isnull(gravity_targets[target])) return UnregisterSignal(target, COMSIG_MOVABLE_MOVED) - target.RemoveElement(/datum/element/forced_gravity, gravito_targets[target]) - gravito_targets -= target + target.RemoveElement(/datum/element/forced_gravity, gravity_targets[target]) + gravity_targets -= target -/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/__distance_check(atom/movable/AM, OldLoc, Dir, Forced) +/mob/living/simple_animal/hostile/guardian/gravitokinetic/proc/distance_check(atom/movable/moving_target, old_loc, dir, forced) SIGNAL_HANDLER - if(get_dist(src, AM) > gravity_power_range) - remove_gravity(AM) + if(get_dist(src, moving_target) > gravity_power_range) + remove_gravity(moving_target) diff --git a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm index 0f560347270..1744065f0c2 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/lightning.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/lightning.dm @@ -1,10 +1,10 @@ -//Beam /obj/effect/ebeam/chain name = "lightning chain" layer = LYING_MOB_LAYER plane = GAME_PLANE_FOV_HIDDEN -/mob/living/simple_animal/hostile/guardian/beam +//Lightning +/mob/living/simple_animal/hostile/guardian/lightning melee_damage_lower = 7 melee_damage_upper = 7 attack_verb_continuous = "shocks" @@ -13,16 +13,19 @@ attack_sound = 'sound/machines/defib_zap.ogg' damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7) range = 7 - playstyle_string = "As a lightning type, you will apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them." - magic_fluff_string = "..And draw the Tesla, a shocking, lethal source of power." - tech_fluff_string = "Boot sequence complete. Lightning modules active. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! Caught one! It's a lightning carp! Everyone else goes zap zap." - miner_fluff_string = "You encounter... Iron, a conductive master of lightning." + playstyle_string = span_holoparasite("As a lightning type, you will apply lightning chains to targets on attack and have a lightning chain to your summoner. Lightning chains will shock anyone near them.") + magic_fluff_string = span_holoparasite("..And draw the Tesla, a shocking, lethal source of power.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Lightning modules active. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one! It's a lightning carp! Everyone else goes zap zap.") + miner_fluff_string = span_holoparasite("You encounter... Iron, a conductive master of lightning.") + creator_name = "Lightning" + creator_desc = "Attacks apply lightning chains to targets. Has a lightning chain to the user. Lightning chains shock everything near them, doing constant damage." + creator_icon = "lightning" var/datum/beam/summonerchain var/list/enemychains = list() var/successfulshocks = 0 -/mob/living/simple_animal/hostile/guardian/beam/AttackingTarget() +/mob/living/simple_animal/hostile/guardian/lightning/AttackingTarget(atom/attacked_target) . = ..() if(. && isliving(target) && target != src && target != summoner) cleardeletedchains() @@ -36,28 +39,19 @@ enemychains -= C enemychains += Beam(target, "lightning[rand(1,12)]", maxdistance=7, beam_type=/obj/effect/ebeam/chain) -/mob/living/simple_animal/hostile/guardian/beam/Destroy() +/mob/living/simple_animal/hostile/guardian/lightning/manifest_effects() + summonerchain = Beam(summoner, "lightning[rand(1,12)]", beam_type=/obj/effect/ebeam/chain) + while(loc != summoner) + if(successfulshocks > 5) + successfulshocks = 0 + if(shockallchains()) + successfulshocks++ + SLEEP_CHECK_DEATH(3, src) + +/mob/living/simple_animal/hostile/guardian/lightning/recall_effects() removechains() - return ..() -/mob/living/simple_animal/hostile/guardian/beam/Manifest() - . = ..() - if(.) - if(summoner) - summonerchain = Beam(summoner, "lightning[rand(1,12)]", beam_type=/obj/effect/ebeam/chain) - while(loc != summoner) - if(successfulshocks > 5) - successfulshocks = 0 - if(shockallchains()) - successfulshocks++ - SLEEP_CHECK_DEATH(3, src) - -/mob/living/simple_animal/hostile/guardian/beam/Recall() - . = ..() - if(.) - removechains() - -/mob/living/simple_animal/hostile/guardian/beam/proc/cleardeletedchains() +/mob/living/simple_animal/hostile/guardian/lightning/proc/cleardeletedchains() if(summonerchain && QDELETED(summonerchain)) summonerchain = null if(enemychains.len) @@ -66,7 +60,7 @@ if(!chain || QDELETED(cd)) enemychains -= chain -/mob/living/simple_animal/hostile/guardian/beam/proc/shockallchains() +/mob/living/simple_animal/hostile/guardian/lightning/proc/shockallchains() . = 0 cleardeletedchains() if(summoner) @@ -77,7 +71,7 @@ for(var/chain in enemychains) . += chainshock(chain) -/mob/living/simple_animal/hostile/guardian/beam/proc/removechains() +/mob/living/simple_animal/hostile/guardian/lightning/proc/removechains() if(summonerchain) qdel(summonerchain) summonerchain = null @@ -86,7 +80,7 @@ qdel(chain) enemychains = list() -/mob/living/simple_animal/hostile/guardian/beam/proc/chainshock(datum/beam/B) +/mob/living/simple_animal/hostile/guardian/lightning/proc/chainshock(datum/beam/B) //fuck you, fuck this . = 0 var/list/turfs = list() for(var/E in B.elements) diff --git a/code/modules/mob/living/simple_animal/guardian/types/protector.dm b/code/modules/mob/living/simple_animal/guardian/types/protector.dm index 413ff9b9806..1e7dc6ba54d 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/protector.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/protector.dm @@ -4,13 +4,20 @@ melee_damage_upper = 15 range = 15 //worse for it due to how it leashes damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4) - playstyle_string = "As a protector type you cause your summoner to leash to you instead of you leashing to them and have two modes; Combat Mode, where you do and take medium damage, and Protection Mode, where you do and take almost no damage, but move slightly slower." - magic_fluff_string = "..And draw the Guardian, a stalwart protector that never leaves the side of its charge." - tech_fluff_string = "Boot sequence complete. Protector modules loaded. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! You caught one! Wait, no... it caught you! The fisher has become the fishy." - miner_fluff_string = "You encounter... Uranium, a very resistant guardian." + playstyle_string = span_holoparasite("As a protector type you cause your summoner to leash to you instead of you leashing to them and have two modes; Combat Mode, where you do and take medium damage, and Protection Mode, where you do and take almost no damage, but move slightly slower.") + magic_fluff_string = span_holoparasite("..And draw the Guardian, a stalwart protector that never leaves the side of its charge.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Protector modules loaded. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! Wait, no... it caught you! The fisher has become the fishy.") + miner_fluff_string = span_holoparasite("You encounter... Uranium, a very resistant guardian.") + creator_name = "Protector" + creator_desc = "Causes you to teleport to it when out of range, unlike other parasites. Has two modes; Combat, where it does and takes medium damage, and Protection, where it does and takes almost no damage but moves slightly slower." + creator_icon = "protector" toggle_button_type = /atom/movable/screen/guardian/toggle_mode + /// Damage removed in protecting mode. + var/damage_penalty = 13 + /// Is it in protecting mode? var/toggle = FALSE + /// Overlay of our protection shield. var/mutable_appearance/shield_overlay /mob/living/simple_animal/hostile/guardian/protector/ex_act(severity) @@ -26,48 +33,44 @@ /mob/living/simple_animal/hostile/guardian/protector/adjustHealth(amount, updating_health = TRUE, forced = FALSE) . = ..() if(. > 0 && toggle) - var/image/I = new('icons/effects/effects.dmi', src, "shield-flash", MOB_LAYER+0.01, dir = pick(GLOB.cardinals)) - if(guardiancolor) - I.color = guardiancolor - flick_overlay_view(I, 5) + var/image/flash_overlay = new('icons/effects/effects.dmi', src, "shield-flash", layer+0.01, dir = pick(GLOB.cardinals)) + flash_overlay.color = guardian_color + flick_overlay_view(flash_overlay, 0.5 SECONDS) -/mob/living/simple_animal/hostile/guardian/protector/ToggleMode() - if(cooldown > world.time) - return 0 - cooldown = world.time + 10 +/mob/living/simple_animal/hostile/guardian/protector/toggle_modes() + if(COOLDOWN_FINISHED(src, manifest_cooldown)) + return + COOLDOWN_START(src, manifest_cooldown, 1 SECONDS) if(toggle) cut_overlay(shield_overlay) - melee_damage_lower = initial(melee_damage_lower) - melee_damage_upper = initial(melee_damage_upper) + melee_damage_lower += damage_penalty + melee_damage_upper += damage_penalty speed = initial(speed) damage_coeff = list(BRUTE = 0.4, BURN = 0.4, TOX = 0.4, CLONE = 0.4, STAMINA = 0, OXY = 0.4) - to_chat(src, "[span_danger("You switch to combat mode.")]") + to_chat(src, span_bolddanger("You switch to combat mode.")) toggle = FALSE else if(!shield_overlay) shield_overlay = mutable_appearance('icons/effects/effects.dmi', "shield-grey") - if(guardiancolor) - shield_overlay.color = guardiancolor + shield_overlay.color = guardian_color add_overlay(shield_overlay) - melee_damage_lower = 2 - melee_damage_upper = 2 + melee_damage_lower -= damage_penalty + melee_damage_upper -= damage_penalty speed = 1 damage_coeff = list(BRUTE = 0.05, BURN = 0.05, TOX = 0.05, CLONE = 0.05, STAMINA = 0, OXY = 0.05) //damage? what's damage? - to_chat(src, "[span_danger("You switch to protection mode.")]") + to_chat(src, span_bolddanger("You switch to protection mode.")) toggle = TRUE -/mob/living/simple_animal/hostile/guardian/protector/snapback() //snap to what? snap to the guardian! - if(summoner) - if(get_dist(get_turf(summoner),get_turf(src)) <= range) - return - else - if(istype(summoner.loc, /obj/effect)) - to_chat(src, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")) - visible_message(span_danger("\The [src] jumps back to its user.")) - Recall(TRUE) - else - to_chat(summoner, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [real_name]!")) - summoner.visible_message(span_danger("\The [summoner] jumps back to [summoner.p_their()] protector.")) - new /obj/effect/temp_visual/guardian/phase/out(get_turf(summoner)) - summoner.forceMove(get_turf(src)) - new /obj/effect/temp_visual/guardian/phase(get_turf(summoner)) +/mob/living/simple_animal/hostile/guardian/protector/check_distance() //snap to what? snap to the guardian! + if(!summoner || get_dist(summoner, src) <= range) + return + if(istype(summoner.loc, /obj/effect)) + to_chat(src, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [summoner.real_name]!")) + visible_message(span_danger("\The [src] jumps back to its user.")) + recall(forced = TRUE) + return + to_chat(summoner, span_holoparasite("You moved out of range, and were pulled back! You can only move [range] meters from [real_name]!")) + summoner.visible_message(span_danger("\The [summoner] jumps back to [summoner.p_their()] protector.")) + new /obj/effect/temp_visual/guardian/phase/out(get_turf(summoner)) + summoner.forceMove(get_turf(src)) + new /obj/effect/temp_visual/guardian/phase(get_turf(summoner)) diff --git a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm index 339396c7e51..c46a926a528 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/ranged.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/ranged.dm @@ -18,50 +18,66 @@ projectilesound = 'sound/effects/hit_on_shattered_glass.ogg' ranged = 1 range = 13 - playstyle_string = "As a ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit." - magic_fluff_string = "..And draw the Sentinel, an alien master of ranged combat." - tech_fluff_string = "Boot sequence complete. Ranged combat modules active. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! Caught one, it's a ranged carp. This fishy can watch people pee in the ocean." - miner_fluff_string = "You encounter... Diamond, a powerful projectile thrower." + playstyle_string = span_holoparasite("As a ranged type, you have only light damage resistance, but are capable of spraying shards of crystal at incredibly high speed. You can also deploy surveillance snares to monitor enemy movement. Finally, you can switch to scout mode, in which you can't attack, but can move without limit.") + magic_fluff_string = span_holoparasite("..And draw the Sentinel, an alien master of ranged combat.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Ranged combat modules active. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! Caught one, it's a ranged carp. This fishy can watch people pee in the ocean.") + miner_fluff_string = span_holoparasite("You encounter... Diamond, a powerful projectile thrower.") + creator_name = "Ranged" + creator_desc = "Has two modes. Ranged; which fires a constant stream of weak, armor-ignoring projectiles. Scout; where it cannot attack, but can move through walls and is quite hard to see. Can lay surveillance snares, which alert it when crossed, in either mode." + creator_icon = "ranged" see_invisible = SEE_INVISIBLE_LIVING see_in_dark = NIGHTVISION_FOV_RANGE toggle_button_type = /atom/movable/screen/guardian/toggle_mode + /// List of all deployed snares. var/list/snares = list() + /// Is it in scouting mode? var/toggle = FALSE + /// Maximum snares deployed at once. + var/max_snares = 6 + /// Lower damage before scouting. + var/previous_lower_damage = 0 + /// Upper damage before scouting. + var/previous_upper_damage = 0 -/mob/living/simple_animal/hostile/guardian/ranged/ToggleMode() - if(loc == summoner) - if(toggle) - ranged = initial(ranged) - melee_damage_lower = initial(melee_damage_lower) - melee_damage_upper = initial(melee_damage_upper) - obj_damage = initial(obj_damage) - environment_smash = initial(environment_smash) - alpha = 255 - range = initial(range) - to_chat(src, "[span_danger("You switch to combat mode.")]") - toggle = FALSE - else - ranged = 0 - melee_damage_lower = 0 - melee_damage_upper = 0 - obj_damage = 0 - environment_smash = ENVIRONMENT_SMASH_NONE - alpha = 45 - range = 255 - to_chat(src, "[span_danger("You switch to scout mode.")]") - toggle = TRUE +/mob/living/simple_animal/hostile/guardian/ranged/toggle_modes() + if(is_deployed() && summoner) + to_chat(src, span_bolddanger("You have to be recalled to toggle modes!")) + return + if(toggle) + ranged = initial(ranged) + melee_damage_lower = previous_lower_damage + melee_damage_upper = previous_upper_damage + previous_lower_damage = 0 + previous_upper_damage = 0 + obj_damage = initial(obj_damage) + environment_smash = initial(environment_smash) + alpha = 255 + range = initial(range) + to_chat(src, span_bolddanger("You switch to combat mode.")) + toggle = FALSE else - to_chat(src, "[span_danger("You have to be recalled to toggle modes!")]") + ranged = 0 + previous_lower_damage = melee_damage_lower + melee_damage_lower = 0 + previous_upper_damage = melee_damage_upper + melee_damage_upper = 0 + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + alpha = 45 + range = 255 + to_chat(src, span_bolddanger("You switch to scout mode.")) + toggle = TRUE + /mob/living/simple_animal/hostile/guardian/ranged/Shoot(atom/targeted_atom) . = ..() - if(istype(., /obj/projectile)) - var/obj/projectile/P = . - if(guardiancolor) - P.color = guardiancolor + if(!istype(., /obj/projectile)) + return + var/obj/projectile/shot_projectile = . + shot_projectile.color = guardian_color -/mob/living/simple_animal/hostile/guardian/ranged/ToggleLight() +/mob/living/simple_animal/hostile/guardian/ranged/toggle_light() var/msg switch(lighting_alpha) if (LIGHTING_PLANE_ALPHA_VISIBLE) @@ -76,55 +92,68 @@ else lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE msg = "You deactivate your night vision." - - to_chat(src, span_notice("[msg]")) + sync_lighting_plane_alpha() + to_chat(src, span_notice(msg)) /mob/living/simple_animal/hostile/guardian/ranged/verb/Snare() set name = "Set Surveillance Snare" set category = "Guardian" set desc = "Set an invisible snare that will alert you when living creatures walk over it. Max of 5" - if(length(snares) < 6) - var/turf/snare_loc = get_turf(loc) - var/obj/effect/snare/S = new /obj/effect/snare(snare_loc) - S.spawner = src - S.name = "[get_area(snare_loc)] snare ([rand(1, 1000)])" - snares |= S - to_chat(src, "[span_danger("Surveillance snare deployed!")]") + if(length(snares) < max_snares) + var/turf/snare_loc = get_turf(src) + var/obj/effect/snare/new_snare = new /obj/effect/snare(snare_loc, src) + new_snare.name = "[get_area(snare_loc)] snare ([rand(1, 1000)])" + snares += new_snare + to_chat(src, span_bolddanger("Surveillance snare deployed!")) else - to_chat(src, "[span_danger("You have too many snares deployed. Remove some first.")]") + to_chat(src, span_bolddanger("You have too many snares deployed. Remove some first.")) /mob/living/simple_animal/hostile/guardian/ranged/verb/DisarmSnare() set name = "Remove Surveillance Snare" set category = "Guardian" set desc = "Disarm unwanted surveillance snares." - var/picked_snare = tgui_input_list(src, "Pick which snare to remove", "Remove Snare", sort_names(snares)) + var/picked_snare = tgui_input_list(src, "Pick which snare to remove.", "Remove Snare", sort_names(snares)) if(isnull(picked_snare)) return - snares -= picked_snare qdel(picked_snare) - to_chat(src, "[span_danger("Snare disarmed.")]") + to_chat(src, span_bolddanger("Snare disarmed.")) /obj/effect/snare name = "snare" desc = "You shouldn't be seeing this!" - var/mob/living/simple_animal/hostile/guardian/spawner invisibility = INVISIBILITY_ABSTRACT + var/datum/weakref/guardian_ref -/obj/effect/snare/Initialize(mapload) +/obj/effect/snare/Initialize(mapload, spawning_guardian) . = ..() + guardian_ref = WEAKREF(spawning_guardian) var/static/list/loc_connections = list( COMSIG_ATOM_ENTERED = PROC_REF(on_entered), ) AddElement(/datum/element/connect_loc, loc_connections) -/obj/effect/snare/proc/on_entered(datum/source, AM as mob|obj) +/obj/effect/snare/Destroy(force) + var/mob/living/simple_animal/hostile/guardian/ranged/spawning_guardian = guardian_ref?.resolve() + if(spawning_guardian) + spawning_guardian.snares -= src + return ..() + +/obj/effect/snare/proc/on_entered(datum/source, crossed_object) SIGNAL_HANDLER - if(isliving(AM) && spawner && spawner.summoner && AM != spawner && !spawner.hasmatchingsummoner(AM)) - to_chat(spawner.summoner, "[span_danger("[AM] has crossed surveillance snare, [name].")]") - var/list/guardians = spawner.summoner.get_all_linked_holoparasites() - for(var/para in guardians) - to_chat(para, "[span_danger("[AM] has crossed surveillance snare, [name].")]") + var/mob/living/simple_animal/hostile/guardian/ranged/spawning_guardian = guardian_ref?.resolve() + if(!spawning_guardian) + qdel(src) + return + if(!isliving(crossed_object) || crossed_object == spawning_guardian || spawning_guardian.hasmatchingsummoner(crossed_object)) + return + send_message(spawning_guardian.summoner || spawning_guardian, crossed_object) + +/obj/effect/snare/proc/send_message(mob/living/recipient, crossed_object) + to_chat(recipient, span_bolddanger("[crossed_object] has crossed [name].")) + var/list/guardians = recipient.get_all_linked_holoparasites() + for(var/guardian in guardians) + send_message(guardian, crossed_object) /obj/effect/snare/singularity_act() return @@ -132,17 +161,15 @@ /obj/effect/snare/singularity_pull() return -/mob/living/simple_animal/hostile/guardian/ranged/Manifest(forced) - if (toggle) +/mob/living/simple_animal/hostile/guardian/ranged/manifest_effects() + if(toggle) incorporeal_move = INCORPOREAL_MOVE_BASIC - . = ..() -/mob/living/simple_animal/hostile/guardian/ranged/Recall(forced) +/mob/living/simple_animal/hostile/guardian/ranged/recall_effects() // To stop scout mode from moving when recalled incorporeal_move = FALSE - . = ..() -/mob/living/simple_animal/hostile/guardian/ranged/AttackingTarget() +/mob/living/simple_animal/hostile/guardian/ranged/AttackingTarget(atom/attacked_target) if(toggle) return - ..() + return ..() diff --git a/code/modules/mob/living/simple_animal/guardian/types/standard.dm b/code/modules/mob/living/simple_animal/guardian/types/standard.dm index 99eb1282f73..e190459ff5e 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/standard.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/standard.dm @@ -1,18 +1,23 @@ //Standard -/mob/living/simple_animal/hostile/guardian/punch +/mob/living/simple_animal/hostile/guardian/standard + damage_coeff = list(BRUTE = 0.5, BURN = 0.5, TOX = 0.5, CLONE = 0.5, STAMINA = 0, OXY = 0.5) melee_damage_lower = 20 melee_damage_upper = 20 obj_damage = 80 next_move_modifier = 0.8 //attacks 20% faster environment_smash = ENVIRONMENT_SMASH_WALLS - playstyle_string = "As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls." - magic_fluff_string = "..And draw the Assistant, faceless and generic, but never to be underestimated." - tech_fluff_string = "Boot sequence complete. Standard combat modules loaded. Holoparasite swarm online." - carp_fluff_string = "CARP CARP CARP! You caught one! It's really boring and standard. Better punch some walls to ease the tension." - miner_fluff_string = "You encounter... Adamantine, a powerful attacker." + playstyle_string = span_holoparasite("As a standard type you have no special abilities, but have a high damage resistance and a powerful attack capable of smashing through walls.") + magic_fluff_string = span_holoparasite("..And draw the Assistant, faceless and generic, but never to be underestimated.") + tech_fluff_string = span_holoparasite("Boot sequence complete. Standard combat modules loaded. Holoparasite swarm online.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught one! It's really boring and standard. Better punch some walls to ease the tension.") + miner_fluff_string = span_holoparasite("You encounter... Adamantine, a powerful attacker.") + creator_name = "Standard" + creator_desc = "Devastating close combat attacks and high damage resistance. Can smash through weak walls." + creator_icon = "standard" + /// The text we shout when attacking. var/battlecry = "AT" -/mob/living/simple_animal/hostile/guardian/punch/verb/Battlecry() +/mob/living/simple_animal/hostile/guardian/standard/verb/Battlecry() set name = "Set Battlecry" set category = "Guardian" set desc = "Choose what you shout as you punch people." @@ -20,13 +25,16 @@ if(input) battlecry = input - - -/mob/living/simple_animal/hostile/guardian/punch/AttackingTarget() +/mob/living/simple_animal/hostile/guardian/standard/AttackingTarget(atom/attacked_target) . = ..() - if(isliving(target)) - say("[battlecry][battlecry][battlecry][battlecry][battlecry][battlecry][battlecry][battlecry][battlecry][battlecry]!!", ignore_spam = TRUE) - playsound(loc, src.attack_sound, 50, TRUE, TRUE) - playsound(loc, src.attack_sound, 50, TRUE, TRUE) - playsound(loc, src.attack_sound, 50, TRUE, TRUE) - playsound(loc, src.attack_sound, 50, TRUE, TRUE) + if(!isliving(target) || attacked_target == src) + return + var/msg = "" + for(var/i in 1 to 9) + msg += battlecry + say("[msg]!!", ignore_spam = TRUE) + for(var/j in 1 to 4) + addtimer(CALLBACK(src, PROC_REF(do_attack_sound), target.loc), j) + +/mob/living/simple_animal/hostile/guardian/standard/proc/do_attack_sound(atom/playing_from) + playsound(playing_from, attack_sound, 50, TRUE, TRUE) diff --git a/code/modules/mob/living/simple_animal/guardian/types/support.dm b/code/modules/mob/living/simple_animal/guardian/types/support.dm index aa3f44b3004..43e64cb9791 100644 --- a/code/modules/mob/living/simple_animal/guardian/types/support.dm +++ b/code/modules/mob/living/simple_animal/guardian/types/support.dm @@ -1,77 +1,66 @@ -//Healer -/mob/living/simple_animal/hostile/guardian/healer - combat_mode = TRUE - friendly_verb_continuous = "heals" - friendly_verb_simple = "heal" +//Support +/mob/living/simple_animal/hostile/guardian/support speed = 0 damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7) melee_damage_lower = 15 melee_damage_upper = 15 - playstyle_string = "As a support type, you may toggle your basic attacks to a healing mode. In addition, Alt-Clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay." - magic_fluff_string = "..And draw the CMO, a potent force of life... and death." - carp_fluff_string = "CARP CARP CARP! You caught a support carp. It's a kleptocarp!" - tech_fluff_string = "Boot sequence complete. Support modules active. Holoparasite swarm online." - miner_fluff_string = "You encounter... Bluespace, the master of support." - toggle_button_type = /atom/movable/screen/guardian/toggle_mode - var/obj/structure/receiving_pad/beacon - var/beacon_cooldown = 0 + playstyle_string = span_holoparasite("As a support type, you may right-click to heal targets. In addition, alt-clicking on an adjacent object or mob will warp them to your bluespace beacon after a short delay.") + magic_fluff_string = span_holoparasite("..And draw the Chief Medical Officer, a potent force of life... and death.") + carp_fluff_string = span_holoparasite("CARP CARP CARP! You caught a support carp. It's a kleptocarp!") + tech_fluff_string = span_holoparasite("Boot sequence complete. Support modules active. Holoparasite swarm online.") + miner_fluff_string = span_holoparasite("You encounter... Bluespace, the master of support.") + creator_name = "Support" + creator_desc = "Does medium damage, but can heal its targets and create beacons to teleport people and things to." + creator_icon = "support" + /// Is it in healing mode? var/toggle = FALSE + /// How much we heal per hit. + var/healing_amount = 5 + /// Our teleportation beacon. + var/obj/structure/receiving_pad/beacon + /// Time it takes to teleport. + var/teleporting_time = 6 SECONDS + /// Time between creating beacons. + var/beacon_cooldown_time = 5 MINUTES + /// Cooldown between creating beacons. + COOLDOWN_DECLARE(beacon_cooldown) -/mob/living/simple_animal/hostile/guardian/healer/Initialize(mapload) +/mob/living/simple_animal/hostile/guardian/support/Initialize(mapload) . = ..() var/datum/atom_hud/medsensor = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED] medsensor.show_to(src) -/mob/living/simple_animal/hostile/guardian/healer/get_status_tab_items() +/mob/living/simple_animal/hostile/guardian/support/get_status_tab_items() . = ..() - if(beacon_cooldown >= world.time) - . += "Beacon Cooldown Remaining: [DisplayTimeText(beacon_cooldown - world.time)]" + if(!COOLDOWN_FINISHED(src, beacon_cooldown)) + . += "Beacon Cooldown Remaining: [DisplayTimeText(COOLDOWN_TIMELEFT(src, beacon_cooldown))]" -/mob/living/simple_animal/hostile/guardian/healer/AttackingTarget() - . = ..() - if(is_deployed() && toggle && iscarbon(target)) - var/mob/living/carbon/C = target - C.adjustBruteLoss(-5) - C.adjustFireLoss(-5) - C.adjustOxyLoss(-5) - C.adjustToxLoss(-5) - var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(C)) - if(guardiancolor) - H.color = guardiancolor - if(C == summoner) - update_health_hud() - med_hud_set_health() - med_hud_set_status() +/mob/living/simple_animal/hostile/guardian/support/UnarmedAttack(atom/attack_target, proximity_flag, list/modifiers) + if(LAZYACCESS(modifiers, RIGHT_CLICK) && proximity_flag && isliving(attack_target)) + heal_target(attack_target) + return + return ..() -/mob/living/simple_animal/hostile/guardian/healer/ToggleMode() - if(src.loc == summoner) - if(toggle) - set_combat_mode(TRUE) - speed = 0 - damage_coeff = list(BRUTE = 0.7, BURN = 0.7, TOX = 0.7, CLONE = 0.7, STAMINA = 0, OXY = 0.7) - melee_damage_lower = 15 - melee_damage_upper = 15 - to_chat(src, "[span_danger("You switch to combat mode.")]") - toggle = FALSE - else - set_combat_mode(FALSE) - speed = 1 - damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1) - melee_damage_lower = 0 - melee_damage_upper = 0 - to_chat(src, "[span_danger("You switch to healing mode.")]") - toggle = TRUE - else - to_chat(src, "[span_danger("You have to be recalled to toggle modes!")]") +/mob/living/simple_animal/hostile/guardian/support/proc/heal_target(mob/living/target) + do_attack_animation(target, ATTACK_EFFECT_PUNCH) + target.visible_message(span_notice("[src] heals [target]!"),\ + span_userdanger("[src] heals you!"), null, COMBAT_MESSAGE_RANGE, src) + to_chat(src, span_notice("You heal [target]!")) + playsound(target, attack_sound, 50, TRUE, TRUE, frequency = -1) //play punch in REVERSE + target.adjustBruteLoss(-healing_amount) + target.adjustFireLoss(-healing_amount) + target.adjustOxyLoss(-healing_amount) + target.adjustToxLoss(-healing_amount) + var/obj/effect/temp_visual/heal/heal_effect = new /obj/effect/temp_visual/heal(get_turf(target)) + heal_effect.color = guardian_color - -/mob/living/simple_animal/hostile/guardian/healer/verb/Beacon() +/mob/living/simple_animal/hostile/guardian/support/verb/Beacon() set name = "Place Bluespace Beacon" set category = "Guardian" set desc = "Mark a floor as your beacon point, allowing you to warp targets to it. Your beacon will not work at extreme distances." - if(beacon_cooldown >= world.time) - to_chat(src, "[span_danger("Your power is on cooldown. You must wait five minutes between placing beacons.")]") + if(!COOLDOWN_FINISHED(src, beacon_cooldown)) + to_chat(src, span_bolddanger("Your power is on cooldown. You must wait five minutes between placing beacons.")) return var/turf/beacon_loc = get_turf(src.loc) @@ -84,9 +73,9 @@ beacon = new(beacon_loc, src) - to_chat(src, "[span_danger("Beacon placed! You may now warp targets and objects to it, including your user, via Alt+Click.")]") + to_chat(src, span_bolddanger("Beacon placed! You may now warp targets and objects to it, including your user, via Alt+Click.")) - beacon_cooldown = world.time + 3000 + COOLDOWN_START(src, beacon_cooldown, beacon_cooldown_time) /obj/structure/receiving_pad name = "bluespace receiving pad" @@ -99,50 +88,51 @@ plane = FLOOR_PLANE layer = ABOVE_OPEN_TURF_LAYER -/obj/structure/receiving_pad/New(loc, mob/living/simple_animal/hostile/guardian/healer/G) +/obj/structure/receiving_pad/New(loc, mob/living/simple_animal/hostile/guardian/spawning_guardian) . = ..() - if(G?.guardiancolor) - add_atom_colour(G.guardiancolor, FIXED_COLOUR_PRIORITY) + add_atom_colour(spawning_guardian?.guardian_color, FIXED_COLOUR_PRIORITY) /obj/structure/receiving_pad/proc/disappear() visible_message(span_notice("[src] vanishes!")) qdel(src) -/mob/living/simple_animal/hostile/guardian/healer/AltClickOn(atom/movable/A) - if(!istype(A)) - return - if(src.loc == summoner) - to_chat(src, "[span_danger("You must be manifested to warp a target!")]") +/mob/living/simple_animal/hostile/guardian/support/AltClickOn(atom/movable/target) + teleport_to_beacon(target) + +/mob/living/simple_animal/hostile/guardian/support/proc/teleport_to_beacon(atom/movable/teleport_target) + if(!istype(teleport_target)) return if(!beacon) - to_chat(src, "[span_danger("You need a beacon placed to warp things!")]") + to_chat(src, span_bolddanger("You need a beacon placed to warp things!")) return - if(!Adjacent(A)) - to_chat(src, "[span_danger("You must be adjacent to your target!")]") + if(!is_deployed()) + to_chat(src, span_bolddanger("You must be manifested to warp a target!")) return - if(A.anchored) - to_chat(src, "[span_danger("Your target cannot be anchored!")]") + if(!Adjacent(teleport_target)) + to_chat(src, span_bolddanger("You must be adjacent to your target!")) return - - var/turf/T = get_turf(A) - if(beacon.z != T.z) - to_chat(src, "[span_danger("The beacon is too far away to warp to!")]") + if(teleport_target.anchored) + to_chat(src, span_bolddanger("Your target is anchored!")) return - - to_chat(src, "[span_danger("You begin to warp [A].")]") - A.visible_message(span_danger("[A] starts to glow faintly!"), \ + var/turf/target_turf = get_turf(teleport_target) + if(beacon.z != target_turf.z) + to_chat(src, span_bolddanger("The beacon is too far away to warp to!")) + return + to_chat(src, span_bolddanger("You begin to warp [teleport_target].")) + teleport_target.visible_message(span_danger("[teleport_target] starts to glow faintly!"), \ span_userdanger("You start to faintly glow, and you feel strangely weightless!")) - do_attack_animation(A) - - if(!do_mob(src, A, 60)) //now start the channel - to_chat(src, "[span_danger("You need to hold still!")]") + do_attack_animation(teleport_target) + playsound(teleport_target, attack_sound, 50, TRUE, TRUE, frequency = -1) //play punch in REVERSE + if(!do_mob(src, teleport_target, teleporting_time)) //now start the channel + to_chat(src, span_bolddanger("You need to hold still!")) return - - new /obj/effect/temp_visual/guardian/phase/out(T) - if(isliving(A)) - var/mob/living/L = A - L.flash_act() - A.visible_message(span_danger("[A] disappears in a flash of light!"), \ - span_userdanger("Your vision is obscured by a flash of light!")) - do_teleport(A, beacon, 0, channel = TELEPORT_CHANNEL_BLUESPACE) - new /obj/effect/temp_visual/guardian/phase(get_turf(A)) + new /obj/effect/temp_visual/guardian/phase/out(target_turf) + if(isliving(teleport_target)) + var/mob/living/living_target = teleport_target + living_target.flash_act() + teleport_target.visible_message( + span_danger("[teleport_target] disappears in a flash of light!"), \ + span_userdanger("Your vision is obscured by a flash of light!"), \ + ) + do_teleport(teleport_target, beacon, 0, channel = TELEPORT_CHANNEL_BLUESPACE) + new /obj/effect/temp_visual/guardian/phase(get_turf(teleport_target)) diff --git a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm index 754346e8cfa..56c308e7565 100644 --- a/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm +++ b/code/modules/mob/living/simple_animal/hostile/wumborian_fugu.dm @@ -141,12 +141,20 @@ item_flags = NOBLUDGEON w_class = WEIGHT_CLASS_NORMAL layer = MOB_LAYER + var/static/list/fugu_blacklist + +/obj/item/fugu_gland/Initialize(mapload) + . = ..() + if(!fugu_blacklist) + fugu_blacklist = typecacheof(list( + /mob/living/simple_animal/hostile/guardian, + )) /obj/item/fugu_gland/afterattack(atom/target, mob/user, proximity_flag) . = ..() if(!proximity_flag) return - if(!isanimal(target)) + if(!isanimal(target) || fugu_blacklist[target.type]) return var/mob/living/simple_animal/animal = target diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index bb5992a6387..ac07fe69cac 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -473,3 +473,11 @@ if(!istype(player, /client)) return return player + +/proc/health_percentage(mob/living/mob) + var/divided_health = mob.health / mob.maxHealth + if(iscyborg(mob) || islarva(mob)) + divided_health = (mob.health + mob.maxHealth) / (mob.maxHealth * 2) + else if(iscarbon(mob) || isAI(mob) || isbrain(mob)) + divided_health = abs(HEALTH_THRESHOLD_DEAD - mob.health) / abs(HEALTH_THRESHOLD_DEAD - mob.maxHealth) + return divided_health * 100 diff --git a/code/modules/unit_tests/simple_animal_freeze.dm b/code/modules/unit_tests/simple_animal_freeze.dm index 99afd5c32d7..d979ca4f4e0 100644 --- a/code/modules/unit_tests/simple_animal_freeze.dm +++ b/code/modules/unit_tests/simple_animal_freeze.dm @@ -177,16 +177,16 @@ /mob/living/simple_animal/hostile/gorilla/cargo_domestic, /mob/living/simple_animal/hostile/guardian, /mob/living/simple_animal/hostile/guardian/assassin, - /mob/living/simple_animal/hostile/guardian/beam, - /mob/living/simple_animal/hostile/guardian/bomb, /mob/living/simple_animal/hostile/guardian/charger, /mob/living/simple_animal/hostile/guardian/dextrous, + /mob/living/simple_animal/hostile/guardian/explosive, /mob/living/simple_animal/hostile/guardian/gaseous, /mob/living/simple_animal/hostile/guardian/gravitokinetic, - /mob/living/simple_animal/hostile/guardian/healer, + /mob/living/simple_animal/hostile/guardian/lightning, /mob/living/simple_animal/hostile/guardian/protector, - /mob/living/simple_animal/hostile/guardian/punch, /mob/living/simple_animal/hostile/guardian/ranged, + /mob/living/simple_animal/hostile/guardian/standard, + /mob/living/simple_animal/hostile/guardian/support, /mob/living/simple_animal/hostile/headcrab, /mob/living/simple_animal/hostile/heretic_summon, /mob/living/simple_animal/hostile/heretic_summon/armsy, diff --git a/code/modules/uplink/uplink_items/dangerous.dm b/code/modules/uplink/uplink_items/dangerous.dm index ed3d305cf63..9624139a639 100644 --- a/code/modules/uplink/uplink_items/dangerous.dm +++ b/code/modules/uplink/uplink_items/dangerous.dm @@ -87,7 +87,7 @@ desc = "Though capable of near sorcerous feats via use of hardlight holograms and nanomachines, they require an \ organic host as a home base and source of fuel. Holoparasites come in various types and share damage with their host." progression_minimum = 30 MINUTES - item = /obj/item/storage/box/syndie_kit/guardian + item = /obj/item/guardiancreator/tech/choose/traitor cost = 18 surplus = 0 purchasable_from = ~(UPLINK_NUKE_OPS | UPLINK_CLOWN_OPS) diff --git a/icons/hud/guardian.dmi b/icons/hud/guardian.dmi index 416c783738ae43ff73b2aba45d3c963b0b99f0d2..6b6c03912dd7d66d9df8a6df3e0ac27a2e1e2c2e 100644 GIT binary patch literal 13153 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 Yp#q0fjk;~<1&XGbG}w2*(6RAoUN7QqE&u=k diff --git a/tgstation.dme b/tgstation.dme index b9767fd0f49..9c1aa1c456c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -94,6 +94,7 @@ #include "code\__DEFINES\generators.dm" #include "code\__DEFINES\ghost.dm" #include "code\__DEFINES\gravity.dm" +#include "code\__DEFINES\guardian_defines.dm" #include "code\__DEFINES\hud.dm" #include "code\__DEFINES\icon_smoothing.dm" #include "code\__DEFINES\id_cards.dm" @@ -3915,6 +3916,7 @@ #include "code\modules\mob\living\simple_animal\friendly\drone\verbs.dm" #include "code\modules\mob\living\simple_animal\friendly\drone\visuals_icons.dm" #include "code\modules\mob\living\simple_animal\guardian\guardian.dm" +#include "code\modules\mob\living\simple_animal\guardian\guardian_creator.dm" #include "code\modules\mob\living\simple_animal\guardian\types\assassin.dm" #include "code\modules\mob\living\simple_animal\guardian\types\charger.dm" #include "code\modules\mob\living\simple_animal\guardian\types\dextrous.dm"