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