mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 04:30:44 +01:00
new aquarium ui and few new additions (#85627)
## About The Pull Request gives the aquarium a new ui:  u can now also pet and nickname ur fish through this interface. petting them will have them do a small dance and increase their happiness  the hearts indicate how happy the fish is with the tank's living conditions and if theyve been petted recently. ## Why It's Good For The Game gives aquariums a better UI making it easier to use ## Changelog 🆑 qol: gives aquariums a new easier to use UI /🆑
This commit is contained in:
@@ -17,6 +17,8 @@
|
||||
#define COMSIG_FISH_EATEN_BY_OTHER_FISH "fish_eaten_by_other_fish"
|
||||
///From /obj/item/fish/feed: (fed_reagents, fed_reagent_type)
|
||||
#define COMSIG_FISH_FED "fish_on_fed"
|
||||
///from /obj/item/fish/pet_fish
|
||||
#define COMSIG_FISH_PETTED "fish_petted"
|
||||
///From /obj/item/fish/update_size_and_weight: (new_size, new_weight)
|
||||
#define COMSIG_FISH_UPDATE_SIZE_AND_WEIGHT "fish_update_size_and_weight"
|
||||
///From /obj/item/fish/update_fish_force: (weight_rank, bonus_malus)
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
//Current layer for the visual object
|
||||
var/base_layer
|
||||
|
||||
|
||||
/**
|
||||
* Fish sprite how to:
|
||||
* Need to be centered on 16,16 in the dmi and facing left by default.
|
||||
@@ -97,6 +96,7 @@
|
||||
ADD_TRAIT(parent, TRAIT_FISH_CASE_COMPATIBILE, REF(src))
|
||||
RegisterSignal(parent, COMSIG_TRY_INSERTING_IN_AQUARIUM, PROC_REF(is_ready_to_insert))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(enter_aquarium))
|
||||
RegisterSignal(parent, COMSIG_FISH_PETTED, PROC_REF(on_fish_petted))
|
||||
|
||||
//If component is added to something already in aquarium at the time initialize it properly.
|
||||
var/atom/movable/movable_parent = parent
|
||||
@@ -274,7 +274,6 @@
|
||||
dead_animation()
|
||||
return
|
||||
|
||||
|
||||
/// Create looping random path animation, pixel offsets parameters include offsets already
|
||||
/datum/component/aquarium_content/proc/swim_animation()
|
||||
var/avg_width = round(sprite_width / 2)
|
||||
@@ -325,6 +324,11 @@
|
||||
base_layer = current_aquarium.request_layer(layer_mode)
|
||||
vc_obj.layer = base_layer
|
||||
|
||||
/datum/component/aquarium_content/proc/on_fish_petted()
|
||||
SIGNAL_HANDLER
|
||||
|
||||
new /obj/effect/temp_visual/heart(get_turf(parent))
|
||||
|
||||
/datum/component/aquarium_content/proc/randomize_base_position()
|
||||
var/list/aq_properties = current_aquarium.get_surface_properties()
|
||||
var/avg_width = round(sprite_width / 2)
|
||||
|
||||
@@ -307,14 +307,30 @@
|
||||
|
||||
/obj/structure/aquarium/ui_data(mob/user)
|
||||
. = ..()
|
||||
.["fluid_type"] = fluid_type
|
||||
.["fluidType"] = fluid_type
|
||||
.["temperature"] = fluid_temp
|
||||
.["allow_breeding"] = allow_breeding
|
||||
.["feeding_interval"] = feeding_interval / (1 MINUTES)
|
||||
var/list/content_data = list()
|
||||
for(var/atom/movable/fish in contents)
|
||||
content_data += list(list("name"=fish.name,"ref"=ref(fish)))
|
||||
.["contents"] = content_data
|
||||
.["allowBreeding"] = allow_breeding
|
||||
.["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" = 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)
|
||||
. = ..()
|
||||
@@ -322,6 +338,7 @@
|
||||
.["minTemperature"] = min_fluid_temp
|
||||
.["maxTemperature"] = max_fluid_temp
|
||||
.["fluidTypes"] = fluid_types
|
||||
.["heart_icon"] = 'icons/effects/effects.dmi'
|
||||
|
||||
/obj/structure/aquarium/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
|
||||
. = ..()
|
||||
@@ -345,14 +362,19 @@
|
||||
if("feeding_interval")
|
||||
feeding_interval = params["feeding_interval"] MINUTES
|
||||
. = TRUE
|
||||
if("remove")
|
||||
var/atom/movable/inside = locate(params["ref"]) in contents
|
||||
if(inside)
|
||||
if(isitem(inside))
|
||||
user.put_in_hands(inside)
|
||||
else
|
||||
inside.forceMove(get_turf(src))
|
||||
to_chat(user,span_notice("You take out [inside] from [src]."))
|
||||
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"])
|
||||
if(!new_name)
|
||||
return
|
||||
var/atom/movable/fish = locate(params["fish_reference"]) in contents
|
||||
fish.name = new_name
|
||||
|
||||
/obj/structure/aquarium/ui_interact(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
#define FISH_SAD 0
|
||||
#define FISH_NEUTRAL 1
|
||||
#define FISH_SATISFIED 2
|
||||
#define FISH_HAPPY 3
|
||||
#define FISH_VERY_HAPPY 4
|
||||
|
||||
// Fish path used for autogenerated fish
|
||||
/obj/item/fish
|
||||
name = "generic looking aquarium fish"
|
||||
@@ -147,6 +153,8 @@
|
||||
|
||||
/// The beauty this fish provides to the aquarium it's inserted in.
|
||||
var/beauty = FISH_BEAUTY_GENERIC
|
||||
///have we recently pet this fish
|
||||
var/recently_petted = FALSE
|
||||
|
||||
/obj/item/fish/Initialize(mapload, apply_qualities = TRUE)
|
||||
. = ..()
|
||||
@@ -755,6 +763,31 @@
|
||||
if(HAS_TRAIT(src, TRAIT_FISH_FROM_CASE)) //Avoid printing money by simply ordering fish and sending it back.
|
||||
calculated_price *= 0.05
|
||||
return round(calculated_price)
|
||||
/obj/item/fish/proc/get_happiness_value()
|
||||
var/happiness_value = 0
|
||||
if(recently_petted)
|
||||
happiness_value++
|
||||
if(HAS_TRAIT(src, TRAIT_FISH_NO_HUNGER) || min((world.time - last_feeding) / feeding_frequency, 1) < 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++
|
||||
return happiness_value
|
||||
|
||||
/obj/item/fish/proc/pet_fish(mob/living/user)
|
||||
if(recently_petted)
|
||||
to_chat(user, span_warning("[src] runs away from your finger as you dip it into the water!"))
|
||||
return
|
||||
if(electrogenesis_power > 15 MEGA JOULES)
|
||||
user.electrocute_act(5, src) //was it all worth it?
|
||||
recently_petted = TRUE
|
||||
SEND_SIGNAL(src, COMSIG_FISH_PETTED)
|
||||
to_chat(user, span_notice("[src] dances around!"))
|
||||
addtimer(VARSET_CALLBACK(src, recently_petted, FALSE), 30 SECONDS)
|
||||
|
||||
/// Returns random fish, using random_case_rarity probabilities.
|
||||
/proc/random_fish_type(required_fluid)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { capitalizeFirst } from 'common/string';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
DmIcon,
|
||||
Flex,
|
||||
Icon,
|
||||
Knob,
|
||||
LabeledControls,
|
||||
LabeledList,
|
||||
NumberInput,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
import { BooleanLike } from 'tgui-core/react';
|
||||
|
||||
@@ -13,98 +18,338 @@ import { Window } from '../layouts';
|
||||
|
||||
type Data = {
|
||||
temperature: number;
|
||||
fluid_type: string;
|
||||
fluidType: string;
|
||||
minTemperature: number;
|
||||
maxTemperature: number;
|
||||
fluidTypes: string[];
|
||||
contents: { ref: string; name: string }[];
|
||||
allow_breeding: BooleanLike;
|
||||
feeding_interval: number;
|
||||
fishData: FishData[];
|
||||
propData: PropData[];
|
||||
allowBreeding: BooleanLike;
|
||||
feedingInterval: number;
|
||||
heartIcon: string;
|
||||
heartIconState: string;
|
||||
heartEmptyIconState: string;
|
||||
};
|
||||
|
||||
type FishData = {
|
||||
fish_ref: string;
|
||||
fish_name: string;
|
||||
fish_happiness: number;
|
||||
fish_icon: string;
|
||||
fish_icon_state: string;
|
||||
fish_health: number;
|
||||
};
|
||||
|
||||
type PropData = {
|
||||
prop_ref: string;
|
||||
prop_name: string;
|
||||
prop_icon: string;
|
||||
prop_icon_state: string;
|
||||
};
|
||||
|
||||
export const Aquarium = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
temperature,
|
||||
fluid_type,
|
||||
minTemperature,
|
||||
maxTemperature,
|
||||
fluidTypes,
|
||||
contents,
|
||||
allow_breeding,
|
||||
feeding_interval,
|
||||
} = data;
|
||||
const { fishData } = data;
|
||||
|
||||
return (
|
||||
<Window width={520} height={400}>
|
||||
<Window width={765} height={500} theme="ntos">
|
||||
<Window.Content>
|
||||
<Section title="Aquarium Controls">
|
||||
<LabeledControls>
|
||||
<LabeledControls.Item label="Temperature">
|
||||
<Knob
|
||||
size={1.25}
|
||||
mb={1}
|
||||
value={temperature}
|
||||
unit="K"
|
||||
minValue={minTemperature}
|
||||
maxValue={maxTemperature}
|
||||
step={1}
|
||||
stepPixelSize={1}
|
||||
onDrag={(_, value) =>
|
||||
act('temperature', {
|
||||
temperature: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Fluid">
|
||||
<Flex direction="column" mb={1}>
|
||||
{fluidTypes.map((f) => (
|
||||
<Flex.Item key={f}>
|
||||
<Button
|
||||
fluid
|
||||
content={f}
|
||||
selected={fluid_type === f}
|
||||
onClick={() => act('fluid', { fluid: f })}
|
||||
/>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Reproduction and Growth">
|
||||
<Button
|
||||
content={allow_breeding ? 'Online' : 'Offline'}
|
||||
selected={allow_breeding}
|
||||
onClick={() => act('allow_breeding')}
|
||||
/>
|
||||
</LabeledControls.Item>
|
||||
<LabeledControls.Item label="Feeding Interval">
|
||||
<NumberInput
|
||||
fluid
|
||||
value={feeding_interval}
|
||||
minValue={1}
|
||||
maxValue={7}
|
||||
step={1}
|
||||
unit="minutes"
|
||||
onChange={(value) =>
|
||||
act('feeding_interval', {
|
||||
feeding_interval: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledControls.Item>
|
||||
</LabeledControls>
|
||||
</Section>
|
||||
<Section title="Contents">
|
||||
{contents.map((movable) => (
|
||||
<Button
|
||||
key={movable.ref}
|
||||
content={movable.name}
|
||||
onClick={() => act('remove', { ref: movable.ref })}
|
||||
/>
|
||||
))}
|
||||
</Section>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Settings />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Flex>
|
||||
<Flex.Item height="300px" width="75%">
|
||||
<Section fill title="Fish" scrollable>
|
||||
<Stack wrap>
|
||||
{fishData.map((fish) => (
|
||||
<Stack.Item
|
||||
width="44%"
|
||||
height="100px"
|
||||
ml={1}
|
||||
key={fish.fish_ref}
|
||||
style={{
|
||||
background: 'rgba(36, 50, 67, 0.5)',
|
||||
padding: '5px 5px',
|
||||
borderRadius: '1em',
|
||||
border: '3px solid #574e82',
|
||||
}}
|
||||
>
|
||||
<FishInfo fish={fish} />
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item ml={1} grow>
|
||||
<PropTypes />
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
const FishInfo = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { fish } = props;
|
||||
|
||||
return (
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Flex>
|
||||
<Flex.Item width="40px">
|
||||
<DmIcon
|
||||
style={{ borderRadius: '1em', background: '#151326' }}
|
||||
icon={fish.fish_icon}
|
||||
icon_state={fish.fish_icon_state}
|
||||
height="40px"
|
||||
width="40px"
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item grow>
|
||||
<Stack vertical>
|
||||
<Stack.Item
|
||||
ml={1}
|
||||
style={{ fontSize: '13px', fontWeight: 'bold' }}
|
||||
>
|
||||
{fish.fish_name.toUpperCase()}
|
||||
</Stack.Item>
|
||||
<Stack.Item mt={fish.fish_health > 0 ? -4 : 1}>
|
||||
{(fish.fish_health > 0 && (
|
||||
<CalculateHappiness happiness={fish.fish_happiness} />
|
||||
)) || <Icon ml={2} name="skull-crossbones" textColor="white" />}
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Flex.Item>
|
||||
<Flex.Item>
|
||||
<Flex>
|
||||
<Button
|
||||
fluid
|
||||
icon="arrow-up"
|
||||
color="transparent"
|
||||
onClick={() =>
|
||||
act('remove_item', {
|
||||
item_reference: fish.fish_ref,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Flex>
|
||||
<Flex.Item width="50%">
|
||||
<Button
|
||||
textAlign="center"
|
||||
mt={1}
|
||||
fluid
|
||||
color="transparent"
|
||||
icon="paw"
|
||||
style={{
|
||||
padding: '3px',
|
||||
borderRadius: '1em',
|
||||
background: '#151326',
|
||||
}}
|
||||
onClick={() =>
|
||||
act('pet_fish', {
|
||||
fish_reference: fish.fish_ref,
|
||||
})
|
||||
}
|
||||
>
|
||||
Pet
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
<Flex.Item width="50%">
|
||||
<Button.Input
|
||||
textAlign="center"
|
||||
mt={1}
|
||||
ml={1}
|
||||
fluid
|
||||
placeholder="Rename"
|
||||
color="transparent"
|
||||
onCommit={(e, value) => {
|
||||
act('rename_fish', {
|
||||
fish_reference: fish.fish_ref,
|
||||
chosen_name: value,
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
padding: '3px',
|
||||
borderRadius: '1em',
|
||||
background: '#151326',
|
||||
}}
|
||||
>
|
||||
<Flex>
|
||||
<Flex.Item ml={3}>
|
||||
<Icon name="keyboard" />
|
||||
</Flex.Item>
|
||||
<Flex.Item ml={1}>Rename</Flex.Item>
|
||||
</Flex>
|
||||
</Button.Input>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
const PropTypes = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { propData } = data;
|
||||
|
||||
return (
|
||||
<Section scrollable fill title="Props">
|
||||
<Stack vertical>
|
||||
{propData.map((prop) => (
|
||||
<Stack.Item className="candystripe" key={prop.prop_ref}>
|
||||
<Button
|
||||
fluid
|
||||
color="transparent"
|
||||
onClick={() =>
|
||||
act('remove_item', {
|
||||
item_reference: prop.prop_ref,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Flex>
|
||||
<Flex.Item>
|
||||
<DmIcon
|
||||
icon={prop.prop_icon}
|
||||
icon_state={prop.prop_icon_state}
|
||||
height="40px"
|
||||
width="40px"
|
||||
/>
|
||||
</Flex.Item>
|
||||
<Flex.Item
|
||||
ml={1}
|
||||
mt={1}
|
||||
style={{ fontSize: '11px', fontWeight: 'bold' }}
|
||||
>
|
||||
{capitalizeFirst(prop.prop_name)}
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const CalculateHappiness = (props) => {
|
||||
const { data } = useBackend<Data>();
|
||||
const { heartIcon } = data;
|
||||
const { happiness } = props;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{Array.from({ length: 5 }, (_, index) => (
|
||||
<DmIcon
|
||||
key={index}
|
||||
ml={index === 0 ? 0 : -6}
|
||||
icon={heartIcon}
|
||||
icon_state={happiness >= index ? 'full_heart' : 'empty_heart'}
|
||||
height="48px"
|
||||
width="48px"
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Settings = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const {
|
||||
temperature,
|
||||
minTemperature,
|
||||
maxTemperature,
|
||||
fluidTypes,
|
||||
fluidType,
|
||||
allowBreeding,
|
||||
feedingInterval,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Flex fill>
|
||||
<Flex.Item grow>
|
||||
<Section fill title="Temperature">
|
||||
<Knob
|
||||
mt={3}
|
||||
size={1.5}
|
||||
mb={1}
|
||||
value={temperature}
|
||||
unit="K"
|
||||
minValue={minTemperature}
|
||||
maxValue={maxTemperature}
|
||||
step={1}
|
||||
stepPixelSize={1}
|
||||
onDrag={(_, value) =>
|
||||
act('temperature', {
|
||||
temperature: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item ml={1} grow>
|
||||
<Section fill title="Fluid">
|
||||
<Flex direction="column" mb={1}>
|
||||
{fluidTypes.map((f) => (
|
||||
<Flex.Item className="candystripe" key={f}>
|
||||
<Button
|
||||
textAlign="center"
|
||||
fluid
|
||||
color="transparent"
|
||||
selected={fluidType === f}
|
||||
onClick={() => act('fluid', { fluid: f })}
|
||||
>
|
||||
{f}
|
||||
</Button>
|
||||
</Flex.Item>
|
||||
))}
|
||||
</Flex>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
<Flex.Item ml={1} grow>
|
||||
<Section fill title="Settings">
|
||||
<Box mt={2}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Reproduction">
|
||||
<Button
|
||||
textAlign="center"
|
||||
width="75px"
|
||||
content={allowBreeding ? 'Online' : 'Offline'}
|
||||
selected={allowBreeding}
|
||||
onClick={() => act('allow_breeding')}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Feeding Interval">
|
||||
<NumberInput
|
||||
width="15px"
|
||||
value={feedingInterval}
|
||||
minValue={1}
|
||||
maxValue={7}
|
||||
step={1}
|
||||
unit="minutes"
|
||||
onChange={(value) =>
|
||||
act('feeding_interval', {
|
||||
feeding_interval: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Box>
|
||||
</Section>
|
||||
</Flex.Item>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
function dissectName(input: string): string {
|
||||
return input.split(' ')[0].slice(0, 18);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user