From ed4ffde5f4fa2049111baceccfa153148478ffe8 Mon Sep 17 00:00:00 2001 From: IndieanaJones <47086570+IndieanaJones@users.noreply.github.com> Date: Sun, 21 Aug 2022 19:08:09 -0700 Subject: [PATCH] Space Dragon Antag Datum Refactor Take 2 (#68988) * Space Dragon Antag Datum Refactor Take 2 Space Dragon's antag datum and the mob has been separated, so it is now possible to have non-dragon space dragons, with customizable spawned mobs and factions. --- code/game/objects/items/dna_probe.dm | 3 +- .../antagonists/space_dragon/carp_rift.dm | 239 ++++++++++++ .../antagonists/space_dragon/space_dragon.dm | 128 ++++++- .../simple_animal/hostile/space_dragon.dm | 340 ------------------ tgstation.dme | 1 + 5 files changed, 368 insertions(+), 343 deletions(-) create mode 100644 code/modules/antagonists/space_dragon/carp_rift.dm diff --git a/code/game/objects/items/dna_probe.dm b/code/game/objects/items/dna_probe.dm index 64bf1f8a191..8a2f61b6aa7 100644 --- a/code/game/objects/items/dna_probe.dm +++ b/code/game/objects/items/dna_probe.dm @@ -97,7 +97,8 @@ if(!do_after(user, CARP_MIX_DNA_TIMER)) return var/mob/living/simple_animal/hostile/space_dragon/new_dragon = user.change_mob_type(/mob/living/simple_animal/hostile/space_dragon, location = loc, delete_old_mob = TRUE) - new_dragon.permanant_empower() + new_dragon.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 5)) + new_dragon.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) priority_announce("A large organic energy flux has been recorded near of [station_name()], please stand-by.", "Lifesign Alert") qdel(src) diff --git a/code/modules/antagonists/space_dragon/carp_rift.dm b/code/modules/antagonists/space_dragon/carp_rift.dm new file mode 100644 index 00000000000..85ed8eeb751 --- /dev/null +++ b/code/modules/antagonists/space_dragon/carp_rift.dm @@ -0,0 +1,239 @@ +/// The carp rift is currently charging. +#define CHARGE_ONGOING 0 +/// The carp rift is currently charging and has output a final warning. +#define CHARGE_FINALWARNING 1 +/// The carp rift is now fully charged. +#define CHARGE_COMPLETED 2 + +/datum/action/innate/summon_rift + name = "Summon Rift" + desc = "Summon a rift to bring forth a horde of space carp." + background_icon_state = "bg_default" + icon_icon = 'icons/mob/actions/actions_space_dragon.dmi' + button_icon_state = "carp_rift" + +/datum/action/innate/summon_rift/Activate() + var/datum/antagonist/space_dragon/dragon = owner.mind?.has_antag_datum(/datum/antagonist/space_dragon) + if(!dragon) + return + var/area/rift_location = get_area(owner) + if(!(rift_location.area_flags & VALID_TERRITORY)) + to_chat(owner, span_warning("You can't summon a rift here! Try summoning somewhere secure within the station!")) + return + for(var/obj/structure/carp_rift/rift as anything in dragon.rift_list) + var/area/used_location = get_area(rift) + if(used_location == rift_location) + to_chat(owner, span_warning("You've already summoned a rift in this area! You have to summon again somewhere else!")) + return + owner.balloon_alert(owner, "You begin to open a rift...") + if(!do_after(owner, 10 SECONDS, target = owner)) + return + if(locate(/obj/structure/carp_rift) in owner.loc) + return + var/obj/structure/carp_rift/new_rift = new(get_turf(owner)) + playsound(owner.loc, 'sound/vehicles/rocketlaunch.ogg', 100, TRUE) + dragon.riftTimer = -1 + new_rift.dragon = dragon + dragon.rift_list += new_rift + to_chat(owner, span_boldwarning("The rift has been summoned. Prevent the crew from destroying it at all costs!")) + notify_ghosts("The Space Dragon has opened a rift!", source = new_rift, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Carp Rift Opened") + qdel(src) + +/** + * # Carp Rift + * + * The portals Space Dragon summons to bring carp onto the station. + * + * The portals Space Dragon summons to bring carp onto the station. His main objective is to summon 3 of them and protect them from being destroyed. + * The portals can summon sentient space carp in limited amounts. The portal also changes color based on whether or not a carp spawn is available. + * Once it is fully charged, it becomes indestructible, and intermitently spawns non-sentient carp. It is still destroyed if Space Dragon dies. + */ +/obj/structure/carp_rift + name = "carp rift" + desc = "A rift akin to the ones space carp use to travel long distances." + armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 50, BIO = 100, FIRE = 100, ACID = 100) + max_integrity = 300 + icon = 'icons/obj/carp_rift.dmi' + icon_state = "carp_rift_carpspawn" + light_color = LIGHT_COLOR_PURPLE + light_range = 10 + anchored = TRUE + density = FALSE + plane = MASSIVE_OBJ_PLANE + /// The amount of time the rift has charged for. + var/time_charged = 0 + /// The maximum charge the rift can have. + var/max_charge = 300 + /// How many carp spawns it has available. + var/carp_stored = 1 + /// A reference to the Space Dragon antag that created it. + var/datum/antagonist/space_dragon/dragon + /// Current charge state of the rift. + var/charge_state = CHARGE_ONGOING + /// The interval for adding additional space carp spawns to the rift. + var/carp_interval = 60 + /// The time since an extra carp was added to the ghost role spawning pool. + var/last_carp_inc = 0 + /// A list of all the ckeys which have used this carp rift to spawn in as carps. + var/list/ckey_list = list() + +/obj/structure/carp_rift/Initialize(mapload) + . = ..() + + AddComponent( \ + /datum/component/aura_healing, \ + range = 0, \ + simple_heal = 5, \ + limit_to_trait = TRAIT_HEALS_FROM_CARP_RIFTS, \ + healing_color = COLOR_BLUE, \ + ) + + START_PROCESSING(SSobj, src) + +// Carp rifts always take heavy explosion damage. Discourages the use of maxcaps +// and favours more weaker explosives to destroy the portal +// as they have the same effect on the portal. +/obj/structure/carp_rift/ex_act(severity, target) + return ..(min(EXPLODE_HEAVY, severity)) + +/obj/structure/carp_rift/examine(mob/user) + . = ..() + if(time_charged < max_charge) + . += span_notice("It seems to be [(time_charged / max_charge) * 100]% charged.") + else + . += span_warning("This one is fully charged. In this state, it is poised to transport a much larger amount of carp than normal.") + + if(isobserver(user)) + . += span_notice("It has [carp_stored] carp available to spawn as.") + +/obj/structure/carp_rift/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) + playsound(src, 'sound/magic/lightningshock.ogg', 50, TRUE) + +/obj/structure/carp_rift/Destroy() + STOP_PROCESSING(SSobj, src) + if(charge_state != CHARGE_COMPLETED) + if(dragon) + to_chat(dragon.owner.current, span_boldwarning("A rift has been destroyed! You have failed, and find yourself weakened.")) + dragon.destroy_rifts() + dragon = null + return ..() + +/obj/structure/carp_rift/process(delta_time) + // If we're fully charged, just start mass spawning carp and move around. + if(charge_state == CHARGE_COMPLETED) + if(DT_PROB(1.25, delta_time) && dragon) + var/mob/living/newcarp = new dragon.ai_to_spawn(loc) + newcarp.faction = dragon.owner.current.faction.Copy() + if(DT_PROB(1.5, delta_time)) + var/rand_dir = pick(GLOB.cardinals) + SSmove_manager.move_to(src, get_step(src, rand_dir), 1) + return + + // Increase time trackers and check for any updated states. + time_charged = min(time_charged + delta_time, max_charge) + last_carp_inc += delta_time + update_check() + +/obj/structure/carp_rift/attack_ghost(mob/user) + . = ..() + if(.) + return + summon_carp(user) + +/** + * Does a series of checks based on the portal's status. + * + * Performs a number of checks based on the current charge of the portal, and triggers various effects accordingly. + * If the current charge is a multiple of carp_interval, add an extra carp spawn. + * If we're halfway charged, announce to the crew our location in a CENTCOM announcement. + * If we're fully charged, tell the crew we are, change our color to yellow, become invulnerable, and give Space Dragon the ability to make another rift, if he hasn't summoned 3 total. + */ +/obj/structure/carp_rift/proc/update_check() + // If the rift is fully charged, there's nothing to do here anymore. + if(charge_state == CHARGE_COMPLETED) + return + + // Can we increase the carp spawn pool size? + if(last_carp_inc >= carp_interval) + carp_stored++ + icon_state = "carp_rift_carpspawn" + if(light_color != LIGHT_COLOR_PURPLE) + set_light_color(LIGHT_COLOR_PURPLE) + update_light() + notify_ghosts("The carp rift can summon an additional carp!", source = src, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "Carp Spawn Available") + last_carp_inc -= carp_interval + + // Is the rift now fully charged? + if(time_charged >= max_charge) + charge_state = CHARGE_COMPLETED + var/area/A = get_area(src) + priority_announce("Spatial object has reached peak energy charge in [initial(A.name)], please stand-by.", "Central Command Wildlife Observations") + atom_integrity = INFINITY + icon_state = "carp_rift_charged" + set_light_color(LIGHT_COLOR_YELLOW) + update_light() + armor = list(MELEE = 100, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 100, BIO = 100, FIRE = 100, ACID = 100) + resistance_flags = INDESTRUCTIBLE + dragon.rifts_charged += 1 + if(dragon.rifts_charged != 3 && !dragon.objective_complete) + var/datum/action/innate/summon_rift/rift = new() + rift.Grant(dragon.owner.current) + dragon.riftTimer = 0 + dragon.rift_empower() + // Early return, nothing to do after this point. + return + + // Do we need to give a final warning to the station at the halfway mark? + if(charge_state < CHARGE_FINALWARNING && time_charged >= (max_charge * 0.5)) + charge_state = CHARGE_FINALWARNING + var/area/A = get_area(src) + priority_announce("A rift is causing an unnaturally large energy flux in [initial(A.name)]. Stop it at all costs!", "Central Command Wildlife Observations", ANNOUNCER_SPANOMALIES) + +/** + * Used to create carp controlled by ghosts when the option is available. + * + * Creates a carp for the ghost to control if we have a carp spawn available. + * Gives them prompt to control a carp, and if our circumstances still allow if when they hit yes, spawn them in as a carp. + * Also add them to the list of carps in Space Dragon's antgonist datum, so they'll be displayed as having assisted him on round end. + * Arguments: + * * mob/user - The ghost which will take control of the carp. + */ +/obj/structure/carp_rift/proc/summon_carp(mob/user) + if(carp_stored <= 0)//Not enough carp points + return FALSE + var/is_listed = FALSE + if (user.ckey in ckey_list) + if(carp_stored == 1) + to_chat(user, span_warning("You've already become a carp using this rift! Either wait for a backlog of carp spawns or until the next rift!")) + return FALSE + is_listed = TRUE + var/carp_ask = tgui_alert(user, "Become a carp?", "Carp Rift", list("Yes", "No")) + if(carp_ask != "Yes" || QDELETED(src) || QDELETED(user)) + return FALSE + if(carp_stored <= 0) + to_chat(user, span_warning("The rift already summoned enough carp!")) + return FALSE + + if(!dragon) + return + var/mob/living/newcarp = new dragon.minion_to_spawn(loc) + newcarp.faction = dragon.owner.current.faction + newcarp.AddElement(/datum/element/nerfed_pulling, GLOB.typecache_general_bad_things_to_easily_move) + newcarp.AddElement(/datum/element/prevent_attacking_of_types, GLOB.typecache_general_bad_hostile_attack_targets, "this tastes awful!") + + if(!is_listed) + ckey_list += user.ckey + newcarp.key = user.key + newcarp.set_name() + dragon.carp += newcarp.mind + to_chat(newcarp, span_boldwarning("You have arrived in order to assist the space dragon with securing the rifts. Do not jeopardize the mission, and protect the rifts at all costs!")) + carp_stored-- + if(carp_stored <= 0 && charge_state < CHARGE_COMPLETED) + icon_state = "carp_rift" + set_light_color(LIGHT_COLOR_BLUE) + update_light() + return TRUE + +#undef CHARGE_ONGOING +#undef CHARGE_FINALWARNING +#undef CHARGE_COMPLETED diff --git a/code/modules/antagonists/space_dragon/space_dragon.dm b/code/modules/antagonists/space_dragon/space_dragon.dm index 41b39425526..5c570298f95 100644 --- a/code/modules/antagonists/space_dragon/space_dragon.dm +++ b/code/modules/antagonists/space_dragon/space_dragon.dm @@ -10,6 +10,20 @@ var/list/datum/mind/carp = list() /// The innate ability to summon rifts var/datum/action/innate/summon_rift/rift_ability + /// Current time since the the last rift was activated. If set to -1, does not increment. + var/riftTimer = 0 + /// Maximum amount of time which can pass without a rift before Space Dragon despawns. + var/maxRiftTimer = 300 + /// A list of all of the rifts created by Space Dragon. Used for setting them all to infinite carp spawn when Space Dragon wins, and removing them when Space Dragon dies. + var/list/obj/structure/carp_rift/rift_list = list() + /// How many rifts have been successfully charged + var/rifts_charged = 0 + /// Whether or not Space Dragon has completed their objective, and thus triggered the ending sequence. + var/objective_complete = FALSE + /// What mob to spawn from ghosts using this dragon's rifts + var/minion_to_spawn = /mob/living/simple_animal/hostile/carp + /// What AI mobs to spawn from this dragon's rifts + var/ai_to_spawn = /mob/living/simple_animal/hostile/carp /datum/antagonist/space_dragon/greet() . = ..() @@ -31,10 +45,21 @@ . = ..() rift_ability = new rift_ability.Grant(owner.current) + owner.current.faction |= "carp" + RegisterSignal(owner.current, COMSIG_LIVING_LIFE, .proc/rift_checks) + RegisterSignal(owner.current, COMSIG_LIVING_DEATH, .proc/destroy_rifts) /datum/antagonist/space_dragon/on_removal() . = ..() rift_ability.Remove(owner.current) + owner.current.faction -= "carp" + UnregisterSignal(owner.current, COMSIG_LIVING_LIFE) + UnregisterSignal(owner.current, COMSIG_LIVING_DEATH) + rift_list = null + +/datum/antagonist/space_dragon/Destroy() + rift_list = null + return ..() /datum/antagonist/space_dragon/get_preview_icon() var/icon/icon = icon('icons/mob/spacedragon.dmi', "spacedragon") @@ -47,6 +72,104 @@ return icon +/** + * Checks to see if we need to do anything with the current state of the dragon's rifts. + * + * A simple update check which sees if we need to do anything based on the current state of the dragon's rifts. + * + */ +/datum/antagonist/space_dragon/proc/rift_checks() + if((rifts_charged == 3 || (SSshuttle.emergency.mode == SHUTTLE_DOCKED && rifts_charged > 0)) && !objective_complete) + victory() + return + if(riftTimer == -1) + return + riftTimer = min(riftTimer + 1, maxRiftTimer + 1) + if(riftTimer == (maxRiftTimer - 60)) + to_chat(owner.current, span_boldwarning("You have a minute left to summon the rift! Get to it!")) + return + if(riftTimer >= maxRiftTimer) + to_chat(owner.current, span_boldwarning("You've failed to summon the rift in a timely manner! You're being pulled back from whence you came!")) + destroy_rifts() + SEND_SOUND(owner.current, sound('sound/magic/demon_dies.ogg')) + QDEL_NULL(owner.current) + +/** + * Destroys all of Space Dragon's current rifts. + * + * QDeletes all the current rifts after removing their references to other objects. + * Currently, the only reference they have is to the Dragon which created them, so we clear that before deleting them. + * Currently used when Space Dragon dies or one of his rifts is destroyed. + */ +/datum/antagonist/space_dragon/proc/destroy_rifts() + if(objective_complete) + return + rifts_charged = 0 + owner.current.add_movespeed_modifier(/datum/movespeed_modifier/dragon_depression) + riftTimer = -1 + SEND_SOUND(owner.current, sound('sound/vehicles/rocketlaunch.ogg')) + for(var/obj/structure/carp_rift/rift as anything in rift_list) + rift.dragon = null + rift_list -= rift + if(!QDELETED(rift)) + QDEL_NULL(rift) + +/** + * Sets up Space Dragon's victory for completing the objectives. + * + * Triggers when Space Dragon completes his objective. + * Calls the shuttle with a coefficient of 3, making it impossible to recall. + * Sets all of his rifts to allow for infinite sentient carp spawns + * Also plays appropiate sounds and CENTCOM messages. + */ +/datum/antagonist/space_dragon/proc/victory() + objective_complete = TRUE + permanant_empower() + var/datum/objective/summon_carp/main_objective = locate() in objectives + if(main_objective) + main_objective.completed = TRUE + priority_announce("A large amount of lifeforms have been detected approaching [station_name()] at extreme speeds. \ + Remaining crew are advised to evacuate as soon as possible.", "Central Command Wildlife Observations") + sound_to_playing_players('sound/creatures/space_dragon_roar.ogg') + for(var/obj/structure/carp_rift/rift as anything in rift_list) + rift.carp_stored = 999999 + rift.time_charged = rift.max_charge + +/** + * Gives Space Dragon their the rift speed buff permanantly and fully heals the user. + * + * Gives Space Dragon the enraged speed buff from charging rifts permanantly. + * Only happens in circumstances where Space Dragon completes their objective. + * Also gives them a full heal. + */ +/datum/antagonist/space_dragon/proc/permanant_empower() + owner.current.fully_heal() + owner.current.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 5)) + owner.current.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) + +/** + * Handles Space Dragon's temporary empowerment after boosting a rift. + * + * Empowers and depowers Space Dragon after a successful rift charge. + * Empowered, Space Dragon regains all his health and becomes temporarily faster for 30 seconds, along with being tinted red. + */ +/datum/antagonist/space_dragon/proc/rift_empower() + owner.current.fully_heal() + owner.current.add_filter("anger_glow", 3, list("type" = "outline", "color" = "#ff330030", "size" = 5)) + owner.current.add_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) + addtimer(CALLBACK(src, .proc/rift_depower), 30 SECONDS) + +/** + * Removes Space Dragon's rift speed buff. + * + * Removes Space Dragon's speed buff from charging a rift. This is only called + * in rift_empower, which uses a timer to call this after 30 seconds. Also + * removes the red glow from Space Dragon which is synonymous with the speed buff. + */ +/datum/antagonist/space_dragon/proc/rift_depower() + owner.current.remove_filter("anger_glow") + owner.current.remove_movespeed_modifier(/datum/movespeed_modifier/dragon_rage) + /datum/objective/summon_carp var/datum/antagonist/space_dragon/dragon explanation_text = "Summon and protect the rifts to flood the station with carp." @@ -68,6 +191,7 @@ parts += "The [name] was successful!" else parts += "The [name] has failed!" - parts += "The [name] was assisted by:" - parts += printplayerlist(carp) + if(carp.len) + parts += "The [name] was assisted by:" + parts += printplayerlist(carp) return "