[MIRROR] Species Stats Visible During Creation (#11595)

Co-authored-by: Will <7099514+Willburd@users.noreply.github.com>
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2025-09-09 12:17:32 -07:00
committed by GitHub
parent 894f20a88a
commit 837fc4c83d
5 changed files with 516 additions and 41 deletions

View File

@@ -95,6 +95,55 @@
data["autohiss"] = pref.autohiss
data["emote_sound_mode"] = pref.read_preference(/datum/preference/choiced/living/emote_sound_mode)
// Get species stats so they can be displayed
var/datum/species/species = null
var/mob/living/carbon/human/dummy/mannequin/mannequin = get_mannequin(pref.client_ckey)
if(mannequin)
species = mannequin.species
else if(pref.species)
species = GLOB.all_species[pref.species]
else
species = GLOB.all_species[SPECIES_HUMAN]
var/list/species_stats = list(
"total_health" = species.total_health,
"slowdown" = species.slowdown,
"brute_mod" = species.brute_mod,
"burn_mod" = species.burn_mod,
"oxy_mod" = species.oxy_mod,
"toxins_mod" = species.toxins_mod,
"radiation_mod" = species.radiation_mod,
"flash_mod" = species.flash_mod,
"pain_mod" = species.pain_mod,
"stun_mod" = species.stun_mod,
"weaken_mod" = species.weaken_mod,
"lightweight" = species.lightweight,
"dispersed_eyes" = species.dispersed_eyes,
"trashcan" = species.trashcan,
"eat_minerals" = species.eat_minerals,
"darksight" = species.darksight,
"chem_strength_tox" = species.chem_strength_tox,
"cold_level_1" = species.cold_level_1,
"heat_level_1" = species.heat_level_1,
"chem_strength_heal" = species.chem_strength_heal,
"siemens_coefficient" = species.siemens_coefficient,
"has_vibration_sense" = species.has_vibration_sense,
"item_slowdown_mod" = species.item_slowdown_mod,
"body_temperature" = species.body_temperature,
"hazard_low_pressure" = species.hazard_low_pressure,
"breath_type" = GLOB.gas_data.name[species.breath_type],
"hazard_high_pressure" = species.hazard_high_pressure,
"soft_landing" = species.soft_landing,
"bloodsucker" = species.bloodsucker,
"can_space_freemove" = species.can_space_freemove,
"can_zero_g_move" = species.can_zero_g_move,
"water_breather" = species.water_breather,
"can_climb" = species.can_climb,
"has_flight" = (/mob/living/proc/flying_toggle in species.inherent_verbs),
)
data["species_stats"] = species_stats
return data
/datum/category_item/player_setup_item/general/basic/tgui_static_data(mob/user)
@@ -102,6 +151,45 @@
data["allow_metadata"] = CONFIG_GET(flag/allow_metadata)
var/list/human_stats = list(
"total_health" = /datum/species/human::total_health,
"slowdown" = /datum/species/human::slowdown,
"brute_mod" = /datum/species/human::brute_mod,
"burn_mod" = /datum/species/human::burn_mod,
"oxy_mod" = /datum/species/human::oxy_mod,
"toxins_mod" = /datum/species/human::toxins_mod,
"radiation_mod" = /datum/species/human::radiation_mod,
"flash_mod" = /datum/species/human::flash_mod,
"pain_mod" = /datum/species/human::pain_mod,
"stun_mod" = /datum/species/human::stun_mod,
"weaken_mod" = /datum/species/human::weaken_mod,
"lightweight" = FALSE,
"dispersed_eyes" = FALSE,
"trashcan" = FALSE,
"eat_minerals" = FALSE,
"darksight" = /datum/species/human::darksight,
"chem_strength_tox" = /datum/species/human::chem_strength_tox,
"cold_level_1" = /datum/species/human::cold_level_1,
"heat_level_1" = /datum/species/human::heat_level_1,
"chem_strength_heal" = /datum/species/human::chem_strength_heal,
"siemens_coefficient" = /datum/species/human::siemens_coefficient,
"has_vibration_sense" = FALSE,
"item_slowdown_mod" = /datum/species/human::item_slowdown_mod,
"body_temperature" = /datum/species/human::body_temperature,
"hazard_low_pressure" = /datum/species/human::hazard_low_pressure,
"breath_type" = GLOB.gas_data.name[/datum/species/human::breath_type],
"hazard_high_pressure" = /datum/species/human::hazard_high_pressure,
"soft_landing" = FALSE,
"bloodsucker" = FALSE,
"can_space_freemove" = FALSE,
"can_zero_g_move" = FALSE,
"water_breather" = FALSE,
"can_climb" = FALSE,
"has_flight" = FALSE,
)
data["basehuman_stats"] = human_stats
return data
/datum/category_item/player_setup_item/general/basic/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)

View File

@@ -4,7 +4,6 @@ import {
Box,
Button,
ColorBox,
Divider,
Floating,
LabeledList,
Stack,
@@ -17,40 +16,8 @@ import {
type GeneralDataConstant,
type GeneralDataStatic,
} from './data';
export const gender2icon = (gender: Gender) => {
switch (gender) {
case Gender.Female: {
return 'venus';
}
case Gender.Male: {
return 'mars';
}
case Gender.Plural: {
return 'transgender';
}
case Gender.Neuter: {
return 'neuter';
}
}
};
export const gender2pronouns = (gender: Gender) => {
switch (gender) {
case Gender.Female: {
return 'She/Her';
}
case Gender.Male: {
return 'He/Him';
}
case Gender.Plural: {
return 'They/Them';
}
case Gender.Neuter: {
return 'It/Its';
}
}
};
import { gender2icon, gender2pronouns } from './functions';
import { SpeciesBaseStats } from './stats/SpeciesSpaceStats';
export const GenderButton = (props: {
gender: Gender;
@@ -115,6 +82,7 @@ export const SubtabInfo = (props: {
custom_species,
selects_bodytype,
custom_base,
species_stats,
} = data;
return (
@@ -251,9 +219,7 @@ export const SubtabInfo = (props: {
</Box>
) : null}
</Stack.Item>
<Stack.Item>
<Divider vertical />
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<Box bold>Language Keys</Box>
{language_keys.map((key) => `${key} `)}
@@ -264,9 +230,7 @@ export const SubtabInfo = (props: {
{preferred_language}
</Button>
</Stack.Item>
<Stack.Item>
<Divider vertical />
</Stack.Item>
<Stack.Divider />
<Stack.Item textAlign="center">
<Box bold>Runechat Color</Box>
<Tooltip content={runechat_color}>
@@ -278,6 +242,13 @@ export const SubtabInfo = (props: {
</Stack.Item>
</Stack>
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<SpeciesBaseStats
speciesStats={species_stats}
baseStats={staticData.basehuman_stats}
/>
</Stack.Item>
</Stack>
);
};

View File

@@ -49,6 +49,7 @@ export type BasicData = {
emote_sound_mode: string;
persistence_settings: PersistanceEnum;
species_stats: SpeciesStats;
};
export enum BodypartFlags {
@@ -301,6 +302,7 @@ export type GeneralData = BasicData &
export type GeneralDataStatic = {
allow_metadata: BooleanLike;
basehuman_stats: SpeciesStats;
can_play: Record<string, { restricted: number; can_select: BooleanLike }>;
available_hair_styles: string[];
available_facial_styles: string[];
@@ -435,3 +437,40 @@ export type GeneralDataConstant = {
wing_styles: Record<string, WingStyle>;
all_traits: Record<string, Trait>;
};
export type SpeciesStats = {
total_health: number;
slowdown: number;
brute_mod: number;
burn_mod: number;
oxy_mod: number;
toxins_mod: number;
radiation_mod: number;
flash_mod: number;
pain_mod: number;
stun_mod: number;
weaken_mod: number;
lightweight: BooleanLike;
has_vibration_sense: BooleanLike;
dispersed_eyes: BooleanLike;
trashcan: BooleanLike;
eat_minerals: BooleanLike;
darksight: number;
breath_type: string | null;
cold_level_1: number;
heat_level_1: number;
chem_strength_heal: number;
chem_strength_tox: number;
body_temperature: number;
item_slowdown_mod: number;
hazard_low_pressure: number;
hazard_high_pressure: number;
siemens_coefficient: number;
soft_landing: BooleanLike;
bloodsucker: BooleanLike;
can_zero_g_move: BooleanLike;
can_space_freemove: BooleanLike;
water_breather: BooleanLike;
has_flight: BooleanLike;
can_climb: BooleanLike;
};

View File

@@ -0,0 +1,108 @@
import { Gender } from './data';
export function gender2icon(gender: Gender): string {
switch (gender) {
case Gender.Female: {
return 'venus';
}
case Gender.Male: {
return 'mars';
}
case Gender.Plural: {
return 'transgender';
}
case Gender.Neuter: {
return 'neuter';
}
}
}
export function gender2pronouns(gender: Gender): string {
switch (gender) {
case Gender.Female: {
return 'She/Her';
}
case Gender.Male: {
return 'He/Him';
}
case Gender.Plural: {
return 'They/Them';
}
case Gender.Neuter: {
return 'It/Its';
}
}
}
export function breathetypeToColor(
type: string | null,
baseType: string | null,
): string | undefined {
if (!type) {
return 'green';
}
if (type !== baseType) {
return 'red';
}
return undefined;
}
export function compareWithBase(
lower: number,
higher: number,
goodColor?: string,
badColor?: string,
): string | undefined {
if (lower < higher) {
return goodColor ?? 'green';
}
if (lower > higher) {
return badColor ?? 'red';
}
return undefined;
}
export function formatStat(value: number, unit: string): string {
if (!Number.isFinite(value)) {
return 'N/A';
}
return `${(Math.round(value * 100) / 100).toFixed(2)}${unit}`;
}
export function slowdownToString(slowdown: number): string {
if (slowdown < -0.8) {
return 'Extremely Fast';
}
if (slowdown < -0.2) {
return 'Very Fast';
}
if (slowdown < -0.01) {
return 'Fast';
}
if (slowdown < 0.2) {
return 'Average';
}
if (slowdown < 0.4) {
return 'Slow';
}
if (slowdown < 0.8) {
return 'Very Slow';
}
return 'Extremely Slow';
}
export function darksightToString(sight: number): string {
if (sight <= 0) {
return 'None';
}
if (sight < 2) {
return 'Low';
}
if (sight < 5) {
return 'Basic';
}
if (sight < 9) {
return 'Great';
}
return 'Advanced';
}

View File

@@ -0,0 +1,269 @@
import { Box, LabeledList, Stack } from 'tgui-core/components';
import { YesNoBox } from '../../../../PublicLibraryWiki/WikiCommon/WikiQuickElements';
import type { SpeciesStats } from '../data';
import {
breathetypeToColor,
compareWithBase,
darksightToString,
formatStat,
slowdownToString,
} from '../functions';
import { T0C } from 'tgui/constants';
export const SpeciesBaseStats = (props: {
speciesStats: SpeciesStats;
baseStats: SpeciesStats;
}) => {
const { speciesStats, baseStats } = props;
return (
<Stack>
<Stack.Item>
<Box bold>Physiology</Box>
<LabeledList>
<LabeledList.Item label="Max Health">
<Box
color={compareWithBase(
baseStats.total_health,
speciesStats.total_health,
)}
>
{speciesStats.total_health}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Breathes Gas">
<Box
color={breathetypeToColor(speciesStats.breath_type, baseStats.breath_type)}
>
{speciesStats.breath_type || "N/A"}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Body Temperature">
<Box
color={compareWithBase(
speciesStats.body_temperature,
baseStats.body_temperature,
'blue',
)}
>
{formatStat(speciesStats.body_temperature - T0C, ' °C')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Hypothermia Threshold">
<Box
color={compareWithBase(
speciesStats.cold_level_1,
baseStats.cold_level_1,
)}
>
{formatStat(speciesStats.cold_level_1 - T0C, ' °C')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Heatstroke Threshold">
<Box
color={compareWithBase(
baseStats.heat_level_1,
speciesStats.heat_level_1,
)}
>
{formatStat(speciesStats.heat_level_1 - T0C, ' °C')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Min Pressure Limit">
<Box
color={compareWithBase(
speciesStats.hazard_low_pressure,
baseStats.hazard_low_pressure,
)}
>
{formatStat(Math.max(0, speciesStats.hazard_low_pressure), ' kPa')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Max Pressure Limit">
<Box
color={compareWithBase(
baseStats.hazard_high_pressure,
speciesStats.hazard_high_pressure,
)}
>
{formatStat(Math.max(0, speciesStats.hazard_high_pressure), ' kPa')}
</Box>
</LabeledList.Item>
</LabeledList>
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<Box bold>Abilities</Box>
<LabeledList>
<LabeledList.Item label="Movement Speed">
<Box
color={compareWithBase(speciesStats.slowdown, baseStats.slowdown)}
>
{slowdownToString(speciesStats.slowdown)}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Darksight">
<Box
color={compareWithBase(
baseStats.darksight,
speciesStats.darksight,
)}
>
{darksightToString(speciesStats.darksight)}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Soft Landing">
<YesNoBox value={!!speciesStats.soft_landing} />
</LabeledList.Item>
<LabeledList.Item label="Lightweight">
<YesNoBox value={!!speciesStats.lightweight} />
</LabeledList.Item>
<LabeledList.Item label="Cliff Climber">
<YesNoBox value={!!speciesStats.can_climb} />
</LabeledList.Item>
<LabeledList.Item label="Water Breathing">
<YesNoBox value={!!speciesStats.water_breather} />
</LabeledList.Item>
<LabeledList.Item label="Vibration Sensing">
<YesNoBox value={!!speciesStats.has_vibration_sense} />
</LabeledList.Item>
<LabeledList.Item label="Winged Flight">
<YesNoBox value={!!speciesStats.has_flight} />
</LabeledList.Item>
<LabeledList.Item label="ZeroG Maneuvering">
<YesNoBox value={!!speciesStats.can_zero_g_move} />
</LabeledList.Item>
<LabeledList.Item label="Space Flight">
<YesNoBox value={!!speciesStats.can_space_freemove} />
</LabeledList.Item>
<LabeledList.Item label="Dispersed Eyes">
<YesNoBox value={!!speciesStats.dispersed_eyes} />
</LabeledList.Item>
<LabeledList.Item label="Trash Eating">
<YesNoBox value={!!speciesStats.trashcan} />
</LabeledList.Item>
<LabeledList.Item label="Metal Eating">
<YesNoBox value={!!speciesStats.eat_minerals} />
</LabeledList.Item>
<LabeledList.Item label="Hemovore">
<YesNoBox value={!!speciesStats.bloodsucker} />
</LabeledList.Item>
</LabeledList>
</Stack.Item>
<Stack.Divider />
<Stack.Item>
<Box bold>Modifiers</Box>
<LabeledList>
<LabeledList.Item label="Brute">
<Box
color={compareWithBase(
speciesStats.brute_mod,
baseStats.brute_mod,
)}
>
{formatStat(speciesStats.brute_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Burn">
<Box
color={compareWithBase(speciesStats.burn_mod, baseStats.burn_mod)}
>
{formatStat(speciesStats.burn_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Toxin">
<Box
color={compareWithBase(
speciesStats.toxins_mod,
baseStats.toxins_mod,
)}
>
{formatStat(speciesStats.toxins_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Suffocation">
<Box
color={compareWithBase(speciesStats.oxy_mod, baseStats.oxy_mod)}
>
{formatStat(speciesStats.oxy_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Radiation">
<Box
color={compareWithBase(
speciesStats.radiation_mod,
baseStats.radiation_mod,
)}
>
{formatStat(speciesStats.radiation_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Electrical">
<Box
color={compareWithBase(
speciesStats.siemens_coefficient,
baseStats.siemens_coefficient,
)}
>
{formatStat(speciesStats.siemens_coefficient, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Flash">
<Box
color={compareWithBase(
speciesStats.flash_mod,
baseStats.flash_mod,
)}
>
{formatStat(speciesStats.flash_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Pain">
<Box
color={compareWithBase(speciesStats.pain_mod, baseStats.pain_mod)}
>
{formatStat(speciesStats.pain_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Stun">
<Box
color={compareWithBase(speciesStats.stun_mod, baseStats.stun_mod)}
>
{formatStat(speciesStats.stun_mod, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Medication">
<Box
color={compareWithBase(
baseStats.chem_strength_heal,
speciesStats.chem_strength_heal,
)}
>
{formatStat(speciesStats.chem_strength_heal, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Poison">
<Box
color={compareWithBase(
speciesStats.chem_strength_tox,
baseStats.chem_strength_tox,
)}
>
{formatStat(speciesStats.chem_strength_tox, 'x')}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Item Slowdown">
<Box
color={compareWithBase(
speciesStats.item_slowdown_mod,
baseStats.item_slowdown_mod,
)}
>
{formatStat(speciesStats.item_slowdown_mod, 'x')}
</Box>
</LabeledList.Item>
</LabeledList>
</Stack.Item>
</Stack>
);
};