diff --git a/code/__DEFINES/dcs/signals/signals.dm b/code/__DEFINES/dcs/signals/signals.dm index b13f9720def45..09f5e157bb96c 100644 --- a/code/__DEFINES/dcs/signals/signals.dm +++ b/code/__DEFINES/dcs/signals/signals.dm @@ -726,6 +726,11 @@ #define COMSIG_XENOMORPH_LEAP_BUMP "xenomorph_leap_bump" //from /mob/living/carbon/xenomorph/bump +// Xeno upgrade chambers +#define COMSIG_UPGRADE_CHAMBER_SURVIVAL "upgrade_chamber_survival" +#define COMSIG_UPGRADE_CHAMBER_ATTACK "upgrade_chamber_attack" +#define COMSIG_UPGRADE_CHAMBER_UTILITY "upgrade_chamber_utility" + //human signals #define COMSIG_CLICK_QUICKEQUIP "click_quickequip" diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 0cd074464345b..187fbc071615f 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -589,12 +589,13 @@ GLOBAL_LIST_INIT(layers_to_offset, list( #define CASTE_STAGGER_RESISTANT (1<<15) //Resistant to some forms of stagger, such as projectiles #define CASTE_HAS_WOUND_MASK (1<<16) //uses an alpha mask for wounded states #define CASTE_EXCLUDE_STRAINS (1<<17) // denotes castes/basetypes that should be excluded from being evoable as a strain +#define CASTE_NO_BIOMASS (1<<18) // Xenos are excluded from getting any biomass // Xeno defines that affect evolution, considering making a new var for these -#define CASTE_LEADER_TYPE (1<<16) //Whether we are a leader type caste, such as the queen, shrike or ?king?, and is affected by queen ban and playtime restrictions -#define CASTE_CANNOT_EVOLVE_IN_CAPTIVITY (1<<17) //Whether we cannot evolve in the research lab -#define CASTE_REQUIRES_FREE_TILE (1<<18) //Whether we require a free tile to evolve -#define CASTE_INSTANT_EVOLUTION (1<<19) //Whether we require no evolution progress to evolve to this caste +#define CASTE_LEADER_TYPE (1<<19) //Whether we are a leader type caste, such as the queen, shrike or ?king?, and is affected by queen ban and playtime restrictions +#define CASTE_CANNOT_EVOLVE_IN_CAPTIVITY (1<<20) //Whether we cannot evolve in the research lab +#define CASTE_REQUIRES_FREE_TILE (1<<21) //Whether we require a free tile to evolve +#define CASTE_INSTANT_EVOLUTION (1<<22) //Whether we require no evolution progress to evolve to this caste #define CASTE_CAN_HOLD_FACEHUGGERS (1<<0) #define CASTE_CAN_BE_QUEEN_HEALED (1<<1) diff --git a/code/__DEFINES/movespeed_modification.dm b/code/__DEFINES/movespeed_modification.dm index b8e4deeb20b35..b72ed7d134eae 100644 --- a/code/__DEFINES/movespeed_modification.dm +++ b/code/__DEFINES/movespeed_modification.dm @@ -32,6 +32,7 @@ #define MOVESPEED_ID_WARLOCK_CHANNELING "WARLOCK_CHANNELING" #define MOVESPEED_ID_XENO_DREAD "DREADXENO" #define MOVESPEED_ID_BEHEMOTH_PRIMAL_WRATH "BEHEMOTH_PRIMAL_WRATH" +#define MOVESPEED_ID_CELERITY_BUFF "UPGRADE_CHAMBER_CELERITY_BUFF" #define MOVESPEED_ID_PRAETORIAN_DANCER_DODGE_SPEED "PRAETORIAN_DANCER_DODGE_SPEED" #define MOVESPEED_ID_SIMPLEMOB_VARSPEED "SIMPLEMOB_VARSPEED_MODIFIER" diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index 64fb57c8ba1af..dead66ff61e43 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -39,6 +39,24 @@ #define STATUS_EFFECT_DRAIN_SURGE /datum/status_effect/drain_surge +#define STATUS_EFFECT_UPGRADE_CARAPACE /datum/status_effect/mutation_upgrade/carapace + +#define STATUS_EFFECT_UPGRADE_REGENERATION /datum/status_effect/mutation_upgrade/regeneration + +#define STATUS_EFFECT_UPGRADE_VAMPIRISM /datum/status_effect/mutation_upgrade/vampirism + +#define STATUS_EFFECT_UPGRADE_CELERITY /datum/status_effect/mutation_upgrade/celerity + +#define STATUS_EFFECT_UPGRADE_ADRENALINE /datum/status_effect/mutation_upgrade/adrenaline + +#define STATUS_EFFECT_UPGRADE_CRUSH /datum/status_effect/mutation_upgrade/crush + +#define STATUS_EFFECT_UPGRADE_TOXIN /datum/status_effect/mutation_upgrade/toxin + +#define STATUS_EFFECT_UPGRADE_PHERO /datum/status_effect/mutation_upgrade/pheromones + +#define STATUS_EFFECT_UPGRADE_TRAIL /datum/status_effect/mutation_upgrade/trail + #define STATUS_EFFECT_MINDMEND /datum/status_effect/mindmeld #define STATUS_EFFECT_REKNIT_FORM /datum/status_effect/reknit_form diff --git a/code/__DEFINES/xeno.dm b/code/__DEFINES/xeno.dm index 87a0bec6b1ff7..b2138bf600d50 100644 --- a/code/__DEFINES/xeno.dm +++ b/code/__DEFINES/xeno.dm @@ -92,6 +92,14 @@ GLOBAL_LIST_INIT(defiler_toxin_type_list, list( /datum/reagent/toxin/xeno_ozelomelyn, )) +//List of Defiler toxin images +GLOBAL_LIST_INIT(defiler_toxin_images_list, list( + DEFILER_OZELOMELYN = image('icons/Xeno/actions/defiler.dmi', icon_state = DEFILER_OZELOMELYN), + DEFILER_HEMODILE = image('icons/Xeno/actions/defiler.dmi', icon_state = DEFILER_HEMODILE), + DEFILER_TRANSVITOX = image('icons/Xeno/actions/defiler.dmi', icon_state = DEFILER_TRANSVITOX), + DEFILER_NEUROTOXIN = image('icons/Xeno/actions/defiler.dmi', icon_state = DEFILER_NEUROTOXIN) +)) + //List of toxins improving defile's damage GLOBAL_LIST_INIT(defiler_toxins_typecache_list, typecacheof(list( /datum/reagent/toxin/xeno_ozelomelyn, @@ -210,3 +218,31 @@ GLOBAL_LIST_INIT(xeno_ai_spawnable, list( /// Life runs every 2 seconds, but we don't want to multiply all healing by 2 due to seconds_per_tick #define XENO_PER_SECOND_LIFE_MOD 0.5 + +// Mutations +GLOBAL_DATUM_INIT(mutation_selector, /datum/mutation_datum, new) + +GLOBAL_LIST_INIT(mutation_upgrades_buyable, list( + /datum/mutation_upgrade/survival/carapace, + /datum/mutation_upgrade/survival/regeneration, + /datum/mutation_upgrade/survival/vampirism, + /datum/mutation_upgrade/attack/celerity, + /datum/mutation_upgrade/attack/adrenaline, + /datum/mutation_upgrade/attack/crush, + /datum/mutation_upgrade/utility/toxin, + /datum/mutation_upgrade/utility/pheromones, + /datum/mutation_upgrade/utility/trail +)) + +#define MUTATION_CATEGORY_SURVIVAL "Survival" +#define MUTATION_CATEGORY_ATTACK "Attack" +#define MUTATION_CATEGORY_UTILITY "Utility" + +#define MUTATION_STRUCTURE_CHAMBER "shell" +#define MUTATION_STRUCTURE_SPUR "spur" +#define MUTATION_STRUCTURE_VEIL "veil" + +#define XENO_UPGRADE_BIOMASS_COST_T1 15 +#define XENO_UPGRADE_BIOMASS_COST_T2 20 +#define XENO_UPGRADE_BIOMASS_COST_T3 25 +#define XENO_UPGRADE_BIOMASS_COST_T4 30 diff --git a/code/datums/status_effects/xeno_buffs.dm b/code/datums/status_effects/xeno_buffs.dm index 696be4748dbfe..5232216ef5c1b 100644 --- a/code/datums/status_effects/xeno_buffs.dm +++ b/code/datums/status_effects/xeno_buffs.dm @@ -871,3 +871,469 @@ xeno_owner.xeno_melee_damage_modifier -= modifier xeno_owner.remove_filter("frenzy_screech_outline") return ..() + +// *************************************** +// *********** Upgrade Chambers Buffs +// *************************************** +/datum/status_effect/mutation_upgrade + /// Owner typed as an xenomorph. + var/mob/living/carbon/xenomorph/buff_owner + /// The structure to base chamber_scaling on. + var/chamber_structure + /// The amount of times to multiply the main effect by. + var/chamber_scaling = 0 + +/datum/status_effect/mutation_upgrade/on_apply() + if(!isxeno(owner)) + return FALSE + buff_owner = owner + update_chamber_scaling() + return TRUE + +/// Sets the chamber_scaling based on the amount of chambers in the xeno's hive. +/datum/status_effect/mutation_upgrade/proc/update_chamber_scaling() + switch(chamber_structure) + if(MUTATION_STRUCTURE_CHAMBER) + chamber_scaling = length(buff_owner.hive?.shell_chambers) + if(MUTATION_STRUCTURE_SPUR) + chamber_scaling = length(buff_owner.hive?.spur_chambers) + if(MUTATION_STRUCTURE_VEIL) + chamber_scaling = length(buff_owner.hive?.veil_chambers) + +// *************************************** +// *********** Upgrade Chambers Buffs - Survival +// *************************************** +/atom/movable/screen/alert/status_effect/carapace + name = "Carapace" + desc = "Armor increased." + icon_state = "xenobuff_carapace" + +/datum/status_effect/mutation_upgrade/carapace + id = "mutation_upgrade_carapace" + alert_type = /atom/movable/screen/alert/status_effect/carapace + chamber_structure = MUTATION_STRUCTURE_CHAMBER + /// The amount of soft armor given. + var/armor_buff_per_chamber = 2.5 + +/datum/status_effect/mutation_upgrade/carapace/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_SURVIVAL, PROC_REF(update_buff)) + buff_owner.soft_armor = buff_owner.soft_armor.modifyAllRatings(armor_buff_per_chamber * chamber_scaling) + return TRUE + +/datum/status_effect/mutation_upgrade/carapace/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_SURVIVAL) + buff_owner.soft_armor = buff_owner.soft_armor.modifyAllRatings(-armor_buff_per_chamber * chamber_scaling) + return ..() + +/// Sets the chamber_scaling to the amount of active survival chambers and adjusts soft armor accordingly. +/datum/status_effect/mutation_upgrade/carapace/proc/update_buff() + SIGNAL_HANDLER + buff_owner.soft_armor = buff_owner.soft_armor.modifyAllRatings(armor_buff_per_chamber * (length(buff_owner.hive.shell_chambers) - chamber_scaling)) + update_chamber_scaling() + + +// *************************************** +// *************************************** +// *************************************** +/atom/movable/screen/alert/status_effect/regeneration + name = "Regeneration" + desc = "Regeneration increased." + icon_state = "xenobuff_regeneration" + +/datum/status_effect/mutation_upgrade/regeneration + id = "mutation_upgrade_regeneration" + alert_type = /atom/movable/screen/alert/status_effect/regeneration + chamber_structure = MUTATION_STRUCTURE_CHAMBER + /// The amount of max health to be regenerated everytime the proc 'heal_wounds' is called. + var/health_regen_per_chamber = 0.008 // 0.8% + /// The amount of sunder to be regenerated everytime the proc 'heal_wounds' is called. + var/sunder_regen_per_chamber = 0.166 + +/datum/status_effect/mutation_upgrade/regeneration/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_SURVIVAL, PROC_REF(update_buff)) + RegisterSignal(buff_owner, COMSIG_XENOMORPH_HEALTH_REGEN, PROC_REF(on_heal_wounds)) + return TRUE + +/datum/status_effect/mutation_upgrade/regeneration/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_SURVIVAL) + UnregisterSignal(buff_owner, COMSIG_XENOMORPH_HEALTH_REGEN) + return ..() + +/// Sets the chamber_scaling to the amount of active survival chambers. +/datum/status_effect/mutation_upgrade/regeneration/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + +/// Heals the xenomorph's health and sunder based on assigned variables. +/datum/status_effect/mutation_upgrade/regeneration/proc/on_heal_wounds(mob/living/carbon/xenomorph/source_xenomorph, heal_data, seconds_per_tick) + SIGNAL_HANDLER + if(!chamber_scaling) + return + var/health_amount = buff_owner.maxHealth * health_regen_per_chamber * chamber_scaling + var/sunder_amount = -sunder_regen_per_chamber * chamber_scaling + HEAL_XENO_DAMAGE(buff_owner, health_amount, FALSE) + buff_owner.adjust_sunder(sunder_amount) + buff_owner.updatehealth() + +// *************************************** +// *************************************** +// *************************************** +/atom/movable/screen/alert/status_effect/vampirism + name = "Vampirism" + desc = "Leech from attacks." + icon_state = "xenobuff_vampirism" + +/datum/status_effect/mutation_upgrade/vampirism + id = "mutation_upgrade_vampirism" + alert_type = /atom/movable/screen/alert/status_effect/vampirism + chamber_structure = MUTATION_STRUCTURE_CHAMBER + /// The amount of max health to be regenerated the owner hits an alive human. + var/leech_buff_per_chamber = 0.016 + +/datum/status_effect/mutation_upgrade/vampirism/on_apply() + if(!..()) + return FALSE + // Ravagers gets half of the effect since they eventually get their own version of vampirism. + if(isxenoravager(buff_owner)) + chamber_scaling /= 2 + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_SURVIVAL, PROC_REF(update_buff)) + RegisterSignal(buff_owner, COMSIG_XENOMORPH_ATTACK_LIVING, PROC_REF(on_slash)) + return TRUE + +/datum/status_effect/mutation_upgrade/vampirism/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_SURVIVAL) + UnregisterSignal(buff_owner, COMSIG_XENOMORPH_ATTACK_LIVING) + return ..() + +/// Sets the chamber_scaling to the amount of active survival chambers. +/datum/status_effect/mutation_upgrade/vampirism/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + if(isxenoravager(buff_owner)) + chamber_scaling /= 2 + +/// Heals the xenomorph for hitting a non-dead human by a percentage of their max health. +/datum/status_effect/mutation_upgrade/vampirism/proc/on_slash(datum/source, mob/living/target) + SIGNAL_HANDLER + if(target.stat == DEAD || !ishuman(target)) + return + var/health_amount = buff_owner.maxHealth * leech_buff_per_chamber * chamber_scaling + HEAL_XENO_DAMAGE(buff_owner, health_amount, FALSE) + buff_owner.updatehealth() + +// *************************************** +// *********** Upgrade Chambers Buffs - Attack +// *************************************** +/atom/movable/screen/alert/status_effect/celerity + name = "Celerity" + desc = "Run faster." + icon_state = "xenobuff_attack" + +/datum/status_effect/mutation_upgrade/celerity + id = "mutation_upgrade_celerity" + alert_type = /atom/movable/screen/alert/status_effect/celerity + chamber_structure = MUTATION_STRUCTURE_SPUR + /// The amount of movement speed the owner get. + var/speed_buff_per_chamber = 0.05 + +/datum/status_effect/mutation_upgrade/celerity/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_ATTACK, PROC_REF(update_buff)) + buff_owner.add_movespeed_modifier(MOVESPEED_ID_CELERITY_BUFF, TRUE, 0, NONE, TRUE, -speed_buff_per_chamber * chamber_scaling) + return TRUE + +/datum/status_effect/mutation_upgrade/celerity/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_ATTACK) + buff_owner.remove_movespeed_modifier(MOVESPEED_ID_CELERITY_BUFF) + return ..() + +/// Sets the chamber_scaling to the amount of active spur chambers and adjusts movement speed accordingly. +/datum/status_effect/mutation_upgrade/celerity/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + buff_owner.add_movespeed_modifier(MOVESPEED_ID_CELERITY_BUFF, TRUE, 0, NONE, TRUE, -speed_buff_per_chamber * chamber_scaling) + +// *************************************** +// *************************************** +// *************************************** +/atom/movable/screen/alert/status_effect/adrenaline + name = "Adrenaline" + desc = "Regenerate plasma." + icon_state = "xenobuff_attack" + +/datum/status_effect/mutation_upgrade/adrenaline + id = "mutation_upgrade_adrenaline" + alert_type = /atom/movable/screen/alert/status_effect/adrenaline + chamber_structure = MUTATION_STRUCTURE_SPUR + /// The amount of plasma to regenerate based on their caste's plasma regeneration. + var/plasma_regen_buff_per_chamber = 0.1 // 10% + /// The amount of plasma to regenerate based on their caste's maximum plasma. + var/plasma_percentage_buff_per_chamber = 0.01 // 1% + +/datum/status_effect/mutation_upgrade/adrenaline/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_ATTACK, PROC_REF(update_buff)) + RegisterSignal(buff_owner, COMSIG_XENOMORPH_PLASMA_REGEN, PROC_REF(on_plasma_regen)) + return TRUE + +/datum/status_effect/mutation_upgrade/adrenaline/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_ATTACK) + UnregisterSignal(buff_owner, COMSIG_XENOMORPH_PLASMA_REGEN) + return ..() + +/// Sets the chamber_scaling to the amount of active spur chambers. +/datum/status_effect/mutation_upgrade/adrenaline/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + +/// Gives the xenomorph more plasma (according to their plasma regeneration and adjusted maximum) everytime they are suppose to regen plasma. +/datum/status_effect/mutation_upgrade/adrenaline/proc/on_plasma_regen(mob/living/carbon/xenomorph/source_xenomorph, plasma_mod, seconds_per_tick) + SIGNAL_HANDLER + if(!chamber_scaling) + return + var/adjusted_plasma_max = buff_owner.xeno_caste.plasma_max * buff_owner.xeno_caste.plasma_regen_limit + var/plasma_regen_amount = buff_owner.xeno_caste.plasma_gain * plasma_regen_buff_per_chamber * chamber_scaling + var/plasma_max_amount = adjusted_plasma_max * plasma_percentage_buff_per_chamber * chamber_scaling + var/plasma_to_give = plasma_regen_amount + plasma_max_amount * ((buff_owner.resting || buff_owner.lying_angle) ? 2 : 1) + var/plasma_stored_predicted = (buff_owner.plasma_stored + plasma_to_give) + // Give only enough plasma to reach the adjusted amount (for castes like Hivelord). + buff_owner.gain_plasma(plasma_stored_predicted > adjusted_plasma_max ? plasma_stored_predicted - adjusted_plasma_max : plasma_to_give) + +// *************************************** +// *************************************** +// *************************************** +/atom/movable/screen/alert/status_effect/crush + name = "Crush" + desc = "Additional damage to objects." + icon_state = "xenobuff_attack" + +/datum/status_effect/mutation_upgrade/crush + id = "mutation_upgrade_crush" + alert_type = /atom/movable/screen/alert/status_effect/crush + chamber_structure = MUTATION_STRUCTURE_SPUR + /// The bonus damage to deal as percentage. + var/damage_buff_per_chamber = 0.333 // 33.3% + /// The armour pentration the damage damage has. + var/penetration_buff_per_chamber = 10 + +/datum/status_effect/mutation_upgrade/crush/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_ATTACK, PROC_REF(update_buff)) + RegisterSignal(buff_owner, COMSIG_XENOMORPH_ATTACK_OBJ, PROC_REF(on_obj_attack)) + return TRUE + +/datum/status_effect/mutation_upgrade/crush/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_ATTACK) + UnregisterSignal(buff_owner, COMSIG_XENOMORPH_ATTACK_OBJ) + return ..() + +/// Sets the chamber_scaling to the amount of active spur chambers. +/datum/status_effect/mutation_upgrade/crush/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + +/// Deals bonus damage along with armour pentration to the object. +/datum/status_effect/mutation_upgrade/crush/proc/on_obj_attack(datum/source, obj/attacked) + SIGNAL_HANDLER + if(!chamber_scaling) + return + if(attacked.resistance_flags & XENO_DAMAGEABLE) + var/adjusted_armour_penetration = penetration_buff_per_chamber * chamber_scaling + var/adjusted_damage = buff_owner.xeno_caste.melee_damage * damage_buff_per_chamber * chamber_scaling + var/expected_damage = round(attacked.modify_by_armor(adjusted_damage, MELEE, adjusted_armour_penetration, null, null), DAMAGE_PRECISION) + var/expected_integrity = max(attacked.obj_integrity - expected_damage, 0) + // Since this signal is called before they actually attack themselves, we do just enough so the main attack does the finishing blow. + if(!expected_integrity) + attacked.take_damage(expected_integrity - 0.1, BRUTE, MELEE, FALSE, armour_penetration = 100) + return + attacked.take_damage(buff_owner.xeno_caste.melee_damage * damage_buff_per_chamber * chamber_scaling, BRUTE, MELEE, FALSE, armour_penetration = (penetration_buff_per_chamber * chamber_scaling)) + +// *************************************** +// *********** Upgrade Chambers Buffs - Utility +// *************************************** +/atom/movable/screen/alert/status_effect/toxin + name = "Toxin" + desc = "Inject toxin on attack. Click to change toxin." + icon_state = "xenobuff_generic" + +/atom/movable/screen/alert/status_effect/toxin/Click() + var/datum/status_effect/mutation_upgrade/toxin/status_effect = attached_effect + var/datum/reagent/toxin/toxin_choice = show_radial_menu(status_effect.buff_owner, status_effect.buff_owner, GLOB.defiler_toxin_images_list, radius = 35, require_near = TRUE) + if(!toxin_choice) + return + for(var/defiler_toxin in GLOB.defiler_toxin_type_list) + var/datum/reagent/reagent = GLOB.chemical_reagents_list[defiler_toxin] + if(reagent.name == toxin_choice) + status_effect.selected_reagent = reagent.type + break + status_effect.buff_owner.balloon_alert(status_effect.buff_owner, "[toxin_choice]") + +/datum/status_effect/mutation_upgrade/toxin + id = "mutation_upgrade_toxin" + alert_type = /atom/movable/screen/alert/status_effect/toxin + chamber_structure = MUTATION_STRUCTURE_VEIL + /// Currently selected reagent to inject. + var/datum/reagent/toxin/selected_reagent = /datum/reagent/toxin/xeno_neurotoxin + +/datum/status_effect/mutation_upgrade/toxin/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_UTILITY, PROC_REF(update_buff)) + RegisterSignal(buff_owner, COMSIG_XENOMORPH_ATTACK_LIVING, PROC_REF(on_slash)) + return TRUE + +/datum/status_effect/mutation_upgrade/toxin/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_UTILITY) + UnregisterSignal(buff_owner, COMSIG_XENOMORPH_ATTACK_LIVING) + return ..() + +/// Sets the chamber_scaling to the amount of active veil chambers. +/datum/status_effect/mutation_upgrade/toxin/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + +/// Injects the human target with a variable amount of the selected reagent. +/datum/status_effect/mutation_upgrade/toxin/proc/on_slash(datum/source, mob/living/target) + SIGNAL_HANDLER + if(!chamber_scaling || target.stat == DEAD || !ishuman(target) || !target.can_sting()) + return + var/datum/reagent/glob_reagent = GLOB.chemical_reagents_list[selected_reagent] + if(!glob_reagent) + return + var/mob/living/carbon/human/human_target = target + human_target.reagents.add_reagent(selected_reagent, glob_reagent.custom_metabolism * chamber_scaling) + +// *************************************** +// *************************************** +// *************************************** +/atom/movable/screen/alert/status_effect/pheromones + name = "Pheromones" + desc = "Allows to emit pheromones. Click to change pheromones." + icon_state = "xenobuff_phero" + +/atom/movable/screen/alert/status_effect/pheromones/Click() + var/datum/status_effect/mutation_upgrade/pheromones/status_effect = attached_effect + var/phero_choice = show_radial_menu(status_effect.buff_owner, status_effect.buff_owner, GLOB.pheromone_images_list, radius = 35, require_near = TRUE) + if(!phero_choice) + return + QDEL_NULL(status_effect.current_aura) + status_effect.emitted_aura = phero_choice + status_effect.create_aura() + +/datum/status_effect/mutation_upgrade/pheromones + id = "mutation_upgrade_pheromones" + alert_type = /atom/movable/screen/alert/status_effect/pheromones + chamber_structure = MUTATION_STRUCTURE_VEIL + /// The aura. + var/datum/aura_bearer/current_aura + /// The aura to emit. + var/emitted_aura = AURA_XENO_RECOVERY + /// The initial value of the aura's power. + var/aura_power_base = 1 + /// The phero power to increase by. + var/aura_power_per_chamber = 1 + +/datum/status_effect/mutation_upgrade/pheromones/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_UTILITY, PROC_REF(update_buff)) + create_aura() + return TRUE + +/datum/status_effect/mutation_upgrade/pheromones/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_UTILITY) + if(current_aura) + current_aura.stop_emitting() + return ..() + +/// (Re)creates the aura. +/datum/status_effect/mutation_upgrade/pheromones/proc/create_aura() + if(current_aura) + QDEL_NULL(current_aura) + if(chamber_scaling) + current_aura = SSaura.add_emitter(buff_owner, emitted_aura, 6 + aura_power_per_chamber * chamber_scaling * 2, aura_power_base + aura_power_per_chamber * chamber_scaling, -1, FACTION_XENO, buff_owner.hivenumber) + +/// Sets the chamber_scaling to the amount of active veil chambers and recreates the aura. +/datum/status_effect/mutation_upgrade/pheromones/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + create_aura() + +// *************************************** +// *************************************** +// *************************************** +/atom/movable/screen/alert/status_effect/trail + name = "Trail" + desc = "We leave a trail behind. Click to change trail." + icon_state = "xenobuff_generic" + +/atom/movable/screen/alert/status_effect/trail/Click() + var/datum/status_effect/mutation_upgrade/trail/effect = attached_effect + if(effect.buff_owner.incapacitated()) + to_chat(usr, span_warning("Cant do that right now!")) + return + var/i = effect.selectable_trails.Find(effect.selected_trail) + if(length(effect.selectable_trails) == i) + effect.selected_trail = effect.selectable_trails[1] + else + effect.selected_trail = effect.selectable_trails[i+1] + effect.buff_owner.balloon_alert(effect.buff_owner, "[effect.selected_trail.name]") + +/datum/status_effect/mutation_upgrade/trail + id = "mutation_upgrade_trail" + alert_type = /atom/movable/screen/alert/status_effect/trail + chamber_structure = MUTATION_STRUCTURE_VEIL + /// The initial odds of the trail spawning. + var/base_chance = 25 + /// The additional chance of the trail starting. + var/chance_per_chamber = 25 + /// The selected trail that will spawn upon moving. + var/obj/selected_trail = /obj/effect/xenomorph/spray + /// A list of trails that can be selected + var/list/selectable_trails = list( + /obj/effect/xenomorph/spray, + /obj/alien/resin/sticky/thin/temporary + ) + +/datum/status_effect/mutation_upgrade/trail/on_apply() + if(!..()) + return FALSE + RegisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_UTILITY, PROC_REF(update_buff)) + RegisterSignal(buff_owner, COMSIG_MOVABLE_MOVED, PROC_REF(create_trail)) + return TRUE + +/datum/status_effect/mutation_upgrade/trail/on_remove() + UnregisterSignal(SSdcs, COMSIG_UPGRADE_CHAMBER_UTILITY) + UnregisterSignal(buff_owner, COMSIG_MOVABLE_MOVED) + return ..() + +/// Sets the chamber_scaling to the amount of active veil chambers. +/datum/status_effect/mutation_upgrade/trail/proc/update_buff() + SIGNAL_HANDLER + update_chamber_scaling() + +/// Rolls the dice and creates the selected trail if the dice is right. +/datum/status_effect/mutation_upgrade/trail/proc/create_trail() + SIGNAL_HANDLER + if(buff_owner.incapacitated() || buff_owner.lying_angle || buff_owner.resting || !chamber_scaling) + return + if(prob(base_chance + (chance_per_chamber * chamber_scaling))) + var/turf/T = get_turf(buff_owner) + if(T.density || isspaceturf(T)) + return + for(var/obj/O in T.contents) + if(is_type_in_typecache(O, GLOB.no_sticky_resin)) + return + if(selected_trail == /obj/effect/xenomorph/spray) + new selected_trail(T, rand(2 SECONDS, 5 SECONDS)) + for(var/obj/O in T) + O.acid_spray_act(buff_owner) + else + new selected_trail(T) diff --git a/code/game/objects/items/cocoon.dm b/code/game/objects/items/cocoon.dm index 1073e7361a96c..4246dda1c0e7c 100644 --- a/code/game/objects/items/cocoon.dm +++ b/code/game/objects/items/cocoon.dm @@ -47,6 +47,14 @@ //Gives marine cloneloss for a total of 30. victim.adjustCloneLoss(0.5) + // Slowly give biomass over time up to 9 biomass (assuming the time based on comment above is correct). + for(var/mob/living/carbon/xenomorph/xeno in GLOB.alive_xeno_list) + if(xeno.hivenumber != hivenumber) + continue + if(xeno.xeno_caste.caste_flags & CASTE_NO_BIOMASS) + continue + xeno.gain_biomass(0.1) + /obj/structure/cocoon/take_damage(damage_amount, damage_type = BRUTE, armor_type = null, effects = TRUE, attack_dir, armour_penetration = 0, mob/living/blame_mob) . = ..() if(anchored && obj_integrity < max_integrity / 2) @@ -71,6 +79,14 @@ var/datum/hive_status/hive_status = GLOB.hive_datums[hivenumber] hive_status.update_tier_limits() GLOB.round_statistics.larva_from_cocoon += larva_point_reward / xeno_job.job_points_needed + // 3.5 biomass to all xenomorphs. + for(var/mob/living/carbon/xenomorph/xeno in GLOB.alive_xeno_list) + if(xeno.hivenumber != hivenumber) + continue + if(xeno.xeno_caste.caste_flags & CASTE_NO_BIOMASS) + continue + xeno.gain_biomass(4) + release_victim() update_icon() diff --git a/code/game/objects/structures/xeno.dm b/code/game/objects/structures/xeno.dm index 1d6f768981852..7a582b6a70fda 100644 --- a/code/game/objects/structures/xeno.dm +++ b/code/game/objects/structures/xeno.dm @@ -145,6 +145,10 @@ ignore_weed_destruction = FALSE refundable = FALSE +/obj/alien/resin/sticky/thin/temporary/Initialize(mapload) + . = ..() + addtimer(CALLBACK(src, PROC_REF(obj_destruction), MELEE), 3 SECONDS) + //Resin Doors /obj/structure/mineral_door/resin name = RESIN_DOOR diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 10c85e8c91ba9..a07f2a37e304e 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -902,6 +902,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) log_game("[key_name(usr)] has joined Valhalla as a Xenomorph.") var/mob/living/carbon/xenomorph/new_xeno = new xeno_choice(pick(GLOB.spawns_by_job[/datum/job/fallen/xenomorph])) new_xeno.transfer_to_hive(XENO_HIVE_FALLEN) + new_xeno.gain_biomass(100) // So they can test it out here. ADD_TRAIT(new_xeno, TRAIT_VALHALLA_XENO, VALHALLA_TRAIT) var/datum/job/xallhala_job = SSjob.GetJobType(/datum/job/fallen/xenomorph) new_xeno.apply_assigned_role_to_spawn(xallhala_job) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities.dm index 51c774ef65351..f4e60943a99ee 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities.dm @@ -1399,6 +1399,21 @@ victim.do_jitter_animation(2) victim.adjustCloneLoss(20) + var/list/mob/living/carbon/xenomorph/nearby_friendly_xenos = list() + for(var/mob/living/carbon/xenomorph/nearby_xeno AS in cheap_get_xenos_near(X, 7)) + // Intentionally includes the owner. + if(!X.issamexenohive(nearby_xeno)) + continue + if(nearby_xeno.xeno_caste.caste_flags & CASTE_NO_BIOMASS) + continue + nearby_friendly_xenos += nearby_xeno + + if(length(nearby_friendly_xenos)) + // Split the reward amongst every xeno nearby, including themselves. + var/biomass_per_xeno = round(15/length(nearby_friendly_xenos), 0.1) + for(var/mob/living/carbon/xenomorph/nearby_friendly_xeno AS in nearby_friendly_xenos) + nearby_friendly_xeno.gain_biomass(biomass_per_xeno) + ADD_TRAIT(victim, TRAIT_PSY_DRAINED, TRAIT_PSY_DRAINED) if(HAS_TRAIT(victim, TRAIT_UNDEFIBBABLE)) victim.med_hud_set_status() @@ -1509,6 +1524,7 @@ victim.dead_ticks = 0 ADD_TRAIT(victim, TRAIT_STASIS, TRAIT_STASIS) X.eject_victim(TRUE, starting_turf) + if(owner.client) var/datum/personal_statistics/personal_statistics = GLOB.personal_statistics_list[owner.ckey] personal_statistics.cocooned++ diff --git a/code/modules/mob/living/carbon/xenomorph/castes/baneling/castedatum_baneling.dm b/code/modules/mob/living/carbon/xenomorph/castes/baneling/castedatum_baneling.dm index 705d0a4fc4850..6791f5c3ebc2c 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/baneling/castedatum_baneling.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/baneling/castedatum_baneling.dm @@ -24,7 +24,7 @@ max_health = 100 // *** Flags *** // - caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION + caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION|CASTE_NO_BIOMASS can_flags = CASTE_CAN_BE_QUEEN_HEALED caste_traits = null diff --git a/code/modules/mob/living/carbon/xenomorph/castes/beetle/castedatum_beetle.dm b/code/modules/mob/living/carbon/xenomorph/castes/beetle/castedatum_beetle.dm index 94dc092dc0630..7e2524b3cdb30 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/beetle/castedatum_beetle.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/beetle/castedatum_beetle.dm @@ -24,7 +24,7 @@ max_health = 260 // *** Flags *** // - caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION + caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION|CASTE_NO_BIOMASS can_flags = CASTE_CAN_BE_QUEEN_HEALED|CASTE_CAN_RIDE_CRUSHER caste_traits = null diff --git a/code/modules/mob/living/carbon/xenomorph/castes/hivemind/castedatum_hivemind.dm b/code/modules/mob/living/carbon/xenomorph/castes/hivemind/castedatum_hivemind.dm index 32fff1412c34f..0bfe11a00de6c 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/hivemind/castedatum_hivemind.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/hivemind/castedatum_hivemind.dm @@ -22,7 +22,7 @@ maximum_active_caste = 1 // *** Flags *** // - caste_flags = CASTE_INNATE_PLASMA_REGEN|CASTE_FIRE_IMMUNE|CASTE_IS_BUILDER|CASTE_DO_NOT_ALERT_LOW_LIFE + caste_flags = CASTE_INNATE_PLASMA_REGEN|CASTE_FIRE_IMMUNE|CASTE_IS_BUILDER|CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_NO_BIOMASS can_flags = CASTE_CAN_BE_QUEEN_HEALED|CASTE_CAN_BE_GIVEN_PLASMA caste_traits = null diff --git a/code/modules/mob/living/carbon/xenomorph/castes/mantis/castedatum_mantis.dm b/code/modules/mob/living/carbon/xenomorph/castes/mantis/castedatum_mantis.dm index 190fa40955b4b..bff0c53574e43 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/mantis/castedatum_mantis.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/mantis/castedatum_mantis.dm @@ -25,7 +25,7 @@ max_health = 150 // *** Flags *** // - caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION + caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION|CASTE_NO_BIOMASS can_flags = CASTE_CAN_BE_QUEEN_HEALED|CASTE_CAN_BE_GIVEN_PLASMA|CASTE_CAN_RIDE_CRUSHER caste_traits = null diff --git a/code/modules/mob/living/carbon/xenomorph/castes/nymph/castedatum_nymph.dm b/code/modules/mob/living/carbon/xenomorph/castes/nymph/castedatum_nymph.dm index ad381f39c107f..de97402bcd3a8 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/nymph/castedatum_nymph.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/nymph/castedatum_nymph.dm @@ -24,7 +24,7 @@ max_health = 120 // *** Flags *** // - caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION|CASTE_IS_BUILDER + caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION|CASTE_IS_BUILDER|CASTE_NO_BIOMASS can_flags = CASTE_CAN_BE_QUEEN_HEALED|CASTE_CAN_BE_GIVEN_PLASMA // *** Defense *** // diff --git a/code/modules/mob/living/carbon/xenomorph/castes/puppet/castedatum_puppet.dm b/code/modules/mob/living/carbon/xenomorph/castes/puppet/castedatum_puppet.dm index 6840b7fb279cd..afa3aace7a811 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/puppet/castedatum_puppet.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/puppet/castedatum_puppet.dm @@ -15,7 +15,7 @@ plasma_max = 2 plasma_gain = 0 max_health = 250 - caste_flags = CASTE_NOT_IN_BIOSCAN|CASTE_DO_NOT_ANNOUNCE_DEATH|CASTE_DO_NOT_ALERT_LOW_LIFE + caste_flags = CASTE_NOT_IN_BIOSCAN|CASTE_DO_NOT_ANNOUNCE_DEATH|CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_NO_BIOMASS minimap_icon = "puppet" soft_armor = list(MELEE = 14, BULLET = 3, LASER = 5, ENERGY = 3, BOMB = 0, BIO = 0, FIRE = 0, ACID = 0) diff --git a/code/modules/mob/living/carbon/xenomorph/castes/scorpion/castedatum_scorpion.dm b/code/modules/mob/living/carbon/xenomorph/castes/scorpion/castedatum_scorpion.dm index 42102310c5e56..66ef28d226b42 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/scorpion/castedatum_scorpion.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/scorpion/castedatum_scorpion.dm @@ -24,7 +24,7 @@ max_health = 130 // *** Flags *** // - caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION + caste_flags = CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_A_MINION|CASTE_NO_BIOMASS can_flags = CASTE_CAN_BE_QUEEN_HEALED|CASTE_CAN_BE_GIVEN_PLASMA|CASTE_CAN_RIDE_CRUSHER caste_traits = null diff --git a/code/modules/mob/living/carbon/xenomorph/castes/spiderling/castedatum_spiderling.dm b/code/modules/mob/living/carbon/xenomorph/castes/spiderling/castedatum_spiderling.dm index 45a9b323f78d3..fb786ef7ebe0c 100644 --- a/code/modules/mob/living/carbon/xenomorph/castes/spiderling/castedatum_spiderling.dm +++ b/code/modules/mob/living/carbon/xenomorph/castes/spiderling/castedatum_spiderling.dm @@ -25,7 +25,7 @@ max_health = 125 // *** Flags *** // - caste_flags = CASTE_NOT_IN_BIOSCAN|CASTE_DO_NOT_ANNOUNCE_DEATH|CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_BUILDER + caste_flags = CASTE_NOT_IN_BIOSCAN|CASTE_DO_NOT_ANNOUNCE_DEATH|CASTE_DO_NOT_ALERT_LOW_LIFE|CASTE_IS_BUILDER|CASTE_NO_BIOMASS // *** Minimap Icon *** // minimap_icon = "spiderling" diff --git a/code/modules/mob/living/carbon/xenomorph/evolution.dm b/code/modules/mob/living/carbon/xenomorph/evolution.dm index 6cfccda4b9fb6..06b4f9a38a7af 100644 --- a/code/modules/mob/living/carbon/xenomorph/evolution.dm +++ b/code/modules/mob/living/carbon/xenomorph/evolution.dm @@ -173,6 +173,9 @@ new_xeno.hive?.update_ruler() // Since ruler wasn't set during initialization, update ruler now. transfer_observers_to(new_xeno) + // Carry over the biomass + new_xeno.biomass = biomass + if(new_xeno.health - getBruteLoss(src) - getFireLoss(src) > 0) //Cmon, don't kill the new one! Shouldnt be possible though new_xeno.bruteloss = bruteloss //Transfers the damage over. new_xeno.fireloss = fireloss //Transfers the damage over. diff --git a/code/modules/mob/living/carbon/xenomorph/hive_datum.dm b/code/modules/mob/living/carbon/xenomorph/hive_datum.dm index 2accf2293d193..febaf5a85c8c0 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_datum.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_datum.dm @@ -24,6 +24,13 @@ var/list/obj/structure/xeno/pherotower/pherotowers = list() ///list of hivemind cores var/list/obj/structure/xeno/hivemindcore/hivemindcores = list() + /// List of shell upgrade chambers. + var/list/obj/structure/xeno/upgrade_chamber/shell/shell_chambers = list() + /// List of spur upgrade chambers. + var/list/obj/structure/xeno/upgrade_chamber/spur/spur_chambers = list() + /// List of veil upgrade chambers. + var/list/obj/structure/xeno/upgrade_chamber/veil/veil_chambers = list() + var/tier3_xeno_limit var/tier2_xeno_limit /// Queue of all clients wanting to join xeno side @@ -122,6 +129,13 @@ // Pheromone towers for(var/obj/structure/xeno/pherotower/tower AS in GLOB.hive_datums[hivenumber].pherotowers) .["hive_structures"] += list(get_structure_packet(tower)) + // Upgrade chambers + for(var/obj/structure/xeno/upgrade_chamber/shell/chamber AS in GLOB.hive_datums[hivenumber].shell_chambers) + .["hive_structures"] += list(get_structure_packet(chamber)) + for(var/obj/structure/xeno/upgrade_chamber/spur/chamber AS in GLOB.hive_datums[hivenumber].spur_chambers) + .["hive_structures"] += list(get_structure_packet(chamber)) + for(var/obj/structure/xeno/upgrade_chamber/veil/chamber AS in GLOB.hive_datums[hivenumber].veil_chambers) + .["hive_structures"] += list(get_structure_packet(chamber)) // Hivemind cores for(var/obj/structure/xeno/hivemindcore/core AS in GLOB.hive_datums[hivenumber].hivemindcores) .["hive_structures"] += list(get_structure_packet(core)) @@ -262,6 +276,10 @@ if(!isxeno(usr)) return SEND_SIGNAL(usr, COMSIG_XENOABILITY_BLESSINGSMENU) + if("Mutations") + if(!isxeno(usr)) + return + GLOB.mutation_selector.interact(usr) if("Compass") var/atom/target = locate(params["target"]) if(isobserver(usr)) diff --git a/code/modules/mob/living/carbon/xenomorph/hive_upgrades.dm b/code/modules/mob/living/carbon/xenomorph/hive_upgrades.dm index 0e7f79c9bf16f..285665df9b3e2 100644 --- a/code/modules/mob/living/carbon/xenomorph/hive_upgrades.dm +++ b/code/modules/mob/living/carbon/xenomorph/hive_upgrades.dm @@ -264,6 +264,51 @@ GLOBAL_LIST_INIT(tier_to_primo_upgrade, list( to_chat(buyer, span_xenowarning("You cannot build in a dense location!")) return FALSE +/datum/hive_upgrade/building/upgrade_chamber + gamemode_flags = ABILITY_NUCLEARWAR + building_loc = 0 + /// The maximum amount of buildings that can exist before being disallowed from buying more. + var/max_chambers = 3 + +/datum/hive_upgrade/building/upgrade_chamber/shell + name = "Shell Upgrade Chamber" + desc = "Constructs a chamber that allows xenos to buy survival mutations. Build up to 3 structures to increase mutation power." + icon = "shell" + psypoint_cost = 300 + building_type = /obj/structure/xeno/upgrade_chamber/shell + +/datum/hive_upgrade/building/upgrade_chamber/shell/can_buy(mob/living/carbon/xenomorph/buyer, silent = TRUE) + . = ..() + if(length(buyer.hive.shell_chambers) >= max_chambers) + to_chat(buyer, span_xenowarning("Hive cannot support more than [max_chambers] active shell chambers!")) + return FALSE + +/datum/hive_upgrade/building/upgrade_chamber/spur + name = "Spur Upgrade Chamber" + desc = "Constructs a chamber that allows xenos to buy attack mutations. Build up to 3 structures to increase mutation power." + icon = "spur" + psypoint_cost = 250 + building_type = /obj/structure/xeno/upgrade_chamber/spur + +/datum/hive_upgrade/building/upgrade_chamber/spur/can_buy(mob/living/carbon/xenomorph/buyer, silent = TRUE) + . = ..() + if(length(buyer.hive.spur_chambers) >= max_chambers) + to_chat(buyer, span_xenowarning("Hive cannot support more than [max_chambers] active spur chambers!")) + return FALSE + +/datum/hive_upgrade/building/upgrade_chamber/veil + name = "Veil Upgrade Chamber" + desc = "Constructs a chamber that allows xenos to buy utility mutations. Build up to 3 structures to increase mutation power." + icon = "veil" + psypoint_cost = 200 + building_type = /obj/structure/xeno/upgrade_chamber/veil + +/datum/hive_upgrade/building/upgrade_chamber/veil/can_buy(mob/living/carbon/xenomorph/buyer, silent = TRUE) + . = ..() + if(length(buyer.hive.veil_chambers) >= max_chambers) + to_chat(buyer, span_xenowarning("Hive cannot support more than [max_chambers] active veil chambers!")) + return FALSE + /datum/hive_upgrade/defence category = "Defences" diff --git a/code/modules/mob/living/carbon/xenomorph/mutation_datum.dm b/code/modules/mob/living/carbon/xenomorph/mutation_datum.dm new file mode 100644 index 0000000000000..9ce6dc58ad347 --- /dev/null +++ b/code/modules/mob/living/carbon/xenomorph/mutation_datum.dm @@ -0,0 +1,204 @@ +/datum/mutation_datum + interaction_flags = INTERACT_UI_INTERACT + +/datum/mutation_datum/ui_state(mob/user) + return GLOB.hive_ui_state // Similar purpose. + +/datum/mutation_datum/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "MutationSelector", "Mutation Selector") + ui.open() + +/datum/mutation_datum/ui_data(mob/living/carbon/xenomorph/xeno_user) + var/list/data = list() + + data["shell_chambers"] = length(xeno_user.hive?.shell_chambers) + data["spur_chambers"] = length(xeno_user.hive?.spur_chambers) + data["veil_chambers"] = length(xeno_user.hive?.veil_chambers) + data["biomass"] = xeno_user.biomass + return data + +/datum/mutation_datum/ui_static_data(mob/living/carbon/xenomorph/xeno_user) + var/list/data = list() + + // Cost is not expected to change as switching castes closes the UI. + switch(xeno_user.xeno_caste.tier) + if(XENO_TIER_ONE) + data["cost"] = XENO_UPGRADE_BIOMASS_COST_T1 + if(XENO_TIER_TWO) + data["cost"] = XENO_UPGRADE_BIOMASS_COST_T2 + if(XENO_TIER_THREE) + data["cost"] = XENO_UPGRADE_BIOMASS_COST_T3 + else + data["cost"] = XENO_UPGRADE_BIOMASS_COST_T4 + + data["survival_mutations"] = list() + for(var/datum/mutation_upgrade/survival/subtype_survival_mutation AS in subtypesof(/datum/mutation_upgrade/survival)) + data["survival_mutations"] += list(list( // Double listing is how it needs to work. + "name" = subtype_survival_mutation.name, + "desc" = subtype_survival_mutation.desc, + "owned" = locate(subtype_survival_mutation.status_effect) in xeno_user.status_effects + )) + + data["attack_mutations"] = list() + for(var/datum/mutation_upgrade/attack/subtype_attack_mutation AS in subtypesof(/datum/mutation_upgrade/attack)) + data["attack_mutations"] += list(list( + "name" = subtype_attack_mutation.name, + "desc" = subtype_attack_mutation.desc, + "owned" = locate(subtype_attack_mutation.status_effect) in xeno_user.status_effects + )) + + data["utility_mutations"] = list() + for(var/datum/mutation_upgrade/utility/subtype_utility_mutation AS in subtypesof(/datum/mutation_upgrade/utility)) + data["utility_mutations"] += list(list( + "name" = subtype_utility_mutation.name, + "desc" = subtype_utility_mutation.desc, + "owned" = locate(subtype_utility_mutation.status_effect) in xeno_user.status_effects + )) + + return data + +/datum/mutation_datum/ui_act(action, params) + . = ..() + if(.) + return + if(!isxeno(usr)) + return + + switch(action) + if("purchase") + try_purchase_mutation(usr, params["upgrade_name"]) + + SStgui.close_user_uis(usr, src) + +/// Tries to purchase a mutation. Denies if possible. Otherwise, removes conflicting mutations and gives the purchased mutation. +/datum/mutation_datum/proc/try_purchase_mutation(mob/living/carbon/xenomorph/xeno_purchaser, upgrade_name) + if(!upgrade_name) + return + + var/upgrade_price + switch(xeno_purchaser.xeno_caste.tier) + if(XENO_TIER_ONE) + upgrade_price = XENO_UPGRADE_BIOMASS_COST_T1 + if(XENO_TIER_TWO) + upgrade_price = XENO_UPGRADE_BIOMASS_COST_T2 + if(XENO_TIER_THREE) + upgrade_price = XENO_UPGRADE_BIOMASS_COST_T3 + else + upgrade_price = XENO_UPGRADE_BIOMASS_COST_T4 + + if(xeno_purchaser.biomass < upgrade_price) + to_chat(usr, span_warning("You don't have enough biomass!")) + return + + var/datum/mutation_upgrade/chosen_mutation_upgrade + for(var/datum/mutation_upgrade/subtype_mutation AS in GLOB.mutation_upgrades_buyable) + if(subtype_mutation.name == upgrade_name) + chosen_mutation_upgrade = subtype_mutation + break + + if(!chosen_mutation_upgrade) + return + + switch(chosen_mutation_upgrade.required_structure) + if(MUTATION_STRUCTURE_CHAMBER) + if(!length(xeno_purchaser.hive?.shell_chambers)) + to_chat(usr, span_xenonotice("This mutation requires a shell chamber to exist!")) + return + if(MUTATION_STRUCTURE_SPUR) + if(!length(xeno_purchaser.hive?.spur_chambers)) + to_chat(usr, span_xenonotice("This mutation requires a spur chamber to exist!")) + return + if(MUTATION_STRUCTURE_VEIL) + if(!length(xeno_purchaser.hive?.veil_chambers)) + to_chat(usr, span_xenonotice("This mutation requires a veil chamber to exist!")) + return + + var/existing_upgrade = locate(chosen_mutation_upgrade.status_effect) in xeno_purchaser.status_effects + if(existing_upgrade) + to_chat(usr, span_xenonotice("Existing mutation chosen. No biomass spent.")) + return + + var/list/mutation_status_effects_to_remove = list() + for(var/datum/mutation_upgrade/subtype_mutation AS in GLOB.mutation_upgrades_buyable) + if(chosen_mutation_upgrade.category == subtype_mutation.category) + mutation_status_effects_to_remove += subtype_mutation.status_effect + + xeno_purchaser.use_biomass(upgrade_price) + to_chat(xeno_purchaser, span_xenonotice("Mutation gained.")) + for(var/datum/status_effect/removed_status_effect AS in mutation_status_effects_to_remove) + xeno_purchaser.remove_status_effect(removed_status_effect) + xeno_purchaser.do_jitter_animation(500) + xeno_purchaser.apply_status_effect(chosen_mutation_upgrade.status_effect) + +/datum/mutation_upgrade + /// The name that is displayed in the TGUI. + var/name + /// The description that is displayed in the TGUI. + var/desc + /// The category slot that this upgrade takes. Upgrades that conflict with this category slot will be removed/replaced. + var/category + /// The structure that needs to exist for a successful purchase. + var/required_structure + /// The status effect given upon successful purchase. + var/datum/status_effect/status_effect + +/datum/mutation_upgrade/survival + category = MUTATION_CATEGORY_SURVIVAL + required_structure = MUTATION_STRUCTURE_CHAMBER + +/datum/mutation_upgrade/survival/carapace + name = "Carapace" + desc = "Increases our soft armor by 2.5 per Shell Chamber." + status_effect = STATUS_EFFECT_UPGRADE_CARAPACE + +/datum/mutation_upgrade/survival/regeneration + name = "Regeneration" + desc = "When regenerating health on weeds, regenerate 0.8% max health and 0.167 sunder per Shell Chamber." + status_effect = STATUS_EFFECT_UPGRADE_REGENERATION + +/datum/mutation_upgrade/survival/vampirism + name = "Vampirism" + desc = "When slashing living humans, heal 1.67% max health per Shell Chamber. Ravagers get half the bonus instead." + status_effect = STATUS_EFFECT_UPGRADE_VAMPIRISM + +/datum/mutation_upgrade/attack + category = MUTATION_CATEGORY_ATTACK + required_structure = MUTATION_STRUCTURE_SPUR + +/datum/mutation_upgrade/attack/celerity + name = "Celerity" + desc = "Move -0.1 units faster per Spur Chamber." + category = MUTATION_CATEGORY_ATTACK + required_structure = MUTATION_STRUCTURE_SPUR + status_effect = STATUS_EFFECT_UPGRADE_CELERITY + +/datum/mutation_upgrade/attack/adrenaline + name = "Adrenaline" + desc = "When regenerating plasma on weeds, regenerate 5% additional plasma and 1% maximum plasma per Spur Chamber. It is doubled if resting." + status_effect = STATUS_EFFECT_UPGRADE_ADRENALINE + +/datum/mutation_upgrade/attack/crush + name = "Crush" + desc = "When attacking structures, deal a second instance of damage that is a 1/3 of your melee damage with 15 AP per Spur Chamber." + status_effect = STATUS_EFFECT_UPGRADE_CRUSH + +/datum/mutation_upgrade/utility + category = MUTATION_CATEGORY_UTILITY + required_structure = MUTATION_STRUCTURE_VEIL + +/datum/mutation_upgrade/utility/toxin + name = "Toxin" + desc = "When slashing living humans, inject a variable amount of a chosen toxin into them. The amount depends on the toxin and Spur Chamber count." + status_effect = STATUS_EFFECT_UPGRADE_TOXIN + +/datum/mutation_upgrade/utility/pheromones + name = "Pheromones" + desc = "Allows you to emit a chosen pheromone starting at a power of 1 and an additional 1 per Veil Chamber." + status_effect = STATUS_EFFECT_UPGRADE_PHERO + +/datum/mutation_upgrade/utility/trail + name = "Trail" + desc = "When moving, randomly leave a chosen trail underneath you at a 25% chance and an additional 25% per Veil Chamber." + status_effect = STATUS_EFFECT_UPGRADE_TRAIL diff --git a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm index 624a032f869d3..03f81d4326ae8 100644 --- a/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm +++ b/code/modules/mob/living/carbon/xenomorph/xeno_defines.dm @@ -347,6 +347,8 @@ GLOBAL_LIST_INIT(strain_list, init_glob_strain_list()) ///Will increase by 10 every decisecond if under 0. ///Increases by xeno_caste.regen_ramp_amount every decisecond. If you want to balance this, look at the xeno_caste defines mentioned above. var/regen_power = 0 + /// The amount of biomass stored. Used for Chamber Upgrades. + var/biomass = 0 var/zoom_turf = null diff --git a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm index a173fb124683d..557c99ecd5140 100644 --- a/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm +++ b/code/modules/mob/living/carbon/xenomorph/xenoprocs.dm @@ -126,6 +126,10 @@ else //Upgrade process finished or impossible . += "Upgrade Progress: (FINISHED)" + if(!(xeno_caste.caste_flags & CASTE_NO_BIOMASS)) + . += "Biomass: [biomass]/100" + + . += "Health: [health]/[maxHealth][overheal ? " + [overheal]": ""]" //Changes with balance scalar, can't just use the caste if(xeno_caste.plasma_max > 0) @@ -580,3 +584,9 @@ /mob/living/carbon/xenomorph/on_eord(turf/destination) revive(TRUE) + +/mob/living/carbon/xenomorph/proc/use_biomass(value) + biomass = clamp(biomass - value, 0, 100) + +/mob/living/carbon/xenomorph/proc/gain_biomass(value) + biomass = clamp(biomass + value, 0, 100) diff --git a/code/modules/xenomorph/upgrade_chambers.dm b/code/modules/xenomorph/upgrade_chambers.dm new file mode 100644 index 0000000000000..719d05adb0a09 --- /dev/null +++ b/code/modules/xenomorph/upgrade_chambers.dm @@ -0,0 +1,62 @@ +/obj/structure/xeno/upgrade_chamber + name = "upgrade chamber" + desc = "You shouldn't see this!" + icon = 'icons/Xeno/1x1building.dmi' + icon_state = "shell_chamber" + bound_width = 32 + bound_height = 32 + max_integrity = 500 + resistance_flags = UNACIDABLE | DROPSHIP_IMMUNE + xeno_structure_flags = IGNORE_WEED_REMOVAL | CRITICAL_STRUCTURE + +/obj/structure/xeno/upgrade_chamber/Initialize(mapload, _hivenumber) + . = ..() + SSminimaps.add_marker(src, MINIMAP_FLAG_XENO, image('icons/UI_icons/map_blips.dmi', null, "upgrade_chamber", ABOVE_FLOAT_LAYER)) + +/obj/structure/xeno/upgrade_chamber/shell + name = "Shell Chamber" + desc = "Shell upgrade chamber" + icon_state = "shell_chamber" + +/obj/structure/xeno/upgrade_chamber/shell/Initialize(mapload, _hivenumber) + . = ..() + set_light(3, 1, COLOR_DARK_CYAN) + GLOB.hive_datums[hivenumber].shell_chambers += src + SEND_GLOBAL_SIGNAL(COMSIG_UPGRADE_CHAMBER_SURVIVAL) + +/obj/structure/xeno/upgrade_chamber/shell/Destroy() + GLOB.hive_datums[hivenumber].shell_chambers -= src + SEND_GLOBAL_SIGNAL(COMSIG_UPGRADE_CHAMBER_SURVIVAL) + return ..() + +/obj/structure/xeno/upgrade_chamber/spur + name = "Spur Chamber" + desc = "Spur upgrade chamber" + icon_state = "spur_chamber" + +/obj/structure/xeno/upgrade_chamber/spur/Initialize(mapload, _hivenumber) + . = ..() + set_light(3, 1, COLOR_RED) + GLOB.hive_datums[hivenumber].spur_chambers += src + SEND_GLOBAL_SIGNAL(COMSIG_UPGRADE_CHAMBER_ATTACK) + +/obj/structure/xeno/upgrade_chamber/spur/Destroy() + GLOB.hive_datums[hivenumber].spur_chambers -= src + SEND_GLOBAL_SIGNAL(COMSIG_UPGRADE_CHAMBER_ATTACK) + return ..() + +/obj/structure/xeno/upgrade_chamber/veil + name = "Veil Chamber" + desc = "Veil upgrade chamber" + icon_state = "veil_chamber" + +/obj/structure/xeno/upgrade_chamber/veil/Initialize(mapload, _hivenumber) + . = ..() + set_light(3, 1, COLOR_LIME) + GLOB.hive_datums[hivenumber].veil_chambers += src + SEND_GLOBAL_SIGNAL(COMSIG_UPGRADE_CHAMBER_UTILITY) + +/obj/structure/xeno/upgrade_chamber/veil/Destroy() + GLOB.hive_datums[hivenumber].veil_chambers -= src + SEND_GLOBAL_SIGNAL(COMSIG_UPGRADE_CHAMBER_UTILITY) + return ..() diff --git a/icons/UI_Icons/buyable_icons.dmi b/icons/UI_Icons/buyable_icons.dmi index 44bdaec35b984..e1ce480200489 100644 Binary files a/icons/UI_Icons/buyable_icons.dmi and b/icons/UI_Icons/buyable_icons.dmi differ diff --git a/icons/UI_Icons/map_blips.dmi b/icons/UI_Icons/map_blips.dmi index b38bbe215fb77..fa6c1cd80f6e3 100644 Binary files a/icons/UI_Icons/map_blips.dmi and b/icons/UI_Icons/map_blips.dmi differ diff --git a/icons/Xeno/1x1building.dmi b/icons/Xeno/1x1building.dmi index b58708b539aa3..d4ac402de1aaf 100644 Binary files a/icons/Xeno/1x1building.dmi and b/icons/Xeno/1x1building.dmi differ diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi index ad6e246e92201..af132dd3a6ac6 100644 Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ diff --git a/tgmc.dme b/tgmc.dme index 51ac661d0895c..11581277b6023 100644 --- a/tgmc.dme +++ b/tgmc.dme @@ -1786,6 +1786,7 @@ F// DM Environment file for baystation12.dme. #include "code\modules\mob\living\carbon\xenomorph\hive_upgrades.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\mutation_datum.dm" #include "code\modules\mob\living\carbon\xenomorph\saddles.dm" #include "code\modules\mob\living\carbon\xenomorph\say.dm" #include "code\modules\mob\living\carbon\xenomorph\update_icons.dm" @@ -2280,6 +2281,7 @@ F// DM Environment file for baystation12.dme. #include "code\modules\xenomorph\spawner.dm" #include "code\modules\xenomorph\trap.dm" #include "code\modules\xenomorph\tunnel.dm" +#include "code\modules\xenomorph\upgrade_chambers.dm" #include "code\modules\xenomorph\xeno_turret.dm" #include "code\modules\xenomorph\xenoplant.dm" #include "code\modules\xenomorph\xenotowers.dm" diff --git a/tgui/packages/tgui/interfaces/HiveStatus.tsx b/tgui/packages/tgui/interfaces/HiveStatus.tsx index 5f89779d85c8e..75a7417765881 100644 --- a/tgui/packages/tgui/interfaces/HiveStatus.tsx +++ b/tgui/packages/tgui/interfaces/HiveStatus.tsx @@ -190,15 +190,12 @@ const BlessingsButton = (_props: any) => { const { act, data } = useBackend(); const { user_purchase_perms, user_ref } = data; - if (!user_purchase_perms) { - return ; - } - return ( @@ -206,6 +203,22 @@ const BlessingsButton = (_props: any) => { ); }; +const MutationsButton = (_props: any) => { + const { act, data } = useBackend(); + const { user_ref } = data; + + return ( + + + + ); +}; + const GeneralInfo = (_props: any) => { const { data } = useBackend(); const { @@ -253,7 +266,10 @@ const GeneralInfo = (_props: any) => { {' ' + hive_larva_burrowed} - + + + + diff --git a/tgui/packages/tgui/interfaces/MutationSelector.tsx b/tgui/packages/tgui/interfaces/MutationSelector.tsx new file mode 100644 index 0000000000000..2dbc586dc4892 --- /dev/null +++ b/tgui/packages/tgui/interfaces/MutationSelector.tsx @@ -0,0 +1,168 @@ +import { useBackend } from '../backend'; +import { + Button, + Collapsible, + Flex, + ProgressBar, + Section, + Tooltip, +} from '../components'; +import { Window } from '../layouts'; + +type Upgrade = { + name: string; + desc: string; + owned: boolean; +}; + +type BiomassData = { + biomass: number; + cost: number; +}; + +type SurvivalMutationData = { + survival_mutations: Upgrade[]; + shell_chambers: number; + biomass: number; + cost: number; +}; + +type AttackMutationData = { + attack_mutations: Upgrade[]; + spur_chambers: number; + biomass: number; + cost: number; +}; + +type UtilityMutationData = { + utility_mutations: Upgrade[]; + veil_chambers: number; + biomass: number; + cost: number; +}; + +export const MutationSelector = (_props: any) => { + return ( + + +
+ +
+ + + +
+
+ ); +}; + +const BiomassBar = (_props: any) => { + const { data } = useBackend(); + const { biomass, cost } = data; + + return ( + + + + + {`${biomass} / 100 `} + + + + + ); +}; + +const SurvivalMutationSection = (_props: any) => { + const { act, data } = useBackend(); + const { survival_mutations, shell_chambers, cost, biomass } = data; + + return ( + + {survival_mutations.map((mutation) => ( +
act('purchase', { upgrade_name: mutation.name })} + disabled={cost > biomass || shell_chambers === 0} + selected={mutation.owned} + /> + } + > + + {mutation.desc} + +
+ ))} +
+ ); +}; + +const AttackMutationSection = (_props: any) => { + const { act, data } = useBackend(); + const { attack_mutations, spur_chambers, cost, biomass } = data; + + return ( + + {attack_mutations.map((mutation) => ( +
act('purchase', { upgrade_name: mutation.name })} + disabled={cost > biomass || spur_chambers === 0} + selected={mutation.owned} + /> + } + > + + {mutation.desc} + +
+ ))} +
+ ); +}; + +const UtilityMutationSection = (_props: any) => { + const { act, data } = useBackend(); + const { utility_mutations, veil_chambers, cost, biomass } = data; + + return ( + + {utility_mutations.map((mutation) => ( +
act('purchase', { upgrade_name: mutation.name })} + disabled={cost > biomass || veil_chambers === 0} + selected={mutation.owned} + /> + } + > + + {mutation.desc} + +
+ ))} +
+ ); +};