Files
nevimer 00ccf0c6b5 Merge remote-tracking branch 'tgstation/master' into upstream-feb12-2026
# Conflicts:
#	.github/CODEOWNERS
#	.github/workflows/compile_changelogs.yml
#	.github/workflows/stale.yml
#	SQL/database_changelog.md
#	_maps/map_files/CatwalkStation/CatwalkStation_2023.dmm
#	code/__DEFINES/atom_hud.dm
#	code/__DEFINES/inventory.dm
#	code/__DEFINES/mobs.dm
#	code/__DEFINES/species_clothing_paths.dm
#	code/__DEFINES/subsystems.dm
#	code/__DEFINES/surgery.dm
#	code/__HELPERS/global_lists.dm
#	code/_globalvars/lists/maintenance_loot.dm
#	code/_globalvars/traits/_traits.dm
#	code/controllers/subsystem/minor_mapping.dm
#	code/controllers/subsystem/processing/quirks.dm
#	code/controllers/subsystem/shuttle.dm
#	code/datums/components/palette.dm
#	code/datums/components/surgery_initiator.dm
#	code/datums/diseases/advance/advance.dm
#	code/datums/hud.dm
#	code/datums/mood.dm
#	code/datums/mutations/chameleon.dm
#	code/datums/quirks/negative_quirks/nyctophobia.dm
#	code/datums/status_effects/debuffs/debuffs.dm
#	code/datums/status_effects/debuffs/drunk.dm
#	code/datums/status_effects/debuffs/slime/slime_leech.dm
#	code/datums/weather/weather.dm
#	code/game/data_huds.dm
#	code/game/objects/items.dm
#	code/game/objects/items/devices/scanners/health_analyzer.dm
#	code/game/objects/items/frog_statue.dm
#	code/game/objects/items/rcd/RLD.dm
#	code/game/objects/items/robot/items/hypo.dm
#	code/game/objects/items/stacks/medical.dm
#	code/game/objects/items/stacks/wrap.dm
#	code/game/objects/items/storage/garment.dm
#	code/game/objects/items/tools/medical/defib.dm
#	code/game/objects/items/weaponry.dm
#	code/game/objects/items/weaponry/melee/misc.dm
#	code/game/objects/structures/crates_lockers/closets/secure/security.dm
#	code/game/objects/structures/curtains.dm
#	code/game/objects/structures/dresser.dm
#	code/game/objects/structures/girders.dm
#	code/game/objects/structures/maintenance.dm
#	code/game/objects/structures/mirror.dm
#	code/modules/admin/greyscale_modify_menu.dm
#	code/modules/admin/verbs/light_debug.dm
#	code/modules/antagonists/ashwalker/ashwalker.dm
#	code/modules/antagonists/heretic/knowledge/starting_lore.dm
#	code/modules/antagonists/ninja/ninjaDrainAct.dm
#	code/modules/art/paintings.dm
#	code/modules/client/preferences.dm
#	code/modules/client/verbs/ooc.dm
#	code/modules/clothing/head/wig.dm
#	code/modules/events/disease_outbreak.dm
#	code/modules/holodeck/holo_effect.dm
#	code/modules/jobs/job_types/head_of_security.dm
#	code/modules/jobs/job_types/security_officer.dm
#	code/modules/library/skill_learning/generic_skillchips/point.dm
#	code/modules/mining/lavaland/ash_flora.dm
#	code/modules/mining/lavaland/mining_loot/megafauna/ash_drake.dm
#	code/modules/mob/dead/new_player/new_player.dm
#	code/modules/mob/living/basic/guardian/guardian.dm
#	code/modules/mob/living/basic/space_fauna/space_dragon/space_dragon.dm
#	code/modules/mob/living/carbon/carbon.dm
#	code/modules/mob/living/carbon/human/human.dm
#	code/modules/mob/living/carbon/human/human_defines.dm
#	code/modules/mob/living/carbon/life.dm
#	code/modules/mob/living/living.dm
#	code/modules/mob/living/living_defines.dm
#	code/modules/mob/mob.dm
#	code/modules/mob_spawn/ghost_roles/mining_roles.dm
#	code/modules/mod/mod_control.dm
#	code/modules/mod/modules/modules_general.dm
#	code/modules/modular_computers/computers/item/computer_ui.dm
#	code/modules/paperwork/paper.dm
#	code/modules/paperwork/paperbin.dm
#	code/modules/power/lighting/light.dm
#	code/modules/projectiles/guns/energy/kinetic_accelerator.dm
#	code/modules/projectiles/projectile.dm
#	code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
#	code/modules/reagents/chemistry/reagents/food_reagents.dm
#	code/modules/reagents/chemistry/reagents/other_reagents.dm
#	code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
#	code/modules/research/xenobiology/crossbreeding/_clothing.dm
#	code/modules/research/xenobiology/crossbreeding/prismatic.dm
#	code/modules/surgery/advanced/brainwashing.dm
#	code/modules/surgery/advanced/lobotomy.dm
#	code/modules/surgery/amputation.dm
#	code/modules/surgery/blood_filter.dm
#	code/modules/surgery/bodyparts/_bodyparts.dm
#	code/modules/surgery/brain_surgery.dm
#	code/modules/surgery/cavity_implant.dm
#	code/modules/surgery/coronary_bypass.dm
#	code/modules/surgery/gastrectomy.dm
#	code/modules/surgery/healing.dm
#	code/modules/surgery/limb_augmentation.dm
#	code/modules/surgery/organ_manipulation.dm
#	code/modules/surgery/revival.dm
#	code/modules/surgery/sleeper_protocol.dm
#	code/modules/surgery/surgery_helpers.dm
#	code/modules/surgery/surgery_step.dm
#	code/modules/unit_tests/_unit_tests.dm
#	code/modules/unit_tests/designs.dm
#	code/modules/unit_tests/icon_state_worn.dm
#	code/modules/unit_tests/screenshots/screenshot_antag_icons_cultist.png
#	code/modules/unit_tests/screenshots/screenshot_antag_icons_headrevolutionary.png
#	code/modules/unit_tests/screenshots/screenshot_antag_icons_provocateur.png
#	code/modules/unit_tests/screenshots/screenshot_husk_body.png
#	code/modules/unit_tests/screenshots/screenshot_husk_body_missing_limbs.png
#	icons/map_icons/clothing/head/_head.dmi
#	icons/map_icons/clothing/shoes.dmi
#	icons/map_icons/items/_item.dmi
#	icons/mob/huds/hud.dmi
#	icons/mob/inhands/64x64_lefthand.dmi
#	icons/mob/inhands/64x64_righthand.dmi
#	icons/obj/machines/computer.dmi
#	tgui/packages/tgui/interfaces/OperatingComputer.jsx
#	tgui/packages/tgui/interfaces/PreferencesMenu/CharacterPreferences/MainPage.tsx
#	tgui/packages/tgui/interfaces/PreferencesMenu/types.ts
#	tgui/packages/tgui/interfaces/SurgeryInitiator.tsx
#	tools/icon_cutter/check.py
2026-02-12 23:50:09 -05:00

253 lines
9.4 KiB
Plaintext

/// Negatives that are virtually harmless and mostly just funny (language)
// Set to 0 because munchkinning via miscommunication = bad
#define NEGATIVE_STABILITY_MINI 0
/// Negatives that are slightly annoying (unused)
#define NEGATIVE_STABILITY_MINOR -20
/// Negatives that present an uncommon or weak, consistent hindrance to gameplay (cough, paranoia)
#define NEGATIVE_STABILITY_MODERATE -30
/// Negatives that present a major consistent hindrance to gameplay (deaf, mute, acid flesh)
#define NEGATIVE_STABILITY_MAJOR -40
/// Positives that provide basically no benefit (glowy)
#define POSITIVE_INSTABILITY_MINI 5
/// Positives that are niche in application or useful in rare circumstances (parlor tricks, geladikinesis, autotomy)
#define POSITIVE_INSTABILITY_MINOR 10
/// Positives that provide a new ability that's roughly par with station equipment (insulated, cryokinesis)
#define POSITIVE_INSTABILITY_MODERATE 25
/// Positives that are unique, very powerful, and noticeably change combat/gameplay (hulk, tk)
#define POSITIVE_INSTABILITY_MAJOR 35
/datum/mutation
var/name = "mutation"
/// Description of the mutation
var/desc = "A mutation."
/// Is this mutation currently locked?
var/locked
/// Quality of the mutation
var/quality
/// Message given to the user upon gaining this mutation
var/text_gain_indication = ""
/// Message given to the user upon losing this mutation
var/text_lose_indication = ""
/// Visual indicators upon the character of the owner of this mutation
var/static/list/visual_indicators = list()
/// The path of action we grant to our user on mutation gain
var/datum/action/cooldown/power_path
/// Which mutation layer to use
var/layer_used = MUTATIONS_LAYER
/// To restrict mutation to only certain species
var/list/species_allowed
/// Minimum health required to acquire the mutation
var/health_req
/// Required limbs to acquire this mutation
var/limb_req
/// The owner of this mutation's DNA
var/datum/dna/dna
/// Owner of this mutation
var/mob/living/carbon/human/owner
/// Instability the holder gets when the mutation is not native
var/instability = 0
/// Amount of those big blocks with gene sequences
var/blocks = 4
/// Amount of missing sequences. Sometimes it removes an entire pair for 2 points
var/difficulty = 8
/// 'Mutation #49', decided every round to get some form of distinction between undiscovered mutations
var/alias
/// Whether we can read it if it's active. To avoid cheesing with mutagen
var/scrambled = FALSE
/// The sources of the mutation (found in defines/dna.dm)
var/list/sources = list()
/**
* any mutations that might conflict.
* put mutation typepath defines in here.
* make sure to enter it both ways (so that A conflicts with B, and B with A)
*/
var/list/conflicts
var/remove_on_aheal = TRUE
/**
* can we take chromosomes?
* 0: CHROMOSOME_NEVER never
* 1: CHROMOSOME_NONE yeah
* 2: CHROMOSOME_USED no, already have one
*/
var/can_chromosome = CHROMOSOME_NONE
/// Name of the chromosome
var/chromosome_name
//Chromosome stuff - set to -1 to prevent people from changing it. Example: It'd be a waste to decrease cooldown on mutism
/// genetic stability coeff
var/stabilizer_coeff = 1
/// Makes the mutation hurt the user less
var/synchronizer_coeff = MUTATION_COEFFICIENT_UNMODIFIABLE
/// Boosts mutation strength
var/power_coeff = MUTATION_COEFFICIENT_UNMODIFIABLE
/// Lowers mutation cooldown
var/energy_coeff = MUTATION_COEFFICIENT_UNMODIFIABLE
/// List of strings of valid chromosomes this mutation can accept.
var/list/valid_chrom_list = list()
/// List of traits that are added or removed by the mutation with GENETIC_TRAIT source.
var/list/mutation_traits
/datum/mutation/New()
. = ..()
/datum/mutation/Destroy()
power_path = null
dna = null
owner = null
return ..()
/datum/mutation/proc/make_copy()
var/datum/mutation/copy = new type
copy.chromosome_name = chromosome_name
copy.stabilizer_coeff = stabilizer_coeff
copy.synchronizer_coeff = synchronizer_coeff
copy.power_coeff = power_coeff
copy.energy_coeff = energy_coeff
copy.can_chromosome = can_chromosome
copy.valid_chrom_list = valid_chrom_list
update_valid_chromosome_list()
return copy
/datum/mutation/proc/on_acquiring(mob/living/carbon/human/acquirer)
if(!acquirer || !istype(acquirer) || acquirer.stat == DEAD || (src in acquirer.dna.mutations))
return FALSE
// SKYRAT EDIT ADDITION
if(acquirer.has_borer())
to_chat(acquirer, span_warning("Something inside holds dearly to your humanity!"))
// SKYRAT EDIT END
if(species_allowed && !species_allowed.Find(acquirer.dna.species.id))
return FALSE
if(health_req && acquirer.health < health_req)
return FALSE
if(limb_req && !acquirer.get_bodypart(limb_req))
return FALSE
for(var/datum/mutation/mewtayshun as anything in acquirer.dna.mutations) //check for conflicting powers
if(!(mewtayshun.type in conflicts) && !(type in mewtayshun.conflicts))
continue
to_chat(acquirer, span_warning("You feel your genes resisting something."))
return FALSE
owner = acquirer
dna = acquirer.dna
LAZYADD(dna.mutations, src)
SEND_SIGNAL(src, COMSIG_MUTATION_GAINED, acquirer)
if(text_gain_indication)
to_chat(owner, text_gain_indication)
if(visual_indicators.len)
var/list/mut_overlay = list(get_visual_indicator())
if(owner.overlays_standing[layer_used])
mut_overlay = owner.overlays_standing[layer_used]
mut_overlay |= get_visual_indicator()
owner.remove_overlay(layer_used)
owner.overlays_standing[layer_used] = mut_overlay
owner.apply_overlay(layer_used)
grant_power() //we do checks here so nothing about hulk getting magic
if(mutation_traits)
owner.add_traits(mutation_traits, GENETIC_MUTATION)
return TRUE
/datum/mutation/proc/get_visual_indicator()
return
/datum/mutation/proc/on_life(seconds_per_tick)
return
/datum/mutation/proc/on_losing(mob/living/carbon/human/owner)
if(!istype(owner) || !(owner.dna.mutations?.Remove(src)))
return TRUE
. = FALSE
SEND_SIGNAL(src, COMSIG_MUTATION_LOST, owner)
if(text_lose_indication && owner.stat != DEAD)
to_chat(owner, text_lose_indication)
if(visual_indicators.len)
var/list/mut_overlay = list()
if(owner.overlays_standing[layer_used])
mut_overlay = owner.overlays_standing[layer_used]
owner.remove_overlay(layer_used)
mut_overlay.Remove(get_visual_indicator())
owner.overlays_standing[layer_used] = mut_overlay
owner.apply_overlay(layer_used)
if(mutation_traits)
owner.remove_traits(mutation_traits, GENETIC_MUTATION)
/mob/living/carbon/proc/update_mutations_overlay()
return
/mob/living/carbon/human/update_mutations_overlay()
for(var/datum/mutation/mutation as anything in dna.mutations)
if(mutation.species_allowed && !mutation.species_allowed.Find(dna.species.id))
dna.remove_mutation(mutation, mutation.sources) //shouldn't have that mutation at all
continue
if(mutation.visual_indicators.len == 0)
continue
var/list/mut_overlay = list()
if(overlays_standing[mutation.layer_used])
mut_overlay = overlays_standing[mutation.layer_used]
var/mutable_appearance/indicator_to_add = mutation.get_visual_indicator()
if(!mut_overlay.Find(indicator_to_add)) //either we lack the visual indicator or we have the wrong one
remove_overlay(mutation.layer_used)
for(var/mutable_appearance/indicator_to_remove in mutation.visual_indicators[mutation.type])
mut_overlay.Remove(indicator_to_remove)
mut_overlay |= indicator_to_add
overlays_standing[mutation.layer_used] = mut_overlay
apply_overlay(mutation.layer_used)
/**
* Called after on_aquiring, or when a chromosome is applied.
* returns the instance of 'power_path' for children calls to use without calling locate() again.
*/
/datum/mutation/proc/setup()
if(!power_path || QDELETED(owner))
return
var/datum/action/cooldown/modified_power = locate(power_path) in owner.actions
if(!modified_power)
CRASH("Genetic mutation [type] called setup(), but could not find a action to modify!")
modified_power.cooldown_time = initial(modified_power.cooldown_time) * GET_MUTATION_ENERGY(src)
return modified_power
/datum/mutation/proc/remove_chromosome()
stabilizer_coeff = initial(stabilizer_coeff)
synchronizer_coeff = initial(synchronizer_coeff)
power_coeff = initial(power_coeff)
energy_coeff = initial(energy_coeff)
can_chromosome = initial(can_chromosome)
chromosome_name = null
/datum/mutation/proc/grant_power()
if(!ispath(power_path) || !owner)
return FALSE
var/datum/action/cooldown/new_power = new power_path(src)
new_power.background_icon_state = "bg_tech_blue"
new_power.base_background_icon_state = new_power.background_icon_state
new_power.active_background_icon_state = "[new_power.base_background_icon_state]_active"
new_power.overlay_icon_state = "bg_tech_blue_border"
new_power.active_overlay_icon_state = "bg_spell_border_active_blue"
new_power.panel = "Genetic"
new_power.Grant(owner)
return new_power
// Runs through all the coefficients and uses this to determine which chromosomes the
// mutation can take. Stores these as text strings in a list.
/datum/mutation/proc/update_valid_chromosome_list()
valid_chrom_list.Cut()
if(can_chromosome == CHROMOSOME_NEVER)
valid_chrom_list += "none"
return
if(stabilizer_coeff != MUTATION_COEFFICIENT_UNMODIFIABLE)
valid_chrom_list += "Stabilizer"
if(synchronizer_coeff != MUTATION_COEFFICIENT_UNMODIFIABLE)
valid_chrom_list += "Synchronizer"
if(power_coeff != MUTATION_COEFFICIENT_UNMODIFIABLE)
valid_chrom_list += "Power"
if(energy_coeff != MUTATION_COEFFICIENT_UNMODIFIABLE)
valid_chrom_list += "Energetic"