diff --git a/code/__DEFINES/fish.dm b/code/__DEFINES/fish.dm
index 3824c3f1ab7..8125013f88e 100644
--- a/code/__DEFINES/fish.dm
+++ b/code/__DEFINES/fish.dm
@@ -186,6 +186,9 @@
///The last feeding timestamp of newly instantiated fish is multiplied by this: ergo, they spawn 50% hungry.
#define NEW_FISH_LAST_FEEDING_MULT 0.33
+///If get_hunger is above this value, the fish is considered to be starving and will slowly lose health because of it
+#define FISH_STARVING_THRESHOLD 1
+
//IF YOU ADD ANY NEW FLAG, ADD IT TO THE RESPECTIVE BITFIELD in _globalvars/bitfields.dm TOO!
///This fish is shown in the catalog and on the wiki (this only matters as an initial, compile-time value)
@@ -227,6 +230,13 @@
///Fluff. The name of the aquarium company shown in the fish catalog
#define AQUARIUM_COMPANY "Aquatech Ltd."
+///aquarium mode where you've to manage food, fluid type and temperature to keep fish alive
+#define AQUARIUM_MODE_MANUAL "Manual"
+///Aquarium mode where the code tries to determine what fluid type and temperature to use. Default.
+#define AQUARIUM_MODE_AUTO "Assisted"
+///Prevents fish from dying because of temp/fluid settings and starvation but stops growth.
+#define AQUARIUM_MODE_SAFE "Stasis"
+
/// how long between electrogenesis zaps
#define ELECTROGENESIS_DURATION 40 SECONDS
/// a random range the electrogenesis cooldown varies by
diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm
index ead1b5cbff5..66de92f17fd 100644
--- a/code/__HELPERS/cmp.dm
+++ b/code/__HELPERS/cmp.dm
@@ -24,6 +24,12 @@
b = REF(b)
return sorttext("[a]", "[b]")
+/proc/cmp_list_len_asc(list/a, list/b)
+ return length(a) - length(b)
+
+/proc/cmp_list_len_dsc(list/a, list/b)
+ return length(b) - length(a)
+
/proc/cmp_name_asc(atom/a, atom/b)
return sorttext(b.name, a.name)
diff --git a/code/datums/components/aquarium.dm b/code/datums/components/aquarium.dm
index e417b5f54fc..576852a471e 100644
--- a/code/datums/components/aquarium.dm
+++ b/code/datums/components/aquarium.dm
@@ -52,10 +52,19 @@
AQUARIUM_FLUID_SULPHWATEVER,
AQUARIUM_FLUID_AIR,
)
+ ///static list of available aquarium modes
+ var/static/list/aquarium_modes = list(
+ AQUARIUM_MODE_MANUAL = "Take full control of fluid and temperature settings. There's no stasis option here.",
+ AQUARIUM_MODE_AUTO = "Let an internal processor regulate aquarium settings based on its fish population. Can temporarily fall back to stasis.",
+ AQUARIUM_MODE_SAFE = "Prevent fish from dying from wrong fluid/temperature settings and hunger, but also stops growth and reproduction",
+ )
///The size of the reagents holder which will store fish feed.
var/reagents_size
+ ///The current aquarium mode, one of either AQUARIUM_MODE_MANUAL, AQUARIUM_MODE_AUTO or AQUARIUM_MODE_SAFE.
+ var/current_mode
+
/datum/component/aquarium/Initialize(
min_px,
max_px,
@@ -65,6 +74,7 @@
reagents_size = 6,
min_fluid_temp = MIN_AQUARIUM_TEMP,
max_fluid_temp = MAX_AQUARIUM_TEMP,
+ init_mode = AQUARIUM_MODE_AUTO,
)
if(!ismovable(parent))
@@ -82,6 +92,8 @@
src.max_fluid_temp = max_fluid_temp
fluid_temp = clamp(fluid_temp, min_fluid_temp, max_fluid_temp)
+ set_aquarium_mode(init_mode)
+
/datum/component/aquarium/RegisterWithParent()
if(default_beauty)
update_aquarium_beauty(0)
@@ -169,9 +181,12 @@
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
+ var/old_mode = current_mode //Make sure we don't keep updating temp/fluid on AQUARIUM_MODE_AUTO for this.
+ current_mode = NONE
for(var/atom/movable/moving as anything in movable.contents)
if(HAS_TRAIT(moving, TRAIT_AQUARIUM_CONTENT))
moving.forceMove(new_parent)
+ current_mode = old_mode
if(reagents_size)
if(!new_parent.reagents)
new_parent.create_reagents(reagents_size, SEALED_CONTAINER)
@@ -220,8 +235,7 @@
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)
+ feed_fishes(item, fishes)
source.balloon_alert(user, "fed the fish")
return ITEM_INTERACT_SUCCESS
@@ -261,9 +275,13 @@
if(world.time < last_feeding + feeding_interval)
return
last_feeding = world.time
- var/list/fishes = get_fishes()
+ feed_fishes(movable)
+
+/datum/component/aquarium/proc/feed_fishes(atom/movable/movable, list/fishes = get_fishes())
for(var/obj/item/fish/fish as anything in fishes)
fish.feed(movable.reagents)
+ if(current_mode == AQUARIUM_MODE_AUTO && is_fish_population_safe())
+ REMOVE_TRAIT(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
/datum/component/aquarium/proc/on_plunger_act(atom/movable/source, obj/item/plunger/plunger, mob/living/user, reinforced)
SIGNAL_HANDLER
@@ -282,8 +300,9 @@
/datum/component/aquarium/proc/on_examine(atom/movable/source, mob/user, list/examine_list)
SIGNAL_HANDLER
+ examine_list += span_notice("Its temperature and fluid are currently set to [EXAMINE_HINT("[fluid_temp] K")] and [EXAMINE_HINT(fluid_type)].")
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.")
+ examine_list += span_notice("[EXAMINE_HINT("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.")
@@ -315,7 +334,11 @@
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)
+
+ if(current_mode == AQUARIUM_MODE_AUTO && fish.status != FISH_DEAD)
+ get_optimal_aquarium_settings()
+ else
+ 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.
@@ -350,6 +373,8 @@
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))
+ if(current_mode == AQUARIUM_MODE_AUTO && fish.status != FISH_DEAD)
+ get_optimal_aquarium_settings()
///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)
@@ -385,7 +410,7 @@
* 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)))
+ if((fluid_type in GLOB.fish_compatible_fluid_types[fish.required_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
@@ -397,6 +422,8 @@
///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)
+ if(current_mode == AQUARIUM_MODE_AUTO)
+ get_optimal_aquarium_settings()
/datum/component/aquarium/proc/update_aquarium_beauty(old_beauty)
if(QDELETED(parent))
@@ -452,6 +479,99 @@
fishes += tracked_fish_by_type[key]
return fishes
+/datum/component/aquarium/proc/is_fish_population_safe()
+ var/list/fishes = get_fishes()
+ var/alive = 0
+ var/not_safe = 0
+ for(var/obj/item/fish/fishie as anything in fishes)
+ if(fishie.status == FISH_DEAD)
+ continue
+ if(!fishie.proper_environment() || fishie.get_hunger() > FISH_STARVING_THRESHOLD * 0.9)
+ not_safe++
+ alive++
+ return not_safe <= (alive / 2)
+
+/**
+ * Called if the mode is AQUARIUM_MODE_AUTO whenever a fish is added, removed, dies or resurrected.
+ *
+ * This tries to find what the best combination of fluid and temperature. is to ensure
+ * the survival of the highest number of fishes. It also can and will activate stasis if over half of
+ * the fish population is at risk of dying. It doesn't care if the fish is big or small, useful
+ * or not. All fish are equal before the impartial eye of AQUARIUM_MODE_AUTO.
+ */
+/datum/component/aquarium/proc/get_optimal_aquarium_settings()
+ if(!length(tracked_fish_by_type))
+ REMOVE_TRAIT(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
+ return
+
+ var/list/can_survive_with = list()
+ var/list/checked_types = list()
+ var/list/sorted_tracked = sortTim(tracked_fish_by_type.Copy(), GLOBAL_PROC_REF(cmp_list_len_dsc), associative = TRUE)
+ var/list/fish_alive_by_type = list()
+ for(var/obj/item/fish/fish_type as anything in sorted_tracked)
+ var/our_min_temp = fish_type::required_temperature_min
+ var/our_max_temp = fish_type::required_temperature_max
+ var/our_fluid_type = fish_type::required_fluid_type
+
+ checked_types += fish_type
+ LAZYINITLIST(can_survive_with[fish_type])
+
+ var/fish_amount = 0
+ for(var/obj/item/fish/fish_instance as anything in tracked_fish_by_type[fish_type])
+ if(fish_instance.status == FISH_ALIVE)
+ fish_amount++
+
+ if(!fish_amount) //all fishes of this type are dead, skip it.
+ continue
+
+ fish_alive_by_type[fish_type] = fish_amount
+
+ //Check if there's an overlap with the temp/fluid requirements of the other types.
+ for(var/obj/item/fish/enemy_type as anything in (tracked_fish_by_type - checked_types))
+ var/enem_min_temp = initial(enemy_type.required_temperature_min)
+ var/enem_max_temp = initial(enemy_type.required_temperature_max)
+ if(our_min_temp > enem_max_temp || our_max_temp < enem_min_temp)
+ continue
+ var/enem_fluid_type = initial(enemy_type.required_fluid_type)
+ if(!length(GLOB.fish_compatible_fluid_types[our_fluid_type] & GLOB.fish_compatible_fluid_types[enem_fluid_type]))
+ continue
+ can_survive_with[fish_type] |= enemy_type
+ LAZYOR(can_survive_with[enemy_type], fish_type)
+
+ var/list/highest_val_list = list(tracked_fish_by_type[1]) //In case there's only one type of fish in here...
+ var/highest_val = -1
+ for(var/fish_type as anything in can_survive_with)
+ var/list/compatible_types = can_survive_with[fish_type]
+ var/fish_amount = fish_alive_by_type[fish_type]
+ for(var/obj/item/fish/fish_instance as anything in tracked_fish_by_type[fish_type])
+ if(fish_instance.status == FISH_ALIVE)
+ fish_amount++
+ for(var/ally_type in compatible_types)
+ var/val = fish_amount + fish_alive_by_type[ally_type]
+ var/list/common_allies = compatible_types & can_survive_with[ally_type]
+ for(var/third_type in common_allies)
+ val += fish_alive_by_type[third_type]
+ if(val <= highest_val)
+ continue
+ highest_val_list = list(fish_type, ally_type) + common_allies
+ highest_val = val
+
+ var/min_temp = MIN_AQUARIUM_TEMP
+ var/max_temp = MAX_AQUARIUM_TEMP
+ var/list/possible_fluids = fluid_types.Copy()
+ for(var/obj/item/fish/chosen_type as anything in highest_val_list)
+ possible_fluids &= GLOB.fish_compatible_fluid_types[chosen_type::required_fluid_type]
+ min_temp = max(min_temp, chosen_type::required_temperature_min)
+ max_temp = min(max_temp, chosen_type::required_temperature_max)
+
+ fluid_temp = clamp(min_temp + round((max_temp - min_temp) / 2), min_fluid_temp, max_fluid_temp)
+ set_fluid_type(possible_fluids[1])
+
+ if(!is_fish_population_safe())
+ ADD_TRAIT(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
+ else
+ REMOVE_TRAIT(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
+
/datum/component/aquarium/proc/interact(atom/movable/source, mob/user)
SIGNAL_HANDLER
if(HAS_TRAIT(source, TRAIT_AQUARIUM_PANEL_OPEN))
@@ -482,7 +602,9 @@
var/atom/movable/aquarium = parent
.["fluidType"] = fluid_type
.["temperature"] = fluid_temp
- .["safe_mode"] = HAS_TRAIT_FROM(aquarium, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
+ .["lockedFluidTemp"] = current_mode == AQUARIUM_MODE_AUTO
+ .["currentMode"] = current_mode
+ .["currentTooltip"] = aquarium_modes[current_mode]
.["fishData"] = list()
.["feedingInterval"] = feeding_interval / (1 MINUTES)
.["propData"] = list()
@@ -512,6 +634,10 @@
.["maxTemperature"] = max_fluid_temp
.["fluidTypes"] = fluid_types
.["heartIcon"] = 'icons/effects/effects.dmi'
+ var/list/modes_no_assoc = list() //the UI dropdown won't work with assoc lists here
+ for(var/mode in aquarium_modes)
+ modes_no_assoc += mode
+ .["aquariumModes"] = modes_no_assoc
/datum/component/aquarium/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
@@ -531,11 +657,9 @@
if(params["fluid"] != fluid_type && (params["fluid"] in fluid_types))
set_fluid_type(params["fluid"])
. = TRUE
- if("safe_mode")
- if(HAS_TRAIT_FROM(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT))
- REMOVE_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
- else
- ADD_TRAIT(movable, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
+ if("change_mode")
+ var/new_mode = params["new_mode"]
+ set_aquarium_mode(new_mode)
. = TRUE
if("feeding_interval")
feeding_interval = params["feeding_interval"] MINUTES
@@ -554,6 +678,17 @@
return
fish.AddComponent(/datum/component/rename, new_name, fish.desc)
+/datum/component/aquarium/proc/set_aquarium_mode(new_mode)
+ if(new_mode == current_mode)
+ return
+ REMOVE_TRAIT(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
+ current_mode = new_mode
+ switch(current_mode)
+ if(AQUARIUM_MODE_SAFE)
+ ADD_TRAIT(parent, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
+ if(AQUARIUM_MODE_AUTO)
+ get_optimal_aquarium_settings()
+
/datum/component/aquarium/proc/set_fluid_type(new_fluid_type)
var/atom/movable/movable = parent
fluid_type = new_fluid_type
diff --git a/code/modules/cargo/bounties/assistant.dm b/code/modules/cargo/bounties/assistant.dm
index ff48ad6df01..5046627f014 100644
--- a/code/modules/cargo/bounties/assistant.dm
+++ b/code/modules/cargo/bounties/assistant.dm
@@ -280,4 +280,4 @@
description = "We need [LOWER_TEXT(fluid_type)] fish to populate our aquariums with. Fishes that are dead or bought from cargo will only be paid half as much."
/datum/bounty/item/assistant/fish/fluid/can_ship_fish(obj/item/fish/fishie)
- return compatible_fluid_type(fishie.required_fluid_type, fluid_type)
+ return (fluid_type in GLOB.fish_compatible_fluid_types[fishie.required_fluid_type])
diff --git a/code/modules/fishing/aquarium/aquarium.dm b/code/modules/fishing/aquarium/aquarium.dm
index 2b1e190a9b1..e5402acc84e 100644
--- a/code/modules/fishing/aquarium/aquarium.dm
+++ b/code/modules/fishing/aquarium/aquarium.dm
@@ -22,9 +22,12 @@
///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
+ ///The initial mode for the aquarium component
+ var/init_mode = AQUARIUM_MODE_AUTO
+
/obj/structure/aquarium/Initialize(mapload)
. = ..()
- AddComponent(/datum/component/aquarium, aquarium_zone_min_pw, aquarium_zone_max_pw, aquarium_zone_min_pz, aquarium_zone_max_pz, default_beauty)
+ AddComponent(/datum/component/aquarium, aquarium_zone_min_pw, aquarium_zone_max_pw, aquarium_zone_min_pz, aquarium_zone_max_pz, default_beauty, init_mode = init_mode)
AddComponent(/datum/component/plumbing/aquarium, start = anchored)
RegisterSignal(src, COMSIG_AQUARIUM_FLUID_CHANGED, PROC_REF(on_aquarium_liquid_changed))
update_appearance()
@@ -100,12 +103,11 @@
/obj/structure/aquarium/prefilled
anchored = TRUE
+ init_mode = AQUARIUM_MODE_SAFE
/obj/structure/aquarium/prefilled/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) //start with safe mode on
-
new /obj/item/aquarium_prop/sand(src)
new /obj/item/aquarium_prop/seaweed(src)
@@ -151,6 +153,9 @@
///The reagent capacity of this fish tank
var/reagent_size = 4
+ ///The initial mode for the aquarium component
+ var/init_mode = AQUARIUM_MODE_AUTO
+
/obj/item/fish_tank/Initialize(mapload)
. = ..()
update_appearance()
@@ -164,6 +169,7 @@
reagents_size = src.reagent_size,\
min_fluid_temp = src.min_fluid_temp,\
max_fluid_temp = src.max_fluid_temp,\
+ init_mode = init_mode,\
)
AddComponent(/datum/component/plumbing/aquarium, start = anchored)
RegisterSignal(src, COMSIG_AQUARIUM_FLUID_CHANGED, PROC_REF(on_aquarium_liquid_changed))
@@ -244,12 +250,11 @@
///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
+ init_mode = AQUARIUM_MODE_SAFE
/obj/item/fish_tank/lawyer/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT) //start with safe mode on
-
new /obj/item/aquarium_prop/sand(src)
new /obj/item/aquarium_prop/seaweed(src)
diff --git a/code/modules/fishing/fish/_fish.dm b/code/modules/fishing/fish/_fish.dm
index 009af3c5414..fe0a60b7879 100644
--- a/code/modules/fishing/fish/_fish.dm
+++ b/code/modules/fishing/fish/_fish.dm
@@ -6,6 +6,15 @@
#define FISH_SUBMERGING_THRESHOLD 100 SECONDS
#define STARVING_FISH_SUBMERGING_THRESHOLD 20 SECONDS
+GLOBAL_LIST_INIT(fish_compatible_fluid_types, list(
+ AQUARIUM_FLUID_ANY_WATER = list(AQUARIUM_FLUID_SALTWATER, AQUARIUM_FLUID_FRESHWATER, AQUARIUM_FLUID_SULPHWATEVER),
+ AQUARIUM_FLUID_ANADROMOUS = list(AQUARIUM_FLUID_SALTWATER, AQUARIUM_FLUID_FRESHWATER),
+ AQUARIUM_FLUID_SALTWATER = list(AQUARIUM_FLUID_SALTWATER),
+ AQUARIUM_FLUID_FRESHWATER = list(AQUARIUM_FLUID_FRESHWATER),
+ AQUARIUM_FLUID_SULPHWATEVER = list(AQUARIUM_FLUID_SULPHWATEVER),
+ AQUARIUM_FLUID_AIR = list(AQUARIUM_FLUID_AIR),
+))
+
// Fish path used for autogenerated fish
/obj/item/fish
name = "fish"
@@ -863,14 +872,14 @@
check_flopping()
///Returns the value for hunger ranging from 0 to the cap (by default 1)
-/obj/item/fish/proc/get_hunger(cap = 1)
+/obj/item/fish/proc/get_hunger(cap = FISH_STARVING_THRESHOLD)
. = clamp((world.time - last_feeding) / feeding_frequency, 0, cap)
if(HAS_TRAIT(src, TRAIT_FISH_NO_HUNGER))
- return min(., 0.2)
+ return min(., FISH_STARVING_THRESHOLD * 0.2)
/obj/item/fish/proc/get_starvation_mult()
- var/hunger = get_hunger(cap = 2)
- return hunger >= 1 ? hunger : 0
+ var/hunger = get_hunger(cap = FISH_STARVING_THRESHOLD * 2)
+ return hunger >= FISH_STARVING_THRESHOLD ? hunger : 0
///Feed the fishes with the contents of the fish feed
/obj/item/fish/proc/feed(datum/reagents/fed_reagents)
@@ -1568,21 +1577,12 @@
continue
if(required_fluid)
var/init_fish_fluid_type = initial(fish.required_fluid_type)
- if(!compatible_fluid_type(init_fish_fluid_type, required_fluid))
+ if(!(required_fluid in GLOB.fish_compatible_fluid_types[init_fish_fluid_type]))
continue
chance_table[fish] = initial(fish.random_case_rarity)
probability_table[argkey] = chance_table
return pick_weight(probability_table[argkey])
-/proc/compatible_fluid_type(fish_fluid_type, fluid_type)
- switch(fish_fluid_type)
- if(AQUARIUM_FLUID_ANY_WATER)
- return fluid_type != AQUARIUM_FLUID_AIR
- if(AQUARIUM_FLUID_ANADROMOUS)
- return fluid_type == AQUARIUM_FLUID_SALTWATER || fluid_type == AQUARIUM_FLUID_FRESHWATER
- else
- return fish_fluid_type == fluid_type
-
#undef GET_FISH_ELECTROGENESIS
#undef FISH_SAD
#undef FISH_VERY_HAPPY
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm b/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm
index 30cb60cc243..07c60c4f380 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/hauntedtradingpost.dm
@@ -85,10 +85,10 @@
/obj/structure/aquarium/donkfish
name = "office aquarium"
desc = "A home for captive fish. This one has 'DONK CO' engraved on the glass."
+ init_mode = AQUARIUM_MODE_SAFE
/obj/structure/aquarium/donkfish/Initialize(mapload)
. = ..()
- ADD_TRAIT(src, TRAIT_STOP_FISH_REPRODUCTION_AND_GROWTH, AQUARIUM_TRAIT)
new /obj/item/aquarium_prop/rocks(src)
new /obj/item/aquarium_prop/seaweed(src)
new /obj/item/fish/donkfish(src)
diff --git a/tgui/packages/tgui/interfaces/Aquarium.tsx b/tgui/packages/tgui/interfaces/Aquarium.tsx
index 2c1a8c1f549..b3b34a9d52b 100644
--- a/tgui/packages/tgui/interfaces/Aquarium.tsx
+++ b/tgui/packages/tgui/interfaces/Aquarium.tsx
@@ -1,7 +1,9 @@
import {
Box,
Button,
+ Dimmer,
DmIcon,
+ Dropdown,
Flex,
Icon,
Knob,
@@ -9,6 +11,7 @@ import {
NumberInput,
Section,
Stack,
+ Tooltip,
} from 'tgui-core/components';
import { BooleanLike } from 'tgui-core/react';
import { capitalizeFirst } from 'tgui-core/string';
@@ -19,6 +22,7 @@ import { Window } from '../layouts';
type Data = {
temperature: number;
fluidType: string;
+ lockedFluidTemp: BooleanLike;
minTemperature: number;
maxTemperature: number;
fluidTypes: string[];
@@ -29,6 +33,9 @@ type Data = {
heartIcon: string;
heartIconState: string;
heartEmptyIconState: string;
+ currentMode: string;
+ currentTooltip: string;
+ aquariumModes: string[];
};
type FishData = {
@@ -273,12 +280,17 @@ const Settings = (props) => {
fluidType,
safe_mode,
feedingInterval,
+ lockedFluidTemp,
+ currentMode,
+ currentTooltip,
+ aquariumModes,
} = data;
return (
+ {!!lockedFluidTemp && }
{
+ {!!lockedFluidTemp && }
{fluidTypes.map((f) => (
@@ -306,11 +319,10 @@ const Settings = (props) => {
textAlign="center"
fluid
color="transparent"
+ content={f}
selected={fluidType === f}
onClick={() => act('fluid', { fluid: f })}
- >
- {f}
-
+ />
))}
@@ -320,15 +332,23 @@ const Settings = (props) => {
-
-