diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm index e6e1e64e9c7e..28f1cfcaac22 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm @@ -78,3 +78,6 @@ /// From /mob/living/carbon/xenomorph/proc/hivemind_talk(): (message) #define COMSIG_XENO_TRY_HIVEMIND_TALK "xeno_try_hivemind_talk" #define COMPONENT_OVERRIDE_HIVEMIND_TALK (1<<0) + +/// From /mob/living/carbon/xenomorph/proc/do_evolve() +#define COMSIG_XENO_EVOLVE_TO_NEW_CASTE "xeno_evolve_to_new_caste" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index e2bd868f9a80..21d66003ffdc 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -381,3 +381,13 @@ GLOBAL_LIST_INIT(default_xeno_onmob_icons, list( #define MOBILITY_FLAGS_DEFAULT (MOBILITY_MOVE | MOBILITY_STAND) #define MOBILITY_FLAGS_CARBON_DEFAULT (MOBILITY_MOVE | MOBILITY_STAND | MOBILITY_REST | MOBILITY_LIEDOWN) #define MOBILITY_FLAGS_REST_CAPABLE_DEFAULT (MOBILITY_MOVE | MOBILITY_STAND | MOBILITY_REST | MOBILITY_LIEDOWN) + +// SLEEP CHECK DEATH + +#define SLEEP_CHECK_DEATH(X, A) \ + sleep(X); \ + if(QDELETED(A)) return; \ + if(ismob(A)) { \ + var/mob/sleep_check_death_mob = A; \ + if(sleep_check_death_mob.stat == DEAD) return; \ + } diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm index fb9d6bfb4faf..173726a9cbfe 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno.dm @@ -255,6 +255,7 @@ #define XENO_HEALTH_TIER_14 950 * XENO_UNIVERSAL_HPMULT #define XENO_HEALTH_QUEEN 1000 * XENO_UNIVERSAL_HPMULT #define XENO_HEALTH_IMMORTAL 1200 * XENO_UNIVERSAL_HPMULT +#define XENO_HEALTH_DESTROYER 1500 * XENO_UNIVERSAL_HPMULT // Plasma bands #define XENO_NO_PLASMA 0 @@ -665,6 +666,7 @@ #define XENO_SHIELD_SOURCE_GARDENER 8 #define XENO_SHIELD_SOURCE_SHIELD_PILLAR 9 #define XENO_SHIELD_SOURCE_CUMULATIVE_GENERIC 10 +#define XENO_SHIELD_SOURCE_DESTROYER_BULWARKSPELL 11 //XENO CASTES #define XENO_CASTE_LARVA "Bloody Larva" @@ -693,13 +695,17 @@ #define XENO_CASTE_CRUSHER "Crusher" #define XENO_CASTE_RAVAGER "Ravager" #define XENO_T3_CASTES list(XENO_CASTE_BOILER, XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER, XENO_CASTE_RAVAGER) -//special + +//Tier 4 +#define XENO_CASTE_DESTROYER "Destroyer" #define XENO_CASTE_QUEEN "Queen" + +//special #define XENO_CASTE_PREDALIEN "Predalien" #define XENO_CASTE_HELLHOUND "Hellhound" #define XENO_SPECIAL_CASTES list(XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND) -#define ALL_XENO_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER, XENO_CASTE_LESSER_DRONE, XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_SENTINEL, XENO_CASTE_DEFENDER, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER, XENO_CASTE_BOILER, XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER, XENO_CASTE_RAVAGER, XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND) +#define ALL_XENO_CASTES list(XENO_CASTE_LARVA, XENO_CASTE_PREDALIEN_LARVA, XENO_CASTE_FACEHUGGER, XENO_CASTE_LESSER_DRONE, XENO_CASTE_DRONE, XENO_CASTE_RUNNER, XENO_CASTE_SENTINEL, XENO_CASTE_DEFENDER, XENO_CASTE_BURROWER, XENO_CASTE_CARRIER, XENO_CASTE_HIVELORD, XENO_CASTE_LURKER, XENO_CASTE_WARRIOR, XENO_CASTE_SPITTER, XENO_CASTE_BOILER, XENO_CASTE_PRAETORIAN, XENO_CASTE_CRUSHER, XENO_CASTE_RAVAGER, XENO_CASTE_QUEEN, XENO_CASTE_PREDALIEN, XENO_CASTE_HELLHOUND, XENO_CASTE_DESTROYER) // Checks if two hives are allied to each other. // PARAMETERS: diff --git a/code/datums/xeno_shields/shield_types/destroyer_shield.dm b/code/datums/xeno_shields/shield_types/destroyer_shield.dm new file mode 100644 index 000000000000..ad2b58f06933 --- /dev/null +++ b/code/datums/xeno_shields/shield_types/destroyer_shield.dm @@ -0,0 +1,18 @@ +/datum/xeno_shield/destroyer_shield + duration = 10 SECONDS + decay_amount_per_second = 100 + var/percent_maxhealth_damagecap = 0.1 + +/datum/xeno_shield/destroyer_shield/on_hit(damage) + var/relative_damage_cap = linked_xeno.maxHealth * percent_maxhealth_damagecap + + if(damage > relative_damage_cap) + damage = relative_damage_cap + return ..(damage) + + +/datum/xeno_shield/destroyer_shield/on_removal() + . = ..() + if(linked_xeno) + // Remove the shield overlay early + linked_xeno.remove_suit_layer() diff --git a/code/datums/xeno_shields/xeno_shield.dm b/code/datums/xeno_shields/xeno_shield.dm index 49f04f19e6e6..dba5698fc53e 100644 --- a/code/datums/xeno_shields/xeno_shield.dm +++ b/code/datums/xeno_shields/xeno_shield.dm @@ -61,7 +61,7 @@ // Use the type var if you need to construct a shield with different on hit behavior, damage reduction, etc. /mob/living/carbon/xenomorph/proc/add_xeno_shield(\ added_amount, shield_source, type = /datum/xeno_shield, \ - duration = -1, decay_amount_per_second = 1, \ + duration, decay_amount_per_second, \ add_shield_on = FALSE, max_shield = 200) for (var/datum/xeno_shield/curr_shield in xeno_shields) if (shield_source == curr_shield.shield_source) @@ -78,12 +78,14 @@ new_shield.shield_source = shield_source xeno_shields += new_shield new_shield.last_damage_taken = world.time // So we don't insta-delete our shield. - - new_shield.decay_amount_per_second = decay_amount_per_second + if(decay_amount_per_second) + new_shield.decay_amount_per_second = decay_amount_per_second + if(duration) + new_shield.duration = duration new_shield.linked_xeno = src - if(duration > -1) - addtimer(CALLBACK(new_shield, TYPE_PROC_REF(/datum/xeno_shield, begin_decay)), duration) + if(new_shield.duration > -1) + addtimer(CALLBACK(new_shield, TYPE_PROC_REF(/datum/xeno_shield, begin_decay)), new_shield.duration) overlay_shields() return new_shield diff --git a/code/game/jobs/role_authority.dm b/code/game/jobs/role_authority.dm index 58c9ad5b5092..c09a93669d50 100644 --- a/code/game/jobs/role_authority.dm +++ b/code/game/jobs/role_authority.dm @@ -760,6 +760,8 @@ I hope it's easier to tell what the heck this proc is even doing, unlike previou M = /mob/living/carbon/xenomorph/predalien if(XENO_CASTE_HELLHOUND) M = /mob/living/carbon/xenomorph/hellhound + if(XENO_CASTE_DESTROYER) + M = /mob/living/carbon/xenomorph/destroyer return M diff --git a/code/game/objects/effects/effect_system/smoke.dm b/code/game/objects/effects/effect_system/smoke.dm index c9e404ae5b60..8baddac87f67 100644 --- a/code/game/objects/effects/effect_system/smoke.dm +++ b/code/game/objects/effects/effect_system/smoke.dm @@ -660,3 +660,6 @@ S.time_to_live = lifetime if(S.amount) S.spread_smoke(direction) + +/datum/effect_system/smoke_spread/destroyer_doom + smoke_type = /obj/effect/particle_effect/smoke diff --git a/code/game/objects/effects/temporary_visuals.dm b/code/game/objects/effects/temporary_visuals.dm index 4dc07b76f3cb..4868a6b779c6 100644 --- a/code/game/objects/effects/temporary_visuals.dm +++ b/code/game/objects/effects/temporary_visuals.dm @@ -21,6 +21,19 @@ . = ..() deltimer(timerid) +//----------------------------------------- +//HEAVY IMPACT +//----------------------------------------- + +/obj/effect/temp_visual/heavy_impact + icon = 'icons/effects/heavyimpact.dmi' + icon_state = "heavyimpact" + duration = 13 + +/obj/effect/temp_visual/heavyimpact/Initialize(mapload) + . = ..() + flick("heavyimpact", src) + /obj/effect/temp_visual/dir_setting randomdir = FALSE @@ -95,4 +108,3 @@ /obj/effect/temp_visual/dir_setting/bloodsplatter/synthsplatter splatter_type = "csplatter" color = BLOOD_COLOR_SYNTHETIC - diff --git a/code/modules/cm_aliens/XenoStructures.dm b/code/modules/cm_aliens/XenoStructures.dm index c014fbf9c211..94fb108110d3 100644 --- a/code/modules/cm_aliens/XenoStructures.dm +++ b/code/modules/cm_aliens/XenoStructures.dm @@ -844,6 +844,29 @@ return ..() +/obj/effect/alien/destroyer_cocoon + name = "alien cocoon" + desc = "A large pulsating cocoon." + health = 500 + icon_state = "Egg" + + var/timer + +/obj/effect/alien/destroyer_cocoon/Initialize(mapload, pylon) + ..() + timer = addtimer(CALLBACK(src, PROC_REF(hatch_destroyer)), 10 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + +/// Roll for candidates to take the destroyer +/obj/effect/alien/destroyer_cocoon/proc/roll_candidates() + return + +/// Hatch the destroyer from the egg +/obj/effect/alien/destroyer_cocoon/proc/hatch_destroyer() + // BIRDTALON ADD HIVENUMBER STUFF + var/mob/living/carbon/xenomorph/destroyer/destroyer = new(get_turf(src)) + destroyer.emote("roar") + qdel(src) + /obj/item/explosive/grenade/alien name = "alien grenade" desc = "an alien grenade." diff --git a/code/modules/cm_aliens/hivebuffs/hivebuff.dm b/code/modules/cm_aliens/hivebuffs/hivebuff.dm new file mode 100644 index 000000000000..8934884c60a0 --- /dev/null +++ b/code/modules/cm_aliens/hivebuffs/hivebuff.dm @@ -0,0 +1,465 @@ +/***************************** +* +* HIVE BUFFS - XENOMORPH ENDGAME +* Contains all the class definitons and code for applying hivebuffs to xeno hives. +* Each buff consists of a /datum/hivebuff +* And associated on_engage and on_cease procs to handle behaviour of activating and ending the buffs +* Buffs are divided into 2 tiers, minor and major. +* +*****************************/ + +///GLOBAL DEFINES/// + +#define HIVE_STARTING_BUFFPOINTS 10 +#define BUFF_POINTS_NAME "Royal resin" + +///LOCAL DEFINES/// + +#define HIVEBUFF_TIER_MINOR "Minor" +#define HIVEBUFF_TIER_MAJOR "Major" + + +///////////////////////////// + +/datum/hivebuff + /// Timer id to call on_cease() if neccessary to end the effects. + var/_timer_id + /// The hive that this buff is applied to. + var/datum/hive_status/hive + /// List of pylons sustaining the hive buff, can be one or two pylons + var/list/sustained_pylons + ///Name of the buff, short and to the point + var/name = "Hivebuff" + /// Description of what the buff does. + var/desc = "Base hivebuff" + /// Path to .dmi with hivebuff radial icons + var/hivebuff_radial_dmi = 'icons/ui_icons/hivebuff_radial.dmi' + /// Image to display on radial menu + var/radial_icon = "health" + /// Round time before the buff becomes available to purchase + var/roundtime_to_enable = 0 HOURS + + /// Flavour message to announce to the hive on buff application. Narrated to all players in the hive. + var/engage_flavourmessage = "The qween has purchased a buff UwU!" + /// Flavour message to announce to the hive on buff expiry. Narrated to all players in the hive. + var/cease_flavourmessage = "Oh noes! =>.<= our buff has expired!!" + + /// Minor or Major buff. Governs announcements made and importance. + var/tier = HIVEBUFF_TIER_MINOR + /// Number of pylons required to buy the buff + var/number_of_required_pylons = 1 + ///If this buff can be used with others + var/is_unique = TRUE + ///If this buff can be used more than once a round. + var/is_reusable = FALSE + /// Time that the buff is active for if it is a timed buff. + var/duration + /// Cost of the buff + var/cost = 1 + + /// Message to send to the user and queen if we fail for any reason during on_engage() + var/engage_failure_message + + /// TRUE when buff has been ended via sustained_pylon Pylon qdeletion + var/ended_via_pylon_qdeletion = FALSE + + /// Flavour message to give to the marines on buff engage + var/marine_flavourmessage + + /// Apply the buff effect to new xenomorphs who spawn or evolve. + var/apply_on_new_xeno = TRUE + + +/datum/hivebuff/New(datum/hive_status/xenohive) + . = ..() + if(!xenohive || !istype(xenohive)) + stack_trace("Hivebuff created without correct hive_status passed.") + return FALSE + hive = xenohive + + return TRUE + +/datum/hivebuff/Destroy(force, ...) + LAZYREMOVE(hive.active_hivebuffs, src) + hive = null + sustained_pylons = null + . = ..() + +/// If the pylon sustaining this hive buff is destroyed for any reason +/datum/hivebuff/proc/_on_pylon_deletion(obj/effect/alien/resin/special/pylon/sustained_pylon) + SIGNAL_HANDLER + ended_via_pylon_qdeletion = TRUE + if(_timer_id) + deltimer(_timer_id) + UnregisterSignal(sustained_pylon, COMSIG_PARENT_QDELETING) + announce_buff_loss(sustained_pylon) + sustained_pylons =- src + // If this is also being sustained by the other pylon(s) clear any references to this + if(LAZYLEN(sustained_pylons)) + for(var/obj/effect/alien/resin/special/pylon/endgame/pylon in sustained_pylons) + pylon.remove_hivebuff() + _on_cease() + +/datum/hivebuff/proc/announce_buff_loss(obj/effect/alien/resin/special/pylon/sustained_pylon) + xeno_announcement("Our pylon at [sustained_pylon.loc] has been destroyed!! Our hive buff [name] has waned...", hive.hivenumber, "Hive Buff Wanes!") + +///Wrapper for on_engage(), handles checking if the buff can be actually purchased as well as adding buff to the active_hivebuffs and used_hivebuffs for the hive. +/datum/hivebuff/proc/_on_engage(mob/living/carbon/xenomorph/purchasing_mob, obj/effect/alien/resin/special/pylon/endgame/purchased_pylon) + var/list/pylons_to_use = list() + + if(!_roundtime_check()) + to_chat(purchasing_mob, SPAN_XENONOTICE("Our hive is not mature enough yet to purchase this!")) + return + + if(!_check_num_required_pylons(purchased_pylon)) + to_chat(purchasing_mob, SPAN_XENONOTICE("Our hive does not have the required number of available pylons! We require [number_of_required_pylons]")) + return FALSE + //Add purchasing pylon to list of pylons to use. + pylons_to_use += purchased_pylon + //If we need more pylons then add them to the list to handle setting up the buffs later + if(number_of_required_pylons > 1) + for(var/obj/effect/alien/resin/special/pylon/endgame/potential_pylon in hive.active_endgame_pylons) + // Already in the list, move onto the next pylon + if(potential_pylon == purchased_pylon) + continue + // We have enough pylons already break the loop + if(length(pylons_to_use) == number_of_required_pylons) + break + // Add the pylon to the list + pylons_to_use += potential_pylon + + if(!_check_can_afford_buff()) + to_chat(purchasing_mob, SPAN_XENONOTICE("Our hive cannot afford [name]! [hive.buff_points] / [cost] points.")) + return FALSE + + if(!_check_pass_active()) + to_chat(purchasing_mob, SPAN_XENONOTICE("[name] is already active in our hive!")) + return FALSE + + if(!_check_pass_reusable()) + to_chat(purchasing_mob, SPAN_XENONOTICE("Our hive has already used [name] and cannot use it again!")) + return FALSE + + if(!_check_pass_unique()) + var/active_buffs = "" + for(var/buff in hive.active_hivebuffs) + active_buffs += buff + " " + active_buffs = trim_right(active_buffs) + to_chat(purchasing_mob, SPAN_XENONOTICE("[name] cannot be used with other active buffs! Wait for those to end first. Active buffs: [active_buffs]")) + return FALSE + + log_admin("[purchasing_mob] of [hive.hivenumber] is attempting to purchase a hive buff: [name].") + + if(!_seek_queen_approval(purchasing_mob)) + to_chat(purchasing_mob, SPAN_XENONOTICE("Our queen has not approved the purchase of [name].")) + return FALSE + + // _seek_queen_approval() includes a 20 second timeout so we check that everything still exists that we need. + if(QDELETED(purchased_pylon) || QDELETED(purchasing_mob) && !purchasing_mob.check_state()) + return FALSE + + // Actually process the buff and apply effects - If the buff succeeds engage_message will return TRUE, if it fails there should be an engage_failure_message set. + if(!on_engage(purchased_pylon)) + if(engage_failure_message && istext(engage_failure_message)) + to_chat(purchasing_mob, SPAN_XENONOTICE(engage_failure_message)) + to_chat(hive.living_xeno_queen, SPAN_XENONOTICE(engage_failure_message)) + return + else + stack_trace("[purchasing_mob] attempted to purchase a hive buff: [name] and failed to engage and returned an invalid failure message or no failure message.") + return + + // All checks have passed. + + // Purchase and deduct funds only after we're sure the buff has engaged + _purchase_and_deduct() + + for(var/mob/living/carbon/xenomorph/xeno in hive.totalXenos) + if(apply_on_new_xeno) + RegisterSignal(SSdcs, COMSIG_GLOB_XENO_SPAWN, PROC_REF(_handle_xenomorph_new)) + apply_buff_effects(xeno) + + + log_admin("[purchasing_mob] and [hive.living_xeno_queen] of [hive.hivenumber] have purchased a hive buff: [name].") + + // Add to the relevant hive lists. + LAZYADD(hive.used_hivebuffs, src) + LAZYADD(hive.active_hivebuffs, src) + + // Register signal to check if the pylon is ever destroyed. + + for(var/obj/effect/alien/resin/special/pylon/endgame/pylon_to_register in pylons_to_use) + LAZYADD(sustained_pylons, purchased_pylon) + pylon_to_register.sustain_hivebuff(src) + RegisterSignal(pylon_to_register, COMSIG_PARENT_QDELETING, PROC_REF(_on_pylon_deletion)) + + // Announce to our hive that we've completed. + _announce_buff_engage() + + // If we need a timer to call _on_cease() we add it here and store the id, used for deleting the timer if we Destroy(). + // If we have no duration to the buff then we call _on_cease() immediately. + if(duration) + _timer_id = addtimer(CALLBACK(src, PROC_REF(_on_cease)), duration, TIMER_STOPPABLE) + else + _on_cease() + return TRUE + +/// Behaviour for the buff goes in here. +/// IMPORTANT: If you buff has any kind of conditions which can fail. Set an engage_failure_message and return FALSE. +/// If your buff succeeds you must return TRUE +/datum/hivebuff/proc/on_engage(obj/effect/alien/resin/special/pylon/purchased_pylon) + return TRUE + +/// Wrapper for on_cease(), calls qdel(src) after on_cease() behaviour. +/datum/hivebuff/proc/_on_cease() + _announce_buff_cease() + /// Clear refernces to this buff and unregister signal + on_cease() + if(!ended_via_pylon_qdeletion) + for(var/obj/effect/alien/resin/special/pylon/endgame/pylon_to_clear in sustained_pylons) + pylon_to_clear.remove_hivebuff() + UnregisterSignal(pylon_to_clear, COMSIG_PARENT_QDELETING) + LAZYREMOVE(hive.active_hivebuffs, src) + + +/// Checks the number of pylons required and if the hive posesses them +/datum/hivebuff/proc/_check_num_required_pylons(obj/effect/alien/resin/special/pylon/endgame/purchased_pylon) + var/list/viable_pylons = list() + if(number_of_required_pylons > 1) + for(var/obj/effect/alien/resin/special/pylon/endgame/potential_pylon in hive.active_endgame_pylons) + if(potential_pylon == purchased_pylon) + continue + + // Pylons can only sustain one buff at a time + if(potential_pylon.sustained_buff) + continue + viable_pylons += potential_pylon + + if(length(viable_pylons) >= (number_of_required_pylons - 1)) + return TRUE + return FALSE + return TRUE + +/datum/hivebuff/proc/_roundtime_check() + //BIRDTALON: REMOVE TO_CHAT(WORLD) + to_chat(world, "[ROUND_TIME] > [SSticker.round_start_time] + [roundtime_to_enable]") + if(ROUND_TIME > (SSticker.round_start_time + roundtime_to_enable)) + return TRUE + return FALSE + +/// Checks if the hive can afford to purchase the buff returns TRUE if they can purchase and FALSE if not. +/datum/hivebuff/proc/_check_can_afford_buff() + if(hive.buff_points < cost) + return FALSE + + return TRUE + +/// Checks if this buff is already active in the hive. Returns TRUE if passed FALSE if not. +/datum/hivebuff/proc/_check_pass_active() + for(var/datum/hivebuff/buff as anything in hive.active_hivebuffs) + if(src.type == buff.type) + return FALSE + + return TRUE + +/// Checks if the buff is unique if other buffs are already in use. Return TRUE if passed FALSE if not. +/datum/hivebuff/proc/_check_pass_unique() + if(!is_unique) + return TRUE + + if(LAZYLEN(hive.active_hivebuffs)) + return FALSE + + return TRUE + +/// Checks if the buff is reusable and if it's already been used. Returns TRUE if passed, FALSE if not. +/datum/hivebuff/proc/_check_pass_reusable() + if(is_reusable) + return TRUE + + for(var/datum/hivebuff/buff as anything in hive.used_hivebuffs) + if(src.type == buff.type) + return FALSE + + return TRUE + +/// Deducts points from the hive buff points equal to the cost of the buff +/datum/hivebuff/proc/_purchase_and_deduct() + + hive.buff_points -= cost + return TRUE + +/datum/hivebuff/proc/_seek_queen_approval(mob/living/purchasing_mob) + if(!hive.living_xeno_queen) + return FALSE + + var/mob/living/queen = hive.living_xeno_queen + var/queen_response = tgui_alert(queen, "[purchasing_mob] is trying to Purchase [name] at a cost of [cost] [BUFF_POINTS_NAME]. Our hive has [hive.buff_points] [BUFF_POINTS_NAME]. Do you want to approve it?", "Approve Hive Buff", list("Yes", "No"), 20 SECONDS) + + if(queen_response && queen_response == "Yes") + return TRUE + else + return FALSE + +/// Any effects which need to be ended or ceased gracefully, called when a buff expires. +/datum/hivebuff/proc/on_cease() + return + +/datum/hivebuff/proc/_announce_buff_engage() + if(engage_flavourmessage) + if(tier > HIVEBUFF_TIER_MINOR) + xeno_announcement(engage_flavourmessage, hive.hivenumber, "Buff Purchased") + for(var/mob/xenomorph as anything in hive.totalXenos) + if(!xenomorph.client) + continue + xenomorph.play_screen_text(engage_flavourmessage, override_color = "#740064") + if(tier <= HIVEBUFF_TIER_MINOR) + to_chat(xenomorph, SPAN_XENO(engage_flavourmessage)) + if(marine_flavourmessage) + marine_announcement(marine_flavourmessage, COMMAND_ANNOUNCE, 'sound/AI/bioscan.ogg') + +/datum/hivebuff/proc/_announce_buff_cease() + for(var/mob/living/xenomorph as anything in hive.totalXenos) + if(!xenomorph.client) + continue + xenomorph.play_screen_text(cease_flavourmessage, override_color = "#740064") + to_chat(xenomorph, SPAN_XENO(cease_flavourmessage)) + +///Signal handler for new xenomorphs joining the hive +/datum/hivebuff/proc/_handle_xenomorph_new(mob/living/carbon/xenomorph/new_xeno) + SIGNAL_HANDLER + if(!apply_on_new_xeno) + return + // If we're the same hive as the buff + if(new_xeno.hive == hive) + apply_buff_effects(new_xeno) + +///The actual effects of the buff to apply +/datum/hivebuff/proc/apply_buff_effects(mob/living/carbon/xenomorph/xeno) + return + +/// Reverse the effects here, should be the opposite of apply_effects() +/datum/hivebuff/proc/remove_buff_effects(mob/living/carbon/xenomorph/xeno) + return + +//////////////////////////////// +// BUFFS +//////////////////////////////// + +/datum/hivebuff/extra_larva + name = "Surge of Larva" + desc = "Provides 5 larva instantly to the hive." + radial_icon = "larba" + + engage_flavourmessage = "The queen has purchased 5 extra larva to join the hive!" + + is_reusable = FALSE + +/datum/hivebuff/extra_larva/on_engage() + hive.stored_larva += 5 + return TRUE + +/datum/hivebuff/extra_life + name = "Boon of Plenty" + desc = "Increases all xenomorph health by 10% for 10 seconds" + tier = HIVEBUFF_TIER_MINOR + + engage_flavourmessage = "The queen has imbued us with greater fortitude." + duration = 10 SECONDS + number_of_required_pylons = 2 + +/datum/hivebuff/extra_life/apply_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.maxHealth *= 1.05 + +/datum/hivebuff/extra_life/remove_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.maxHealth = initial(xeno.caste.max_health) + +/datum/hivebuff/extra_life/major + name = "Major Boon of Plenty" + desc = "Increases all xenomorph health by 10% for 10 minutes" + tier = HIVEBUFF_TIER_MAJOR + + engage_flavourmessage = "The queen has imbued us with greater fortitude." + duration = 10 MINUTES + number_of_required_pylons = 2 + radial_icon = "health_m" + +/datum/hivebuff/game_ender_caste + name = "Boon of Destruction" + desc = "A huge behemoth of a Xenomorph which can tear its way through defences and flesh alike." + tier = HIVEBUFF_TIER_MAJOR + is_unique = TRUE + is_reusable = FALSE + +/datum/hivebuff/game_ender_caste/on_engage(obj/effect/alien/resin/special/pylon/purchased_pylon) + var/turf/spawn_turf + for(var/turf/potential_turf in orange(5, purchased_pylon)) + if(potential_turf.density) + continue + var/area/target_area = get_area(potential_turf) + if(target_area.flags_area & AREA_NOTUNNEL) + continue + + // Do we have a turf for the Destroyer to exit after spawning? + var/exit_turf = FALSE + for(var/cardinal in GLOB.cardinals) + var/turf/turf_to_check = get_step(potential_turf, cardinal) + if(turf_to_check.density) + continue + var/area/exit_target_area = get_area(turf_to_check) + if(exit_target_area.flags_area & AREA_NOTUNNEL) + continue + exit_turf = TRUE + break + if(!exit_turf) + engage_failure_message = "Unable to find a viable spawn point for the Destroyer" + return FALSE + + spawn_turf = potential_turf + break + + new /obj/effect/alien/destroyer_cocoon(spawn_turf) + + /// CODE FOR SPAWNING OF THE DESTROYER HERE. + return TRUE + +/datum/hivebuff/defence + name = "Boon of Defence" + desc = "Increases all xenomorph armour by 5% for 5 minutes" + tier = HIVEBUFF_TIER_MINOR + + engage_flavourmessage = "The queen has imbued us with greater chitin." + duration = 5 MINUTES + number_of_required_pylons = 1 + radial_icon = "shield" + +/datum/hivebuff/defence/major + name = "Major Boon of Defence" + desc = "Increases all xenomorph armour by 10% for 10 minutes" + tier = HIVEBUFF_TIER_MAJOR + + engage_flavourmessage = "The queen has imbued us with even greater chitin." + duration = 10 MINUTES + number_of_required_pylons = 2 + radial_icon = "shield_m" + + +/datum/hivebuff/attack + name = "Boon of Aggression" + desc = "Increases all xenomorph damage by 5% for 5 minutes" + tier = HIVEBUFF_TIER_MINOR + + engage_flavourmessage = "The queen has imbued us with slarp claws." + duration = 5 MINUTES + number_of_required_pylons = 1 + radial_icon = "slash" + +/datum/hivebuff/attack/major + name = "Major Boon of Aggression" + desc = "Increases all xenomorph damage by 10% for 10 minutes" + tier = HIVEBUFF_TIER_MAJOR + + engage_flavourmessage = "The queen has imbued us with razer sharp claws." + duration = 10 MINUTES + number_of_required_pylons = 2 + radial_icon = "slash_m" + diff --git a/code/modules/cm_aliens/structures/special/pylon_core.dm b/code/modules/cm_aliens/structures/special/pylon_core.dm index add9646c56ac..42b20984af01 100644 --- a/code/modules/cm_aliens/structures/special/pylon_core.dm +++ b/code/modules/cm_aliens/structures/special/pylon_core.dm @@ -141,7 +141,19 @@ cover_range = WEED_RANGE_CORE var/activated = FALSE + /// Hivebuff being sustained by this pylon + var/datum/hivebuff/sustained_buff + + /// Cooldown between trying to activate a hive buff + COOLDOWN_DECLARE(buff_cooldown) + +/obj/effect/alien/resin/special/pylon/endgame/Initialize(mapload, mob/builder) + . = ..() + LAZYADD(linked_hive.active_endgame_pylons, src) + /obj/effect/alien/resin/special/pylon/endgame/Destroy() + sustained_buff = null + LAZYREMOVE(linked_hive.active_endgame_pylons, src) if(activated) activated = FALSE @@ -162,6 +174,21 @@ linked_hive.hive_ui.update_pylon_status() return ..() +/obj/effect/alien/resin/special/pylon/endgame/update_icon() + ..() + if(sustained_buff) + icon_state = "[initial(icon_state)]_active" + else + icon_state = initial(icon_state) + +/obj/effect/alien/resin/special/pylon/endgame/proc/sustain_hivebuff(datum/hivebuff/buff) + sustained_buff = buff + update_icon() + +/obj/effect/alien/resin/special/pylon/endgame/proc/remove_hivebuff() + sustained_buff = null + update_icon() + /// Checks if all comms towers are connected and then starts end game content on all pylons if they are /obj/effect/alien/resin/special/pylon/endgame/proc/comms_relay_connection() marine_announcement("ALERT.\n\nIrregular build up of energy around communication relays at [get_area(src)], biological hazard detected.\n\nDANGER: Hazard is generating new xenomorph entities, advise urgent termination of hazard by ground forces.", "[MAIN_AI_SYSTEM] Biological Scanner") @@ -195,6 +222,78 @@ linked_hive.convert_partial_larva_to_full_larva() linked_hive.hive_ui.update_burrowed_larva() +/// APPLYING HIVE BUFFS /// + +/obj/effect/alien/resin/special/pylon/endgame/attack_alien(mob/living/carbon/xenomorph/xeno) + choose_hivebuff(xeno) + return XENO_NONCOMBAT_ACTION + ///BIRDTALON: REVERT TO THIS AFTER TESTING + /* + if(!damaged && health == maxhealth && xeno.a_intent == INTENT_HELP && xeno.hivenumber == linked_hive.hivenumber && IS_XENO_LEADER(xeno)) + if(!LAZYISIN(players_on_buff_cooldown, xeno)) + choose_hivebuff(xeno) + return + else + to_chat(xeno, SPAN_XENONOTICE("We cannot choose a hive buff just yet. Try again later.")) + return ..() + else + ..() */ + +/// To choose a hivebuff +/obj/effect/alien/resin/special/pylon/endgame/proc/choose_hivebuff(mob/living/carbon/xenomorph/xeno) + if(!COOLDOWN_FINISHED(src, buff_cooldown)) + to_chat(xeno, SPAN_XENONOTICE("We can't do that again yet!")) + return + var/list/buffs = list() + var/list/names = list() + var/list/radial_images = list() + var/major_available = FALSE + for(var/datum/hivebuff/buff as anything in linked_hive.get_available_hivebuffs()) + var/buffname = initial(buff.name) + names += buffname + buffs[buffname] = buff + if(!major_available) + if(initial(buff.tier) == HIVEBUFF_TIER_MAJOR) + major_available = TRUE + + + if(!length(buffs)) + to_chat(xeno, SPAN_XENONOTICE("No boons are available to us!")) + return + + var/selection + var/list/radial_images_tiers = list(HIVEBUFF_TIER_MINOR = image('icons/ui_icons/hivebuff_radial.dmi', "minor"), HIVEBUFF_TIER_MAJOR = image('icons/ui_icons/hivebuff_radial.dmi', "major")) + + if((xeno.client.prefs && xeno.client.prefs.no_radials_preference)) + selection = tgui_input_list(xeno, "Pick a buff.", "Select Buff", names) + else + var/tier = HIVEBUFF_TIER_MINOR + if(major_available) + tier = show_radial_menu(xeno, src, radial_images_tiers, require_near = TRUE) + + if(tier == HIVEBUFF_TIER_MAJOR) + for(var/filtered_buffname as anything in buffs) + var/datum/hivebuff/filtered_buff = buffs[filtered_buffname] + if(initial(filtered_buff.tier) == HIVEBUFF_TIER_MAJOR) + radial_images[initial(filtered_buff.name)] += image(initial(filtered_buff.hivebuff_radial_dmi), initial(filtered_buff.radial_icon)) + else + for(var/filtered_buffname as anything in buffs) + var/datum/hivebuff/filtered_buff = buffs[filtered_buffname] + if(initial(filtered_buff.tier) == HIVEBUFF_TIER_MINOR) + radial_images[initial(filtered_buff.name)] += image(initial(filtered_buff.hivebuff_radial_dmi), initial(filtered_buff.radial_icon)) + + selection = show_radial_menu(xeno, src, radial_images, radius = 72, require_near = TRUE, tooltips = TRUE) + if(!selection || !Adjacent(xeno)) + return + + if(!buffs[selection]) + to_chat(xeno, "This selection is impossible!") + return FALSE + + xeno.hive.attempt_apply_hivebuff(buffs[selection], xeno, src) + COOLDOWN_START(src, buff_cooldown, 30 SECONDS) + return TRUE + //Hive Core - Generates strong weeds, supports other buildings /obj/effect/alien/resin/special/pylon/core name = XENO_STRUCTURE_CORE diff --git a/code/modules/mob/living/carbon/xenomorph/Evolution.dm b/code/modules/mob/living/carbon/xenomorph/Evolution.dm index b6576b764b51..394cf764c36d 100644 --- a/code/modules/mob/living/carbon/xenomorph/Evolution.dm +++ b/code/modules/mob/living/carbon/xenomorph/Evolution.dm @@ -194,6 +194,8 @@ GLOB.round_statistics.track_new_participant(new_xeno.faction, -1) //so an evolved xeno doesn't count as two. SSround_recording.recorder.track_player(new_xeno) + SEND_SIGNAL(src, COMSIG_XENO_EVOLVE_TO_NEW_CASTE, new_xeno) + /mob/living/carbon/xenomorph/proc/evolve_checks() if(!check_state(TRUE)) return FALSE diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_abilities.dm new file mode 100644 index 000000000000..1c6d289b99c0 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_abilities.dm @@ -0,0 +1,59 @@ + +/// 3 x 3 damage centred on the xenomorph +/datum/action/xeno_action/onclick/rend + name = "Rend" + action_icon_state = "crest_defense" + ability_name = "rend" + macro_path = /datum/action_xeno_action/verb/verb_rend + xeno_cooldown = 2.5 SECONDS + plasma_cost = 50 + ability_primacy = XENO_PRIMARY_ACTION_1 + + var/damage = 25 + + var/slash_sounds = list('sound/weapons/alien_claw_flesh1.ogg', 'sound/weapons/alien_claw_flesh2.ogg', 'sound/weapons/alien_claw_flesh3.ogg', 'sound/weapons/alien_claw_flesh4.ogg', 'sound/weapons/alien_claw_flesh5.ogg', 'sound/weapons/alien_claw_flesh6.ogg') + +/// Screech which puts out lights in a 7 tile radius, slows and dazes. +/datum/action/xeno_action/activable/doom + name = "Doom" + action_icon_state = "crest_defense" + ability_name = "doom" + macro_path = /datum/action_xeno_action/verb/verb_doom + xeno_cooldown = 2.5 SECONDS //90 SECONDS? + plasma_cost = 50 + ability_primacy = XENO_PRIMARY_ACTION_2 + + var/daze_length_seconds = 1 + var/slow_length_seconds = 2 + + var/extinguish_light_range = 7 + + +/// Leap ability, crashing down dealing major damage to mobs and structures in the area. +/datum/action/xeno_action/activable/destroy + name = "Destroy" + action_icon_state = "crest_defense" + ability_name = "destroy" + macro_path = /datum/action/xeno_action/verb/verb_destroy + action_type = XENO_ACTION_ACTIVATE + xeno_cooldown = 1.5 SECONDS // 90 SECONDS + plasma_cost = 0 + ability_primacy = XENO_PRIMARY_ACTION_3 + + var/range = 7 + var/leaping = FALSE + +/// Shield ability, limits the amount of damage from a single instance of damage to 10% of the xenomorph's max health. +/datum/action/xeno_action/onclick/destroyer_shield + name = "Bulwark of the Hive" + action_icon_state = "crest_defense" + ability_name = "legion_shield" + macro_path = /datum/action_xeno_action/verb/destroyer_shield + action_type = XENO_ACTION_ACTIVATE + xeno_cooldown = 180 SECONDS + plasma_cost = 0 + ability_primacy = XENO_PRIMARY_ACTION_4 + + var/shield_duration = 10 SECONDS + var/area_of_effect = 6 + var/shield_amount = 200 diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_macros.dm b/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_macros.dm new file mode 100644 index 000000000000..6d3aab704161 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_macros.dm @@ -0,0 +1,27 @@ +/datum/action_xeno_action/verb/verb_rend() + set category = "Alien" + set name = "Rend" + set hidden = TRUE + var/action_name = "Rend" + handle_xeno_macro(src, action_name) + +/datum/action/xeno_action/verb/verb_destroy() + set category = "Alien" + set name = "Destroy" + set hidden = TRUE + var/action_name = "Destroy" + handle_xeno_macro(src, action_name) + +/datum/action_xeno_action/verb/verb_doom() + set category = "Alien" + set name = "Doom" + set hidden = TRUE + var/action_name = "Doom" + handle_xeno_macro(src, action_name) + +/datum/action_xeno_action/verb/destroyer_shield() + set category = "Alien" + set name = "Bulwark" + set hidden = TRUE + var/action_name = "Bulwark" + handle_xeno_macro(src, action_name) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_powers.dm new file mode 100644 index 000000000000..7c4f52683fbc --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/destroyer/destroyer_powers.dm @@ -0,0 +1,345 @@ +/* +* TO ADD +* +* CAN NOT HARM CHECKS / DEAD CHECKS? X +* PLASMA CHECKS X +* STATUS CHECKS X +* LOGS X +* EARLY RETURNS? X +* ATTACK VISUALS FOR ALL ABILITIES X +* SOUNDS FOR ALL ABILITIES X +* LAST DAMAGE DATA = cause_data X +*/ + +/* + REND ABILITY + 3x3 aoe damage centred on the destroyer. Basic ability, spammable, low damage. +*/ + +/datum/action/xeno_action/onclick/rend/use_ability() + var/mob/living/carbon/xenomorph/xeno = owner + XENO_ACTION_CHECK_USE_PLASMA(xeno) + + xeno.spin_circle() + xeno.emote("hiss") + for(var/mob/living/carbon/carbon in orange(1, xeno) - xeno) + + if(carbon.stat == DEAD) + continue + if(xeno.can_not_harm(carbon)) + continue + carbon.apply_armoured_damage(damage) + carbon.last_damage_data = create_cause_data(initial(xeno.name), xeno) + xeno.flick_attack_overlay(carbon, "slash") + to_chat(carbon, SPAN_DANGER("[xeno] slices into you with its razor sharp talons.")) + log_attack("[key_name(xeno)] hit [key_name(carbon)] with [name]") + playsound(carbon, pick(slash_sounds), 30, TRUE) + + xeno.visible_message(SPAN_DANGER("[xeno] slices around itself!"), SPAN_NOTICE("We slice around ourself!")) + apply_cooldown() + ..() + + + +/* + DOOM ABILITY + Destroyer channels for a while shrieks which turns off all lights in the vicinity and applies a mild daze + Medium cooldown soft CC +*/ + +/datum/action/xeno_action/activable/doom/use_ability(atom/target) + var/mob/living/carbon/xenomorph/xeno = owner + XENO_ACTION_CHECK_USE_PLASMA(xeno) + + playsound(xeno.loc, 'sound/voice/deep_alien_screech2.ogg', 75, 0, status = 0) + xeno.visible_message(SPAN_XENOHIGHDANGER("[xeno] emits an raspy guttural roar!")) + xeno.create_shriekwave(color = COLOR_RED) //Adds the visual effect. + + var/datum/effect_system/smoke_spread/destroyer_doom/smoke_gas = new /datum/effect_system/smoke_spread/destroyer_doom + smoke_gas.set_up(3, 0, get_turf(xeno), null, 6) + smoke_gas.start() + + // Turn off lights for items in the area dependant on distance. + for(var/obj/item/device/potential_lightsource in orange(extinguish_light_range, owner)) + + var/time_to_extinguish = get_dist(owner, potential_lightsource) SECONDS + + //Flares + if(istype(potential_lightsource, /obj/item/device/flashlight/flare)) + var/obj/item/device/flashlight/flare/flare = potential_lightsource + addtimer(CALLBACK(flare, TYPE_PROC_REF(/obj/item/device/flashlight/flare/, burn_out)), time_to_extinguish) + + //Flashlights + if(istype(potential_lightsource, /obj/item/device/flashlight)) + var/obj/item/device/flashlight/flashlight = potential_lightsource + addtimer(CALLBACK(flashlight, TYPE_PROC_REF(/obj/item/device/flashlight, turn_off_light)), time_to_extinguish) + + //Armour lights + if(istype(potential_lightsource, /obj/item/clothing/suit/storage/marine)) + var/obj/item/clothing/suit/storage/marine/marinearmour = potential_lightsource + addtimer(CALLBACK(marinearmour, TYPE_PROC_REF(/atom, turn_light), null, FALSE), time_to_extinguish) + + // "Confuse" and slow humans in the area and turn off their armour lights. + for(var/mob/living/carbon/human/human in orange(extinguish_light_range, owner)) + human.EyeBlur(daze_length_seconds) + human.Daze(daze_length_seconds) + human.Superslow(slow_length_seconds) + to_chat(human, SPAN_HIGHDANGER("[xeno]'s roar overwhelms your entire being!")) + shake_camera(human, 6, 1) + + var/time_to_extinguish = get_dist(owner, human) SECONDS + var/obj/item/clothing/suit/suit = human.get_item_by_slot(WEAR_JACKET) + if(istype(suit, /obj/item/clothing/suit/storage/marine)) + var/obj/item/clothing/suit/storage/marine/armour = suit + addtimer(CALLBACK(armour, TYPE_PROC_REF(/atom, turn_light), null, FALSE), time_to_extinguish) + + apply_cooldown() + ..() + +/* + BULWARK ABILITY - AoE shield + Long cooldown defensive ability, provides a shield which caps damage taken to 10% of the xeno's max health per individual source of damage. +*/ + +/datum/action/xeno_action/onclick/destroyer_shield/use_ability() + var/mob/living/carbon/xenomorph/xeno = owner + + XENO_ACTION_CHECK_USE_PLASMA(xeno) + + + playsound(xeno.loc, 'sound/voice/deep_alien_screech.ogg', 50, 0, status = 0) + // Add our shield + start_shield(xeno) + + // Add other xeno's shields in AoE range + for(var/mob/living/carbon/xenomorph/xeno_in_aoe in range(area_of_effect, xeno)) + if(xeno_in_aoe.stat == DEAD) + continue + if(xeno_in_aoe.hivenumber != xeno.hivenumber) + continue + start_shield(xeno_in_aoe) + xeno.beam(xeno_in_aoe, "purple_lightning", time = 4 SECONDS) + + apply_cooldown() + return ..() + +/datum/action/xeno_action/onclick/destroyer_shield/proc/start_shield(mob/living/carbon/xenomorph/xeno) + var/datum/xeno_shield/shield = xeno.add_xeno_shield(shield_amount, XENO_SHIELD_SOURCE_DESTROYER_BULWARKSPELL, /datum/xeno_shield/destroyer_shield) + if(shield) + xeno.create_shield(shield_duration, "purple_animated_shield_full") + + +/* + DESTROY ABILITY + Destroyer leaps into the air and crashes down damaging cades and mobs in a 3x3 area centred on him. + Long cooldown high damage ability, massive damage against cades, highly telegraphed. +*/ + +#define LEAP_HEIGHT 210 //how high up leaps go, in pixels +#define LEAP_DIRECTION_CHANGE_RANGE 5 //the range our x has to be within to not change the direction we slam from + +/datum/action/xeno_action/activable/destroy/use_ability(atom/target) + // TODO: Make sure we're not targetting a space tile or a wall etc!!! + + var/mob/living/carbon/xenomorph/xeno = owner + XENO_ACTION_CHECK(xeno) + + if(get_dist(owner, target) > range) + to_chat(xeno, SPAN_XENONOTICE("We cannot leap that far!")) + return + + var/turf/target_turf = get_turf(target) + + if(!target_turf || target_turf.density) + to_chat(xeno, SPAN_XENONOTICE("We cannot leap to that!")) + return + + if(istype(target_turf, /turf/open/space)) + to_chat(xeno, SPAN_XENONOTICE("It would not be wise to try to leap there...")) + return + + var/area/target_area = get_area(target_turf) + if(target_area.flags_area & AREA_NOTUNNEL) + to_chat(xeno, SPAN_XENONOTICE("We cannot leap to that area!")) + + var/list/leap_line = get_line(xeno, target) + for(var/turf/jump_turf in leap_line) + if(jump_turf.density) + to_chat(xeno, SPAN_XENONOTICE("We don't have a clear path to leap to that location!")) + return + + for(var/obj/structure/possible_blocker in jump_turf) + if(possible_blocker.density && !possible_blocker.throwpass) + to_chat(xeno, SPAN_XENONOTICE("There's something blocking us from leaping.")) + return + + if(!check_and_use_plasma_owner()) + to_chat(xeno, SPAN_XENONOTICE("We don't have enough plasma to use [name].")) + return + + var/turf/template_turf = get_step(target_turf, SOUTHWEST) + + to_chat(xeno, SPAN_XENONOTICE("Our muscles tense as we prepare ourself for a giant leap.")) + xeno.make_jittery(2 SECONDS) + if(!do_after(xeno, 2 SECONDS, INTERRUPT_ALL, BUSY_ICON_HOSTILE)) + to_chat(xeno, SPAN_XENONOTICE("We relax our muslces and end our leap.")) + return + if(leaping || !target) + return + // stop target movement + leaping = TRUE + ADD_TRAIT(owner, TRAIT_UNDENSE, "Destroy") + ADD_TRAIT(owner, TRAIT_IMMOBILIZED, "Destroy") + owner.visible_message(SPAN_WARNING("[owner] takes a giant leap into the air!")) + + var/negative + var/initial_x = owner.x + if(target.x < initial_x) //if the target's x is lower than ours, go to the left + negative = TRUE + else if(target.x > initial_x) + negative = FALSE + else if(target.x == initial_x) //if their x is the same, pick a direction + negative = prob(50) + + owner.face_atom(target) + owner.emote("roar") + + //Initial visual + var/obj/effect/temp_visual/destroyer_leap/leap_visual = new(owner.loc, negative, owner.dir) + new /obj/effect/xenomorph/xeno_telegraph/destroyer_attack_template(template_turf, 20) + + negative = !negative //invert it for the descent later + + var/oldtransform = owner.transform + owner.alpha = 255 + animate(owner, alpha = 0, transform = matrix()*0.9, time = 3, easing = BOUNCE_EASING) + for(var/i in 1 to 3) + sleep(1 DECISECONDS) + if(QDELETED(owner) || owner.stat == DEAD) //we got hit and died, rip us + + //Initial effect + qdel(leap_visual) + + if(owner.stat == DEAD) + leaping = FALSE + animate(owner, alpha = 255, transform = oldtransform, time = 0, flags = ANIMATION_END_NOW) //reset immediately + return + + owner.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + SLEEP_CHECK_DEATH(7, owner) + + while(target && owner.loc != get_turf(target)) + owner.forceMove(get_step(owner, get_dir(owner, target))) + SLEEP_CHECK_DEATH(0.5, owner) + + animate(owner, alpha = 100, transform = matrix()*0.7, time = 7) + var/descentTime = 5 + + if(negative) + if(ISINRANGE(owner.x, initial_x + 1, initial_x + LEAP_DIRECTION_CHANGE_RANGE)) + negative = FALSE + else + if(ISINRANGE(owner.x, initial_x - LEAP_DIRECTION_CHANGE_RANGE, initial_x - 1)) + negative = TRUE + + new /obj/effect/temp_visual/destroyer_leap/end(owner.loc, negative, owner.dir) + + SLEEP_CHECK_DEATH(descentTime, owner) + animate(owner, alpha = 255, transform = oldtransform, descentTime) + owner.mouse_opacity = initial(owner.mouse_opacity) + playsound(owner.loc, 'sound/effects/meteorimpact.ogg', 200, TRUE) + + /// Effects for landing + new /obj/effect/temp_visual/heavy_impact(owner.loc) + for(var/step in CARDINAL_ALL_DIRS) + new /obj/effect/temp_visual/heavy_impact(get_step(owner.loc, step)) + + // Actual Damaging Effects - Add stuff for cades - NEED TELEGRAPHS NEED EFFECTS + + // Mobs first high damage and knockback away from centre + for(var/mob/living/carbon/carbon in orange(1, owner) - owner) + if(xeno.can_not_harm(carbon)) + continue + carbon.adjustBruteLoss(75) + if(!QDELETED(carbon)) // Some mobs are deleted on death + log_attack("[key_name(xeno)] hit [key_name(carbon)] with [name]") + carbon.last_damage_data = create_cause_data(initial(xeno.name), xeno) + var/throw_dir = get_dir(owner, carbon) + if(carbon.loc == owner.loc) + throw_dir = pick(GLOB.alldirs) + var/throwtarget = get_edge_target_turf(owner, throw_dir) + carbon.throw_atom(throwtarget, 2, SPEED_REALLY_FAST, owner, TRUE) + carbon.KnockDown(0.5) + xeno.visible_message(SPAN_WARNING("[carbon] is thrown clear of [owner]!")) + + // Any items get thrown away + for(var/obj/item/item in orange(1, owner)) + if(!QDELETED(item)) + var/throw_dir = get_dir(owner, item) + if(item.loc == owner.loc) + throw_dir = pick(GLOB.alldirs) + var/throwtarget = get_edge_target_turf(owner, throw_dir) + item.throw_atom(throwtarget, 2, SPEED_REALLY_FAST, owner, TRUE) + + for(var/obj/structure/structure in orange(1, owner)) + structure.ex_act(300, get_dir(owner, structure)) + + for(var/mob/living in range(7, owner)) + shake_camera(living, 15, 1) + + REMOVE_TRAIT(owner, TRAIT_UNDENSE, "Destroy") + REMOVE_TRAIT(owner, TRAIT_IMMOBILIZED, "Destroy") + + SLEEP_CHECK_DEATH(1, owner) + leaping = FALSE + apply_cooldown() + ..() + +/datum/action/xeno_action/activable/destroy/proc/second_template(turf/template_turf) + new /obj/effect/xenomorph/xeno_telegraph/destroyer_attack_template(template_turf, 10) + +/obj/effect/temp_visual/destroyer_leap + icon = 'icons/mob/xenos/destroyer.dmi' + icon_state = "Normal Destroyer Charging" + layer = 4.7 + plane = -4 + pixel_x = -32 + duration = 10 + randomdir = FALSE + +/obj/effect/temp_visual/destroyer_leap/Initialize(mapload, negative, dir) + . = ..() + setDir(dir) + INVOKE_ASYNC(src, PROC_REF(flight), negative) + +/obj/effect/temp_visual/destroyer_leap/proc/flight(negative) + if(negative) + animate(src, pixel_x = -LEAP_HEIGHT*0.1, pixel_z = LEAP_HEIGHT*0.15, time = 3, easing = BOUNCE_EASING) + else + animate(src, pixel_x = LEAP_HEIGHT*0.1, pixel_z = LEAP_HEIGHT*0.15, time = 3, easing = BOUNCE_EASING) + sleep(0.3 SECONDS) + icon_state = "Normal Destroyer Charging" + if(negative) + animate(src, pixel_x = -LEAP_HEIGHT, pixel_z = LEAP_HEIGHT, time = 7) + else + animate(src, pixel_x = LEAP_HEIGHT, pixel_z = LEAP_HEIGHT, time = 7) + +/obj/effect/temp_visual/destroyer_leap/end + pixel_x = LEAP_HEIGHT + pixel_z = LEAP_HEIGHT + duration = 10 + +/obj/effect/temp_visual/destroyer_leap/end/flight(negative) + if(negative) + pixel_x = -LEAP_HEIGHT + animate(src, pixel_x = -16, pixel_z = 0, time = 5) + else + animate(src, pixel_x = -16, pixel_z = 0, time = 5) + +/obj/effect/xenomorph/xeno_telegraph/destroyer_attack_template + icon = 'icons/effects/96x96.dmi' + icon_state = "xenolandingpink" + layer = BELOW_MOB_LAYER + +/obj/effect/xenomorph/xeno_telegraph/destroyer_attack_template/yellow + icon_state = "xenolandingyellow" diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm index 8fe101a08dfa..2634086e0582 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/ravager/ravager_powers.dm @@ -443,7 +443,7 @@ shield.shrapnel_amount = shield_shrapnel_amount xeno.overlay_shields() - xeno.create_shield(shield_duration) + xeno.create_shield(shield_duration, "shield2") shield_active = TRUE button.icon_state = "template_active" addtimer(CALLBACK(src, PROC_REF(remove_shield)), shield_duration) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm b/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm index 80cf5c1e37ac..26c792e6ba3b 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/xeno_action.dm @@ -454,3 +454,6 @@ track_xeno_ability_stats() if(action_start_message) to_chat(owner, SPAN_NOTICE(action_start_message)) + +#define XENO_ACTION_CHECK(X) if(!X.check_state() || !action_cooldown_check() || !check_plasma_owner(src.plasma_cost)) return +#define XENO_ACTION_CHECK_USE_PLASMA(X) if(!X.check_state() || !action_cooldown_check() || !check_and_use_plasma_owner(src.plasma_cost)) return diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Destroyer.dm b/code/modules/mob/living/carbon/xenomorph/castes/Destroyer.dm new file mode 100644 index 000000000000..8c35fb740e3d --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/castes/Destroyer.dm @@ -0,0 +1,104 @@ +/datum/caste_datum/destroyer + caste_type = XENO_CASTE_DESTROYER + caste_desc = "The end of the line." + tier = 1 + + // All to change + melee_damage_lower = XENO_DAMAGE_TIER_6 + melee_damage_upper = XENO_DAMAGE_TIER_8 + melee_vehicle_damage = XENO_DAMAGE_TIER_5 + max_health = XENO_HEALTH_DESTROYER + plasma_gain = XENO_PLASMA_GAIN_TIER_3 + plasma_max = XENO_PLASMA_TIER_10 + xeno_explosion_resistance = XENO_EXPLOSIVE_ARMOR_TIER_7 + armor_deflection = XENO_ARMOR_FACTOR_TIER_5 + speed = XENO_SPEED_TIER_1 + + evolves_to = null + deevolves_to = null + can_vent_crawl = FALSE + + behavior_delegate_type = /datum/behavior_delegate/destroyer_base + + tackle_min = 2 + tackle_max = 4 + + minimap_icon = "defender" + + fire_immunity = FIRE_IMMUNITY_NO_DAMAGE + +/mob/living/carbon/xenomorph/destroyer + caste_type = XENO_CASTE_DESTROYER + name = XENO_CASTE_DESTROYER + desc = "A massive alien covered in spines and armoured plates." + icon = 'icons/mob/xenos/destroyer.dmi' + icon_size = 64 + icon_state = "Destroyer Walking" + plasma_types = list(PLASMA_CHITIN) + pixel_x = -16 + old_x = -16 + tier = 4 + small_explosives_stun = FALSE + counts_for_slots = FALSE + tackle_min = 5 + tackle_max = 8 + tackle_chance = 10 + + claw_type = CLAW_TYPE_VERY_SHARP + age = -1 + + base_actions = list( + /datum/action/xeno_action/onclick/xeno_resting, + /datum/action/xeno_action/onclick/regurgitate, + /datum/action/xeno_action/watch_xeno, + /datum/action/xeno_action/activable/tail_stab, + /datum/action/xeno_action/onclick/rend, + /datum/action/xeno_action/activable/doom, + /datum/action/xeno_action/activable/destroy, + /datum/action/xeno_action/onclick/destroyer_shield, + ) + + icon_xeno = 'icons/mob/xenos/destroyer.dmi' + + //BIRDTALON: Do we need xenonid stuff? + //icon_xenonid = '' + + //BIRDTALON: need weed food icon + //weed_food_icon = '' + weed_food_states = list() + weed_food_states_flipped = list() + +/datum/behavior_delegate/destroyer_base + name = "Base Destroyer Behavior Delegate" + ///reward for hitting shots instead of spamming acid ball + + +///Direct override on Collide() to prevent bumping +/mob/living/carbon/xenomorph/destroyer/Collide(atom/movable/movable_atom) + if(behavior_delegate) + behavior_delegate.on_collide(movable_atom) + ..() + +/// Knocks down hostiles and deals damage, knocks down allies for a much shorter time +/datum/behavior_delegate/destroyer_base/on_collide(atom/movable/movable_atom) + if(!bound_xeno.Adjacent(movable_atom)) + return + + if(isxeno_human(movable_atom)) + var/mob/living/carbon/carbon = movable_atom + + if(isxeno(carbon)) + var/mob/living/carbon/xenomorph/xeno = carbon + if(xeno.hivenumber == bound_xeno.hivenumber) + xeno.KnockDown((5 DECISECONDS) / GLOBAL_STATUS_MULTIPLIER) + else + xeno.KnockDown((1 SECONDS) / GLOBAL_STATUS_MULTIPLIER) + else + //Review damage and knockdown + carbon.apply_armoured_damage(20) + + var/turf/T = get_turf(carbon) + var/list/contents = T.contents + + for(var/mob/living/carbon/carbon in contents) + carbon.KnockDown((1 SECONDS) / GLOBAL_STATUS_MULTIPLIER) diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm index 22b061715892..00adfda8a46d 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm @@ -133,6 +133,18 @@ var/list/available_nicknumbers = list() + + /// Hive buffs + var/buff_points = HIVE_STARTING_BUFFPOINTS + + /// List of references to the currently active hivebuffs + var/list/active_hivebuffs + /// List of references to used hivebuffs + var/list/used_hivebuffs + + /// List of references to hive pylons active in the game world + var/list/active_endgame_pylons + /*Stores the image()'s for the xeno evolution radial menu To add an image for your caste - add an icon to icons/mob/xenos/radial_xenos.dmi Icon size should be 32x32, to make them fit within the radial menu border size your icon 22x22 and leave 10px transparent border. @@ -150,6 +162,10 @@ internal_faction = name for(var/number in 1 to 999) available_nicknumbers += number + LAZYINITLIST(active_hivebuffs) + LAZYINITLIST(used_hivebuffs) + LAZYINITLIST(active_endgame_pylons) + if(hivenumber != XENO_HIVE_NORMAL) return @@ -1327,6 +1343,68 @@ if(SSxevolution) SSxevolution.override_power(hivenumber, evil, override) +/// Hive buffs + +/// Get a list of hivebuffs which can be bought now. +/datum/hive_status/proc/get_available_hivebuffs() + var/list/potential_hivebuffs = subtypesof(/datum/hivebuff) + + var/num_of_free_pylons = 0 + // First check if we have available pylons which are capable of supporting hivebuffs + for(var/obj/effect/alien/resin/special/pylon/endgame/pylon in active_endgame_pylons) + if(!pylon.sustained_buff) + num_of_free_pylons++ + + if(!num_of_free_pylons) + return + + for(var/datum/hivebuff/possible_hivebuff as anything in potential_hivebuffs) + // Round isn't old enough yet + if(ROUND_TIME < SSticker.round_start_time + initial(possible_hivebuff.roundtime_to_enable)) + potential_hivebuffs -= possible_hivebuff + continue + + if(initial(possible_hivebuff.number_of_required_pylons) > LAZYLEN(active_endgame_pylons)) + potential_hivebuffs -= possible_hivebuff + continue + + //If this hive has buffs already active, check against other currently active hivebuffs + if(LAZYLEN(active_hivebuffs)) + var/found + for(var/datum/hivebuff/active_buff in active_hivebuffs) + if(istype(active_buff, possible_hivebuff)) + found = TRUE + break + if(found) + potential_hivebuffs -= possible_hivebuff + continue + + //If this buff is unique, check if any other hivebuffs are active + if(initial(possible_hivebuff.is_unique) && LAZYLEN(active_hivebuffs)) + potential_hivebuffs -= possible_hivebuff + continue + + // If the buff is not reusable check against used hivebuffs. + if(!initial(possible_hivebuff.is_reusable) && LAZYLEN(used_hivebuffs)) + var/found + for(var/datum/hivebuff/used_buff in used_hivebuffs) + if(istype(used_buff, possible_hivebuff)) + found = TRUE + break + if(found) + potential_hivebuffs -= possible_hivebuff + continue + + return potential_hivebuffs + +/datum/hive_status/proc/attempt_apply_hivebuff(datum/hivebuff/hivebuff, mob/living/purchasing_player, obj/effect/alien/resin/special/pylon/endgame/purchased_pylon) + var/datum/hivebuff/new_buff = new hivebuff(src) + if(!new_buff._on_engage(purchasing_player, purchased_pylon)) + qdel(new_buff) + return FALSE + return TRUE + + //Xeno Resin Mark Shit, the very best place for it too :0) //Defines at the bottom of this list here will show up at the top in the mark menu /datum/xeno_mark_define @@ -1387,3 +1465,4 @@ name = "Attack" desc = "Attack the enemy here!" icon_state = "attack" + diff --git a/code/modules/mob/living/carbon/xenomorph/update_icons.dm b/code/modules/mob/living/carbon/xenomorph/update_icons.dm index 571f261ab981..dd31272b5372 100644 --- a/code/modules/mob/living/carbon/xenomorph/update_icons.dm +++ b/code/modules/mob/living/carbon/xenomorph/update_icons.dm @@ -250,10 +250,10 @@ apply_overlay(X_SUIT_LAYER) addtimer(CALLBACK(src, PROC_REF(remove_overlay), X_SUIT_LAYER), 2 SECONDS) -/mob/living/carbon/xenomorph/proc/create_shield(duration = 10) +/mob/living/carbon/xenomorph/proc/create_shield(duration = 10, iconstate) remove_suit_layer() - overlays_standing[X_SUIT_LAYER] = image("icon"='icons/mob/xenos/overlay_effects64x64.dmi', "icon_state" = "shield2") + overlays_standing[X_SUIT_LAYER] = image("icon"='icons/mob/xenos/overlay_effects64x64.dmi', "icon_state" = iconstate) apply_overlay(X_SUIT_LAYER) addtimer(CALLBACK(src, PROC_REF(remove_overlay), X_SUIT_LAYER), duration) diff --git a/colonialmarines.dme b/colonialmarines.dme index 4c7eaf582e90..461d018f5a9f 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -698,6 +698,7 @@ #include "code\datums\weather\weather_map_holders\sorokyne.dm" #include "code\datums\xeno_shields\xeno_shield.dm" #include "code\datums\xeno_shields\shield_types\crusher_shield.dm" +#include "code\datums\xeno_shields\shield_types\destroyer_shield.dm" #include "code\datums\xeno_shields\shield_types\hedgehog_shield.dm" #include "code\datums\xeno_shields\shield_types\vanguard_shield.dm" #include "code\defines\procs\admin.dm" @@ -1596,6 +1597,7 @@ #include "code\modules\cm_aliens\Ovipositor.dm" #include "code\modules\cm_aliens\weeds.dm" #include "code\modules\cm_aliens\XenoStructures.dm" +#include "code\modules\cm_aliens\hivebuffs\hivebuff.dm" #include "code\modules\cm_aliens\structures\construction_node.dm" #include "code\modules\cm_aliens\structures\egg.dm" #include "code\modules\cm_aliens\structures\fruit.dm" @@ -2004,6 +2006,9 @@ #include "code\modules\mob\living\carbon\xenomorph\abilities\defender\defender_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\defender\defender_macros.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\defender\defender_powers.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\destroyer\destroyer_abilities.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\destroyer\destroyer_macros.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\destroyer\destroyer_powers.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\facehugger\facehugger_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\facehugger\facehugger_powers.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\hivelord\hivelord_abilities.dm" @@ -2044,6 +2049,7 @@ #include "code\modules\mob\living\carbon\xenomorph\castes\caste_datum.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Crusher.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Defender.dm" +#include "code\modules\mob\living\carbon\xenomorph\castes\Destroyer.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Drone.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Facehugger.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Hellhound.dm" diff --git a/icons/effects/96x96.dmi b/icons/effects/96x96.dmi index 521aea66d403..bcee3737f5a4 100644 Binary files a/icons/effects/96x96.dmi and b/icons/effects/96x96.dmi differ diff --git a/icons/effects/heavyimpact.dmi b/icons/effects/heavyimpact.dmi new file mode 100644 index 000000000000..e7af5cf49b76 Binary files /dev/null and b/icons/effects/heavyimpact.dmi differ diff --git a/icons/mob/xenos/destroyer.dmi b/icons/mob/xenos/destroyer.dmi new file mode 100644 index 000000000000..4e8b7e33787f Binary files /dev/null and b/icons/mob/xenos/destroyer.dmi differ diff --git a/icons/mob/xenos/overlay_effects64x64.dmi b/icons/mob/xenos/overlay_effects64x64.dmi index 360aa4c54ff8..0be09311a780 100644 Binary files a/icons/mob/xenos/overlay_effects64x64.dmi and b/icons/mob/xenos/overlay_effects64x64.dmi differ diff --git a/icons/mob/xenos/roguedamage.dmi b/icons/mob/xenos/roguedamage.dmi new file mode 100644 index 000000000000..92ca12ed9d7c Binary files /dev/null and b/icons/mob/xenos/roguedamage.dmi differ diff --git a/icons/mob/xenos/rogueking.dmi b/icons/mob/xenos/rogueking.dmi new file mode 100644 index 000000000000..eeaeadde9e2a Binary files /dev/null and b/icons/mob/xenos/rogueking.dmi differ diff --git a/icons/mob/xenos/structures64x64.dmi b/icons/mob/xenos/structures64x64.dmi index 92ffdccf3b95..410f1a6f49e2 100644 Binary files a/icons/mob/xenos/structures64x64.dmi and b/icons/mob/xenos/structures64x64.dmi differ diff --git a/icons/mob/xenos/xenoDestroyer64x64s.dmi b/icons/mob/xenos/xenoDestroyer64x64s.dmi new file mode 100644 index 000000000000..ce082004e2b7 Binary files /dev/null and b/icons/mob/xenos/xenoDestroyer64x64s.dmi differ diff --git a/icons/mob/xenos/xenoDestroyerBuffs.dmi b/icons/mob/xenos/xenoDestroyerBuffs.dmi new file mode 100644 index 000000000000..b1b4d3443020 Binary files /dev/null and b/icons/mob/xenos/xenoDestroyerBuffs.dmi differ diff --git a/icons/mob/xenos/xenoDestroyerHatcheryfix.dmi b/icons/mob/xenos/xenoDestroyerHatcheryfix.dmi new file mode 100644 index 000000000000..562f0f2b41a4 Binary files /dev/null and b/icons/mob/xenos/xenoDestroyerHatcheryfix.dmi differ diff --git a/icons/ui_icons/hivebuff_radial.dmi b/icons/ui_icons/hivebuff_radial.dmi new file mode 100644 index 000000000000..b66811daa284 Binary files /dev/null and b/icons/ui_icons/hivebuff_radial.dmi differ diff --git a/sound/voice/deep_alien_screech.ogg b/sound/voice/deep_alien_screech.ogg new file mode 100644 index 000000000000..994ac0abee1c Binary files /dev/null and b/sound/voice/deep_alien_screech.ogg differ diff --git a/sound/voice/deep_alien_screech2.ogg b/sound/voice/deep_alien_screech2.ogg new file mode 100644 index 000000000000..1f13e8859d61 Binary files /dev/null and b/sound/voice/deep_alien_screech2.ogg differ