mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-06-05 22:31:04 +01:00
ae5a4f955d
## About The Pull Request Signals were initially only usable with component listeners, which while no longer the case has lead to outdated documentation, names, and a similar location in code. This pr pulls the two apart. Partially because mso thinks we should, but also because they really aren't directly linked anymore, and having them in this midstate just confuses people. [Renames comp_lookup to listen_lookup, since that's what it does](https://github.com/tgstation/tgstation/commit/102b79694fa8eb57ecf7b36032616a9e368ccced) [Moves signal procs over to their own file](https://github.com/tgstation/tgstation/commit/33d07d01fd336726b4f6f6f1b61bb0b3f11a00dc) [Renames the PREQDELETING and QDELETING comsigs to drop the parent bit since they can hook to more then just comps now](https://github.com/tgstation/tgstation/commit/335ea4ad081ec63c42cfa05856e582cca833af6e) [Does something similar to the attackby comsigs (PARENT -> ATOM)](https://github.com/tgstation/tgstation/commit/210e57051df63f88dac3dd83321236da825aae5e) [And finally passes over the examine signals](https://github.com/tgstation/tgstation/commit/65917658fb8a1e7d28ae23c9437a583d646f0302) ## Why It's Good For The Game Code makes more sense, things are better teased apart, s just good imo ## Changelog 🆑 refactor: Pulled apart the last vestiges of names/docs directly linking signals to components /🆑
371 lines
12 KiB
Plaintext
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, screentip_verb = "Cut")
|
|
AddComponent(/datum/component/aquarium_content, PROC_REF(get_aquarium_animation), list(COMSIG_FISH_STATUS_CHANGED,COMSIG_FISH_STIRRED))
|
|
RegisterSignal(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, PROC_REF(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_REF(aquarium_exited))
|
|
RegisterSignal(aquarium, COMSIG_ATOM_ATTACKBY, PROC_REF(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_ATOM_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(seconds_per_tick)
|
|
if(in_stasis || status != FISH_ALIVE)
|
|
return
|
|
|
|
process_health(seconds_per_tick)
|
|
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(seconds_per_tick)
|
|
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 * 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()
|
|
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_REF(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])
|
|
|
|
|