Files
Bubberstation/code/modules/fishing/fish/_fish.dm
SkyratBot 4af0dd0b8f [MIRROR] addresses reviews on the tram pr made after merge, fixes diagonal movement bugs [MDB IGNORE] (#14943)
* addresses reviews on the tram pr made after merge, fixes diagonal movement bugs (#68033)

* addresses reviews on the tram pr made after merge, fixes diagonal movement bugs

* wew

Co-authored-by: Kylerace <kylerlumpkin1@gmail.com>
Co-authored-by: Gandalf <9026500+Gandalf2k15@users.noreply.github.com>
2022-07-19 00:51:33 +00:00

371 lines
12 KiB
Plaintext

// Fish path used for autogenerated fish
/obj/item/fish
name = "generic looking aquarium fish"
desc = "very bland"
icon = 'icons/obj/aquarium.dmi'
icon_state = "bugfish"
w_class = WEIGHT_CLASS_TINY
/// Resulting width of aquarium visual icon - default size of "fish_greyscale" state
var/sprite_width = 3
/// Resulting height of aquarium visual icon - default size of "fish_greyscale" state
var/sprite_height = 3
/// Original width of aquarium visual icon - used to calculate scaledown factor
var/source_width = 32
/// Original height of aquarium visual icon - used to calculate scaledown factor
var/source_height = 32
/// If present this icon will be used for in-aquarium visual for the fish instead of icon_state
var/dedicated_in_aquarium_icon_state
/// If present aquarium visual will be 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/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
/// Current fish health. Dies at 0.
var/health = 100
/// Should this fish type show in fish catalog
var/show_in_catalog = TRUE
/// Should this fish spawn in random fish cases
var/available_in_random_cases = 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
/// Won't breed more than this amount in single aquarium.
var/stable_population = 1
/// Last time new fish was created
var/last_breeding
/// How long it takes to produce new fish
var/breeding_timeout = 2 MINUTES
var/flopping = FALSE
var/in_stasis = FALSE
// Fishing related properties
/// List of fishing trait types, these modify probabilty/difficulty depending on rod/user properties
var/list/fishing_traits = list()
/// Fishing behaviour
var/fish_ai_type = FISH_AI_DUMB
/// 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
var/size = 50
/// 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
var/weight = 1000
/// Average weight for this fish type in grams
var/average_weight = 1000
/obj/item/fish/Initialize(mapload)
. = ..()
if(fillet_type)
AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, 1, 5)
AddComponent(/datum/component/aquarium_content, .proc/get_aquarium_animation, list(COMSIG_FISH_STATUS_CHANGED,COMSIG_FISH_STIRRED))
RegisterSignal(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, .proc/on_temp_animation)
check_environment_after_movement()
if(status != FISH_DEAD)
START_PROCESSING(SSobj, src)
size = average_size
weight = average_weight
/obj/item/fish/examine(mob/user)
. = ..()
// All spacemen have magic eyes of fish weight perception until fish scale (get it?) is implemented.
. += span_notice("It's [size] cm long.")
. += span_notice("It weighs [weight] g.")
/obj/item/fish/proc/randomize_weight_and_size(modifier = 0)
var/size_deviation = 0.2 * average_size
var/size_mod = modifier * average_size
size = max(1,gaussian(average_size + size_mod, size_deviation))
var/weight_deviation = 0.2 * average_weight
var/weight_mod = modifier * average_weight
weight = max(1,gaussian(average_weight + weight_mod, weight_deviation))
/obj/item/fish/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change = TRUE)
. = ..()
check_environment_after_movement()
/obj/item/fish/proc/enter_stasis()
in_stasis = TRUE
// Stop processing until inserted into aquarium again.
stop_flopping()
STOP_PROCESSING(SSobj, src)
/obj/item/fish/proc/exit_stasis()
in_stasis = FALSE
if(status != FISH_DEAD)
START_PROCESSING(SSobj, src)
/obj/item/fish/proc/on_aquarium_insertion(obj/structure/aquarium)
if(isnull(last_feeding)) //Fish start fed.
last_feeding = world.time
RegisterSignal(aquarium, COMSIG_ATOM_EXITED, .proc/aquarium_exited)
RegisterSignal(aquarium, COMSIG_PARENT_ATTACKBY, .proc/attack_reaction)
/obj/item/fish/proc/aquarium_exited(datum/source, atom/movable/gone, direction)
SIGNAL_HANDLER
if(src != gone)
return
UnregisterSignal(source,list(COMSIG_ATOM_EXITED,COMSIG_PARENT_ATTACKBY))
/// Our aquarium is hit with stuff
/obj/item/fish/proc/attack_reaction(datum/source, obj/item/thing, mob/user, params)
SIGNAL_HANDLER
if(is_food(thing))
on_feeding(thing.reagents)
return COMPONENT_NO_AFTERATTACK
else
//stirred effect
SEND_SIGNAL(src, COMSIG_FISH_STIRRED)
/obj/item/fish/proc/is_food(obj/item/thing)
return istype(thing, /obj/item/fish_feed)
/obj/item/fish/proc/on_feeding(datum/reagents/feed_reagents)
if(feed_reagents.has_reagent(food))
last_feeding = world.time
/obj/item/fish/proc/check_environment_after_movement()
if(QDELETED(src)) //we don't care anymore
return
// Apply/remove stasis as needed
if(loc && HAS_TRAIT(loc, TRAIT_FISH_SAFE_STORAGE))
enter_stasis()
else if(in_stasis)
exit_stasis()
// Do additional stuff
var/in_aquarium = istype(loc,/obj/structure/aquarium)
if(in_aquarium)
on_aquarium_insertion(loc)
// Start flopping if outside of fish container
var/should_be_flopping = status == FISH_ALIVE && loc && !HAS_TRAIT(loc,TRAIT_FISH_SAFE_STORAGE) && !in_aquarium
if(should_be_flopping)
start_flopping()
else
stop_flopping()
/obj/item/fish/process(delta_time)
if(in_stasis || status != FISH_ALIVE)
return
process_health(delta_time)
if(ready_to_reproduce())
try_to_reproduce()
/obj/item/fish/proc/set_status(new_status)
switch(new_status)
if(FISH_ALIVE)
status = FISH_ALIVE
health = initial(health) // this is admin option anyway
START_PROCESSING(SSobj, src)
if(FISH_DEAD)
status = FISH_DEAD
STOP_PROCESSING(SSobj, src)
stop_flopping()
var/message = span_notice("\The [name] dies.")
if(istype(loc,/obj/structure/aquarium))
loc.visible_message(message)
else
visible_message(message)
SEND_SIGNAL(src, COMSIG_FISH_STATUS_CHANGED)
/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))
return FALSE
if(required_fluid_type != AQUARIUM_FLUID_ANADROMOUS)
if(aquarium.fluid_type != required_fluid_type)
return FALSE
else
if(aquarium.fluid_type != AQUARIUM_FLUID_SALTWATER && aquarium.fluid_type != AQUARIUM_FLUID_FRESHWATER)
return FALSE
if(aquarium.fluid_temp < required_temperature_min || aquarium.fluid_temp > required_temperature_max)
return FALSE
return TRUE
/obj/item/fish/proc/process_health(delta_time)
var/health_change_per_second = 0
if(!proper_environment())
health_change_per_second -= 3 //Dying here
if(world.time - last_feeding >= feeding_frequency)
health_change_per_second -= 0.5 //Starving
else
health_change_per_second += 0.5 //Slowly healing
adjust_health(health + health_change_per_second * delta_time)
/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()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return FALSE
return aquarium.allow_breeding && health == initial(health) && stable_population > 1 && world.time - last_breeding >= breeding_timeout
//Fish breeding stops if fish count exceeds this.
#define AQUARIUM_MAX_BREEDING_POPULATION 20
/obj/item/fish/proc/try_to_reproduce()
var/obj/structure/aquarium/aquarium = loc
if(!istype(aquarium))
return
if(length(aquarium.tracked_fish) >= AQUARIUM_MAX_BREEDING_POPULATION) //so aquariums full of fish don't need to do these expensive checks
return
var/list/other_fish_of_same_type = list()
for(var/obj/item/fish/fish_in_aquarium in aquarium)
if(fish_in_aquarium == src || fish_in_aquarium.type != type)
continue
other_fish_of_same_type += fish_in_aquarium
if(length(other_fish_of_same_type) >= stable_population)
return
var/obj/item/fish/second_fish
for(var/obj/item/fish/other_fish in other_fish_of_same_type)
if(other_fish.ready_to_reproduce())
second_fish = other_fish
break
if(second_fish)
new type(loc) //could use child_type var
last_breeding = world.time
second_fish.last_breeding = world.time
#undef AQUARIUM_MAX_BREEDING_POPULATION
#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 animation should be applied to actual parent atom instead of vc_object.
/proc/flop_animation(atom/movable/animation_target)
var/pause_between = PAUSE_BETWEEN_PHASES + rand(1, 5) //randomized a bit so fish are not in sync
animate(animation_target, 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(!flopping) //Requires update_transform/animate_wrappers to be less restrictive.
flopping = TRUE
flop_animation(src)
/// Stops flopping animation
/obj/item/fish/proc/stop_flopping()
if(flopping)
flopping = FALSE
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/refresh_flopping), animation_duration)
/obj/item/fish/proc/refresh_flopping()
if(flopping)
flop_animation(src)
/// Returns random fish, using random_case_rarity probabilities.
/proc/random_fish_type(case_fish_only=TRUE, required_fluid)
var/static/probability_table
var/argkey = "fish_[required_fluid]_[case_fish_only]" //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
if(required_fluid && initial(fish.required_fluid_type) != required_fluid)
continue
if(initial(fish.available_in_random_cases) || !case_fish_only)
chance_table[fish] = initial(fish.random_case_rarity)
probability_table[argkey] = chance_table
return pick_weight(probability_table[argkey])