diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm index 85133ce4519..2295d095571 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_main.dm @@ -82,4 +82,5 @@ ///When the transform or an atom is varedited through vv topic. #define COMSIG_ATOM_VV_MODIFY_TRANSFORM "atom_vv_modify_transform" - +/// generally called before temporary non-parallel animate()s on the atom (animation_duration) +#define COMSIG_ATOM_TEMPORARY_ANIMATION_START "atom_temp_animate_start" diff --git a/code/__DEFINES/dcs/signals/signals_fish.dm b/code/__DEFINES/dcs/signals/signals_fish.dm new file mode 100644 index 00000000000..d40d6f3e595 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_fish.dm @@ -0,0 +1,7 @@ +// Aquarium related signals +#define COMSIG_AQUARIUM_SURFACE_CHANGED "aquarium_surface_changed" +#define COMSIG_AQUARIUM_FLUID_CHANGED "aquarium_fluid_changed" + +// Fish signals +#define COMSIG_FISH_STATUS_CHANGED "fish_status_changed" +#define COMSIG_FISH_STIRRED "fish_stirred" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index cd6dd66ff99..6a1670569ef 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -372,10 +372,6 @@ #define COMSIG_ITEM_AFTERATTACK_SECONDARY "item_afterattack_secondary" ///from base of obj/item/attack_qdeleted(): (atom/target, mob/user, params) #define COMSIG_ITEM_ATTACK_QDELETED "item_attack_qdeleted" -// Aquarium related signals -#define COMSIG_AQUARIUM_BEFORE_INSERT_CHECK "aquarium_about_to_be_inserted" -#define COMSIG_AQUARIUM_SURFACE_CHANGED "aquarium_surface_changed" -#define COMSIG_AQUARIUM_FLUID_CHANGED "aquarium_fluid_changed" ///from /obj/item/assembly/proc/pulsed() #define COMSIG_ASSEMBLY_PULSED "assembly_pulsed" diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm index 93baff31112..ea75dd15ff2 100644 --- a/code/datums/components/aquarium.dm +++ b/code/datums/components/aquarium.dm @@ -1,8 +1,5 @@ /// Allows movables to be inserted/displayed in aquariums. /datum/component/aquarium_content - /// This is a datum that describes our in-aquarium functionality - var/datum/aquarium_behaviour/properties - /// Keeps track of our current aquarium. var/obj/structure/aquarium/current_aquarium @@ -16,17 +13,69 @@ //Current layer for the visual object var/base_layer - //If flopping animation was applied to parent, tracked to stop it on removal/destroy - var/flopping = FALSE -/datum/component/aquarium_content/Initialize(property_type) + /** + * Fish sprite how to: + * Need to be centered on 16,16 in the dmi and facing left by default. + * sprite_height/sprite_width is the size it will have in aquarium and used to control animation boundaries. + * source_height/source_width is the size of the original icon (ideally only the non-empty parts) + */ + + + /// Icon used for in aquarium sprite + var/icon = 'icons/obj/aquarium.dmi' + /// If this is set this icon state will be used for the holder while icon_state will only be used for item/catalog. Transformation from source_width/height WON'T be applied. + var/icon_state + /// Applied to vc object only for use with greyscaled icons. + var/aquarium_vc_color + /// Transformation applied to the visual holder - used when scaled down sprites are used as in aquarium visual + var/matrix/base_transform + + /// How the thing will be layered + var/layer_mode = AQUARIUM_LAYER_MODE_AUTO + + /// If the starting position is randomised within bounds when inserted into aquarium. + var/randomize_position = FALSE + + //Target sprite size for path/position calculations. + var/sprite_height = 3 + var/sprite_width = 3 + + //This is the size of the source sprite. This will be used to calculate scale down factor. + var/source_width = 32 + var/source_height = 32 + + /// Currently playing animation + var/current_animation + + /// Does this behviour need additional processing in aquarium, will be added to SSobj processing on insertion + var/processing = FALSE + + /// TODO: Change this into trait checked on aquarium insertion + var/unique = FALSE + + /// Proc used to retrieve current animation state from the parent, optional + var/animation_getter + + /// Signals of the parent that will trigger animation update + var/animation_update_signals + + +/datum/component/aquarium_content/Initialize(animation_getter, animation_update_signals) if(!ismovable(parent)) return COMPONENT_INCOMPATIBLE - if(ispath(property_type, /datum/aquarium_behaviour)) - properties = new property_type + + src.animation_getter = animation_getter + src.animation_update_signals = animation_update_signals + if(animation_update_signals) + RegisterSignal(parent, animation_update_signals, .proc/generate_animation) + + if(istype(parent,/obj/item/fish)) + InitializeFromFish() + else if(istype(parent,/obj/item/aquarium_prop)) + InitializeFromProp() else - CRASH("Invalid property type provided for aquarium content component") - properties.parent = src + InitializeOther() ADD_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, src) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/enter_aquarium) @@ -36,6 +85,53 @@ if(istype(movable_parent.loc, /obj/structure/aquarium)) on_inserted(movable_parent.loc) +/// Sets visuals properties for fish +/datum/component/aquarium_content/proc/InitializeFromFish() + var/obj/item/fish/fish = parent + + icon = fish.icon + sprite_height = fish.sprite_height + sprite_width = fish.sprite_width + aquarium_vc_color = fish.aquarium_vc_color + + if(fish.dedicated_in_aquarium_icon_state) + icon_state = fish.dedicated_in_aquarium_icon_state + base_transform = matrix() + else + icon_state = fish.icon_state + var/matrix/matrix = matrix() + var/x_scale = fish.sprite_width / fish.source_width + var/y_scale = fish.sprite_height / fish.source_height + matrix.Scale(x_scale, y_scale) + base_transform = matrix + + randomize_position = TRUE + +/// Sets visuals properties for fish +/datum/component/aquarium_content/proc/InitializeFromProp() + var/obj/item/aquarium_prop/prop = parent + + icon = prop.icon + icon_state = prop.icon_state + layer_mode = prop.layer_mode + sprite_height = 32 + sprite_width = 32 + base_transform = matrix() + + unique = TRUE + +/// Mostly for admin abuse +/datum/component/aquarium_content/proc/InitializeOther() + sprite_width = 8 + sprite_height = 8 + + var/matrix/matrix = matrix() + var/x_scale = sprite_width / 32 + var/y_scale = sprite_height / 32 + matrix.Scale(x_scale, y_scale) + base_transform = matrix + + /datum/component/aquarium_content/PreTransfer() . = ..() REMOVE_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, src) @@ -43,9 +139,7 @@ /datum/component/aquarium_content/Destroy(force, silent) if(current_aquarium) remove_from_aquarium() - stop_flopping() QDEL_NULL(vc_obj) - QDEL_NULL(properties) return ..() /datum/component/aquarium_content/proc/enter_aquarium(datum/source, OldLoc, Dir, Forced) @@ -53,17 +147,14 @@ var/atom/movable/movable_parent = parent if(istype(movable_parent.loc, /obj/structure/aquarium)) on_inserted(movable_parent.loc) - if(HAS_TRAIT(movable_parent.loc, TRAIT_FISH_SAFE_STORAGE)) - on_tank_stasis() /datum/component/aquarium_content/proc/is_ready_to_insert(obj/structure/aquarium/aquarium) //This is kinda awful but we're unaware of other fish - if(properties.unique) + if(unique) for(var/atom/movable/fish_or_prop in aquarium) if(fish_or_prop == parent) continue - var/datum/component/aquarium_content/other_content = fish_or_prop.GetComponent(/datum/component/aquarium_content) - if(other_content && other_content.properties.type == properties.type) + if(fish_or_prop.type == parent.type) return FALSE return TRUE @@ -72,8 +163,9 @@ RegisterSignal(current_aquarium, COMSIG_ATOM_EXITED, .proc/on_removed) RegisterSignal(current_aquarium, COMSIG_AQUARIUM_SURFACE_CHANGED, .proc/on_surface_changed) RegisterSignal(current_aquarium, COMSIG_AQUARIUM_FLUID_CHANGED,.proc/on_fluid_changed) - RegisterSignal(current_aquarium, COMSIG_PARENT_ATTACKBY, .proc/attack_reaction) - properties.on_inserted() + + if(processing) + START_PROCESSING(SSobj, src) //If we don't have vc object yet build it if(!vc_obj) @@ -81,7 +173,7 @@ //Set default position and layer set_vc_base_position() - generate_animation() + generate_animation(reset = TRUE) //Finally add it to to objects vis_contents current_aquarium.vis_contents |= vc_obj @@ -90,21 +182,11 @@ /datum/component/aquarium_content/proc/on_surface_changed() SIGNAL_HANDLER set_vc_base_position() - generate_animation() //our animation start point changed, gotta redo - -/// Our aquarium is hit with stuff -/datum/component/aquarium_content/proc/attack_reaction(datum/source, obj/item/thing, mob/user, params) - SIGNAL_HANDLER - if(istype(thing, /obj/item/fish_feed)) - properties.on_feeding(thing.reagents) - return COMPONENT_NO_AFTERATTACK - else - //stirred effect - generate_animation() + generate_animation(reset = TRUE) //our animation start point changed, gotta redo /datum/component/aquarium_content/proc/on_fluid_changed() SIGNAL_HANDLER - properties.on_fluid_changed() + generate_animation() /datum/component/aquarium_content/proc/remove_visual_from_aquarium() current_aquarium.vis_contents -= vc_obj @@ -113,17 +195,29 @@ /// Generates common visual object, propeties that don't depend on aquarium surface /datum/component/aquarium_content/proc/generate_base_vc() - if(!properties) - CRASH("Generating visual without properties.") - var/obj/effect/visual = new - properties.apply_appearance(visual) + apply_appearance(visual) visual.vis_flags |= VIS_INHERIT_ID | VIS_INHERIT_PLANE //plane so it shows properly in containers on inventory ui for handheld cases return visual +/// Applies icon,color and base scaling to our visual holder +/datum/component/aquarium_content/proc/apply_appearance(obj/effect/holder) + holder.icon = icon + holder.icon_state = icon_state + holder.transform = matrix(base_transform) + if(aquarium_vc_color) + holder.color = aquarium_vc_color + + /// Actually animates the vc holder -/datum/component/aquarium_content/proc/generate_animation() - switch(properties.current_animation) +/datum/component/aquarium_content/proc/generate_animation(reset=FALSE) + if(!current_aquarium) + return + var/next_animation = animation_getter ? call(parent,animation_getter)() : null + if(current_animation == next_animation && !reset) + return + current_animation = next_animation + switch(current_animation) if(AQUARIUM_ANIMATION_FISH_SWIM) swim_animation() return @@ -134,8 +228,8 @@ /// Create looping random path animation, pixel offsets parameters include offsets already /datum/component/aquarium_content/proc/swim_animation() - var/avg_width = round(properties.sprite_width / 2) - var/avg_height = round(properties.sprite_height / 2) + var/avg_width = round(sprite_width / 2) + var/avg_height = round(sprite_height / 2) var/list/aq_properties = current_aquarium.get_surface_properties() var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] + avg_width - 16 @@ -160,7 +254,7 @@ var/dist = abs(dx) + abs(dy) var/eyeballed_time = dist * 2 //2ds per px //Face the direction we're going - var/matrix/dir_mx = properties.base_transform() + var/matrix/dir_mx = matrix(base_transform) if(dx <= 0) //assuming default sprite is facing left here dir_mx.Scale(-1, 1) animate(transform = dir_mx, time = 0, loop = -1) @@ -168,85 +262,35 @@ /datum/component/aquarium_content/proc/dead_animation() //Set base_py to lowest possible value - var/avg_height = round(properties.sprite_height / 2) + var/avg_height = round(sprite_height / 2) var/list/aq_properties = current_aquarium.get_surface_properties() var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16 base_py = py_min animate(vc_obj, pixel_y = py_min, time = 1) //flop to bottom and end current animation. -#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 -/datum/component/aquarium_content/proc/start_flopping() - if(!flopping && istype(parent,/obj/item/fish)) //Requires update_transform/animate_wrappers to be less restrictive. - flopping = TRUE - flop_animation(parent) - -/// Stops flopping animation -/datum/component/aquarium_content/proc/stop_flopping() - if(flopping) - flopping = FALSE - animate(parent, transform = matrix()) //stop animation - /datum/component/aquarium_content/proc/set_vc_base_position() - var/list/aq_properties = current_aquarium.get_surface_properties() - if(properties.randomize_position) - var/avg_width = round(properties.sprite_width / 2) - var/avg_height = round(properties.sprite_height / 2) - var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] + avg_width - 16 - var/px_max = aq_properties[AQUARIUM_PROPERTIES_PX_MAX] - avg_width - 16 - var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16 - var/py_max = aq_properties[AQUARIUM_PROPERTIES_PY_MAX] - avg_width - 16 - - base_px = rand(px_min,px_max) - base_py = rand(py_min,py_max) - - vc_obj.pixel_x = base_px - vc_obj.pixel_y = base_py - + if(randomize_position) + randomize_base_position() if(base_layer) current_aquarium.free_layer(base_layer) - base_layer = current_aquarium.request_layer(properties.layer_mode) + base_layer = current_aquarium.request_layer(layer_mode) vc_obj.layer = base_layer +/datum/component/aquarium_content/proc/randomize_base_position() + var/list/aq_properties = current_aquarium.get_surface_properties() + var/avg_width = round(sprite_width / 2) + var/avg_height = round(sprite_height / 2) + var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] + avg_width - 16 + var/px_max = aq_properties[AQUARIUM_PROPERTIES_PX_MAX] - avg_width - 16 + var/py_min = aq_properties[AQUARIUM_PROPERTIES_PY_MIN] + avg_height - 16 + var/py_max = aq_properties[AQUARIUM_PROPERTIES_PY_MAX] - avg_width - 16 + + base_px = rand(px_min,px_max) + base_py = rand(py_min,py_max) + + vc_obj.pixel_x = base_px + vc_obj.pixel_y = base_py + /datum/component/aquarium_content/proc/on_removed(datum/source, atom/movable/gone, direction) SIGNAL_HANDLER if(parent != gone) @@ -254,12 +298,7 @@ remove_from_aquarium() /datum/component/aquarium_content/proc/remove_from_aquarium() - properties.before_removal() UnregisterSignal(current_aquarium, list(COMSIG_AQUARIUM_SURFACE_CHANGED, COMSIG_AQUARIUM_FLUID_CHANGED, COMSIG_PARENT_ATTACKBY, COMSIG_ATOM_EXITED)) remove_visual_from_aquarium() current_aquarium = null -/datum/component/aquarium_content/proc/on_tank_stasis() - // Stop processing until inserted into aquarium again. - stop_flopping() - STOP_PROCESSING(SSobj, properties) diff --git a/code/datums/components/storage/concrete/fish_case.dm b/code/datums/components/storage/concrete/fish_case.dm index 4b74aa8c09b..398956c77b4 100644 --- a/code/datums/components/storage/concrete/fish_case.dm +++ b/code/datums/components/storage/concrete/fish_case.dm @@ -2,9 +2,3 @@ max_items = 1 can_hold_trait = TRAIT_FISH_CASE_COMPATIBILE can_hold_description = "fish and aquarium equipment" - -/datum/component/storage/concrete/fish_case/can_be_inserted(obj/item/I, stop_messages, mob/M) - /// Activate deferred components if any. - SEND_SIGNAL(I, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK) - . = ..() - diff --git a/code/datums/elements/deferred_aquarium_content.dm b/code/datums/elements/deferred_aquarium_content.dm deleted file mode 100644 index 5569e0359a6..00000000000 --- a/code/datums/elements/deferred_aquarium_content.dm +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Create /datum/component/aquarium_content with the preset path on the target right before being inserted into aquarium and deletes itself. - * Used to save memory from aquarium properties on common objects/stacks that won't see aquarium in 99 out of 100 rounds. - */ -/datum/element/deferred_aquarium_content - element_flags = ELEMENT_BESPOKE - id_arg_index = 2 - var/aquarium_content_type - -/datum/element/deferred_aquarium_content/Attach(datum/target, aquarium_content_type) - . = ..() - if(!ismovable(target)) - return ELEMENT_INCOMPATIBLE - if(!aquarium_content_type) - CRASH("Deferred aquarium content missing behaviour type.") - src.aquarium_content_type = aquarium_content_type - - //If element is added to something already in aquarium, just create the component. - var/atom/movable/movable_target = target - if(istype(movable_target.loc, /obj/structure/aquarium)) - create_aquarium_component(movable_target) - else //otherwise the component will be created when trying to insert the thing. - RegisterSignal(target, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK, .proc/create_aquarium_component) - -/datum/element/deferred_aquarium_content/Detach(datum/target) - . = ..() - UnregisterSignal(target, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK) - -/datum/element/deferred_aquarium_content/proc/create_aquarium_component(datum/source) - SIGNAL_HANDLER - - source.AddComponent(/datum/component/aquarium_content, aquarium_content_type) - Detach(source) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 01b59f61793..46df27b23e0 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -1340,6 +1340,7 @@ GLOBAL_DATUM_INIT(fire_overlay, /mutable_appearance, mutable_appearance('icons/e alpha = 0 transform = animation_matrix + SEND_SIGNAL(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, 3) // This is instant on byond's end, but to our clients this looks like a quick drop animate(src, alpha = old_alpha, pixel_x = old_x, pixel_y = old_y, transform = old_transform, time = 3, easing = CUBIC_EASING) diff --git a/code/game/objects/structures/maintenance.dm b/code/game/objects/structures/maintenance.dm index 1b70dddaf4f..55a727189a5 100644 --- a/code/game/objects/structures/maintenance.dm +++ b/code/game/objects/structures/maintenance.dm @@ -25,7 +25,7 @@ at the cost of risking a vicious bite.**/ /obj/item/restraints/handcuffs/cable/pink = 1, /obj/item/restraints/handcuffs/alien = 2, /obj/item/coin/bananium = 9, - /datum/aquarium_behaviour/fish/ratfish = 10, + /obj/item/fish/ratfish = 10, /obj/item/knife/butcher = 5, /obj/item/coin/mythril = 1, ) @@ -33,16 +33,14 @@ at the cost of risking a vicious bite.**/ /obj/structure/moisture_trap/Initialize(mapload) . = ..() + ADD_TRAIT(src, TRAIT_FISH_SAFE_STORAGE, TRAIT_GENERIC) + AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOIST, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 20) if(prob(40)) critter_infested = FALSE if(prob(75)) var/picked_item = pick_weight(loot_table) - if(ispath(picked_item, /datum/aquarium_behaviour/fish)) - hidden_item = generate_fish(src, picked_item) - else - hidden_item = new picked_item(src) - AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOIST, CELL_VIRUS_TABLE_GENERIC, rand(2,4), 20) - ADD_TRAIT(src, TRAIT_FISH_SAFE_STORAGE, TRAIT_GENERIC) + hidden_item = new picked_item(src) + /obj/structure/moisture_trap/Destroy() if(hidden_item) diff --git a/code/modules/aquarium/aquarium.dm b/code/modules/aquarium/aquarium.dm index 457c181790d..437e7fec9ea 100644 --- a/code/modules/aquarium/aquarium.dm +++ b/code/modules/aquarium/aquarium.dm @@ -37,14 +37,22 @@ ///Current layers in use by aquarium contents var/list/used_layers = list() - var/alive_fish = 0 - var/dead_fish = 0 + /// /obj/item/fish in the aquarium - does not include things with aquarium visuals that are not fish + var/list/tracked_fish = list() /obj/structure/aquarium/Initialize(mapload) . = ..() update_appearance() RegisterSignal(src,COMSIG_PARENT_ATTACKBY, .proc/feed_feedback) +/obj/structure/aquarium/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) + . = ..() + if(istype(arrived,/obj/item/fish)) + tracked_fish += arrived + +/obj/structure/aquarium/Exited(atom/movable/gone, direction) + . = ..() + tracked_fish -= gone /obj/structure/aquarium/proc/request_layer(layer_type) /** @@ -117,9 +125,6 @@ update_appearance() return TRUE else - // This signal exists so we common items instead of adding component on init can just register creation of one in response. - // This way we can avoid the cost of 9999 aquarium components on rocks that will never see water in their life. - SEND_SIGNAL(I,COMSIG_AQUARIUM_BEFORE_INSERT_CHECK,src) var/datum/component/aquarium_content/content_component = I.GetComponent(/datum/component/aquarium_content) if(content_component && content_component.is_ready_to_insert(src)) if(user.transferItemToLoc(I,src)) @@ -138,7 +143,6 @@ /obj/structure/aquarium/interact(mob/user) if(!broken && user.pulling && isliving(user.pulling)) var/mob/living/living_pulled = user.pulling - SEND_SIGNAL(living_pulled, COMSIG_AQUARIUM_BEFORE_INSERT_CHECK,src) var/datum/component/aquarium_content/content_component = living_pulled.GetComponent(/datum/component/aquarium_content) if(content_component && content_component.is_ready_to_insert(src)) try_to_put_mob_in(user) @@ -169,6 +173,13 @@ /obj/structure/aquarium/proc/admire(mob/user) to_chat(user,span_notice("You take a moment to watch [src].")) if(do_after(user, 5 SECONDS, target = src)) + var/alive_fish = 0 + var/dead_fish = 0 + for(var/obj/item/fish/fish in tracked_fish) + if(fish.status == FISH_ALIVE) + alive_fish++ + else + dead_fish++ //Check if there are live fish - good mood //All fish dead - bad mood. //No fish - nothing. @@ -255,3 +266,14 @@ #undef AQUARIUM_LAYER_STEP #undef AQUARIUM_MIN_OFFSET #undef AQUARIUM_MAX_OFFSET + + +/obj/structure/aquarium/prefilled/Initialize(mapload) + . = ..() + + new /obj/item/aquarium_prop/rocks(src) + new /obj/item/aquarium_prop/seaweed(src) + + new /obj/item/fish/goldfish(src) + new /obj/item/fish/angelfish(src) + new /obj/item/fish/guppy(src) diff --git a/code/modules/aquarium/aquarium_behaviour.dm b/code/modules/aquarium/aquarium_behaviour.dm index 46d6e86faa7..e735b3ec45d 100644 --- a/code/modules/aquarium/aquarium_behaviour.dm +++ b/code/modules/aquarium/aquarium_behaviour.dm @@ -1,301 +1,8 @@ -//Fish breeding stops if fish count exceeds this. -#define AQUARIUM_MAX_BREEDING_POPULATION 20 - -// Configuration objects defining prop/fish behaviour in the aquarium -// These are used as a base for autogenerating the actual instances -/datum/aquarium_behaviour - var/name = "base aquarium element" - var/desc = "" - - var/unique = FALSE - - var/icon = 'icons/obj/aquarium.dmi' - /// Icon state used for catalog/autogenerated fish item. Also used as basis for in aquarium visual if dedicated_in_aquarium_icon_state is not set. - var/icon_state - /// Applied and item/catalog for use with greyscaled icons. - var/color - - /// How the thing will be layered - var/layer_mode = AQUARIUM_LAYER_MODE_AUTO - /// If the starting position is randomised within bounds. - var/randomize_position = FALSE - - /** - * Fish sprite how to: - * Need to be centered on 16,16 in the dmi and facing left by default. - * sprite_height/sprite_width is the size it will have in aquarium and used to control animation boundaries. - * source_height/source_width is the size of the original icon (ideally only the non-empty parts) - */ - - /// If this is set this icon state will be used for the holder while icon_state will only be used for item/catalog. Transformation from source_width/height WON'T be applied. - var/dedicated_in_aquarium_icon_state - /// Applied to vc object only for use with greyscaled icons. - var/aquarium_vc_color - - //Target sprite size for path/position calculations. - var/sprite_height = 3 - var/sprite_width = 3 - //This is the size of the source sprite. This will be used to calculate scale down factor. - var/source_width = 32 - var/source_height = 32 - - /// Current animation - var/current_animation - - /// Does this behviour need additional processing in aquarium, will be added to SSobj processing on insertion - var/processing = FALSE - - /// Our holder component - var/datum/component/aquarium_content/parent - -/// Applies icon,color and base scaling to our visual holder -/datum/aquarium_behaviour/proc/apply_appearance(obj/effect/holder) - holder.icon = icon - if(dedicated_in_aquarium_icon_state) - holder.icon_state = dedicated_in_aquarium_icon_state - else - holder.icon_state = icon_state - holder.transform = base_transform() - if(aquarium_vc_color) - holder.color = aquarium_vc_color - -// Generates scaling matrix for our visual -/datum/aquarium_behaviour/proc/base_transform() - var/matrix/matrix = matrix() - if(!dedicated_in_aquarium_icon_state) - var/x_scale = sprite_width / source_width - var/y_scale = sprite_height / source_height - matrix.Scale(x_scale, y_scale) - return matrix - -/// Called when fed. -/datum/aquarium_behaviour/proc/on_feeding(datum/reagents/feed_reagents) - return - -/// Called when aquarium fluid changes. -/datum/aquarium_behaviour/proc/on_fluid_changed() - return - -/// Called to update current animation based on state -/datum/aquarium_behaviour/proc/update_animation() - return - -/// Called when inserted into new aquarium -/datum/aquarium_behaviour/proc/on_inserted() - if(processing) - START_PROCESSING(SSobj, src) - -/// Called just before the object gets removed from the aquarium -/datum/aquarium_behaviour/proc/before_removal() - return - -/datum/aquarium_behaviour/Destroy(force, ...) - STOP_PROCESSING(SSobj, src) - parent = null - return ..() - -/datum/aquarium_behaviour/prop - name = "aquarium prop" - unique = TRUE - layer_mode = AQUARIUM_LAYER_MODE_BOTTOM - - // Prop sprites do not need any scaling they go 1:1 - sprite_width = 32 - sprite_height = 32 - source_width = 32 - source_height = 32 - -/datum/aquarium_behaviour/prop/rocks - name = "rocks" - icon_state = "rocks" - -/datum/aquarium_behaviour/prop/seaweed_top - name = "dense seaweeds" - icon_state = "seaweeds_front" - layer_mode = AQUARIUM_LAYER_MODE_TOP - -/datum/aquarium_behaviour/prop/seaweed - name = "seaweeds" - icon_state = "seaweeds_back" - layer_mode = AQUARIUM_LAYER_MODE_BOTTOM - -/datum/aquarium_behaviour/prop/rockfloor - name = "rock floor" - icon_state = "rockfloor" - layer_mode = AQUARIUM_LAYER_MODE_BOTTOM - -/datum/aquarium_behaviour/prop/treasure - name = "tiny treasure chest" - icon_state = "treasure" - layer_mode = AQUARIUM_LAYER_MODE_BOTTOM - -/datum/aquarium_behaviour/fish - name = "generic fish" - icon_state = "fish_greyscale" - randomize_position = TRUE //We have random starting point of our swim path - - current_animation = AQUARIUM_ANIMATION_FISH_SWIM - processing = TRUE - +/* // Default size of the "greyscale_fish" icon_state sprite_height = 3 sprite_width = 3 - /// 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 - -/datum/aquarium_behaviour/fish/on_inserted() - . = ..() - if(isnull(last_feeding)) //Fish start fed. - last_feeding = world.time - if(status == FISH_DEAD) - parent.current_aquarium.dead_fish += 1 - else - parent.current_aquarium.alive_fish += 1 - parent.stop_flopping() - -/datum/aquarium_behaviour/fish/before_removal() - . = ..() - if(status == FISH_DEAD) - parent.current_aquarium.dead_fish -= 1 - else - parent.current_aquarium.alive_fish -= 1 - //We do not stop processing properties here. We want fish to die outside of aquariums after first insert. We only stop processing in properties.death or destroy - if(!QDELETED(parent) && status != FISH_DEAD) - parent.start_flopping() - -/datum/aquarium_behaviour/fish/on_fluid_changed() - //In case we'll flop to bottom from this or go back to swimming. - update_animation() - -/// Updates our animation variable based on state and prompts component to animate it. -/datum/aquarium_behaviour/fish/update_animation() - var/obj/structure/aquarium/aquarium = parent.current_aquarium - var/old_animation = current_animation - if(!aquarium || aquarium.fluid_type == AQUARIUM_FLUID_AIR || status == FISH_DEAD) - current_animation = AQUARIUM_ANIMATION_FISH_DEAD - else - current_animation = AQUARIUM_ANIMATION_FISH_SWIM - if(aquarium && old_animation != current_animation) - parent.generate_animation() - -/datum/aquarium_behaviour/fish/on_feeding(datum/reagents/feed_reagents) - if(feed_reagents.has_reagent(food)) - last_feeding = world.time - -/// Checks if our current environment lets us live. -/datum/aquarium_behaviour/fish/proc/proper_environment() - var/obj/structure/aquarium/aquarium = parent.current_aquarium - if(!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 - -/datum/aquarium_behaviour/fish/process(delta_time) - set waitfor = FALSE - process_health(delta_time) - if(status != FISH_DEAD && ready_to_reproduce()) - try_to_reproduce() - -/datum/aquarium_behaviour/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) - -/datum/aquarium_behaviour/fish/proc/adjust_health(amt) - health = clamp(amt, 0, initial(health)) - if(health <= 0) - death() - -/datum/aquarium_behaviour/fish/proc/death() - STOP_PROCESSING(SSobj, src) - status = FISH_DEAD - var/message = span_notice("\The [name] dies.") - if(parent.current_aquarium) - parent.current_aquarium.visible_message(message) - parent.current_aquarium.alive_fish -= 1 - parent.current_aquarium.dead_fish += 1 - else - var/atom/movable/AM = parent.parent - AM.visible_message(message) - parent.stop_flopping() - update_animation() - -/datum/aquarium_behaviour/fish/proc/ready_to_reproduce() - return parent.current_aquarium && parent.current_aquarium.allow_breeding && health == initial(health) && stable_population > 1 && world.time - last_breeding >= breeding_timeout - -/datum/aquarium_behaviour/fish/proc/try_to_reproduce() - if(!parent.current_aquarium) - return - if(parent.current_aquarium.alive_fish + parent.current_aquarium.dead_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/atom/movable/fish_or_prop in parent.current_aquarium) - if(fish_or_prop == parent.parent) - continue - var/datum/component/aquarium_content/other_content = fish_or_prop.GetComponent(/datum/component/aquarium_content) - if(other_content && other_content.properties.type == type) - other_fish_of_same_type += other_content.properties - if(length(other_fish_of_same_type) >= stable_population) - return - var/datum/aquarium_behaviour/fish/second_fish - for(var/datum/aquarium_behaviour/fish/other_fish in other_fish_of_same_type) - if(other_fish.ready_to_reproduce()) - second_fish = other_fish - break - if(second_fish) - generate_fish(parent.current_aquarium,type) - last_breeding = world.time - second_fish.last_breeding = world.time - /// This path exists mostly for admin abuse. /datum/aquarium_behaviour/fish/auto name = "automatic fish" @@ -309,6 +16,4 @@ holder.appearance = parent.parent holder.transform = base_transform() holder.dir = WEST - - -#undef AQUARIUM_MAX_BREEDING_POPULATION +*/ diff --git a/code/modules/aquarium/aquarium_kit.dm b/code/modules/aquarium/aquarium_kit.dm index d9a8e28112e..1080372fba5 100644 --- a/code/modules/aquarium/aquarium_kit.dm +++ b/code/modules/aquarium/aquarium_kit.dm @@ -25,13 +25,14 @@ component_type = /datum/component/storage/concrete/fish_case /obj/item/storage/fish_case/Initialize(mapload) + ADD_TRAIT(src, TRAIT_FISH_SAFE_STORAGE, TRAIT_GENERIC) // Before populate so fish instatiates in ready container already . = ..() - ADD_TRAIT(src, TRAIT_FISH_SAFE_STORAGE, TRAIT_GENERIC) ///Fish case with single random fish inside. /obj/item/storage/fish_case/random/PopulateContents() . = ..() - generate_fish(src, select_fish_type()) + var/fish_type = select_fish_type() + new fish_type(src) /obj/item/storage/fish_case/random/proc/select_fish_type() return random_fish_type() @@ -47,14 +48,16 @@ /obj/item/storage/fish_case/syndicate/PopulateContents() . = ..() - generate_fish(src, pick(/datum/aquarium_behaviour/fish/donkfish, /datum/aquarium_behaviour/fish/emulsijack)) + var/fish_type = pick(/obj/item/fish/donkfish, /obj/item/fish/emulsijack) + new fish_type(src) /obj/item/storage/fish_case/tiziran name = "imported fish case" /obj/item/storage/fish_case/tiziran/PopulateContents() . = ..() - generate_fish(src, pick(/datum/aquarium_behaviour/fish/dwarf_moonfish, /datum/aquarium_behaviour/fish/gunner_jellyfish, /datum/aquarium_behaviour/fish/needlefish, /datum/aquarium_behaviour/fish/armorfish)) + var/fish_type = pick(/obj/item/fish/dwarf_moonfish, /obj/item/fish/gunner_jellyfish, /obj/item/fish/needlefish, /obj/item/fish/armorfish) + new fish_type(src) ///Book detailing where to get the fish and their properties. /obj/item/book/fish_catalog @@ -78,25 +81,26 @@ var/static/fish_info if(!fish_info) fish_info = list() - for(var/_fish_behaviour in subtypesof(/datum/aquarium_behaviour/fish)) - var/datum/aquarium_behaviour/fish/fish_behaviour = _fish_behaviour + for(var/_fish_type in subtypesof(/obj/item/fish)) + var/obj/item/fish/fish = _fish_type var/list/fish_data = list() - if(!initial(fish_behaviour.show_in_catalog)) + if(!initial(fish.show_in_catalog)) continue - fish_data["name"] = initial(fish_behaviour.name) - fish_data["desc"] = initial(fish_behaviour.desc) - fish_data["fluid"] = initial(fish_behaviour.required_fluid_type) - fish_data["temp_min"] = initial(fish_behaviour.required_temperature_min) - fish_data["temp_max"] = initial(fish_behaviour.required_temperature_max) - fish_data["icon"] = sanitize_css_class_name("[initial(fish_behaviour.icon)][initial(fish_behaviour.icon_state)]") - fish_data["color"] = initial(fish_behaviour.color) - fish_data["source"] = initial(fish_behaviour.available_in_random_cases) ? "[AQUARIUM_COMPANY] Fish Packs" : "Unknown" - var/datum/reagent/food_type = initial(fish_behaviour.food) + fish_data["name"] = initial(fish.name) + fish_data["desc"] = initial(fish.desc) + fish_data["fluid"] = initial(fish.required_fluid_type) + fish_data["temp_min"] = initial(fish.required_temperature_min) + fish_data["temp_max"] = initial(fish.required_temperature_max) + fish_data["icon"] = sanitize_css_class_name("[initial(fish.icon)][initial(fish.icon_state)]") + fish_data["color"] = initial(fish.color) + fish_data["source"] = initial(fish.available_in_random_cases) ? "[AQUARIUM_COMPANY] Fish Packs" : "Unknown" + var/datum/reagent/food_type = initial(fish.food) if(food_type != /datum/reagent/consumable/nutriment) fish_data["feed"] = initial(food_type.name) else fish_data["feed"] = "[AQUARIUM_COMPANY] Fish Feed" fish_info += list(fish_data) + // TODO: Custom entries .["fish_info"] = fish_info .["sponsored_by"] = AQUARIUM_COMPANY @@ -121,12 +125,43 @@ /obj/item/aquarium_prop name = "generic aquarium prop" desc = "very boring" + icon = 'icons/obj/aquarium.dmi' + w_class = WEIGHT_CLASS_TINY + var/layer_mode = AQUARIUM_LAYER_MODE_BOTTOM + +/obj/item/aquarium_prop/Initialize(mapload) + . = ..() + AddComponent(/datum/component/aquarium_content) + +/obj/item/aquarium_prop/rocks + name = "rocks" + icon_state = "rocks" + +/obj/item/aquarium_prop/seaweed_top + name = "dense seaweeds" + icon_state = "seaweeds_front" + layer_mode = AQUARIUM_LAYER_MODE_TOP + +/obj/item/aquarium_prop/seaweed + name = "seaweeds" + icon_state = "seaweeds_back" + layer_mode = AQUARIUM_LAYER_MODE_BOTTOM + +/obj/item/aquarium_prop/rockfloor + name = "rock floor" + icon_state = "rockfloor" + layer_mode = AQUARIUM_LAYER_MODE_BOTTOM + +/obj/item/aquarium_prop/treasure + name = "tiny treasure chest" + icon_state = "treasure" + layer_mode = AQUARIUM_LAYER_MODE_BOTTOM /obj/item/storage/box/aquarium_props name = "aquarium props box" desc = "All you need to make your aquarium look good." /obj/item/storage/box/aquarium_props/PopulateContents() - for(var/prop_type in subtypesof(/datum/aquarium_behaviour/prop)) - generate_fish(src, prop_type, /obj/item/aquarium_prop) + for(var/prop_type in subtypesof(/obj/item/aquarium_prop)) + new prop_type(src) diff --git a/code/modules/aquarium/fish.dm b/code/modules/aquarium/fish.dm index f3d494b5ecd..220918499aa 100644 --- a/code/modules/aquarium/fish.dm +++ b/code/modules/aquarium/fish.dm @@ -2,25 +2,300 @@ /obj/item/fish name = "generic looking aquarium fish" desc = "very bland" + icon = 'icons/obj/aquarium.dmi' + icon_state = "bugfish" + w_class = WEIGHT_CLASS_TINY -/// Automatically generates object of given base path from the behaviour type in loc -/proc/generate_fish(loc,behaviour_type,base_path=/obj/item/fish) - var/datum/aquarium_behaviour/behaviour = behaviour_type - var/obj/item/fish = new base_path(loc) - fish.name = initial(behaviour.name) - fish.icon = initial(behaviour.icon) - fish.icon_state = initial(behaviour.icon_state) - fish.desc = initial(behaviour.desc) - if(initial(behaviour.color)) - fish.add_atom_colour(initial(behaviour.color), FIXED_COLOUR_PRIORITY) - if(ispath(behaviour_type,/datum/aquarium_behaviour/fish)) - var/datum/aquarium_behaviour/fish/fish_behaviour = behaviour_type - var/fillet_type = initial(fish_behaviour.fillet_type) - if(fillet_type) - fish.AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, 1, 5) - fish.AddElement(/datum/element/deferred_aquarium_content, behaviour_type) - return fish + /// 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 + +/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) + +/obj/item/fish/Moved(atom/OldLoc, Dir) + . = ..() + 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(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 && !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) @@ -30,20 +305,18 @@ if(!probability_table) probability_table = list() var/chance_table = list() - for(var/_fish_behavior in subtypesof(/datum/aquarium_behaviour/fish)) - var/datum/aquarium_behaviour/fish/fish_behavior = _fish_behavior - if(required_fluid && initial(fish_behavior.required_fluid_type) != required_fluid) + 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_behavior.available_in_random_cases) || !case_fish_only) - chance_table[fish_behavior] = initial(fish_behavior.random_case_rarity) + 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]) -// Actual fish definitions below - there's no specific paths, they are autogenerated from behaviours - // Freshwater fish -/datum/aquarium_behaviour/fish/goldfish +/obj/item/fish/goldfish name = "goldfish" desc = "Despite common belief, goldfish do not have three-second memories. They can actually remember things that happened up to three months ago." icon_state = "goldfish" @@ -52,7 +325,7 @@ stable_population = 3 -/datum/aquarium_behaviour/fish/angelfish +/obj/item/fish/angelfish name = "angelfish" desc = "Young Angelfish often live in groups, while adults prefer solitary life. They become territorial and aggressive toward other fish when they reach adulthood." icon_state = "angelfish" @@ -62,7 +335,7 @@ stable_population = 3 -/datum/aquarium_behaviour/fish/guppy +/obj/item/fish/guppy name = "guppy" desc = "Guppy is also known as rainbow fish because of the brightly colored body and fins." icon_state = "guppy" @@ -73,7 +346,7 @@ stable_population = 6 -/datum/aquarium_behaviour/fish/plasmatetra +/obj/item/fish/plasmatetra name = "plasma tetra" desc = "Due to their small size, tetras are prey to many predators in their watery world, including eels, crustaceans, and invertebrates." icon_state = "plastetra" @@ -82,7 +355,7 @@ stable_population = 3 -/datum/aquarium_behaviour/fish/catfish +/obj/item/fish/catfish name = "cory catfish" desc = "A catfish has about 100,000 taste buds, and their bodies are covered with them to help detect chemicals present in the water and also to respond to touch." icon_state = "catfish" @@ -91,17 +364,9 @@ stable_population = 3 -/datum/aquarium_behaviour/fish/spacecarp - name = "space carp" - desc = "This space predator fish is known to cause yearly damage to space stations during their migrations." - icon_state = "carp" - sprite_width = 8 - sprite_height = 8 - available_in_random_cases = FALSE - // Saltwater fish below -/datum/aquarium_behaviour/fish/clownfish +/obj/item/fish/clownfish name = "clownfish" desc = "Clownfish catch prey by swimming onto the reef, attracting larger fish, and luring them back to the anemone. The anemone will sting and eat the larger fish, leaving the remains for the clownfish." icon_state = "clownfish" @@ -112,7 +377,7 @@ stable_population = 4 -/datum/aquarium_behaviour/fish/cardinal +/obj/item/fish/cardinal name = "cardinalfish" desc = "Cardinalfish are often found near sea urchins, where the fish hide when threatened." icon_state = "cardinalfish" @@ -121,7 +386,7 @@ stable_population = 4 -/datum/aquarium_behaviour/fish/greenchromis +/obj/item/fish/greenchromis name = "green chromis" desc = "The Chromis can vary in color from blue to green depending on the lighting and distance from the lights." icon_state = "greenchromis" @@ -129,9 +394,9 @@ aquarium_vc_color = "#00ff00" required_fluid_type = AQUARIUM_FLUID_SALTWATER - stable_population = 5 -/datum/aquarium_behaviour/fish/firefish + stable_population = 5 +/obj/item/fish/firefish name = "firefish goby" desc = "To communicate in the wild, the firefish uses its dorsal fin to alert others of potential danger." icon_state = "firefish" @@ -141,7 +406,7 @@ stable_population = 3 -/datum/aquarium_behaviour/fish/pufferfish +/obj/item/fish/pufferfish name = "pufferfish" desc = "One Pufferfish contains enough toxins in its liver to kill 30 people." icon_state = "pufferfish" @@ -151,7 +416,7 @@ stable_population = 3 -/datum/aquarium_behaviour/fish/lanternfish +/obj/item/fish/lanternfish name = "lanternfish" desc = "Typically found in areas below 6600 feet below the surface of the ocean, they live in complete darkness." icon_state = "lanternfish" @@ -165,7 +430,7 @@ stable_population = 3 //Tiziran Fish -/datum/aquarium_behaviour/fish/dwarf_moonfish +/obj/item/fish/dwarf_moonfish name = "dwarf moonfish" desc = "Ordinarily in the wild, the Zagoskian moonfish is around the size of a tuna, however through selective breeding a smaller breed suitable for being kept as an aquarium pet has been created." icon_state = "dwarf_moonfish" @@ -173,7 +438,7 @@ stable_population = 2 fillet_type = /obj/item/food/fishmeat/moonfish -/datum/aquarium_behaviour/fish/gunner_jellyfish +/obj/item/fish/gunner_jellyfish name = "gunner jellyfish" desc = "So called due to their resemblance to an artillery shell, the gunner jellyfish is native to Tizira, where it is enjoyed as a delicacy. Produces a mild hallucinogen that is destroyed by cooking." icon_state = "gunner_jellyfish" @@ -181,7 +446,7 @@ stable_population = 4 fillet_type = /obj/item/food/fishmeat/gunner_jellyfish -/datum/aquarium_behaviour/fish/needlefish +/obj/item/fish/needlefish name = "needlefish" desc = "A tiny, transparent fish which resides in large schools in the oceans of Tizira. A common food for other, larger fish." icon_state = "needlefish" @@ -189,7 +454,7 @@ stable_population = 12 fillet_type = null -/datum/aquarium_behaviour/fish/armorfish +/obj/item/fish/armorfish name = "armorfish" desc = "A small shellfish native to Tizira's oceans, known for its exceptionally hard shell. Consumed similarly to prawns." icon_state = "armorfish" @@ -201,10 +466,10 @@ name = "box full of fish" /obj/item/storage/box/fish_debug/PopulateContents() - for(var/fish_type in subtypesof(/datum/aquarium_behaviour/fish)) - generate_fish(src,fish_type) + for(var/fish_type in subtypesof(/obj/item/fish)) + new fish_type(src) -/datum/aquarium_behaviour/fish/donkfish +/obj/item/fish/donkfish name = "donk co. company patent donkfish" desc = "A lab-grown donkfish. Its invention was an accident for the most part, as it was intended to be consumed in donk pockets. Unfortunately, it tastes horrible, so it has now become a pseudo-mascot." icon_state = "donkfish" @@ -213,7 +478,7 @@ stable_population = 4 fillet_type = /obj/item/food/fishmeat/donkfish -/datum/aquarium_behaviour/fish/emulsijack +/obj/item/fish/emulsijack name = "toxic emulsijack" desc = "Ah, the terrifying emulsijack. Created in a laboratory, this slimey, scaleless fish emits an invisible toxin that emulsifies other fish for it to feed on. Its only real use is for completely ruining a tank." icon_state = "emulsijack" @@ -221,22 +486,21 @@ required_fluid_type = AQUARIUM_FLUID_ANADROMOUS stable_population = 3 -/datum/aquarium_behaviour/fish/emulsijack/process(delta_time = SSOBJ_DT) +/obj/item/fish/emulsijack/process(delta_time = SSOBJ_DT) var/emulsified = FALSE - if(parent.current_aquarium) - for(var/obj/item/fish/victim in parent.current_aquarium.contents) - var/datum/component/aquarium_content/content_component = victim.GetComponent(/datum/component/aquarium_content) - var/datum/aquarium_behaviour/fish/fish_properties = content_component.properties - if(istype(fish_properties, /datum/aquarium_behaviour/fish/emulsijack)) + var/obj/structure/aquarium/aquarium = loc + if(istype(aquarium)) + for(var/obj/item/fish/victim in aquarium) + if(istype(victim, /obj/item/fish/emulsijack)) continue //no team killing - fish_properties.adjust_health((fish_properties.health - 3) * delta_time) //the victim may heal a bit but this will quickly kill + victim.adjust_health((victim.health - 3) * delta_time) //the victim may heal a bit but this will quickly kill emulsified = TRUE if(emulsified) adjust_health((health + 3) * delta_time) last_feeding = world.time //emulsijack feeds on the emulsion! ..() -/datum/aquarium_behaviour/fish/ratfish +/obj/item/fish/ratfish name = "ratfish" desc = "A rat exposed to the murky waters of maintenance too long. Any higher power, if it revealed itself, would state that the ratfish's continued existence is extremely unwelcome." icon_state = "ratfish" @@ -245,6 +509,7 @@ stable_population = 10 //set by New, but this is the default config value fillet_type = /obj/item/food/meat/slab/human/mutant/zombie //eww... -/datum/aquarium_behaviour/fish/ratfish/New() +/obj/item/fish/ratfish/Initialize(mapload) + . = ..() //stable pop reflects the config for how many mice migrate. powerful... stable_population = CONFIG_GET(number/mice_roundstart) diff --git a/code/modules/asset_cache/assets/fish.dm b/code/modules/asset_cache/assets/fish.dm index 2914b686038..a76e8b096fe 100644 --- a/code/modules/asset_cache/assets/fish.dm +++ b/code/modules/asset_cache/assets/fish.dm @@ -2,8 +2,8 @@ name = "fish" /datum/asset/spritesheet/fish/create_spritesheets() - for (var/path in subtypesof(/datum/aquarium_behaviour/fish)) - var/datum/aquarium_behaviour/fish/fish_type = path + for (var/path in subtypesof(/obj/item/fish)) + var/obj/item/fish/fish_type = path var/fish_icon = initial(fish_type.icon) var/fish_icon_state = initial(fish_type.icon_state) var/id = sanitize_css_class_name("[fish_icon][fish_icon_state]") diff --git a/tgstation.dme b/tgstation.dme index 2baf6bd78fb..d425187590e 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -204,6 +204,7 @@ #include "code\__DEFINES\dcs\signals\signals_customizable.dm" #include "code\__DEFINES\dcs\signals\signals_cytology.dm" #include "code\__DEFINES\dcs\signals\signals_datum.dm" +#include "code\__DEFINES\dcs\signals\signals_fish.dm" #include "code\__DEFINES\dcs\signals\signals_food.dm" #include "code\__DEFINES\dcs\signals\signals_gib.dm" #include "code\__DEFINES\dcs\signals\signals_global.dm" @@ -914,7 +915,6 @@ #include "code\datums\elements\curse_announcement.dm" #include "code\datums\elements\cursed.dm" #include "code\datums\elements\death_drops.dm" -#include "code\datums\elements\deferred_aquarium_content.dm" #include "code\datums\elements\delete_on_drop.dm" #include "code\datums\elements\deliver_first.dm" #include "code\datums\elements\digitalcamo.dm"