mirror of
https://github.com/Skyrat-SS13/Skyrat-tg.git
synced 2026-07-15 09:54:03 +01:00
bb70889f6e
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
826 lines
30 KiB
Plaintext
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
|