diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index 77ec96a4fe8..f18d092fd15 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -50755,7 +50755,8 @@ "rqt" = ( /obj/machinery/airalarm/directional/north, /obj/effect/decal/cleanable/dirt, -/obj/structure/aquarium/lawyer, +/obj/item/fish_tank/lawyer, +/obj/structure/table/wood, /turf/open/floor/iron/dark, /area/station/service/lawoffice) "rqw" = ( diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 13c2c019fdb..08d3aa51e9b 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -44890,7 +44890,8 @@ /area/station/hallway/primary/fore) "lhp" = ( /obj/structure/sign/poster/official/report_crimes/directional/south, -/obj/structure/aquarium/lawyer, +/obj/item/fish_tank/lawyer, +/obj/structure/table/wood, /turf/open/floor/wood, /area/station/service/lawoffice) "lhC" = ( diff --git a/_maps/map_files/IceBoxStation/IceBoxStation.dmm b/_maps/map_files/IceBoxStation/IceBoxStation.dmm index 3a895cc7b53..498bba9c2e8 100644 --- a/_maps/map_files/IceBoxStation/IceBoxStation.dmm +++ b/_maps/map_files/IceBoxStation/IceBoxStation.dmm @@ -7969,7 +7969,8 @@ /turf/open/floor/iron/dark, /area/station/service/hydroponics/garden) "cgB" = ( -/obj/structure/aquarium/lawyer, +/obj/item/fish_tank/lawyer, +/obj/structure/table/wood, /turf/open/floor/wood, /area/station/service/lawoffice) "cgC" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 839edbd4327..1bc583699ef 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -59697,7 +59697,8 @@ name = "Lawyer Requests Console" }, /obj/machinery/newscaster/directional/west, -/obj/structure/aquarium/lawyer, +/obj/item/fish_tank/lawyer, +/obj/structure/table/wood, /turf/open/floor/wood, /area/station/service/lawoffice) "uZP" = ( diff --git a/_maps/map_files/NorthStar/north_star.dmm b/_maps/map_files/NorthStar/north_star.dmm index aaa34d20351..94d868d1227 100644 --- a/_maps/map_files/NorthStar/north_star.dmm +++ b/_maps/map_files/NorthStar/north_star.dmm @@ -65672,7 +65672,8 @@ /turf/open/floor/iron/white, /area/station/science/lobby) "qSD" = ( -/obj/structure/aquarium/lawyer, +/obj/item/fish_tank/lawyer, +/obj/structure/table/wood, /turf/open/floor/wood/parquet, /area/station/service/lawoffice) "qSJ" = ( diff --git a/_maps/map_files/tramstation/tramstation.dmm b/_maps/map_files/tramstation/tramstation.dmm index 0335fd1b427..0c1245591ec 100644 --- a/_maps/map_files/tramstation/tramstation.dmm +++ b/_maps/map_files/tramstation/tramstation.dmm @@ -18865,7 +18865,8 @@ /area/station/commons/vacant_room) "fEZ" = ( /obj/structure/noticeboard/directional/north, -/obj/structure/aquarium/lawyer, +/obj/item/fish_tank/lawyer, +/obj/structure/table/wood, /obj/structure/cable, /obj/machinery/power/apc/auto_name/directional/west, /turf/open/floor/wood, diff --git a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm index 36a2ca2c805..f0024072218 100644 --- a/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm +++ b/code/__DEFINES/dcs/signals/signals_atom/signals_atom_movable.dm @@ -122,6 +122,9 @@ ///from base of /atom/movable/point_at: (atom/A, obj/effect/temp_visual/point/point) #define COMSIG_MOVABLE_POINTED "movable_pointed" +///From /datum/component/aquarium/get_content_beauty: (beauty_holder) +#define COMSIG_MOVABLE_GET_AQUARIUM_BEAUTY "movable_ge_aquarium_beauty" + /// Sent to movables when they are being stolen by a spy: (mob/living/spy, datum/spy_bounty/bounty) #define COMSIG_MOVABLE_SPY_STEALING "movable_spy_stealing" /// Called when something is pushed by a living mob bumping it: (mob/living/pusher, push force) diff --git a/code/__DEFINES/dcs/signals/signals_fish.dm b/code/__DEFINES/dcs/signals/signals_fish.dm index b3147466592..08a38fc02ab 100644 --- a/code/__DEFINES/dcs/signals/signals_fish.dm +++ b/code/__DEFINES/dcs/signals/signals_fish.dm @@ -1,23 +1,32 @@ // Aquarium related signals -#define COMSIG_AQUARIUM_SURFACE_CHANGED "aquarium_surface_changed" + +///From /datum/component/aquarium/ui_act, when changing the fluid of the aquarium: (fluid_type) #define COMSIG_AQUARIUM_FLUID_CHANGED "aquarium_fluid_changed" -///Called on aquarium/attackby: (aquarium) -#define COMSIG_TRY_INSERTING_IN_AQUARIUM "item_try_inserting_in_aquarium" +///From /datum/component/aquarium/can_insert: (obj/item/item) +#define COMSIG_AQUARIUM_CAN_INSERT "aquarium_can_insert" ///The item will be inserted into the aquarium #define COMSIG_CAN_INSERT_IN_AQUARIUM (1<<0) ///The item won't be inserted into the aquarium, but will early return attackby anyway. #define COMSIG_CANNOT_INSERT_IN_AQUARIUM (1<<1) +///From /datum/component/aquarium_content/set_vc_base_position: (obj/effect/aquarium/visual) +#define COMSIG_AQUARIUM_SET_VISUAL "aquarium_set_visual" +///From /datum/component/aquarium_content/remove_from_aquarium: (obj/effect/aquarium/visual) +#define COMSIG_AQUARIUM_REMOVE_VISUAL "aquarium_remove_visual" +///From /obj/item/fish/try_to_reproduce: (fish, candidates) +#define COMSIG_AQUARIUM_GET_REPRODUCTION_CANDIDATES "aquarium_get_reproduction_candidates" +///From /datum/fish_evolution/check_conditions: (fish, mate, evolution) +#define COMSIG_AQUARIUM_CHECK_EVOLUTION_CONDITIONS "aquarium_check_evolution_conditions" + #define COMPONENT_ALLOW_EVOLUTION (1<<0) ///Updates the appearance of a newly generated aquarium content visual:(visual) #define COMSIG_AQUARIUM_CONTENT_GENERATE_APPEARANCE "aquarium_content_apply_appearance" ///Updates the base position of an aquarium content visual:(aquarium, visual) -#define AQUARIUM_CONTENT_RANDOMIZE_POSITION "aquarium_content_randomize_position" +#define COMSIG_AQUARIUM_CONTENT_RANDOMIZE_POSITION "aquarium_content_randomize_position" ///Updates the animation of an aquarium content visual:(aquarium, visual) #define COMSIG_AQUARIUM_CONTENT_DO_ANIMATION "aquarium_content_do_animation" // Fish signals #define COMSIG_FISH_STATUS_CHANGED "fish_status_changed" -#define COMSIG_FISH_STIRRED "fish_stirred" ///From /obj/item/fish/process: (seconds_per_tick) #define COMSIG_FISH_LIFE "fish_life" ///From /datum/fish_trait/eat_fish: (predator) diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm index 387d8b47d1b..7d4b5e0d547 100644 --- a/code/__DEFINES/fish.dm +++ b/code/__DEFINES/fish.dm @@ -98,10 +98,19 @@ #define AQUARIUM_ANIMATION_FISH_SWIM "fish" #define AQUARIUM_ANIMATION_FISH_DEAD "dead" -#define AQUARIUM_PROPERTIES_PX_MIN "px_min" -#define AQUARIUM_PROPERTIES_PX_MAX "px_max" -#define AQUARIUM_PROPERTIES_PY_MIN "py_min" -#define AQUARIUM_PROPERTIES_PY_MAX "py_max" +//standard layer defines for aquariums + +///The distance that should separate each layer of the aquarium +#define AQUARIUM_LAYER_STEP 0.01 +/// Aquarium content layer offsets +#define AQUARIUM_MIN_OFFSET 0.02 +#define AQUARIUM_MAX_OFFSET 1 +/// The layer of the glass overlay +#define AQUARIUM_GLASS_LAYER 0.02 +/// The layer of the aquarium pane borders +#define AQUARIUM_BORDERS_LAYER AQUARIUM_MAX_OFFSET + AQUARIUM_LAYER_STEP +/// Layer for stuff rendered below the glass overlay +#define AQUARIUM_BELOW_GLASS_LAYER 0.01 #define AQUARIUM_LAYER_MODE_BOTTOM "bottom" #define AQUARIUM_LAYER_MODE_TOP "top" @@ -141,6 +150,16 @@ ///Used to calculate how many bites a fish can take and therefore the amount of reagents it has. #define FISH_WEIGHT_BITE_DIVISOR (FISH_GRIND_RESULTS_WEIGHT_DIVISOR * FISH_WEIGHT_GRIND_TO_BITE_MULT) +///Set of operations that calculate the slowdown of fish based on weight +#define GET_FISH_SLOWDOWN(weighty) round(((weighty/FISH_WEIGHT_SLOWDOWN_DIVISOR)**FISH_WEIGHT_SLOWDOWN_EXPONENT)-1.3, 0.1) + +/** + * Gets a "rank" for fish weight to determine the force of the fish (or fish tank) + * basically, a gross estimate based on how weight generaly scales up (250, 500, 1000, 2000, 4000 etc...) + * for most fish + */ +#define GET_FISH_WEIGHT_RANK(weighty) max(round(1 + log(2, max(weighty/FISH_WEIGHT_FORCE_DIVISOR, 1)), 1), 1) + ///The breeding timeout for newly instantiated fish is multiplied by this. #define NEW_FISH_BREEDING_TIMEOUT_MULT 2 ///The last feeding timestamp of newly instantiated fish is multiplied by this: ergo, they spawn 50% hungry. @@ -158,6 +177,12 @@ #define FISH_FLAG_EXPERIMENT_SCANNABLE (1<<3) ///It lets us know that fish/update_size_and_weight() is currently running. #define FISH_FLAG_UPDATING_SIZE_AND_WEIGHT (1<<4) +///Flag added when the population of this fish type exceeeds the stable population inside the aquarium +#define FISH_FLAG_OVERPOPULATED (1<<5) +///Flag added when in an aquarium which temperature is within its safe limits +#define FISH_FLAG_SAFE_TEMPERATURE (1<<6) +///Flag added when in an aquarium with the right fluid type. +#define FISH_FLAG_SAFE_FLUID (1<<7) #define MIN_AQUARIUM_TEMP T0C diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 26baf699409..b7919353f1a 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -241,8 +241,6 @@ GLOBAL_LIST_INIT(turfs_pass_meteor, typecacheof(list( #define isstructure(A) (istype(A, /obj/structure)) -#define isaquarium(A) (istype(A, /obj/structure/aquarium)) - #define ismachinery(A) (istype(A, /obj/machinery)) #define istramwall(A) (istype(A, /obj/structure/tram)) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 64e5031cd66..94064352b84 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -731,6 +731,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///Mobs won't slip on a wet turf while it has this trait #define TRAIT_TURF_IGNORE_SLIPPERY "turf_ignore_slippery" +///failsafe for whether an item with the beauty element is influencing the beauty of the area of not. +#define TRAIT_BEAUTY_APPLIED "beauty_applied" + /// Mobs with this trait can't send the mining shuttle console when used outside the station itself #define TRAIT_FORBID_MINING_SHUTTLE_CONSOLE_OUTSIDE_STATION "forbid_mining_shuttle_console_outside_station" @@ -799,8 +802,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_ROD_ATTRACT_SHINY_LOVERS "rod_attract_shiny_lovers" /// This rod can be used to fish on lava #define TRAIT_ROD_LAVA_USABLE "rod_lava_usable" -/// Stuff that can go inside fish cases -#define TRAIT_FISH_CASE_COMPATIBILE "fish_case_compatibile" +/// Stuff that can go inside fish cases and aquariums +#define TRAIT_AQUARIUM_CONTENT "aquarium_content" /// If the item can be used as a bit. #define TRAIT_FISHING_BAIT "fishing_bait" /// This bait will kill any fish that doesn't have it on its favorite_bait list @@ -818,6 +821,19 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai * instead, it'll allow the fishing dud to be there unless there's at least one fish that likes the bait */ #define TRAIT_BAIT_ALLOW_FISHING_DUD "bait_dont_affect_fishing_dud" +/** + * This location has the aquarium component. Not much different than a GetComponent() + * disguised as an 'is_x' macro, but I don't have to hide anything here. + * I just don't want a confusing 'is_aquarium(A)' macro which people think it's interchangable with + * an 'istype(A, /obj/structure/aquarium)' when it's the component what truly matters. + */ +#define TRAIT_IS_AQUARIUM "is_aquarium" +/// A location (probably aquarium) that amplifies the zaps of electricity-generating fish. +#define TRAIT_BIOELECTRIC_GENERATOR "bioelectric_generator" +/// A location (likely aquarium) that doesn't allow fish to growth and reproduce +#define TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH "stop_fish_reproduction_and_growth" +/// This is an aquarium with an open panel +#define TRAIT_AQUARIUM_PANEL_OPEN "aquarium_panel_open" /// Plants that were mutated as a result of passive instability, not a mutation threshold. #define TRAIT_PLANT_WILDMUTATE "wildmutation" /// If you hit an APC with exposed internals with this item it will try to shock you diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index 12d1c2e3b90..4deb3925141 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -309,3 +309,6 @@ /// Trait from an engraving #define ENGRAVED_TRAIT "engraved" + +/// From the aquarium component +#define AQUARIUM_TRAIT "aquarium" diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 9772ebff8d1..2bd9309406a 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -81,7 +81,7 @@ #endif // REFERENCE_TRACKING_STANDARD // If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between -// #define UNIT_TESTS +//#define UNIT_TESTS // If this is uncommented, will attempt to load and initialize prof.dll/libprof.so by default. // Even if it's not defined, you can pass "tracy" via -params in order to try to load it. diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 0f5f47b242a..689750ff9ce 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -571,6 +571,9 @@ DEFINE_BITFIELD(fish_flags, list( "FISH_DO_FLOP_ANIM" = FISH_DO_FLOP_ANIM, "FISH_FLAG_PETTED" = FISH_FLAG_PETTED, "FISH_FLAG_EXPERIMENT_SCANNABLE" = FISH_FLAG_EXPERIMENT_SCANNABLE, + "FISH_FLAG_UPDATING_SIZE_AND_WEIGHT" = FISH_FLAG_OVERPOPULATED, + "FISH_FLAG_SAFE_TEMPERATURE" = FISH_FLAG_SAFE_TEMPERATURE, + "FISH_FLAG_SAFE_FLUID" = FISH_FLAG_SAFE_FLUID, )) DEFINE_BITFIELD(bot_mode_flags, list( diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 9f71718a5f2..cd68ebac4ac 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -9,6 +9,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( /atom = list( "TRAIT_AI_PAUSED" = TRAIT_AI_PAUSED, "TRAIT_BANNED_FROM_CARGO_SHUTTLE" = TRAIT_BANNED_FROM_CARGO_SHUTTLE, + "TRAIT_BEAUTY_APPLIED" = TRAIT_BEAUTY_APPLIED, "TRAIT_BEING_SHOCKED" = TRAIT_BEING_SHOCKED, "TRAIT_CATCH_AND_RELEASE" = TRAIT_CATCH_AND_RELEASE, "TRAIT_COMMISSIONED" = TRAIT_COMMISSIONED, @@ -35,8 +36,10 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /atom/movable = list( "TRAIT_ACTIVE_STORAGE" = TRAIT_ACTIVE_STORAGE, + "TRAIT_AQUARIUM_PANEL_OPEN" = TRAIT_AQUARIUM_PANEL_OPEN, "TRAIT_AREA_SENSITIVE" = TRAIT_AREA_SENSITIVE, "TRAIT_ASHSTORM_IMMUNE" = TRAIT_ASHSTORM_IMMUNE, + "TRAIT_BIOELECTRIC_GENERATOR" = TRAIT_BIOELECTRIC_GENERATOR, "TRAIT_BLOCKING_EXPLOSIVES" = TRAIT_BLOCKING_EXPLOSIVES, "TRAIT_BOULDER_BREAKER" = TRAIT_BOULDER_BREAKER, "TRAIT_CASTABLE_LOC" = TRAIT_CASTABLE_LOC, @@ -44,13 +47,13 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_COMBAT_MODE_SKIP_INTERACTION" = TRAIT_COMBAT_MODE_SKIP_INTERACTION, "TRAIT_DEL_ON_SPACE_DUMP" = TRAIT_DEL_ON_SPACE_DUMP, "TRAIT_VALID_DNA_INFUSION" = TRAIT_VALID_DNA_INFUSION, - "TRAIT_FISH_CASE_COMPATIBILE" = TRAIT_FISH_CASE_COMPATIBILE, "TRAIT_FROZEN" = TRAIT_FROZEN, "TRAIT_HAS_LABEL" = TRAIT_HAS_LABEL, "TRAIT_HEARING_SENSITIVE" = TRAIT_HEARING_SENSITIVE, "TRAIT_HYPERSPACED" = TRAIT_HYPERSPACED, "TRAIT_IMMERSED" = TRAIT_IMMERSED, "TRAIT_IRRADIATED" = TRAIT_IRRADIATED, + "TRAIT_IS_AQUARIUM" = TRAIT_IS_AQUARIUM, "TRAIT_LAVA_IMMUNE" = TRAIT_LAVA_IMMUNE, "TRAIT_MOVE_FLOATING" = TRAIT_MOVE_FLOATING, "TRAIT_MOVE_FLYING" = TRAIT_MOVE_FLYING, @@ -72,6 +75,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SECLUDED_LOCATION" = TRAIT_SECLUDED_LOCATION, "TRAIT_SNOWSTORM_IMMUNE" = TRAIT_SNOWSTORM_IMMUNE, "TRAIT_SPELLS_TRANSFER_TO_LOC" = TRAIT_SPELLS_TRANSFER_TO_LOC, + "TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH" = TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, "TRAIT_TELEKINESIS_CONTROLLED" = TRAIT_TELEKINESIS_CONTROLLED, "TRAIT_UNDERFLOOR" = TRAIT_UNDERFLOOR, "TRAIT_UNIQUE_IMMERSE" = TRAIT_UNIQUE_IMMERSE, @@ -576,6 +580,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( ), /obj/item = list( "TRAIT_APC_SHOCKING" = TRAIT_APC_SHOCKING, + "TRAIT_AQUARIUM_CONTENT" = TRAIT_AQUARIUM_CONTENT, "TRAIT_BAIT_ALLOW_FISHING_DUD" = TRAIT_BAIT_ALLOW_FISHING_DUD, "TRAIT_BAIT_UNCONSUMABLE" = TRAIT_BAIT_UNCONSUMABLE, "TRAIT_BAKEABLE" = TRAIT_BAKEABLE, diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index f78e2af6be1..85def333f4c 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -17,15 +17,6 @@ */ var/dupe_mode = COMPONENT_DUPE_HIGHLANDER - /** - * The type to check for duplication - * - * `null` means exact match on `type` (default) - * - * Any other type means that and all subtypes - */ - var/dupe_type - /// The datum this components belongs to var/datum/parent @@ -57,7 +48,7 @@ qdel(src, TRUE, TRUE) return - _JoinParent(parent) + _JoinParent() /** * Called during component creation with the same arguments as in new excluding parent. @@ -217,7 +208,7 @@ * * Use this to do any special cleanup you might need to do before being deregged from an object */ -/datum/component/proc/PreTransfer() +/datum/component/proc/PreTransfer(datum/new_parent) return /** @@ -328,7 +319,6 @@ CRASH("[component_type] attempted instantiation!") var/dupe_mode = initial(component_type.dupe_mode) - var/dupe_type = initial(component_type.dupe_type) var/uses_sources = (dupe_mode == COMPONENT_DUPE_SOURCES) if(uses_sources && !source) CRASH("Attempted to add a sourced component of type '[component_type]' to '[type]' without a source!") @@ -339,10 +329,7 @@ raw_args[1] = src if(dupe_mode != COMPONENT_DUPE_ALLOWED && dupe_mode != COMPONENT_DUPE_SELECTIVE && dupe_mode != COMPONENT_DUPE_SOURCES) - if(!dupe_type) - old_component = GetExactComponent(component_type) - else - old_component = GetComponent(dupe_type) + old_component = GetComponent(component_type) if(old_component) switch(dupe_mode) @@ -434,11 +421,11 @@ * Removes the component from parent, ends up with a null parent * Used as a helper proc by the component transfer proc, does not clean up the component like Destroy does */ -/datum/component/proc/ClearFromParent() +/datum/component/proc/ClearFromParent(datum/new_parent) if(!parent) return var/datum/old_parent = parent - PreTransfer() + PreTransfer(new_parent) _RemoveFromParent() parent = null SEND_SIGNAL(old_parent, COMSIG_COMPONENT_REMOVING, src) @@ -455,16 +442,17 @@ if(!target || target.parent == src) return if(target.parent) - target.ClearFromParent() - target.parent = src - var/result = target.PostTransfer() + target.ClearFromParent(src) + var/result = target.PostTransfer(src) switch(result) if(COMPONENT_INCOMPATIBLE) var/c_type = target.type qdel(target) CRASH("Incompatible [c_type] transfer attempt to a [type]!") - if(target == AddComponent(target)) + AddComponent(target) + if(!QDELETED(target)) + target.parent = src target._JoinParent() /** diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm new file mode 100644 index 00000000000..3a07c2f1481 --- /dev/null +++ b/code/datums/components/aquarium.dm @@ -0,0 +1,564 @@ +///Defines that clamp the beauty of the aquarium, to prevent it from making most areas great or horrid all by itself. +#define MIN_AQUARIUM_BEAUTY -3500 +#define MAX_AQUARIUM_BEAUTY 6000 + +/** + * The component that manages the aquariums UI, fluid, temperature, the current fish inside the parent object, as well as beauty, + * and a few other common aquarium features. + */ +/datum/component/aquarium + dupe_mode = COMPONENT_DUPE_UNIQUE + can_transfer = TRUE + /// list of fishes inside the parent object, sorted by type - does not include things with aquarium visuals that are not fish + var/list/tracked_fish_by_type + + ///The current type of fluid of the aquarium + var/fluid_type = AQUARIUM_FLUID_FRESHWATER + ///The current temperature of the fluid of the aquarium + var/fluid_temp = DEFAULT_AQUARIUM_TEMP + + ///A lazy list of key instances and assoc vals representing how much beauty they contribute to the aquarium + var/list/beauty_by_content + + ///The default beauty of the aquarium when empty. + var/default_beauty + + ///A list of layers that are currently being used for the various overlays of the aquarium (from aquarium_content comp) + var/list/used_layers = list() + + ///The minimum pixel x of the area where vis overlays should be displayed + var/aquarium_zone_min_px + ///The maximum pixel x of the area where vis overlays should be displayed + var/aquarium_zone_max_px + ///The minimum pixel y of the area where vis overlays should be displayed + var/aquarium_zone_min_py + ///The maximum pixel y of the area where vis overlays should be displayed + var/aquarium_zone_max_py + + ///While the feed (reagent) storage is not empty, this is the interval which the fish are fed. + var/feeding_interval = 3 MINUTES + ///The last time fishes were fed by the acquarium itsef. + var/last_feeding + + ///The minimum fluid temperature that can be reached by this aquarium + var/min_fluid_temp = MIN_AQUARIUM_TEMP + ///The maximum fluid temperature that can be reached by this aquarium + var/max_fluid_temp = MAX_AQUARIUM_TEMP + + ///static list of available fluid types. + var/static/list/fluid_types = list( + AQUARIUM_FLUID_SALTWATER, + AQUARIUM_FLUID_FRESHWATER, + AQUARIUM_FLUID_SULPHWATEVER, + AQUARIUM_FLUID_AIR, + ) + + ///The size of the reagents holder which will store fish feed. + var/reagents_size + +/datum/component/aquarium/Initialize( + min_px, + max_px, + min_py, + max_py, + default_beauty = 0, + reagents_size = 6, + min_fluid_temp = MIN_AQUARIUM_TEMP, + max_fluid_temp = MAX_AQUARIUM_TEMP, +) + + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + src.default_beauty = default_beauty + src.reagents_size = reagents_size + + aquarium_zone_min_px = min_px + aquarium_zone_max_px = max_px + aquarium_zone_min_py = min_py + aquarium_zone_max_py = max_py + + src.min_fluid_temp = min_fluid_temp + src.max_fluid_temp = max_fluid_temp + fluid_temp = clamp(fluid_temp, min_fluid_temp, max_fluid_temp) + +/datum/component/aquarium/RegisterWithParent() + if(default_beauty) + update_aquarium_beauty(0) + + RegisterSignals(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON), PROC_REF(on_entered)) + RegisterSignal(parent, COMSIG_ATOM_EXITED, PROC_REF(on_exited)) + + RegisterSignal(parent, COMSIG_AQUARIUM_GET_REPRODUCTION_CANDIDATES, PROC_REF(get_candidates)) + RegisterSignal(parent, COMSIG_AQUARIUM_CHECK_EVOLUTION_CONDITIONS, PROC_REF(check_evolution)) + + RegisterSignal(parent, COMSIG_AQUARIUM_SET_VISUAL, PROC_REF(set_visual)) + RegisterSignal(parent, COMSIG_AQUARIUM_REMOVE_VISUAL, PROC_REF(remove_visual)) + + var/atom/movable/movable = parent + + ADD_KEEP_TOGETHER(movable, AQUARIUM_TRAIT) //render the fish on the same layer of the aquarium. + + if(reagents_size > 0) + RegisterSignal(movable.reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed)) + if(!movable.reagents) + movable.create_reagents(reagents_size, SEALED_CONTAINER) + else if(movable.reagents.total_volume) + start_autofeed(movable.reagents) + RegisterSignal(movable, COMSIG_PLUNGER_ACT, PROC_REF(on_plunger_act)) + + RegisterSignal(movable, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_interaction)) + RegisterSignal(movable, COMSIG_CLICK_ALT, PROC_REF(on_click_alt)) + RegisterSignal(movable, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) + + if(isitem(movable)) + RegisterSignal(movable, COMSIG_ITEM_ATTACK_SELF, PROC_REF(interact)) + RegisterSignals(movable, list(COMSIG_ATOM_ATTACK_ROBOT_SECONDARY, COMSIG_ATOM_ATTACK_HAND_SECONDARY), PROC_REF(on_secondary_attack_hand)) + else + RegisterSignal(movable, COMSIG_ATOM_UI_INTERACT, PROC_REF(interact)) + + movable.AddElement(/datum/element/relay_attackers) + + for(var/atom/movable/content as anything in movable.contents) + if(content.flags_1 & INITIALIZED_1) + on_entered(movable, content) + + ADD_TRAIT(movable, TRAIT_IS_AQUARIUM, AQUARIUM_TRAIT) + +/datum/component/aquarium/UnregisterFromParent() + var/atom/movable/movable = parent + UnregisterSignal(movable, list( + COMSIG_ATOM_ENTERED, + COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, + COMSIG_ATOM_EXITED, + COMSIG_AQUARIUM_GET_REPRODUCTION_CANDIDATES, + COMSIG_AQUARIUM_CHECK_EVOLUTION_CONDITIONS, + COMSIG_AQUARIUM_SET_VISUAL, + COMSIG_AQUARIUM_REMOVE_VISUAL, + COMSIG_PLUNGER_ACT, + COMSIG_ATOM_ITEM_INTERACTION, + COMSIG_CLICK_ALT, + COMSIG_ATOM_EXAMINE, + COMSIG_ITEM_ATTACK_SELF, + COMSIG_ATOM_ATTACK_ROBOT_SECONDARY, + COMSIG_ATOM_ATTACK_HAND_SECONDARY, + COMSIG_ATOM_UI_INTERACT, + )) + if(movable.reagents) + UnregisterSignal(movable, COMSIG_REAGENTS_NEW_REAGENT) + STOP_PROCESSING(SSobj, src) + beauty_by_content = null + tracked_fish_by_type = null + movable.remove_traits(list(TRAIT_IS_AQUARIUM, TRAIT_AQUARIUM_PANEL_OPEN, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH), AQUARIUM_TRAIT) + REMOVE_KEEP_TOGETHER(movable, AQUARIUM_TRAIT) + +/datum/component/aquarium/PreTransfer(atom/movable/new_parent) + if(!istype(new_parent)) + return + if(HAS_TRAIT(parent, TRAIT_AQUARIUM_PANEL_OPEN)) + ADD_TRAIT(new_parent, TRAIT_AQUARIUM_PANEL_OPEN, AQUARIUM_TRAIT) + if(HAS_TRAIT_FROM(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)) + ADD_TRAIT(new_parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) + var/atom/movable/movable = parent + for(var/atom/movable/moving as anything in movable.contents) + if(HAS_TRAIT(moving, TRAIT_AQUARIUM_CONTENT)) + moving.forceMove(new_parent) + if(reagents_size) + if(!new_parent.reagents) + new_parent.create_reagents(reagents_size, SEALED_CONTAINER) + movable.reagents.trans_to(new_parent, movable.reagents.total_volume) + +/datum/component/aquarium/PostTransfer(datum/new_parent) + if(!ismovable(new_parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/aquarium/InheritComponent(datum/component/aquarium/new_comp, i_am_original) + fluid_temp = clamp(new_comp.fluid_temp, min_fluid_temp, max_fluid_temp) + set_fluid_type(new_comp.fluid_type) + feeding_interval = new_comp.feeding_interval + last_feeding = new_comp.last_feeding + var/atom/movable/movable = parent + movable.update_appearance() + +/datum/component/aquarium/proc/on_click_alt(atom/movable/source, mob/living/user) + SIGNAL_HANDLER + var/closing = HAS_TRAIT(parent, TRAIT_AQUARIUM_PANEL_OPEN) + if(closing) + REMOVE_TRAIT(parent, TRAIT_AQUARIUM_PANEL_OPEN, AQUARIUM_TRAIT) + source.reagents.flags &= ~(TRANSPARENT|REFILLABLE) + SStgui.close_uis(src) + else + ADD_TRAIT(parent, TRAIT_AQUARIUM_PANEL_OPEN, AQUARIUM_TRAIT) + source.reagents.flags |= TRANSPARENT|REFILLABLE + + source.balloon_alert(user, "panel [closing ? "closed" : "open"]") + source.update_appearance() + return CLICK_ACTION_SUCCESS + +///This proc handles feeding the aquarium and inserting aquarium content. +/datum/component/aquarium/proc/on_item_interaction(atom/movable/source, mob/living/user, obj/item/item, modifiers) + SIGNAL_HANDLER + + if(istype(item, /obj/item/reagent_containers/cup/fish_feed)) + if(source.reagents && HAS_TRAIT(source, TRAIT_AQUARIUM_PANEL_OPEN)) + return //don't block, we'll be transferring reagents to the feed storage. + if(!item.reagents.total_volume) + source.balloon_alert(user, "[item] is empty!") + return ITEM_INTERACT_BLOCKING + var/list/fishes = get_fishes() + if(!length(fishes)) + source.balloon_alert(user, "no fish to feed!") + return ITEM_INTERACT_BLOCKING + for(var/obj/item/fish/fish as anything in fishes) + fish.feed(item.reagents) + source.balloon_alert(user, "fed the fish") + return ITEM_INTERACT_SUCCESS + + if(!HAS_TRAIT(item, TRAIT_AQUARIUM_CONTENT)) + return //proceed with normal interactions + + var/broken = source.get_integrity_percentage() <= source.integrity_failure + if(!can_insert(source, item, user)) + return ITEM_INTERACT_BLOCKING + if(broken) + source.balloon_alert(user, "aquarium is broken!") + return ITEM_INTERACT_BLOCKING + if(!user.transferItemToLoc(item, source)) + user.balloon_alert(user, "stuck to your hand!") + return ITEM_INTERACT_BLOCKING + source.balloon_alert(user, "added to aquarium") + source.update_appearance() + return ITEM_INTERACT_SUCCESS + +///Called when the feed storage is no longer empty. +/datum/component/aquarium/proc/start_autofeed(atom/movable/source, new_reagent, amount, reagtemp, data, no_react) + SIGNAL_HANDLER + START_PROCESSING(SSobj, src) + UnregisterSignal(source.reagents, COMSIG_REAGENTS_NEW_REAGENT) + +///Feed the fish at defined intervals until the feed storage is empty. +/datum/component/aquarium/process(seconds_per_tick) + var/atom/movable/movable = parent + if(!movable.reagents?.total_volume) + if(movable.reagents) + RegisterSignal(movable.reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed)) + return PROCESS_KILL + if(world.time < last_feeding + feeding_interval) + return + last_feeding = world.time + var/list/fishes = get_fishes() + for(var/obj/item/fish/fish as anything in fishes) + fish.feed(movable.reagents) + +/datum/component/aquarium/proc/on_plunger_act(atom/movable/source, obj/item/plunger/plunger, mob/living/user, reinforced) + SIGNAL_HANDLER + if(!HAS_TRAIT(source, TRAIT_AQUARIUM_PANEL_OPEN)) + return + INVOKE_ASYNC(src, PROC_REF(do_plunging), source, user) + return COMPONENT_NO_AFTERATTACK + +/datum/component/aquarium/proc/do_plunging(atom/movable/source, mob/living/user) + user.balloon_alert_to_viewers("plunging...") + if(do_after(user, 3 SECONDS, target = source)) + user.balloon_alert_to_viewers("finished plunging") + source.reagents.expose(get_turf(source), TOUCH) //splash on the floor + source.reagents.clear_reagents() + +/datum/component/aquarium/proc/on_examine(atom/movable/source, mob/user, list/examine_list) + SIGNAL_HANDLER + var/panel_open = HAS_TRAIT(source, TRAIT_AQUARIUM_PANEL_OPEN) + examine_list += span_notice("Alt-click to [panel_open ? "close" : "open"] the control and feed panel.") + if(panel_open && source.reagents.total_volume) + examine_list += span_notice("You can use a plunger to empty the feed storage.") + +///Check if an item can be inserted into the aquarium +/datum/component/aquarium/proc/can_insert(atom/movable/source, obj/item/item, mob/living/user) + var/return_value = SEND_SIGNAL(src, COMSIG_AQUARIUM_CAN_INSERT, item, user) + if(return_value & COMSIG_CANNOT_INSERT_IN_AQUARIUM) + return FALSE + if(return_value & COMSIG_CAN_INSERT_IN_AQUARIUM) + return TRUE + + if(HAS_TRAIT(item, TRAIT_UNIQUE_AQUARIUM_CONTENT)) + for(var/atom/movable/content as anything in source) + if(content == item) + continue + if(content.type == item.type) + source.balloon_alert(user, "cannot add to aquarium!") + return FALSE + return TRUE + +///Handles aquarium content insertion +/datum/component/aquarium/proc/on_entered(atom/movable/source, atom/movable/entered) + SIGNAL_HANDLER + get_content_beauty(entered) + if(!isfish(entered)) + return + var/obj/item/fish/fish = entered + LAZYADDASSOCLIST(tracked_fish_by_type, entered.type, entered) + if(fish.stable_population < length(tracked_fish_by_type[fish.type])) + for(var/obj/item/fish/anyfin as anything in tracked_fish_by_type[entered.type]) + anyfin.fish_flags |= FISH_FLAG_OVERPOPULATED + check_fluid_and_temperature(fish) + RegisterSignal(fish, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_fish_status_changed)) + +///update the beauty_by_content of a 'beauty_by_content' key and then recalculate the beauty. +/datum/component/aquarium/proc/get_content_beauty(atom/movable/content) + var/list/beauty_holder = list() + SEND_SIGNAL(content, COMSIG_MOVABLE_GET_AQUARIUM_BEAUTY, beauty_holder) + var/beauty = beauty_holder[1] + if(!beauty) + return + var/old_beauty = default_beauty + for(var/key in beauty_by_content) + old_beauty += beauty_by_content[key] + LAZYSET(beauty_by_content, content, beauty) + update_aquarium_beauty(old_beauty) + +///Handles aquarium content removal. +/datum/component/aquarium/proc/on_exited(atom/movable/source, atom/movable/gone) + SIGNAL_HANDLER + var/beauty = beauty_by_content?[gone] + if(beauty) + var/old_beauty = default_beauty + for(var/key in beauty_by_content) + old_beauty += beauty_by_content[key] + LAZYREMOVE(beauty_by_content, gone) + update_aquarium_beauty(old_beauty) + if(!isfish(gone)) + return + var/obj/item/fish/fish = gone + if(fish.stable_population == length(tracked_fish_by_type[fish.type])) + for(var/obj/item/fish/anyfin as anything in tracked_fish_by_type[fish.type]) + anyfin.fish_flags &= ~FISH_FLAG_OVERPOPULATED + LAZYREMOVEASSOC(tracked_fish_by_type, fish.type, fish) + fish.fish_flags &= ~(FISH_FLAG_SAFE_TEMPERATURE|FISH_FLAG_SAFE_FLUID) + UnregisterSignal(gone, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_fish_status_changed)) + +///Return a list of fish which our fishie can reproduce with (including itself if self-reproducing) +/datum/component/aquarium/proc/get_candidates(atom/movable/source, obj/item/fish/fish, list/candidates) + SIGNAL_HANDLER + var/list/types_to_mate_with = tracked_fish_by_type + if(!HAS_TRAIT(fish, TRAIT_FISH_CROSSBREEDER)) + var/list/types_to_check = list(fish.type) + if(fish.compatible_types) + types_to_check |= fish.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 + candidates += type_fishes + +///Check if an offspring of two fish (or one if self-reproducing) can evolve. +/datum/component/aquarium/proc/check_evolution(atom/movable/source, obj/item/fish/fish, obj/item/fish/mate, datum/fish_evolution/evolution) + SIGNAL_HANDLER + //chances are halved if only one parent has this evolution. + var/real_probability = (mate && (evolution.type in mate.evolution_types)) ? evolution.probability : evolution.probability * 0.5 + if(HAS_TRAIT(fish, TRAIT_FISH_MUTAGENIC) || (mate && HAS_TRAIT(mate, TRAIT_FISH_MUTAGENIC))) + real_probability *= 3 + if(!prob(real_probability)) + return NONE + if(!ISINRANGE(fluid_temp, evolution.required_temperature_min, evolution.required_temperature_max)) + return NONE + return COMPONENT_ALLOW_EVOLUTION + +/** + * Toggles a couple flags that determine if the fish is in safe waters so that we won't have to use signals or + * access this comp in multiple places just to confirm that. + */ +/datum/component/aquarium/proc/check_fluid_and_temperature(obj/item/fish/fish) + if(compatible_fluid_type(fish.required_fluid_type, fluid_type) || (fluid_type == AQUARIUM_FLUID_AIR && HAS_TRAIT(fish, TRAIT_FISH_AMPHIBIOUS))) + fish.fish_flags |= FISH_FLAG_SAFE_FLUID + else + fish.fish_flags &= ~FISH_FLAG_SAFE_FLUID + if(ISINRANGE(fluid_temp, fish.required_temperature_min, fish.required_temperature_max)) + fish.fish_flags |= FISH_FLAG_SAFE_TEMPERATURE + else + fish.fish_flags &= ~FISH_FLAG_SAFE_TEMPERATURE + +///Fish beauty changes when they're dead, so we need to update the beauty of the aquarium too. +/datum/component/aquarium/proc/on_fish_status_changed(obj/item/fish/fish) + get_content_beauty(fish) + +/datum/component/aquarium/proc/update_aquarium_beauty(old_beauty) + if(QDELETED(parent)) + return + old_beauty = clamp(old_beauty, MIN_AQUARIUM_BEAUTY, MAX_AQUARIUM_BEAUTY) + var/new_beauty = 0 + for(var/key in beauty_by_content) + new_beauty += beauty_by_content[key] + new_beauty = clamp(new_beauty, MIN_AQUARIUM_BEAUTY, MAX_AQUARIUM_BEAUTY) + if(new_beauty == old_beauty) + return + if(old_beauty) + parent.RemoveElement(/datum/element/beauty, old_beauty) + if(new_beauty) + parent.AddElement(/datum/element/beauty, new_beauty) + +///Remove a visual overlay from an aquarium_content comp +/datum/component/aquarium/proc/remove_visual(atom/movable/source, obj/effect/aquarium/visual) + SIGNAL_HANDLER + source.vis_contents -= visual + used_layers -= visual.layer + +///set values for a visual overlay for an aquarium_content comp +/datum/component/aquarium/proc/set_visual(atom/movable/source, obj/effect/aquarium/visual) + SIGNAL_HANDLER + used_layers -= visual.layer + visual.layer = request_layer(visual.layer_mode) + visual.aquarium_zone_min_px = aquarium_zone_min_px + visual.aquarium_zone_max_px = aquarium_zone_max_px + visual.aquarium_zone_min_py = aquarium_zone_min_py + visual.aquarium_zone_max_py = aquarium_zone_max_py + visual.fluid_type = fluid_type + +/datum/component/aquarium/proc/request_layer(layer_type) + var/atom/movable/movable = parent + switch(layer_type) + if(AQUARIUM_LAYER_MODE_BEHIND_GLASS) + return movable.layer + AQUARIUM_BELOW_GLASS_LAYER + if(AQUARIUM_LAYER_MODE_BOTTOM) + return movable.layer + AQUARIUM_MIN_OFFSET + if(AQUARIUM_LAYER_MODE_TOP) + return movable.layer + AQUARIUM_MAX_OFFSET + if(AQUARIUM_LAYER_MODE_AUTO) + var/chosen_layer = AQUARIUM_MIN_OFFSET + AQUARIUM_LAYER_STEP + while((chosen_layer in used_layers) && (chosen_layer <= AQUARIUM_MAX_OFFSET)) + chosen_layer += AQUARIUM_LAYER_STEP + used_layers += chosen_layer + return movable.layer + chosen_layer + +/datum/component/aquarium/proc/get_fishes() + var/list/fishes = list() + for(var/key in tracked_fish_by_type) + fishes += tracked_fish_by_type[key] + return fishes + +/datum/component/aquarium/proc/interact(atom/movable/source, mob/user) + SIGNAL_HANDLER + + if(HAS_TRAIT(source, TRAIT_AQUARIUM_PANEL_OPEN)) + INVOKE_ASYNC(src, PROC_REF(ui_interact), user) + else if(!isitem(source)) + INVOKE_ASYNC(src, PROC_REF(admire), user) + +/datum/component/aquarium/proc/on_secondary_attack_hand(obj/item/source, mob/living/user) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(admire), user) + return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/component/aquarium/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + var/atom/movable/movable = parent + ui = new(user, src, "Aquarium", movable.name) + ui.open() + +/datum/component/aquarium/ui_data(atom/movable/source, mob/user) + . = ..() + .["fluidType"] = fluid_type + .["temperature"] = fluid_temp + .["allowBreeding"] = HAS_TRAIT_FROM(source, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) + .["fishData"] = list() + .["feedingInterval"] = feeding_interval / (1 MINUTES) + .["propData"] = list() + for(var/atom/movable/item as anything in source.contents) + if(isfish(item)) + var/obj/item/fish/fish = item + .["fishData"] += list(list( + "fish_ref" = REF(fish), + "fish_name" = uppertext(fish.name), + "fish_happiness" = fish.get_happiness_value(), + "fish_icon" = fish::icon, + "fish_icon_state" = fish::icon_state, + "fish_health" = fish.health, + )) + continue + .["propData"] += list(list( + "prop_ref" = REF(item), + "prop_name" = item.name, + "prop_icon" = item::icon, + "prop_icon_state" = item::icon_state, + )) + +/datum/component/aquarium/ui_static_data(mob/user) + . = ..() + //I guess these should depend on the fluid so lava critters can get high or stuff below water freezing point but let's keep it simple for now. + .["minTemperature"] = min_fluid_temp + .["maxTemperature"] = max_fluid_temp + .["fluidTypes"] = fluid_types + .["heartIcon"] = 'icons/effects/effects.dmi' + +/datum/component/aquarium/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + var/mob/user = usr + var/atom/movable/movable = parent + switch(action) + if("temperature") + var/temperature = params["temperature"] + if(isnum(temperature) && temperature != fluid_temp) + fluid_temp = clamp(temperature, min_fluid_temp, max_fluid_temp) + for(var/obj/item/fish/fish as anything in get_fishes()) + check_fluid_and_temperature(fish) + . = TRUE + if("fluid") + if(params["fluid"] != fluid_type && (params["fluid"] in fluid_types)) + set_fluid_type(params["fluid"]) + . = TRUE + if("allow_breeding") + if(HAS_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH)) + REMOVE_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) + else + ADD_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) + . = TRUE + if("feeding_interval") + feeding_interval = params["feeding_interval"] MINUTES + . = TRUE + if("pet_fish") + var/obj/item/fish/fish = locate(params["fish_reference"]) in movable.contents + fish?.pet_fish(user) + if("remove_item") + var/atom/movable/item = locate(params["item_reference"]) in movable.contents + item?.forceMove(movable.drop_location()) + to_chat(user, span_notice("You take out [item] from [movable].")) + if("rename_fish") + var/new_name = sanitize_name(params["chosen_name"]) + var/atom/movable/fish = locate(params["fish_reference"]) in movable.contents + if(!fish || !new_name || new_name == fish.name) + return + fish.AddComponent(/datum/component/rename, new_name, fish.desc) + +/datum/component/aquarium/proc/set_fluid_type(new_fluid_type) + var/atom/movable/movable = parent + fluid_type = new_fluid_type + SEND_SIGNAL(movable, COMSIG_AQUARIUM_FLUID_CHANGED, fluid_type) + for(var/obj/item/fish/fish as anything in get_fishes()) + check_fluid_and_temperature(fish) + +/datum/component/aquarium/proc/admire(atom/movable/source, mob/living/user) + source.balloon_alert(user, "admiring aquarium...") + if(!do_after(user, 5 SECONDS, target = source)) + return + var/alive_fish = 0 + var/dead_fish = 0 + for(var/obj/item/fish/fish as anything in get_fishes()) + if(fish.status == FISH_ALIVE) + alive_fish++ + else + dead_fish++ + + var/morb = HAS_MIND_TRAIT(user, TRAIT_MORBID) + //Check if there are live fish - good mood + //All fish dead - bad mood. + //No fish - nothing. + if(alive_fish > 0) + user.add_mood_event("aquarium", morb ? /datum/mood_event/morbid_aquarium_bad : /datum/mood_event/aquarium_positive) + else if(dead_fish > 0) + user.add_mood_event("aquarium", morb ? /datum/mood_event/morbid_aquarium_good : /datum/mood_event/aquarium_negative) + +#undef MIN_AQUARIUM_BEAUTY +#undef MAX_AQUARIUM_BEAUTY diff --git a/code/datums/components/aquarium_content.dm b/code/datums/components/aquarium_content.dm index 1307cb45fbb..4e6e25c86bc 100644 --- a/code/datums/components/aquarium_content.dm +++ b/code/datums/components/aquarium_content.dm @@ -1,18 +1,5 @@ -///Malus to the beauty value if the fish content is dead -#define DEAD_FISH_BEAUTY -500 -///Prevents more impressive fishes from providing a positive beauty even when dead. -#define MAX_DEAD_FISH_BEAUTY -200 -///Some fish are already so ugly, they can't get much worse when dead -#define MIN_DEAD_FISH_BEAUTY -600 - -///Defines that clamp the beauty of the aquarium, to prevent it from making most areas great or horrid all by itself. -#define MIN_AQUARIUM_BEAUTY -3500 -#define MAX_AQUARIUM_BEAUTY 6000 - /// Allows movables to be inserted/displayed in aquariums. /datum/component/aquarium_content - /// Keeps track of our current aquarium. - var/obj/structure/aquarium/current_aquarium //This is visual effect holder that will end up in aquarium's vis_contents var/obj/effect/aquarium/vc_obj @@ -32,161 +19,103 @@ /// Does this behviour need additional processing in aquarium, will be added to SSobj processing on insertion var/processing = FALSE - /// Signals of the parent that will trigger animation update - var/animation_update_signals + /// Signals for the aquarium we're in that trigger an animation update + var/list/animation_update_signals - /// The current beauty this component gives to the aquarium it's in - var/beauty - - /// The original value of the beauty this component had when initialized - var/original_beauty - -/datum/component/aquarium_content/Initialize(animation_update_signals, beauty) - if(!ismovable(parent)) +/datum/component/aquarium_content/Initialize(animation_update_signals) + if(!isitem(parent)) return COMPONENT_INCOMPATIBLE - src.animation_update_signals = animation_update_signals - src.beauty = original_beauty = beauty - if(animation_update_signals) - RegisterSignals(parent, animation_update_signals, PROC_REF(generate_animation)) + src.animation_update_signals = islist(animation_update_signals) ? animation_update_signals : list(animation_update_signals) - ADD_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, REF(src)) - RegisterSignal(parent, COMSIG_TRY_INSERTING_IN_AQUARIUM, PROC_REF(is_ready_to_insert)) + ADD_TRAIT(parent, TRAIT_AQUARIUM_CONTENT, REF(src)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(enter_aquarium)) - if(isfish(parent)) - RegisterSignal(parent, COMSIG_FISH_STATUS_CHANGED, PROC_REF(on_fish_status_changed)) - //If component is added to something already in aquarium at the time initialize it properly. var/atom/movable/movable_parent = parent - if(istype(movable_parent.loc, /obj/structure/aquarium)) + if(movable_parent.loc && HAS_TRAIT(movable_parent.loc, TRAIT_IS_AQUARIUM)) on_inserted(movable_parent.loc) -/datum/component/aquarium_content/proc/on_fish_status_changed(obj/item/fish/source) - SIGNAL_HANDLER - var/old_beauty = beauty - beauty = original_beauty - if(source.status == FISH_DEAD) - beauty = clamp(beauty + DEAD_FISH_BEAUTY, MIN_DEAD_FISH_BEAUTY, MAX_DEAD_FISH_BEAUTY) - if(current_aquarium) - change_aquarium_beauty(beauty - old_beauty) - generate_animation() - -/datum/component/aquarium_content/PreTransfer() - . = ..() - REMOVE_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, REF(src)) - /datum/component/aquarium_content/Destroy(force) - if(current_aquarium) - remove_from_aquarium() + var/atom/movable/movable = parent + if(movable.loc && HAS_TRAIT(movable.loc, TRAIT_IS_AQUARIUM)) + remove_from_aquarium(movable.loc) QDEL_NULL(vc_obj) + REMOVE_TRAIT(parent, TRAIT_AQUARIUM_CONTENT, REF(src)) return ..() /datum/component/aquarium_content/proc/enter_aquarium(datum/source, OldLoc, Dir, Forced) SIGNAL_HANDLER var/atom/movable/movable_parent = parent - if(istype(movable_parent.loc, /obj/structure/aquarium)) + if(HAS_TRAIT(movable_parent.loc, TRAIT_IS_AQUARIUM)) on_inserted(movable_parent.loc) -/datum/component/aquarium_content/proc/is_ready_to_insert(datum/source, obj/structure/aquarium/aquarium) - SIGNAL_HANDLER - if(HAS_TRAIT(parent, TRAIT_UNIQUE_AQUARIUM_CONTENT)) - for(var/atom/movable/content as anything in aquarium) - if(content == parent) - continue - if(content.type == parent.type) - return COMSIG_CANNOT_INSERT_IN_AQUARIUM - return COMSIG_CAN_INSERT_IN_AQUARIUM - -/datum/component/aquarium_content/proc/on_inserted(atom/aquarium) - current_aquarium = aquarium - RegisterSignal(current_aquarium, COMSIG_ATOM_EXITED, PROC_REF(on_removed)) - RegisterSignal(current_aquarium, COMSIG_AQUARIUM_SURFACE_CHANGED, PROC_REF(on_surface_changed)) - RegisterSignal(current_aquarium, COMSIG_AQUARIUM_FLUID_CHANGED, PROC_REF(on_fluid_changed)) +/datum/component/aquarium_content/proc/on_inserted(atom/movable/aquarium) + RegisterSignal(aquarium, COMSIG_ATOM_EXITED, PROC_REF(on_removed)) + RegisterSignal(aquarium, COMSIG_AQUARIUM_FLUID_CHANGED, PROC_REF(on_fluid_changed)) + RegisterSignals(aquarium, animation_update_signals, PROC_REF(animation_update_signal_proc)) if(processing) START_PROCESSING(SSobj, src) //If we don't have vc object yet build it if(!vc_obj) - generate_base_vc() + generate_base_vc(aquarium) //Set default position and layer set_vc_base_position() generate_animation(reset = TRUE) //Finally add it to to objects vis_contents - current_aquarium.vis_contents |= vc_obj + aquarium.vis_contents |= vc_obj - change_aquarium_beauty(beauty) - -///Modifies the beauty of the aquarium when content is added or removed, or when fishes die or live again somehow. -/datum/component/aquarium_content/proc/change_aquarium_beauty(change) - if(QDELETED(current_aquarium) || !change) - return - var/old_clamped_beauty = clamp(current_aquarium.current_beauty, MIN_AQUARIUM_BEAUTY, MAX_AQUARIUM_BEAUTY) - current_aquarium.current_beauty += change - var/new_clamped_beauty = clamp(current_aquarium.current_beauty, MIN_AQUARIUM_BEAUTY, MAX_AQUARIUM_BEAUTY) - if(new_clamped_beauty == old_clamped_beauty) - return - if(current_aquarium.current_beauty) - current_aquarium.RemoveElement(/datum/element/beauty, current_aquarium.current_beauty) - if(current_aquarium.current_beauty) - current_aquarium.AddElement(/datum/element/beauty, current_aquarium.current_beauty) - -/// Aquarium surface changed in some way, we need to recalculate base position and aninmation -/datum/component/aquarium_content/proc/on_surface_changed() +/datum/component/aquarium_content/proc/on_fluid_changed(datum/source, new_fluid_type) SIGNAL_HANDLER - set_vc_base_position() - generate_animation(reset = TRUE) //our animation start point changed, gotta redo + vc_obj.fluid_type = new_fluid_type + generate_animation() -/datum/component/aquarium_content/proc/on_fluid_changed() - SIGNAL_HANDLER +///Called when one of the signals in the 'animation_update_signals' is sent +/datum/component/aquarium_content/proc/animation_update_signal_proc(datum/source) generate_animation() ///Sends a signal to the parent to get them to update the aquarium animation of the visual object -/datum/component/aquarium_content/proc/generate_animation(reset=FALSE) - if(!current_aquarium) - return - SEND_SIGNAL(parent, COMSIG_AQUARIUM_CONTENT_DO_ANIMATION, reset ? null : current_animation, current_aquarium, vc_obj) - -/datum/component/aquarium_content/proc/remove_visual_from_aquarium() - current_aquarium.vis_contents -= vc_obj - if(vc_obj.layer) - current_aquarium.free_layer(vc_obj.layer) +/datum/component/aquarium_content/proc/generate_animation(reset = FALSE) + var/atom/movable/movable = parent + SEND_SIGNAL(movable, COMSIG_AQUARIUM_CONTENT_DO_ANIMATION, reset ? null : current_animation, vc_obj) /// Generates common visual object, propeties that don't depend on aquarium surface -/datum/component/aquarium_content/proc/generate_base_vc() +/datum/component/aquarium_content/proc/generate_base_vc(atom/movable/aquarium) vc_obj = new vc_obj.vis_flags |= VIS_INHERIT_ID | VIS_INHERIT_PLANE //plane so it shows properly in containers on inventory ui for handheld cases - SEND_SIGNAL(parent, COMSIG_AQUARIUM_CONTENT_GENERATE_APPEARANCE, vc_obj) + SEND_SIGNAL(parent, COMSIG_AQUARIUM_CONTENT_GENERATE_APPEARANCE, vc_obj, aquarium) /datum/component/aquarium_content/proc/set_vc_base_position() - SEND_SIGNAL(parent, AQUARIUM_CONTENT_RANDOMIZE_POSITION, current_aquarium, vc_obj) - if(vc_obj.layer) - current_aquarium.free_layer(vc_obj.layer) - vc_obj.layer = current_aquarium.request_layer(vc_obj.layer_mode) + var/atom/movable/movable = parent + SEND_SIGNAL(movable, COMSIG_AQUARIUM_CONTENT_RANDOMIZE_POSITION, movable.loc, vc_obj) + SEND_SIGNAL(movable.loc, COMSIG_AQUARIUM_SET_VISUAL, vc_obj) -/datum/component/aquarium_content/proc/on_removed(obj/structure/aquarium/source, atom/movable/gone, direction) +/datum/component/aquarium_content/proc/on_removed(atom/movable/aquarium, atom/movable/gone, direction) SIGNAL_HANDLER if(parent != gone) return - remove_from_aquarium() + remove_from_aquarium(aquarium) -/datum/component/aquarium_content/proc/remove_from_aquarium() - change_aquarium_beauty(-beauty) - UnregisterSignal(current_aquarium, list(COMSIG_AQUARIUM_SURFACE_CHANGED, COMSIG_AQUARIUM_FLUID_CHANGED, COMSIG_ATOM_ATTACKBY, COMSIG_ATOM_EXITED)) - remove_visual_from_aquarium() - current_aquarium = null +/datum/component/aquarium_content/proc/remove_from_aquarium(atom/movable/aquarium) + UnregisterSignal(aquarium, list(COMSIG_AQUARIUM_FLUID_CHANGED, COMSIG_ATOM_EXITED) + animation_update_signals) + SEND_SIGNAL(aquarium, COMSIG_AQUARIUM_REMOVE_VISUAL, vc_obj) -///The visual overlay of the aquarium content. It can hold a couple vars that we can modify through signal procs. +///The visual overlay of the aquarium content. It can hold a few vars with values about the component of the aquarium it's in. /obj/effect/aquarium layer = 0 //set on set_vc_base_position /// How the visual will be layered var/layer_mode = AQUARIUM_LAYER_MODE_AUTO - -#undef DEAD_FISH_BEAUTY -#undef MIN_DEAD_FISH_BEAUTY -#undef MAX_DEAD_FISH_BEAUTY -#undef MIN_AQUARIUM_BEAUTY -#undef MAX_AQUARIUM_BEAUTY + ///minimum pixel x, inherited from the aquarium + var/aquarium_zone_min_px + ///maximum pixel x, inherited from the aquarium + var/aquarium_zone_max_px + ///minimum pixel y, inherited from the aquarium + var/aquarium_zone_min_py + ///maximum pixel y, inherited from the aquarium + var/aquarium_zone_max_py + ///The current fluid type, inherited fom the aquarium + var/fluid_type diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm index 78bff449317..8366a65b511 100644 --- a/code/datums/components/blob_minion.dm +++ b/code/datums/components/blob_minion.dm @@ -150,6 +150,6 @@ SIGNAL_HANDLER overmind?.assume_direct_control(replacement) -/datum/component/blob_minion/PostTransfer() - if(!isliving(parent)) +/datum/component/blob_minion/PostTransfer(datum/new_parent) + if(!isliving(new_parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/crafting/furniture.dm b/code/datums/components/crafting/furniture.dm index 39f5a25a19f..c3f6557f89a 100644 --- a/code/datums/components/crafting/furniture.dm +++ b/code/datums/components/crafting/furniture.dm @@ -22,12 +22,22 @@ result = /obj/structure/aquarium time = 10 SECONDS reqs = list( - /obj/item/stack/sheet/iron = 15, + /obj/item/stack/sheet/iron = 10, /obj/item/stack/sheet/glass = 10, /obj/item/aquarium_kit = 1, ) category = CAT_FURNITURE +/datum/crafting_recipe/fish_tank + name = "Portable Fish Tank" + result = /obj/item/fish_tank + time = 10 SECONDS + reqs = list( + /obj/item/stack/sheet/plastic = 5, + /obj/item/aquarium_kit = 1, + ) + category = CAT_FURNITURE + /datum/crafting_recipe/mirror name = "Mirror" result = /obj/item/wallframe/mirror diff --git a/code/datums/components/customizable_reagent_holder.dm b/code/datums/components/customizable_reagent_holder.dm index fd1b8a2ac64..fda64d6c256 100644 --- a/code/datums/components/customizable_reagent_holder.dm +++ b/code/datums/components/customizable_reagent_holder.dm @@ -82,10 +82,10 @@ )) REMOVE_TRAIT(parent, TRAIT_CUSTOMIZABLE_REAGENT_HOLDER, REF(src)) -/datum/component/customizable_reagent_holder/PostTransfer() - if(!isatom(parent)) +/datum/component/customizable_reagent_holder/PostTransfer(datum/new_parent) + if(!isatom(new_parent)) return COMPONENT_INCOMPATIBLE - var/atom/atom_parent = parent + var/atom/atom_parent = new_parent if (!atom_parent.reagents) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/itempicky.dm b/code/datums/components/itempicky.dm index bda8b1ae138..f8e4e2256d8 100644 --- a/code/datums/components/itempicky.dm +++ b/code/datums/components/itempicky.dm @@ -27,8 +27,8 @@ /datum/component/itempicky/UnregisterFromParent() UnregisterSignal(parent, COMSIG_LIVING_TRY_PUT_IN_HAND) -/datum/component/itempicky/PostTransfer() - if(!ismob(parent)) +/datum/component/itempicky/PostTransfer(datum/new_parent) + if(!ismob(new_parent)) return COMPONENT_INCOMPATIBLE /datum/component/itempicky/InheritComponent(datum/component/itempicky/friend, i_am_original, list/arguments) diff --git a/code/datums/components/multiple_lives.dm b/code/datums/components/multiple_lives.dm index ce2c4a5053f..f374b3e3775 100644 --- a/code/datums/components/multiple_lives.dm +++ b/code/datums/components/multiple_lives.dm @@ -51,6 +51,6 @@ /datum/component/multiple_lives/InheritComponent(datum/component/multiple_lives/new_comp , lives_left) src.lives_left += new_comp ? new_comp.lives_left : lives_left -/datum/component/multiple_lives/PostTransfer() - if(!isliving(parent)) +/datum/component/multiple_lives/PostTransfer(datum/new_parent) + if(!isliving(new_parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/orbiter.dm b/code/datums/components/orbiter.dm index 71f391e599a..74a59301e73 100644 --- a/code/datums/components/orbiter.dm +++ b/code/datums/components/orbiter.dm @@ -56,10 +56,10 @@ orbiter_list += newcomp.orbiter_list newcomp.orbiter_list = null -/datum/component/orbiter/PostTransfer() - if(!isatom(parent) || isarea(parent) || !get_turf(parent)) +/datum/component/orbiter/PostTransfer(datum/new_parent) + if(!isatom(parent) || isarea(new_parent) || !get_turf(new_parent)) return COMPONENT_INCOMPATIBLE - move_react(parent) + move_react(new_parent) /datum/component/orbiter/proc/begin_orbit(atom/movable/orbiter, radius, clockwise, rotation_speed, rotation_segments, pre_rotation) if(orbiter.orbiting) diff --git a/code/datums/components/rotation.dm b/code/datums/components/rotation.dm index 40df294af12..16d7bddec87 100644 --- a/code/datums/components/rotation.dm +++ b/code/datums/components/rotation.dm @@ -28,7 +28,7 @@ RegisterSignal(parent, COMSIG_ATOM_REQUESTING_CONTEXT_FROM_ITEM, PROC_REF(on_requesting_context_from_item)) return ..() -/datum/component/simple_rotation/PostTransfer() +/datum/component/simple_rotation/PostTransfer(datum/new_parent) //Because of the callbacks which we don't track cleanly we can't transfer this //item cleanly, better to let the new of the new item create a new rotation datum //instead (there's no real state worth transferring) diff --git a/code/datums/components/shy.dm b/code/datums/components/shy.dm index a9b50a07efb..a6303b87a03 100644 --- a/code/datums/components/shy.dm +++ b/code/datums/components/shy.dm @@ -59,7 +59,7 @@ COMSIG_TRY_ALT_ACTION, )) -/datum/component/shy/PostTransfer() +/datum/component/shy/PostTransfer(datum/new_parent) if(!ismob(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/shy_in_room.dm b/code/datums/components/shy_in_room.dm index 4e0c2421561..d4eeeb108d9 100644 --- a/code/datums/components/shy_in_room.dm +++ b/code/datums/components/shy_in_room.dm @@ -31,7 +31,7 @@ COMSIG_TRY_ALT_ACTION, )) -/datum/component/shy_in_room/PostTransfer() +/datum/component/shy_in_room/PostTransfer(datum/new_parent) if(!ismob(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/spill.dm b/code/datums/components/spill.dm index 9112706046c..6c84299f689 100644 --- a/code/datums/components/spill.dm +++ b/code/datums/components/spill.dm @@ -25,7 +25,7 @@ dropsound = _dropsound drop_memory = _drop_memory -/datum/component/spill/PostTransfer() +/datum/component/spill/PostTransfer(datum/new_parent) if(!isitem(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/technointrovert.dm b/code/datums/components/technointrovert.dm index c6f51f80416..0797d24cb48 100644 --- a/code/datums/components/technointrovert.dm +++ b/code/datums/components/technointrovert.dm @@ -20,7 +20,7 @@ /datum/component/technointrovert/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_TRY_USE_MACHINE, COMSIG_TRY_WIRES_INTERACT)) -/datum/component/technointrovert/PostTransfer() +/datum/component/technointrovert/PostTransfer(datum/new_parent) if(!ismob(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/technoshy.dm b/code/datums/components/technoshy.dm index e8d4441a287..168f6d61ab5 100644 --- a/code/datums/components/technoshy.dm +++ b/code/datums/components/technoshy.dm @@ -26,7 +26,7 @@ /datum/component/technoshy/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_TRY_USE_MACHINE, COMSIG_TRY_WIRES_INTERACT)) -/datum/component/technoshy/PostTransfer() +/datum/component/technoshy/PostTransfer(datum/new_parent) if(!ismob(parent)) return COMPONENT_INCOMPATIBLE diff --git a/code/datums/components/wet_floor.dm b/code/datums/components/wet_floor.dm index d1f5b0fb1b8..f362359f3cd 100644 --- a/code/datums/components/wet_floor.dm +++ b/code/datums/components/wet_floor.dm @@ -159,14 +159,14 @@ for(var/i in time_left_list) . |= text2num(i) -/datum/component/wet_floor/PreTransfer() +/datum/component/wet_floor/PreTransfer(datum/new_parent) var/turf/O = parent O.cut_overlay(current_overlay) //That turf is no longer slippery, we're out of here //Slippery components don't transfer due to callbacks qdel(O.GetComponent(/datum/component/slippery)) -/datum/component/wet_floor/PostTransfer() +/datum/component/wet_floor/PostTransfer(datum/new_parent) if(!isopenturf(parent)) return COMPONENT_INCOMPATIBLE var/turf/T = parent diff --git a/code/datums/elements/beauty.dm b/code/datums/elements/beauty.dm index ddd3a8f5eab..8bfa298c1a7 100644 --- a/code/datums/elements/beauty.dm +++ b/code/datums/elements/beauty.dm @@ -20,57 +20,76 @@ src.beauty = beauty - if(!beauty_counter[target] && ismovable(target)) + var/area/current_area = get_area(target) + var/beauty_active = TRUE + if(ismovable(target)) var/atom/movable/mov_target = target - mov_target.become_area_sensitive(BEAUTY_ELEMENT_TRAIT) - RegisterSignal(mov_target, COMSIG_ENTER_AREA, PROC_REF(enter_area)) - RegisterSignal(mov_target, COMSIG_EXIT_AREA, PROC_REF(exit_area)) + var/is_item = isitem(mov_target) + beauty_active = !is_item || isturf(mov_target.loc) + if(!beauty_counter[target]) + if(is_item) + RegisterSignal(mov_target, COMSIG_MOVABLE_MOVED, PROC_REF(on_item_moved)) + if(beauty_active) + mov_target.become_area_sensitive(BEAUTY_ELEMENT_TRAIT) + RegisterSignal(mov_target, COMSIG_ENTER_AREA, PROC_REF(enter_area)) + RegisterSignal(mov_target, COMSIG_EXIT_AREA, PROC_REF(exit_area)) beauty_counter[target]++ - var/area/current_area = get_area(target) - if(current_area && !current_area.outdoors) + if(current_area && !current_area.outdoors && beauty_active) current_area.totalbeauty += beauty current_area.update_beauty() /datum/element/beauty/proc/enter_area(datum/source, area/new_area) SIGNAL_HANDLER - if(new_area.outdoors) + if(new_area.outdoors || HAS_TRAIT(source, TRAIT_BEAUTY_APPLIED)) return new_area.totalbeauty += beauty * beauty_counter[source] new_area.update_beauty() + ADD_TRAIT(source, TRAIT_BEAUTY_APPLIED, INNATE_TRAIT) /datum/element/beauty/proc/exit_area(datum/source, area/old_area) SIGNAL_HANDLER - if(old_area.outdoors) + if(old_area.outdoors || !HAS_TRAIT(source, TRAIT_BEAUTY_APPLIED)) return old_area.totalbeauty -= beauty * beauty_counter[source] old_area.update_beauty() + REMOVE_TRAIT(source, TRAIT_BEAUTY_APPLIED, INNATE_TRAIT) -/datum/element/beauty/Detach(datum/source) +///Items only contribute to beauty while not inside other objects or mobs (e.g on the floor, on a table etc.). +/datum/element/beauty/proc/on_item_moved(obj/item/source, atom/old_loc, direction, forced) + SIGNAL_HANDLER + + var/is_old_turf = isturf(old_loc) + if(!is_old_turf && isturf(source.loc)) + source.become_area_sensitive(BEAUTY_ELEMENT_TRAIT) + RegisterSignal(source, COMSIG_ENTER_AREA, PROC_REF(enter_area), TRUE) + RegisterSignal(source, COMSIG_EXIT_AREA, PROC_REF(exit_area), TRUE) + enter_area(source, get_area(source.loc)) + else if(is_old_turf && !isturf(source.loc)) + source.lose_area_sensitivity(BEAUTY_ELEMENT_TRAIT) + UnregisterSignal(source, list(COMSIG_ENTER_AREA, COMSIG_EXIT_AREA)) + exit_area(source, get_area(old_loc)) + +/datum/element/beauty/Detach(atom/source) if(!beauty_counter[source]) return ..() - var/area/current_area = get_area(source) - if(QDELETED(source)) - . = ..() - UnregisterSignal(source, list(COMSIG_ENTER_AREA, COMSIG_EXIT_AREA)) - if(current_area) - exit_area(source, current_area) - beauty_counter -= source - var/atom/movable/movable_source = source - if(istype(movable_source)) - movable_source.lose_area_sensitivity(BEAUTY_ELEMENT_TRAIT) - else //lower the 'counter' down by one, update the area, and call parent if it's reached zero. + + var/area/current_area = (!isitem(source) || isturf(source.loc)) ? get_area(source) : null + if(!QDELETED(source))//lower the 'counter' down by one, update the area, and call parent if it's reached zero. beauty_counter[source]-- if(current_area && !current_area.outdoors) current_area.totalbeauty -= beauty current_area.update_beauty() - if(!beauty_counter[source]) - . = ..() - UnregisterSignal(source, list(COMSIG_ENTER_AREA, COMSIG_EXIT_AREA)) - beauty_counter -= source - var/atom/movable/movable_source = source - if(istype(movable_source)) - movable_source.lose_area_sensitivity(BEAUTY_ELEMENT_TRAIT) + if(beauty_counter[source]) + return + else if(current_area) + exit_area(source, current_area) + + UnregisterSignal(source, list(COMSIG_ENTER_AREA, COMSIG_EXIT_AREA, COMSIG_MOVABLE_MOVED)) + beauty_counter -= source + var/atom/movable/movable_source = source + if(istype(movable_source)) + movable_source.lose_area_sensitivity(BEAUTY_ELEMENT_TRAIT) diff --git a/code/datums/storage/subtypes/backpack.dm b/code/datums/storage/subtypes/backpack.dm index a6b3e1477dd..a2a1a13fcd4 100644 --- a/code/datums/storage/subtypes/backpack.dm +++ b/code/datums/storage/subtypes/backpack.dm @@ -2,6 +2,16 @@ max_total_storage = 21 max_slots = 21 +/datum/storage/backpack/New( + atom/parent, + max_slots, + max_specific_storage, + max_total_storage, +) + . = ..() + var/static/list/exception_cache = typecacheof(list(/obj/item/fish_tank)) + exception_hold = exception_cache + /datum/storage/backpack/santabag max_total_storage = 60 max_slots = 21 diff --git a/code/datums/storage/subtypes/duffel_bag.dm b/code/datums/storage/subtypes/duffel_bag.dm index cfa073da5e5..bb3a236d38b 100644 --- a/code/datums/storage/subtypes/duffel_bag.dm +++ b/code/datums/storage/subtypes/duffel_bag.dm @@ -2,6 +2,16 @@ max_total_storage = 30 max_slots = 21 +/datum/storage/duffel/New( + atom/parent, + max_slots, + max_specific_storage, + max_total_storage, +) + . = ..() + var/static/list/exception_cache = typecacheof(list(/obj/item/fish_tank)) + exception_hold = exception_cache + // Syndi bags get some FUN extras // You can fit any 2 bulky objects (assuming they're in the whitelist) // Should have traitorus stuff in here, not just useful big things @@ -60,6 +70,9 @@ /obj/item/storage/bag/money, // Heads! /obj/item/bodypart/head, + // Fish + /obj/item/fish, + /obj/item/fish_tank, ) // We keep the type list and the typecache list separate... diff --git a/code/datums/storage/subtypes/fish_case.dm b/code/datums/storage/subtypes/fish_case.dm index 47103e931b1..e38d55e0e1d 100644 --- a/code/datums/storage/subtypes/fish_case.dm +++ b/code/datums/storage/subtypes/fish_case.dm @@ -7,7 +7,7 @@ . = ..() if(!.) return . - if(!HAS_TRAIT(to_insert, TRAIT_FISH_CASE_COMPATIBILE)) + if(!HAS_TRAIT(to_insert, TRAIT_AQUARIUM_CONTENT)) if(messages && user) user.balloon_alert(user, "can't hold!") return FALSE diff --git a/code/game/objects/items/food/lizard.dm b/code/game/objects/items/food/lizard.dm index 74534592a5f..f8d8dbec8f5 100644 --- a/code/game/objects/items/food/lizard.dm +++ b/code/game/objects/items/food/lizard.dm @@ -142,8 +142,9 @@ . = ..() //Moonfish can lay eggs (unaffected by breeding, so think of them as unfertilizard) RegisterSignal(src, COMSIG_AQUARIUM_CONTENT_GENERATE_APPEARANCE, PROC_REF(generate_aquarium_appearance)) - RegisterSignal(src, AQUARIUM_CONTENT_RANDOMIZE_POSITION, PROC_REF(randomize_aquarium_position)) - AddComponent(/datum/component/aquarium_content, beauty = 100) + RegisterSignal(src, COMSIG_AQUARIUM_CONTENT_RANDOMIZE_POSITION, PROC_REF(randomize_aquarium_position)) + AddComponent(/datum/component/aquarium_content) + RegisterSignal(src, COMSIG_MOVABLE_GET_AQUARIUM_BEAUTY, PROC_REF(get_aquarium_beauty)) /obj/item/food/moonfish_eggs/proc/generate_aquarium_appearance(datum/source, obj/effect/aquarium/visual) SIGNAL_HANDLER @@ -153,13 +154,18 @@ /obj/item/food/moonfish_eggs/proc/randomize_aquarium_position(datum/source, obj/structure/aquarium/current_aquarium, obj/effect/aquarium/visual) SIGNAL_HANDLER - var/list/aq_properties = current_aquarium.get_surface_properties() var/sprite_width = 5 - var/px_min = aq_properties[AQUARIUM_PROPERTIES_PX_MIN] - var/px_max = aq_properties[AQUARIUM_PROPERTIES_PX_MAX] - sprite_width + var/sprite_height = 4 + var/px_min = visual.aquarium_zone_min_px + var/px_max = visual.aquarium_zone_max_px - sprite_width + var/py_min = visual.aquarium_zone_min_py - sprite_height - visual.pixel_x = rand(px_min,px_max) - visual.pixel_y = rand(-1, 1) + visual.pixel_x = rand(px_min, px_max) + visual.pixel_y = py_min + rand(-1, 1) + +/obj/item/food/moonfish_eggs/proc/get_aquarium_beauty(datum/source, list/beauty_holder) + SIGNAL_HANDLER + beauty_holder += 100 //moonfish eggs are kinda eye candy /obj/item/food/moonfish_caviar name = "moonfish caviar paste" diff --git a/code/modules/fishing/aquarium/aquarium.dm b/code/modules/fishing/aquarium/aquarium.dm index 7301f1a296e..18a54aebd2e 100644 --- a/code/modules/fishing/aquarium/aquarium.dm +++ b/code/modules/fishing/aquarium/aquarium.dm @@ -1,14 +1,3 @@ -#define AQUARIUM_LAYER_STEP 0.01 -/// Aquarium content layer offsets -#define AQUARIUM_MIN_OFFSET 0.02 -#define AQUARIUM_MAX_OFFSET 1 -/// The layer of the glass overlay -#define AQUARIUM_GLASS_LAYER 0.01 -/// The layer of the aquarium pane borders -#define AQUARIUM_BORDERS_LAYER AQUARIUM_MAX_OFFSET + AQUARIUM_LAYER_STEP -/// Layer for stuff rendered below the glass overlay -#define AQUARIUM_BELOW_GLASS_LAYER 0.01 - /obj/structure/aquarium name = "aquarium" desc = "A vivarium in which aquatic fauna and flora are usually kept and displayed." @@ -17,351 +6,75 @@ icon = 'icons/obj/aquarium/tanks.dmi' icon_state = "aquarium_map" + base_icon_state = "aquarium" integrity_failure = 0.3 - /// The icon state is used for mapping so mappers know what they're placing. This prefixes the real icon used in game. - /// For an example, "aquarium" gives the base sprite of "aquarium_base", the glass is "aquarium_glass_water", and so on. - var/icon_prefix = "aquarium" - - var/fluid_type = AQUARIUM_FLUID_FRESHWATER - var/fluid_temp = DEFAULT_AQUARIUM_TEMP - var/min_fluid_temp = MIN_AQUARIUM_TEMP - var/max_fluid_temp = MAX_AQUARIUM_TEMP - - ///While the feed storage is not empty, this is the interval which the fish are fed. - var/feeding_interval = 3 MINUTES - ///The last time fishes were fed by the acquarium itsef. - var/last_feeding - - /// Can fish reproduce in this quarium. - var/reproduction_and_growth = TRUE - //This is the area where fish can swim var/aquarium_zone_min_px = 2 var/aquarium_zone_max_px = 31 var/aquarium_zone_min_py = 10 var/aquarium_zone_max_py = 28 - var/list/fluid_types = list(AQUARIUM_FLUID_SALTWATER, AQUARIUM_FLUID_FRESHWATER, AQUARIUM_FLUID_SULPHWATEVER, AQUARIUM_FLUID_AIR) + /// Default beauty of the aquarium, without anything inside it + var/default_beauty = 150 - var/panel_open = FALSE - - ///Current layers in use by aquarium contents - var/list/used_layers = list() - - /// /obj/item/fish in the aquarium, sorted by type - does not include things with aquarium visuals that are not fish - var/list/tracked_fish_by_type - - /// Var used to keep track of the current beauty of the aquarium, which can be throughfully changed by aquarium content. - var/current_beauty = 150 + ///Tracks the fluid type of our aquarium component. Used for the icon suffix of some overlays and splashing water when broken. + var/fluid_type = AQUARIUM_FLUID_FRESHWATER /obj/structure/aquarium/Initialize(mapload) . = ..() - update_appearance() - RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(track_if_fish)) - AddElement(/datum/element/relay_attackers) - RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(on_attacked)) - create_reagents(6, SEALED_CONTAINER) - RegisterSignal(reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed)) + AddComponent(/datum/component/aquarium, aquarium_zone_min_px, aquarium_zone_max_px, aquarium_zone_min_py, aquarium_zone_max_py, default_beauty) AddComponent(/datum/component/plumbing/aquarium, start = anchored) - if(current_beauty) - AddElement(/datum/element/beauty, current_beauty) - ADD_KEEP_TOGETHER(src, INNATE_TRAIT) - -/obj/structure/aquarium/proc/track_if_fish(atom/source, atom/initialized) - SIGNAL_HANDLER - if(isfish(initialized)) - LAZYADDASSOCLIST(tracked_fish_by_type, initialized.type, initialized) - -/obj/structure/aquarium/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) - . = ..() - if(isfish(arrived)) - LAZYADDASSOCLIST(tracked_fish_by_type, arrived.type, arrived) - -/obj/structure/aquarium/Exited(atom/movable/gone, direction) - . = ..() - LAZYREMOVEASSOC(tracked_fish_by_type, gone.type, gone) - -/obj/structure/aquarium/proc/start_autofeed(datum/source, new_reagent, amount, reagtemp, data, no_react) - SIGNAL_HANDLER - START_PROCESSING(SSobj, src) - UnregisterSignal(reagents, COMSIG_REAGENTS_NEW_REAGENT) - -/obj/structure/aquarium/process(seconds_per_tick) - if(!reagents.total_volume) - RegisterSignal(reagents, COMSIG_REAGENTS_NEW_REAGENT, PROC_REF(start_autofeed)) - return PROCESS_KILL - if(world.time < last_feeding + feeding_interval) - return - last_feeding = world.time - var/list/fishes = get_fishes() - for(var/obj/item/fish/fish as anything in fishes) - fish.feed(reagents) - -/// Returns tracked_fish_by_type but flattened and without the items in the blacklist, also shuffled if shuffle is TRUE. -/obj/structure/aquarium/proc/get_fishes(shuffle = FALSE, blacklist) - . = list() - for(var/fish_type in tracked_fish_by_type) - . += tracked_fish_by_type[fish_type] - . -= blacklist - if(shuffle) - . = shuffle(.) - return . - -/obj/structure/aquarium/proc/request_layer(layer_type) - /** - * base aq layer - * min_offset = this value is returned on bottom layer mode - * min_offset + 0.1 fish1 - * min_offset + 0.2 fish2 - * ... these layers are returned for auto layer mode and tracked by used_layers - * min_offset + max_offset = this value is returned for top layer mode - * min_offset + max_offset + 1 = this is used for glass overlay - */ - //optional todo: hook up sending surface changed on aquarium changing layers - switch(layer_type) - if(AQUARIUM_LAYER_MODE_BEHIND_GLASS) - return layer + AQUARIUM_BELOW_GLASS_LAYER - if(AQUARIUM_LAYER_MODE_BOTTOM) - return layer + AQUARIUM_MIN_OFFSET - if(AQUARIUM_LAYER_MODE_TOP) - return layer + AQUARIUM_MAX_OFFSET - if(AQUARIUM_LAYER_MODE_AUTO) - var/chosen_layer = AQUARIUM_MIN_OFFSET + AQUARIUM_LAYER_STEP - while((chosen_layer in used_layers) && (chosen_layer <= AQUARIUM_MAX_OFFSET)) - chosen_layer += AQUARIUM_LAYER_STEP - used_layers += chosen_layer - return layer + chosen_layer - -/obj/structure/aquarium/proc/free_layer(value) - used_layers -= value - -/obj/structure/aquarium/proc/get_surface_properties() - . = list() - .[AQUARIUM_PROPERTIES_PX_MIN] = aquarium_zone_min_px - .[AQUARIUM_PROPERTIES_PX_MAX] = aquarium_zone_max_px - .[AQUARIUM_PROPERTIES_PY_MIN] = aquarium_zone_min_py - .[AQUARIUM_PROPERTIES_PY_MAX] = aquarium_zone_max_py + RegisterSignal(src, COMSIG_AQUARIUM_FLUID_CHANGED, PROC_REF(on_aquarium_liquid_changed)) + update_appearance() /obj/structure/aquarium/update_icon() . = ..() ///"aquarium_map" is used for mapping, so mappers can tell what it's. - icon_state = icon_prefix + "_base" + icon_state = base_icon_state + "_base" + +/obj/structure/aquarium/proc/on_aquarium_liquid_changed(datum/source, fluid_type) + SIGNAL_HANDLER + src.fluid_type = fluid_type + update_appearance() /obj/structure/aquarium/update_overlays() . = ..() - if(panel_open) - . += icon_prefix + "_panel" + if(HAS_TRAIT(src, TRAIT_AQUARIUM_PANEL_OPEN)) + . += base_icon_state + "_panel" + var/icon_suffix = fluid_type == AQUARIUM_FLUID_AIR ? "air" : "water" ///The glass overlay - var/suffix = fluid_type == AQUARIUM_FLUID_AIR ? "air" : "water" if(broken) - suffix += "_broken" - . += mutable_appearance(icon, icon_prefix + "_glass_cracks", layer = layer + AQUARIUM_BORDERS_LAYER) - . += mutable_appearance(icon, icon_prefix + "_glass_[suffix]", layer = layer + AQUARIUM_GLASS_LAYER) - . += mutable_appearance(icon, icon_prefix + "_borders", layer = layer + AQUARIUM_BORDERS_LAYER) - -/obj/structure/aquarium/examine(mob/user) - . = ..() - . += span_notice("Alt-click to [panel_open ? "close" : "open"] the control and feed panel.") - if(panel_open && reagents.total_volume) - . += span_notice("You can use a plunger to empty the feed storage.") - -/obj/structure/aquarium/click_alt(mob/living/user) - panel_open = !panel_open - balloon_alert(user, "panel [panel_open ? "open" : "closed"]") - if(panel_open) - reagents.flags |= TRANSPARENT|REFILLABLE - else - reagents.flags &= ~(TRANSPARENT|REFILLABLE) - update_appearance() - return CLICK_ACTION_SUCCESS + icon_suffix += "_broken" + . += mutable_appearance(icon, base_icon_state + "_glass_cracks", layer = layer + AQUARIUM_BORDERS_LAYER) + . += mutable_appearance(icon, base_icon_state + "_glass_[icon_suffix]", layer = layer + AQUARIUM_GLASS_LAYER) + . += mutable_appearance(icon, base_icon_state + "_borders", layer = layer + AQUARIUM_BORDERS_LAYER) /obj/structure/aquarium/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) return ITEM_INTERACT_SUCCESS -/obj/structure/aquarium/plunger_act(obj/item/plunger/P, mob/living/user, reinforced) - if(!panel_open) +/obj/structure/aquarium/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(!istype(tool, /obj/item/stack/sheet/glass)) return - user.balloon_alert_to_viewers("plunging...") - if(do_after(user, 3 SECONDS, target = src)) - user.balloon_alert_to_viewers("finished plunging") - reagents.expose(get_turf(src), TOUCH) //splash on the floor - reagents.clear_reagents() - -/obj/structure/aquarium/attackby(obj/item/item, mob/living/user, params) - if(broken) - var/obj/item/stack/sheet/glass/glass = item - if(istype(glass)) - if(glass.get_amount() < 2) - balloon_alert(user, "it needs two sheets!") - return - balloon_alert(user, "fixing the aquarium...") - if(do_after(user, 2 SECONDS, target = src)) - glass.use(2) - broken = FALSE - atom_integrity = max_integrity - update_appearance() - return TRUE - else - var/insert_attempt = SEND_SIGNAL(item, COMSIG_TRY_INSERTING_IN_AQUARIUM, src) - switch(insert_attempt) - if(COMSIG_CAN_INSERT_IN_AQUARIUM) - if(!user.transferItemToLoc(item, src)) - user.balloon_alert(user, "stuck to your hand!") - return TRUE - balloon_alert(user, "added to aquarium") - update_appearance() - return TRUE - if(COMSIG_CANNOT_INSERT_IN_AQUARIUM) - balloon_alert(user, "cannot add to aquarium!") - return TRUE - - if(istype(item, /obj/item/reagent_containers/cup/fish_feed) && !panel_open) - if(!item.reagents.total_volume) - balloon_alert(user, "[item] is empty!") - return TRUE - var/list/fishes = get_fishes() - if(!length(fishes)) - balloon_alert(user, "no fish to feed!") - return TRUE - for(var/obj/item/fish/fish as anything in fishes) - fish.feed(item.reagents) - balloon_alert(user, "fed the fish") - return TRUE - if(istype(item, /obj/item/aquarium_upgrade)) - var/obj/item/aquarium_upgrade/upgrade = item - if(upgrade.upgrade_from_type != type) - balloon_alert(user, "wrong kind of aquarium!") - return - balloon_alert(user, "upgrading...") - if(!do_after(user, 5 SECONDS, src)) - return - var/obj/structure/aquarium/upgraded_aquarium = new upgrade.upgrade_to_type(loc) - for(var/atom/movable/moving in contents) - moving.forceMove(upgraded_aquarium) - balloon_alert(user, "upgraded") - qdel(upgrade) - qdel(src) - return - return ..() - -/obj/structure/aquarium/proc/on_attacked(datum/source, mob/attacker, attack_flags) - var/list/fishes = get_fishes() - //I wish this were an aquarium signal, but the aquarium_content component got in the way. - for(var/obj/item/fish/fish as anything in fishes) - SEND_SIGNAL(fish, COMSIG_FISH_STIRRED) - -/obj/structure/aquarium/interact(mob/user) - if(panel_open) - return ..() //call base ui_interact - else - admire(user) - -///Apply mood bonus depending on aquarium status -/obj/structure/aquarium/proc/admire(mob/living/user) - user.balloon_alert(user, "admiring aquarium...") - if(!do_after(user, 5 SECONDS, target = src)) - return - var/alive_fish = 0 - var/dead_fish = 0 - var/list/tracked_fish = get_fishes() - for(var/obj/item/fish/fish in tracked_fish) - if(fish.status == FISH_ALIVE) - alive_fish++ - else - dead_fish++ - - var/morb = HAS_MIND_TRAIT(user, TRAIT_MORBID) - //Check if there are live fish - good mood - //All fish dead - bad mood. - //No fish - nothing. - if(alive_fish > 0) - user.add_mood_event("aquarium", morb ? /datum/mood_event/morbid_aquarium_bad : /datum/mood_event/aquarium_positive) - else if(dead_fish > 0) - user.add_mood_event("aquarium", morb ? /datum/mood_event/morbid_aquarium_good : /datum/mood_event/aquarium_negative) - // Could maybe scale power of this mood with number/types of fish - -/obj/structure/aquarium/ui_data(mob/user) - . = ..() - .["fluidType"] = fluid_type - .["temperature"] = fluid_temp - .["allowBreeding"] = reproduction_and_growth - .["fishData"] = list() - .["feedingInterval"] = feeding_interval / (1 MINUTES) - .["propData"] = list() - for(var/atom/movable/item in contents) - if(isfish(item)) - var/obj/item/fish/fish = item - .["fishData"] += list(list( - "fish_ref" = REF(fish), - "fish_name" = uppertext(fish.name), - "fish_happiness" = fish.get_happiness_value(), - "fish_icon" = fish::icon, - "fish_icon_state" = fish::icon_state, - "fish_health" = fish.health, - )) - continue - .["propData"] += list(list( - "prop_ref" = REF(item), - "prop_name" = item.name, - "prop_icon" = item::icon, - "prop_icon_state" = item::icon_state, - )) - -/obj/structure/aquarium/ui_static_data(mob/user) - . = ..() - //I guess these should depend on the fluid so lava critters can get high or stuff below water freezing point but let's keep it simple for now. - .["minTemperature"] = min_fluid_temp - .["maxTemperature"] = max_fluid_temp - .["fluidTypes"] = fluid_types - .["heartIcon"] = 'icons/effects/effects.dmi' - -/obj/structure/aquarium/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) - . = ..() - if(.) - return - var/mob/user = usr - switch(action) - if("temperature") - var/temperature = params["temperature"] - if(isnum(temperature)) - fluid_temp = clamp(temperature, min_fluid_temp, max_fluid_temp) - . = TRUE - if("fluid") - if(params["fluid"] in fluid_types) - fluid_type = params["fluid"] - SEND_SIGNAL(src, COMSIG_AQUARIUM_FLUID_CHANGED, fluid_type) - . = TRUE - if("allow_breeding") - reproduction_and_growth = !reproduction_and_growth - . = TRUE - if("feeding_interval") - feeding_interval = params["feeding_interval"] MINUTES - . = TRUE - if("pet_fish") - var/obj/item/fish/fish = locate(params["fish_reference"]) in contents - fish?.pet_fish(user) - if("remove_item") - var/atom/movable/item = locate(params["item_reference"]) in contents - item?.forceMove(drop_location()) - to_chat(user, span_notice("You take out [item] from [src].")) - if("rename_fish") - var/new_name = sanitize_name(params["chosen_name"]) - var/atom/movable/fish = locate(params["fish_reference"]) in contents - if(!fish || !new_name || new_name == fish.name) - return - fish.AddComponent(/datum/component/rename, new_name, fish.desc) - -/obj/structure/aquarium/ui_interact(mob/user, datum/tgui/ui) - . = ..() - ui = SStgui.try_update_ui(user, src, ui) - if(!ui) - ui = new(user, src, "Aquarium", name) - ui.open() + if(!broken) + balloon_alert(user, "aquarium not broken!") + return ITEM_INTERACT_BLOCKING + var/obj/item/stack/sheet/glass/glass = tool + if(glass.get_amount() < 2) + balloon_alert(user, "it needs two sheets!") + return ITEM_INTERACT_BLOCKING + balloon_alert(user, "fixing the aquarium...") + if(!do_after(user, 2 SECONDS, target = src)) + return ITEM_INTERACT_BLOCKING + glass.use(2) + broken = FALSE + atom_integrity = max_integrity + update_appearance() + return ITEM_INTERACT_SUCCESS /obj/structure/aquarium/atom_break(damage_flag) . = ..() @@ -377,38 +90,14 @@ else possible_destinations_for_fish = list(droploc) playsound(src, 'sound/effects/glass/glassbr3.ogg', 100, TRUE) - for(var/atom/movable/fish in contents) - fish.forceMove(pick(possible_destinations_for_fish)) + for(var/atom/movable/content as anything in contents) + content.forceMove(pick(possible_destinations_for_fish)) if(fluid_type != AQUARIUM_FLUID_AIR) var/datum/reagents/reagent_splash = new() reagent_splash.add_reagent(/datum/reagent/water, 30) chem_splash(droploc, null, 3, list(reagent_splash)) update_appearance() -#undef AQUARIUM_LAYER_STEP -#undef AQUARIUM_MIN_OFFSET -#undef AQUARIUM_MAX_OFFSET -#undef AQUARIUM_GLASS_LAYER -#undef AQUARIUM_BORDERS_LAYER -#undef AQUARIUM_BELOW_GLASS_LAYER - -/obj/structure/aquarium/lawyer - anchored = TRUE - -/obj/structure/aquarium/lawyer/Initialize(mapload) - . = ..() - - new /obj/item/aquarium_prop/sand(src) - new /obj/item/aquarium_prop/seaweed(src) - - if(prob(85)) - new /obj/item/fish/goldfish/gill(src) - reagents.add_reagent(/datum/reagent/consumable/nutriment, 2) - else - new /obj/item/fish/goldfish/three_eyes/gill(src) - reagents.add_reagent(/datum/reagent/toxin/mutagen, 2) //three eyes goldfish feed on mutagen. - - /obj/structure/aquarium/prefilled anchored = TRUE @@ -424,3 +113,145 @@ //They'll be alive for about 30 minutes with this amount. reagents.add_reagent(/datum/reagent/consumable/nutriment, 3) + +/obj/item/fish_tank + name = "fish tank" + desc = "A more portable sort of aquarium to store various fishes in, unless they're too big or there're too many of them." + icon = 'icons/obj/aquarium/tanks.dmi' + icon_state = "fish_tank_map" + base_icon_state = "fish_tank" + force = 5 + throwforce = 5 + throw_range = 3 + w_class = WEIGHT_CLASS_BULKY + item_flags = SLOWS_WHILE_IN_HAND + + custom_price = PAYCHECK_CREW * 9 + + ///Tracks the fluid type of our aquarium component. Used for overlays + var/fluid_type = AQUARIUM_FLUID_FRESHWATER + + ///Fish which size exceed this value cannot be inserted + var/maximum_relative_size = 100 + ///Fish cannot be inserted if the sum of the size of all fish in this tank exceeds this value. + var/max_total_size = 220 + ///Tracks the sum of the size of all fish in this tank + var/current_summed_size = 0 + ///Tracks the sum of the weight of all fish in this tank + var/current_summed_weight = 0 + + var/slowdown_coeff = 1 + + ///The minimum fluid temperature of this fish tank + var/min_fluid_temp = MIN_AQUARIUM_TEMP + 12 + ///The maximum fluid temperature of this fish tank + var/max_fluid_temp = MAX_AQUARIUM_TEMP - 32 + ///The reagent capacity of this fish tank + var/reagent_size = 4 + +/obj/item/fish_tank/Initialize(mapload) + . = ..() + update_appearance() + AddComponent(\ + /datum/component/aquarium,\ + min_px = 6,\ + max_px = 26,\ + min_py = 6,\ + min_py = 24,\ + default_beauty = 100,\ + reagents_size = src.reagent_size,\ + min_fluid_temp = src.min_fluid_temp,\ + max_fluid_temp = src.max_fluid_temp,\ + ) + AddComponent(/datum/component/plumbing/aquarium, start = anchored) + RegisterSignal(src, COMSIG_AQUARIUM_FLUID_CHANGED, PROC_REF(on_aquarium_liquid_changed)) + RegisterSignal(src, COMSIG_AQUARIUM_CAN_INSERT, PROC_REF(can_insert)) + RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(on_new_fish)) + +/obj/item/fish_tank/update_icon() + . = ..() + ///"aquarium_map" is used for mapping, so mappers can tell what it's. + icon_state = base_icon_state + +/obj/item/fish_tank/proc/on_aquarium_liquid_changed(datum/source, fluid_type) + SIGNAL_HANDLER + src.fluid_type = fluid_type + update_appearance() + +/obj/item/fish_tank/update_overlays() + . = ..() + . += "[base_icon_state]_panel[HAS_TRAIT(src, TRAIT_AQUARIUM_PANEL_OPEN) ? "_open" : ""]" + . += mutable_appearance(icon, "[base_icon_state]_[fluid_type == AQUARIUM_FLUID_AIR ? "air" : "water"]", layer = layer + AQUARIUM_GLASS_LAYER) + . += mutable_appearance(icon, "[base_icon_state]_borders", layer = layer + AQUARIUM_BORDERS_LAYER) + +/obj/item/fish_tank/proc/can_insert(atom/movable/source, obj/item/item, mob/living/user) + SIGNAL_HANDLER + if(!isfish(item)) + return + var/obj/item/fish/fish = item + if(fish.size > maximum_relative_size) + balloon_alert(user, "fish is too big!") + return COMSIG_CANNOT_INSERT_IN_AQUARIUM + if(current_summed_size > max_total_size) + balloon_alert(user, "fish tank is full!") + return COMSIG_CANNOT_INSERT_IN_AQUARIUM + return COMSIG_CAN_INSERT_IN_AQUARIUM + +/obj/item/fish_tank/Entered(atom/movable/entered) + . = ..() + on_new_fish(src, entered) + +/obj/item/fish_tank/proc/on_new_fish(datum/source, atom/movable/movable) + SIGNAL_HANDLER + if(!isfish(movable)) + return + var/obj/item/fish/fish = movable + change_size_weight(fish.size, fish.weight) + RegisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT, PROC_REF(on_fish_size_weight_updated)) + +/obj/item/fish_tank/proc/on_fish_size_weight_updated(obj/item/fish/source, new_size, new_weight) + SIGNAL_HANDLER + change_size_weight(new_size - source.size, new_weight - source.weight) + +/obj/item/fish_tank/Exited(atom/movable/gone) + if(isfish(gone)) + var/obj/item/fish/fish = gone + change_size_weight(-fish.size, -fish.weight) + UnregisterSignal(fish, COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT) + return ..() + +/obj/item/fish_tank/proc/change_size_weight(size_change, weight_change) + current_summed_size += size_change + current_summed_weight += weight_change + if(current_summed_size > max_total_size) + ADD_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, INNATE_TRAIT) + else + REMOVE_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, INNATE_TRAIT) + if(HAS_TRAIT(src, TRAIT_SPEED_POTIONED) || current_summed_weight < FISH_WEIGHT_SLOWDOWN) + slowdown = 0 + drag_slowdown = 0 + else + slowdown = GET_FISH_SLOWDOWN(current_summed_weight) * slowdown_coeff + drag_slowdown = slowdown * 0.5 + if(ismob(loc)) + var/mob/mob = loc + mob.update_equipment_speed_mods() + + force = min(2 + (GET_FISH_WEIGHT_RANK(current_summed_weight) * 3), 21) + throwforce = force + +///The lawyer's own pet goldfish's fish tank. It used to be an aquarium, but now it can be held and carried around. +/obj/item/fish_tank/lawyer + +/obj/item/fish_tank/lawyer/Initialize(mapload) + . = ..() + + new /obj/item/aquarium_prop/sand(src) + new /obj/item/aquarium_prop/seaweed(src) + + if(prob(85)) + new /obj/item/fish/goldfish/gill(src) + reagents.add_reagent(/datum/reagent/consumable/nutriment, 3) + else + new /obj/item/fish/goldfish/three_eyes/gill(src) + reagents.add_reagent(/datum/reagent/toxin/mutagen, 3) //three eyes goldfish feed on mutagen. diff --git a/code/modules/fishing/aquarium/aquarium_kit.dm b/code/modules/fishing/aquarium/aquarium_kit.dm index 17dd518710d..f18f67b9313 100644 --- a/code/modules/fishing/aquarium/aquarium_kit.dm +++ b/code/modules/fishing/aquarium/aquarium_kit.dm @@ -117,20 +117,20 @@ /obj/item/aquarium_kit name = "DIY Aquarium Construction Kit" - desc = "Everything you need to build your own aquarium. Raw materials sold separately." + desc = "Everything you need to build your own aquarium or fish tank. Raw materials sold separately." icon = 'icons/obj/aquarium/supplies.dmi' icon_state = "construction_kit" w_class = WEIGHT_CLASS_TINY /obj/item/aquarium_kit/Initialize(mapload) . = ..() - var/static/list/recipes = list(/datum/crafting_recipe/aquarium) + var/static/list/recipes = list(/datum/crafting_recipe/aquarium, /datum/crafting_recipe/fish_tank) AddElement(/datum/element/slapcrafting, recipes) /obj/item/aquarium_prop name = "generic aquarium prop" desc = "very boring" - icon = 'icons/obj/aquarium/supplies.dmi' + icon = 'icons/obj/aquarium/tanks.dmi' w_class = WEIGHT_CLASS_TINY custom_materials = list(/datum/material/plastic = COIN_MATERIAL_AMOUNT) @@ -141,15 +141,21 @@ . = ..() //It's important that we register the signals before the component is attached. RegisterSignal(src, COMSIG_AQUARIUM_CONTENT_GENERATE_APPEARANCE, PROC_REF(generate_aquarium_appearance)) - AddComponent(/datum/component/aquarium_content, beauty = beauty) + AddComponent(/datum/component/aquarium_content) ADD_TRAIT(src, TRAIT_UNIQUE_AQUARIUM_CONTENT, INNATE_TRAIT) -/obj/item/aquarium_prop/proc/generate_aquarium_appearance(datum/source, obj/effect/aquarium/visual) + RegisterSignal(src, COMSIG_MOVABLE_GET_AQUARIUM_BEAUTY, PROC_REF(get_aquarium_beauty)) + +/obj/item/aquarium_prop/proc/generate_aquarium_appearance(datum/source, obj/effect/aquarium/visual, atom/movable/aquarium) SIGNAL_HANDLER - visual.icon = icon - visual.icon_state = icon_state + visual.icon = aquarium.icon + visual.icon_state = "[icon_state][isitem(aquarium) ? "_fish_tank" : ""]" visual.layer_mode = layer_mode +/obj/item/aquarium_prop/proc/get_aquarium_beauty(datum/source, list/beauty_holder) + SIGNAL_HANDLER + beauty_holder += beauty + /obj/item/aquarium_prop/rocks name = "decorative rocks" desc = "A bunch of tiny plastic rocks for decorating an aquarium. Surely you could have just used real pebbles?" diff --git a/code/modules/fishing/aquarium/aquarium_upgrades.dm b/code/modules/fishing/aquarium/aquarium_upgrades.dm index c73e6e9d230..042074500af 100644 --- a/code/modules/fishing/aquarium/aquarium_upgrades.dm +++ b/code/modules/fishing/aquarium/aquarium_upgrades.dm @@ -11,8 +11,25 @@ /// typepath of the new aquarium subtype created. var/upgrade_to_type = /obj/structure/aquarium +/obj/item/aquarium_upgrade/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if(!HAS_TRAIT(interacting_with, TRAIT_IS_AQUARIUM)) + return NONE + if(upgrade_from_type != interacting_with.type) + interacting_with.balloon_alert(user, "wrong kind of aquarium!") + return ITEM_INTERACT_BLOCKING + interacting_with.balloon_alert(user, "upgrading...") + if(!PERFORM_ALL_TESTS(aquarium_upgrade) && !do_after(user, 5 SECONDS, interacting_with)) + return ITEM_INTERACT_BLOCKING + var/atom/movable/upgraded_aquarium = new upgrade_to_type(interacting_with.drop_location()) + //This should transfer all the fish, reagents and settings from the aquarium component + interacting_with.TransferComponents(upgraded_aquarium) + upgraded_aquarium.balloon_alert(user, "upgraded") + qdel(src) + qdel(interacting_with) + return ITEM_INTERACT_SUCCESS + /obj/item/aquarium_upgrade/bioelec_gen - name = "Aquarium Bioelectricity Kit" + name = "aquarium bioelectricity kit" desc = "All the required components to allow an aquarium to harness energy bioelectric fish." icon_state = "bioelec_kit" upgrade_to_type = /obj/structure/aquarium/bioelec_gen @@ -22,9 +39,13 @@ desc = "An unconventional type of generator that boosts and harvests the energy produced by bioelectric fish." icon_state = "bioelec_map" - icon_prefix = "bioelec" + base_icon_state = "bioelec" - current_beauty = 0 + default_beauty = 0 + +/obj/structure/aquarium/bioelec_gen/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_BIOELECTRIC_GENERATOR, INNATE_TRAIT) /obj/structure/aquarium/bioelec_gen/zap_act(power, zap_flags) var/explosive = zap_flags & ZAP_MACHINE_EXPLOSIVE @@ -37,3 +58,23 @@ . += span_boldwarning("WARNING! WARNING! WARNING!") . += span_warning("The bioelectric potential of the fish inside is magnified to dangerous levels by the generator.") . += span_notice("Tesla coils are required to collect this magnified energy... and you'll want a grounding rod to protect yourself as well.") + +/obj/item/aquarium_upgrade/bluespace_tank + name = "bluespace fish tank kit" + desc = "The required components to upgrade your portable fish tank into bottomless, handheld aquarium." + icon_state = "bluespace_kit" + upgrade_from_type = /obj/item/fish_tank + upgrade_to_type = /obj/item/fish_tank/bluespace + +/obj/item/fish_tank/bluespace + name = "bluespace fish tank" + desc = "All the capacity of a bulky room aquarium, squeezed in a bag-sized rectangular cuboid." + icon_state = "fish_tank_bluespace_map" + base_icon_state = "fish_tank_bluespace" + w_class = WEIGHT_CLASS_NORMAL + maximum_relative_size = INFINITY + max_total_size = 2000 + slowdown_coeff = 0.15 + min_fluid_temp = MIN_AQUARIUM_TEMP + max_fluid_temp = MAX_AQUARIUM_TEMP + reagent_size = 6 diff --git a/code/modules/fishing/aquarium/fish_analyzer.dm b/code/modules/fishing/aquarium/fish_analyzer.dm index 617d8b600ba..3df28e326ac 100644 --- a/code/modules/fishing/aquarium/fish_analyzer.dm +++ b/code/modules/fishing/aquarium/fish_analyzer.dm @@ -59,18 +59,15 @@ . += emissive_appearance(icon, "fish_analyzer_emissive", src) /obj/item/fish_analyzer/interact_with_atom(atom/target, mob/living/user, list/modifiers) - if(!isfish(target) && !isaquarium(target)) + if(!isfish(target) && !HAS_TRAIT(target, TRAIT_IS_AQUARIUM)) return NONE if(!user.can_read(src) || user.is_blind()) return ITEM_INTERACT_BLOCKING - if(isfish(target) || istype(target, /obj/structure/aquarium)) - scanned_item = WEAKREF(target) - SEND_SIGNAL(src, COMSIG_FISH_ANALYZER_ANALYZE_STATUS, target, user) - ui_interact(user) - return ITEM_INTERACT_SUCCESS - - return NONE + scanned_item = WEAKREF(target) + SEND_SIGNAL(src, COMSIG_FISH_ANALYZER_ANALYZE_STATUS, target, user) + ui_interact(user) + return ITEM_INTERACT_SUCCESS /obj/item/fish_analyzer/ui_interact(mob/user, datum/tgui/ui) if(isnull(scanned_item?.resolve())) @@ -92,13 +89,13 @@ data["fish_scanned"] = TRUE return extract_fish_info(data, scanned_object) - var/obj/structure/aquarium/aquarium = scanned_object + var/atom/movable/aquarium = scanned_object for(var/obj/item/fish/fishie in aquarium) - extract_fish_info(data, fishie, aquarium) + extract_fish_info(data, fishie) return data -/obj/item/fish_analyzer/proc/extract_fish_info(list/data, obj/item/fish/fishie, obj/structure/aquarium/aquarium) +/obj/item/fish_analyzer/proc/extract_fish_info(list/data, obj/item/fish/fishie) var/list/fish_traits = list() var/list/fish_evolutions = list() @@ -129,12 +126,12 @@ "fish_min_temp" = fishie.required_temperature_min, "fish_max_temp" = fishie.required_temperature_max, "fish_hunger" = HAS_TRAIT(fishie, TRAIT_FISH_NO_HUNGER) ? 0 : 1 - fishie.get_hunger(), - "fish_fluid_compatible" = aquarium ? compatible_fluid_type(fishie.required_fluid_type, aquarium.fluid_type) : null, + "fish_fluid_compatible" = fishie.fish_flags & FISH_FLAG_SAFE_FLUID, "fish_fluid_type" = fishie.required_fluid_type, "fish_breed_timer" = round(max(fishie.breeding_wait - world.time, 0) / 10), "fish_traits" = fish_traits, "fish_evolutions" = fish_evolutions, - "fish_suitable_temp" = aquarium ? ISINRANGE(aquarium.fluid_temp, fishie.required_temperature_min, fishie.required_temperature_max) : null + "fish_suitable_temp" = fishie.fish_flags & FISH_FLAG_SAFE_TEMPERATURE, )) return data diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm index 033e6e60f2f..b43a2bf439a 100644 --- a/code/modules/fishing/fish/_fish.dm +++ b/code/modules/fishing/fish/_fish.dm @@ -176,10 +176,11 @@ base_icon_state = icon_state //It's important that we register the signals before the component is attached. RegisterSignal(src, COMSIG_AQUARIUM_CONTENT_DO_ANIMATION, PROC_REF(update_aquarium_animation)) - RegisterSignal(src, AQUARIUM_CONTENT_RANDOMIZE_POSITION, PROC_REF(randomize_aquarium_position)) + RegisterSignal(src, COMSIG_AQUARIUM_CONTENT_RANDOMIZE_POSITION, PROC_REF(randomize_aquarium_position)) RegisterSignal(src, COMSIG_AQUARIUM_CONTENT_GENERATE_APPEARANCE, PROC_REF(update_aquarium_appearance)) - AddComponent(/datum/component/aquarium_content, list(COMSIG_FISH_STIRRED), beauty) + AddComponent(/datum/component/aquarium_content, list(COMSIG_ATOM_WAS_ATTACKED)) + RegisterSignal(src, COMSIG_MOVABLE_GET_AQUARIUM_BEAUTY, PROC_REF(get_aquarium_beauty)) RegisterSignal(src, COMSIG_ATOM_ON_LAZARUS_INJECTOR, PROC_REF(use_lazarus)) if(fish_flags & FISH_DO_FLOP_ANIM) RegisterSignal(src, COMSIG_ATOM_TEMPORARY_ANIMATION_START, PROC_REF(on_temp_animation)) @@ -549,7 +550,7 @@ make_edible() if(weight >= FISH_WEIGHT_SLOWDOWN && !HAS_TRAIT(src, TRAIT_SPEED_POTIONED)) - slowdown = round(((weight/FISH_WEIGHT_SLOWDOWN_DIVISOR)**FISH_WEIGHT_SLOWDOWN_EXPONENT)-1.3, 0.1) + slowdown = GET_FISH_SLOWDOWN(weight) drag_slowdown = round(slowdown * 0.5, 1) else slowdown = 0 @@ -592,14 +593,6 @@ AddElement(/datum/element/processable, TOOL_KNIFE, fillet_type, amount, time, screentip_verb = "Cut") return amount //checked by a unit test -/** - * Weight, unlike size, is a bit more exponential, but the world isn't perfect, so isn't my code. - * Anyway, this returns a gross estimate of the "rank" of "category" for our fish weight, based on how - * weight generaly scales up (250, 500, 1000, 2000, 4000 etc...) - */ -/obj/item/fish/proc/get_weight_rank() - return max(round(1 + log(2, weight/FISH_WEIGHT_FORCE_DIVISOR), 1), 1) - ///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) @@ -619,7 +612,7 @@ bare_wound_bonus = initial(bare_wound_bonus) toolspeed = initial(toolspeed) - var/weight_rank = get_weight_rank() + var/weight_rank = GET_FISH_WEIGHT_RANK(weight) throw_range -= weight_rank get_force_rank() @@ -828,16 +821,13 @@ ///Proc that should be called when the fish is fed. By default, it grows the fish depending on various variables. /obj/item/fish/proc/sate_hunger() - if(isaquarium(loc)) - var/obj/structure/aquarium/aquarium = loc - if(!aquarium.reproduction_and_growth) - last_feeding = world.time - return + if(HAS_TRAIT(loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH)) + last_feeding = world.time + return var/hunger = get_hunger() if(hunger < 0.05) //don't bother growing for very small amounts. last_feeding = world.time return - last_feeding = world.time var/new_size = size var/new_weight = weight var/hunger_mult @@ -854,7 +844,7 @@ new_size += CEILING((maximum_size - size) * base_mult / (w_class * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1) new_size = min(new_size, maximum_size) if(weight < maximum_weight) - new_weight += CEILING((maximum_weight - weight) * base_mult / (get_weight_rank() * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1) + new_weight += CEILING((maximum_weight - weight) * base_mult / (GET_FISH_WEIGHT_RANK(weight) * FISH_SIZE_WEIGHT_GROWTH_MALUS) * hunger_mult, 1) new_weight = min(new_weight, maximum_weight) if(new_size != size || new_weight != weight) update_size_and_weight(new_size, new_weight) @@ -871,9 +861,8 @@ 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 + var/should_be_flopping = status == FISH_ALIVE && !HAS_TRAIT(src, TRAIT_FISH_STASIS) && loc && HAS_TRAIT(loc, TRAIT_IS_AQUARIUM) if(should_be_flopping) start_flopping() @@ -912,7 +901,7 @@ stop_flopping() if(!silent) var/message = span_notice(replacetext(death_text, "%SRC", "[src]")) - if(isaquarium(loc)) + if(loc && HAS_TRAIT(loc, TRAIT_IS_AQUARIUM)) loc.visible_message(message) else visible_message(message) @@ -997,47 +986,44 @@ visual.icon_state = dedicated_in_aquarium_icon_state || "[initial(icon_state)]_small" visual.color = aquarium_vc_color -/obj/item/fish/proc/randomize_aquarium_position(datum/source, obj/structure/aquarium/current_aquarium, obj/effect/aquarium/visual) +/obj/item/fish/proc/randomize_aquarium_position(datum/source, atom/movable/current_aquarium, obj/effect/aquarium/visual) SIGNAL_HANDLER - var/list/aq_properties = current_aquarium.get_surface_properties() var/avg_width = round(sprite_width * 0.5) var/avg_height = round(sprite_height * 0.5) - 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_height - 16 + var/px_min = visual.aquarium_zone_min_px + avg_width - 16 + var/px_max = visual.aquarium_zone_max_px - avg_width - 16 + var/py_min = visual.aquarium_zone_min_py + avg_height - 16 + var/py_max = visual.aquarium_zone_max_py - avg_height - 16 visual.pixel_x = visual.base_pixel_x = rand(px_min,px_max) visual.pixel_y = visual.base_pixel_y = rand(py_min,py_max) -/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 - -/obj/item/fish/proc/update_aquarium_animation(datum/source, current_animation, obj/structure/current_aquarium, obj/effect/visual) +/obj/item/fish/proc/update_aquarium_animation(datum/source, current_animation, obj/effect/visual, fluid_type) SIGNAL_HANDLER - var/animation = get_aquarium_animation() + var/animation = get_aquarium_animation(fluid_type) if(animation == current_animation) return switch(animation) if(AQUARIUM_ANIMATION_FISH_SWIM) - swim_animation(current_aquarium, visual) + swim_animation(visual) if(AQUARIUM_ANIMATION_FISH_DEAD) - dead_animation(current_aquarium, visual) + dead_animation(visual) + +/obj/item/fish/proc/get_aquarium_animation(fluid_type) + if(fluid_type == AQUARIUM_FLUID_AIR || status == FISH_DEAD) + return AQUARIUM_ANIMATION_FISH_DEAD + else + return AQUARIUM_ANIMATION_FISH_SWIM /// Create looping random path animation, pixel offsets parameters include offsets already -/obj/item/fish/proc/swim_animation(obj/structure/aquarium/current_aquarium, obj/effect/aquarium/visual) +/obj/item/fish/proc/swim_animation(obj/effect/aquarium/visual) 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 - 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 + var/px_min = visual.aquarium_zone_min_px + avg_width - 16 + var/px_max = visual.aquarium_zone_max_px - avg_width - 16 + var/py_min = visual.aquarium_zone_min_py + avg_height - 16 + var/py_max = visual.aquarium_zone_max_py - avg_width - 16 var/origin_x = visual.base_pixel_x var/origin_y = visual.base_pixel_y @@ -1062,22 +1048,36 @@ animate(transform = dir_mx, time = 0, loop = -1) animate(pixel_x = target_x, pixel_y = target_y, time = eyeballed_time, loop = -1) -/obj/item/fish/proc/dead_animation(obj/structure/aquarium/current_aquarium, obj/effect/aquarium/visual) +/obj/item/fish/proc/dead_animation(obj/effect/aquarium/visual) //Set base_pixel_y to lowest possible value 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 + var/py_min = visual.aquarium_zone_min_py + avg_height - 16 visual.base_pixel_y = py_min animate(visual, pixel_y = py_min, time = 1) //flop to bottom and end current animation. +///Malus to the beauty value if the fish content is dead +#define DEAD_FISH_BEAUTY -500 +///Prevents more impressive fishes from providing a positive beauty even when dead. +#define MAX_DEAD_FISH_BEAUTY -200 +///Some fish are already so ugly, they can't get much worse when dead +#define MIN_DEAD_FISH_BEAUTY -600 + +/obj/item/fish/proc/get_aquarium_beauty(datum/source, list/beauty_holder) + SIGNAL_HANDLER + var/actual_beauty = beauty + if(status == FISH_DEAD) + actual_beauty = clamp(beauty + DEAD_FISH_BEAUTY, MIN_DEAD_FISH_BEAUTY, MAX_DEAD_FISH_BEAUTY) + + beauty_holder += actual_beauty + +#undef DEAD_FISH_BEAUTY +#undef MIN_DEAD_FISH_BEAUTY +#undef MAX_DEAD_FISH_BEAUTY + /// Checks if our current environment lets us live. /obj/item/fish/proc/proper_environment(temp_range_min = required_temperature_min, temp_range_max = required_temperature_max) - 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)) + if(loc && HAS_TRAIT(loc, TRAIT_IS_AQUARIUM)) + if(!(fish_flags & FISH_FLAG_SAFE_TEMPERATURE) || !(fish_flags & FISH_FLAG_SAFE_FLUID)) return FALSE return TRUE @@ -1131,54 +1131,48 @@ bites_amount -= amount generate_fish_reagents(amount) +/// Returns tracked_fish_by_type but flattened and without the items in the blacklist, also shuffled if shuffle is TRUE. +/obj/item/fish/proc/get_aquarium_fishes(shuffle = FALSE, blacklist) + . = list() + for(var/obj/item/fish/fish in loc) + . += fish + . -= blacklist + if(shuffle) + . = shuffle(.) + return . + /obj/item/fish/proc/ready_to_reproduce(being_targeted = FALSE) - var/obj/structure/aquarium/aquarium = loc - if(!istype(aquarium)) + if(!loc || !HAS_TRAIT(loc, TRAIT_IS_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) + if(!being_targeted && length(get_aquarium_fishes()) >= AQUARIUM_MAX_BREEDING_POPULATION) return FALSE - return aquarium.reproduction_and_growth && health >= initial(health) * 0.8 && stable_population >= 1 && world.time >= breeding_wait + return !HAS_TRAIT(loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH) && 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)) + if(!loc || !HAS_TRAIT(loc, TRAIT_IS_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. - */ + ///Fishes with this trait cannot mate, but could still reproduce asexually, so don't early return. 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(type) - 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. + SEND_SIGNAL(loc, COMSIG_AQUARIUM_GET_REPRODUCTION_CANDIDATES, src, available_fishes) if(length(available_fishes)) - for(var/obj/item/fish/other_fish as anything in shuffle(available_fishes)) + //make sure we check if the fish can reproduce with itself last, since that should've lower priority + available_fishes = shuffle(available_fishes) - src + available_fishes += src + for(var/obj/item/fish/other_fish as anything in available_fishes) if(other_fish.ready_to_reproduce(TRUE)) second_fish = other_fish break - if(!second_fish) + if(!second_fish || second_fish == src) //check if the fish can self-reproduce in these cases. if(!HAS_TRAIT(src, TRAIT_FISH_SELF_REPRODUCE)) return FALSE - if(length(aquarium.tracked_fish_by_type[type]) >= stable_population) - return FALSE + second_fish = null //set it to null, since this will make the following operations a bit easier if(PERFORM_ALL_TESTS(fish_breeding) && second_fish && !length(evolution_types)) return create_offspring(second_fish.type, second_fish) @@ -1188,13 +1182,13 @@ 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)) + if(evolution.check_conditions(src, second_fish, loc)) 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)) + if(evolution.check_conditions(second_fish, src, loc)) possible_evolutions += evolution var/list/types = spawn_types || list(type) @@ -1205,7 +1199,7 @@ var/list/second_fish_types = second_fish.spawn_types || list(second_fish.type) 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(fish_flags & FISH_FLAG_OVERPOPULATED) if(recessive_partner && !recessive) return FALSE chosen_type = pick(second_fish_types) @@ -1335,7 +1329,7 @@ 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)) + if(HAS_TRAIT(loc, TRAIT_BIOELECTRIC_GENERATOR)) fish_zap_range = 5 fish_zap_power = GET_FISH_ELECTROGENESIS(src) if(HAS_TRAIT(src, TRAIT_FISH_ON_TESLIUM)) @@ -1378,13 +1372,13 @@ happiness_value++ if(get_hunger() < 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++ + if(loc && HAS_TRAIT(loc, TRAIT_IS_AQUARIUM)) + if(fish_flags & FISH_FLAG_SAFE_FLUID) + happiness_value++ + if(fish_flags & FISH_FLAG_SAFE_TEMPERATURE) + happiness_value++ + else if(proper_environment()) + happiness_value += 2 if(bites_amount) // ouch happiness_value -= 2 if(health < initial(health) * 0.6) @@ -1396,7 +1390,7 @@ pet_fish(user) /obj/item/fish/proc/pet_fish(mob/living/user) - var/in_aquarium = isaquarium(loc) + var/in_aquarium = loc && HAS_TRAIT(loc, TRAIT_IS_AQUARIUM) if(status == FISH_DEAD) to_chat(user, span_warning("You try to pet [src], but [p_theyre()] motionless!")) return FALSE diff --git a/code/modules/fishing/fish/fish_evolution.dm b/code/modules/fishing/fish/fish_evolution.dm index a9261b091c9..8dd9bb0ffcd 100644 --- a/code/modules/fishing/fish/fish_evolution.dm +++ b/code/modules/fishing/fish/fish_evolution.dm @@ -43,20 +43,14 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) * Keep in mind the mate and aquarium arguments may be null if * the fish is self-reproducing or this evolution is a result of a fish_growth component */ -/datum/fish_evolution/proc/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) +/datum/fish_evolution/proc/check_conditions(obj/item/fish/source, obj/item/fish/mate, atom/movable/aquarium) SHOULD_CALL_PARENT(TRUE) - if(aquarium) - //chances are halved if only one parent has this evolution. - var/real_probability = (mate && (type in mate.evolution_types)) ? probability : probability/2 - if(HAS_TRAIT(source, TRAIT_FISH_MUTAGENIC) || (mate && HAS_TRAIT(mate, TRAIT_FISH_MUTAGENIC))) - real_probability *= 3 - if(!prob(real_probability)) - return FALSE - if(!ISINRANGE(aquarium.fluid_temp, required_temperature_min, required_temperature_max)) - return FALSE - else if(!source.proper_environment(required_temperature_min, required_temperature_max)) - return FALSE - return TRUE + //the fish don't reproduce outside of aquariums but can still grow there, so we just check if the temperature is right. + if(!aquarium) + return source.proper_environment(required_temperature_min, required_temperature_max) + if(SEND_SIGNAL(aquarium, COMSIG_AQUARIUM_CHECK_EVOLUTION_CONDITIONS, source, mate, src) & COMPONENT_ALLOW_EVOLUTION) + return TRUE + return FALSE ///This is called when the evolution is set as the result type of a fish_growth component /datum/fish_evolution/proc/growth_checks(obj/item/fish/source, seconds_per_tick, growth) @@ -66,20 +60,17 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) return COMPONENT_DONT_GROW if(source.get_hunger() >= 0.5) //too hungry to grow return COMPONENT_DONT_GROW - var/obj/structure/aquarium/aquarium = source.loc - if(istype(aquarium) && !aquarium.reproduction_and_growth) //the aquarium has breeding disabled + if(HAS_TRAIT(source.loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH)) //the aquarium has breeding disabled return COMPONENT_DONT_GROW - else - aquarium = null - if(!check_conditions(source, aquarium = aquarium)) + if(!check_conditions(source)) return COMPONENT_DONT_GROW ///Called by the fish analyzer right click function. Returns a text string used as tooltip. /datum/fish_evolution/proc/get_evolution_tooltip() . = "" if(required_temperature_min > 0 || required_temperature_max < INFINITY) - var/max_temp = required_temperature_max < INFINITY ? " and [required_temperature_max]" : "" - . = "An aquarium temperature between [required_temperature_min][max_temp] is required." + var/max_temp = required_temperature_max < INFINITY ? " to [required_temperature_max]" : "" + . = "An aquarium temperature of [required_temperature_min][max_temp] is required." if(conditions_note) . += " [conditions_note]" return . @@ -90,7 +81,7 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) new_traits = list(/datum/fish_trait/lubed) conditions_note = "The fish must be fed lube beforehand." -/datum/fish_evolution/lubefish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) +/datum/fish_evolution/lubefish/check_conditions(obj/item/fish/source, obj/item/fish/mate, atom/movable/aquarium) if(!HAS_TRAIT(source, TRAIT_FISH_FED_LUBE)) return FALSE return ..() @@ -109,7 +100,7 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) conditions_note = "The fish (and its mate) needs to be unusually big both in size and weight." show_result_on_wiki = FALSE -/datum/fish_evolution/mastodon/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) +/datum/fish_evolution/mastodon/check_conditions(obj/item/fish/source, obj/item/fish/mate, atom/movable/aquarium) if((source.size < 120 || source.weight < 3000) || (mate && (mate.size < 120 || mate.weight < 3000))) return FALSE return ..() @@ -137,7 +128,7 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) new_traits = list(/datum/fish_trait/predator, /datum/fish_trait/aggressive) conditions_note = "The fish needs to be unusually big and aggressive" -/datum/fish_evolution/chainsawfish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) +/datum/fish_evolution/chainsawfish/check_conditions(obj/item/fish/source, obj/item/fish/mate, atom/movable/aquarium) var/double_avg_size = /obj/item/fish/goldfish::average_size * 2 var/double_avg_weight = /obj/item/fish/goldfish::average_weight * 2 if(source.size >= double_avg_size && source.weight >= double_avg_weight && (/datum/fish_trait/aggressive in source.fish_traits)) @@ -149,7 +140,7 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) new_fish_type = /obj/item/fish/pike/armored conditions_note = "The fish needs to have the stinger trait" -/datum/fish_evolution/armored_pike/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) +/datum/fish_evolution/armored_pike/check_conditions(obj/item/fish/source, obj/item/fish/mate, atom/movable/aquarium) if(HAS_TRAIT(source, TRAIT_FISH_STINGER)) return ..() return FALSE @@ -165,7 +156,7 @@ GLOBAL_LIST_EMPTY(fishes_by_fish_evolution) conditions_note = "The final stage of fritterfish growth. It gotta be big!" show_result_on_wiki = FALSE -/datum/fish_evolution/nessiefish/check_conditions(obj/item/fish/source, obj/item/fish/mate, obj/structure/aquarium/aquarium) +/datum/fish_evolution/nessiefish/check_conditions(obj/item/fish/source, obj/item/fish/mate, atom/movable/aquarium) if(source.size >= (/obj/item/fish/fryish/fritterish::average_size * 1.5) && source.size >= (/obj/item/fish/fryish/fritterish::average_weight * 1.5)) return ..() return FALSE diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm index dd0edb7ca3d..aa045c6308b 100644 --- a/code/modules/fishing/fish/fish_traits.dm +++ b/code/modules/fishing/fish/fish_traits.dm @@ -184,7 +184,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/nocturnal/proc/check_light(obj/item/fish/source, seconds_per_tick) SIGNAL_HANDLER - if(isturf(source.loc) || isaquarium(source)) + if(source.loc && (HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM) || isturf(source.loc))) var/turf/turf = get_turf(source) var/light_amount = turf.get_lumcount() if(light_amount > SHADOW_SPECIES_LIGHT_THRESHOLD) @@ -293,7 +293,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/emulsijack/proc/emulsify(obj/item/fish/source, seconds_per_tick) SIGNAL_HANDLER - if(!isaquarium(source.loc)) + if(!source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM)) return var/emulsified = FALSE for(var/obj/item/fish/victim in source.loc) @@ -332,7 +332,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/necrophage/proc/eat_dead_fishes(obj/item/fish/source, seconds_per_tick) SIGNAL_HANDLER - if(source.get_hunger() > 0.75 || !isaquarium(source.loc)) + if(source.get_hunger() > 0.75 || !source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM)) return for(var/obj/item/fish/victim in source.loc) if(victim.status != FISH_DEAD || victim == source || HAS_TRAIT(victim, TRAIT_YUCKY_FISH)) @@ -400,7 +400,7 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) return source.set_status(FISH_ALIVE) var/message = span_nicegreen("[source] twitches. It's alive!") - if(isaquarium(source.loc)) + if(source.loc && HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM)) source.loc.visible_message(message) else source.visible_message(message) @@ -425,10 +425,9 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/predator/proc/eat_fishes(obj/item/fish/source, seconds_per_tick) SIGNAL_HANDLER - if(source.get_hunger() > 0.75 || !isaquarium(source.loc)) + if(source.get_hunger() > 0.75 || !source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM)) return - var/obj/structure/aquarium/aquarium = source.loc - for(var/obj/item/fish/victim in aquarium.get_fishes(TRUE, source)) + for(var/obj/item/fish/victim as anything in source.get_aquarium_fishes(TRUE, source)) if(victim.size < source.size * 0.7) // It's a big fish eat small fish world continue if(victim.status != FISH_ALIVE || victim == source || HAS_TRAIT(victim, TRAIT_YUCKY_FISH) || SPT_PROB(80, seconds_per_tick)) @@ -531,13 +530,12 @@ GLOBAL_LIST_INIT(spontaneous_fish_traits, populate_spontaneous_fish_traits()) /datum/fish_trait/aggressive/proc/try_attack_fish(obj/item/fish/source, seconds_per_tick) SIGNAL_HANDLER - if(!isaquarium(source.loc) || !SPT_PROB(1, seconds_per_tick)) + if(!source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM) || !SPT_PROB(1, seconds_per_tick)) return - var/obj/structure/aquarium/aquarium = source.loc - for(var/obj/item/fish/victim in aquarium.get_fishes(TRUE, source)) + for(var/obj/item/fish/victim as anything in source.get_aquarium_fishes(TRUE, source)) if(victim.status != FISH_ALIVE) continue - aquarium.visible_message(span_warning("[source] violently [pick("whips", "bites", "attacks", "slams")] [victim]")) + source.loc.visible_message(span_warning("[source] violently [pick("whips", "bites", "attacks", "slams")] [victim]")) var/damage = round(rand(4, 20) * (source.size / victim.size)) //smaller fishes take extra damage. victim.adjust_health(victim.health - damage) return diff --git a/code/modules/fishing/fish/types/air_space.dm b/code/modules/fishing/fish/types/air_space.dm index c60fc7a36ef..3b053dc4c25 100644 --- a/code/modules/fishing/fish/types/air_space.dm +++ b/code/modules/fishing/fish/types/air_space.dm @@ -173,13 +173,12 @@ if(health <= initial(health) * 0.6 || hunger >= 0.6) //if too hurt or hungry, don't grow. return COMPONENT_DONT_GROW - if(!isaquarium(loc)) + if(!loc || !HAS_TRAIT(loc, TRAIT_IS_AQUARIUM)) return - var/obj/structure/aquarium/aquarium = loc - if(!aquarium.reproduction_and_growth) //the aquarium has breeding disabled + if(HAS_TRAIT(loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH)) //the aquarium has breeding disabled return COMPONENT_DONT_GROW - if(length(aquarium.get_fishes()) > AQUARIUM_MAX_BREEDING_POPULATION * 0.5) //check if there's enough room to maturate. + if(length(get_aquarium_fishes()) > AQUARIUM_MAX_BREEDING_POPULATION * 0.5) //check if there's enough room to maturate. return COMPONENT_DONT_GROW /obj/item/fish/baby_carp/proc/on_growth(datum/source, mob/living/basic/carp/result) diff --git a/code/modules/fishing/fish/types/freshwater.dm b/code/modules/fishing/fish/types/freshwater.dm index 660a406183c..16687bc6497 100644 --- a/code/modules/fishing/fish/types/freshwater.dm +++ b/code/modules/fishing/fish/types/freshwater.dm @@ -201,8 +201,7 @@ var/hunger = get_hunger() if(hunger >= 0.7) //too hungry to grow return COMPONENT_DONT_GROW - var/obj/structure/aquarium/aquarium = loc - if(istype(aquarium) && !aquarium.reproduction_and_growth) //the aquarium has breeding disabled + if(HAS_TRAIT(loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH)) //the aquarium has breeding disabled return COMPONENT_DONT_GROW /obj/item/fish/tadpole/proc/on_growth(datum/source, mob/living/basic/frog/result) diff --git a/code/modules/fishing/fish/types/mining.dm b/code/modules/fishing/fish/types/mining.dm index 53c1b89673c..3a1d6ff8310 100644 --- a/code/modules/fishing/fish/types/mining.dm +++ b/code/modules/fishing/fish/types/mining.dm @@ -78,15 +78,14 @@ if(hunger >= 0.4) //I'm hungry and angry anger += growth * 0.6 - if(!isaquarium(loc)) + if(!loc || !HAS_TRAIT(loc, TRAIT_IS_AQUARIUM)) return - var/obj/structure/aquarium/aquarium = loc - if(!aquarium.reproduction_and_growth) //the aquarium has breeding disabled + if(HAS_TRAIT(loc, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH)) //the aquarium has breeding disabled return COMPONENT_DONT_GROW - if(!locate(/obj/item/aquarium_prop) in aquarium) //the aquarium deco is quite barren + if(!locate(/obj/item/aquarium_prop) in loc) //the aquarium deco is quite barren anger += growth * 0.25 - var/fish_count = length(aquarium.get_fishes()) + var/fish_count = length(get_aquarium_fishes()) if(!ISINRANGE(fish_count, 3, AQUARIUM_MAX_BREEDING_POPULATION * 0.5)) //too lonely or overcrowded anger += growth * 0.3 if(fish_count > AQUARIUM_MAX_BREEDING_POPULATION * 0.5) //check if there's enough room to maturate. diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index 3aaa99e5ae0..56764318f1b 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -1046,6 +1046,18 @@ ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE +/datum/design/bluespace_fish_tank + name = "Bluespace Fish Tank Kit" + desc = "The required components to upgrade the capacity of a fish tank." + id = "bluespace_fish_tank_kit" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 3, /datum/material/titanium = SMALL_MATERIAL_AMOUNT, /datum/material/bluespace = SMALL_MATERIAL_AMOUNT * 3) + build_path = /obj/item/aquarium_upgrade/bluespace_tank + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_SERVICE, + ) + departmental_flags = DEPARTMENT_BITFLAG_SERVICE | DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE + // Coffeemaker Stuff /datum/design/coffeepot diff --git a/code/modules/research/designs/power_designs.dm b/code/modules/research/designs/power_designs.dm index deb2256ff10..38edc5b5593 100644 --- a/code/modules/research/designs/power_designs.dm +++ b/code/modules/research/designs/power_designs.dm @@ -167,10 +167,12 @@ ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING -/datum/design/board/bioelec_gen +/datum/design/bioelec_gen name = "Aquarium Bioelectricity Kit" desc = "The required components to convert an aquarium into a bioelectricity generator." id = "bioelec_gen" + build_type = PROTOLATHE | AWAY_LATHE + materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 5, /datum/material/gold = SMALL_MATERIAL_AMOUNT) build_path = /obj/item/aquarium_upgrade/bioelec_gen category = list( RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING, diff --git a/code/modules/research/techweb/nodes/service_nodes.dm b/code/modules/research/techweb/nodes/service_nodes.dm index a2f6b030071..824cd611b83 100644 --- a/code/modules/research/techweb/nodes/service_nodes.dm +++ b/code/modules/research/techweb/nodes/service_nodes.dm @@ -174,6 +174,7 @@ "auto_reel", "fish_analyzer", "bluespace_fish_case", + "bluespace_fish_tank_kit", ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_2_POINTS) required_experiments = list(/datum/experiment/scanning/fish) diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm index f609e73c4b7..1fae3982510 100644 --- a/code/modules/unit_tests/component_tests.dm +++ b/code/modules/unit_tests/component_tests.dm @@ -1,12 +1,7 @@ /datum/unit_test/component_duping/Run() var/list/bad_dms = list() - var/list/bad_dts = list() for(var/t in typesof(/datum/component)) var/datum/component/comp = t if(!isnum(initial(comp.dupe_mode))) bad_dms += t - var/dupe_type = initial(comp.dupe_type) - if(dupe_type && !ispath(dupe_type)) - bad_dts += t - TEST_ASSERT(!length(bad_dms) && !length(bad_dts), - "Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])") + TEST_ASSERT(!length(bad_dms), "Components with invalid dupe modes: ([bad_dms.Join(",")])") diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm index 94337ae2cc2..43f06ad11e5 100644 --- a/code/modules/unit_tests/fish_unit_tests.dm +++ b/code/modules/unit_tests/fish_unit_tests.dm @@ -128,7 +128,6 @@ description = "It smells fishy." /obj/structure/aquarium/traits - reproduction_and_growth = TRUE var/obj/item/fish/testdummy/crossbreeder/crossbreeder var/obj/item/fish/testdummy/cloner/cloner var/obj/item/fish/testdummy/sterile/sterile @@ -155,7 +154,6 @@ fish_traits = list(/datum/fish_trait/no_mating) /obj/structure/aquarium/evolution - reproduction_and_growth = TRUE var/obj/item/fish/testdummy/evolve/evolve var/obj/item/fish/testdummy/evolve_two/evolve_two @@ -195,7 +193,7 @@ ///During the fish_growth unit test, we spawn a fish outside of the aquarium and check that this actually stops it from growing /datum/fish_evolution/dummy/two/growth_checks(obj/item/fish/source, seconds_per_tick, growth) . = ..() - if(!isaquarium(source.loc)) + if(!source.loc || !HAS_TRAIT(source.loc, TRAIT_IS_AQUARIUM)) return COMPONENT_DONT_GROW ///A test that checks that fishing portals can be linked and function as expected @@ -353,7 +351,6 @@ TEST_ASSERT(dummy_boogaloo, "The new fish type cannot be found inside the aquarium") /obj/structure/aquarium/crab - reproduction_and_growth = TRUE //needed for growing up ///Our test subject var/obj/item/fish/chasm_crab/instant_growth/crabbie @@ -489,5 +486,36 @@ for(var/obj/item/fish/fish as anything in box) fish.randomize_size_and_weight() +/datum/unit_test/aquarium_upgrade + +/datum/unit_test/aquarium_upgrade/Run() + var/mob/living/carbon/human/dummy/user = allocate(__IMPLIED_TYPE__) + var/obj/item/aquarium_upgrade/bioelec_gen/upgrade = allocate(__IMPLIED_TYPE__) + var/obj/structure/aquarium/aquarium = allocate(upgrade::upgrade_from_type) + + var/datum/component/aquarium/comp = aquarium.GetComponent(__IMPLIED_TYPE__) + TEST_ASSERT(comp, "[aquarium.type] doesn't have an aquarium component") + comp.set_fluid_type(AQUARIUM_FLUID_AIR) + comp.fluid_temp = MAX_AQUARIUM_TEMP + aquarium.add_traits(list(TRAIT_AQUARIUM_PANEL_OPEN, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH), AQUARIUM_TRAIT) + + var/type_to_check = upgrade::upgrade_to_type + var/turf/aquarium_loc = aquarium.loc + user.put_in_hands(upgrade) + upgrade.melee_attack_chain(user, aquarium) + TEST_ASSERT(QDELETED(aquarium), "Old [aquarium.type] was not deleted after upgrade") + + var/obj/structure/aquarium/upgraded_aquarium = locate(type_to_check) in aquarium_loc + TEST_ASSERT(upgraded_aquarium, "New [upgraded_aquarium.type] was not spawned after upgrade") + comp = upgraded_aquarium.GetComponent(/datum/component/aquarium) + TEST_ASSERT(comp, "New [upgraded_aquarium.type] doesn't have an aquarium component") + + TEST_ASSERT_EQUAL(comp.fluid_type, AQUARIUM_FLUID_AIR, "Inherited aquarium fluid type should be [AQUARIUM_FLUID_AIR]") + TEST_ASSERT_EQUAL(comp.fluid_temp, MAX_AQUARIUM_TEMP, "Inherited aquarium fluid temperature should be [MAX_AQUARIUM_TEMP]") + TEST_ASSERT(HAS_TRAIT(upgraded_aquarium, TRAIT_AQUARIUM_PANEL_OPEN), "The new aquarium should have its panel open") + TEST_ASSERT(HAS_TRAIT(upgraded_aquarium, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH), "The 'growth and reproduction' setting for this aquarium should be disabled") + + TEST_ASSERT(QDELETED(upgrade), "Aquarium upgrade wasn't deleted afterward") + #undef FISH_REAGENT_AMOUNT #undef TRAIT_FISH_TESTING diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm index 27d2afa88e5..a5f2c84920d 100644 --- a/code/modules/vending/games.dm +++ b/code/modules/vending/games.dm @@ -57,6 +57,7 @@ /obj/item/reagent_containers/cup/fish_feed = 4, /obj/item/fish_analyzer = 2, /obj/item/fishing_rod/telescopic = 1, + /obj/item/fish_tank = 1, ), ), list( diff --git a/icons/obj/aquarium/supplies.dmi b/icons/obj/aquarium/supplies.dmi index 08f4923a0ab..73a1f5ffe34 100644 Binary files a/icons/obj/aquarium/supplies.dmi and b/icons/obj/aquarium/supplies.dmi differ diff --git a/icons/obj/aquarium/tanks.dmi b/icons/obj/aquarium/tanks.dmi index 44f526f8c99..b4a5e06471f 100644 Binary files a/icons/obj/aquarium/tanks.dmi and b/icons/obj/aquarium/tanks.dmi differ diff --git a/icons/obj/fishing.dmi b/icons/obj/fishing.dmi index d97384f1f79..ac307ae2384 100644 Binary files a/icons/obj/fishing.dmi and b/icons/obj/fishing.dmi differ diff --git a/icons/obj/food/lizard.dmi b/icons/obj/food/lizard.dmi index 9e60c814a2e..44dc43e84de 100644 Binary files a/icons/obj/food/lizard.dmi and b/icons/obj/food/lizard.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 12698d296b1..51f540d2c85 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1046,6 +1046,7 @@ #include "code\datums\components\amputating_limbs.dm" #include "code\datums\components\anti_magic.dm" #include "code\datums\components\appearance_on_aggro.dm" +#include "code\datums\components\aquarium.dm" #include "code\datums\components\aquarium_content.dm" #include "code\datums\components\area_based_godmode.dm" #include "code\datums\components\area_sound_manager.dm" diff --git a/tools/UpdatePaths/Scripts/87866_fish_tanks.txt b/tools/UpdatePaths/Scripts/87866_fish_tanks.txt new file mode 100644 index 00000000000..a0762acacf2 --- /dev/null +++ b/tools/UpdatePaths/Scripts/87866_fish_tanks.txt @@ -0,0 +1 @@ +/obj/structure/aquarium/lawyer : /obj/item/fish_tank/lawyer