diff --git a/code/__DEFINES/job.dm b/code/__DEFINES/job.dm index 56062cb0213b..03b2071a8e66 100644 --- a/code/__DEFINES/job.dm +++ b/code/__DEFINES/job.dm @@ -358,14 +358,6 @@ var/global/list/job_command_roles = JOB_COMMAND_ROLES_LIST #define JOB_PLAYTIME_TIER_3 (70 HOURS) #define JOB_PLAYTIME_TIER_4 (175 HOURS) -#define XENO_NO_AGE -1 -#define XENO_YOUNG 0 -#define XENO_NORMAL 1 -#define XENO_MATURE 2 -#define XENO_ELDER 3 -#define XENO_ANCIENT 4 -#define XENO_PRIME 5 - /// For monthly time tracking #define JOB_OBSERVER "Observer" #define TIMELOCK_JOB(role_id, hours) new/datum/timelock(role_id, hours, role_id) diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 072738184807..3417d3624419 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -117,26 +117,6 @@ #define CANSLOW (1<<19) #define NO_PERMANENT_DAMAGE (1<<20) -// ============================= -// hive types - -#define XENO_HIVE_NORMAL "xeno_hive_normal" -#define XENO_HIVE_CORRUPTED "xeno_hive_corrupted" -#define XENO_HIVE_ALPHA "xeno_hive_alpha" -#define XENO_HIVE_BRAVO "xeno_hive_bravo" -#define XENO_HIVE_CHARLIE "xeno_hive_charlie" -#define XENO_HIVE_DELTA "xeno_hive_delta" -#define XENO_HIVE_FERAL "xeno_hive_feral" -#define XENO_HIVE_TAMED "xeno_hive_tamed" -#define XENO_HIVE_MUTATED "xeno_hive_mutated" -#define XENO_HIVE_FORSAKEN "xeno_hive_forsaken" -#define XENO_HIVE_YAUTJA "xeno_hive_yautja" -#define XENO_HIVE_RENEGADE "xeno_hive_renegade" - -#define ALL_XENO_HIVES list(XENO_HIVE_NORMAL, XENO_HIVE_CORRUPTED, XENO_HIVE_ALPHA, XENO_HIVE_BRAVO, XENO_HIVE_CHARLIE, XENO_HIVE_DELTA, XENO_HIVE_FERAL, XENO_HIVE_TAMED, XENO_HIVE_MUTATED, XENO_HIVE_FORSAKEN, XENO_HIVE_YAUTJA, XENO_HIVE_RENEGADE) - -//================================================= - // ============================= // slowdowns #define XENO_SLOWED_AMOUNT 0.7 diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno_defines.dm similarity index 96% rename from code/__DEFINES/xeno.dm rename to code/__DEFINES/xeno_defines.dm index a0a4c927d3d9..47fdee20be66 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno_defines.dm @@ -1,3 +1,34 @@ +// Xeno ages + +#define XENO_NO_AGE -1 +#define XENO_YOUNG 0 +#define XENO_NORMAL 1 +#define XENO_MATURE 2 +#define XENO_ELDER 3 +#define XENO_ANCIENT 4 +#define XENO_PRIME 5 + + +// ============================= +// hive types + +#define XENO_HIVE_NORMAL "xeno_hive_normal" +#define XENO_HIVE_CORRUPTED "xeno_hive_corrupted" +#define XENO_HIVE_ALPHA "xeno_hive_alpha" +#define XENO_HIVE_BRAVO "xeno_hive_bravo" +#define XENO_HIVE_CHARLIE "xeno_hive_charlie" +#define XENO_HIVE_DELTA "xeno_hive_delta" +#define XENO_HIVE_FERAL "xeno_hive_feral" +#define XENO_HIVE_TAMED "xeno_hive_tamed" +#define XENO_HIVE_MUTATED "xeno_hive_mutated" +#define XENO_HIVE_FORSAKEN "xeno_hive_forsaken" +#define XENO_HIVE_YAUTJA "xeno_hive_yautja" +#define XENO_HIVE_RENEGADE "xeno_hive_renegade" + +#define ALL_XENO_HIVES list(XENO_HIVE_NORMAL, XENO_HIVE_CORRUPTED, XENO_HIVE_ALPHA, XENO_HIVE_BRAVO, XENO_HIVE_CHARLIE, XENO_HIVE_DELTA, XENO_HIVE_FERAL, XENO_HIVE_TAMED, XENO_HIVE_MUTATED, XENO_HIVE_FORSAKEN, XENO_HIVE_YAUTJA, XENO_HIVE_RENEGADE) + +//================================================= + #define XENOCON_THRESHOLD 6000 #define TUNNEL_MOVEMENT_XENO_DELAY 20 diff --git a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm index 7beaaab8a04e..ee6b39a1fcf7 100644 --- a/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm +++ b/code/modules/mob/living/carbon/xenomorph/Xenomorph.dm @@ -347,157 +347,158 @@ var/atom/movable/vis_obj/xeno_wounds/wound_icon_holder var/atom/movable/vis_obj/xeno_pack/backpack_icon_holder -/mob/living/carbon/xenomorph/Initialize(mapload, mob/living/carbon/xenomorph/oldXeno, h_number) - var/area/A = get_area(src) - if(A && A.statistic_exempt) - statistic_exempt = TRUE +/mob/living/carbon/xenomorph/Initialize(mapload, mob/living/carbon/xenomorph/old_xeno, hivenumber) + + if(old_xeno && old_xeno.hivenumber) + src.hivenumber = old_xeno.hivenumber + else if(hivenumber) + src.hivenumber = hivenumber + + var/datum/hive_status/hive = GLOB.hive_datum[src.hivenumber] + + if(hive) + hive.add_xeno(src) wound_icon_holder = new(null, src) vis_contents += wound_icon_holder - if(oldXeno) - set_movement_intent(oldXeno.m_intent) - hivenumber = oldXeno.hivenumber - nicknumber = oldXeno.nicknumber - life_kills_total = oldXeno.life_kills_total - life_damage_taken_total = oldXeno.life_damage_taken_total - evolution_stored = oldXeno.evolution_stored - if(oldXeno.iff_tag) - iff_tag = oldXeno.iff_tag - iff_tag.forceMove(src) - oldXeno.iff_tag = null - else if (h_number) - hivenumber = h_number - set_languages(list(LANGUAGE_XENOMORPH, LANGUAGE_HIVEMIND)) - if(oldXeno) - for(var/datum/language/L in oldXeno.languages) - add_language(L.name)//Make sure to keep languages (mostly for event Queens that know English) - // Well, not yet, technically - var/datum/hive_status/in_hive = GLOB.hive_datum[hivenumber] - if(in_hive) - in_hive.add_xeno(src) - // But now we are! + ///Handle transferring things from the old Xeno if we have one in the case of evolve, devolve etc. + if(old_xeno) + src.nicknumber = old_xeno.nicknumber + src.life_kills_total = old_xeno.life_kills_total + src.life_damage_taken_total = old_xeno.life_damage_taken_total + src.evolution_stored = old_xeno.evolution_stored + + for(var/datum/language/language in old_xeno.languages) + add_language(language.name)//Make sure to keep languages (mostly for event Queens that know English) + + //Carry over intents & targeted limb to the new Xeno + set_movement_intent(old_xeno.m_intent) + a_intent_change(old_xeno.a_intent) + + //We are hiding, let's keep hiding if we can! + if(old_xeno.layer == XENO_HIDING_LAYER) + for(var/datum/action/xeno_action/onclick/xenohide/hide in actions) + if(istype(hide)) + layer = XENO_HIDING_LAYER + hide.button.icon_state = "template_active" + + //If we're holding things drop them + for(var/obj/item/item in old_xeno.contents) //Drop stuff + old_xeno.drop_inv_item_on_ground(item) + old_xeno.empty_gut() + + //Set leader to the new mob + if(hive && IS_XENO_LEADER(old_xeno)) + hive.replace_hive_leader(old_xeno, src) + + if(old_xeno.iff_tag) + iff_tag = old_xeno.iff_tag + iff_tag.forceMove(src) + old_xeno.iff_tag = null - for(var/T in in_hive.hive_inherant_traits) - ADD_TRAIT(src, T, TRAIT_SOURCE_HIVE) + if(hive) + for(var/trait in hive.hive_inherant_traits) + ADD_TRAIT(src, trait, TRAIT_SOURCE_HIVE) mutators.xeno = src + //Set caste stuff if(caste_type && GLOB.xeno_datum_list[caste_type]) caste = GLOB.xeno_datum_list[caste_type] - else - to_world("something went very wrong") - return - update_icon_source() + //Fire immunity signals + if (caste.fire_immunity != FIRE_IMMUNITY_NONE) + if(caste.fire_immunity & FIRE_IMMUNITY_NO_IGNITE) + RegisterSignal(src, COMSIG_LIVING_PREIGNITION, PROC_REF(fire_immune)) - acid_splash_cooldown = caste.acid_splash_cooldown + RegisterSignal(src, list(COMSIG_LIVING_FLAMER_CROSSED, COMSIG_LIVING_FLAMER_FLAMED), PROC_REF(flamer_crossed_immune)) + else + UnregisterSignal(src, list( + COMSIG_LIVING_PREIGNITION, + COMSIG_LIVING_FLAMER_CROSSED, + COMSIG_LIVING_FLAMER_FLAMED + )) - if (caste.fire_immunity != FIRE_IMMUNITY_NONE) - if(caste.fire_immunity & FIRE_IMMUNITY_NO_IGNITE) - RegisterSignal(src, COMSIG_LIVING_PREIGNITION, PROC_REF(fire_immune)) - RegisterSignal(src, list( - COMSIG_LIVING_FLAMER_CROSSED, - COMSIG_LIVING_FLAMER_FLAMED, - ), PROC_REF(flamer_crossed_immune)) - else - UnregisterSignal(src, list( - COMSIG_LIVING_PREIGNITION, - COMSIG_LIVING_FLAMER_CROSSED, - COMSIG_LIVING_FLAMER_FLAMED, - )) + if(caste.spit_types && length(caste.spit_types)) + ammo = GLOB.ammo_list[caste.spit_types[1]] - recalculate_everything() + acid_splash_cooldown = caste.acid_splash_cooldown + + if(caste.adjust_size_x != 1) + var/matrix/matrix = matrix() + matrix.Scale(caste.adjust_size_x, caste.adjust_size_y) + apply_transform(matrix) + + behavior_delegate = new caste.behavior_delegate_type() + behavior_delegate.bound_xeno = src + behavior_delegate.add_to_xeno() + resin_build_order = caste.resin_build_order + + job = caste.caste_type // Used for tracking the caste playtime + + else + CRASH("Attempted to create a new xenomorph [src] without caste datum.") if(mob_size < MOB_SIZE_BIG) mob_flags |= SQUEEZE_UNDER_VEHICLES + // More setup stuff for names, abilities etc + update_icon_source() generate_name() + add_inherent_verbs() + add_abilities() + create_reagents(100) + regenerate_icons() + toggle_xeno_mobhud() //This is a verb, but fuck it, it just werks + toggle_xeno_hostilehud() + recalculate_everything() - if(isqueen(src)) - SStracking.set_leader("hive_[hivenumber]", src) + //Begin SStracking SStracking.start_tracking("hive_[hivenumber]", src) . = ..() + + GLOB.living_xeno_list += src + GLOB.xeno_mob_list += src + //WO GAMEMODE if(SSticker?.mode?.hardcore) hardcore = 1 //Prevents healing and queen evolution time_of_birth = world.time - add_inherent_verbs() - add_abilities() - recalculate_actions() - + //Minimap if(z) INVOKE_NEXT_TICK(src, PROC_REF(add_minimap_marker)) + //Sight sight |= SEE_MOBS see_invisible = SEE_INVISIBLE_LIVING see_in_dark = 12 + if(client) set_lighting_alpha_from_prefs(client) else lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE - if(caste && caste.spit_types && caste.spit_types.len) - ammo = GLOB.ammo_list[caste.spit_types[1]] - - create_reagents(100) - - GLOB.living_xeno_list += src - GLOB.xeno_mob_list += src - - if(caste && caste.adjust_size_x != 1) - var/matrix/M = matrix() - M.Scale(caste.adjust_size_x, caste.adjust_size_y) - apply_transform(M) - - if(caste) - behavior_delegate = new caste.behavior_delegate_type() - behavior_delegate.bound_xeno = src - behavior_delegate.add_to_xeno() - resin_build_order = caste.resin_build_order - else - CRASH("Xenomorph [src] has no caste datum! Tell the devs!") - - regenerate_icons() - toggle_xeno_mobhud() //This is a verb, but fuck it, it just werks - toggle_xeno_hostilehud() - - if(oldXeno) - a_intent_change(oldXeno.a_intent)//Keep intent - - if(oldXeno.layer == XENO_HIDING_LAYER) - //We are hiding, let's keep hiding if we can! - for(var/datum/action/xeno_action/onclick/xenohide/hide in actions) - if(istype(hide)) - layer = XENO_HIDING_LAYER - hide.button.icon_state = "template_active" - - for(var/obj/item/W in oldXeno.contents) //Drop stuff - oldXeno.drop_inv_item_on_ground(W) - - oldXeno.empty_gut() - - if(IS_XENO_LEADER(oldXeno)) - hive.replace_hive_leader(oldXeno, src) - // Only handle free slots if the xeno is not in tdome - if(!is_admin_level(z)) + if(hive && !is_admin_level(z)) var/selected_caste = GLOB.xeno_datum_list[caste_type]?.type hive.used_slots[selected_caste]++ + //Statistics + var/area/current_area = get_area(src) + if(current_area && current_area.statistic_exempt) + statistic_exempt = TRUE if(round_statistics && !statistic_exempt) round_statistics.track_new_participant(faction, 1) - generate_name() // This can happen if a xeno gets made before the game starts if (hive && hive.hive_ui) hive.hive_ui.update_all_xeno_data() - job = caste.caste_type // Used for tracking the caste playtime Decorate() RegisterSignal(src, COMSIG_MOB_SCREECH_ACT, PROC_REF(handle_screech_act)) @@ -518,11 +519,11 @@ return SSminimaps.add_marker(src, z, hud_flags = flags, given_image = caste.get_minimap_icon()) -/mob/living/carbon/xenomorph/initialize_pass_flags(datum/pass_flags_container/PF) +/mob/living/carbon/xenomorph/initialize_pass_flags(datum/pass_flags_container/pass_flags) ..() - if (PF) - PF.flags_pass = PASS_MOB_IS_XENO - PF.flags_can_pass_all = PASS_MOB_THRU_XENO|PASS_AROUND|PASS_HIGH_OVER_ONLY + if (pass_flags) + pass_flags.flags_pass = PASS_MOB_IS_XENO + pass_flags.flags_can_pass_all = PASS_MOB_THRU_XENO|PASS_AROUND|PASS_HIGH_OVER_ONLY /mob/living/carbon/xenomorph/initialize_pain() pain = new /datum/pain/xeno(src) @@ -530,18 +531,18 @@ /mob/living/carbon/xenomorph/initialize_stamina() stamina = new /datum/stamina/none(src) -/mob/living/carbon/xenomorph/proc/fire_immune(mob/living/L) +/mob/living/carbon/xenomorph/proc/fire_immune(mob/living/living_mob) SIGNAL_HANDLER - if(L.fire_reagent?.fire_penetrating && !HAS_TRAIT(src, TRAIT_ABILITY_BURROWED)) + if(living_mob.fire_reagent?.fire_penetrating && !HAS_TRAIT(src, TRAIT_ABILITY_BURROWED)) return return COMPONENT_CANCEL_IGNITION -/mob/living/carbon/xenomorph/proc/flamer_crossed_immune(mob/living/L, datum/reagent/R) +/mob/living/carbon/xenomorph/proc/flamer_crossed_immune(mob/living/living_mob, datum/reagent/reagent) SIGNAL_HANDLER - if(R.fire_penetrating) + if(reagent.fire_penetrating) return . = COMPONENT_NO_BURN @@ -557,16 +558,7 @@ /mob/living/carbon/xenomorph/proc/generate_name() //We don't have a nicknumber yet, assign one to stick with us if(!nicknumber) - var/tempnumber = rand(1, 999) - var/list/numberlist = list() - for(var/mob/living/carbon/xenomorph/X in GLOB.xeno_mob_list) - numberlist += X.nicknumber - - while(tempnumber in numberlist) - tempnumber = rand(1, 999) - - nicknumber = tempnumber - + generate_and_set_nicknumber() // Even if we don't have the hive datum we usually still have the hive number var/datum/hive_status/in_hive = hive if(!in_hive) @@ -575,12 +567,10 @@ //Im putting this in here, because this proc gets called when a player inhabits a SSD xeno and it needs to go somewhere (sorry) hud_set_marks() - handle_name(in_hive) - -/mob/living/carbon/xenomorph/proc/handle_name(datum/hive_status/in_hive) var/name_prefix = in_hive.prefix var/name_client_prefix = "" var/name_client_postfix = "" + var/number_decorator = "" if(client) name_client_prefix = "[(client.xeno_prefix||client.xeno_postfix) ? client.xeno_prefix : "XX"]-" name_client_postfix = client.xeno_postfix ? ("-"+client.xeno_postfix) : "" @@ -591,9 +581,12 @@ var/age_display = show_age_prefix ? age_prefix : "" var/name_display = "" + // Rare easter egg + if(nicknumber == 666) + number_decorator = "Infernal " if(show_name_numbers) name_display = show_only_numbers ? " ([nicknumber])" : " ([name_client_prefix][nicknumber][name_client_postfix])" - name = "[name_prefix][age_display][caste.display_name || caste.caste_type][name_display]" + name = "[name_prefix][number_decorator][age_display][caste.display_name || caste.caste_type][name_display]" //Update linked data so they show up properly change_real_name(src, name) @@ -722,44 +715,37 @@ if(hardcore) attack_log?.Cut() // Completely clear out attack_log to limit mem usage if we fail to delete - . = ..() - - // Everything below fits the "we have to clear by principle it but i dont wanna break stuff" bill - mutators = null - - + return ..() /mob/living/carbon/xenomorph/slip(slip_source_name, stun_level, weaken_level, run_only, override_noslip, slide_steps) return FALSE - - -/mob/living/carbon/xenomorph/start_pulling(atom/movable/AM, lunge, no_msg) - if(SEND_SIGNAL(AM, COMSIG_MOVABLE_XENO_START_PULLING, src) & COMPONENT_ALLOW_PULL) - return do_pull(AM, lunge, no_msg) +/mob/living/carbon/xenomorph/start_pulling(atom/movable/movable_atom, lunge, no_msg) + if(SEND_SIGNAL(movable_atom, COMSIG_MOVABLE_XENO_START_PULLING, src) & COMPONENT_ALLOW_PULL) + return do_pull(movable_atom, lunge, no_msg) if(HAS_TRAIT(src,TRAIT_ABILITY_BURROWED)) return - if(!isliving(AM)) + if(!isliving(movable_atom)) return FALSE - var/mob/living/L = AM - if(issynth(L) && L.health < 0) // no pulling critted or dead synths + var/mob/living/living_mob = movable_atom + if(issynth(living_mob) && living_mob.health < 0) // no pulling critted or dead synths return FALSE - if(L.buckled) + if(living_mob.buckled) return FALSE //to stop xeno from pulling marines on roller beds. - if(!L.is_xeno_grabbable()) + if(!living_mob.is_xeno_grabbable()) return FALSE - var/atom/A = AM.handle_barriers(src) - if(A != AM) - A.attack_alien(src) + var/atom/atom = movable_atom.handle_barriers(src) + if(atom != movable_atom) + atom.attack_alien(src) xeno_attack_delay(src) return FALSE return ..() /mob/living/carbon/xenomorph/pull_response(mob/puller) if(stat != DEAD && has_species(puller,"Human")) // If the Xeno is alive, fight back against a grab/pull - var/mob/living/carbon/human/H = puller - if(H.ally_of_hivenumber(hivenumber)) + var/mob/living/carbon/human/human = puller + if(human.ally_of_hivenumber(hivenumber)) return TRUE puller.apply_effect(rand(caste.tacklestrength_min,caste.tacklestrength_max), WEAKEN) playsound(puller.loc, 'sound/weapons/pierce.ogg', 25, 1) @@ -775,8 +761,6 @@ pulledby.stop_pulling() . = 1 - - /mob/living/carbon/xenomorph/prepare_huds() ..() //updating all the mob's hud images @@ -786,12 +770,10 @@ hud_set_pheromone() hud_set_marks() - //and display them add_to_all_mob_huds() - var/datum/mob_hud/MH = huds[MOB_HUD_XENO_INFECTION] - MH.add_hud_to(src, src) - + var/datum/mob_hud/mob_hud = huds[MOB_HUD_XENO_INFECTION] + mob_hud.add_hud_to(src, src) /mob/living/carbon/xenomorph/check_improved_pointing() //xeno leaders get a big arrow and less cooldown @@ -813,23 +795,17 @@ /mob/living/carbon/xenomorph/proc/set_hive_and_update(new_hivenumber = XENO_HIVE_NORMAL) var/datum/hive_status/new_hive = GLOB.hive_datum[new_hivenumber] if(!new_hive) - return + return FALSE - for(var/T in _status_traits) // They can't keep getting away with this!!! - REMOVE_TRAIT(src, T, TRAIT_SOURCE_HIVE) + for(var/trait in _status_traits) // They can't keep getting away with this!!! + REMOVE_TRAIT(src, trait, TRAIT_SOURCE_HIVE) new_hive.add_xeno(src) - for(var/T in new_hive.hive_inherant_traits) - ADD_TRAIT(src, T, TRAIT_SOURCE_HIVE) + for(var/trait in new_hive.hive_inherant_traits) + ADD_TRAIT(src, trait, TRAIT_SOURCE_HIVE) - if(istype(src, /mob/living/carbon/xenomorph/larva)) - var/mob/living/carbon/xenomorph/larva/L = src - L.update_icons() // larva renaming done differently - else - generate_name() - if(istype(src, /mob/living/carbon/xenomorph/queen)) - update_living_queens() + generate_name() lock_evolve = FALSE banished = FALSE @@ -840,6 +816,8 @@ // Update the hive status UI new_hive.hive_ui.update_all_xeno_data() + return TRUE + //*********************************************************// //********************Mutator functions********************// //*********************************************************// @@ -854,7 +832,6 @@ if(hive && hive.living_xeno_queen && hive.living_xeno_queen == src) hive.recalculate_hive() //Recalculating stuff around Queen maturing - /mob/living/carbon/xenomorph/proc/recalculate_stats() recalculate_health() recalculate_plasma() @@ -932,18 +909,8 @@ recalculate_acid() recalculate_weeds() pull_multiplier = mutators.pull_multiplier - if(isrunner(src)) - //Xeno runners need a small nerf to dragging speed mutator - pull_multiplier = 1 - (1 - mutators.pull_multiplier) * 0.85 - if(is_zoomed) - zoom_out() - if(iscarrier(src)) - var/mob/living/carbon/xenomorph/carrier/carrier = src - carrier.huggers_max = caste.huggers_max - carrier.eggs_max = caste.eggs_max need_weeds = mutators.need_weeds - /mob/living/carbon/xenomorph/proc/recalculate_acid() if(caste) acid_level = caste.acid_level @@ -1029,14 +996,14 @@ var/displaytime = max(1, round(breakouttime / 600)) //Minutes to_chat(src, SPAN_WARNING("You attempt to remove [legcuffed]. (This will take around [displaytime] minute(s) and you need to stand still)")) - for(var/mob/O in viewers(src)) - O.show_message(SPAN_DANGER("[usr] attempts to remove [legcuffed]!"), SHOW_MESSAGE_VISIBLE) + for(var/mob/viewer in viewers(src)) + viewer.show_message(SPAN_DANGER("[usr] attempts to remove [legcuffed]!"), SHOW_MESSAGE_VISIBLE) if(!do_after(src, breakouttime, INTERRUPT_NO_NEEDHAND^INTERRUPT_RESIST, BUSY_ICON_HOSTILE)) return if(!legcuffed || buckled) return // time leniency for lag which also might make this whole thing pointless but the server - for(var/mob/O in viewers(src))// lags so hard that 40s isn't lenient enough - Quarxink - O.show_message(SPAN_DANGER("[src] manages to remove [legcuffed]!"), SHOW_MESSAGE_VISIBLE) + for(var/mob/viewer in viewers(src))// lags so hard that 40s isn't lenient enough - Quarxink + viewer.show_message(SPAN_DANGER("[src] manages to remove [legcuffed]!"), SHOW_MESSAGE_VISIBLE) to_chat(src, SPAN_NOTICE(" You successfully remove [legcuffed].")) drop_inv_item_on_ground(legcuffed) @@ -1078,9 +1045,9 @@ /mob/living/carbon/xenomorph/handle_blood_splatter(splatter_dir, duration) var/color_override if(special_blood) - var/datum/reagent/D = chemical_reagents_list[special_blood] - if(D) - color_override = D.color + var/datum/reagent/reagent_datum = chemical_reagents_list[special_blood] + if(reagent_datum) + color_override = reagent_datum.color new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(loc, splatter_dir, duration, color_override) /mob/living/carbon/xenomorph/Collide(atom/movable/movable_atom) @@ -1112,3 +1079,16 @@ . = ..() if(!resting) // !resting because we dont wanna prematurely update wounds if they're just trying to rest update_wounds() + +///Generate a new unused nicknumber for the current hive, if hive doesn't exist return 0 +/mob/living/carbon/xenomorph/proc/generate_and_set_nicknumber() + if(!hive) + //If hive doesn't exist make it 0 + nicknumber = 0 + return + var/datum/hive_status/hive_status = hive + if(length(hive_status.available_nicknumbers)) + nicknumber = pick_n_take(hive_status.available_nicknumbers) + else + //If we somehow use all 999 numbers fallback on 0 + nicknumber = 0 diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm index c13555cba12c..2439e78faf69 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Carrier.dm @@ -92,6 +92,11 @@ var/eggs_max = 0 var/laid_egg = 0 +/mob/living/carbon/xenomorph/carrier/recalculate_actions() + ..() + huggers_max = caste.huggers_max + eggs_max = caste.eggs_max + /mob/living/carbon/xenomorph/carrier/update_icons() . = ..() if (mutation_type == CARRIER_NORMAL) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm b/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm index 82d80752ec54..a5dc98524c85 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Larva.dm @@ -50,15 +50,22 @@ var/burrowable = TRUE //Can it be safely burrowed if it has no player? var/state_override + var/is_bloody = TRUE //We're still "bloody" icon_xeno = 'icons/mob/xenos/larva.dmi' icon_xenonid = 'icons/mob/xenonids/larva.dmi' -/mob/living/carbon/xenomorph/larva/initialize_pass_flags(datum/pass_flags_container/PF) +/mob/living/carbon/xenomorph/larva/Life() + if(is_bloody && (evolution_stored >= evolution_threshold / 2)) //We're no longer bloody so update our name... + generate_name() + is_bloody = FALSE + return ..() + +/mob/living/carbon/xenomorph/larva/initialize_pass_flags(datum/pass_flags_container/pass_flags) ..() - if (PF) - PF.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER - PF.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM + if (pass_flags) + pass_flags.flags_pass = PASS_MOB_THRU|PASS_FLAGS_CRAWLER + pass_flags.flags_can_pass_all = PASS_ALL^PASS_OVER_THROW_ITEM /mob/living/carbon/xenomorph/larva/corrupted hivenumber = XENO_HIVE_CORRUPTED @@ -101,29 +108,10 @@ //Larva code is just a mess, so let's get it over with /mob/living/carbon/xenomorph/larva/update_icons() - var/progress = "" //Naming convention, three different names var/state = "" //Icon convention, two different sprite sets - var/name_prefix = "" - - if(hive) - name_prefix = hive.prefix - color = hive.color - - if(evolution_stored >= evolution_threshold) - progress = "Mature " - else if(evolution_stored < evolution_threshold / 2) //We're still bloody - progress = "Bloody " + if(evolution_stored < evolution_threshold / 2) //We're still bloody state = "Bloody " - else - progress = "" - - name = "[name_prefix][progress]Larva ([nicknumber])" - - if(istype(src,/mob/living/carbon/xenomorph/larva/predalien)) state = "Predalien " //Sort of a hack. - - //Update linked data so they show up properly - change_real_name(src, name) if(stat == DEAD) icon_state = "[state_override || state]Larva Dead" @@ -141,16 +129,13 @@ /mob/living/carbon/xenomorph/larva/alter_ghost(mob/dead/observer/ghost) ghost.icon_state = "[caste.caste_type]" -/mob/living/carbon/xenomorph/larva/handle_name() - return - /mob/living/carbon/xenomorph/larva/start_pulling(atom/movable/AM) return /mob/living/carbon/xenomorph/larva/pull_response(mob/puller) return TRUE -/mob/living/carbon/xenomorph/larva/UnarmedAttack(atom/A, proximity, click_parameters, tile_attack, ignores_resin = FALSE) +/mob/living/carbon/xenomorph/larva/UnarmedAttack(atom/atom, proximity, click_parameters, tile_attack, ignores_resin = FALSE) a_intent = INTENT_HELP //Forces help intent for all interactions. if(!caste) return FALSE @@ -158,21 +143,50 @@ if(lying) //No attacks while laying down return FALSE - A.attack_larva(src) + atom.attack_larva(src) xeno_attack_delay(src) //Adds some lag to the 'attack' -/proc/spawn_hivenumber_larva(atom/A, hivenumber) - if(!GLOB.hive_datum[hivenumber] || isnull(A)) +/proc/spawn_hivenumber_larva(atom/atom, hivenumber) + if(!GLOB.hive_datum[hivenumber] || isnull(atom)) return - var/mob/living/carbon/xenomorph/larva/L = new /mob/living/carbon/xenomorph/larva(A) + var/mob/living/carbon/xenomorph/larva/larva = new /mob/living/carbon/xenomorph/larva(atom) - L.set_hive_and_update(hivenumber) + larva.set_hive_and_update(hivenumber) - return L + return larva /mob/living/carbon/xenomorph/larva/emote(act, m_type, message, intentional, force_silence) playsound(loc, "alien_roar_larva", 15) /mob/living/carbon/xenomorph/larva/is_xeno_grabbable() return TRUE + +/* +Larva name generation, set nicknumber = (number between 1 & 999) which isn't taken by any other xenos in GLOB.xeno_mob_list if doesn't already exist. +Also handles the "Mature / Bloody naming convention. Call this to update the name." +*/ +/mob/living/carbon/xenomorph/larva/generate_name() + if(!nicknumber) + generate_and_set_nicknumber() + + var/progress = "" //Naming convention, three different names + var/name_prefix = "" // Prefix for hive + + if(hive) + name_prefix = hive.prefix + color = hive.color + + if(evolution_stored >= evolution_threshold) + progress = "Mature " + else if(evolution_stored < evolution_threshold / 2) //We're still bloody + progress = "Bloody " + + name = "[name_prefix][progress]Larva ([nicknumber])" + + //Update linked data so they show up properly + change_real_name(src, name) + //Update the hive status UI + if(hive) + var/datum/hive_status/hive_status = hive + hive_status.hive_ui.update_xeno_info() diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm index b83b33e2eee5..159909669984 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm @@ -389,6 +389,7 @@ /mob/living/carbon/xenomorph/queen/Initialize() . = ..() + SStracking.set_leader("hive_[hivenumber]", src) if(!is_admin_level(z))//so admins can safely spawn Queens in Thunderdome for tests. xeno_message(SPAN_XENOANNOUNCE("A new Queen has risen to lead the Hive! Rejoice!"),3,hivenumber) notify_ghosts(header = "New Queen", message = "A new Queen has risen.", source = src, action = NOTIFY_ORBIT) @@ -409,8 +410,10 @@ AddComponent(/datum/component/footstep, 2 , 35, 11, 4, "alien_footstep_large") -/mob/living/carbon/xenomorph/queen/handle_name(datum/hive_status/in_hive) - var/name_prefix = in_hive.prefix +/mob/living/carbon/xenomorph/queen/generate_name() + if(!nicknumber) + generate_and_set_nicknumber() + var/name_prefix = hive.prefix if(queen_aged) age_xeno() switch(age) @@ -439,11 +442,16 @@ name_client_prefix = "[(client.xeno_prefix||client.xeno_postfix) ? client.xeno_prefix : "XX"]-" name_client_postfix = client.xeno_postfix ? ("-"+client.xeno_postfix) : "" full_designation = "[name_client_prefix][nicknumber][name_client_postfix]" - color = in_hive.color + color = hive.color //Update linked data so they show up properly change_real_name(src, name) +/mob/living/carbon/xenomorph/queen/set_hive_and_update(new_hivenumber) + if(!..()) + return FALSE + update_living_queens() + /mob/living/carbon/xenomorph/queen/proc/make_combat_effective() queen_aged = TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm index 77e4291ee84b..9fcee5134a48 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/Runner.dm @@ -73,6 +73,12 @@ if (pass_flags_container) pass_flags_container.flags_pass |= PASS_FLAGS_CRAWLER +/mob/living/carbon/xenomorph/runner/recalculate_actions() + ..() + //Xeno runners need a small nerf to dragging speed mutator + pull_multiplier = 1 - (1 - mutators.pull_multiplier) * 0.85 + if(is_zoomed) + zoom_out() /datum/behavior_delegate/runner_base name = "Base Runner Behavior Delegate" diff --git a/code/modules/mob/living/carbon/xenomorph/castes/caste_datum.dm b/code/modules/mob/living/carbon/xenomorph/castes/caste_datum.dm new file mode 100644 index 000000000000..f32202540ce2 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/castes/caste_datum.dm @@ -0,0 +1,183 @@ +// Actual caste datum basedef +/datum/caste_datum + var/caste_type = "" + var/display_name = "" + var/tier = 0 + var/dead_icon = "Drone Dead" + var/language = LANGUAGE_XENOMORPH + var/melee_damage_lower = 10 + var/melee_damage_upper = 20 + ///allows fine tuning melee damage to vehicles per caste. + var/melee_vehicle_damage = 10 + var/evasion = XENO_EVASION_NONE + + var/speed = XENO_SPEED_TIER_10 + + var/plasma_max = 10 + var/plasma_gain = 5 + + var/crystal_max = 0 + + var/max_health = XENO_UNIVERSAL_HPMULT * 100 + ///Are they allowed to evolve (and have their evolution progress group) + var/evolution_allowed = 1 + ///Threshold to next evolution + var/evolution_threshold = 0 + /// whether they can get evo points without needing an ovi queen + var/evolve_without_queen = FALSE + ///This is where you add castes to evolve into. "Separated", "by", "commas" + var/list/evolves_to = list() + /// what caste or castes to de-evolve to. + var/list/deevolves_to = list() + ///If they can use consoles, etc. Set on Queen + var/is_intelligent = 0 + var/caste_desc = null + + // Tackles + var/tackle_min = 2 + var/tackle_max = 6 + var/tackle_chance = 35 + var/tacklestrength_min = 2 + var/tacklestrength_max = 3 + + ///Chance of deflecting projectiles. + var/armor_deflection = 0 + var/fire_immunity = FIRE_IMMUNITY_NONE + var/fire_intensity_resistance = 0 + + ///Delay timer for spitting + var/spit_delay = 60 + + /// Windup for spits + var/spit_windup = FALSE + + ///The strength of our aura. Zero means we can't emit one + var/aura_strength = 0 + + ///"Evolving" removed for the time being + var/aura_allowed = list("frenzy", "warding", "recovery") + + ///Adjust pixel size. 0.x is smaller, 1.x is bigger, percentage based. + var/adjust_size_x = 1 + var/adjust_size_y = 1 + + ///list of datum projectile types the xeno can use. + var/list/spit_types + + var/attack_delay = 0 //Bonus or pen to time in between attacks. + makes slashes slower. + + var/agility_speed_increase = 0 // this opens up possibilities for balancing + + // The type of mutator delegate to instantiate on the base caste. Will + // be replaced when the Xeno chooses a strain. + var/behavior_delegate_type = /datum/behavior_delegate + + // Resin building-related vars + /// Default build time and build distance + var/build_time_mult = BUILD_TIME_MULT_XENO + var/max_build_dist = 0 + + // Carrier vars // + + /// if a hugger is held in hand, won't attempt to leap and kill itself + var/hugger_nurturing = FALSE + var/huggers_max = 0 + var/throwspeed = 0 + var/hugger_delay = 0 + var/eggs_max = 0 + var/egg_cooldown = 30 + ///Armor but for explosions + var/xeno_explosion_resistance = 0 + + //Queen vars + var/can_hold_facehuggers = 0 + var/can_hold_eggs = CANNOT_HOLD_EGGS + + var/can_be_queen_healed = TRUE + var/can_be_revived = TRUE + + var/can_vent_crawl = 1 + + var/caste_luminosity = 0 + + /// if fire_immunity is set to be vulnerable, how much will fire damage be multiplied. Defines in xeno.dm + var/fire_vulnerability_mult = 0 + + var/burrow_cooldown = 5 SECONDS + var/tunnel_cooldown = 100 + var/widen_cooldown = 10 SECONDS + ///Big strong ability, big cooldown. + var/tremor_cooldown = 30 SECONDS + ///whether the xeno heals even outside weeds. + var/innate_healing = FALSE + + var/acid_level = 0 + var/weed_level = WEED_LEVEL_STANDARD + ///Time it takes between acid splash retaliate procs. Variable per caste, for if we want future castes that are acid bombs + var/acid_splash_cooldown = 3 SECONDS + + // regen vars + + var/heal_delay_time = 0 SECONDS + var/heal_resting = 1 + var/heal_standing = 0.4 + var/heal_knocked_out = 0.33 + + var/list/resin_build_order + var/minimum_xeno_playtime = 0 + +// cannot evolve to this caste until the round has been going on for this amount of time + // IMPORTANT: this is ROUND_TIME, not world.time + var/minimum_evolve_time = 1 MINUTES + /// Iconstate for the xeno on the minimap + var/minimap_icon = "xeno" + ///The iconstate for leadered xenos on the minimap, added as overlay + var/minimap_leadered_overlay = "xenoleader" + + var/royal_caste = FALSE + + +/datum/caste_datum/can_vv_modify() + return FALSE + +/datum/caste_datum/New() + . = ..() + + //Initialise evolution and upgrade thresholds in one place, once and for all + evolution_threshold = 0 + if(evolution_allowed) + switch(tier) + if(0) + evolution_threshold = 60 + if(1) + evolution_threshold = 200 + if(2) + evolution_threshold = 500 + //Other tiers (T3, Queen, etc.) can't evolve anyway + + resin_build_order = GLOB.resin_build_order_drone + +/datum/caste_datum/proc/get_caste_requirement(client/client) + return minimum_xeno_playtime - client.get_total_xeno_playtime() + +/datum/caste_datum/proc/get_minimap_icon() + var/image/background = mutable_appearance('icons/ui_icons/map_blips.dmi', "background") + background.color = MINIMAP_ICON_BACKGROUND_XENO + + var/iconstate = minimap_icon ? minimap_icon : "unknown" + var/mutable_appearance/icon = image('icons/ui_icons/map_blips.dmi', icon_state = iconstate) + icon.appearance_flags = RESET_COLOR + background.overlays += icon + + return background + +/datum/caste_datum/proc/can_play_caste(client/client) + if(!CONFIG_GET(flag/use_timelocks)) + return TRUE + + var/total_xeno_playtime = client.get_total_xeno_playtime() + + if(minimum_xeno_playtime && total_xeno_playtime < minimum_xeno_playtime) + return FALSE + + return TRUE diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status.dm b/code/modules/mob/living/carbon/xenomorph/hive_status.dm index 4fe1be51bfff..48a54bf96f89 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_status.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_status.dm @@ -1,216 +1,1280 @@ -/datum/hive_status_ui - var/name = "Hive Status" +/datum/hive_status + var/name = "Normal Hive" - // Data to pass when rendering the UI (not static) - var/total_xenos - var/list/xeno_counts - var/list/tier_slots - var/list/xeno_vitals - var/list/xeno_keys - var/list/xeno_info - var/hive_location - var/burrowed_larva - var/evilution_level + // Used for the faction of the xenomorph. Not recommended to modify. + var/internal_faction - var/data_initialized = FALSE + /// Short Hive ID as string used in stats reporting + var/reporting_id = "normal" - var/datum/hive_status/assoc_hive = null + var/hivenumber = XENO_HIVE_NORMAL + var/mob/living/carbon/xenomorph/queen/living_xeno_queen + var/egg_planting_range = 15 + var/slashing_allowed = XENO_SLASH_ALLOWED //This initial var allows the queen to turn on or off slashing. Slashing off means harm intent does much less damage. + var/construction_allowed = NORMAL_XENO //Who can place construction nodes for special structures + var/destruction_allowed = XENO_LEADER //Who can destroy special structures + var/unnesting_allowed = TRUE + var/hive_orders = "" //What orders should the hive have + var/color = null + var/ui_color = null // Color for hive status collapsible buttons and xeno count list + var/prefix = "" + var/queen_leader_limit = 2 + var/list/open_xeno_leader_positions = list(1, 2) // Ordered list of xeno leader positions (indexes in xeno_leader_list) that are not occupied + var/list/xeno_leader_list[2] // Ordered list (i.e. index n holds the nth xeno leader) + var/stored_larva = 0 -/datum/hive_status_ui/New(datum/hive_status/hive) - assoc_hive = hive - update_all_data() - START_PROCESSING(SShive_status, src) + ///used by /datum/hive_status/proc/increase_larva_after_burst() to support non-integer increases to larva + var/partial_larva = 0 + /// Assoc list of free slots available to specific castes + var/list/free_slots = list( + /datum/caste_datum/burrower = 1, + /datum/caste_datum/hivelord = 1, + /datum/caste_datum/carrier = 1 + ) + /// Assoc list of slots currently used by specific castes (for calculating free_slot usage) + var/list/used_slots = list() + /// list of living tier2 xenos + var/list/tier_2_xenos = list() + /// list of living tier3 xenos + var/list/tier_3_xenos = list() + /// list of living xenos + var/list/totalXenos = list() + /// list of previously living xenos (hardrefs currently) + var/list/total_dead_xenos = list() + var/xeno_queen_timer + var/isSlotOpen = TRUE //Set true for starting alerts only after the hive has reached its full potential + var/allowed_nest_distance = 15 //How far away do we allow nests from an ovied Queen. Default 15 tiles. + var/obj/effect/alien/resin/special/pylon/core/hive_location = null //Set to ref every time a core is built, for defining the hive location + var/crystal_stored = 0 //How much stockpiled material is stored for the hive to use. -/datum/hive_status_ui/process() - update_xeno_vitals() - update_xeno_info(FALSE) - SStgui.update_uis(src) + var/datum/mutator_set/hive_mutators/mutators = new + var/tier_slot_multiplier = 1 + var/larva_gestation_multiplier = 1 + var/bonus_larva_spawn_chance = 1 + var/hijack_burrowed_surge = FALSE //at hijack, start spawning lots of burrowed + /// how many burrowed is going to spawn during larva surge + var/hijack_burrowed_left = 0 -// Updates the list tracking how many xenos there are in each tier, and how many there are in total -/datum/hive_status_ui/proc/update_xeno_counts(send_update = TRUE) - xeno_counts = assoc_hive.get_xeno_counts() + var/ignore_slots = FALSE + var/dynamic_evolution = TRUE + var/evolution_rate = 3 // Only has use if dynamic_evolution is false + var/evolution_bonus = 0 - total_xenos = 0 - for(var/counts in xeno_counts) - for(var/caste in counts) - total_xenos += counts[caste] + var/allow_no_queen_actions = FALSE + var/allow_no_queen_evo = FALSE + var/evolution_without_ovipositor = TRUE //Temporary for the roundstart. + /// Set to false if you want to prevent evolutions into Queens + var/allow_queen_evolve = TRUE + /// Set to true if you want to prevent bursts and spawns of new xenos. Will also prevent healing if the queen no longer exists + var/hardcore = FALSE + /// Set to false if you want to prevent getting burrowed larva from latejoin marines + var/latejoin_burrowed = TRUE - if(send_update) - SStgui.update_uis(src) + var/list/hive_inherant_traits - xeno_counts[1] -= "Queen" // don't show queen in the amount of xenos + // Cultist Info + var/mob/living/carbon/leading_cult_sl - // Also update the amount of T2/T3 slots - tier_slots = assoc_hive.get_tier_slots() + //List of how many maximum of each special structure you can have + var/list/hive_structures_limit = list( + XENO_STRUCTURE_CORE = 1, + XENO_STRUCTURE_CLUSTER = 8, + XENO_STRUCTURE_POOL = 1, + XENO_STRUCTURE_EGGMORPH = 6, + XENO_STRUCTURE_EVOPOD = 2, + XENO_STRUCTURE_RECOVERY = 6, + XENO_STRUCTURE_PYLON = 2, + ) -// Updates the hive location using the area name of the defined hive location turf -/datum/hive_status_ui/proc/update_hive_location(send_update = TRUE) - if(!assoc_hive.hive_location) + var/global/list/hive_structure_types = list( + XENO_STRUCTURE_CORE = /datum/construction_template/xenomorph/core, + XENO_STRUCTURE_CLUSTER = /datum/construction_template/xenomorph/cluster, + XENO_STRUCTURE_EGGMORPH = /datum/construction_template/xenomorph/eggmorph, + XENO_STRUCTURE_RECOVERY = /datum/construction_template/xenomorph/recovery + ) + + var/list/list/hive_structures = list() //Stringref list of structures that have been built + var/list/list/hive_constructions = list() //Stringref list of structures that are being built + + var/datum/hive_status_ui/hive_ui + var/datum/mark_menu_ui/mark_ui + var/datum/hive_faction_ui/faction_ui + + var/list/tunnels = list() + + var/list/allies = list() + + var/list/resin_marks = list() + + var/list/banished_ckeys = list() + + var/hivecore_cooldown = FALSE + + var/need_round_end_check = FALSE + + //Joining as Facehugger vars + /// When can huggers join the round + var/hugger_timelock = 15 MINUTES + /// How many huggers can the hive support + var/playable_hugger_limit = 0 + /// Minimum number of huggers available at any hive size + var/playable_hugger_minimum = 2 + /// This number divides the total xenos counted for slots to give the max number of facehuggers + var/playable_hugger_max_divisor = 4 + + /// How many lesser drones the hive can support + var/lesser_drone_limit = 0 + /// Slots available for lesser drones will never go below this number + var/lesser_drone_minimum = 2 + /// This number divides the total xenos counted for slots to give the max number of lesser drones + var/playable_lesser_drones_max_divisor = 3 + + var/datum/tacmap/drawing/xeno/tacmap + var/minimap_type = MINIMAP_FLAG_XENO + + var/list/available_nicknumbers = list() + +/datum/hive_status/New() + mutators.hive = src + hive_ui = new(src) + mark_ui = new(src) + faction_ui = new(src) + minimap_type = get_minimap_flag_for_faction(hivenumber) + tacmap = new(src, minimap_type) + if(!internal_faction) + internal_faction = name + if(hivenumber != XENO_HIVE_NORMAL) return - hive_location = strip_improper(get_area_name(assoc_hive.hive_location)) + for(var/number in 1 to 999) + available_nicknumbers += number + + RegisterSignal(SSdcs, COMSIG_GLOB_POST_SETUP, PROC_REF(post_setup)) - if(send_update) - SStgui.update_uis(src) +/datum/hive_status/proc/post_setup() + SIGNAL_HANDLER -// Updates the sorted list of all xenos that we use as a key for all other information -/datum/hive_status_ui/proc/update_xeno_keys(send_update = TRUE) - xeno_keys = assoc_hive.get_xeno_keys() + setup_evolution_announcements() + setup_pylon_limits() - if(send_update) - SStgui.update_uis(src) +/datum/hive_status/proc/setup_evolution_announcements() + for(var/time in GLOB.xeno_evolve_times) + if(time == "0") + continue -// Mildly related to the above, but only for when xenos are removed from the hive -// If a xeno dies, we don't have to regenerate all xeno info and sort it again, just remove them from the data list -/datum/hive_status_ui/proc/xeno_removed(mob/living/carbon/xenomorph/X) - if(!xeno_keys) + addtimer(CALLBACK(src, PROC_REF(announce_evolve_available), GLOB.xeno_evolve_times[time]), text2num(time)) + +/// Sets up limits on pylons in New() for potential futureproofing with more static comms +/datum/hive_status/proc/setup_pylon_limits() + hive_structures_limit[XENO_STRUCTURE_PYLON] = length(GLOB.all_static_telecomms_towers) || 2 + +/datum/hive_status/proc/announce_evolve_available(list/datum/caste_datum/available_castes) + + var/list/castes_available = list() + for(var/datum/caste_datum/current_caste as anything in available_castes) + castes_available += initial(current_caste.caste_type) + + var/castes = castes_available.Join(", ") + xeno_message(SPAN_XENOANNOUNCE("The Hive is now strong enough to support: [castes]")) + xeno_maptext("The Hive can now support: [castes]", "Hive Strengthening") + + +// Adds a xeno to this hive +/datum/hive_status/proc/add_xeno(mob/living/carbon/xenomorph/xeno) + if(!xeno || !istype(xeno)) return - for(var/index in 1 to length(xeno_keys)) - var/list/info = xeno_keys[index] - if(info["nicknumber"] == X.nicknumber) + // If the xeno is part of another hive, they should be removed from that one first + if(xeno.hive && xeno.hive != src) + xeno.hive.remove_xeno(xeno, TRUE) - // tried Remove(), didn't work. *shrug* - xeno_keys[index] = null - xeno_keys -= null - return + // Already in the hive + if(xeno in totalXenos) + return + + // Can only have one queen. + if(isqueen(xeno)) + if(!living_xeno_queen && !is_admin_level(xeno.z)) // Don't consider xenos in admin level + set_living_xeno_queen(xeno) + + xeno.hivenumber = hivenumber + xeno.hive = src + + xeno.set_faction(internal_faction) + + if(xeno.hud_list) + xeno.hud_update() + + var/area/area = get_area(xeno) + if(!is_admin_level(xeno.z) || (area.flags_atom & AREA_ALLOW_XENO_JOIN)) + totalXenos += xeno + if(xeno.tier == 2) + tier_2_xenos += xeno + else if(xeno.tier == 3) + tier_3_xenos += xeno - SStgui.update_uis(src) + // Xenos are a fuckfest of cross-dependencies of different datums that are initialized at different times + // So don't even bother trying updating UI here without large refactors -// Updates the list of xeno names, strains and references -/datum/hive_status_ui/proc/update_xeno_info(send_update = TRUE) - xeno_info = assoc_hive.get_xeno_info() +// Removes the xeno from the hive +/datum/hive_status/proc/remove_xeno(mob/living/carbon/xenomorph/xeno, hard = FALSE, light_mode = FALSE) + if(!xeno || !istype(xeno)) + return + + // Make sure the xeno was in the hive in the first place + if(!(xeno in totalXenos)) + return - if(send_update) - SStgui.update_uis(src) + // This might be a redundant check now that Queen/Destroy() checks, but doesn't hurt to double check + if(living_xeno_queen == xeno) + var/mob/living/carbon/xenomorph/queen/next_queen = null + for(var/mob/living/carbon/xenomorph/queen/queen in totalXenos) + if(!is_admin_level(queen.z) && queen != src && !QDELETED(queen)) + next_queen = queen + break -// Updates vital information about xenos such as health and location. Only info that should be updated regularly -/datum/hive_status_ui/proc/update_xeno_vitals() - xeno_vitals = assoc_hive.get_xeno_vitals() + set_living_xeno_queen(next_queen) // either null or a queen -// Updates how many buried larva there are -/datum/hive_status_ui/proc/update_burrowed_larva(send_update = TRUE) - burrowed_larva = assoc_hive.stored_larva - if(SSxevolution) - evilution_level = SSxevolution.get_evolution_boost_power(assoc_hive.hivenumber) + // We allow "soft" removals from the hive (the xeno still retains information about the hive) + // This is so that xenos can add themselves back to the hive if they should die or otherwise go "on leave" from the hive + if(hard) + xeno.hivenumber = 0 + xeno.hive = null +#ifndef UNIT_TESTS // Since this is a hard ref, we shouldn't confuse create_and_destroy else - evilution_level = 1 - if(send_update) - SStgui.update_uis(src) - -// Updates all data except burrowed larva -/datum/hive_status_ui/proc/update_all_xeno_data(send_update = TRUE) - update_xeno_counts(FALSE) - update_xeno_vitals() - update_xeno_keys(FALSE) - update_xeno_info(FALSE) - - if(send_update) - SStgui.update_uis(src) - -// Updates all data, including burrowed larva -/datum/hive_status_ui/proc/update_all_data() - data_initialized = TRUE - update_all_xeno_data(FALSE) - update_burrowed_larva(FALSE) - SStgui.update_uis(src) - -/datum/hive_status_ui/ui_state(mob/user) - return GLOB.hive_state[assoc_hive.internal_faction] - -/datum/hive_status_ui/ui_status(mob/user, datum/ui_state/state) - . = ..() - if(isobserver(user)) - return UI_INTERACTIVE + total_dead_xenos += xeno +#endif + + totalXenos -= xeno + if(xeno.tier == 2) + tier_2_xenos -= xeno + else if(xeno.tier == 3) + tier_3_xenos -= xeno + + // Only handle free slots if the xeno is not in tdome + if(!is_admin_level(xeno.z)) + var/selected_caste = GLOB.xeno_datum_list[xeno.caste_type]?.type + if(used_slots[selected_caste]) + used_slots[selected_caste]-- + + if(!light_mode) + hive_ui.update_xeno_counts() + hive_ui.xeno_removed(xeno) + +/datum/hive_status/proc/set_living_xeno_queen(mob/living/carbon/xenomorph/queen/queen) + if(!queen) + mutators.reset_mutators() + SStracking.delete_leader("hive_[hivenumber]") + SStracking.stop_tracking("hive_[hivenumber]", living_xeno_queen) + SShive_status.wait = 10 SECONDS + else + SStracking.set_leader("hive_[hivenumber]", queen) + SShive_status.wait = 2 SECONDS + + SEND_SIGNAL(src, COMSIG_HIVE_NEW_QUEEN, queen) + living_xeno_queen = queen + + recalculate_hive() + +/datum/hive_status/proc/recalculate_hive() + if (!living_xeno_queen) + queen_leader_limit = 0 //No leaders for a Hive without a Queen! + else + queen_leader_limit = 4 + mutators.leader_count_boost + + if (xeno_leader_list.len > queen_leader_limit) + var/diff = 0 + for (var/i in queen_leader_limit + 1 to xeno_leader_list.len) + if(!open_xeno_leader_positions.Remove(i)) + remove_hive_leader(xeno_leader_list[i]) + diff++ + xeno_leader_list.len -= diff // Changing the size of xeno_leader_list needs to go at the end or else it won't iterate through the list properly + else if (xeno_leader_list.len < queen_leader_limit) + for (var/i in xeno_leader_list.len + 1 to queen_leader_limit) + open_xeno_leader_positions += i + xeno_leader_list.len++ + + + tier_slot_multiplier = mutators.tier_slot_multiplier + larva_gestation_multiplier = mutators.larva_gestation_multiplier + bonus_larva_spawn_chance = mutators.bonus_larva_spawn_chance + + hive_ui.update_all_data() + +/datum/hive_status/proc/add_hive_leader(mob/living/carbon/xenomorph/xeno) + if(!xeno) + return FALSE //How did this even happen? + if(!open_xeno_leader_positions.len) + return FALSE //Too many leaders already (no available xeno leader positions) + if(xeno.hive_pos != NORMAL_XENO) + return FALSE //Already on the list + var/leader_num = open_xeno_leader_positions[1] + xeno_leader_list[leader_num] = xeno + xeno.hive_pos = XENO_LEADER_HIVE_POS(leader_num) + xeno.handle_xeno_leader_pheromones() + xeno.hud_update() // To add leader star + open_xeno_leader_positions -= leader_num + + xeno.update_minimap_icon() + + give_action(xeno, /datum/action/xeno_action/activable/info_marker) + + hive_ui.update_xeno_keys() + return TRUE + +/datum/hive_status/proc/remove_hive_leader(mob/living/carbon/xenomorph/xeno, light_mode = FALSE) + if(!istype(xeno) || !IS_XENO_LEADER(xeno)) + return FALSE + + var/leader_num = GET_XENO_LEADER_NUM(xeno) + + xeno_leader_list[leader_num] = null + + if(!light_mode) // Don't run side effects during deletions. Better yet, replace all this by signals someday + xeno.hive_pos = NORMAL_XENO + xeno.handle_xeno_leader_pheromones() + xeno.hud_update() // To remove leader star + + // Need to maintain ascending order of open_xeno_leader_positions + for (var/i in 1 to queen_leader_limit) + if (i > open_xeno_leader_positions.len || open_xeno_leader_positions[i] > leader_num) + open_xeno_leader_positions.Insert(i, leader_num) + break + + if(!light_mode) + hive_ui.update_xeno_keys() -/datum/hive_status_ui/ui_data(mob/user) - . = list() - .["total_xenos"] = total_xenos - .["xeno_counts"] = xeno_counts - .["tier_slots"] = tier_slots - .["xeno_keys"] = xeno_keys - .["xeno_info"] = xeno_info - .["xeno_vitals"] = xeno_vitals - .["queen_location"] = get_area_name(assoc_hive.living_xeno_queen) - .["hive_location"] = hive_location - .["burrowed_larva"] = burrowed_larva - .["evilution_level"] = evilution_level + for(var/obj/effect/alien/resin/marker/leaderless_mark in resin_marks) //no resin_mark limit abuse + if(leaderless_mark.createdby == xeno.nicknumber) + qdel(leaderless_mark) - var/mob/living/carbon/xenomorph/queen/Q = user - .["is_in_ovi"] = istype(Q) && Q.ovipositor + xeno.update_minimap_icon() -/datum/hive_status_ui/ui_static_data(mob/user) - . = list() - .["user_ref"] = REF(user) - .["hive_color"] = assoc_hive.ui_color - .["hive_name"] = assoc_hive.name + remove_action(xeno, /datum/action/xeno_action/activable/info_marker) -/datum/hive_status_ui/proc/open_hive_status(mob/user) - if(!user) + return TRUE + +/datum/hive_status/proc/replace_hive_leader(mob/living/carbon/xenomorph/original, mob/living/carbon/xenomorph/replacement) + if(!replacement || replacement.hive_pos != NORMAL_XENO) + return remove_hive_leader(original) + + var/leader_num = GET_XENO_LEADER_NUM(original) + + xeno_leader_list[leader_num] = replacement + + original.hive_pos = NORMAL_XENO + original.handle_xeno_leader_pheromones() + original.hud_update() // To remove leader star + remove_action(original, /datum/action/xeno_action/activable/info_marker) + + replacement.hive_pos = XENO_LEADER_HIVE_POS(leader_num) + replacement.handle_xeno_leader_pheromones() + replacement.hud_update() // To add leader star + give_action(replacement, /datum/action/xeno_action/activable/info_marker) + + hive_ui.update_xeno_keys() + +/datum/hive_status/proc/handle_xeno_leader_pheromones() + for(var/mob/living/carbon/xenomorph/xeno_leader in xeno_leader_list) + xeno_leader.handle_xeno_leader_pheromones() + +/* + * Helper procs for the Hive Status UI + * These are all called by the hive status UI manager to update its data + */ + +// Returns a list of how many of each caste of xeno there are, sorted by tier +/datum/hive_status/proc/get_xeno_counts() + // Every caste is manually defined here so you get + var/list/xeno_counts = list( + // Yes, Queen is technically considered to be tier 0 + list(XENO_CASTE_LARVA = 0, "Queen" = 0), + list(XENO_CASTE_DRONE = 0, XENO_CASTE_RUNNER = 0, XENO_CASTE_SENTINEL = 0, XENO_CASTE_DEFENDER = 0), + list(XENO_CASTE_HIVELORD = 0, XENO_CASTE_BURROWER = 0, XENO_CASTE_CARRIER = 0, XENO_CASTE_LURKER = 0, XENO_CASTE_SPITTER = 0, XENO_CASTE_WARRIOR = 0), + list(XENO_CASTE_BOILER = 0, XENO_CASTE_CRUSHER = 0, XENO_CASTE_PRAETORIAN = 0, XENO_CASTE_RAVAGER = 0) + ) + + for(var/mob/living/carbon/xenomorph/X in totalXenos) + //don't show xenos in the thunderdome when admins test stuff. + if(is_admin_level(X.z)) + var/area/A = get_area(X) + if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN)) + continue + + if(X.caste && X.counts_for_slots) + xeno_counts[X.caste.tier+1][X.caste.caste_type]++ + + return xeno_counts + +// Returns a sorted list of some basic info (stuff that's needed for sorting) about all the xenos in the hive +// The idea is that we sort this list, and use it as a "key" for all the other information (especially the nicknumber) +// in the hive status UI. That way we can minimize the amount of sorts performed by only calling this when xenos are created/disposed +/datum/hive_status/proc/get_xeno_keys() + var/list/xenos[totalXenos.len] + + var/index = 1 + var/useless_slots = 0 + for(var/mob/living/carbon/xenomorph/X in totalXenos) + if(is_admin_level(X.z)) + var/area/A = get_area(X) + if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN)) + useless_slots++ + continue + + // Insert without doing list merging + xenos[index++] = list( + "nicknumber" = X.nicknumber, + "tier" = X.tier, // This one is only important for sorting + "is_leader" = (IS_XENO_LEADER(X)), + "is_queen" = istype(X.caste, /datum/caste_datum/queen), + "caste_type" = X.caste_type + ) + + // Clear nulls from the xenos list + xenos.len -= useless_slots + + // Make it all nice and fancy by sorting the list before returning it + var/list/sorted_keys = sort_xeno_keys(xenos) + if(length(sorted_keys)) + return sorted_keys + return xenos + +// This sorts the xeno info list by multiple criteria. Prioritized in order: +// 1. Queen +// 2. Leaders +// 3. Tier +// It uses a slightly modified insertion sort to accomplish this +/datum/hive_status/proc/sort_xeno_keys(list/xenos) + if(!length(xenos)) + return + + var/list/sorted_list = xenos.Copy() + + if(!length(sorted_list)) return - // Update absolutely all data - if(!data_initialized) - update_all_data() + for(var/index in 2 to length(sorted_list)) + var/j = index + + while(j > 1) + var/current = sorted_list[j] + var/prev = sorted_list[j-1] + + // Queen comes first, always + if(current["is_queen"]) + sorted_list.Swap(j-1, j) + j-- + continue + + // don't muck up queen's slot + if(prev["is_queen"]) + j-- + continue + + // Leaders before normal xenos + if(!prev["is_leader"] && current["is_leader"]) + sorted_list.Swap(j-1, j) + j-- + continue + + // Make sure we're only comparing leaders to leaders and non-leaders to non-leaders when sorting + // This means we get leaders sorted first, then non-leaders sorted + // Sort by tier first, higher tiers over lower tiers, and then by name alphabetically + + // Could not think of an elegant way to write this + if(!(current["is_leader"]^prev["is_leader"])\ + && (prev["tier"] < current["tier"]\ + || prev["tier"] == current["tier"] && prev["caste_type"] > current["caste_type"]\ + )) + sorted_list.Swap(j-1, j) - tgui_interact(user) + j-- -/datum/hive_status_ui/tgui_interact(mob/user, datum/tgui/ui) - if(!assoc_hive) + return sorted_list + +// Returns a list with some more info about all xenos in the hive +/datum/hive_status/proc/get_xeno_info() + var/list/xenos = list() + + for(var/mob/living/carbon/xenomorph/xeno in totalXenos) + if(is_admin_level(xeno.z)) + var/area/area = get_area(xeno) + if(!(area.flags_atom & AREA_ALLOW_XENO_JOIN)) + continue + + var/xeno_name = xeno.name + // goddamn fucking larvas with their weird ass maturing system + // its name updates with its icon, unlike other castes which only update the mature/elder, etc. prefix on evolve + if(istype(xeno, /mob/living/carbon/xenomorph/larva)) + xeno_name = "Larva ([xeno.nicknumber])" + xenos["[xeno.nicknumber]"] = list( + "name" = xeno_name, + "strain" = xeno.mutation_type, + "ref" = "\ref[xeno]" + ) + + return xenos + +/datum/hive_status/proc/set_hive_location(obj/effect/alien/resin/special/pylon/core/hive_core) + if(!hive_core || hive_core == hive_location) return + var/area/area = get_area(hive_core) + xeno_message(SPAN_XENOANNOUNCE("The Queen has set the hive location as [area]."), 3, hivenumber) + hive_location = hive_core + hive_ui.update_hive_location() + +// Returns a list of xeno healths and locations +/datum/hive_status/proc/get_xeno_vitals() + var/list/xenos = list() + + for(var/mob/living/carbon/xenomorph/xeno in totalXenos) + if(is_admin_level(xeno.z)) + var/area/area = get_area(xeno) + if(!(area.flags_atom & AREA_ALLOW_XENO_JOIN)) + continue + + if(!(xeno in GLOB.living_xeno_list)) + continue + + var/area/area = get_area(xeno) + var/area_name = "Unknown" + if(area) + area_name = area.name + + xenos["[xeno.nicknumber]"] = list( + "health" = round((xeno.health / xeno.maxHealth) * 100, 1), + "area" = area_name, + "is_ssd" = (!xeno.client) + ) + + return xenos + +#define TIER_3 "3" +#define TIER_2 "2" +#define OPEN_SLOTS "open_slots" +#define GUARANTEED_SLOTS "guaranteed_slots" + +// Returns an assoc list of open slots and guaranteed slots left +/datum/hive_status/proc/get_tier_slots() + var/list/slots = list( + TIER_3 = list( + OPEN_SLOTS = 0, + GUARANTEED_SLOTS = list(), + ), + TIER_2 = list( + OPEN_SLOTS = 0, + GUARANTEED_SLOTS = list(), + ), + ) + + var/used_tier_2_slots = length(tier_2_xenos) + var/used_tier_3_slots = length(tier_3_xenos) + + for(var/caste_path in free_slots) + var/slots_free = free_slots[caste_path] + var/slots_used = used_slots[caste_path] + var/datum/caste_datum/current_caste = caste_path + if(slots_used) + // Don't count any free slots in use + switch(initial(current_caste.tier)) + if(2) + used_tier_2_slots -= min(slots_used, slots_free) + if(3) + used_tier_3_slots -= min(slots_used, slots_free) + if(slots_free <= slots_used) + continue + // Display any free slots available + switch(initial(current_caste.tier)) + if(2) + slots[TIER_2][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used + if(3) + slots[TIER_3][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used + + var/burrowed_factor = min(stored_larva, sqrt(4*stored_larva)) + var/effective_total = round(burrowed_factor) + for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos) + if(xeno.counts_for_slots) + effective_total++ + + // Tier 3 slots are always 20% of the total xenos in the hive + slots[TIER_3][OPEN_SLOTS] = max(0, Ceiling(0.20*effective_total/tier_slot_multiplier) - used_tier_3_slots) + // Tier 2 slots are between 30% and 50% of the hive, depending + // on how many T3s there are. + slots[TIER_2][OPEN_SLOTS] = max(0, Ceiling(0.5*effective_total/tier_slot_multiplier) - used_tier_2_slots - used_tier_3_slots) + + return slots + +#undef TIER_3 +#undef TIER_2 +#undef OPEN_SLOTS +#undef GUARANTEED_SLOTS + +/datum/hive_status/proc/can_build_structure(structure_name) + if(!structure_name || !hive_structures_limit[structure_name]) + return FALSE + var/total_count = 0 + if(hive_structures[structure_name]) + total_count += hive_structures[structure_name].len + if(hive_constructions[structure_name]) + total_count += hive_constructions[structure_name].len + if(total_count >= hive_structures_limit[structure_name]) + return FALSE + return TRUE + +/datum/hive_status/proc/has_structure(structure_name) + if(!structure_name) + return FALSE + if(hive_structures[structure_name] && length(hive_structures[structure_name])) + return TRUE + return FALSE + +/datum/hive_status/proc/add_construction(obj/effect/alien/resin/construction/construction) + if(!construction || !construction.template) + return FALSE + var/name_ref = initial(construction.template.name) + if(!hive_constructions[name_ref]) + hive_constructions[name_ref] = list() + if(hive_constructions[name_ref].len >= hive_structures_limit[name_ref]) + return FALSE + hive_constructions[name_ref] += src + return TRUE + +/datum/hive_status/proc/remove_construction(obj/effect/alien/resin/construction/construction) + if(!construction || !construction.template) + return FALSE + var/name_ref = initial(construction.template.name) + hive_constructions[name_ref] -= src + return TRUE + +/datum/hive_status/proc/add_special_structure(obj/effect/alien/resin/special/construction) + if(!construction) + return FALSE + var/name_ref = initial(construction.name) + if(!hive_structures[name_ref]) + hive_structures[name_ref] = list() + if(length(hive_structures[name_ref]) >= hive_structures_limit[name_ref]) + return FALSE + hive_structures[name_ref] += construction + return TRUE + +/datum/hive_status/proc/remove_special_structure(obj/effect/alien/resin/special/construction) + if(!construction) + return FALSE + var/name_ref = initial(construction.name) + hive_structures[name_ref] -= construction + return TRUE + +/datum/hive_status/proc/has_special_structure(name_ref) + if(!name_ref || !hive_structures[name_ref] || length(!hive_structures[name_ref])) + return 0 + return length(hive_structures[name_ref]) - ui = SStgui.try_update_ui(user, src, ui) - if (!ui) - ui = new(user, src, "HiveStatus", "[assoc_hive.name] Status") - ui.open() - ui.set_autoupdate(FALSE) +/datum/hive_status/proc/abandon_on_hijack() + var/area/hijacked_dropship = get_area(living_xeno_queen) + var/shipside_humans_weighted_count = 0 + var/xenos_count = 0 + for(var/name_ref in hive_structures) + for(var/obj/effect/alien/resin/special/construction in hive_structures[name_ref]) + if(get_area(construction) == hijacked_dropship) + continue + construction.hijack_delete = TRUE + hive_structures[name_ref] -= construction + qdel(construction) + for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos) + if(get_area(xeno) != hijacked_dropship && xeno.loc && is_ground_level(xeno.loc.z)) + if(isfacehugger(xeno) || islesserdrone(xeno)) + to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind.")) + if(length(xeno.stomach_contents)) + xeno.devour_timer = 0 + xeno.handle_stomach_contents() + qdel(xeno) + continue + if(xeno.hunter_data.hunted && !isqueen(xeno)) + to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, seperating you from her hive! You must defend yourself from the headhunter before you can enter hibernation...")) + xeno.set_hive_and_update(XENO_HIVE_FORSAKEN) + else + to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind.")) + if(length(xeno.stomach_contents)) + xeno.devour_timer = 0 + xeno.handle_stomach_contents() + qdel(xeno) + stored_larva++ + continue + if(xeno.tier >= 1) + xenos_count++ + for(var/alive_mob in GLOB.alive_mob_list) + var/mob/living/potential_host = alive_mob + if(!(potential_host.status_flags & XENO_HOST)) + continue + if(!is_ground_level(potential_host.z) || get_area(potential_host) == hijacked_dropship) + continue + var/obj/item/alien_embryo/alien_embryo = locate() in potential_host + if(alien_embryo && alien_embryo.hivenumber != hivenumber) + continue + for(var/obj/item/alien_embryo/embryo in potential_host) + embryo.hivenumber = XENO_HIVE_FORSAKEN + potential_host.update_med_icon() + for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list) + if(!(isspecieshuman(current_human) || isspeciessynth(current_human))) + continue + var/datum/job/job = RoleAuthority.roles_for_mode[current_human.job] + if(!job) + continue + var/turf/turf = get_turf(current_human) + if(is_mainship_level(turf?.z)) + shipside_humans_weighted_count += RoleAuthority.calculate_role_weight(job) + hijack_burrowed_surge = TRUE + hijack_burrowed_left = max(n_ceil(shipside_humans_weighted_count * 0.5) - xenos_count, 5) + hivecore_cooldown = FALSE + xeno_message(SPAN_XENOBOLDNOTICE("The weeds have recovered! A new hive core can be built!"),3,hivenumber) -/datum/hive_status_ui/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) +/datum/hive_status/proc/free_respawn(client/client) + stored_larva++ + if(!hive_location || !hive_location.spawn_burrowed_larva(client.mob)) + stored_larva-- + else + hive_ui.update_burrowed_larva() + +/datum/hive_status/proc/respawn_on_turf(client/xeno_client, turf/spawning_turf) + var/mob/living/carbon/xenomorph/larva/new_xeno = spawn_hivenumber_larva(spawning_turf, hivenumber) + if(isnull(new_xeno)) + return FALSE + + if(!SSticker.mode.transfer_xeno(xeno_client.mob, new_xeno)) + qdel(new_xeno) + return FALSE + + new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly emerges from a dead husk!"), + SPAN_XENOANNOUNCE("The hive has no core! You manage to emerge from your old husk as a larva!")) + msg_admin_niche("[key_name(new_xeno)] respawned at \a [spawning_turf]. [ADMIN_JMP(spawning_turf)]") + playsound(new_xeno, 'sound/effects/xeno_newlarva.ogg', 50, 1) + if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN) + window_flash(new_xeno.client) + + hive_ui.update_burrowed_larva() + +/datum/hive_status/proc/do_buried_larva_spawn(mob/xeno_candidate) + var/spawning_area + if(hive_location) + spawning_area = hive_location + else if(living_xeno_queen) + spawning_area = living_xeno_queen + else for(var/mob/living/carbon/xenomorpheus as anything in totalXenos) + if(islarva(xenomorpheus) || isxeno_builder(xenomorpheus)) //next to xenos that should be in a safe spot + spawning_area = xenomorpheus + if(!spawning_area) + spawning_area = pick(totalXenos) // FUCK IT JUST GO ANYWHERE + var/list/turf_list + for(var/turf/open/open_turf in orange(3, spawning_area)) + LAZYADD(turf_list, open_turf) + var/turf/open/spawning_turf = pick(turf_list) + + var/mob/living/carbon/xenomorph/larva/new_xeno = spawn_hivenumber_larva(spawning_turf, hivenumber) + if(isnull(new_xeno)) + return FALSE + + if(!SSticker.mode.transfer_xeno(xeno_candidate, new_xeno)) + qdel(new_xeno) + return FALSE + new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly burrows out of [spawning_turf]!"), + SPAN_XENODANGER("You burrow out of [spawning_turf] and awaken from your slumber. For the Hive!")) + msg_admin_niche("[key_name(new_xeno)] burrowed out from \a [spawning_turf]. [ADMIN_JMP(spawning_turf)]") + playsound(new_xeno, 'sound/effects/xeno_newlarva.ogg', 50, 1) + to_chat(new_xeno, SPAN_XENOANNOUNCE("You are a xenomorph larva awakened from slumber!")) + if(new_xeno.client) + if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN) + window_flash(new_xeno.client) + + stored_larva-- + hive_ui.update_burrowed_larva() + +/mob/living/proc/ally_of_hivenumber(hivenumber) + var/datum/hive_status/indexed_hive = GLOB.hive_datum[hivenumber] + if(!indexed_hive) + return FALSE + + return indexed_hive.is_ally(src) + +/datum/hive_status/proc/is_ally(mob/living/living_mob) + if(isxeno(living_mob)) + var/mob/living/carbon/xenomorph/xeno = living_mob + if(xeno.hivenumber == hivenumber) + return !xeno.banished + + if(!living_mob.faction) + return FALSE + + return faction_is_ally(living_mob.faction) + +/datum/hive_status/proc/faction_is_ally(faction, ignore_queen_check = FALSE) + if(faction == internal_faction) + return TRUE + if(!ignore_queen_check && !living_xeno_queen) + return FALSE + + return allies[faction] + +/datum/hive_status/proc/can_delay_round_end(mob/living/carbon/xenomorph/xeno) + if(HAS_TRAIT(src, TRAIT_NO_HIVE_DELAY)) + return FALSE + return TRUE + +/datum/hive_status/proc/update_hugger_limit() + var/countable_xeno_iterator = 0 + for(var/mob/living/carbon/xenomorph/cycled_xeno as anything in totalXenos) + if(cycled_xeno.counts_for_slots) + countable_xeno_iterator++ + + playable_hugger_limit = max(Floor(countable_xeno_iterator / playable_hugger_max_divisor), playable_hugger_minimum) + +/datum/hive_status/proc/can_spawn_as_hugger(mob/dead/observer/user) + if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber]) + return FALSE + if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned + to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph.")) + return FALSE + if(world.time < hugger_timelock) + to_chat(user, SPAN_WARNING("The hive cannot support facehuggers yet...")) + return FALSE + if(world.time - user.timeofdeath < JOIN_AS_FACEHUGGER_DELAY) + var/time_left = round((user.timeofdeath + JOIN_AS_FACEHUGGER_DELAY - world.time) / 10) + to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a facehugger until 3 minutes have passed ([time_left] seconds remaining).")) + return FALSE + if(totalXenos.len <= 0) + //This is to prevent people from joining as Forsaken Huggers on the pred ship + to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!")) + return FALSE + for(var/mob_name in banished_ckeys) + if(banished_ckeys[mob_name] == user.ckey) + to_chat(user, SPAN_WARNING("You are banished from the [name], you may not rejoin unless the Queen re-admits you or dies.")) + return FALSE + + update_hugger_limit() + + var/current_hugger_count = 0 + for(var/mob/mob as anything in totalXenos) + if(isfacehugger(mob)) + current_hugger_count++ + if(playable_hugger_limit <= current_hugger_count) + to_chat(user, SPAN_WARNING("\The [GLOB.hive_datum[hivenumber]] cannot support more facehuggers! Limit: [current_hugger_count]/[playable_hugger_limit]")) + return FALSE + + if(tgui_alert(user, "Are you sure you want to become a facehugger?", "Confirmation", list("Yes", "No")) != "Yes") + return FALSE + + if(!user.client) + return FALSE + + return TRUE + +/datum/hive_status/proc/spawn_as_hugger(mob/dead/observer/user, atom/atom) + var/mob/living/carbon/xenomorph/facehugger/hugger = new /mob/living/carbon/xenomorph/facehugger(atom.loc, null, hivenumber) + user.mind.transfer_to(hugger, TRUE) + hugger.visible_message(SPAN_XENODANGER("A facehugger suddenly emerges out of [atom]!"), SPAN_XENODANGER("You emerge out of [atom] and awaken from your slumber. For the Hive!")) + playsound(hugger, 'sound/effects/xeno_newlarva.ogg', 25, TRUE) + hugger.generate_name() + hugger.timeofdeath = user.timeofdeath // Keep old death time + +/datum/hive_status/proc/update_lesser_drone_limit() + var/countable_xeno_iterator = 0 + for(var/mob/living/carbon/xenomorph/cycled_xeno as anything in totalXenos) + if(cycled_xeno.counts_for_slots) + countable_xeno_iterator++ + + lesser_drone_limit = max(Floor(countable_xeno_iterator / playable_lesser_drones_max_divisor), lesser_drone_minimum) + +/datum/hive_status/proc/can_spawn_as_lesser_drone(mob/dead/observer/user, obj/effect/alien/resin/special/pylon/spawning_pylon) + if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber]) + return FALSE + + if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned + to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph.")) + return FALSE + + if(world.time - user.timeofdeath < JOIN_AS_LESSER_DRONE_DELAY) + var/time_left = round((user.timeofdeath + JOIN_AS_LESSER_DRONE_DELAY - world.time) / 10) + to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a lesser drone until 30 seconds have passed ([time_left] seconds remaining).")) + return FALSE + + if(totalXenos.len <= 0) + to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!")) + return FALSE + + if(!living_xeno_queen) + to_chat(user, SPAN_WARNING("The selected hive does not have a Queen!")) + return FALSE + + if(spawning_pylon.lesser_drone_spawns < 1) + to_chat(user, SPAN_WARNING("The selected core or pylon does not have enough power for a lesser drone!")) + return FALSE + + update_lesser_drone_limit() + + var/current_lesser_drone_count = 0 + for(var/mob/mob as anything in totalXenos) + if(islesserdrone(mob)) + current_lesser_drone_count++ + + if(lesser_drone_limit <= current_lesser_drone_count) + to_chat(user, SPAN_WARNING("[GLOB.hive_datum[hivenumber]] cannot support more lesser drones! Limit: [current_lesser_drone_count]/[lesser_drone_limit]")) + return FALSE + + if(!user.client) + return FALSE + + return TRUE + +///Called by /obj/item/alien_embryo when a host is bursting to determine extra larva per burst +/datum/hive_status/proc/increase_larva_after_burst() + var/extra_per_burst = CONFIG_GET(number/extra_larva_per_burst) + partial_larva += extra_per_burst + convert_partial_larva_to_full_larva() + +///Called after times when partial larva are added to process them to stored larva +/datum/hive_status/proc/convert_partial_larva_to_full_larva() + for(var/i = 1 to partial_larva) + partial_larva-- + stored_larva++ + +/datum/hive_status/corrupted + name = "Corrupted Hive" + reporting_id = "corrupted" + hivenumber = XENO_HIVE_CORRUPTED + prefix = "Corrupted " + color = "#80ff80" + ui_color ="#4d994d" + latejoin_burrowed = FALSE + + need_round_end_check = TRUE + + var/list/defectors = list() + +/datum/hive_status/corrupted/add_xeno(mob/living/carbon/xenomorph/xeno) + . = ..() + xeno.add_language(LANGUAGE_ENGLISH) + +/datum/hive_status/corrupted/remove_xeno(mob/living/carbon/xenomorph/xeno, hard) + . = ..() + xeno.remove_language(LANGUAGE_ENGLISH) + +/datum/hive_status/corrupted/can_delay_round_end(mob/living/carbon/xenomorph/xeno) + if(!faction_is_ally(FACTION_MARINE, TRUE)) + return TRUE + return FALSE + +/datum/hive_status/alpha + name = "Alpha Hive" + reporting_id = "alpha" + hivenumber = XENO_HIVE_ALPHA + prefix = "Alpha " + color = "#ff4040" + ui_color = "#992626" + latejoin_burrowed = FALSE + + dynamic_evolution = FALSE + +/datum/hive_status/bravo + name = "Bravo Hive" + reporting_id = "bravo" + hivenumber = XENO_HIVE_BRAVO + prefix = "Bravo " + color = "#ffff80" + ui_color = "#99994d" + latejoin_burrowed = FALSE + + dynamic_evolution = FALSE + +/datum/hive_status/charlie + name = "Charlie Hive" + reporting_id = "charlie" + hivenumber = XENO_HIVE_CHARLIE + prefix = "Charlie " + color = "#bb40ff" + ui_color = "#702699" + latejoin_burrowed = FALSE + + dynamic_evolution = FALSE + +/datum/hive_status/delta + name = "Delta Hive" + reporting_id = "delta" + hivenumber = XENO_HIVE_DELTA + prefix = "Delta " + color = "#8080ff" + ui_color = "#4d4d99" + latejoin_burrowed = FALSE + + dynamic_evolution = FALSE + +/datum/hive_status/feral + name = "Feral Hive" + reporting_id = "feral" + hivenumber = XENO_HIVE_FERAL + prefix = "Feral " + color = "#828296" + ui_color = "#828296" + + construction_allowed = XENO_NOBODY + destruction_allowed = XENO_NOBODY + dynamic_evolution = FALSE + allow_no_queen_actions = TRUE + allow_no_queen_evo = TRUE + allow_queen_evolve = FALSE + ignore_slots = TRUE + latejoin_burrowed = FALSE + +/datum/hive_status/forsaken + name = "Forsaken Hive" + reporting_id = "forsaken" + hivenumber = XENO_HIVE_FORSAKEN + prefix = "Forsaken " + color = "#cc8ec4" + ui_color = "#cc8ec4" + + dynamic_evolution = FALSE + allow_no_queen_actions = TRUE + allow_no_queen_evo = TRUE + allow_queen_evolve = FALSE + ignore_slots = TRUE + latejoin_burrowed = FALSE + + need_round_end_check = TRUE + +/datum/hive_status/forsaken/can_delay_round_end(mob/living/carbon/xenomorph/xeno) + return FALSE + +/datum/hive_status/yautja + name = "Hellhound Pack" + reporting_id = "hellhounds" + hivenumber = XENO_HIVE_YAUTJA + internal_faction = FACTION_YAUTJA + + dynamic_evolution = FALSE + allow_no_queen_actions = TRUE + allow_no_queen_evo = TRUE + allow_queen_evolve = FALSE + ignore_slots = TRUE + latejoin_burrowed = FALSE + + need_round_end_check = TRUE + +/datum/hive_status/yautja/can_delay_round_end(mob/living/carbon/xenomorph/xeno) + return FALSE + +/datum/hive_status/mutated + name = "Mutated Hive" + reporting_id = "mutated" + hivenumber = XENO_HIVE_MUTATED + prefix = "Mutated " + color = "#6abd99" + ui_color = "#6abd99" + + hive_inherant_traits = list(TRAIT_XENONID, TRAIT_NO_COLOR) + latejoin_burrowed = FALSE + +/datum/hive_status/corrupted/tamed + name = "Tamed Hive" + reporting_id = "tamed" + hivenumber = XENO_HIVE_TAMED + prefix = "Tamed " + color = "#80ff80" + + dynamic_evolution = FALSE + allow_no_queen_actions = TRUE + allow_no_queen_evo = TRUE + allow_queen_evolve = FALSE + ignore_slots = TRUE + latejoin_burrowed = FALSE + + var/mob/living/carbon/human/leader + var/list/allied_factions + +/datum/hive_status/corrupted/tamed/New() . = ..() - if(.) + hive_structures_limit[XENO_STRUCTURE_EGGMORPH] = 0 + hive_structures_limit[XENO_STRUCTURE_EVOPOD] = 0 + +/datum/hive_status/corrupted/tamed/proc/make_leader(mob/living/carbon/human/human) + if(!istype(human)) return - switch(action) - if("give_plasma") - var/mob/living/carbon/xenomorph/xenoTarget = locate(params["target_ref"]) in GLOB.living_xeno_list - var/mob/living/carbon/xenomorph/xenoSrc = ui.user + if(leader) + UnregisterSignal(leader, COMSIG_PARENT_QDELETING) - if(QDELETED(xenoTarget) || xenoTarget.stat == DEAD || is_admin_level(xenoTarget.z)) - return + leader = human + RegisterSignal(leader, COMSIG_PARENT_QDELETING, PROC_REF(handle_qdelete)) - if(xenoSrc.stat == DEAD) - return +/datum/hive_status/corrupted/tamed/proc/handle_qdelete(mob/living/carbon/human/human) + SIGNAL_HANDLER - var/datum/action/xeno_action/A = get_xeno_action_by_type(xenoSrc, /datum/action/xeno_action/activable/queen_give_plasma) - A?.use_ability_wrapper(xenoTarget) + if(human == leader) + leader = null - if("heal") - var/mob/living/carbon/xenomorph/xenoTarget = locate(params["target_ref"]) in GLOB.living_xeno_list - var/mob/living/carbon/xenomorph/xenoSrc = ui.user + var/list/faction_groups = human.faction_group + if(faction_groups) + allied_factions = faction_groups.Copy() + if(!(human.faction in allied_factions)) + allied_factions += human.faction - if(QDELETED(xenoTarget) || xenoTarget.stat == DEAD || is_admin_level(xenoTarget.z)) - return +/datum/hive_status/corrupted/tamed/add_xeno(mob/living/carbon/xenomorph/xeno) + . = ..() + xeno.faction_group = allied_factions + +/datum/hive_status/corrupted/tamed/remove_xeno(mob/living/carbon/xenomorph/xeno, hard) + . = ..() + xeno.faction_group = list(xeno.faction) - if(xenoSrc.stat == DEAD) - return +/datum/hive_status/corrupted/tamed/is_ally(mob/living/carbon/carbon) + if(leader) + if(carbon.faction in leader.faction_group) + return TRUE - var/datum/action/xeno_action/A = get_xeno_action_by_type(xenoSrc, /datum/action/xeno_action/activable/queen_heal) - A?.use_ability_wrapper(xenoTarget, TRUE) + if(carbon.faction == leader.faction) + return TRUE + else + if(carbon.faction in allied_factions) + return TRUE + + return ..() - if("overwatch") - var/mob/living/carbon/xenomorph/xenoTarget = locate(params["target_ref"]) in GLOB.living_xeno_list - var/mob/living/carbon/xenomorph/xenoSrc = ui.user +/datum/hive_status/corrupted/renegade + name = "Renegade Hive" + reporting_id = "renegade" + hivenumber = XENO_HIVE_RENEGADE + prefix = "Renegade " + color = "#9c7a4d" + ui_color ="#80705c" - if(QDELETED(xenoTarget) || xenoTarget.stat == DEAD || is_admin_level(xenoTarget.z)) - return + dynamic_evolution = FALSE + allow_queen_evolve = FALSE + allow_no_queen_evo = TRUE + latejoin_burrowed = FALSE - if(xenoSrc.stat == DEAD) - if(isobserver(xenoSrc)) - var/mob/dead/observer/O = xenoSrc - O.ManualFollow(xenoTarget) - return +/datum/hive_status/corrupted/renegade/New() + . = ..() + hive_structures_limit[XENO_STRUCTURE_EGGMORPH] = 0 + hive_structures_limit[XENO_STRUCTURE_EVOPOD] = 0 + for(var/faction in FACTION_LIST_HUMANOID) //renegades allied to all humanoids, but it mostly affects structures. Their ability to attack humanoids and other xenos (including of the same hive) depends on iff settings + allies[faction] = TRUE + +/datum/hive_status/corrupted/renegade/can_spawn_as_hugger(mob/dead/observer/user) + to_chat(user, SPAN_WARNING("The [name] cannot support facehuggers.")) + return FALSE + +/datum/hive_status/corrupted/renegade/proc/iff_protection_check(mob/living/carbon/xenomorph/xeno, mob/living/carbon/attempt_harm_mob) + if(xeno == attempt_harm_mob) + return TRUE //you cannot hurt yourself... + if(!xeno.iff_tag) + return FALSE //can attack anyone if you don't have iff tag + if(isxeno(attempt_harm_mob)) + var/mob/living/carbon/xenomorph/target_xeno = attempt_harm_mob + if(!target_xeno.iff_tag) + return FALSE //can attack any xeno who don't have iff tag + for(var/faction in xeno.iff_tag.faction_groups) + if(faction in target_xeno.iff_tag.faction_groups) + return TRUE //cannot attack xenos with same iff setting + return FALSE + for(var/faction in xeno.iff_tag.faction_groups) + if(faction in attempt_harm_mob.faction_group) + return TRUE //cannot attack mob if iff is set to at least one of its factions + return FALSE + +/datum/hive_status/corrupted/renegade/faction_is_ally(faction, ignore_queen_check = TRUE) + return ..() + +/datum/hive_status/proc/on_queen_death() //break alliances on queen's death + if(allow_no_queen_actions || living_xeno_queen) + return + var/broken_alliances = FALSE + for(var/faction in allies) + if(!allies[faction]) + continue + change_stance(faction, FALSE) + broken_alliances = TRUE + + + if(broken_alliances) + xeno_message(SPAN_XENOANNOUNCE("With the death of the Queen, all alliances have been broken."), 3, hivenumber) + +/datum/hive_status/proc/change_stance(faction, should_ally) + if(faction == name) + return + if(allies[faction] == should_ally) + return + allies[faction] = should_ally + + if(living_xeno_queen) + if(allies[faction]) + xeno_message(SPAN_XENOANNOUNCE("Your Queen set up an alliance with [faction]!"), 3, hivenumber) + else + xeno_message(SPAN_XENOANNOUNCE("Your Queen broke the alliance with [faction]!"), 3, hivenumber) + + for(var/number in GLOB.hive_datum) + var/datum/hive_status/target_hive = GLOB.hive_datum[number] + if(target_hive.name != faction) + continue + if(!target_hive.living_xeno_queen && !target_hive.allow_no_queen_actions) + return + if(allies[faction]) + xeno_message(SPAN_XENOANNOUNCE("You sense that [name] [living_xeno_queen ? "Queen " : ""]set up an alliance with us!"), 3, target_hive.hivenumber) + return + + xeno_message(SPAN_XENOANNOUNCE("You sense that [name] [living_xeno_queen ? "Queen " : ""]broke the alliance with us!"), 3, target_hive.hivenumber) + if(target_hive.allies[name]) //autobreak alliance on betrayal + target_hive.change_stance(name, FALSE) + + +/datum/hive_status/corrupted/change_stance(faction, should_ally) + . = ..() + if(allies[faction]) + return + if(!(faction in FACTION_LIST_HUMANOID)) + return + + for(var/mob/living/carbon/xenomorph/xeno in totalXenos) // handle defecting xenos on betrayal + if(!xeno.iff_tag) + continue + if(!(faction in xeno.iff_tag.faction_groups)) + continue + if(xeno in defectors) + continue + if(xeno.caste_type == XENO_CASTE_QUEEN) + continue + INVOKE_ASYNC(src, PROC_REF(give_defection_choice), xeno, faction) + addtimer(CALLBACK(src, PROC_REF(handle_defectors), faction), 11 SECONDS) + +/datum/hive_status/corrupted/proc/give_defection_choice(mob/living/carbon/xenomorph/xeno, faction) + if(tgui_alert(xeno, "Your Queen has broken the alliance with the [faction]. The device inside your carapace begins to suppress your connection with the Hive. Do you remove it and stay loyal to her?", "Alliance broken!", list("Stay loyal", "Obey the talls"), 10 SECONDS) == "Obey the talls") + if(!xeno.iff_tag) + to_chat(xeno, SPAN_XENOWARNING("It's too late now. The device is gone and your service to the Queen continues.")) + return + defectors += xeno + xeno.set_hive_and_update(XENO_HIVE_RENEGADE) + to_chat(xeno, SPAN_XENOANNOUNCE("You lost the connection with your Hive. Now you have no Queen, only your masters.")) + to_chat(xeno, SPAN_NOTICE("Your instincts have changed, you seem compelled to protect [english_list(xeno.iff_tag.faction_groups, "no one")].")) + return + xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("You rip out [xeno.iff_tag]! For the Hive!")) + xeno.adjustBruteLoss(50) + xeno.iff_tag.forceMove(get_turf(xeno)) + xeno.iff_tag = null + +/datum/hive_status/corrupted/proc/handle_defectors(faction) + for(var/mob/living/carbon/xenomorph/xeno in totalXenos) + if(!xeno.iff_tag) + continue + if(xeno in defectors) + continue + if(!(faction in xeno.iff_tag.faction_groups)) + continue + xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("You rip out [xeno.iff_tag]! For the hive!")) + xeno.adjustBruteLoss(50) + xeno.iff_tag.forceMove(get_turf(xeno)) + xeno.iff_tag = null + if(!length(defectors)) + return - if(!xenoSrc.check_state(TRUE)) - return + xeno_message(SPAN_XENOANNOUNCE("You sense that [english_list(defectors)] turned their backs against their sisters and the Queen in favor of their slavemasters!"), 3, hivenumber) + defectors.Cut() - xenoSrc.overwatch(xenoTarget) +//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 diff --git a/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm b/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm new file mode 100644 index 000000000000..4fe1be51bfff --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/hive_status_ui.dm @@ -0,0 +1,216 @@ +/datum/hive_status_ui + var/name = "Hive Status" + + // Data to pass when rendering the UI (not static) + var/total_xenos + var/list/xeno_counts + var/list/tier_slots + var/list/xeno_vitals + var/list/xeno_keys + var/list/xeno_info + var/hive_location + var/burrowed_larva + var/evilution_level + + var/data_initialized = FALSE + + var/datum/hive_status/assoc_hive = null + +/datum/hive_status_ui/New(datum/hive_status/hive) + assoc_hive = hive + update_all_data() + START_PROCESSING(SShive_status, src) + +/datum/hive_status_ui/process() + update_xeno_vitals() + update_xeno_info(FALSE) + SStgui.update_uis(src) + +// Updates the list tracking how many xenos there are in each tier, and how many there are in total +/datum/hive_status_ui/proc/update_xeno_counts(send_update = TRUE) + xeno_counts = assoc_hive.get_xeno_counts() + + total_xenos = 0 + for(var/counts in xeno_counts) + for(var/caste in counts) + total_xenos += counts[caste] + + if(send_update) + SStgui.update_uis(src) + + xeno_counts[1] -= "Queen" // don't show queen in the amount of xenos + + // Also update the amount of T2/T3 slots + tier_slots = assoc_hive.get_tier_slots() + +// Updates the hive location using the area name of the defined hive location turf +/datum/hive_status_ui/proc/update_hive_location(send_update = TRUE) + if(!assoc_hive.hive_location) + return + + hive_location = strip_improper(get_area_name(assoc_hive.hive_location)) + + if(send_update) + SStgui.update_uis(src) + +// Updates the sorted list of all xenos that we use as a key for all other information +/datum/hive_status_ui/proc/update_xeno_keys(send_update = TRUE) + xeno_keys = assoc_hive.get_xeno_keys() + + if(send_update) + SStgui.update_uis(src) + +// Mildly related to the above, but only for when xenos are removed from the hive +// If a xeno dies, we don't have to regenerate all xeno info and sort it again, just remove them from the data list +/datum/hive_status_ui/proc/xeno_removed(mob/living/carbon/xenomorph/X) + if(!xeno_keys) + return + + for(var/index in 1 to length(xeno_keys)) + var/list/info = xeno_keys[index] + if(info["nicknumber"] == X.nicknumber) + + // tried Remove(), didn't work. *shrug* + xeno_keys[index] = null + xeno_keys -= null + return + + SStgui.update_uis(src) + +// Updates the list of xeno names, strains and references +/datum/hive_status_ui/proc/update_xeno_info(send_update = TRUE) + xeno_info = assoc_hive.get_xeno_info() + + if(send_update) + SStgui.update_uis(src) + +// Updates vital information about xenos such as health and location. Only info that should be updated regularly +/datum/hive_status_ui/proc/update_xeno_vitals() + xeno_vitals = assoc_hive.get_xeno_vitals() + +// Updates how many buried larva there are +/datum/hive_status_ui/proc/update_burrowed_larva(send_update = TRUE) + burrowed_larva = assoc_hive.stored_larva + if(SSxevolution) + evilution_level = SSxevolution.get_evolution_boost_power(assoc_hive.hivenumber) + else + evilution_level = 1 + if(send_update) + SStgui.update_uis(src) + +// Updates all data except burrowed larva +/datum/hive_status_ui/proc/update_all_xeno_data(send_update = TRUE) + update_xeno_counts(FALSE) + update_xeno_vitals() + update_xeno_keys(FALSE) + update_xeno_info(FALSE) + + if(send_update) + SStgui.update_uis(src) + +// Updates all data, including burrowed larva +/datum/hive_status_ui/proc/update_all_data() + data_initialized = TRUE + update_all_xeno_data(FALSE) + update_burrowed_larva(FALSE) + SStgui.update_uis(src) + +/datum/hive_status_ui/ui_state(mob/user) + return GLOB.hive_state[assoc_hive.internal_faction] + +/datum/hive_status_ui/ui_status(mob/user, datum/ui_state/state) + . = ..() + if(isobserver(user)) + return UI_INTERACTIVE + +/datum/hive_status_ui/ui_data(mob/user) + . = list() + .["total_xenos"] = total_xenos + .["xeno_counts"] = xeno_counts + .["tier_slots"] = tier_slots + .["xeno_keys"] = xeno_keys + .["xeno_info"] = xeno_info + .["xeno_vitals"] = xeno_vitals + .["queen_location"] = get_area_name(assoc_hive.living_xeno_queen) + .["hive_location"] = hive_location + .["burrowed_larva"] = burrowed_larva + .["evilution_level"] = evilution_level + + var/mob/living/carbon/xenomorph/queen/Q = user + .["is_in_ovi"] = istype(Q) && Q.ovipositor + +/datum/hive_status_ui/ui_static_data(mob/user) + . = list() + .["user_ref"] = REF(user) + .["hive_color"] = assoc_hive.ui_color + .["hive_name"] = assoc_hive.name + +/datum/hive_status_ui/proc/open_hive_status(mob/user) + if(!user) + return + + // Update absolutely all data + if(!data_initialized) + update_all_data() + + tgui_interact(user) + +/datum/hive_status_ui/tgui_interact(mob/user, datum/tgui/ui) + if(!assoc_hive) + return + + ui = SStgui.try_update_ui(user, src, ui) + if (!ui) + ui = new(user, src, "HiveStatus", "[assoc_hive.name] Status") + ui.open() + ui.set_autoupdate(FALSE) + +/datum/hive_status_ui/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + + switch(action) + if("give_plasma") + var/mob/living/carbon/xenomorph/xenoTarget = locate(params["target_ref"]) in GLOB.living_xeno_list + var/mob/living/carbon/xenomorph/xenoSrc = ui.user + + if(QDELETED(xenoTarget) || xenoTarget.stat == DEAD || is_admin_level(xenoTarget.z)) + return + + if(xenoSrc.stat == DEAD) + return + + var/datum/action/xeno_action/A = get_xeno_action_by_type(xenoSrc, /datum/action/xeno_action/activable/queen_give_plasma) + A?.use_ability_wrapper(xenoTarget) + + if("heal") + var/mob/living/carbon/xenomorph/xenoTarget = locate(params["target_ref"]) in GLOB.living_xeno_list + var/mob/living/carbon/xenomorph/xenoSrc = ui.user + + if(QDELETED(xenoTarget) || xenoTarget.stat == DEAD || is_admin_level(xenoTarget.z)) + return + + if(xenoSrc.stat == DEAD) + return + + var/datum/action/xeno_action/A = get_xeno_action_by_type(xenoSrc, /datum/action/xeno_action/activable/queen_heal) + A?.use_ability_wrapper(xenoTarget, TRUE) + + if("overwatch") + var/mob/living/carbon/xenomorph/xenoTarget = locate(params["target_ref"]) in GLOB.living_xeno_list + var/mob/living/carbon/xenomorph/xenoSrc = ui.user + + if(QDELETED(xenoTarget) || xenoTarget.stat == DEAD || is_admin_level(xenoTarget.z)) + return + + if(xenoSrc.stat == DEAD) + if(isobserver(xenoSrc)) + var/mob/dead/observer/O = xenoSrc + O.ManualFollow(xenoTarget) + return + + if(!xenoSrc.check_state(TRUE)) + return + + xenoSrc.overwatch(xenoTarget) diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_client_procs.dm b/code/modules/mob/living/carbon/xenomorph/xeno_client_procs.dm new file mode 100644 index 000000000000..0de15315796f --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/xeno_client_procs.dm @@ -0,0 +1,48 @@ +/client/var/cached_xeno_playtime + +/// playtime for all castes +/client/proc/get_total_xeno_playtime(skip_cache = FALSE) + if(cached_xeno_playtime && !skip_cache) + return cached_xeno_playtime + + var/total_xeno_playtime = 0 + + for(var/caste in RoleAuthority.castes_by_name) + total_xeno_playtime += get_job_playtime(src, caste) + + total_xeno_playtime += get_job_playtime(src, JOB_XENOMORPH) + + if(player_entity) + var/past_xeno_playtime = player_entity.get_playtime(STATISTIC_XENO) + if(past_xeno_playtime) + total_xeno_playtime += past_xeno_playtime + + + cached_xeno_playtime = total_xeno_playtime + + return total_xeno_playtime + +/// playtime for drone and drone evolution castes +/client/proc/get_total_drone_playtime() + var/total_drone_playtime = 0 + + var/list/drone_evo_castes = list(XENO_CASTE_DRONE, XENO_CASTE_CARRIER, XENO_CASTE_BURROWER, XENO_CASTE_HIVELORD, XENO_CASTE_QUEEN) + + for(var/caste in RoleAuthority.castes_by_name) + if(!(caste in drone_evo_castes)) + continue + total_drone_playtime += get_job_playtime(src, caste) + + return total_drone_playtime + +/// playtime for t3 castes and queen +/client/proc/get_total_t3_playtime() + var/total_t3_playtime = 0 + var/datum/caste_datum/caste + for(var/caste_name in RoleAuthority.castes_by_name) + caste = RoleAuthority.castes_by_name[caste_name] + if(caste.tier < 3) + continue + total_t3_playtime += get_job_playtime(src, caste_name) + + return total_t3_playtime diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm deleted file mode 100644 index 79f73631c7b1..000000000000 --- a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm +++ /dev/null @@ -1,1569 +0,0 @@ -// Actual caste datum basedef -/datum/caste_datum - var/caste_type = "" - var/display_name = "" - var/tier = 0 - var/dead_icon = "Drone Dead" - var/language = LANGUAGE_XENOMORPH - var/melee_damage_lower = 10 - var/melee_damage_upper = 20 - ///allows fine tuning melee damage to vehicles per caste. - var/melee_vehicle_damage = 10 - var/evasion = XENO_EVASION_NONE - - var/speed = XENO_SPEED_TIER_10 - - var/plasma_max = 10 - var/plasma_gain = 5 - - var/crystal_max = 0 - - var/max_health = XENO_UNIVERSAL_HPMULT * 100 - ///Are they allowed to evolve (and have their evolution progress group) - var/evolution_allowed = 1 - ///Threshold to next evolution - var/evolution_threshold = 0 - /// whether they can get evo points without needing an ovi queen - var/evolve_without_queen = FALSE - ///This is where you add castes to evolve into. "Separated", "by", "commas" - var/list/evolves_to = list() - /// what caste or castes to de-evolve to. - var/list/deevolves_to = list() - ///If they can use consoles, etc. Set on Queen - var/is_intelligent = 0 - var/caste_desc = null - - // Tackles - var/tackle_min = 2 - var/tackle_max = 6 - var/tackle_chance = 35 - var/tacklestrength_min = 2 - var/tacklestrength_max = 3 - - ///Chance of deflecting projectiles. - var/armor_deflection = 0 - var/fire_immunity = FIRE_IMMUNITY_NONE - var/fire_intensity_resistance = 0 - - ///Delay timer for spitting - var/spit_delay = 60 - - /// Windup for spits - var/spit_windup = FALSE - - ///The strength of our aura. Zero means we can't emit one - var/aura_strength = 0 - - ///"Evolving" removed for the time being - var/aura_allowed = list("frenzy", "warding", "recovery") - - ///Adjust pixel size. 0.x is smaller, 1.x is bigger, percentage based. - var/adjust_size_x = 1 - var/adjust_size_y = 1 - - ///list of datum projectile types the xeno can use. - var/list/spit_types - - var/attack_delay = 0 //Bonus or pen to time in between attacks. + makes slashes slower. - - var/agility_speed_increase = 0 // this opens up possibilities for balancing - - // The type of mutator delegate to instantiate on the base caste. Will - // be replaced when the Xeno chooses a strain. - var/behavior_delegate_type = /datum/behavior_delegate - - // Resin building-related vars - /// Default build time and build distance - var/build_time_mult = BUILD_TIME_MULT_XENO - var/max_build_dist = 0 - - // Carrier vars // - - /// if a hugger is held in hand, won't attempt to leap and kill itself - var/hugger_nurturing = FALSE - var/huggers_max = 0 - var/throwspeed = 0 - var/hugger_delay = 0 - var/eggs_max = 0 - var/egg_cooldown = 30 - ///Armor but for explosions - var/xeno_explosion_resistance = 0 - - //Queen vars - var/can_hold_facehuggers = 0 - var/can_hold_eggs = CANNOT_HOLD_EGGS - - var/can_be_queen_healed = TRUE - var/can_be_revived = TRUE - - var/can_vent_crawl = 1 - - var/caste_luminosity = 0 - - /// if fire_immunity is set to be vulnerable, how much will fire damage be multiplied. Defines in xeno.dm - var/fire_vulnerability_mult = 0 - - var/burrow_cooldown = 5 SECONDS - var/tunnel_cooldown = 100 - var/widen_cooldown = 10 SECONDS - ///Big strong ability, big cooldown. - var/tremor_cooldown = 30 SECONDS - ///whether the xeno heals even outside weeds. - var/innate_healing = FALSE - - var/acid_level = 0 - var/weed_level = WEED_LEVEL_STANDARD - ///Time it takes between acid splash retaliate procs. Variable per caste, for if we want future castes that are acid bombs - var/acid_splash_cooldown = 3 SECONDS - - // regen vars - - var/heal_delay_time = 0 SECONDS - var/heal_resting = 1 - var/heal_standing = 0.4 - var/heal_knocked_out = 0.33 - - var/list/resin_build_order - var/minimum_xeno_playtime = 0 - -// cannot evolve to this caste until the round has been going on for this amount of time - // IMPORTANT: this is ROUND_TIME, not world.time - var/minimum_evolve_time = 1 MINUTES - /// Iconstate for the xeno on the minimap - var/minimap_icon = "xeno" - ///The iconstate for leadered xenos on the minimap, added as overlay - var/minimap_leadered_overlay = "xenoleader" - - var/royal_caste = FALSE - - -/datum/caste_datum/can_vv_modify() - return FALSE - -/datum/caste_datum/New() - . = ..() - - //Initialise evolution and upgrade thresholds in one place, once and for all - evolution_threshold = 0 - if(evolution_allowed) - switch(tier) - if(0) - evolution_threshold = 60 - if(1) - evolution_threshold = 200 - if(2) - evolution_threshold = 500 - //Other tiers (T3, Queen, etc.) can't evolve anyway - - resin_build_order = GLOB.resin_build_order_drone - -/client/var/cached_xeno_playtime - -/// playtime for all castes -/client/proc/get_total_xeno_playtime(skip_cache = FALSE) - if(cached_xeno_playtime && !skip_cache) - return cached_xeno_playtime - - var/total_xeno_playtime = 0 - - for(var/caste in RoleAuthority.castes_by_name) - total_xeno_playtime += get_job_playtime(src, caste) - - total_xeno_playtime += get_job_playtime(src, JOB_XENOMORPH) - - if(player_entity) - var/past_xeno_playtime = player_entity.get_playtime(STATISTIC_XENO) - if(past_xeno_playtime) - total_xeno_playtime += past_xeno_playtime - - - cached_xeno_playtime = total_xeno_playtime - - return total_xeno_playtime - -/// playtime for drone and drone evolution castes -/client/proc/get_total_drone_playtime() - var/total_drone_playtime = 0 - - var/list/drone_evo_castes = list(XENO_CASTE_DRONE, XENO_CASTE_CARRIER, XENO_CASTE_BURROWER, XENO_CASTE_HIVELORD, XENO_CASTE_QUEEN) - - for(var/caste in RoleAuthority.castes_by_name) - if(!(caste in drone_evo_castes)) - continue - total_drone_playtime += get_job_playtime(src, caste) - - return total_drone_playtime - -/// playtime for t3 castes and queen -/client/proc/get_total_t3_playtime() - var/total_t3_playtime = 0 - var/datum/caste_datum/caste - for(var/caste_name in RoleAuthority.castes_by_name) - caste = RoleAuthority.castes_by_name[caste_name] - if(caste.tier < 3) - continue - total_t3_playtime += get_job_playtime(src, caste_name) - - return total_t3_playtime - -/datum/caste_datum/proc/can_play_caste(client/client) - if(!CONFIG_GET(flag/use_timelocks)) - return TRUE - - var/total_xeno_playtime = client.get_total_xeno_playtime() - - if(minimum_xeno_playtime && total_xeno_playtime < minimum_xeno_playtime) - return FALSE - - return TRUE - -/datum/caste_datum/proc/get_caste_requirement(client/client) - return minimum_xeno_playtime - client.get_total_xeno_playtime() - -/datum/caste_datum/proc/get_minimap_icon() - var/image/background = mutable_appearance('icons/ui_icons/map_blips.dmi', "background") - background.color = MINIMAP_ICON_BACKGROUND_XENO - - var/iconstate = minimap_icon ? minimap_icon : "unknown" - var/mutable_appearance/icon = image('icons/ui_icons/map_blips.dmi', icon_state = iconstate) - icon.appearance_flags = RESET_COLOR - background.overlays += icon - - return background - -/datum/hive_status - var/name = "Normal Hive" - - // Used for the faction of the xenomorph. Not recommended to modify. - var/internal_faction - - /// Short Hive ID as string used in stats reporting - var/reporting_id = "normal" - - var/hivenumber = XENO_HIVE_NORMAL - var/mob/living/carbon/xenomorph/queen/living_xeno_queen - var/egg_planting_range = 15 - var/slashing_allowed = XENO_SLASH_ALLOWED //This initial var allows the queen to turn on or off slashing. Slashing off means harm intent does much less damage. - var/construction_allowed = NORMAL_XENO //Who can place construction nodes for special structures - var/destruction_allowed = XENO_LEADER //Who can destroy special structures - var/unnesting_allowed = TRUE - var/hive_orders = "" //What orders should the hive have - var/color = null - var/ui_color = null // Color for hive status collapsible buttons and xeno count list - var/prefix = "" - var/queen_leader_limit = 2 - var/list/open_xeno_leader_positions = list(1, 2) // Ordered list of xeno leader positions (indexes in xeno_leader_list) that are not occupied - var/list/xeno_leader_list[2] // Ordered list (i.e. index n holds the nth xeno leader) - var/stored_larva = 0 - - ///used by /datum/hive_status/proc/increase_larva_after_burst() to support non-integer increases to larva - var/partial_larva = 0 - /// Assoc list of free slots available to specific castes - var/list/free_slots = list( - /datum/caste_datum/burrower = 1, - /datum/caste_datum/hivelord = 1, - /datum/caste_datum/carrier = 1 - ) - /// Assoc list of slots currently used by specific castes (for calculating free_slot usage) - var/list/used_slots = list() - /// list of living tier2 xenos - var/list/tier_2_xenos = list() - /// list of living tier3 xenos - var/list/tier_3_xenos = list() - /// list of living xenos - var/list/totalXenos = list() - /// list of previously living xenos (hardrefs currently) - var/list/total_dead_xenos = list() - var/xeno_queen_timer - var/isSlotOpen = TRUE //Set true for starting alerts only after the hive has reached its full potential - var/allowed_nest_distance = 15 //How far away do we allow nests from an ovied Queen. Default 15 tiles. - var/obj/effect/alien/resin/special/pylon/core/hive_location = null //Set to ref every time a core is built, for defining the hive location - var/crystal_stored = 0 //How much stockpiled material is stored for the hive to use. - - var/datum/mutator_set/hive_mutators/mutators = new - var/tier_slot_multiplier = 1 - var/larva_gestation_multiplier = 1 - var/bonus_larva_spawn_chance = 1 - var/hijack_burrowed_surge = FALSE //at hijack, start spawning lots of burrowed - /// how many burrowed is going to spawn during larva surge - var/hijack_burrowed_left = 0 - - var/ignore_slots = FALSE - var/dynamic_evolution = TRUE - var/evolution_rate = 3 // Only has use if dynamic_evolution is false - var/evolution_bonus = 0 - - var/allow_no_queen_actions = FALSE - var/allow_no_queen_evo = FALSE - var/evolution_without_ovipositor = TRUE //Temporary for the roundstart. - /// Set to false if you want to prevent evolutions into Queens - var/allow_queen_evolve = TRUE - /// Set to true if you want to prevent bursts and spawns of new xenos. Will also prevent healing if the queen no longer exists - var/hardcore = FALSE - /// Set to false if you want to prevent getting burrowed larva from latejoin marines - var/latejoin_burrowed = TRUE - - var/list/hive_inherant_traits - - // Cultist Info - var/mob/living/carbon/leading_cult_sl - - //List of how many maximum of each special structure you can have - var/list/hive_structures_limit = list( - XENO_STRUCTURE_CORE = 1, - XENO_STRUCTURE_CLUSTER = 8, - XENO_STRUCTURE_POOL = 1, - XENO_STRUCTURE_EGGMORPH = 6, - XENO_STRUCTURE_EVOPOD = 2, - XENO_STRUCTURE_RECOVERY = 6, - XENO_STRUCTURE_PYLON = 2, - ) - - var/global/list/hive_structure_types = list( - XENO_STRUCTURE_CORE = /datum/construction_template/xenomorph/core, - XENO_STRUCTURE_CLUSTER = /datum/construction_template/xenomorph/cluster, - XENO_STRUCTURE_EGGMORPH = /datum/construction_template/xenomorph/eggmorph, - XENO_STRUCTURE_RECOVERY = /datum/construction_template/xenomorph/recovery - ) - - var/list/list/hive_structures = list() //Stringref list of structures that have been built - var/list/list/hive_constructions = list() //Stringref list of structures that are being built - - var/datum/hive_status_ui/hive_ui - var/datum/mark_menu_ui/mark_ui - var/datum/hive_faction_ui/faction_ui - - var/list/tunnels = list() - - var/list/allies = list() - - var/list/resin_marks = list() - - var/list/banished_ckeys = list() - - var/hivecore_cooldown = FALSE - - var/need_round_end_check = FALSE - - //Joining as Facehugger vars - /// When can huggers join the round - var/hugger_timelock = 15 MINUTES - /// How many huggers can the hive support - var/playable_hugger_limit = 0 - /// Minimum number of huggers available at any hive size - var/playable_hugger_minimum = 2 - /// This number divides the total xenos counted for slots to give the max number of facehuggers - var/playable_hugger_max_divisor = 4 - - /// How many lesser drones the hive can support - var/lesser_drone_limit = 0 - /// Slots available for lesser drones will never go below this number - var/lesser_drone_minimum = 2 - /// This number divides the total xenos counted for slots to give the max number of lesser drones - var/playable_lesser_drones_max_divisor = 3 - - var/datum/tacmap/drawing/xeno/tacmap - var/minimap_type = MINIMAP_FLAG_XENO - -/datum/hive_status/New() - mutators.hive = src - hive_ui = new(src) - mark_ui = new(src) - faction_ui = new(src) - minimap_type = get_minimap_flag_for_faction(hivenumber) - tacmap = new(src, minimap_type) - if(!internal_faction) - internal_faction = name - if(hivenumber != XENO_HIVE_NORMAL) - return - - RegisterSignal(SSdcs, COMSIG_GLOB_POST_SETUP, PROC_REF(post_setup)) - -/datum/hive_status/proc/post_setup() - SIGNAL_HANDLER - - setup_evolution_announcements() - setup_pylon_limits() - -/datum/hive_status/proc/setup_evolution_announcements() - for(var/time in GLOB.xeno_evolve_times) - if(time == "0") - continue - - addtimer(CALLBACK(src, PROC_REF(announce_evolve_available), GLOB.xeno_evolve_times[time]), text2num(time)) - -/// Sets up limits on pylons in New() for potential futureproofing with more static comms -/datum/hive_status/proc/setup_pylon_limits() - hive_structures_limit[XENO_STRUCTURE_PYLON] = length(GLOB.all_static_telecomms_towers) || 2 - -/datum/hive_status/proc/announce_evolve_available(list/datum/caste_datum/available_castes) - - var/list/castes_available = list() - for(var/datum/caste_datum/current_caste as anything in available_castes) - castes_available += initial(current_caste.caste_type) - - var/castes = castes_available.Join(", ") - xeno_message(SPAN_XENOANNOUNCE("The Hive is now strong enough to support: [castes]")) - xeno_maptext("The Hive can now support: [castes]", "Hive Strengthening") - - -// Adds a xeno to this hive -/datum/hive_status/proc/add_xeno(mob/living/carbon/xenomorph/X) - if(!X || !istype(X)) - return - - // If the xeno is part of another hive, they should be removed from that one first - if(X.hive && X.hive != src) - X.hive.remove_xeno(X, TRUE) - - // Already in the hive - if(X in totalXenos) - return - - // Can only have one queen. - if(isqueen(X)) - if(!living_xeno_queen && !is_admin_level(X.z)) // Don't consider xenos in admin level - set_living_xeno_queen(X) - - X.hivenumber = hivenumber - X.hive = src - - X.set_faction(internal_faction) - - if(X.hud_list) - X.hud_update() - - var/area/A = get_area(X) - if(!is_admin_level(X.z) || (A.flags_atom & AREA_ALLOW_XENO_JOIN)) - totalXenos += X - if(X.tier == 2) - tier_2_xenos += X - else if(X.tier == 3) - tier_3_xenos += X - - // Xenos are a fuckfest of cross-dependencies of different datums that are initialized at different times - // So don't even bother trying updating UI here without large refactors - -// Removes the xeno from the hive -/datum/hive_status/proc/remove_xeno(mob/living/carbon/xenomorph/xeno, hard = FALSE, light_mode = FALSE) - if(!xeno || !istype(xeno)) - return - - // Make sure the xeno was in the hive in the first place - if(!(xeno in totalXenos)) - return - - // This might be a redundant check now that Queen/Destroy() checks, but doesn't hurt to double check - if(living_xeno_queen == xeno) - var/mob/living/carbon/xenomorph/queen/next_queen = null - for(var/mob/living/carbon/xenomorph/queen/queen in totalXenos) - if(!is_admin_level(queen.z) && queen != src && !QDELETED(queen)) - next_queen = queen - break - - set_living_xeno_queen(next_queen) // either null or a queen - - // We allow "soft" removals from the hive (the xeno still retains information about the hive) - // This is so that xenos can add themselves back to the hive if they should die or otherwise go "on leave" from the hive - if(hard) - xeno.hivenumber = 0 - xeno.hive = null -#ifndef UNIT_TESTS // Since this is a hard ref, we shouldn't confuse create_and_destroy - else - total_dead_xenos += xeno -#endif - - totalXenos -= xeno - if(xeno.tier == 2) - tier_2_xenos -= xeno - else if(xeno.tier == 3) - tier_3_xenos -= xeno - - // Only handle free slots if the xeno is not in tdome - if(!is_admin_level(xeno.z)) - var/selected_caste = GLOB.xeno_datum_list[xeno.caste_type]?.type - if(used_slots[selected_caste]) - used_slots[selected_caste]-- - - if(!light_mode) - hive_ui.update_xeno_counts() - hive_ui.xeno_removed(xeno) - -/datum/hive_status/proc/set_living_xeno_queen(mob/living/carbon/xenomorph/queen/queen) - if(!queen) - mutators.reset_mutators() - SStracking.delete_leader("hive_[hivenumber]") - SStracking.stop_tracking("hive_[hivenumber]", living_xeno_queen) - SShive_status.wait = 10 SECONDS - else - SStracking.set_leader("hive_[hivenumber]", queen) - SShive_status.wait = 2 SECONDS - - SEND_SIGNAL(src, COMSIG_HIVE_NEW_QUEEN, queen) - living_xeno_queen = queen - - recalculate_hive() - -/datum/hive_status/proc/recalculate_hive() - if (!living_xeno_queen) - queen_leader_limit = 0 //No leaders for a Hive without a Queen! - else - queen_leader_limit = 4 + mutators.leader_count_boost - - if (xeno_leader_list.len > queen_leader_limit) - var/diff = 0 - for (var/i in queen_leader_limit + 1 to xeno_leader_list.len) - if(!open_xeno_leader_positions.Remove(i)) - remove_hive_leader(xeno_leader_list[i]) - diff++ - xeno_leader_list.len -= diff // Changing the size of xeno_leader_list needs to go at the end or else it won't iterate through the list properly - else if (xeno_leader_list.len < queen_leader_limit) - for (var/i in xeno_leader_list.len + 1 to queen_leader_limit) - open_xeno_leader_positions += i - xeno_leader_list.len++ - - - tier_slot_multiplier = mutators.tier_slot_multiplier - larva_gestation_multiplier = mutators.larva_gestation_multiplier - bonus_larva_spawn_chance = mutators.bonus_larva_spawn_chance - - hive_ui.update_all_data() - -/datum/hive_status/proc/add_hive_leader(mob/living/carbon/xenomorph/xeno) - if(!xeno) - return FALSE //How did this even happen? - if(!open_xeno_leader_positions.len) - return FALSE //Too many leaders already (no available xeno leader positions) - if(xeno.hive_pos != NORMAL_XENO) - return FALSE //Already on the list - var/leader_num = open_xeno_leader_positions[1] - xeno_leader_list[leader_num] = xeno - xeno.hive_pos = XENO_LEADER_HIVE_POS(leader_num) - xeno.handle_xeno_leader_pheromones() - xeno.hud_update() // To add leader star - open_xeno_leader_positions -= leader_num - - xeno.update_minimap_icon() - - give_action(xeno, /datum/action/xeno_action/activable/info_marker) - - hive_ui.update_xeno_keys() - return TRUE - -/datum/hive_status/proc/remove_hive_leader(mob/living/carbon/xenomorph/xeno, light_mode = FALSE) - if(!istype(xeno) || !IS_XENO_LEADER(xeno)) - return FALSE - - var/leader_num = GET_XENO_LEADER_NUM(xeno) - - xeno_leader_list[leader_num] = null - - if(!light_mode) // Don't run side effects during deletions. Better yet, replace all this by signals someday - xeno.hive_pos = NORMAL_XENO - xeno.handle_xeno_leader_pheromones() - xeno.hud_update() // To remove leader star - - // Need to maintain ascending order of open_xeno_leader_positions - for (var/i in 1 to queen_leader_limit) - if (i > open_xeno_leader_positions.len || open_xeno_leader_positions[i] > leader_num) - open_xeno_leader_positions.Insert(i, leader_num) - break - - if(!light_mode) - hive_ui.update_xeno_keys() - - for(var/obj/effect/alien/resin/marker/leaderless_mark in resin_marks) //no resin_mark limit abuse - if(leaderless_mark.createdby == xeno.nicknumber) - qdel(leaderless_mark) - - xeno.update_minimap_icon() - - remove_action(xeno, /datum/action/xeno_action/activable/info_marker) - - return TRUE - -/datum/hive_status/proc/replace_hive_leader(mob/living/carbon/xenomorph/original, mob/living/carbon/xenomorph/replacement) - if(!replacement || replacement.hive_pos != NORMAL_XENO) - return remove_hive_leader(original) - - var/leader_num = GET_XENO_LEADER_NUM(original) - - xeno_leader_list[leader_num] = replacement - - original.hive_pos = NORMAL_XENO - original.handle_xeno_leader_pheromones() - original.hud_update() // To remove leader star - remove_action(original, /datum/action/xeno_action/activable/info_marker) - - replacement.hive_pos = XENO_LEADER_HIVE_POS(leader_num) - replacement.handle_xeno_leader_pheromones() - replacement.hud_update() // To add leader star - give_action(replacement, /datum/action/xeno_action/activable/info_marker) - - hive_ui.update_xeno_keys() - -/datum/hive_status/proc/handle_xeno_leader_pheromones() - for(var/mob/living/carbon/xenomorph/L in xeno_leader_list) - L.handle_xeno_leader_pheromones() - -/* - * Helper procs for the Hive Status UI - * These are all called by the hive status UI manager to update its data - */ - -// Returns a list of how many of each caste of xeno there are, sorted by tier -/datum/hive_status/proc/get_xeno_counts() - // Every caste is manually defined here so you get - var/list/xeno_counts = list( - // Yes, Queen is technically considered to be tier 0 - list(XENO_CASTE_LARVA = 0, "Queen" = 0), - list(XENO_CASTE_DRONE = 0, XENO_CASTE_RUNNER = 0, XENO_CASTE_SENTINEL = 0, XENO_CASTE_DEFENDER = 0), - list(XENO_CASTE_HIVELORD = 0, XENO_CASTE_BURROWER = 0, XENO_CASTE_CARRIER = 0, XENO_CASTE_LURKER = 0, XENO_CASTE_SPITTER = 0, XENO_CASTE_WARRIOR = 0), - list(XENO_CASTE_BOILER = 0, XENO_CASTE_CRUSHER = 0, XENO_CASTE_PRAETORIAN = 0, XENO_CASTE_RAVAGER = 0) - ) - - for(var/mob/living/carbon/xenomorph/X in totalXenos) - //don't show xenos in the thunderdome when admins test stuff. - if(is_admin_level(X.z)) - var/area/A = get_area(X) - if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN)) - continue - - if(X.caste && X.counts_for_slots) - xeno_counts[X.caste.tier+1][X.caste.caste_type]++ - - return xeno_counts - -// Returns a sorted list of some basic info (stuff that's needed for sorting) about all the xenos in the hive -// The idea is that we sort this list, and use it as a "key" for all the other information (especially the nicknumber) -// in the hive status UI. That way we can minimize the amount of sorts performed by only calling this when xenos are created/disposed -/datum/hive_status/proc/get_xeno_keys() - var/list/xenos[totalXenos.len] - - var/index = 1 - var/useless_slots = 0 - for(var/mob/living/carbon/xenomorph/X in totalXenos) - if(is_admin_level(X.z)) - var/area/A = get_area(X) - if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN)) - useless_slots++ - continue - - // Insert without doing list merging - xenos[index++] = list( - "nicknumber" = X.nicknumber, - "tier" = X.tier, // This one is only important for sorting - "is_leader" = (IS_XENO_LEADER(X)), - "is_queen" = istype(X.caste, /datum/caste_datum/queen), - "caste_type" = X.caste_type - ) - - // Clear nulls from the xenos list - xenos.len -= useless_slots - - // Make it all nice and fancy by sorting the list before returning it - var/list/sorted_keys = sort_xeno_keys(xenos) - if(length(sorted_keys)) - return sorted_keys - return xenos - -// This sorts the xeno info list by multiple criteria. Prioritized in order: -// 1. Queen -// 2. Leaders -// 3. Tier -// It uses a slightly modified insertion sort to accomplish this -/datum/hive_status/proc/sort_xeno_keys(list/xenos) - if(!length(xenos)) - return - - var/list/sorted_list = xenos.Copy() - - if(!length(sorted_list)) - return - - for(var/index in 2 to length(sorted_list)) - var/j = index - - while(j > 1) - var/current = sorted_list[j] - var/prev = sorted_list[j-1] - - // Queen comes first, always - if(current["is_queen"]) - sorted_list.Swap(j-1, j) - j-- - continue - - // don't muck up queen's slot - if(prev["is_queen"]) - j-- - continue - - // Leaders before normal xenos - if(!prev["is_leader"] && current["is_leader"]) - sorted_list.Swap(j-1, j) - j-- - continue - - // Make sure we're only comparing leaders to leaders and non-leaders to non-leaders when sorting - // This means we get leaders sorted first, then non-leaders sorted - // Sort by tier first, higher tiers over lower tiers, and then by name alphabetically - - // Could not think of an elegant way to write this - if(!(current["is_leader"]^prev["is_leader"])\ - && (prev["tier"] < current["tier"]\ - || prev["tier"] == current["tier"] && prev["caste_type"] > current["caste_type"]\ - )) - sorted_list.Swap(j-1, j) - - j-- - - return sorted_list - -// Returns a list with some more info about all xenos in the hive -/datum/hive_status/proc/get_xeno_info() - var/list/xenos = list() - - for(var/mob/living/carbon/xenomorph/X in totalXenos) - if(is_admin_level(X.z)) - var/area/A = get_area(X) - if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN)) - continue - - var/xeno_name = X.name - // goddamn fucking larvas with their weird ass maturing system - // its name updates with its icon, unlike other castes which only update the mature/elder, etc. prefix on evolve - if(istype(X, /mob/living/carbon/xenomorph/larva)) - xeno_name = "Larva ([X.nicknumber])" - xenos["[X.nicknumber]"] = list( - "name" = xeno_name, - "strain" = X.mutation_type, - "ref" = "\ref[X]" - ) - - return xenos - -/datum/hive_status/proc/set_hive_location(obj/effect/alien/resin/special/pylon/core/C) - if(!C || C == hive_location) - return - var/area/A = get_area(C) - xeno_message(SPAN_XENOANNOUNCE("The Queen has set the hive location as \the [A]."), 3, hivenumber) - hive_location = C - hive_ui.update_hive_location() - -// Returns a list of xeno healths and locations -/datum/hive_status/proc/get_xeno_vitals() - var/list/xenos = list() - - for(var/mob/living/carbon/xenomorph/X in totalXenos) - if(is_admin_level(X.z)) - var/area/A = get_area(X) - if(!(A.flags_atom & AREA_ALLOW_XENO_JOIN)) - continue - - if(!(X in GLOB.living_xeno_list)) - continue - - var/area/A = get_area(X) - var/area_name = "Unknown" - if(A) - area_name = A.name - - xenos["[X.nicknumber]"] = list( - "health" = round((X.health / X.maxHealth) * 100, 1), - "area" = area_name, - "is_ssd" = (!X.client) - ) - - return xenos - -#define TIER_3 "3" -#define TIER_2 "2" -#define OPEN_SLOTS "open_slots" -#define GUARANTEED_SLOTS "guaranteed_slots" - -// Returns an assoc list of open slots and guaranteed slots left -/datum/hive_status/proc/get_tier_slots() - var/list/slots = list( - TIER_3 = list( - OPEN_SLOTS = 0, - GUARANTEED_SLOTS = list(), - ), - TIER_2 = list( - OPEN_SLOTS = 0, - GUARANTEED_SLOTS = list(), - ), - ) - - var/used_tier_2_slots = length(tier_2_xenos) - var/used_tier_3_slots = length(tier_3_xenos) - - for(var/caste_path in free_slots) - var/slots_free = free_slots[caste_path] - var/slots_used = used_slots[caste_path] - var/datum/caste_datum/current_caste = caste_path - if(slots_used) - // Don't count any free slots in use - switch(initial(current_caste.tier)) - if(2) - used_tier_2_slots -= min(slots_used, slots_free) - if(3) - used_tier_3_slots -= min(slots_used, slots_free) - if(slots_free <= slots_used) - continue - // Display any free slots available - switch(initial(current_caste.tier)) - if(2) - slots[TIER_2][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used - if(3) - slots[TIER_3][GUARANTEED_SLOTS][initial(current_caste.caste_type)] = slots_free - slots_used - - var/burrowed_factor = min(stored_larva, sqrt(4*stored_larva)) - var/effective_total = round(burrowed_factor) - for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos) - if(xeno.counts_for_slots) - effective_total++ - - // Tier 3 slots are always 20% of the total xenos in the hive - slots[TIER_3][OPEN_SLOTS] = max(0, Ceiling(0.20*effective_total/tier_slot_multiplier) - used_tier_3_slots) - // Tier 2 slots are between 30% and 50% of the hive, depending - // on how many T3s there are. - slots[TIER_2][OPEN_SLOTS] = max(0, Ceiling(0.5*effective_total/tier_slot_multiplier) - used_tier_2_slots - used_tier_3_slots) - - return slots - -#undef TIER_3 -#undef TIER_2 -#undef OPEN_SLOTS -#undef GUARANTEED_SLOTS - -/datum/hive_status/proc/can_build_structure(structure_name) - if(!structure_name || !hive_structures_limit[structure_name]) - return FALSE - var/total_count = 0 - if(hive_structures[structure_name]) - total_count += hive_structures[structure_name].len - if(hive_constructions[structure_name]) - total_count += hive_constructions[structure_name].len - if(total_count >= hive_structures_limit[structure_name]) - return FALSE - return TRUE - -/datum/hive_status/proc/has_structure(structure_name) - if(!structure_name) - return FALSE - if(hive_structures[structure_name] && hive_structures[structure_name].len) - return TRUE - return FALSE - -/datum/hive_status/proc/add_construction(obj/effect/alien/resin/construction/S) - if(!S || !S.template) - return FALSE - var/name_ref = initial(S.template.name) - if(!hive_constructions[name_ref]) - hive_constructions[name_ref] = list() - if(hive_constructions[name_ref].len >= hive_structures_limit[name_ref]) - return FALSE - hive_constructions[name_ref] += src - return TRUE - -/datum/hive_status/proc/remove_construction(obj/effect/alien/resin/construction/S) - if(!S || !S.template) - return FALSE - var/name_ref = initial(S.template.name) - hive_constructions[name_ref] -= src - return TRUE - -/datum/hive_status/proc/add_special_structure(obj/effect/alien/resin/special/S) - if(!S) - return FALSE - var/name_ref = initial(S.name) - if(!hive_structures[name_ref]) - hive_structures[name_ref] = list() - if(hive_structures[name_ref].len >= hive_structures_limit[name_ref]) - return FALSE - hive_structures[name_ref] += S - return TRUE - -/datum/hive_status/proc/remove_special_structure(obj/effect/alien/resin/special/S) - if(!S) - return FALSE - var/name_ref = initial(S.name) - hive_structures[name_ref] -= S - return TRUE - -/datum/hive_status/proc/has_special_structure(name_ref) - if(!name_ref || !hive_structures[name_ref] || !hive_structures[name_ref].len) - return 0 - return hive_structures[name_ref].len - -/datum/hive_status/proc/abandon_on_hijack() - var/area/hijacked_dropship = get_area(living_xeno_queen) - var/shipside_humans_weighted_count = 0 - var/xenos_count = 0 - for(var/name_ref in hive_structures) - for(var/obj/effect/alien/resin/special/S in hive_structures[name_ref]) - if(get_area(S) == hijacked_dropship) - continue - S.hijack_delete = TRUE - hive_structures[name_ref] -= S - qdel(S) - for(var/mob/living/carbon/xenomorph/xeno as anything in totalXenos) - if(get_area(xeno) != hijacked_dropship && xeno.loc && is_ground_level(xeno.loc.z)) - if(isfacehugger(xeno) || islesserdrone(xeno)) - to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind.")) - if(xeno.stomach_contents.len) - xeno.devour_timer = 0 - xeno.handle_stomach_contents() - qdel(xeno) - continue - if(xeno.hunter_data.hunted && !isqueen(xeno)) - to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, seperating you from her hive! You must defend yourself from the headhunter before you can enter hibernation...")) - xeno.set_hive_and_update(XENO_HIVE_FORSAKEN) - else - to_chat(xeno, SPAN_XENOANNOUNCE("The Queen has left without you, you quickly find a hiding place to enter hibernation as you lose touch with the hive mind.")) - if(xeno.stomach_contents.len) - xeno.devour_timer = 0 - xeno.handle_stomach_contents() - qdel(xeno) - stored_larva++ - continue - if(xeno.tier >= 1) - xenos_count++ - for(var/i in GLOB.alive_mob_list) - var/mob/living/potential_host = i - if(!(potential_host.status_flags & XENO_HOST)) - continue - if(!is_ground_level(potential_host.z) || get_area(potential_host) == hijacked_dropship) - continue - var/obj/item/alien_embryo/A = locate() in potential_host - if(A && A.hivenumber != hivenumber) - continue - for(var/obj/item/alien_embryo/embryo in potential_host) - embryo.hivenumber = XENO_HIVE_FORSAKEN - potential_host.update_med_icon() - for(var/mob/living/carbon/human/current_human as anything in GLOB.alive_human_list) - if(!(isspecieshuman(current_human) || isspeciessynth(current_human))) - continue - var/datum/job/job = RoleAuthority.roles_for_mode[current_human.job] - if(!job) - continue - var/turf/turf = get_turf(current_human) - if(is_mainship_level(turf?.z)) - shipside_humans_weighted_count += RoleAuthority.calculate_role_weight(job) - hijack_burrowed_surge = TRUE - hijack_burrowed_left = max(n_ceil(shipside_humans_weighted_count * 0.5) - xenos_count, 5) - hivecore_cooldown = FALSE - xeno_message(SPAN_XENOBOLDNOTICE("The weeds have recovered! A new hive core can be built!"),3,hivenumber) - -/datum/hive_status/proc/free_respawn(client/C) - stored_larva++ - if(!hive_location || !hive_location.spawn_burrowed_larva(C.mob)) - stored_larva-- - else - hive_ui.update_burrowed_larva() - -/datum/hive_status/proc/respawn_on_turf(client/xeno_client, turf/spawning_turf) - var/mob/living/carbon/xenomorph/larva/new_xeno = spawn_hivenumber_larva(spawning_turf, hivenumber) - if(isnull(new_xeno)) - return FALSE - - if(!SSticker.mode.transfer_xeno(xeno_client.mob, new_xeno)) - qdel(new_xeno) - return FALSE - - new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly emerges from a dead husk!"), - SPAN_XENOANNOUNCE("The hive has no core! You manage to emerge from your old husk as a larva!")) - msg_admin_niche("[key_name(new_xeno)] respawned at \a [spawning_turf]. [ADMIN_JMP(spawning_turf)]") - playsound(new_xeno, 'sound/effects/xeno_newlarva.ogg', 50, 1) - if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN) - window_flash(new_xeno.client) - - hive_ui.update_burrowed_larva() - -/datum/hive_status/proc/do_buried_larva_spawn(mob/xeno_candidate) - var/spawning_area - if(hive_location) - spawning_area = hive_location - else if(living_xeno_queen) - spawning_area = living_xeno_queen - else for(var/mob/living/carbon/xenomorpheus as anything in totalXenos) - if(islarva(xenomorpheus) || isxeno_builder(xenomorpheus)) //next to xenos that should be in a safe spot - spawning_area = xenomorpheus - if(!spawning_area) - spawning_area = pick(totalXenos) // FUCK IT JUST GO ANYWHERE - var/list/turf_list - for(var/turf/open/open_turf in orange(3, spawning_area)) - LAZYADD(turf_list, open_turf) - var/turf/open/spawning_turf = pick(turf_list) - - var/mob/living/carbon/xenomorph/larva/new_xeno = spawn_hivenumber_larva(spawning_turf, hivenumber) - if(isnull(new_xeno)) - return FALSE - - if(!SSticker.mode.transfer_xeno(xeno_candidate, new_xeno)) - qdel(new_xeno) - return FALSE - new_xeno.visible_message(SPAN_XENODANGER("A larva suddenly burrows out of \the [spawning_turf]!"), - SPAN_XENODANGER("You burrow out of \the [spawning_turf] and awaken from your slumber. For the Hive!")) - msg_admin_niche("[key_name(new_xeno)] burrowed out from \a [spawning_turf]. [ADMIN_JMP(spawning_turf)]") - playsound(new_xeno, 'sound/effects/xeno_newlarva.ogg', 50, 1) - to_chat(new_xeno, SPAN_XENOANNOUNCE("You are a xenomorph larva awakened from slumber!")) - if(new_xeno.client) - if(new_xeno.client?.prefs?.toggles_flashing & FLASH_POOLSPAWN) - window_flash(new_xeno.client) - - stored_larva-- - hive_ui.update_burrowed_larva() - -/mob/living/proc/ally_of_hivenumber(hivenumber) - var/datum/hive_status/indexed_hive = GLOB.hive_datum[hivenumber] - if(!indexed_hive) - return FALSE - - return indexed_hive.is_ally(src) - -/datum/hive_status/proc/is_ally(mob/living/living_mob) - if(isxeno(living_mob)) - var/mob/living/carbon/xenomorph/zenomorf = living_mob - if(zenomorf.hivenumber == hivenumber) - return !zenomorf.banished - - if(!living_mob.faction) - return FALSE - - return faction_is_ally(living_mob.faction) - -/datum/hive_status/proc/faction_is_ally(faction, ignore_queen_check = FALSE) - if(faction == internal_faction) - return TRUE - if(!ignore_queen_check && !living_xeno_queen) - return FALSE - - return allies[faction] - -/datum/hive_status/proc/can_delay_round_end(mob/living/carbon/xenomorph/xeno) - if(HAS_TRAIT(src, TRAIT_NO_HIVE_DELAY)) - return FALSE - return TRUE - -/datum/hive_status/proc/update_hugger_limit() - var/countable_xeno_iterator = 0 - for(var/mob/living/carbon/xenomorph/cycled_xeno as anything in totalXenos) - if(cycled_xeno.counts_for_slots) - countable_xeno_iterator++ - - playable_hugger_limit = max(Floor(countable_xeno_iterator / playable_hugger_max_divisor), playable_hugger_minimum) - -/datum/hive_status/proc/can_spawn_as_hugger(mob/dead/observer/user) - if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber]) - return FALSE - if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned - to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph.")) - return FALSE - if(world.time < hugger_timelock) - to_chat(user, SPAN_WARNING("The hive cannot support facehuggers yet...")) - return FALSE - if(world.time - user.timeofdeath < JOIN_AS_FACEHUGGER_DELAY) - var/time_left = round((user.timeofdeath + JOIN_AS_FACEHUGGER_DELAY - world.time) / 10) - to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a facehugger until 3 minutes have passed ([time_left] seconds remaining).")) - return FALSE - if(totalXenos.len <= 0) - //This is to prevent people from joining as Forsaken Huggers on the pred ship - to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!")) - return FALSE - for(var/mob_name in banished_ckeys) - if(banished_ckeys[mob_name] == user.ckey) - to_chat(user, SPAN_WARNING("You are banished from the [name], you may not rejoin unless the Queen re-admits you or dies.")) - return FALSE - - update_hugger_limit() - - var/current_hugger_count = 0 - for(var/mob/mob as anything in totalXenos) - if(isfacehugger(mob)) - current_hugger_count++ - if(playable_hugger_limit <= current_hugger_count) - to_chat(user, SPAN_WARNING("\The [GLOB.hive_datum[hivenumber]] cannot support more facehuggers! Limit: [current_hugger_count]/[playable_hugger_limit]")) - return FALSE - - if(tgui_alert(user, "Are you sure you want to become a facehugger?", "Confirmation", list("Yes", "No")) != "Yes") - return FALSE - - if(!user.client) - return FALSE - - return TRUE - -/datum/hive_status/proc/spawn_as_hugger(mob/dead/observer/user, atom/A) - var/mob/living/carbon/xenomorph/facehugger/hugger = new /mob/living/carbon/xenomorph/facehugger(A.loc, null, hivenumber) - user.mind.transfer_to(hugger, TRUE) - hugger.visible_message(SPAN_XENODANGER("A facehugger suddenly emerges out of \the [A]!"), SPAN_XENODANGER("You emerge out of \the [A] and awaken from your slumber. For the Hive!")) - playsound(hugger, 'sound/effects/xeno_newlarva.ogg', 25, TRUE) - hugger.generate_name() - hugger.timeofdeath = user.timeofdeath // Keep old death time - -/datum/hive_status/proc/update_lesser_drone_limit() - var/countable_xeno_iterator = 0 - for(var/mob/living/carbon/xenomorph/cycled_xeno as anything in totalXenos) - if(cycled_xeno.counts_for_slots) - countable_xeno_iterator++ - - lesser_drone_limit = max(Floor(countable_xeno_iterator / playable_lesser_drones_max_divisor), lesser_drone_minimum) - -/datum/hive_status/proc/can_spawn_as_lesser_drone(mob/dead/observer/user, obj/effect/alien/resin/special/pylon/spawning_pylon) - if(!GLOB.hive_datum || ! GLOB.hive_datum[hivenumber]) - return FALSE - - if(jobban_isbanned(user, JOB_XENOMORPH)) // User is jobbanned - to_chat(user, SPAN_WARNING("You are banned from playing aliens and cannot spawn as a xenomorph.")) - return FALSE - - if(world.time - user.timeofdeath < JOIN_AS_LESSER_DRONE_DELAY) - var/time_left = round((user.timeofdeath + JOIN_AS_LESSER_DRONE_DELAY - world.time) / 10) - to_chat(user, SPAN_WARNING("You ghosted too recently. You cannot become a lesser drone until 30 seconds have passed ([time_left] seconds remaining).")) - return FALSE - - if(totalXenos.len <= 0) - to_chat(user, SPAN_WARNING("The hive has fallen, you can't join it!")) - return FALSE - - if(!living_xeno_queen) - to_chat(user, SPAN_WARNING("The selected hive does not have a Queen!")) - return FALSE - - if(spawning_pylon.lesser_drone_spawns < 1) - to_chat(user, SPAN_WARNING("The selected core or pylon does not have enough power for a lesser drone!")) - return FALSE - - update_lesser_drone_limit() - - var/current_lesser_drone_count = 0 - for(var/mob/mob as anything in totalXenos) - if(islesserdrone(mob)) - current_lesser_drone_count++ - - if(lesser_drone_limit <= current_lesser_drone_count) - to_chat(user, SPAN_WARNING("[GLOB.hive_datum[hivenumber]] cannot support more lesser drones! Limit: [current_lesser_drone_count]/[lesser_drone_limit]")) - return FALSE - - if(!user.client) - return FALSE - - return TRUE - -///Called by /obj/item/alien_embryo when a host is bursting to determine extra larva per burst -/datum/hive_status/proc/increase_larva_after_burst() - var/extra_per_burst = CONFIG_GET(number/extra_larva_per_burst) - partial_larva += extra_per_burst - convert_partial_larva_to_full_larva() - -///Called after times when partial larva are added to process them to stored larva -/datum/hive_status/proc/convert_partial_larva_to_full_larva() - for(var/i = 1 to partial_larva) - partial_larva-- - stored_larva++ - -/datum/hive_status/corrupted - name = "Corrupted Hive" - reporting_id = "corrupted" - hivenumber = XENO_HIVE_CORRUPTED - prefix = "Corrupted " - color = "#80ff80" - ui_color ="#4d994d" - latejoin_burrowed = FALSE - - need_round_end_check = TRUE - - var/list/defectors = list() - -/datum/hive_status/corrupted/add_xeno(mob/living/carbon/xenomorph/xeno) - . = ..() - xeno.add_language(LANGUAGE_ENGLISH) - -/datum/hive_status/corrupted/remove_xeno(mob/living/carbon/xenomorph/xeno, hard) - . = ..() - xeno.remove_language(LANGUAGE_ENGLISH) - -/datum/hive_status/corrupted/can_delay_round_end(mob/living/carbon/xenomorph/xeno) - if(!faction_is_ally(FACTION_MARINE, TRUE)) - return TRUE - return FALSE - -/datum/hive_status/alpha - name = "Alpha Hive" - reporting_id = "alpha" - hivenumber = XENO_HIVE_ALPHA - prefix = "Alpha " - color = "#ff4040" - ui_color = "#992626" - latejoin_burrowed = FALSE - - dynamic_evolution = FALSE - -/datum/hive_status/bravo - name = "Bravo Hive" - reporting_id = "bravo" - hivenumber = XENO_HIVE_BRAVO - prefix = "Bravo " - color = "#ffff80" - ui_color = "#99994d" - latejoin_burrowed = FALSE - - dynamic_evolution = FALSE - -/datum/hive_status/charlie - name = "Charlie Hive" - reporting_id = "charlie" - hivenumber = XENO_HIVE_CHARLIE - prefix = "Charlie " - color = "#bb40ff" - ui_color = "#702699" - latejoin_burrowed = FALSE - - dynamic_evolution = FALSE - -/datum/hive_status/delta - name = "Delta Hive" - reporting_id = "delta" - hivenumber = XENO_HIVE_DELTA - prefix = "Delta " - color = "#8080ff" - ui_color = "#4d4d99" - latejoin_burrowed = FALSE - - dynamic_evolution = FALSE - -/datum/hive_status/feral - name = "Feral Hive" - reporting_id = "feral" - hivenumber = XENO_HIVE_FERAL - prefix = "Feral " - color = "#828296" - ui_color = "#828296" - - construction_allowed = XENO_NOBODY - destruction_allowed = XENO_NOBODY - dynamic_evolution = FALSE - allow_no_queen_actions = TRUE - allow_no_queen_evo = TRUE - allow_queen_evolve = FALSE - ignore_slots = TRUE - latejoin_burrowed = FALSE - -/datum/hive_status/forsaken - name = "Forsaken Hive" - reporting_id = "forsaken" - hivenumber = XENO_HIVE_FORSAKEN - prefix = "Forsaken " - color = "#cc8ec4" - ui_color = "#cc8ec4" - - dynamic_evolution = FALSE - allow_no_queen_actions = TRUE - allow_no_queen_evo = TRUE - allow_queen_evolve = FALSE - ignore_slots = TRUE - latejoin_burrowed = FALSE - - need_round_end_check = TRUE - -/datum/hive_status/forsaken/can_delay_round_end(mob/living/carbon/xenomorph/xeno) - return FALSE - -/datum/hive_status/yautja - name = "Hellhound Pack" - reporting_id = "hellhounds" - hivenumber = XENO_HIVE_YAUTJA - internal_faction = FACTION_YAUTJA - - dynamic_evolution = FALSE - allow_no_queen_actions = TRUE - allow_no_queen_evo = TRUE - allow_queen_evolve = FALSE - ignore_slots = TRUE - latejoin_burrowed = FALSE - - need_round_end_check = TRUE - -/datum/hive_status/yautja/can_delay_round_end(mob/living/carbon/xenomorph/xeno) - return FALSE - -/datum/hive_status/mutated - name = "Mutated Hive" - reporting_id = "mutated" - hivenumber = XENO_HIVE_MUTATED - prefix = "Mutated " - color = "#6abd99" - ui_color = "#6abd99" - - hive_inherant_traits = list(TRAIT_XENONID, TRAIT_NO_COLOR) - latejoin_burrowed = FALSE - -/datum/hive_status/corrupted/tamed - name = "Tamed Hive" - reporting_id = "tamed" - hivenumber = XENO_HIVE_TAMED - prefix = "Tamed " - color = "#80ff80" - - dynamic_evolution = FALSE - allow_no_queen_actions = TRUE - allow_no_queen_evo = TRUE - allow_queen_evolve = FALSE - ignore_slots = TRUE - latejoin_burrowed = FALSE - - var/mob/living/carbon/human/leader - var/list/allied_factions - -/datum/hive_status/corrupted/tamed/New() - . = ..() - hive_structures_limit[XENO_STRUCTURE_EGGMORPH] = 0 - hive_structures_limit[XENO_STRUCTURE_EVOPOD] = 0 - -/datum/hive_status/corrupted/tamed/proc/make_leader(mob/living/carbon/human/H) - if(!istype(H)) - return - - if(leader) - UnregisterSignal(leader, COMSIG_PARENT_QDELETING) - - leader = H - RegisterSignal(leader, COMSIG_PARENT_QDELETING, PROC_REF(handle_qdelete)) - -/datum/hive_status/corrupted/tamed/proc/handle_qdelete(mob/living/carbon/human/H) - SIGNAL_HANDLER - - if(H == leader) - leader = null - - var/list/faction_groups = H.faction_group - if(faction_groups) - allied_factions = faction_groups.Copy() - if(!(H.faction in allied_factions)) - allied_factions += H.faction - -/datum/hive_status/corrupted/tamed/add_xeno(mob/living/carbon/xenomorph/X) - . = ..() - X.faction_group = allied_factions - -/datum/hive_status/corrupted/tamed/remove_xeno(mob/living/carbon/xenomorph/X, hard) - . = ..() - X.faction_group = list(X.faction) - -/datum/hive_status/corrupted/tamed/is_ally(mob/living/carbon/C) - if(leader) - if(C.faction in leader.faction_group) - return TRUE - - if(C.faction == leader.faction) - return TRUE - else - if(C.faction in allied_factions) - return TRUE - - return ..() - -/datum/hive_status/corrupted/renegade - name = "Renegade Hive" - reporting_id = "renegade" - hivenumber = XENO_HIVE_RENEGADE - prefix = "Renegade " - color = "#9c7a4d" - ui_color ="#80705c" - - dynamic_evolution = FALSE - allow_queen_evolve = FALSE - allow_no_queen_evo = TRUE - latejoin_burrowed = FALSE - -/datum/hive_status/corrupted/renegade/New() - . = ..() - hive_structures_limit[XENO_STRUCTURE_EGGMORPH] = 0 - hive_structures_limit[XENO_STRUCTURE_EVOPOD] = 0 - for(var/faction in FACTION_LIST_HUMANOID) //renegades allied to all humanoids, but it mostly affects structures. Their ability to attack humanoids and other xenos (including of the same hive) depends on iff settings - allies[faction] = TRUE - -/datum/hive_status/corrupted/renegade/can_spawn_as_hugger(mob/dead/observer/user) - to_chat(user, SPAN_WARNING("The [name] cannot support facehuggers.")) - return FALSE - -/datum/hive_status/corrupted/renegade/proc/iff_protection_check(mob/living/carbon/xenomorph/xeno, mob/living/carbon/attempt_harm_mob) - if(xeno == attempt_harm_mob) - return TRUE //you cannot hurt yourself... - if(!xeno.iff_tag) - return FALSE //can attack anyone if you don't have iff tag - if(isxeno(attempt_harm_mob)) - var/mob/living/carbon/xenomorph/target_xeno = attempt_harm_mob - if(!target_xeno.iff_tag) - return FALSE //can attack any xeno who don't have iff tag - for(var/faction in xeno.iff_tag.faction_groups) - if(faction in target_xeno.iff_tag.faction_groups) - return TRUE //cannot attack xenos with same iff setting - return FALSE - for(var/faction in xeno.iff_tag.faction_groups) - if(faction in attempt_harm_mob.faction_group) - return TRUE //cannot attack mob if iff is set to at least one of its factions - return FALSE - -/datum/hive_status/corrupted/renegade/faction_is_ally(faction, ignore_queen_check = TRUE) - return ..() - -/datum/hive_status/proc/on_queen_death() //break alliances on queen's death - if(allow_no_queen_actions || living_xeno_queen) - return - var/broken_alliances = FALSE - for(var/faction in allies) - if(!allies[faction]) - continue - change_stance(faction, FALSE) - broken_alliances = TRUE - - - if(broken_alliances) - xeno_message(SPAN_XENOANNOUNCE("With the death of the Queen, all alliances have been broken."), 3, hivenumber) - -/datum/hive_status/proc/change_stance(faction, should_ally) - if(faction == name) - return - if(allies[faction] == should_ally) - return - allies[faction] = should_ally - - if(living_xeno_queen) - if(allies[faction]) - xeno_message(SPAN_XENOANNOUNCE("Your Queen set up an alliance with [faction]!"), 3, hivenumber) - else - xeno_message(SPAN_XENOANNOUNCE("Your Queen broke the alliance with [faction]!"), 3, hivenumber) - - for(var/number in GLOB.hive_datum) - var/datum/hive_status/target_hive = GLOB.hive_datum[number] - if(target_hive.name != faction) - continue - if(!target_hive.living_xeno_queen && !target_hive.allow_no_queen_actions) - return - if(allies[faction]) - xeno_message(SPAN_XENOANNOUNCE("You sense that [name] [living_xeno_queen ? "Queen " : ""]set up an alliance with us!"), 3, target_hive.hivenumber) - return - - xeno_message(SPAN_XENOANNOUNCE("You sense that [name] [living_xeno_queen ? "Queen " : ""]broke the alliance with us!"), 3, target_hive.hivenumber) - if(target_hive.allies[name]) //autobreak alliance on betrayal - target_hive.change_stance(name, FALSE) - - -/datum/hive_status/corrupted/change_stance(faction, should_ally) - . = ..() - if(allies[faction]) - return - if(!(faction in FACTION_LIST_HUMANOID)) - return - - for(var/mob/living/carbon/xenomorph/xeno in totalXenos) // handle defecting xenos on betrayal - if(!xeno.iff_tag) - continue - if(!(faction in xeno.iff_tag.faction_groups)) - continue - if(xeno in defectors) - continue - if(xeno.caste_type == XENO_CASTE_QUEEN) - continue - INVOKE_ASYNC(src, PROC_REF(give_defection_choice), xeno, faction) - addtimer(CALLBACK(src, PROC_REF(handle_defectors), faction), 11 SECONDS) - -/datum/hive_status/corrupted/proc/give_defection_choice(mob/living/carbon/xenomorph/xeno, faction) - if(tgui_alert(xeno, "Your Queen has broken the alliance with the [faction]. The device inside your carapace begins to suppress your connection with the Hive. Do you remove it and stay loyal to her?", "Alliance broken!", list("Stay loyal", "Obey the talls"), 10 SECONDS) == "Obey the talls") - if(!xeno.iff_tag) - to_chat(xeno, SPAN_XENOWARNING("It's too late now. The device is gone and your service to the Queen continues.")) - return - defectors += xeno - xeno.set_hive_and_update(XENO_HIVE_RENEGADE) - to_chat(xeno, SPAN_XENOANNOUNCE("You lost the connection with your Hive. Now you have no Queen, only your masters.")) - to_chat(xeno, SPAN_NOTICE("Your instincts have changed, you seem compelled to protect [english_list(xeno.iff_tag.faction_groups, "no one")].")) - return - xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("You rip out [xeno.iff_tag]! For the Hive!")) - xeno.adjustBruteLoss(50) - xeno.iff_tag.forceMove(get_turf(xeno)) - xeno.iff_tag = null - -/datum/hive_status/corrupted/proc/handle_defectors(faction) - for(var/mob/living/carbon/xenomorph/xeno in totalXenos) - if(!xeno.iff_tag) - continue - if(xeno in defectors) - continue - if(!(faction in xeno.iff_tag.faction_groups)) - continue - xeno.visible_message(SPAN_XENOWARNING("[xeno] rips out [xeno.iff_tag]!"), SPAN_XENOWARNING("You rip out [xeno.iff_tag]! For the hive!")) - xeno.adjustBruteLoss(50) - xeno.iff_tag.forceMove(get_turf(xeno)) - xeno.iff_tag = null - if(!length(defectors)) - return - - xeno_message(SPAN_XENOANNOUNCE("You sense that [english_list(defectors)] turned their backs against their sisters and the Queen in favor of their slavemasters!"), 3, hivenumber) - defectors.Cut() - -//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 - var/name = "xeno_declare" - var/icon_state = "empty" - var/desc = "Xenos make psychic markers with this meaning as positional lasting communication to eachother" - -/datum/xeno_mark_define/fortify - name = "Fortify" - desc = "Fortify this area!" - icon_state = "fortify" - -/datum/xeno_mark_define/weeds - name = "Need Weeds" - desc = "Need weeds here!" - icon_state = "weed" - -/datum/xeno_mark_define/nest - name = "Nest" - desc = "Nest enemies here!" - icon_state = "nest" - -/datum/xeno_mark_define/hosts - name = "Hosts" - desc = "Hosts here!" - icon_state = "hosts" - -/datum/xeno_mark_define/aide - name = "Aide" - desc = "Aide here!" - icon_state = "aide" - -/datum/xeno_mark_define/defend - name = "Defend" - desc = "Defend the hive here!" - icon_state = "defend" - -/datum/xeno_mark_define/danger - name = "Danger Warning" - desc = "Caution, danger here!" - icon_state = "danger" - -/datum/xeno_mark_define/rally - name = "Rally" - desc = "Group up here!" - icon_state = "rally" - -/datum/xeno_mark_define/hold - name = "Hold" - desc = "Hold this area!" - icon_state = "hold" - -/datum/xeno_mark_define/ambush - name = "Ambush" - desc = "Ambush the enemy here!" - icon_state = "ambush" -/datum/xeno_mark_define/attack - name = "Attack" - desc = "Attack the enemy here!" - icon_state = "attack" - - - diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_marks.dm b/code/modules/mob/living/carbon/xenomorph/xeno_marks.dm new file mode 100644 index 000000000000..e3f168f26de0 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/xeno_marks.dm @@ -0,0 +1,60 @@ +// Xeno mark defines + +/datum/xeno_mark_define + var/name = "xeno_declare" + var/icon_state = "empty" + var/desc = "Xenos make psychic markers with this meaning as positional lasting communication to eachother" + +/datum/xeno_mark_define/fortify + name = "Fortify" + desc = "Fortify this area!" + icon_state = "fortify" + +/datum/xeno_mark_define/weeds + name = "Need Weeds" + desc = "Need weeds here!" + icon_state = "weed" + +/datum/xeno_mark_define/nest + name = "Nest" + desc = "Nest enemies here!" + icon_state = "nest" + +/datum/xeno_mark_define/hosts + name = "Hosts" + desc = "Hosts here!" + icon_state = "hosts" + +/datum/xeno_mark_define/aide + name = "Aide" + desc = "Aide here!" + icon_state = "aide" + +/datum/xeno_mark_define/defend + name = "Defend" + desc = "Defend the hive here!" + icon_state = "defend" + +/datum/xeno_mark_define/danger + name = "Danger Warning" + desc = "Caution, danger here!" + icon_state = "danger" + +/datum/xeno_mark_define/rally + name = "Rally" + desc = "Group up here!" + icon_state = "rally" + +/datum/xeno_mark_define/hold + name = "Hold" + desc = "Hold this area!" + icon_state = "hold" + +/datum/xeno_mark_define/ambush + name = "Ambush" + desc = "Ambush the enemy here!" + icon_state = "ambush" +/datum/xeno_mark_define/attack + name = "Attack" + desc = "Attack the enemy here!" + icon_state = "attack" diff --git a/colonialmarines.dme b/colonialmarines.dme index 293b69c60d1c..cd32d56022ca 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -116,7 +116,7 @@ #include "code\__DEFINES\vv.dm" #include "code\__DEFINES\weapon_stats.dm" #include "code\__DEFINES\weather.dm" -#include "code\__DEFINES\xeno.dm" +#include "code\__DEFINES\xeno_defines.dm" #include "code\__DEFINES\dcs\flags.dm" #include "code\__DEFINES\dcs\helpers.dm" #include "code\__DEFINES\dcs\signals\signals_admin.dm" @@ -1915,6 +1915,7 @@ #include "code\modules\mob\living\carbon\xenomorph\Facehuggers.dm" #include "code\modules\mob\living\carbon\xenomorph\hive_faction.dm" #include "code\modules\mob\living\carbon\xenomorph\hive_status.dm" +#include "code\modules\mob\living\carbon\xenomorph\hive_status_ui.dm" #include "code\modules\mob\living\carbon\xenomorph\life.dm" #include "code\modules\mob\living\carbon\xenomorph\login.dm" #include "code\modules\mob\living\carbon\xenomorph\mark_menu.dm" @@ -1922,8 +1923,9 @@ #include "code\modules\mob\living\carbon\xenomorph\resin_constructions.dm" #include "code\modules\mob\living\carbon\xenomorph\say.dm" #include "code\modules\mob\living\carbon\xenomorph\update_icons.dm" -#include "code\modules\mob\living\carbon\xenomorph\xeno_defines.dm" +#include "code\modules\mob\living\carbon\xenomorph\xeno_client_procs.dm" #include "code\modules\mob\living\carbon\xenomorph\xeno_helpers.dm" +#include "code\modules\mob\living\carbon\xenomorph\xeno_marks.dm" #include "code\modules\mob\living\carbon\xenomorph\xeno_tackle_counter.dm" #include "code\modules\mob\living\carbon\xenomorph\xeno_verbs.dm" #include "code\modules\mob\living\carbon\xenomorph\XenoAttacks.dm" @@ -1989,6 +1991,7 @@ #include "code\modules\mob\living\carbon\xenomorph\castes\Boiler.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Burrower.dm" #include "code\modules\mob\living\carbon\xenomorph\castes\Carrier.dm" +#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\Drone.dm"