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 e76768a82b46..bb48e1050771 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm @@ -85,3 +85,5 @@ /// used in /datum/component/status_effect/cleanse() #define COMSIG_XENO_DEBUFF_CLEANSE "xeno_debuff_cleanse" +/// 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 d8f820ab382b..2668d5546d66 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -383,3 +383,12 @@ GLOBAL_LIST_INIT(default_xeno_onmob_icons, list( #define MOBILITY_FLAGS_LYING_CAPABLE_DEFAULT (MOBILITY_MOVE | MOBILITY_STAND | MOBILITY_LIEDOWN) #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) + +/// Sleeps for X and will perform return if A is qdeleted or a dead mob. +#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 18d4908c9df7..3c19a0154920 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno.dm @@ -174,7 +174,7 @@ /// The time when xenos can start taking over comm towers #define XENO_COMM_ACQUISITION_TIME (55 MINUTES) -/// The time it takes for a pylon to give one larva while activated +/// The time it takes for a pylon to give one royal resin while activated #define XENO_PYLON_ACTIVATION_COOLDOWN (5 MINUTES) /// The time until you can re-corrupt a comms relay after the last pylon was destroyed @@ -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_KING 1500 * XENO_UNIVERSAL_HPMULT // Plasma bands #define XENO_NO_PLASMA 0 @@ -434,6 +435,7 @@ // Armor mods. Use the above defines for some guidance // In general, +20 armor should be a little more than +20% effective HP, however, // the higher the Xeno's base armor, the greater the effect. +#define XENO_ARMOR_MOD_TINY 2.5 #define XENO_ARMOR_MOD_VERY_SMALL 5 #define XENO_ARMOR_MOD_SMALL 10 #define XENO_ARMOR_MOD_MED 15 @@ -665,6 +667,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_KING_BULWARKSPELL 11 //XENO CASTES #define XENO_CASTE_LARVA "Bloody Larva" @@ -693,13 +696,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_KING "King" #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_KING) // Checks if two hives are allied to each other. // PARAMETERS: diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm index c6957eefd22c..5fdd35d26137 100644 --- a/code/_globalvars/global_lists.dm +++ b/code/_globalvars/global_lists.dm @@ -217,11 +217,15 @@ GLOBAL_LIST_INIT_TYPED(hive_datum, /datum/hive_status, list( XENO_HIVE_TUTORIAL = new /datum/hive_status/tutorial() )) +GLOBAL_VAR_INIT(king_acquisition_time, 1 HOURS + 30 MINUTES + rand(0, 25) MINUTES) GLOBAL_LIST_INIT(xeno_evolve_times, setup_xeno_evolve_times()) /proc/setup_xeno_evolve_times() for(var/datum/caste_datum/caste as anything in subtypesof(/datum/caste_datum)) - LAZYADDASSOCLIST(., num2text(initial(caste.minimum_evolve_time)), caste) + if(initial(caste.caste_type) == XENO_CASTE_KING) + LAZYADDASSOCLIST(., num2text(GLOB.king_acquisition_time), caste) + else + LAZYADDASSOCLIST(., num2text(initial(caste.minimum_evolve_time)), caste) GLOBAL_LIST_INIT(custom_event_info_list, setup_custom_event_info()) diff --git a/code/datums/langchat/langchat.dm b/code/datums/langchat/langchat.dm index b82b03b51774..317f80642c45 100644 --- a/code/datums/langchat/langchat.dm +++ b/code/datums/langchat/langchat.dm @@ -10,6 +10,7 @@ /mob/living/carbon/xenomorph/hivelord/langchat_height = 64 /mob/living/carbon/xenomorph/defender/langchat_height = 48 /mob/living/carbon/xenomorph/warrior/langchat_height = 48 +/mob/living/carbon/xenomorph/king/langchat_height = 64 #define LANGCHAT_LONGEST_TEXT 64 #define LANGCHAT_WIDTH 96 diff --git a/code/datums/xeno_shields/shield_types/king_shield.dm b/code/datums/xeno_shields/shield_types/king_shield.dm new file mode 100644 index 000000000000..c8a54bf2c5d5 --- /dev/null +++ b/code/datums/xeno_shields/shield_types/king_shield.dm @@ -0,0 +1,20 @@ +/// Shield can be equal to a maximum of percent_maxhealth_damagecap of the receiver's max hp +/datum/xeno_shield/king_shield + duration = 10 SECONDS + decay_amount_per_second = 100 + /// The maximum damage multiplier of max health to apply in a hit + var/percent_maxhealth_damagecap = 0.1 + +/datum/xeno_shield/king_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/king_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 b4894eda4d24..f32860c06d2c 100644 --- a/code/game/jobs/role_authority.dm +++ b/code/game/jobs/role_authority.dm @@ -759,6 +759,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_KING) + M = /mob/living/carbon/xenomorph/king return M diff --git a/code/game/objects/effects/effect_system/smoke.dm b/code/game/objects/effects/effect_system/smoke.dm index d0ea5d2ed5ef..1efe8e5e3b66 100644 --- a/code/game/objects/effects/effect_system/smoke.dm +++ b/code/game/objects/effects/effect_system/smoke.dm @@ -769,6 +769,22 @@ // XENO SMOKES +/obj/effect/particle_effect/smoke/king + opacity = FALSE + color = "#000000" + icon = 'icons/effects/effects.dmi' + icon_state = "sparks" + anchored = TRUE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + layer = BELOW_OBJ_LAYER + time_to_live = 5 + spread_speed = 1 + pixel_x = 0 + pixel_y = 0 + +/datum/effect_system/smoke_spread/king_doom + smoke_type = /obj/effect/particle_effect/smoke/king + /datum/effect_system/smoke_spread/xeno_acid smoke_type = /obj/effect/particle_effect/smoke/xeno_burn diff --git a/code/game/objects/effects/temporary_visuals.dm b/code/game/objects/effects/temporary_visuals.dm index d05e7789b1d5..61a339958491 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 = 1.3 SECONDS + +/obj/effect/temp_visual/heavyimpact/Initialize(mapload) + . = ..() + flick("heavyimpact", src) + /obj/effect/temp_visual/dir_setting randomdir = FALSE diff --git a/code/modules/admin/player_panel/actions/transform.dm b/code/modules/admin/player_panel/actions/transform.dm index 91a62b1a1d02..2fc52dbd1b9a 100644 --- a/code/modules/admin/player_panel/actions/transform.dm +++ b/code/modules/admin/player_panel/actions/transform.dm @@ -140,6 +140,11 @@ GLOBAL_LIST_INIT(pp_transformables, list( name = XENO_CASTE_PREDALIEN, key = /mob/living/carbon/xenomorph/predalien, color = "purple" + ), + list( + name = XENO_CASTE_KING, + key = /mob/living/carbon/xenomorph/king, + color="purple" ) ), diff --git a/code/modules/cm_aliens/XenoStructures.dm b/code/modules/cm_aliens/XenoStructures.dm index 635bca03241f..9cae194b75d7 100644 --- a/code/modules/cm_aliens/XenoStructures.dm +++ b/code/modules/cm_aliens/XenoStructures.dm @@ -21,7 +21,7 @@ unacidable = TRUE var/should_track_build = FALSE var/datum/cause_data/construction_data - var/list/blocks = list() + var/list/blockers = list() var/block_range = 0 /obj/effect/alien/resin/Initialize(mapload, mob/builder) @@ -29,17 +29,12 @@ if(istype(builder) && should_track_build) construction_data = create_cause_data(initial(name), builder) if(block_range) - for(var/turf/T in range(block_range, src)) - var/obj/effect/build_blocker/SP = new(T) - SP.linked_structure = src - blocks.Add(SP) + for(var/turf/turf in range(block_range, src)) + var/obj/effect/build_blocker/blocker = new(turf, src) + blockers.Add(blocker) /obj/effect/alien/resin/Destroy() - if(block_range) - for(var/obj/effect/build_blocker/SP as anything in blocks) - blocks -= SP - SP.linked_structure = null - qdel(SP) + QDEL_LIST(blockers) return ..() /obj/effect/alien/resin/proc/healthcheck() @@ -133,14 +128,20 @@ /obj/effect/build_blocker health = 500000 - unacidable = TRUE indestructible = TRUE invisibility = 101 - alpha = 0 + /// The atom we are blocking for + var/atom/linked_structure + +/obj/effect/build_blocker/New(loc, linked_structure) + . = ..() + src.linked_structure = linked_structure - var/obj/effect/alien/resin/linked_structure +/obj/effect/build_blocker/Destroy(force) + linked_structure = null + return ..() /obj/effect/alien/resin/sticky name = "sticky resin" @@ -844,6 +845,359 @@ return ..() +/obj/effect/alien/resin/king_cocoon + name = "alien cocoon" + desc = "A large pulsating cocoon." + icon = 'icons/obj/structures/alien/xenoKingHatchery.dmi' + icon_state = "static" + health = 4000 + pixel_x = -48 + pixel_y = -64 + density = TRUE + plane = FLOOR_PLANE + + /// The mob picked as a candidate to be the King + var/client/chosen_candidate + /// The hive associated with this cocoon + var/hive_number = XENO_HIVE_NORMAL + /// Whether the cocoon has hatched + var/hatched = FALSE + /// Current running timer + var/timer + /// Is currently rolling candidates + var/rolling_candidates = FALSE + /// Voting for King + var/list/mob/living/carbon/xenomorph/votes = list() + +/obj/effect/alien/resin/king_cocoon/Destroy() + if(!hatched) + marine_announcement("ALERT.\n\nUNSUAL ENERGY BUILDUP IN [uppertext(get_area_name(loc))] HAS BEEN STOPPED.", "[MAIN_AI_SYSTEM] Biological Scanner", 'sound/misc/notice1.ogg') + var/datum/hive_status/hive + for(var/cur_hive_num in GLOB.hive_datum) + hive = GLOB.hive_datum[cur_hive_num] + if(!length(hive.totalXenos)) + continue + if(cur_hive_num == hive_number) + xeno_announcement(SPAN_XENOANNOUNCE("THE HATCHERY WAS DESTROYED! VENGENCE!"), cur_hive_num, XENO_GENERAL_ANNOUNCE) + else + xeno_announcement(SPAN_XENOANNOUNCE("THE HATCHERY WAS DESTROYED!"), cur_hive_num, XENO_GENERAL_ANNOUNCE) + + var/datum/hive_status/hive = GLOB.hive_datum[hive_number] + hive.has_hatchery = FALSE + for(var/obj/effect/alien/resin/special/pylon/pylon as anything in hive.active_endgame_pylons) + pylon.protection_level = initial(pylon.protection_level) + pylon.update_icon() + + votes = null + chosen_candidate = null + + . = ..() + +/obj/effect/alien/resin/king_cocoon/Initialize(mapload, hivenumber) + . = ..() + if(hivenumber) + hive_number = hivenumber + + var/datum/hive_status/hatchery_hive = GLOB.hive_datum[hive_number] + hatchery_hive.has_hatchery = TRUE + color = hatchery_hive.color + + for(var/x_offset in -1 to 1) + for(var/y_offset in -1 to 1) + var/turf/turf_to_block = locate(x + x_offset, y + y_offset, z) + var/obj/effect/build_blocker/blocker = new(turf_to_block, src) + blockers += blocker + + timer = addtimer(CALLBACK(src, PROC_REF(start_growing)), 10 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + addtimer(CALLBACK(src, PROC_REF(check_pylons)), 10 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME|TIMER_LOOP) + + marine_announcement("ALERT.\n\nUNSUAL ENERGY BUILDUP DETECTED IN [uppertext(get_area_name(loc))].\n\nESTIMATED TIME UNTIL COMPLETION - 10 MINUTES.", "[MAIN_AI_SYSTEM] Biological Scanner", 'sound/misc/notice1.ogg') + var/datum/hive_status/hive + for(var/cur_hive_num in GLOB.hive_datum) + hive = GLOB.hive_datum[cur_hive_num] + if(!length(hive.totalXenos)) + continue + if(cur_hive_num == hive_number) + xeno_announcement(SPAN_XENOANNOUNCE("The King is growing at [get_area_name(loc)]. Protect it at all costs!"), cur_hive_num, XENO_GENERAL_ANNOUNCE) + else + xeno_announcement(SPAN_XENOANNOUNCE("Another hive's King is growing at [get_area_name(loc)]."), cur_hive_num, XENO_GENERAL_ANNOUNCE) + +/// Callback for a repeating 10s timer to ensure both pylons are active (otherwise delete) and counts the number of marines groundside (would cause hatching to expedite). +/obj/effect/alien/resin/king_cocoon/proc/check_pylons() + var/datum/hive_status/hive = GLOB.hive_datum[hive_number] + + if(length(hive.active_endgame_pylons) < 2) + qdel(src) + return + + if(chosen_candidate || rolling_candidates) + return + + if(icon_state == "hatching" || icon_state == "hatched") + return + + var/groundside_humans = 0 + for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list) + if(!(isspecieshuman(current_human) || isspeciessynth(current_human))) + continue + + var/turf/turf = get_turf(current_human) + if(is_ground_level(turf?.z)) + groundside_humans += 1 + + if(groundside_humans > 12) + return + + // Too few marines are now groundside, hatch immediately + deltimer(timer) + start_vote(expedite = TRUE) + +/// Causes the cocoon to change visually for growing and initiates the next timer. +/obj/effect/alien/resin/king_cocoon/proc/start_growing() + icon_state = "growing" + timer = addtimer(CALLBACK(src, PROC_REF(announce_halfway)), 5 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + +/// Causes the halfway announcements and initiates the next timer. +/obj/effect/alien/resin/king_cocoon/proc/announce_halfway() + timer = addtimer(CALLBACK(src, PROC_REF(start_vote)), 4 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + + marine_announcement("ALERT.\n\nUNSUAL ENERGY BUILDUP DETECTED IN [uppertext(get_area_name(loc))].\n\nESTIMATED TIME UNTIL COMPLETION - 5 MINUTES.", "[MAIN_AI_SYSTEM] Biological Scanner", 'sound/misc/notice1.ogg') + var/datum/hive_status/hive + for(var/cur_hive_num in GLOB.hive_datum) + hive = GLOB.hive_datum[cur_hive_num] + if(!length(hive.totalXenos)) + continue + if(cur_hive_num == hive_number) + xeno_announcement(SPAN_XENOANNOUNCE("The King will hatch in approximately 5 minutes."), cur_hive_num, XENO_GENERAL_ANNOUNCE) + else + xeno_announcement(SPAN_XENOANNOUNCE("Another hive's King will hatch in approximately 5 minutes."), cur_hive_num, XENO_GENERAL_ANNOUNCE) + +#define KING_PLAYTIME_HOURS (50 HOURS) + +/** + * Returns TRUE is the candidate passed is valid: Returns TRUE is the candidate passed is valid: Has client, not facehugger, not lesser drone, not banished, and conditionally on playtime. + * + * Arguments: + * * hive: The hive_status to check banished ckeys against + * * candidate: The mob that we want to check + * * playtime_restricted: Determines whether being below KING_PLAYTIME_HOURS makes the candidate invalid + * * skip_playtime: Determines whether being above KING_PLAYTIME_HOURS makes the candidate invalid (does nothing unless playtime_restricted is FALSE) + */ +/obj/effect/alien/resin/king_cocoon/proc/is_candidate_valid(datum/hive_status/hive, mob/candidate, playtime_restricted = TRUE, skip_playtime = TRUE) + if(!candidate?.client) + return FALSE + if(isfacehugger(candidate) || islesserdrone(candidate)) + return FALSE + if(playtime_restricted) + if(candidate.client.get_total_xeno_playtime() < KING_PLAYTIME_HOURS) + return FALSE + else if(candidate.client.get_total_xeno_playtime() >= KING_PLAYTIME_HOURS && skip_playtime) + return FALSE // We do this under the assumption we tried it the other way already so don't ask twice + for(var/mob_name in hive.banished_ckeys) + if(hive.banished_ckeys[mob_name] == candidate.ckey) + return FALSE + return TRUE + +/** + * Returns TRUE if a valid candidate accepts a TGUI alert asking them to be King. + * + * Arguments: + * * hive: The hive_status to check banished ckeys against + * * candidate: The mob that we want to ask + * * playtime_restricted: Determines whether being below KING_PLAYTIME_HOURS makes the candidate invalid (otherwise above) + */ +/obj/effect/alien/resin/king_cocoon/proc/try_roll_candidate(datum/hive_status/hive, mob/candidate, playtime_restricted = TRUE) + if(!is_candidate_valid(hive, candidate, playtime_restricted)) + return FALSE + + return tgui_alert(candidate, "Would you like to become the King?", "Choice", list("Yes", "No"), 10 SECONDS) == "Yes" + +#undef KING_PLAYTIME_HOURS + +/** + * Tallies up votes by asking the passed candidate who they wish to vote for King. + * + * Arguments: + * * candidate: The mob that was want to ask + * * voting_candidates: A list of xenomorph mobs that are candidates + */ +/obj/effect/alien/resin/king_cocoon/proc/cast_vote(mob/candidate, list/mob/living/carbon/xenomorph/voting_candidates) + var/mob/living/carbon/xenomorph/choice = tgui_input_list(candidate, "Vote for a sister you wish to become the King.", "Choose a xeno", voting_candidates , 20 SECONDS) + + if(votes[choice]) + votes[choice] += 1 + else + votes[choice] = 1 + +/// Initiates a vote that will end in 20 seconds to vote for the King. Hatching will then begin in 1 minute unless expedited. +/obj/effect/alien/resin/king_cocoon/proc/start_vote(expedite = FALSE) + rolling_candidates = TRUE + var/datum/hive_status/hive = GLOB.hive_datum[hive_number] + + var/list/mob/living/carbon/xenomorph/voting_candidates = hive.totalXenos.Copy() - hive.living_xeno_queen + + for(var/mob/living/carbon/xenomorph/voting_candidate in voting_candidates) + if(!is_candidate_valid(hive, voting_candidate)) + voting_candidates -= voting_candidate + + for(var/mob/living/carbon/xenomorph/candidate in hive.totalXenos) + if(is_candidate_valid(hive, candidate, playtime_restricted = FALSE, skip_playtime = FALSE)) + INVOKE_ASYNC(src, PROC_REF(cast_vote), candidate, voting_candidates) + + addtimer(CALLBACK(src, PROC_REF(roll_candidates), voting_candidates, expedite), 20 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + +/** + * Finalizes the vote for King opting to use a series of fallbacks in case a candidate declines. + * + * First is a vote where the first and or second top picked is asked. + * Then all other living xenos meeting the playtime requirement are asked. + * Then all xeno observer candidates meeting the playtime requirement are asked. + * Then all other living xenos not meeting the playtime requirement are asked. + * Then all other xeno observer candidates not meeting the playtime requirement are asked. + * Then finally if after all that, the search is given up and will ultimately result in a freed King mob. + * + * Arguments: + * * voting_candidates: A list of xenomorphs that are valid candidates to vote on. + * * expedite: Whether hatching should begin in a minute or immediately after a candidate is found. + */ +/obj/effect/alien/resin/king_cocoon/proc/roll_candidates(list/mob/living/carbon/xenomorph/voting_candidates, expedite = FALSE) + var/datum/hive_status/hive = GLOB.hive_datum[hive_number] + + var/primary_votes = 0 + var/mob/living/carbon/xenomorph/primary_candidate + var/secondary_votes = 0 + var/mob/living/carbon/xenomorph/secondary_candidate + + for(var/mob/living/carbon/xenomorph/candidate in votes) + if(votes[candidate] > primary_votes) + primary_votes = votes[candidate] + primary_candidate = candidate + else if(votes[candidate] > secondary_votes) + secondary_votes = votes[candidate] + secondary_candidate = candidate + + votes.Cut() + + if(prob(50) && try_roll_candidate(hive, primary_candidate, playtime_restricted = TRUE)) + chosen_candidate = primary_candidate.client + rolling_candidates = FALSE + start_hatching(expedite) + return + + voting_candidates -= primary_candidate + + + if(try_roll_candidate(hive, secondary_candidate, playtime_restricted = TRUE)) + chosen_candidate = secondary_candidate.client + rolling_candidates = FALSE + start_hatching(expedite) + return + + voting_candidates -= secondary_candidate + + // Otherwise ask all the living xenos (minus the player(s) who got voted on earlier) + for(var/mob/living/carbon/xenomorph/candidate in shuffle(voting_candidates)) + if(try_roll_candidate(hive, candidate, playtime_restricted = TRUE)) + chosen_candidate = candidate.client + rolling_candidates = FALSE + start_hatching(expedite) + return + // Then observers + var/list/observer_list_copy = shuffle(get_alien_candidates(hive)) + + for(var/mob/candidate in observer_list_copy) + if(try_roll_candidate(hive, candidate, playtime_restricted = TRUE)) + chosen_candidate = candidate.client + rolling_candidates = FALSE + start_hatching(expedite) + return + // Lastly all of the above again, without playtime requirements + for(var/mob/living/carbon/xenomorph/candidate in shuffle(hive.totalXenos.Copy() - hive.living_xeno_queen)) + if(try_roll_candidate(hive, candidate, playtime_restricted = FALSE)) + chosen_candidate = candidate.client + rolling_candidates = FALSE + start_hatching(expedite) + return + for(var/mob/candidate in observer_list_copy) + if(try_roll_candidate(hive, candidate, playtime_restricted = FALSE)) + chosen_candidate = candidate.client + rolling_candidates = FALSE + start_hatching(expedite) + return + message_admins("Failed to find a client for the King, releasing as freed mob.") + start_hatching(expedite) + +/// Starts the hatching in one minute, otherwise immediately if expedited +/obj/effect/alien/resin/king_cocoon/proc/start_hatching(expedite = FALSE) + if(expedite) + animate_hatch_king() + return + + marine_announcement("ALERT.\n\nUNSUAL ENERGY BUILDUP DETECTED IN [get_area_name(loc)].\n\nESTIMATED TIME UNTIL COMPLETION - ONE MINUTE.", "[MAIN_AI_SYSTEM] Biological Scanner", 'sound/misc/notice1.ogg') + var/datum/hive_status/hive + for(var/cur_hive_num in GLOB.hive_datum) + hive = GLOB.hive_datum[cur_hive_num] + if(!length(hive.totalXenos)) + continue + if(cur_hive_num == hive_number) + xeno_announcement(SPAN_XENOANNOUNCE("The King will hatch in approximately one minute."), cur_hive_num, XENO_GENERAL_ANNOUNCE) + else + xeno_announcement(SPAN_XENOANNOUNCE("Another hive's King will hatch in approximately one minute."), cur_hive_num, XENO_GENERAL_ANNOUNCE) + + timer = addtimer(CALLBACK(src, PROC_REF(animate_hatch_king)), 1 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + +/// Causes the cocoon to change visually for hatching and initiates the next timer. +/obj/effect/alien/resin/king_cocoon/proc/animate_hatch_king() + flick("hatching", src) + addtimer(CALLBACK(src, PROC_REF(hatch_king)), 2 SECONDS, TIMER_UNIQUE|TIMER_STOPPABLE) + + marine_announcement("ALERT.\n\nEXTREME ENERGY INFLUX DETECTED IN [get_area_name(loc)].\n\nCAUTION IS ADVISED.", "[MAIN_AI_SYSTEM] Biological Scanner", 'sound/misc/notice1.ogg') + var/datum/hive_status/hive + for(var/cur_hive_num in GLOB.hive_datum) + hive = GLOB.hive_datum[cur_hive_num] + if(!length(hive.totalXenos)) + continue + if(cur_hive_num == hive_number) + xeno_announcement(SPAN_XENOANNOUNCE("All hail the King."), cur_hive_num, XENO_GENERAL_ANNOUNCE) + else + xeno_announcement(SPAN_XENOANNOUNCE("Another hive's King has hatched!"), cur_hive_num, XENO_GENERAL_ANNOUNCE) + +/// Actually hatches the King transferring the candidate into the spawned mob and initiates the next timer. +/obj/effect/alien/resin/king_cocoon/proc/hatch_king() + icon_state = "hatched" + hatched = TRUE + + QDEL_LIST(blockers) + + var/mob/living/carbon/xenomorph/king/king = new(get_turf(src), null, hive_number) + if(chosen_candidate?.mob) + var/mob/old_mob = chosen_candidate.mob + old_mob.mind.transfer_to(king) + + if(isliving(old_mob) && old_mob.stat != DEAD) + old_mob.free_for_ghosts(TRUE) + else + king.free_for_ghosts(TRUE) + playsound(src, 'sound/voice/alien_queen_command.ogg', 75, 0) + + chosen_candidate = null + + // Gives some time for the King to get their barings before it can be OBed + addtimer(CALLBACK(src, PROC_REF(remove_ob_protection)), 1 MINUTES, TIMER_UNIQUE|TIMER_STOPPABLE|TIMER_DELETE_ME) + +/// The final step in the cocoon sequence: Resets pylon protection levels +/obj/effect/alien/resin/king_cocoon/proc/remove_ob_protection() + var/datum/hive_status/hive = GLOB.hive_datum[hive_number] + for(var/obj/effect/alien/resin/special/pylon/pylon as anything in hive.active_endgame_pylons) + pylon.protection_level = initial(pylon.protection_level) + pylon.update_icon() + +/obj/effect/alien/resin/king_cocoon/attack_alien(mob/living/carbon/xenomorph/M) + if(M.can_destroy_special() || hatched) + return ..() + + return XENO_NO_DELAY_ACTION + /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..f0d0e12f3bde --- /dev/null +++ b/code/modules/cm_aliens/hivebuffs/hivebuff.dm @@ -0,0 +1,530 @@ +//GLOBAL DEFINES// + +#define HIVE_STARTING_BUFFPOINTS 0 +#define HIVE_MAX_BUFFPOINTS 10 +#define BUFF_POINTS_NAME "Royal resin" + +//LOCAL DEFINES// + +#define HIVEBUFF_TIER_MINOR "Minor" +#define HIVEBUFF_TIER_MAJOR "Major" + +/** + * + * 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. + * + */ +/datum/hivebuff + /// Timer id for cooldown duration + var/_timer_id_cooldown = TIMER_ID_NULL + /// The hive that this buff is applied to. + var/datum/hive_status/hive + ///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 + /// Flavour message to announce to the hive on buff expiry. Narrated to all players in the hive. + var/cease_flavourmessage + + /// 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_combineable = TRUE + ///If this buff can be used more than once a round. + var/is_reusable = TRUE + /// Time that the buff is active for if it is a timed buff. + var/duration + /// Time that the buff is on cooldown after ending + var/cooldown_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 + + /// 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 + + /// Special fail message + var/special_fail_message = "" + + /// Ask the buyer where to put the buff + var/must_select_pylon = FALSE + + /// _on_cease timer id + var/cease_timer_id + +/datum/hivebuff/New(datum/hive_status/xenohive) + . = ..() + if(!xenohive || !istype(xenohive)) + stack_trace("Hivebuff created without correct hive_status passed.") + return FALSE + hive = xenohive + + if(!engage_flavourmessage) + engage_flavourmessage = "The Queen has purchased [name]." + if(!cease_flavourmessage) + cease_flavourmessage = "The [name] has expired." + + return TRUE + +/datum/hivebuff/Destroy(force, ...) + LAZYREMOVE(hive.active_hivebuffs, src) + LAZYREMOVE(hive.used_hivebuffs, src) + LAZYREMOVE(hive.cooldown_hivebuffs, src) + hive = null + UnregisterSignal(SSdcs, COMSIG_GLOB_XENO_SPAWN) + return ..() + +///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/purchased_pylon) + 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()) + 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 + + if(!_check_danger()) + to_chat(purchasing_mob, SPAN_XENONOTICE("There is not enough danger to warrant hive buffs.")) + return FALSE + + 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("Our hive can't benefit from [name] yet!")) + 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 + + var/datum/hivebuff/cooldown_buff = locate(type) in hive.cooldown_hivebuffs + if(cooldown_buff) + to_chat(purchasing_mob, SPAN_XENONOTICE("Our hive has already used [name] recently! Wait [DisplayTimeText(timeleft(cooldown_buff._timer_id_cooldown))].")) + return FALSE + + if(!_check_pass_combineable()) + 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 + + if(!handle_special_checks()) + to_chat(purchasing_mob, SPAN_XENONOTICE(special_fail_message)) + return FALSE + + log_admin("[key_name(purchasing_mob)] of [hive.hivenumber] is attempting to purchase a hive buff: [name].") + + if(!_seek_queen_approval(purchasing_mob)) + return FALSE + + // _seek_queen_approval() includes a 20 second timeout so we check that everything still exists that we need. + if(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 + if(!_purchase_and_deduct(purchasing_mob)) + return + + for(var/mob/living/carbon/xenomorph/xeno in hive.totalXenos) + apply_buff_effects(xeno) + + if(apply_on_new_xeno) + RegisterSignal(SSdcs, COMSIG_GLOB_XENO_SPAWN, PROC_REF(_handle_xenomorph_new)) + + var/involved = purchasing_mob == hive.living_xeno_queen ? "[key_name_admin(purchasing_mob)]" : "[key_name_admin(purchasing_mob)] and [key_name_admin(hive.living_xeno_queen)]" + message_admins("[involved] of [hive.hivenumber] has purchased a hive buff: [name].") + + // Add to the relevant hive lists. + LAZYADD(hive.used_hivebuffs, src) + LAZYADD(hive.active_hivebuffs, src) + + // 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) + cease_timer_id = addtimer(CALLBACK(src, PROC_REF(_on_cease)), duration, TIMER_STOPPABLE|TIMER_DELETE_ME) + 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() +/datum/hivebuff/proc/_on_cease() + if(cease_timer_id) + deltimer(cease_timer_id) + + _announce_buff_cease() + on_cease() + LAZYREMOVE(hive.active_hivebuffs, src) + UnregisterSignal(SSdcs, COMSIG_GLOB_XENO_SPAWN) + + for(var/mob/living/carbon/xenomorph/xeno in hive.totalXenos) + remove_buff_effects(xeno) + + if(cooldown_duration) + LAZYADD(hive.cooldown_hivebuffs, src) + _timer_id_cooldown = addtimer(CALLBACK(src, PROC_REF(_on_cooldown_end)), cooldown_duration, TIMER_STOPPABLE|TIMER_DELETE_ME) + +/// Handler for the end of a cooldown +/datum/hivebuff/proc/_on_cooldown_end() + LAZYREMOVE(hive.cooldown_hivebuffs, src) + _timer_id_cooldown = TIMER_ID_NULL + +/// Checks the number of pylons required and if the hive posesses them +/datum/hivebuff/proc/_check_num_required_pylons() + return number_of_required_pylons >= hive.active_endgame_pylons + +/datum/hivebuff/proc/_roundtime_check() + if(ROUND_TIME > roundtime_to_enable) + return TRUE + return FALSE + +/datum/hivebuff/proc/_check_danger() + var/groundside_humans = 0 + for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list) + if(!(isspecieshuman(current_human) || isspeciessynth(current_human))) + continue + + var/turf/turf = get_turf(current_human) + if(is_ground_level(turf?.z)) + groundside_humans++ + if(groundside_humans >= 12) + 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() + // Prevent the same lineage of buff (e.g. no minor and major health allowed) + for(var/datum/hivebuff/buff as anything in hive.active_hivebuffs) + if(istype(src, buff.type)) + return FALSE + if(istype(buff, type)) + return FALSE + + return TRUE + +/// Checks if the buff is combineable if other buffs are already in use. Return TRUE if passed FALSE if not. +/datum/hivebuff/proc/_check_pass_combineable() + if(is_combineable) + return TRUE + for(var/datum/hivebuff/active_hivebuff in hive.active_hivebuffs) + if(!active_hivebuff.is_combineable) + 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(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(mob/purchasing_mob) + if(!_check_can_afford_buff()) + to_chat(purchasing_mob, SPAN_XENONOTICE("Something went wrong, try again.")) + return FALSE + + hive.buff_points -= cost + hive.check_if_hit_larva_from_pylon_limit() + 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, "You are trying to Purchase [name] at a cost of [cost] [BUFF_POINTS_NAME]. Our hive has [hive.buff_points] [BUFF_POINTS_NAME]. Are you sure you want to purchase it? Description: [desc]", "Approve Hive Buff", list("Yes", "No"), 20 SECONDS) + + return queen_response == "Yes" + +/// 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) + xeno_announcement(SPAN_XENOANNOUNCE(engage_flavourmessage), hive.hivenumber, XENO_GENERAL_ANNOUNCE) + + if(marine_flavourmessage) + marine_announcement(marine_flavourmessage, COMMAND_ANNOUNCE, 'sound/AI/bioscan.ogg') + +/datum/hivebuff/proc/_announce_buff_cease() + if(!duration) + return + + 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(datum/source, mob/living/carbon/xenomorph/new_xeno) + SIGNAL_HANDLER + if(!apply_on_new_xeno) + return + + if(!(src in hive.active_hivebuffs)) + 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 + +/datum/hivebuff/proc/handle_special_checks() + return TRUE + + +// 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!" + cost = 5 + number_of_required_pylons = 1 + is_reusable = FALSE + +/datum/hivebuff/extra_larva/on_engage(obj/effect/alien/resin/special/pylon/purchased_pylon) + hive.stored_larva += 5 + return TRUE + +/datum/hivebuff/evo_buff + name = "Boon of Evolution" + desc = "Doubles evolution speed for 5 minutes." + tier = HIVEBUFF_TIER_MINOR + engage_flavourmessage = "The Queen has blessed us with faster evolution." + duration = 5 MINUTES + number_of_required_pylons = 1 + var/value_before_buff + +/datum/hivebuff/evo_buff/on_engage(obj/effect/alien/resin/special/pylon/purchased_pylon) + value_before_buff = SSxevolution.get_evolution_boost_power(hive.hivenumber) + hive.override_evilution(value_before_buff * 2, TRUE) + + return TRUE + +/datum/hivebuff/evo_buff/on_cease() + hive.override_evilution(value_before_buff, FALSE) + +/datum/hivebuff/evo_buff/major + name = "Major Boon of Evolution" + desc = "Doubles evolution speed for 10 minutes and allows evolution progress without an ovipositor." + tier = HIVEBUFF_TIER_MAJOR + + engage_flavourmessage = "The Queen has blessed us with faster evolution." + duration = 10 MINUTES + cost = 2 + number_of_required_pylons = 2 + radial_icon = "health_m" + +/datum/hivebuff/evo_buff/major/on_engage(obj/effect/alien/resin/special/pylon/purchased_pylon) + hive.allow_no_queen_evo = TRUE + + return ..() + +/datum/hivebuff/evo_buff/major/on_cease() + . = ..() + hive.allow_no_queen_evo = FALSE + +/datum/hivebuff/game_ender_caste + name = "His Grace" + desc = "A huge behemoth of a Xenomorph which can tear its way through defences and flesh alike. Requires open space to grow." + tier = HIVEBUFF_TIER_MAJOR + radial_icon = "king" + + is_reusable = TRUE + cost = 0 + special_fail_message = "Only one hatchery may exist at a time." + cooldown_duration = 15 MINUTES // This buff ceases instantly so we need to incorporation the spawning time too + number_of_required_pylons = 2 + must_select_pylon = TRUE + +/datum/hivebuff/game_ender_caste/New() + roundtime_to_enable = GLOB.king_acquisition_time + + return ..() + +/datum/hivebuff/game_ender_caste/handle_special_checks() + if(locate(/mob/living/carbon/xenomorph/king) in hive.totalXenos) + special_fail_message = "Only one King may exist at a time." + return FALSE + + return !hive.has_hatchery + +/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)) + var/failed = FALSE + for(var/x_offset in -1 to 1) + for(var/y_offset in -1 to 1) + var/turf/turf_to_check = locate(potential_turf.x + x_offset, potential_turf.y + y_offset, potential_turf.z) + if(turf_to_check.density) + failed = TRUE + break + if(!turf_to_check.is_weedable()) + failed = TRUE + break + var/area/target_area = get_area(turf_to_check) + if(target_area.flags_area & AREA_NOTUNNEL) + failed = TRUE + break + for(var/obj/structure/struct in turf_to_check) + if(struct.density) + failed = TRUE + break + if(!failed) + spawn_turf = potential_turf + break + + if(!spawn_turf) + engage_failure_message = "Unable to find a viable spawn point for the King." + return FALSE + + for(var/obj/effect/alien/resin/special/pylon/pylon as anything in hive.active_endgame_pylons) + pylon.protection_level = TURF_PROTECTION_OB + pylon.update_icon() + + new /obj/effect/alien/resin/king_cocoon(spawn_turf, hive.hivenumber) + + return TRUE + +/datum/hivebuff/defence + name = "Boon of Defence" + desc = "Increases all xenomorph armour by 2.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/apply_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.armor_modifier += XENO_ARMOR_MOD_TINY + xeno.recalculate_armor() + +/datum/hivebuff/defence/remove_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.armor_modifier -= XENO_ARMOR_MOD_TINY + xeno.recalculate_armor() + +/datum/hivebuff/defence/major + name = "Major Boon of Defence" + desc = "Increases all xenomorph armour by 5 for 10 minutes" + tier = HIVEBUFF_TIER_MAJOR + + engage_flavourmessage = "The Queen has imbued us with even greater chitin." + duration = 10 MINUTES + cost = 2 + number_of_required_pylons = 2 + radial_icon = "shield_m" + +/datum/hivebuff/defence/major/apply_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.armor_modifier += XENO_ARMOR_MOD_VERY_SMALL + xeno.recalculate_armor() + +/datum/hivebuff/defence/major/remove_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.armor_modifier -= XENO_ARMOR_MOD_VERY_SMALL + xeno.recalculate_armor() + +/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 sharp claws." + duration = 5 MINUTES + number_of_required_pylons = 1 + radial_icon = "slash" + +/datum/hivebuff/attack/apply_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.damage_modifier += XENO_DAMAGE_MOD_VERY_SMALL + xeno.recalculate_damage() + +/datum/hivebuff/attack/remove_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.damage_modifier -= XENO_DAMAGE_MOD_VERY_SMALL + xeno.recalculate_damage() + +/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 razor-sharp claws." + duration = 10 MINUTES + number_of_required_pylons = 2 + cost = 2 + radial_icon = "slash_m" + +/datum/hivebuff/attack/major/apply_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.damage_modifier += XENO_DAMAGE_MOD_SMALL + xeno.recalculate_damage() + +/datum/hivebuff/attack/major/remove_buff_effects(mob/living/carbon/xenomorph/xeno) + xeno.damage_modifier -= XENO_DAMAGE_MOD_SMALL + xeno.recalculate_damage() diff --git a/code/modules/cm_aliens/structures/special/pylon_core.dm b/code/modules/cm_aliens/structures/special/pylon_core.dm index 71211d67e23c..cff839baf1d7 100644 --- a/code/modules/cm_aliens/structures/special/pylon_core.dm +++ b/code/modules/cm_aliens/structures/special/pylon_core.dm @@ -28,6 +28,13 @@ plane = FLOOR_PLANE +/obj/effect/alien/resin/special/pylon/endgame/update_icon() + if(protection_level == TURF_PROTECTION_OB) + icon_state = "pylon_active" + return + + icon_state = "pylon" + /obj/effect/alien/resin/special/pylon/Initialize(mapload, hive_ref) . = ..() @@ -142,16 +149,22 @@ /obj/effect/alien/resin/special/pylon/endgame cover_range = WEED_RANGE_CORE + protection_level = TURF_PROTECTION_CAS var/activated = FALSE +/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() + LAZYREMOVE(linked_hive.active_endgame_pylons, src) if(activated) activated = FALSE if(hijack_delete) return ..() - marine_announcement("ALERT.\n\nEnergy build up around communication relay at [get_area(src)] halted.", "[MAIN_AI_SYSTEM] Biological Scanner") + marine_announcement("ALERT.\n\nEnergy build up around communication relay at [get_area_name(src)] halted.", "[MAIN_AI_SYSTEM] Biological Scanner") for(var/hivenumber in GLOB.hive_datum) var/datum/hive_status/checked_hive = GLOB.hive_datum[hivenumber] @@ -159,15 +172,15 @@ continue if(checked_hive == linked_hive) - xeno_announcement(SPAN_XENOANNOUNCE("We have lost our control of the tall's communication relay at [get_area(src)]."), hivenumber, XENO_GENERAL_ANNOUNCE) + xeno_announcement(SPAN_XENOANNOUNCE("We have lost our control of the tall's communication relay at [get_area_name(src)]."), hivenumber, XENO_GENERAL_ANNOUNCE) else - xeno_announcement(SPAN_XENOANNOUNCE("Another hive has lost control of the tall's communication relay at [get_area(src)]."), hivenumber, XENO_GENERAL_ANNOUNCE) + xeno_announcement(SPAN_XENOANNOUNCE("Another hive has lost control of the tall's communication relay at [get_area_name(src)]."), hivenumber, XENO_GENERAL_ANNOUNCE) linked_hive.hive_ui.update_pylon_status() return ..() /// 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") + marine_announcement("ALERT.\n\nIrregular build up of energy around communication relays at [get_area_name(src)], biological hazard detected.\n\nDANGER: Hazard is strengthening xenomorphs, advise urgent termination of hazard by ground forces.", "[MAIN_AI_SYSTEM] Biological Scanner") for(var/hivenumber in GLOB.hive_datum) var/datum/hive_status/checked_hive = GLOB.hive_datum[hivenumber] @@ -175,28 +188,26 @@ continue if(checked_hive == linked_hive) - xeno_announcement(SPAN_XENOANNOUNCE("We have harnessed the tall's communication relay at [get_area(src)].\n\nWe will now grow our numbers from this pylon. Hold it!"), hivenumber, XENO_GENERAL_ANNOUNCE) + xeno_announcement(SPAN_XENOANNOUNCE("We have harnessed the tall's communication relay at [get_area_name(src)].\n\nWe will now grow royal resin from this pylon. Hold it!"), hivenumber, XENO_GENERAL_ANNOUNCE) else - xeno_announcement(SPAN_XENOANNOUNCE("Another hive has harnessed the tall's communication relay at [get_area(src)].[linked_hive.faction_is_ally(checked_hive.name) ? "" : " Stop them!"]"), hivenumber, XENO_GENERAL_ANNOUNCE) + xeno_announcement(SPAN_XENOANNOUNCE("Another hive has harnessed the tall's communication relay at [get_area_name(src)].[linked_hive.faction_is_ally(checked_hive.name) ? "" : " Stop them!"]"), hivenumber, XENO_GENERAL_ANNOUNCE) activated = TRUE linked_hive.check_if_hit_larva_from_pylon_limit() - addtimer(CALLBACK(src, PROC_REF(give_larva)), XENO_PYLON_ACTIVATION_COOLDOWN, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_LOOP|TIMER_DELETE_ME) + addtimer(CALLBACK(src, PROC_REF(give_royal_resin)), XENO_PYLON_ACTIVATION_COOLDOWN, TIMER_UNIQUE|TIMER_OVERRIDE|TIMER_LOOP|TIMER_DELETE_ME) /// Looped proc via timer to give larva after time -/obj/effect/alien/resin/special/pylon/endgame/proc/give_larva() +/obj/effect/alien/resin/special/pylon/endgame/proc/give_royal_resin() if(!activated) return if(!linked_hive.hive_location || !linked_hive.living_xeno_queen) return - if(linked_hive.check_if_hit_larva_from_pylon_limit()) - return + if(linked_hive.buff_points < linked_hive.max_buff_points) + linked_hive.buff_points += 1 - linked_hive.partial_larva += (linked_hive.get_real_total_xeno_count() + linked_hive.stored_larva) * LARVA_ADDITION_MULTIPLIER - linked_hive.convert_partial_larva_to_full_larva() - linked_hive.hive_ui.update_burrowed_larva() + linked_hive.check_if_hit_larva_from_pylon_limit() //Hive Core - Generates strong weeds, supports other buildings /obj/effect/alien/resin/special/pylon/core diff --git a/code/modules/mob/living/carbon/human/death.dm b/code/modules/mob/living/carbon/human/death.dm index 34582a6612d4..1bd9562c2cf5 100644 --- a/code/modules/mob/living/carbon/human/death.dm +++ b/code/modules/mob/living/carbon/human/death.dm @@ -96,7 +96,7 @@ if(last_living_human && shipside_humans_count <= 1 && (GLOB.last_qm_callout + 2 MINUTES) < world.time) GLOB.last_qm_callout = world.time // Tell the xenos where the human is. - xeno_announcement("I sense the last tallhost hiding in [get_area(last_living_human)].", XENO_HIVE_NORMAL, SPAN_ANNOUNCEMENT_HEADER_BLUE("[QUEEN_MOTHER_ANNOUNCE]")) + xeno_announcement("I sense the last tallhost hiding in [get_area_name(last_living_human)].", XENO_HIVE_NORMAL, SPAN_ANNOUNCEMENT_HEADER_BLUE("[QUEEN_MOTHER_ANNOUNCE]")) // Tell the human he is the last guy. if(last_living_human.client) to_chat(last_living_human, SPAN_ANNOUNCEMENT_HEADER_BLUE("Panic creeps up your spine. You realize that you are the last survivor.")) diff --git a/code/modules/mob/living/carbon/xenomorph/Evolution.dm b/code/modules/mob/living/carbon/xenomorph/Evolution.dm index e0f0844bfa13..b0889bf9834d 100644 --- a/code/modules/mob/living/carbon/xenomorph/Evolution.dm +++ b/code/modules/mob/living/carbon/xenomorph/Evolution.dm @@ -196,6 +196,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/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index baf69a54e16e..b6db7210b351 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -616,8 +616,10 @@ return "Moderate" if(3 to 3.9) return "Strong" - if(4 to INFINITY) + if(4 to 4.9) return "Very Strong" + if(4.9 to INFINITY) + return "Overwhelming" /mob/living/carbon/xenomorph/proc/start_tracking_resin_mark(obj/effect/alien/resin/marker/target) if(!target) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/king/king_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/king/king_abilities.dm new file mode 100644 index 000000000000..6d66f8136dc5 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/king/king_abilities.dm @@ -0,0 +1,56 @@ + +/// 3 x 3 damage centred on the xenomorph +/datum/action/xeno_action/onclick/rend + name = "Rend" + action_icon_state = "rav_eviscerate" + 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 = "screech" + ability_name = "doom" + macro_path = /datum/action_xeno_action/verb/verb_doom + xeno_cooldown = 45 SECONDS + plasma_cost = 50 + ability_primacy = XENO_PRIMARY_ACTION_2 + + var/daze_length_seconds = 1 + var/slow_length_seconds = 4 + +/// 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 = "charge" + ability_name = "destroy" + macro_path = /datum/action/xeno_action/verb/verb_destroy + action_type = XENO_ACTION_ACTIVATE + xeno_cooldown = 60 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/king_shield + name = "Bulwark of the Hive" + action_icon_state = "soak" + ability_name = "legion_shield" + macro_path = /datum/action_xeno_action/verb/king_shield + action_type = XENO_ACTION_ACTIVATE + xeno_cooldown = 60 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/king/king_macros.dm b/code/modules/mob/living/carbon/xenomorph/abilities/king/king_macros.dm new file mode 100644 index 000000000000..2df965861efc --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/king/king_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/king_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/king/king_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/king/king_powers.dm new file mode 100644 index 000000000000..62a085eb3c35 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/abilities/king/king_powers.dm @@ -0,0 +1,351 @@ +/* + REND ABILITY + 3x3 aoe damage centred on the King. 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 + King 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, 'sound/voice/deep_alien_screech2.ogg', 75, 0, status = 0) + xeno.visible_message(SPAN_XENOHIGHDANGER("[xeno] emits an raspy guttural roar!")) + xeno.create_shriekwave() + + var/datum/effect_system/smoke_spread/king_doom/smoke_gas = new /datum/effect_system/smoke_spread/king_doom + smoke_gas.set_up(7, 0, get_turf(xeno), null, 6) + smoke_gas.start() + + for(var/atom/current_atom as anything in view(owner)) + if(istype(current_atom, /obj/item/device)) + var/obj/item/device/potential_lightsource = current_atom + + var/time_to_extinguish = get_dist(owner, potential_lightsource) DECISECONDS + + //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) + + else if(ishuman(current_atom)) + // "Confuse" and slow humans in the area and turn off their armour lights. + var/mob/living/carbon/human/human = current_atom + + human.EyeBlur(daze_length_seconds) + human.Daze(daze_length_seconds) + human.Superslow(slow_length_seconds) + human.add_client_color_matrix("doom", 99, color_matrix_multiply(color_matrix_saturation(0), color_matrix_from_string("#eeeeee"))) + human.overlay_fullscreen("doom", /atom/movable/screen/fullscreen/flash/noise/nvg) + addtimer(CALLBACK(human, TYPE_PROC_REF(/mob, remove_client_color_matrix), "doom", 1 SECONDS), 5 SECONDS) + addtimer(CALLBACK(human, TYPE_PROC_REF(/mob, clear_fullscreen), "doom", 0.5 SECONDS), 5 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) + + if(!istype(current_atom, /mob/dead)) + var/power = current_atom.light_power + var/range = current_atom.light_range + if(power > 0 && range > 0) + if(current_atom.light_system != MOVABLE_LIGHT) + current_atom.set_light(l_range=0) + addtimer(CALLBACK(current_atom, TYPE_PROC_REF(/atom, set_light), range, power), 10 SECONDS) + else + current_atom.set_light_range(0) + addtimer(CALLBACK(current_atom, TYPE_PROC_REF(/atom, set_light_range), range), 10 SECONDS) + + + 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/king_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 == xeno) + continue + 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/king_shield/proc/start_shield(mob/living/carbon/xenomorph/xeno) + var/datum/xeno_shield/shield = xeno.add_xeno_shield(shield_amount, XENO_SHIELD_SOURCE_KING_BULWARKSPELL, /datum/xeno_shield/king_shield) + if(shield) + xeno.create_shield(shield_duration, "purple_animated_shield_full") + + +/* + DESTROY ABILITY + King 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) + 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 + + if(istype(target, /obj/vehicle/multitile)) + 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/king_leap/leap_visual = new(owner.loc, negative, owner.dir) + new /obj/effect/xenomorph/xeno_telegraph/king_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_turf && owner.loc != target_turf) + owner.forceMove(get_step(owner, get_dir(owner, target_turf))) + 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/king_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/king_attack_template(template_turf, 10) + +/obj/effect/temp_visual/king_leap + icon = 'icons/mob/xenos/king.dmi' + icon_state = "Normal King Charging" + layer = 4.7 + plane = -4 + pixel_x = -32 + duration = 10 + randomdir = FALSE + +/obj/effect/temp_visual/king_leap/Initialize(mapload, negative, dir) + . = ..() + setDir(dir) + INVOKE_ASYNC(src, PROC_REF(flight), negative) + +/obj/effect/temp_visual/king_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 King 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/king_leap/end + pixel_x = LEAP_HEIGHT + pixel_z = LEAP_HEIGHT + duration = 10 + +/obj/effect/temp_visual/king_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/king_attack_template + icon = 'icons/effects/96x96.dmi' + icon_state = "xenolandingpink" + layer = BELOW_MOB_LAYER + +/obj/effect/xenomorph/xeno_telegraph/king_attack_template/yellow + icon_state = "xenolandingyellow" diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm index 6ef111aed66f..ff9e1ce1776f 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/queen/queen_powers.dm @@ -375,7 +375,7 @@ /datum/action/xeno_action/onclick/manage_hive/use_ability(atom/Atom) var/mob/living/carbon/xenomorph/queen/queen_manager = owner plasma_cost = 0 - var/list/options = list("Banish (500)", "Re-Admit (100)", "De-evolve (500)", "Reward Jelly (500)", "Exchange larva for evolution (100)",) + var/list/options = list("Banish (500)", "Re-Admit (100)", "De-evolve (500)", "Reward Jelly (500)", "Exchange larva for evolution (100)", "Purchase Buffs") if(queen_manager.hive.hivenumber == XENO_HIVE_CORRUPTED) var/datum/hive_status/corrupted/hive = queen_manager.hive options += "Add Personal Ally" @@ -401,8 +401,76 @@ remove_personal_ally() if("Clear Personal Allies") clear_personal_allies() + if("Purchase Buffs") + purchase_buffs() return ..() +/datum/action/xeno_action/onclick/manage_hive/proc/purchase_buffs() + var/mob/living/carbon/xenomorph/queen/xeno = owner + + var/list/datum/hivebuff/buffs = list() + var/list/names = list() + var/list/radial_images = list() + var/major_available = FALSE + for(var/datum/hivebuff/buff as anything in xeno.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?.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, xeno?.client?.eye, radial_images_tiers) + + 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, xeno?.client?.eye, radial_images, radius = 72, tooltips = TRUE) + + if(!selection) + return FALSE + + if(!buffs[selection]) + to_chat(xeno, "This selection is impossible!") + return FALSE + + if(buffs[selection].must_select_pylon) + var/list/pylon_to_area_dictionary = list() + for(var/obj/effect/alien/resin/special/pylon/endgame/pylon as anything in xeno.hive.active_endgame_pylons) + pylon_to_area_dictionary[get_area_name(pylon.loc)] = pylon + + var/choice = tgui_input_list(xeno, "Select a pylon for the buff:", "Choice", pylon_to_area_dictionary, 1 MINUTES) + + if(!choice) + to_chat(xeno, "You must choose a pylon.") + return FALSE + + xeno.hive.attempt_apply_hivebuff(buffs[selection], xeno, pylon_to_area_dictionary[choice]) + return TRUE + + xeno.hive.attempt_apply_hivebuff(buffs[selection], xeno, pick(xeno.hive.active_endgame_pylons)) + return TRUE + /datum/action/xeno_action/onclick/manage_hive/proc/add_personal_ally() var/mob/living/carbon/xenomorph/queen/user_xeno = owner if(user_xeno.hive.hivenumber != XENO_HIVE_CORRUPTED) 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 bff59186fd04..3f248212e51e 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/King.dm b/code/modules/mob/living/carbon/xenomorph/castes/King.dm new file mode 100644 index 000000000000..e6b084db2d04 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/castes/King.dm @@ -0,0 +1,111 @@ +/datum/caste_datum/king + caste_type = XENO_CASTE_KING + caste_desc = "The end of the line." + tier = 4 + + 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_KING + 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 + evolution_allowed = FALSE + can_vent_crawl = FALSE + + behavior_delegate_type = /datum/behavior_delegate/king_base + + tackle_min = 6 + tackle_max = 10 + + minimap_icon = "xenoqueen" + + fire_immunity = FIRE_IMMUNITY_NO_DAMAGE + +/mob/living/carbon/xenomorph/king + caste_type = XENO_CASTE_KING + name = XENO_CASTE_KING + desc = "A massive alien covered in spines and armoured plates." + icon = 'icons/mob/xenos/king.dmi' + icon_size = 64 + icon_state = "King Walking" + plasma_types = list(PLASMA_CHITIN) + pixel_x = -16 + old_x = -16 + mob_size = MOB_SIZE_IMMOBILE + tier = 4 + small_explosives_stun = FALSE + counts_for_slots = FALSE + organ_value = 50000 + + claw_type = CLAW_TYPE_VERY_SHARP + age = -1 + aura_strength = 6 + + 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/king_shield, + /datum/action/xeno_action/onclick/emit_pheromones, + ) + + icon_xeno = 'icons/mob/xenos/king.dmi' + weed_food_states = list() + weed_food_states_flipped = list() + + bubble_icon = "alienroyal" + +/mob/living/carbon/xenomorph/king/get_organ_icon() + return "heart_t3" + +/mob/living/carbon/xenomorph/king/Destroy() + UnregisterSignal(src, COMSIG_MOVABLE_PRE_MOVE) + + return ..() + +/mob/living/carbon/xenomorph/king/Initialize() + . = ..() + AddComponent(/datum/component/footstep, 2 , 35, 11, 4, "alien_footstep_large") + RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(check_block)) + +/mob/living/carbon/xenomorph/king/proc/check_block(mob/king, turf/new_loc) + SIGNAL_HANDLER + for(var/mob/living/carbon/carbon in new_loc.contents) + if(isxeno(carbon)) + var/mob/living/carbon/xenomorph/xeno = carbon + if(xeno.hivenumber == src.hivenumber) + xeno.KnockDown((5 DECISECONDS) / GLOBAL_STATUS_MULTIPLIER) + else + xeno.KnockDown((1 SECONDS) / GLOBAL_STATUS_MULTIPLIER) + else + if(carbon.stat != DEAD) + carbon.apply_armoured_damage(20) + carbon.KnockDown((1 SECONDS) / GLOBAL_STATUS_MULTIPLIER) + + playsound(src, 'sound/weapons/alien_knockdown.ogg', 25, 1) + +/datum/behavior_delegate/king_base + name = "Base King Behavior Delegate" + +/mob/living/carbon/xenomorph/king/rogue + icon_xeno = 'icons/mob/xenos/rogueking.dmi' + icon = 'icons/mob/xenos/rogueking.dmi' + +/atom/movable/vis_obj/xeno_wounds/rogue + icon = 'icons/mob/xenos/roguedamage.dmi' + +/mob/living/carbon/xenomorph/king/rogue/Initialize(mapload, mob/living/carbon/xenomorph/old_xeno, hivenumber) + . = ..() + vis_contents -= wound_icon_holder + wound_icon_holder = new /atom/movable/vis_obj/xeno_wounds/rogue(null, src) + vis_contents += wound_icon_holder diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm index db682e161e87..eb720834ec6d 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm @@ -552,6 +552,7 @@ . += "Pooled Larvae: [stored_larvae]" . += "Leaders: [xeno_leader_num] / [hive?.queen_leader_limit]" + . += "Royal Resin: [hive?.buff_points]" if(!queen_aged && queen_age_timer_id != TIMER_ID_NULL) . += "Maturity: [time2text(timeleft(queen_age_timer_id), "mm:ss")] remaining" diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm index 9c8b54dad483..d2545a85d527 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm @@ -133,6 +133,21 @@ var/list/available_nicknumbers = list() + + /// Hive buffs + var/buff_points = HIVE_STARTING_BUFFPOINTS + var/max_buff_points = HIVE_MAX_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 used hivebuffs currently on cooldown + var/list/cooldown_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. @@ -140,6 +155,9 @@ */ var/static/list/evolution_menu_images + /// Has a King hatchery + var/has_hatchery = FALSE + /datum/hive_status/New() hive_ui = new(src) mark_ui = new(src) @@ -150,6 +168,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 @@ -730,6 +752,10 @@ hivecore_cooldown = FALSE xeno_message(SPAN_XENOBOLDNOTICE("The weeds have recovered! A new hive core can be built!"),3,hivenumber) + // No buffs in hijack + for(var/datum/hivebuff/buff in active_hivebuffs) + buff._on_cease() + /datum/hive_status/proc/free_respawn(client/C) stored_larva++ if(!hive_location || !hive_location.spawn_burrowed_larva(C.mob)) @@ -1372,6 +1398,65 @@ if(!length(personal_allies)) return clear_personal_allies(TRUE) +/// 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) + + // First check if we any pylons which are capable of supporting hivebuffs + if(!LAZYLEN(active_endgame_pylons)) + return + + for(var/datum/hivebuff/possible_hivebuff as anything in potential_hivebuffs) + // Round isn't old enough yet + if(ROUND_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 + + // Prevent the same lineage of buff in active hivebuffs (e.g. no minor and major health allowed) + var/already_active = FALSE + for(var/datum/hivebuff/buff as anything in active_hivebuffs) + if(istype(buff, possible_hivebuff)) + already_active = TRUE + break + if(ispath(possible_hivebuff, buff.type)) + already_active = TRUE + break + if(already_active) + potential_hivebuffs -= possible_hivebuff + continue + + //If this buff isn't combineable, check if any other active hivebuffs aren't combineable + if(!initial(possible_hivebuff.is_combineable)) + var/found_conflict = FALSE + for(var/datum/hivebuff/active_hivebuff in active_hivebuffs) + if(!active_hivebuff.is_combineable) + found_conflict = TRUE + break + if(found_conflict) + potential_hivebuffs -= possible_hivebuff + continue + + // If the buff is not reusable check against used hivebuffs. + if(!initial(possible_hivebuff.is_reusable)) + if(locate(possible_hivebuff) in used_hivebuffs) + 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 @@ -1433,3 +1518,4 @@ name = "Attack" desc = "Attack the enemy here!" icon_state = "attack" + diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm b/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm index eca88761ad9b..4d031941052b 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm @@ -121,10 +121,10 @@ /datum/hive_status_ui/proc/update_pylon_status(send_update = TRUE) if(assoc_hive.get_structure_count(XENO_STRUCTURE_PYLON) < 1) pylon_status = "" - else if(assoc_hive.hit_larva_pylon_limit) - pylon_status = "The hive's power has surpassed what the pylons can provide." + else if(assoc_hive.buff_points >= assoc_hive.max_buff_points) + pylon_status = "Royal resin has reached its maximum capacity of [assoc_hive.max_buff_points], spend it!" else - pylon_status = "Pylons are strengthening our numbers!" + pylon_status = "Pylons are providing us with royal resin! Current royal resin: [assoc_hive.buff_points]." if(send_update) SStgui.update_uis(src) diff --git a/code/modules/mob/living/carbon/xenomorph/update_icons.dm b/code/modules/mob/living/carbon/xenomorph/update_icons.dm index 64f9949e94cd..7724c4f66a86 100644 --- a/code/modules/mob/living/carbon/xenomorph/update_icons.dm +++ b/code/modules/mob/living/carbon/xenomorph/update_icons.dm @@ -276,10 +276,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 d4c8bf6ce6e3..ceb6e42f4a4f 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -718,6 +718,7 @@ #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\hedgehog_shield.dm" +#include "code\datums\xeno_shields\shield_types\king_shield.dm" #include "code\datums\xeno_shields\shield_types\vanguard_shield.dm" #include "code\defines\procs\admin.dm" #include "code\defines\procs\announcement.dm" @@ -1610,6 +1611,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" @@ -2025,6 +2027,9 @@ #include "code\modules\mob\living\carbon\xenomorph\abilities\facehugger\facehugger_powers.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\hivelord\hivelord_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\hivelord\hivelord_powers.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\king\king_abilities.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\king\king_macros.dm" +#include "code\modules\mob\living\carbon\xenomorph\abilities\king\king_powers.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\lesser_drone\lesser_drone_abilities.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\lesser_drone\lesser_drone_macros.dm" #include "code\modules\mob\living\carbon\xenomorph\abilities\lesser_drone\lesser_drone_powers.dm" @@ -2065,6 +2070,7 @@ #include "code\modules\mob\living\carbon\xenomorph\castes\Facehugger.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Hellhound.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Hivelord.dm" +#include "code\modules\mob\living\carbon\xenomorph\castes\King.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Larva.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\lesser_drone.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Lurker.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/king.dmi b/icons/mob/xenos/king.dmi new file mode 100644 index 000000000000..cee599ba7f77 Binary files /dev/null and b/icons/mob/xenos/king.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..10ea5680698a 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/wounds.dmi b/icons/mob/xenos/wounds.dmi index 1b8ca0cebdd5..68f728c1efd8 100644 Binary files a/icons/mob/xenos/wounds.dmi and b/icons/mob/xenos/wounds.dmi differ diff --git a/icons/obj/structures/alien/xenoKingHatchery.dmi b/icons/obj/structures/alien/xenoKingHatchery.dmi new file mode 100644 index 000000000000..e7911658b3f0 Binary files /dev/null and b/icons/obj/structures/alien/xenoKingHatchery.dmi differ diff --git a/icons/ui_icons/hivebuff_radial.dmi b/icons/ui_icons/hivebuff_radial.dmi new file mode 100644 index 000000000000..f546ec268512 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