Files
Alexis 21b4095dfd [MDB IGNORE] [IDB IGNORE] Upstream Sync - 04/17/2026 (#5453)
Upstream 04/17/2026

fixes https://github.com/Bubberstation/Bubberstation/issues/5549

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: ArcaneMusic <41715314+ArcaneMusic@users.noreply.github.com>
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
Co-authored-by: Rhials <28870487+Rhials@users.noreply.github.com>
Co-authored-by: rageguy505 <54517726+rageguy505@users.noreply.github.com>
Co-authored-by: LemonInTheDark <58055496+LemonInTheDark@users.noreply.github.com>
Co-authored-by: John Willard <53777086+JohnFulpWillard@users.noreply.github.com>
Co-authored-by: Aliceee2ch <160794176+Aliceee2ch@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
Co-authored-by: Tsar-Salat <62388554+Tsar-Salat@users.noreply.github.com>
Co-authored-by: SmArtKar <44720187+SmArtKar@users.noreply.github.com>
Co-authored-by: Maxipat <108554989+Maxipat112@users.noreply.github.com>
Co-authored-by: SyncIt21 <110812394+SyncIt21@users.noreply.github.com>
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
Co-authored-by: SimplyLogan <47579821+loganuk@users.noreply.github.com>
Co-authored-by: loganuk <fakeemail123@aol.com>
Co-authored-by: Leland Kemble <70413276+lelandkemble@users.noreply.github.com>
Co-authored-by: FalloutFalcon <86381784+FalloutFalcon@users.noreply.github.com>
Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com>
Co-authored-by: Lucy <lucy@absolucy.moe>
Co-authored-by: siliconOpossum <138069572+siliconOpossum@users.noreply.github.com>
Co-authored-by: Isratosh <Isratosh@hotmail.com>
Co-authored-by: TheRyeGuyWhoWillNowDie <70169560+TheRyeGuyWhoWillNowDie@users.noreply.github.com>
Co-authored-by: Neocloudy <88008002+Neocloudy@users.noreply.github.com>
Co-authored-by: Alexander V. <volas@ya.ru>
Co-authored-by: ElGitificador <168473461+ElGitificador@users.noreply.github.com>
Co-authored-by: Twaticus <46540570+Twaticus@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bloop <13398309+vinylspiders@users.noreply.github.com>
Co-authored-by: Cameron Lennox <killer65311@gmail.com>
Co-authored-by: Tim <timothymtorres@gmail.com>
Co-authored-by: Iamgoofball <iamgoofball@gmail.com>
Co-authored-by: Layzu666 <121319428+Layzu666@users.noreply.github.com>
Co-authored-by: Arturlang <24881678+Arturlang@users.noreply.github.com>
Co-authored-by: _0Steven <42909981+00-Steven@users.noreply.github.com>
Co-authored-by: mrmanlikesbt <99309552+mrmanlikesbt@users.noreply.github.com>
Co-authored-by: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com>
Co-authored-by: John F. Kennedy <54908920+MacaroniCritter@users.noreply.github.com>
Co-authored-by: Cursor <102828457+theselfish@users.noreply.github.com>
Co-authored-by: Josh <josh.adam.powell@gmail.com>
Co-authored-by: Josh Powell <josh.powell@softwire.com>
Co-authored-by: Yobrocharlie <Charliemiller5617@gmail.com>
Co-authored-by: Hardly3D <66234359+Hardly3D@users.noreply.github.com>
Co-authored-by: shayoki <96078776+shayoki@users.noreply.github.com>
Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
2026-05-16 00:56:00 +02:00

905 lines
38 KiB
Plaintext

///A global list of singleton fish traits by their paths
GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list()))
/**
* A nested list of fish types and traits that they can spontaneously manifest with associated probabilities
* e.g. list(/obj/item/fish = list(/datum/fish_trait = 100), etc...)
*/
GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits())
/proc/populate_spontaneous_fish_traits()
var/list/list = list()
for(var/trait_path in GLOB.fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[trait_path]
if(isnull(trait.spontaneous_manifest_types))
continue
var/list/trait_typecache = zebra_typecacheof(trait.spontaneous_manifest_types) - /obj/item/fish
for(var/fish_type in trait_typecache)
var/trait_prob = trait_typecache[fish_type]
if(!trait_prob)
continue
LAZYSET(list[fish_type], trait_path, trait_typecache[fish_type])
return list
/datum/fish_trait
var/name = "Unnamed Trait"
/// Description of the trait in the fishing catalog and scanner
var/catalog_description = "Uh uh, someone has forgotten to set description to this trait. Yikes!"
///A list of traits fish cannot have in conjunction with this trait.
var/list/incompatible_traits
/// The probability this trait can be inherited by offsprings when both mates have it
var/inheritability = 50
/// A list of fish types and traits that they can spontaneously manifest with associated probabilities
var/list/spontaneous_manifest_types
/// An optional whitelist of fish that can get this trait
var/list/fish_whitelist
/// Depending on the value, fish with trait will be reported as more or less difficult in the catalog.
var/added_difficulty = 0
/// Reagents to add to the fish whenever the COMSIG_GENERATE_REAGENTS_TO_ADD signal is sent. Their values will be multiplied later.
var/list/reagents_to_add
/// Trait/s to add on apply_to_fish() and remove on remove_from_fish()
var/list/traits_to_add
/// If set, the fish may return this infusion entry when get_infusion_entry is called instead of /datum/infuser_entry/fish
var/infusion_entry
/// Difficulty modifier from this mod, needs to return a list with two values
/datum/fish_trait/proc/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
SHOULD_CALL_PARENT(TRUE) //Technically it doesn't but this makes it saner without custom unit test
return list(ADDITIVE_FISHING_MOD = 0, MULTIPLICATIVE_FISHING_MOD = 1)
/// Catch weight table modifier from this mod, needs to return a list with two values
/datum/fish_trait/proc/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type)
SHOULD_CALL_PARENT(TRUE)
return list(ADDITIVE_FISHING_MOD = 0, MULTIPLICATIVE_FISHING_MOD = 1)
/// Returns special minigame rules and effects applied by this trait
/datum/fish_trait/proc/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
return
/// Applies some special qualities to the fish that has been spawned
/datum/fish_trait/proc/apply_to_fish(obj/item/fish/fish, initial = TRUE)
SHOULD_CALL_PARENT(TRUE)
if(!initial)
if(type in fish.fish_traits)
return FALSE
if(!isnull(fish_whitelist) && !(fish.type in fish_whitelist))
return FALSE
if(length(fish.fish_traits & incompatible_traits))
return FALSE
fish.fish_traits |= type
if(traits_to_add)
fish.add_traits(traits_to_add, FISH_TRAIT_DATUM)
if(length(reagents_to_add))
RegisterSignal(fish, COMSIG_GENERATE_REAGENTS_TO_ADD, PROC_REF(add_reagents))
return TRUE
/// Applies some special qualities to basic mobs generated by fish (i.e. chasm chrab --> young lobstrosity --> lobstrosity).
/datum/fish_trait/proc/apply_to_mob(mob/living/basic/mob)
SHOULD_CALL_PARENT(TRUE)
RegisterSignal(mob, COMSIG_MOB_CHANGED_TYPE, PROC_REF(on_transformed))
/datum/fish_trait/proc/remove_from_fish(obj/item/fish/fish)
SHOULD_CALL_PARENT(TRUE)
fish.fish_traits -= type
if(traits_to_add)
fish.remove_traits(traits_to_add, FISH_TRAIT_DATUM)
if(length(reagents_to_add))
UnregisterSignal(fish, COMSIG_GENERATE_REAGENTS_TO_ADD, PROC_REF(add_reagents))
/datum/fish_trait/proc/on_transformed(mob/source, mob/desired_mob)
SIGNAL_HANDLER
apply_to_mob(desired_mob)
/// Proc used by both the predator and necrophage traits.
/datum/fish_trait/proc/eat_fish(obj/item/fish/predator, obj/item/fish/prey)
var/message = prey.status == FISH_DEAD ? "[predator] eats [prey]'s carcass." : "[predator] hunts down and eats [prey]."
predator.loc.visible_message(span_warning(message))
SEND_SIGNAL(prey, COMSIG_FISH_EATEN_BY_OTHER_FISH, predator)
qdel(prey)
predator.sate_hunger()
/**
* Signal sent when we need to generate an abstract holder containing
* reagents to be transfered, usually as a result of the fish being eaten by someone
*/
/datum/fish_trait/proc/add_reagents(obj/item/fish/fish, list/reagents)
SIGNAL_HANDLER
for(var/reagent in reagents_to_add)
reagents[reagent] += reagents_to_add[reagent]
/// Proc that adds or changes the venomous when the fish size and/or weight are updated
/datum/fish_trait/proc/add_venom(obj/item/fish/source, venom_path, new_weight, mult = 0.25)
if(source.size)
var/old_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * mult, 0.1), mult)
source.RemoveElement(/datum/element/venomous, venom_path, old_amount)
var/new_amount = max(round((new_weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * mult, 0.1), mult)
source.AddElement(/datum/element/venomous, venom_path, new_amount)
/// Proc that changes the venomous element based on if the fish is alive or dead (basically dead fish are weaker).
/datum/fish_trait/proc/change_venom(obj/item/fish/source, venom_path, live_mult, dead_mult, just_remove = FALSE)
var/live_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * live_mult, 0.1), live_mult)
var/dead_amount = max(round((source.weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR) * dead_mult, 0.1), dead_mult)
var/is_dead = source.status == FISH_DEAD
source.RemoveElement(/datum/element/venomous, venom_path, is_dead ? live_amount : dead_amount, thrown_effect = TRUE)
if(just_remove)
return
source.AddElement(/datum/element/venomous, venom_path, is_dead ? dead_amount : live_amount, thrown_effect = TRUE)
/datum/fish_trait/wary
name = "Wary"
catalog_description = "This fish will avoid visible fish lines, cloaked line recommended."
/datum/fish_trait/wary/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
if(rod.alpha <= /datum/material/glass::alpha)
return
// Wary fish require transparent line or they're harder
if(!rod.line || !(rod.line.fishing_line_traits & FISHING_LINE_CLOAKED))
.[ADDITIVE_FISHING_MOD] += FISH_TRAIT_MINOR_DIFFICULTY_BOOST
/datum/fish_trait/shiny_lover
name = "Shiny Lover"
catalog_description = "This fish loves shiny things and money, shiny lure recommended."
/datum/fish_trait/shiny_lover/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
// These fish are easier to catch with shiny hook
if(HAS_TRAIT(rod, TRAIT_ROD_ATTRACT_SHINY_LOVERS) || (rod.bait?.get_item_credit_value() >= PAYCHECK_CREW))
.[ADDITIVE_FISHING_MOD] -= FISH_TRAIT_MINOR_DIFFICULTY_BOOST
/datum/fish_trait/shiny_lover/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
// These fish are harder to find without a shiny hook
if(!HAS_TRAIT(rod, TRAIT_ROD_ATTRACT_SHINY_LOVERS))
.[MULTIPLICATIVE_FISHING_MOD] = 0.5
/datum/fish_trait/picky_eater
name = "Picky Eater"
catalog_description = "This fish is very picky and will ignore low quality bait (unless it's amongst its favorites)."
/datum/fish_trait/picky_eater/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type)
. = ..()
var/list/fav_baits = SSfishing.fish_properties[fish_type][FISH_PROPERTIES_FAV_BAIT]
for(var/list/identifier in fav_baits)
if(identifier[FISH_BAIT_TYPE] != FISH_BAIT_FOODTYPE)
continue
if(is_matching_bait(rod, identifier)) //Bait or no bait, it's a yummy rod.
return
if(!rod.bait)
.[MULTIPLICATIVE_FISHING_MOD] = 0
return
if(HAS_TRAIT(rod.bait, TRAIT_OMNI_BAIT))
return
for(var/identifier in fav_baits)
if(is_matching_bait(rod.bait, identifier)) //we like this bait anyway
return
var/list/bad_baits = SSfishing.fish_properties[fish_type][FISH_PROPERTIES_BAD_BAIT]
for(var/identifier in bad_baits)
if(is_matching_bait(rod.bait, identifier)) //we hate this bait.
.[MULTIPLICATIVE_FISHING_MOD] = 0
return
if(!HAS_TRAIT(rod.bait, TRAIT_GOOD_QUALITY_BAIT) && !HAS_TRAIT(rod.bait, TRAIT_GREAT_QUALITY_BAIT))
.[MULTIPLICATIVE_FISHING_MOD] = 0
/datum/fish_trait/nocturnal
name = "Nocturnal"
catalog_description = "This fish avoids bright lights, fishing and storing in darkness recommended."
/datum/fish_trait/nocturnal/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type)
. = ..()
if(HAS_TRAIT(rod, TRAIT_ROD_IGNORE_ENVIRONMENT))
return
var/turf/turf = get_turf(location)
var/light_amount = turf?.get_lumcount()
if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD)
.[MULTIPLICATIVE_FISHING_MOD] = 0
/datum/fish_trait/nocturnal/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(check_light))
/datum/fish_trait/nocturnal/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_LIFE)
/datum/fish_trait/nocturnal/proc/check_light(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(!source.loc || (!HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM) && !isturf(source.loc)))
return
var/turf/turf = get_turf(source)
var/light_amount = turf.get_lumcount()
if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD)
source.damage_fish(0.5 * seconds_per_tick)
/datum/fish_trait/nocturnal/apply_to_mob(mob/living/basic/mob)
. = ..()
// Make sure the mob can also ee in the dark
mob.lighting_cutoff_red = min(mob.lighting_cutoff_red, 20)
mob.lighting_cutoff_green = min(mob.lighting_cutoff_green, 20)
mob.lighting_cutoff_blue = min(mob.lighting_cutoff_blue, 20)
mob.update_sight()
RegisterSignal(mob, COMSIG_LIVING_HANDLE_BREATHING, PROC_REF(on_non_stasis_life))
/datum/fish_trait/nocturnal/proc/on_non_stasis_life(mob/living/basic/mob, seconds_per_tick = SSMOBS_DT)
SIGNAL_HANDLER
var/turf/our_turf = mob.loc
if(!isturf(our_turf))
return
var/light_amount = our_turf.get_lumcount()
if (light_amount < SHADOW_SPECIES_LIGHT_THRESHOLD) //heal in the dark
mob.apply_status_effect(/datum/status_effect/shadow/regeneration)
/datum/fish_trait/heavy
name = "Demersal"
catalog_description = "This fish tends to stay near the waterbed."
/datum/fish_trait/heavy/apply_to_mob(mob/living/basic/mob)
. = ..()
mob.add_movespeed_modifier(/datum/movespeed_modifier/heavy_fish)
mob.maxHealth *= 1.5
mob.health *= 1.5
mob.melee_damage_lower *= 1.3
mob.melee_damage_upper *= 1.3
mob.obj_damage *= 1.3
/datum/fish_trait/heavy/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
minigame.mover.fish_idle_velocity -= 10
/datum/fish_trait/carnivore
name = "Carnivore"
catalog_description = "This fish can only be baited with meat."
incompatible_traits = list(/datum/fish_trait/vegan)
/datum/fish_trait/carnivore/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type)
. = ..()
if(IS_EDIBLE(rod)) //who cares about the bait, that fishing rod is yummy!
return
if(!rod.bait)
.[MULTIPLICATIVE_FISHING_MOD] = 0
return
if(HAS_TRAIT(rod.bait, TRAIT_OMNI_BAIT))
return
var/list/bait_identifier = list(
FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE,
FISH_BAIT_VALUE = MEAT|SEAFOOD|BUGS,
)
if(!is_matching_bait(rod.bait, bait_identifier))
.[MULTIPLICATIVE_FISHING_MOD] = 0
/datum/fish_trait/vegan
name = "Herbivore"
catalog_description = "This fish can only be baited with fresh produce."
incompatible_traits = list(/datum/fish_trait/carnivore, /datum/fish_trait/predator, /datum/fish_trait/necrophage)
/datum/fish_trait/vegan/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type)
. = ..()
if(istype(rod.get_master_material(), /datum/material/bamboo)) //bamboo is technically grass.
return
if(!rod.bait)
.[MULTIPLICATIVE_FISHING_MOD] = 0
return
if(HAS_TRAIT(rod.bait, TRAIT_OMNI_BAIT))
return
if(istype(rod.bait, /obj/item/food/grown))
return
var/list/bait_liked_identifier = list(
FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE,
FISH_BAIT_VALUE = VEGETABLES|FRUIT,
)
var/list/bait_hated_identifier = list(
FISH_BAIT_TYPE = FISH_BAIT_FOODTYPE,
FISH_BAIT_VALUE = MEAT|SEAFOOD|GORE|BUGS|DAIRY,
)
if(!is_matching_bait(rod.bait, bait_liked_identifier) || is_matching_bait(rod.bait, bait_hated_identifier))
.[MULTIPLICATIVE_FISHING_MOD] = 0
/datum/fish_trait/emulsijack
name = "Emulsifier"
catalog_description = "This fish emits an invisible toxin that emulsifies other fish for it to feed on."
var/list/resistance_traits = list(TRAIT_RESIST_EMULSIFY, TRAIT_FISH_TOXIN_IMMUNE)
traits_to_add = list(TRAIT_RESIST_EMULSIFY)
/datum/fish_trait/emulsijack/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(emulsify))
/datum/fish_trait/emulsijack/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_LIFE)
/datum/fish_trait/emulsijack/proc/emulsify(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(!source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM))
return
var/emulsified = FALSE
for(var/obj/item/fish/victim in source.loc)
for(var/trait in resistance_traits)
if(HAS_TRAIT(victim, trait))
continue
victim.damage_fish(3 * seconds_per_tick) //the victim may heal a bit but this will quickly kill
emulsified = TRUE
if(emulsified)
source.repair_damage(3 * seconds_per_tick)
source.sate_hunger()
/datum/fish_trait/emulsijack/apply_to_mob(mob/living/basic/mob)
. = ..()
RegisterSignal(mob, COMSIG_LIVING_HANDLE_BREATHING, PROC_REF(on_non_stasis_life))
/datum/fish_trait/emulsijack/proc/on_non_stasis_life(mob/living/basic/mob, seconds_per_tick = SSMOBS_DT)
SIGNAL_HANDLER
var/turf/open/our_turf = get_turf(mob)
if(our_turf.return_air().return_pressure() > ONE_ATMOSPHERE * 1.5) //put a cap otherwise closed spaces may overpressurize
return
var/datum/gas_mixture/stench = new
stench.set_gas(/datum/gas/miasma, MIASMA_CORPSE_MOLES * 2 * seconds_per_tick)
stench.temperature = mob.bodytemperature
our_turf.assume_air(stench)
/datum/fish_trait/emulsijack/psychic
name = "Psychic Aura"
catalog_description = "This fish emits an almost unblockable psychic aura that assaults minds, slowly killing all nearby fish and making humanoids have a bad time."
resistance_traits = list(TRAIT_RESIST_PSYCHIC)
traits_to_add = list(TRAIT_RESIST_PSYCHIC)
/datum/fish_trait/emulsijack/psychic/on_non_stasis_life(mob/living/basic/mob, seconds_per_tick)
return
/datum/fish_trait/necrophage
name = "Necrophage"
catalog_description = "This fish will eat carcasses of dead fish when hungry."
incompatible_traits = list(/datum/fish_trait/vegan)
/datum/fish_trait/necrophage/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(eat_dead_fishes))
/datum/fish_trait/necrophage/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_LIFE)
/datum/fish_trait/necrophage/proc/eat_dead_fishes(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(source.get_hunger() < 0.75 || !source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM))
return
for(var/obj/item/fish/victim in source.loc)
if(victim.status != FISH_DEAD || victim == source || HAS_TRAIT(victim, TRAIT_YUCKY_FISH))
continue
eat_fish(source, victim)
return
/datum/fish_trait/parthenogenesis
name = "Parthenogenesis"
catalog_description = "This fish can reproduce asexually, without the need of a mate."
inheritability = 40
traits_to_add = list(TRAIT_FISH_SELF_REPRODUCE)
/**
* Useful for those species with the parthenogenesis trait if you don't want them to mate with each other,
* or for similar shenanigans, I don't know.
* Otherwise you could just set the stable_population to 1.
*/
/datum/fish_trait/no_mating
name = "Mateless"
catalog_description = "This fish cannot reproduce with other fishes."
incompatible_traits = list(/datum/fish_trait/crossbreeder)
spontaneous_manifest_types = list(
/obj/item/fish/fryish = 100,
/obj/item/fish/fryish/fritterish = 0,
/obj/item/fish/fryish/nessie = 0
)
traits_to_add = list(TRAIT_FISH_NO_MATING)
///Prevent offsprings of fish with this trait from being of the same type (unless self-mating or the partner also has the trait)
/datum/fish_trait/recessive
name = "Recessive"
catalog_description = "If crossbred, offsprings will always be of the mate species, unless it also possess the trait."
inheritability = 0
traits_to_add = list(TRAIT_FISH_RECESSIVE)
/datum/fish_trait/revival
name = "Self-Revival"
catalog_description = "This fish shows a peculiar ability of reviving itself a minute or two after death."
spontaneous_manifest_types = list(/obj/item/fish/boned = 100, /obj/item/fish/mastodon = 100)
/datum/fish_trait/revival/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(check_status))
/datum/fish_trait/revival/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, FISH_TRAIT_DATUM)
/datum/fish_trait/revival/proc/check_status(obj/item/fish/source)
SIGNAL_HANDLER
if(source.status == FISH_DEAD)
addtimer(CALLBACK(src, PROC_REF(revive), WEAKREF(source)), rand(1 MINUTES, 2 MINUTES))
/datum/fish_trait/revival/proc/revive(datum/weakref/fish_ref)
var/obj/item/fish/source = fish_ref.resolve()
if(QDELETED(source) || source.status != FISH_DEAD || !(type in source.fish_traits))
return
source.set_status(FISH_ALIVE)
var/message = span_nicegreen("[source] twitches. It's alive!")
if(source.loc && HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM))
source.loc.visible_message(message)
else
source.visible_message(message)
/datum/fish_trait/revival/apply_to_mob(mob/living/basic/mob)
. = ..()
mob.AddComponent(/datum/component/regenerator, regeneration_delay = 6 SECONDS, brute_per_second = 2 SECONDS, outline_colour = COLOR_BLUE)
/datum/fish_trait/predator
name = "Predator"
catalog_description = "It's a predatory fish. It'll hunt down and eat live fishes of smaller size when hungry."
incompatible_traits = list(/datum/fish_trait/vegan)
/datum/fish_trait/predator/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type)
. = ..()
if(isfish(rod.bait))
.[MULTIPLICATIVE_FISHING_MOD] *= 2
/datum/fish_trait/predator/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(eat_fishes))
/datum/fish_trait/predator/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_LIFE)
/datum/fish_trait/predator/proc/eat_fishes(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(source.get_hunger() < 0.75 || !source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM))
return
for(var/obj/item/fish/victim as anything in source.get_aquarium_fishes(TRUE, source))
if(victim.size >= source.size * 0.7) // It's a big fish eat small fish world
continue
if(victim.status != FISH_ALIVE || HAS_TRAIT(victim, TRAIT_YUCKY_FISH) || SPT_PROB(80, seconds_per_tick))
continue
eat_fish(source, victim)
return
/datum/fish_trait/yucky
name = "Yucky"
catalog_description = "This fish tastes so repulsive, other fishes won't try to eat it."
reagents_to_add = list(/datum/reagent/yuck = 1.2)
traits_to_add = list(TRAIT_YUCKY_FISH)
/datum/fish_trait/toxic
name = "Toxic"
catalog_description = "This fish contains toxins. Feeding it to predatory fishes or people is not recommended."
reagents_to_add = list(/datum/reagent/toxin/tetrodotoxin = 1)
infusion_entry = /datum/infuser_entry/ttx_healing
///The amount of venom injected if the fish has a stinger is multiplied by this value.
var/venom_mult = 1
/datum/fish_trait/toxic/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, PROC_REF(make_venomous))
RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_status_change))
RegisterSignal(fish, COMSIG_FISH_EATEN_BY_OTHER_FISH, PROC_REF(on_eaten))
if(!initial)
make_venomous(fish, fish.size, fish.weight)
/datum/fish_trait/toxic/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, list(COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, COMSIG_FISH_STATUS_CHANGED, COMSIG_FISH_EATEN_BY_OTHER_FISH))
change_venom(fish, reagents_to_add[1], 0.25 * venom_mult, 0.1 * venom_mult, just_remove = TRUE)
/datum/fish_trait/toxic/proc/make_venomous(obj/item/fish/source, new_size, new_weight)
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
return
add_venom(source, reagents_to_add[1], new_weight, mult = (source.status == FISH_DEAD ? 0.1 : 0.25) * venom_mult)
/datum/fish_trait/toxic/proc/on_status_change(obj/item/fish/source)
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
return
change_venom(source, reagents_to_add[1], 0.25 * venom_mult, 0.1 * venom_mult)
/datum/fish_trait/toxic/proc/on_eaten(obj/item/fish/source, obj/item/fish/predator)
if(HAS_TRAIT(predator, TRAIT_FISH_TOXIN_IMMUNE))
return
RegisterSignal(predator, COMSIG_FISH_LIFE, PROC_REF(damage_predator), TRUE)
RegisterSignal(predator, COMSIG_FISH_STATUS_CHANGED, PROC_REF(stop_damaging), TRUE)
/datum/fish_trait/toxic/proc/damage_predator(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
source.damage_fish(3 * seconds_per_tick)
/datum/fish_trait/toxic/proc/stop_damaging(obj/item/fish/source)
SIGNAL_HANDLER
if(source.status == FISH_DEAD)
UnregisterSignal(source, list(COMSIG_FISH_LIFE, COMSIG_FISH_STATUS_CHANGED))
/datum/fish_trait/toxic/apply_to_mob(mob/living/basic/mob)
. = ..()
mob.AddElement(/datum/element/venomous, reagents_to_add[1], 0.5 * mob.mob_size * venom_mult)
/datum/fish_trait/toxic/carpotoxin
name = "Carpotoxic"
catalog_description = "This fish contains carpotoxin. Definitely not safe for consumption."
reagents_to_add = list(/datum/reagent/toxin/carpotoxin = 4)
infusion_entry = null
venom_mult = 6
/datum/fish_trait/toxin_immunity
name = "Toxin Immunity"
catalog_description = "This fish has developed an ample-spected immunity to toxins."
traits_to_add = list(TRAIT_FISH_TOXIN_IMMUNE)
/datum/fish_trait/crossbreeder
name = "Crossbreeder"
catalog_description = "This fish's adaptive genetics allows it to crossbreed with other fish species."
inheritability = 40
incompatible_traits = list(/datum/fish_trait/no_mating)
traits_to_add = list(TRAIT_FISH_CROSSBREEDER)
/datum/fish_trait/territorial
name = "Territorial"
catalog_description = "This fish will start attacking other fish if the aquarium has five or more."
/datum/fish_trait/territorial/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(try_attack_fish))
/datum/fish_trait/territorial/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_LIFE)
/datum/fish_trait/territorial/proc/try_attack_fish(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(!source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM) || !SPT_PROB(1, seconds_per_tick))
return
var/list/fishes = source.get_aquarium_fishes(TRUE, source)
if(length(fishes) < 5)
return
for(var/obj/item/fish/victim as anything in source.get_aquarium_fishes(TRUE, source))
if(victim.status != FISH_ALIVE)
continue
source.loc.visible_message(span_warning("[source] violently [pick("whips", "bites", "attacks", "slams")] [victim]"))
var/damage = round(rand(4, 20) * (source.size / victim.size)) //smaller fishes take extra damage.
victim.damage_fish(damage)
return
/datum/fish_trait/lubed
name = "Lubed"
spontaneous_manifest_types = list(/obj/item/fish/clownfish/lube = 100)
catalog_description = "This fish exudes a viscous, slippery lubrificant. It's recommended not to step on it."
added_difficulty = 5
reagents_to_add = list(/datum/reagent/lube = 1.2)
/datum/fish_trait/lubed/catch_weight_mod(obj/item/fishing_rod/rod, mob/fisherman, atom/location, obj/item/fish/fish_type)
. = ..()
if(istype(rod.get_master_material(), /datum/material/bananium)) //x5 chance of catching lubefish & co with a bananium rod.
.[MULTIPLICATIVE_FISHING_MOD] *= 5
/datum/fish_trait/lubed/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
fish.AddComponent(/datum/component/slippery, 8 SECONDS, SLIDE|GALOSHES_DONT_HELP)
/datum/fish_trait/lubed/remove_from_fish(obj/item/fish/fish)
. = ..()
qdel(fish.GetComponent(/datum/component/slippery))
/datum/fish_trait/lubed/apply_to_mob(mob/living/basic/mob)
. = ..()
mob.AddElement(/datum/element/lube_walking)
/datum/fish_trait/lubed/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
minigame.reeling_velocity *= 1.4
minigame.gravity_velocity *= 1.4
/datum/fish_trait/amphibious
name = "Amphibious"
catalog_description = "This fish has developed a primitive adaptation to life on both land and water."
infusion_entry = /datum/infuser_entry/amphibious
traits_to_add = list(TRAIT_FISH_AMPHIBIOUS)
/datum/fish_trait/amphibious/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
if(fish.required_fluid_type == AQUARIUM_FLUID_AIR)
fish.required_fluid_type = AQUARIUM_FLUID_FRESHWATER
/datum/fish_trait/amphibious/remove_from_fish(obj/item/fish/fish)
. = ..()
fish.required_fluid_type = initial(fish.required_fluid_type)
/datum/fish_trait/amphibious/apply_to_mob(mob/living/basic/mob)
. = ..()
ADD_TRAIT(mob, TRAIT_NODROWN, FISH_TRAIT_DATUM)
/datum/fish_trait/mixotroph
name = "Mixotroph"
catalog_description = "This fish is capable of substaining itself by producing its own sources of energy (food)."
incompatible_traits = list(/datum/fish_trait/predator, /datum/fish_trait/necrophage)
traits_to_add = list(TRAIT_FISH_NO_HUNGER)
/datum/fish_trait/antigrav
name = "Anti-Gravity"
catalog_description = "This fish will invert the gravity of the bait at random. May fall upward outside after being caught."
added_difficulty = 20
reagents_to_add = list(/datum/reagent/gravitum = 2.3)
/datum/fish_trait/antigrav/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
minigame.special_effects |= FISHING_MINIGAME_RULE_ANTIGRAV
/datum/fish_trait/antigrav/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
fish.AddElement(/datum/element/forced_gravity, NEGATIVE_GRAVITY)
/datum/fish_trait/antigrav/remove_from_fish(obj/item/fish/fish)
. = ..()
fish.RemoveElement(/datum/element/forced_gravity, NEGATIVE_GRAVITY)
/datum/fish_trait/antigrav/apply_to_mob(mob/living/basic/mob)
. = ..()
mob.add_traits(list(TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_SPACEWALK), FISH_TRAIT_DATUM)
mob.AddElement(/datum/element/simple_flying)
///Anxiety means the fish will die if in a location with more than 3 fish (including itself)
///This is just barely enough to crossbreed out of anxiety, but it severely limits the potential of
/datum/fish_trait/anxiety
name = "Anxiety"
catalog_description = "This fish tends to die of stress when forced to be around too many other fish."
/datum/fish_trait/anxiety/difficulty_mod(obj/item/fishing_rod/rod, mob/fisherman)
. = ..()
// Anxious fish are easier with a cloaked line.
if(rod.line && (rod.line.fishing_line_traits & FISHING_LINE_CLOAKED))
.[ADDITIVE_FISHING_MOD] -= FISH_TRAIT_MINOR_DIFFICULTY_BOOST
/datum/fish_trait/anxiety/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(on_fish_life))
/datum/fish_trait/anxiety/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_LIFE)
///signal sent when the anxiety fish is fed, killing it if sharing contents with too many fish.
/datum/fish_trait/anxiety/proc/on_fish_life(obj/item/fish/fish, seconds_per_tick)
SIGNAL_HANDLER
var/fish_tolerance = 3
if(!fish.loc || fish.status == FISH_DEAD)
return
for(var/obj/item/fish/other_fish in fish.loc.contents)
if(fish_tolerance <= 0)
fish.loc.visible_message(span_warning("[fish] seems to freak out for a moment, then it stops moving..."))
fish.set_status(FISH_DEAD)
return
fish_tolerance -= 1
/datum/fish_trait/electrogenesis
name = "Electrogenesis"
catalog_description = "This fish is electroreceptive, and will generate electric fields. Can be harnessed inside a bioelectric generator."
reagents_to_add = list(/datum/reagent/consumable/liquidelectricity = 1.5)
traits_to_add = list(TRAIT_FISH_ELECTROGENESIS)
/datum/fish_trait/electrogenesis/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_FORCE_UPDATED, PROC_REF(on_force_updated))
RegisterSignals(fish, list(COMSIG_ITEM_FRIED, TRAIT_FOOD_BBQ_GRILLED), PROC_REF(on_fish_cooked))
if(!initial)
fish.update_fish_force()
/datum/fish_trait/electrogenesis/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, list(COMSIG_FISH_FORCE_UPDATED, COMSIG_ITEM_FRIED, TRAIT_FOOD_BBQ_GRILLED))
fish.update_fish_force()
/datum/fish_trait/electrogenesis/proc/on_fish_cooked(obj/item/fish/fish, cooked_time)
SIGNAL_HANDLER
if(cooked_time >= FISH_SAFE_COOKING_DURATION)
fish.reagents.del_reagent(/datum/reagent/consumable/liquidelectricity)
else
fish.reagents.multiply(0.66, /datum/reagent/consumable/liquidelectricity)
/datum/fish_trait/electrogenesis/add_reagents(obj/item/fish/fish, list/reagents)
. = ..()
if(HAS_TRAIT(fish, TRAIT_FISH_WELL_COOKED)) // Cooking it well removes all liquid electricity
reagents -= /datum/reagent/consumable/liquidelectricity
else
reagents -= /datum/reagent/blood
//Otherwise, undercooking it will remove 2/3 of it.
if(!HAS_TRAIT(fish, TRAIT_FOOD_FRIED) && !HAS_TRAIT(fish, TRAIT_FOOD_BBQ_GRILLED))
reagents[/datum/reagent/consumable/liquidelectricity] -= 1
/datum/fish_trait/electrogenesis/proc/on_force_updated(obj/item/fish/fish, weight_rank, bonus_or_malus)
SIGNAL_HANDLER
if(fish.status == FISH_ALIVE)
fish.force += 10 - fish.w_class
fish.damtype = BURN
fish.attack_verb_continuous = list("shocks", "zaps")
fish.attack_verb_simple = list("shock", "zap")
fish.hitsound = 'sound/effects/sparks/sparks4.ogg'
/datum/fish_trait/electrogenesis/apply_to_mob(mob/living/basic/mob)
. = ..()
ADD_TRAIT(mob, TRAIT_SHOCKIMMUNE, FISH_TRAIT_DATUM)
mob.grant_actions_by_list(list(/datum/action/cooldown/mob_cooldown/charge_apc))
mob.AddElement(/datum/element/venomous, /datum/reagent/teslium, 3 * mob.mob_size)
/datum/fish_trait/stunted
name = "Stunted Growth"
catalog_description = "This chrab's development is stunted, and will not properly reach adulthood."
spontaneous_manifest_types = list(/obj/item/fish/chasm_crab = 12)
fish_whitelist = list(/obj/item/fish/chasm_crab, /obj/item/fish/chasm_crab/ice)
/datum/fish_trait/stunted/apply_to_mob(mob/living/basic/mob)
. = ..()
qdel(mob.GetComponent(/datum/component/growth_and_differentiation))
/datum/fish_trait/stinger
name = "Stinger"
catalog_description = "This fish is equipped with a sharp stringer or bill capable of delivering damage and toxins."
spontaneous_manifest_types = list(
/obj/item/fish/stingray = 100,
/obj/item/fish/swordfish = 100,
/obj/item/fish/chainsawfish = 100,
/obj/item/fish/pike/armored = 100,
)
traits_to_add = list(TRAIT_FISH_STINGER)
/datum/fish_trait/stinger/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_FORCE_UPDATED, PROC_REF(on_force_updated))
if(!initial)
fish.update_fish_force()
/datum/fish_trait/stinger/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, COMSIG_FISH_FORCE_UPDATED)
fish.update_fish_force()
/datum/fish_trait/stinger/proc/on_force_updated(obj/item/fish/fish, weight_rank, bonus_or_malus)
SIGNAL_HANDLER
fish.force += 1 + fish.w_class + bonus_or_malus
/datum/fish_trait/toxic_barbs
name = "Toxic Barbs"
catalog_description = "The stinger or bill of this fish is coated in a simple, yet effective venom."
spontaneous_manifest_types = list(/obj/item/fish/stingray = 35)
/datum/fish_trait/toxic_barbs/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, PROC_REF(make_venomous))
RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_status_change))
if(!initial)
make_venomous(fish, fish.size, fish.weight)
/datum/fish_trait/toxic_barbs/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, list(COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, COMSIG_FISH_STATUS_CHANGED))
change_venom(fish, /datum/reagent/toxin/venom, 0.7, 0.3, just_remove = TRUE)
/datum/fish_trait/toxic_barbs/proc/make_venomous(obj/item/fish/source, new_size, new_weight)
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
///Remove the trait from the fish so it doesn't show on the analyzer as it doesn't do anything on stingerless ones.
remove_from_fish(source)
return
add_venom(source, /datum/reagent/toxin/venom, new_weight, mult = source.status == FISH_DEAD ? 0.3 : 0.7)
/datum/fish_trait/toxic_barbs/proc/on_status_change(obj/item/fish/source)
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
return
change_venom(source, /datum/reagent/toxin/venom, 0.7, 0.3)
/datum/fish_trait/hallucinogenic
name = "Hallucinogenic"
catalog_description = "This fish is coated with hallucinogenic neurotoxin. We advise cooking it before consumption."
reagents_to_add = list(/datum/reagent/toxin/mindbreaker/fish = 1)
/datum/fish_trait/hallucinogenic/add_reagents(obj/item/fish/fish, list/reagents)
if(!HAS_TRAIT(src, TRAIT_FOOD_FRIED) && !HAS_TRAIT(src, TRAIT_FOOD_BBQ_GRILLED))
return ..()
/datum/fish_trait/hallucinogenic/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, PROC_REF(make_venomous))
RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_status_change))
if(!initial)
make_venomous(fish, fish.size, fish.weight)
/datum/fish_trait/hallucinogenic/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, list(COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, COMSIG_FISH_STATUS_CHANGED))
change_venom(fish, /datum/reagent/toxin/mindbreaker/fish, 0.7, 0.3, just_remove = TRUE)
/datum/fish_trait/hallucinogenic/proc/make_venomous(obj/item/fish/source, new_size, new_weight)
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
///Remove the trait from the fish so it doesn't show on the analyzer as it doesn't do anything on stingerless ones.
source.fish_traits -= type
UnregisterSignal(source, list(COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, COMSIG_FISH_STATUS_CHANGED))
return
add_venom(source, /datum/reagent/toxin/mindbreaker/fish, new_weight, mult = source.status == FISH_DEAD ? 0.3 : 0.7)
/datum/fish_trait/hallucinogenic/proc/on_status_change(obj/item/fish/source)
SIGNAL_HANDLER
if(!HAS_TRAIT(source, TRAIT_FISH_STINGER))
return
change_venom(source, /datum/reagent/toxin/mindbreaker/fish, 0.7, 0.3)
/datum/fish_trait/ink
name = "Ink Production"
catalog_description = "This fish possess a sac that produces ink."
spontaneous_manifest_types = list(/obj/item/fish/squid = 35)
infusion_entry = /datum/infuser_entry/squid
/datum/fish_trait/ink/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_ATOM_PROCESSED, PROC_REF(on_process))
RegisterSignal(fish, COMSIG_ITEM_ATTACK_ZONE, PROC_REF(attacked_someone))
/datum/fish_trait/ink/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, list(COMSIG_ATOM_PROCESSED, COMSIG_ITEM_ATTACK_ZONE))
/datum/fish_trait/ink/proc/attacked_someone(obj/item/fish/source, mob/living/target, mob/living/user, zone)
SIGNAL_HANDLER
if(HAS_TRAIT(source, TRAIT_FISH_INK_ON_COOLDOWN) || source.status == FISH_DEAD)
return
if(!iscarbon(target) || target.get_bodypart(BODY_ZONE_HEAD))
target.adjust_temp_blindness_up_to(4 SECONDS, 8 SECONDS)
target.adjust_confusion_up_to(1.5 SECONDS, 4 SECONDS)
target.AddComponent(/datum/component/face_decal/splat, \
color = COLOR_NEARLY_ALL_BLACK, \
memory_type = /datum/memory/witnessed_inking, \
mood_event_type = /datum/mood_event/inked, \
)
target.visible_message(span_warning("[target] is inked by [source]!"), span_userdanger("You've been inked by [source]!"))
playsound(target, SFX_DESECRATION, 50, TRUE)
ADD_TRAIT(source, TRAIT_FISH_INK_ON_COOLDOWN, FISH_TRAIT_DATUM)
addtimer(TRAIT_CALLBACK_REMOVE(source, TRAIT_FISH_INK_ON_COOLDOWN, FISH_TRAIT_DATUM), 9 SECONDS)
/datum/fish_trait/ink/proc/on_process(obj/item/fish/source, mob/living/user, obj/item/process_item, list/results)
SIGNAL_HANDLER
new /obj/item/food/ink_sac(source.drop_location())
/datum/fish_trait/camouflage
name = "Camouflage"
catalog_description = "This fish possess the ability to blend with its surroundings."
spontaneous_manifest_types = list(/obj/item/fish/squid = 35)
added_difficulty = 5
/datum/fish_trait/camouflage/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
minigame.special_effects |= FISHING_MINIGAME_RULE_CAMO
/datum/fish_trait/camouflage/apply_to_fish(obj/item/fish/fish, initial = TRUE)
. = ..()
RegisterSignal(fish, COMSIG_FISH_LIFE, PROC_REF(fade_out))
RegisterSignals(fish, list(COMSIG_MOVABLE_MOVED, COMSIG_FISH_STATUS_CHANGED), PROC_REF(reset_alpha))
/datum/fish_trait/camouflage/remove_from_fish(obj/item/fish/fish)
. = ..()
UnregisterSignal(fish, list(COMSIG_FISH_LIFE, COMSIG_MOVABLE_MOVED, COMSIG_FISH_STATUS_CHANGED))
reset_alpha(fish)
/datum/fish_trait/camouflage/proc/fade_out(obj/item/fish/source, seconds_per_tick)
SIGNAL_HANDLER
if(source.status == FISH_DEAD || source.last_move + 5 SECONDS >= world.time)
return
source.alpha = max(source.alpha - 10 * seconds_per_tick, 10)
/datum/fish_trait/camouflage/proc/reset_alpha(obj/item/fish/source)
SIGNAL_HANDLER
if(QDELETED(source))
return
var/init_alpha = initial(source.alpha)
if(init_alpha != source.alpha)
animate(source, alpha = init_alpha, time = 1.2 SECONDS, easing = CIRCULAR_EASING|EASE_OUT)