Files
Waterpig bb70889f6e TG Upstream Part 1
3591 individual conflicts

Update build.js

Update install_node.sh

Update byond.js

oh my fucking god

hat

slow

huh

holy shit

we all fall down

2 more I missed

2900 individual conflicts

2700 Individual conflicts

replaces yarn file with tg version, bumping us down to 2200-ish

Down to 2000 individual conflicts

140 down

mmm

aaaaaaaaaaaaaaaaaaa

not yt

575

soon

900 individual conflicts

600 individual conflicts, 121 file conflicts

im not okay

160 across 19 files

29 in 4 files

0 conflicts, compiletime fix time

some minor incap stuff

missed ticks

weird dupe definition stuff

missed ticks 2

incap fixes

undefs and pie fix

Radio update and some extra minor stuff

returns a single override

no more dupe definitions, 175 compiletime errors

Unticked file fix

sound and emote stuff

honk and more radio stuff
2024-10-19 08:04:33 -07:00

826 lines
30 KiB
Plaintext

#define FISH_SAD 0
#define FISH_NEUTRAL 1
#define FISH_SATISFIED 2
#define FISH_HAPPY 3
#define FISH_VERY_HAPPY 4
// Fish path used for autogenerated fish
/obj/item/fish
name = "generic looking aquarium fish"
desc = "very bland"
icon = 'icons/obj/aquarium/fish.dmi'
lefthand_file = 'icons/mob/inhands/fish_lefthand.dmi'
righthand_file = 'icons/mob/inhands/fish_righthand.dmi'
force = 6
throwforce = 6
throw_range = 8
attack_verb_continuous = list("slaps", "whacks")
attack_verb_simple = list("slap", "whack")
hitsound = SFX_DEFAULT_FISH_SLAP
drop_sound = 'sound/creatures/fish/fish_drop1.ogg'
pickup_sound = SFX_FISH_PICKUP
sound_vary = TRUE
///The grind results of the fish. They scale with the weight of the fish.
grind_results = list(/datum/reagent/blood = 5, /datum/reagent/consumable/liquidgibs = 5)
obj_flags = UNIQUE_RENAME
item_flags = IMMUTABLE_SLOW|SLOWS_WHILE_IN_HAND
/// width of aquarium visual icon
var/sprite_width
/// height of aquarium visual icon
var/sprite_height
///this icon file will be used for in-aquarium visual for the fish
var/dedicated_in_aquarium_icon = 'icons/obj/aquarium/fish.dmi'
/**
* The icon_state that will be used for in-aquarium visual for the fish
* If not set, "[initial(icon_state)]_small" will be used instead
*/
var/dedicated_in_aquarium_icon_state
/// If present aquarium visual will be of this color
var/aquarium_vc_color
/// Required fluid type for this fish to live.
var/required_fluid_type = AQUARIUM_FLUID_FRESHWATER
/// Required minimum temperature for the fish to live.
var/required_temperature_min = MIN_AQUARIUM_TEMP
/// Maximum possible temperature for the fish to live.
var/required_temperature_max = MAX_AQUARIUM_TEMP
/// What type of reagent this fish needs to be fed.
var/datum/reagent/food = /datum/reagent/consumable/nutriment
/// How often the fish needs to be fed
var/feeding_frequency = 5 MINUTES
/// Time of last feedeing
var/last_feeding
/// Fish status
var/status = FISH_ALIVE
///icon used when the fish is dead, ifset.
var/icon_state_dead
///If this fish should do the flopping animation
var/do_flop_animation = TRUE
/// Current fish health. Dies at 0.
var/health = 100
/// The message shown when the fish dies.
var/death_text = "%SRC dies."
/// Should this fish type show in fish catalog
var/show_in_catalog = TRUE
/// How rare this fish is in the random cases
var/random_case_rarity = FISH_RARITY_BASIC
/// Fish autogenerated from this behaviour will be processable into this
var/fillet_type = /obj/item/food/fishmeat
/// number of fillets given by the fish. It scales with its size.
var/num_fillets = 1
/// Won't breed more than this amount in single aquarium.
var/stable_population = 1
/// The time limit before new fish can be created
var/breeding_wait
/// How long it takes to produce new fish
var/breeding_timeout = 2 MINUTES
/// If set, the fish can also breed with these fishes types
var/list/compatible_types
/// A list of possible evolutions. If set, offsprings may be of a different, new fish type if conditions are met.
var/list/evolution_types
// Fishing related properties
/**
* List of fish trait types, these may modify probabilty/difficulty depending on rod/user properties
* or dictate how the fish behaves or some of its qualities.
*/
var/list/fish_traits = list()
/// path to datums that dictate how the fish moves during the fishing minigame
var/fish_movement_type = /datum/fish_movement
/// Base additive modifier to fishing difficulty
var/fishing_difficulty_modifier = 0
/**
* Bait identifiers that make catching this fish easier and more likely
* Bait identifiers: Path | Trait | list("Type"="Foodtype","Value"= Food Type Flag like [MEAT])
*/
var/list/favorite_bait = list()
/**
* Bait identifiers that make catching this fish harder and less likely
* Bait identifiers: Path | Trait | list("Type"="Foodtype","Value"= Food Type Flag like [MEAT])
*/
var/list/disliked_bait = list()
/// Size in centimeters. Null until update_size_and_weight is called. Number of fillets and w_class scale with it.
var/size
/// Average size for this fish type in centimeters. Will be used as gaussian distribution with 20% deviation for fishing, bought fish are always standard size
var/average_size = 50
/// Weight in grams. Null until update_size_and_weight is called. Grind results scale with it. Don't think too hard how a trout could fit in a blender.
var/weight
/// Average weight for this fish type in grams
var/average_weight = 1000
///The general deviation from the average weight and size this fish has in the wild
var/weight_size_deviation = 0.2
/// When outside of an aquarium, these gases that are checked (as well as pressure and temp) to assert if the environment is safe or not.
var/list/safe_air_limits = list(
/datum/gas/oxygen = list(12, 100),
/datum/gas/nitrogen,
/datum/gas/carbon_dioxide = list(0, 10),
/datum/gas/water_vapor,
)
/// Outside of an aquarium, the pressure needs to be within these two variables for the environment to be safe.
var/min_pressure = WARNING_LOW_PRESSURE
var/max_pressure = HAZARD_HIGH_PRESSURE
/// If this fish type counts towards the Fish Species Scanning experiments
var/experisci_scannable = TRUE
/// cooldown on creating tesla zaps
COOLDOWN_DECLARE(electrogenesis_cooldown)
/// power of the tesla zap created by the fish in a bioelectric generator
var/electrogenesis_power = 10 MEGA JOULES
/// The beauty this fish provides to the aquarium it's inserted in.
var/beauty = FISH_BEAUTY_GENERIC
///have we recently pet this fish
var/recently_petted = FALSE
/obj/item/fish/Initialize(mapload, apply_qualities = TRUE)
. = ..()
AddComponent(/datum/component/aquarium_content, icon, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STIRRED), beauty)
RegisterSignal(src, COMSIG_ATOM_ON_LAZARUS_INJECTOR, PROC_REF(use_lazarus))
if(do_flop_animation)
RegisterSignal(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, PROC_REF(on_temp_animation))
check_environment()
if(status != FISH_DEAD)
START_PROCESSING(SSobj, src)
//stops new fish from being able to reproduce right away.
breeding_wait = world.time + (breeding_timeout * NEW_FISH_BREEDING_TIMEOUT_MULT)
last_feeding = world.time - (feeding_frequency * NEW_FISH_LAST_FEEDING_MULT)
if(apply_qualities)
apply_traits() //Make sure traits are applied before size and weight.
update_size_and_weight()
register_evolutions()
/obj/item/fish/update_icon_state()
if(status == FISH_DEAD && icon_state_dead)
icon_state = icon_state_dead
else
icon_state = initial(icon_state)
return ..()
/obj/item/fish/attackby(obj/item/item, mob/living/user, params)
if(!istype(item, /obj/item/fish_feed))
return ..()
if(!item.reagents.total_volume)
balloon_alert(user, "[item] is empty!")
return TRUE
if(status == FISH_DEAD)
balloon_alert(user, "[src] is dead!")
return TRUE
feed(item.reagents)
balloon_alert(user, "fed [src]")
return TRUE
/obj/item/fish/examine(mob/user)
. = ..()
if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_DEEPER_FISH))
if(status == FISH_DEAD)
. += span_deadsay("it's dead.")
var/list/warnings = list()
if(is_hungry())
warnings += "starving"
if(!HAS_TRAIT(src, TRAIT_FISH_STASIS) && !proper_environment())
warnings += "drowning"
if(health < initial(health) * 0.6)
warnings += "sick"
if(length(warnings))
. += span_warning("it's [english_list(warnings)]")
if(HAS_MIND_TRAIT(user, TRAIT_EXAMINE_FISH))
. += span_notice("It's [size] cm long.")
. += span_notice("It weighs [weight] g.")
if(HAS_TRAIT(src, TRAIT_FISHING_BAIT))
. += span_smallnoticeital("It can be used as a fishing bait.")
///Randomizes weight and size.
/obj/item/fish/proc/randomize_size_and_weight(base_size = average_size, base_weight = average_weight, deviation = weight_size_deviation)
var/size_deviation = 0.2 * base_size
var/new_size = round(clamp(gaussian(base_size, size_deviation), average_size * 1/MAX_FISH_DEVIATION_COEFF, average_size * MAX_FISH_DEVIATION_COEFF))
var/weight_deviation = 0.2 * base_weight
var/new_weight = round(clamp(gaussian(base_weight, weight_deviation), average_weight * 1/MAX_FISH_DEVIATION_COEFF, average_weight * MAX_FISH_DEVIATION_COEFF))
update_size_and_weight(new_size, new_weight)
///Updates weight and size, along with weight class, number of fillets you can get and grind results.
/obj/item/fish/proc/update_size_and_weight(new_size = average_size, new_weight = average_weight)
SEND_SIGNAL(src, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, new_size, new_weight)
if(size)
if(fillet_type)
RemoveElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS * num_fillets, screentip_verb = "Cut")
if(size > FISH_SIZE_TWO_HANDS_REQUIRED)
qdel(GetComponent(/datum/component/two_handed))
size = new_size
var/init_icon_state = initial(inhand_icon_state)
switch(size)
if(0 to FISH_SIZE_TINY_MAX)
update_weight_class(WEIGHT_CLASS_TINY)
if(!init_icon_state)
inhand_icon_state = "fish_small"
if(FISH_SIZE_TINY_MAX to FISH_SIZE_SMALL_MAX)
if(!init_icon_state)
inhand_icon_state = "fish_small"
update_weight_class(WEIGHT_CLASS_SMALL)
if(FISH_SIZE_SMALL_MAX to FISH_SIZE_NORMAL_MAX)
if(!init_icon_state)
inhand_icon_state = "fish_normal"
update_weight_class(WEIGHT_CLASS_NORMAL)
if(FISH_SIZE_NORMAL_MAX to FISH_SIZE_BULKY_MAX)
if(!init_icon_state)
inhand_icon_state = "fish_bulky"
update_weight_class(WEIGHT_CLASS_BULKY)
if(FISH_SIZE_BULKY_MAX to FISH_SIZE_HUGE_MAX)
if(!init_icon_state)
inhand_icon_state = "fish_huge"
update_weight_class(WEIGHT_CLASS_HUGE)
if(FISH_SIZE_HUGE_MAX to INFINITY)
if(!init_icon_state)
inhand_icon_state = "fish_huge"
update_weight_class(WEIGHT_CLASS_GIGANTIC)
if(size > FISH_SIZE_TWO_HANDS_REQUIRED || (HAS_TRAIT(src, TRAIT_FISH_SHOULD_TWOHANDED) && w_class >= WEIGHT_CLASS_BULKY))
inhand_icon_state = "[inhand_icon_state]_wielded"
AddComponent(/datum/component/two_handed, require_twohands = TRUE)
if(fillet_type)
var/init_fillets = initial(num_fillets)
var/amount = max(round(init_fillets * size / FISH_FILLET_NUMBER_SIZE_DIVISOR, 1), 1)
num_fillets = amount
AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, num_fillets, 0.5 SECONDS * num_fillets, screentip_verb = "Cut")
if(weight)
for(var/reagent_type in grind_results)
grind_results[reagent_type] /= FLOOR(weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR, 0.1)
weight = new_weight
if(weight >= FISH_WEIGHT_SLOWDOWN)
slowdown = round(((weight/FISH_WEIGHT_SLOWDOWN_DIVISOR)**FISH_WEIGHT_SLOWDOWN_EXPONENT)-1.3, 0.1)
drag_slowdown = round(slowdown * 0.5, 1)
else
slowdown = 0
drag_slowdown = 0
if(ismob(loc))
var/mob/mob = loc
mob.update_equipment_speed_mods()
for(var/reagent_type in grind_results)
grind_results[reagent_type] *= FLOOR(weight/FISH_GRIND_RESULTS_WEIGHT_DIVISOR, 0.1)
update_fish_force()
///Reset weapon-related variables of this items and recalculates those values based on the fish weight and size.
/obj/item/fish/proc/update_fish_force()
if(force >= 15 && hitsound == SFX_ALT_FISH_SLAP)
hitsound = SFX_DEFAULT_FISH_SLAP
force = initial(force)
throwforce = initial(throwforce)
throw_range = initial(throw_range)
demolition_mod = initial(demolition_mod)
attack_verb_continuous = initial(attack_verb_continuous)
attack_verb_simple = initial(attack_verb_simple)
hitsound = initial(hitsound)
damtype = initial(damtype)
attack_speed = initial(attack_speed)
block_chance = initial(block_chance)
armour_penetration = initial(armour_penetration)
wound_bonus = initial(wound_bonus)
bare_wound_bonus = initial(bare_wound_bonus)
toolspeed = initial(toolspeed)
var/weight_rank = max(round(1 + log(2, weight/FISH_WEIGHT_FORCE_DIVISOR), 1), 1)
throw_range -= weight_rank
get_force_rank()
var/bonus_malus = weight_rank - w_class
if(bonus_malus)
calculate_fish_force_bonus(bonus_malus)
throwforce = force
SEND_SIGNAL(src, COMSIG_FISH_FORCE_UPDATED, weight_rank, bonus_malus)
if(material_flags & MATERIAL_EFFECTS) //struck by metal gen or something.
for(var/current_material in custom_materials)
var/datum/material/material = GET_MATERIAL_REF(current_material)
force *= material.strength_modifier
throwforce *= material.strength_modifier
if(material.item_sound_override)
hitsound = material.item_sound_override
if(force >=15 && hitsound == SFX_DEFAULT_FISH_SLAP) // don't override special attack sounds
hitsound = SFX_ALT_FISH_SLAP // do more damage - do heavier slap sound
///A proc that makes the fish slightly stronger or weaker if there's a noticeable discrepancy between size and weight.
/obj/item/fish/proc/calculate_fish_force_bonus(bonus_malus)
demolition_mod += bonus_malus * 0.1
attack_speed += bonus_malus * 0.1
force = round(force * (1 + bonus_malus * 0.1), 0.1)
/obj/item/fish/proc/get_force_rank()
switch(w_class)
if(WEIGHT_CLASS_TINY)
force -= 3
attack_speed -= 0.1 SECONDS
if(WEIGHT_CLASS_NORMAL)
force += 2
if(WEIGHT_CLASS_BULKY)
force += 5
attack_speed += 0.1 SECONDS
if(WEIGHT_CLASS_HUGE)
force += 9
attack_speed += 0.2 SECONDS
demolition_mod += 0.2
if(WEIGHT_CLASS_GIGANTIC)
force += 13
attack_speed += 0.4 SECONDS
demolition_mod += 0.4
/**
* This proc has fish_traits list populated with fish_traits paths from three different lists:
* traits from x_traits and y_traits are compared, and inserted if conditions are met;
* traits from fixed_traits are inserted unconditionally.
* traits from removed_traits will be removed from the for loop.
*
* This proc should only be called if the fish was spawned with the apply_qualities arg set to FALSE
* and hasn't had inherited traits already.
*/
/obj/item/fish/proc/inherit_traits(list/x_traits, list/y_traits, list/fixed_traits, list/removed_traits)
fish_traits = fixed_traits?.Copy() || list()
var/list/same_traits = x_traits & y_traits
var/list/all_traits = (x_traits|y_traits)-removed_traits
/// a list of incompatible traits that'll be filled as it goes on. Don't let any such trait pass onto the fish.
var/list/incompatible_traits = list()
///some traits can spontaneously manifest for some fishes. These have higher priorities than other traits
var/list/potential_spontaneous_traits = GLOB.spontaneous_fish_traits[type]
for(var/trait_type in potential_spontaneous_traits)
if(!prob(potential_spontaneous_traits[trait_type]))
continue
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
if(length(fish_traits & trait.incompatible_traits))
continue
fish_traits |= trait_type
incompatible_traits |= trait.incompatible_traits
for(var/trait_type in fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
incompatible_traits |= trait.incompatible_traits
/**
* shuffle the traits, so, in the case of incompatible traits, we don't have to choose which to discard.
* Instead we let the random numbers do it for us in a first come, first served basis.
*/
for(var/trait_type in shuffle(all_traits))
if(trait_type in fish_traits)
continue //likely a fixed trait
if(trait_type in incompatible_traits)
continue
var/datum/fish_trait/trait = GLOB.fish_traits[trait_type]
if(!isnull(trait.fish_whitelist) && !(type in trait.fish_whitelist))
continue
if(length(fish_traits & trait.incompatible_traits))
continue
if((trait_type in same_traits) ? prob(trait.inheritability) : prob(trait.diff_traits_inheritability))
fish_traits |= trait_type
incompatible_traits |= trait.incompatible_traits
apply_traits()
/obj/item/fish/proc/apply_traits()
for(var/fish_trait_type in fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait_type]
trait.apply_to_fish(src)
/obj/item/fish/proc/register_evolutions()
for(var/evolution_type in evolution_types)
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
evolution.register_fish(src)
/obj/item/fish/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
check_environment()
/obj/item/fish/proc/enter_stasis()
ADD_TRAIT(src, TRAIT_FISH_STASIS, INNATE_TRAIT)
// Stop processing until inserted into aquarium again.
stop_flopping()
STOP_PROCESSING(SSobj, src)
/obj/item/fish/proc/exit_stasis()
REMOVE_TRAIT(src, TRAIT_FISH_STASIS, INNATE_TRAIT)
if(status != FISH_DEAD)
START_PROCESSING(SSobj, src)
///Feed the fishes with the contents of the fish feed
/obj/item/fish/proc/feed(datum/reagents/fed_reagents)
if(status != FISH_ALIVE)
return
var/fed_reagent_type
if(fed_reagents.remove_reagent(food, 0.1))
fed_reagent_type = food
last_feeding = world.time
else
var/datum/reagent/wrong_reagent = pick(fed_reagents.reagent_list)
if(!wrong_reagent)
return
fed_reagent_type = wrong_reagent.type
fed_reagents.remove_reagent(fed_reagent_type, 0.1)
SEND_SIGNAL(src, COMSIG_FISH_FED, fed_reagents, fed_reagent_type)
/obj/item/fish/proc/check_environment()
if(QDELETED(src)) //we don't care anymore
return
if(!do_flop_animation)
return
// Do additional stuff
var/in_aquarium = isaquarium(loc)
// Start flopping if outside of fish container
var/should_be_flopping = status == FISH_ALIVE && !HAS_TRAIT(src, TRAIT_FISH_STASIS) && !in_aquarium
if(should_be_flopping)
start_flopping()
else
stop_flopping()
/obj/item/fish/process(seconds_per_tick)
if(HAS_TRAIT(src, TRAIT_FISH_STASIS) || status != FISH_ALIVE)
return
process_health(seconds_per_tick)
if(ready_to_reproduce())
try_to_reproduce()
if(HAS_TRAIT(src, TRAIT_FISH_ELECTROGENESIS) && COOLDOWN_FINISHED(src, electrogenesis_cooldown))
try_electrogenesis()
SEND_SIGNAL(src, COMSIG_FISH_LIFE, seconds_per_tick)
/obj/item/fish/proc/set_status(new_status, silent = FALSE)
if(status == new_status)
return
switch(new_status)
if(FISH_ALIVE)
status = FISH_ALIVE
health = initial(health) // since the fishe has been revived
last_feeding = world.time //reset hunger
check_environment()
START_PROCESSING(SSobj, src)
if(FISH_DEAD)
status = FISH_DEAD
STOP_PROCESSING(SSobj, src)
stop_flopping()
if(!silent)
var/message = span_notice(replacetext(death_text, "%SRC", "[src]"))
if(isaquarium(loc))
loc.visible_message(message)
else
visible_message(message)
update_appearance()
update_fish_force()
SEND_SIGNAL(src, COMSIG_FISH_STATUS_CHANGED)
/obj/item/fish/expose_reagents(list/reagents, datum/reagents/source, methods = TOUCH, volume_modifier = 1, show_message = TRUE)
. = ..()
if(. & COMPONENT_NO_EXPOSE_REAGENTS || status != FISH_DEAD)
return
var/datum/reagent/medicine/strange_reagent/revival = locate() in reagents
if(!revival)
return
if(reagents[revival] >= 2 * w_class)
set_status(FISH_ALIVE)
else
balloon_alert_to_viewers("twitches for a moment!")
animate(src, pixel_x = 1, time = 0.1 SECONDS, loop = 2, flags = ANIMATION_RELATIVE|ANIMATION_PARALLEL)
animate(pixel_x = -1, flags = ANIMATION_RELATIVE)
/obj/item/fish/proc/use_lazarus(datum/source, obj/item/lazarus_injector/injector, mob/user)
SIGNAL_HANDLER
if(injector.revive_type != SENTIENCE_ORGANIC)
balloon_alert(user, "invalid creature!")
return
if(status != FISH_DEAD)
balloon_alert(user, "it's not dead!")
return
set_status(FISH_ALIVE)
injector.expend(src, user)
return LAZARUS_INJECTOR_USED
/obj/item/fish/proc/get_aquarium_animation()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium) || aquarium.fluid_type == AQUARIUM_FLUID_AIR || status == FISH_DEAD)
return AQUARIUM_ANIMATION_FISH_DEAD
else
return AQUARIUM_ANIMATION_FISH_SWIM
/// Checks if our current environment lets us live.
/obj/item/fish/proc/proper_environment()
var/obj/structure/aquarium/aquarium = loc
if(istype(aquarium))
if(!compatible_fluid_type(required_fluid_type, aquarium.fluid_type))
if(aquarium.fluid_type != AQUARIUM_FLUID_AIR || !HAS_TRAIT(src, TRAIT_FISH_AMPHIBIOUS))
return FALSE
if(!ISINRANGE(aquarium.fluid_temp, required_temperature_min, required_temperature_max))
return FALSE
return TRUE
if(required_fluid_type != AQUARIUM_FLUID_AIR && !HAS_TRAIT(src, TRAIT_FISH_AMPHIBIOUS))
return FALSE
var/datum/gas_mixture/mixture = loc.return_air()
if(!mixture)
return FALSE
if(safe_air_limits && !check_gases(mixture.gases, safe_air_limits))
return FALSE
if(!ISINRANGE(mixture.temperature, required_temperature_min, required_temperature_max))
return FALSE
var/pressure = mixture.return_pressure()
if(!ISINRANGE(pressure, min_pressure, max_pressure))
return FALSE
return TRUE
/obj/item/fish/proc/is_hungry()
return !HAS_TRAIT(src, TRAIT_FISH_NO_HUNGER) && world.time - last_feeding >= feeding_frequency
/obj/item/fish/proc/process_health(seconds_per_tick)
var/health_change_per_second = 0
if(!proper_environment())
health_change_per_second -= 3 //Dying here
if(is_hungry())
health_change_per_second -= 0.5 //Starving
else
health_change_per_second += 0.5 //Slowly healing
adjust_health(health + health_change_per_second * seconds_per_tick)
/obj/item/fish/proc/adjust_health(amt)
health = clamp(amt, 0, initial(health))
if(health <= 0)
set_status(FISH_DEAD)
/obj/item/fish/proc/ready_to_reproduce(being_targeted = FALSE)
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return FALSE
if(being_targeted && HAS_TRAIT(src, TRAIT_FISH_NO_MATING))
return FALSE
if(!being_targeted && length(aquarium.get_fishes()) >= AQUARIUM_MAX_BREEDING_POPULATION)
return FALSE
return aquarium.allow_breeding && health >= initial(health) * 0.8 && stable_population >= 1 && world.time >= breeding_wait
/obj/item/fish/proc/try_to_reproduce()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return FALSE
var/obj/item/fish/second_fish
/**
* Fishes with this trait cannot mate, but could still reproduce asexually, so don't early return.
* Also mating takes priority over that.
*/
if(!HAS_TRAIT(src, TRAIT_FISH_NO_MATING))
var/list/available_fishes = list()
var/types_to_mate_with = aquarium.tracked_fish_by_type
if(!HAS_TRAIT(src, TRAIT_FISH_CROSSBREEDER))
var/list/types_to_check = list(src)
if(compatible_types)
types_to_check |= compatible_types
types_to_mate_with = types_to_mate_with & types_to_check
for(var/obj/item/fish/fish_type as anything in types_to_mate_with)
var/list/type_fishes = types_to_mate_with[fish_type]
if(length(type_fishes) >= initial(fish_type.stable_population))
continue
available_fishes += type_fishes
available_fishes -= src //no self-mating.
if(length(available_fishes))
for(var/obj/item/fish/other_fish as anything in shuffle(available_fishes))
if(other_fish.ready_to_reproduce(TRUE))
second_fish = other_fish
break
if(!second_fish)
if(!HAS_TRAIT(src, TRAIT_FISH_SELF_REPRODUCE))
return FALSE
if(length(aquarium.tracked_fish_by_type[type]) >= stable_population)
return FALSE
if(PERFORM_ALL_TESTS(fish_breeding) && second_fish && !length(evolution_types))
return create_offspring(second_fish.type, second_fish)
var/chosen_type
var/datum/fish_evolution/chosen_evolution
var/list/possible_evolutions = list()
for(var/evolution_type in evolution_types)
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
if(evolution.check_conditions(src, second_fish, aquarium))
possible_evolutions += evolution
if(second_fish?.evolution_types)
var/secondary_evolutions = (second_fish.evolution_types - evolution_types)
for(var/evolution_type in secondary_evolutions)
var/datum/fish_evolution/evolution = GLOB.fish_evolutions[evolution_type]
if(evolution.check_conditions(second_fish, src, aquarium))
possible_evolutions += evolution
if(length(possible_evolutions))
chosen_evolution = pick(possible_evolutions)
chosen_type = chosen_evolution.new_fish_type
else if(second_fish)
var/recessive = HAS_TRAIT(src, TRAIT_FISH_RECESSIVE)
var/recessive_partner = HAS_TRAIT(second_fish, TRAIT_FISH_RECESSIVE)
if(length(aquarium.tracked_fish_by_type[type]) >= stable_population)
if(recessive_partner && !recessive)
return FALSE
chosen_type = second_fish.type
else
if(recessive && !recessive_partner)
chosen_type = second_fish.type
else if(recessive_partner && !recessive)
chosen_type = type
else
chosen_type = pick(second_fish.type, type)
else
chosen_type = type
return create_offspring(chosen_type, second_fish, chosen_evolution)
/obj/item/fish/proc/create_offspring(chosen_type, obj/item/fish/partner, datum/fish_evolution/evolution)
var/obj/item/fish/new_fish = new chosen_type (loc, FALSE)
//Try to pass down compatible traits based on inheritability
new_fish.inherit_traits(fish_traits, partner?.fish_traits, evolution?.new_traits, evolution?.removed_traits)
if(partner)
var/mean_size = (size + partner.size)/2
var/mean_weight = (weight + partner.weight)/2
new_fish.randomize_size_and_weight(mean_size, mean_weight, 0.3, TRUE)
partner.breeding_wait = world.time + breeding_timeout
else //Make a close of this fish.
new_fish.update_size_and_weight(size, weight, TRUE)
breeding_wait = world.time + breeding_timeout
return new_fish
#define PAUSE_BETWEEN_PHASES 15
#define PAUSE_BETWEEN_FLOPS 2
#define FLOP_COUNT 2
#define FLOP_DEGREE 20
#define FLOP_SINGLE_MOVE_TIME 1.5
#define JUMP_X_DISTANCE 5
#define JUMP_Y_DISTANCE 6
/// This flopping animation played while the fish is alive.
/obj/item/fish/proc/flop_animation()
var/pause_between = PAUSE_BETWEEN_PHASES + rand(1, 5) //randomized a bit so fish are not in sync
animate(src, time = pause_between, loop = -1)
//move nose down and up
for(var/_ in 1 to FLOP_COUNT)
var/matrix/up_matrix = matrix()
up_matrix.Turn(FLOP_DEGREE)
var/matrix/down_matrix = matrix()
down_matrix.Turn(-FLOP_DEGREE)
animate(transform = down_matrix, time = FLOP_SINGLE_MOVE_TIME, loop = -1)
animate(transform = up_matrix, time = FLOP_SINGLE_MOVE_TIME, loop = -1)
animate(transform = matrix(), time = FLOP_SINGLE_MOVE_TIME, loop = -1, easing = BOUNCE_EASING | EASE_IN)
animate(time = PAUSE_BETWEEN_FLOPS, loop = -1)
//bounce up and down
animate(time = pause_between, loop = -1, flags = ANIMATION_PARALLEL)
var/jumping_right = FALSE
var/up_time = 3 * FLOP_SINGLE_MOVE_TIME / 2
for(var/_ in 1 to FLOP_COUNT)
jumping_right = !jumping_right
var/x_step = jumping_right ? JUMP_X_DISTANCE/2 : -JUMP_X_DISTANCE/2
animate(time = up_time, pixel_y = JUMP_Y_DISTANCE , pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_IN)
animate(time = up_time, pixel_y = -JUMP_Y_DISTANCE, pixel_x=x_step, loop = -1, flags= ANIMATION_RELATIVE, easing = BOUNCE_EASING | EASE_OUT)
animate(time = PAUSE_BETWEEN_FLOPS, loop = -1)
#undef PAUSE_BETWEEN_PHASES
#undef PAUSE_BETWEEN_FLOPS
#undef FLOP_COUNT
#undef FLOP_DEGREE
#undef FLOP_SINGLE_MOVE_TIME
#undef JUMP_X_DISTANCE
#undef JUMP_Y_DISTANCE
/// Starts flopping animation
/obj/item/fish/proc/start_flopping()
if(HAS_TRAIT(src, TRAIT_FISH_FLOPPING)) //Requires update_transform/animate_wrappers to be less restrictive.
return
ADD_TRAIT(src, TRAIT_FISH_FLOPPING, TRAIT_GENERIC)
flop_animation()
/// Stops flopping animation
/obj/item/fish/proc/stop_flopping()
if(HAS_TRAIT(src, TRAIT_FISH_FLOPPING))
REMOVE_TRAIT(src, TRAIT_FISH_FLOPPING, TRAIT_GENERIC)
animate(src, transform = matrix()) //stop animation
/// Refreshes flopping animation after temporary animation finishes
/obj/item/fish/proc/on_temp_animation(datum/source, animation_duration)
if(animation_duration > 0)
addtimer(CALLBACK(src, PROC_REF(refresh_flopping)), animation_duration)
/obj/item/fish/proc/refresh_flopping()
if(HAS_TRAIT(src, TRAIT_FISH_FLOPPING))
flop_animation()
/obj/item/fish/proc/try_electrogenesis()
if(status == FISH_DEAD || is_hungry())
return
COOLDOWN_START(src, electrogenesis_cooldown, ELECTROGENESIS_DURATION + ELECTROGENESIS_VARIANCE)
var/fish_zap_range = 1
var/fish_zap_power = 1 KILO JOULES // ~5 damage, just a little friendly "yeeeouch!"
var/fish_zap_flags = ZAP_MOB_DAMAGE
if(istype(loc, /obj/structure/aquarium/bioelec_gen))
fish_zap_range = 5
fish_zap_power = electrogenesis_power
fish_zap_flags |= (ZAP_GENERATES_POWER | ZAP_MOB_STUN)
tesla_zap(source = get_turf(src), zap_range = fish_zap_range, power = fish_zap_power, cutoff = 1 MEGA JOULES, zap_flags = fish_zap_flags)
///Returns the price of this fish, for the fish export.
/obj/item/fish/proc/get_export_price(price, percent)
var/size_weight_exponentation = (size * weight * 0.01)^0.85
var/calculated_price = price + size_weight_exponentation * percent
if(HAS_TRAIT(src, TRAIT_FISH_FROM_CASE)) //Avoid printing money by simply ordering fish and sending it back.
calculated_price *= 0.05
return round(calculated_price)
/obj/item/fish/proc/get_happiness_value()
var/happiness_value = 0
if(recently_petted)
happiness_value++
if(HAS_TRAIT(src, TRAIT_FISH_NO_HUNGER) || min((world.time - last_feeding) / feeding_frequency, 1) < 0.5)
happiness_value++
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return happiness_value
if(compatible_fluid_type(required_fluid_type, aquarium.fluid_type))
happiness_value++
if(ISINRANGE(aquarium.fluid_temp, required_temperature_min, required_temperature_max))
happiness_value++
return happiness_value
/obj/item/fish/proc/pet_fish(mob/living/user)
if(recently_petted)
to_chat(user, span_warning("[src] runs away from your finger as you dip it into the water!"))
return
if(electrogenesis_power > 15 MEGA JOULES)
user.electrocute_act(5, src) //was it all worth it?
recently_petted = TRUE
SEND_SIGNAL(src, COMSIG_FISH_PETTED)
to_chat(user, span_notice("[src] dances around!"))
addtimer(VARSET_CALLBACK(src, recently_petted, FALSE), 30 SECONDS)
/// Returns random fish, using random_case_rarity probabilities.
/proc/random_fish_type(required_fluid)
var/static/probability_table
var/argkey = "fish_[required_fluid]" //If this expands more extract bespoke element arg generation to some common helper.
if(!probability_table || !probability_table[argkey])
if(!probability_table)
probability_table = list()
var/chance_table = list()
for(var/_fish_type in subtypesof(/obj/item/fish))
var/obj/item/fish/fish = _fish_type
var/rarity = initial(fish.random_case_rarity)
if(!rarity)
continue
if(required_fluid)
var/init_fish_fluid_type = initial(fish.required_fluid_type)
if(!compatible_fluid_type(init_fish_fluid_type, required_fluid))
continue
chance_table[fish] = initial(fish.random_case_rarity)
probability_table[argkey] = chance_table
return pick_weight(probability_table[argkey])
/proc/compatible_fluid_type(fish_fluid_type, fluid_type)
switch(fish_fluid_type)
if(AQUARIUM_FLUID_ANY_WATER)
return fluid_type != AQUARIUM_FLUID_AIR
if(AQUARIUM_FLUID_ANADROMOUS)
return fluid_type == AQUARIUM_FLUID_SALTWATER || fluid_type == AQUARIUM_FLUID_FRESHWATER
else
return fish_fluid_type == fluid_type