From f637ed7639a7c16031c33f1fa1097918a6b3b7a9 Mon Sep 17 00:00:00 2001
From: Will <7099514+Willburd@users.noreply.github.com>
Date: Tue, 9 Sep 2025 14:27:57 -0400
Subject: [PATCH] Species Stats Visible During Creation (#18428)
* species stats visible
* rounding and review
* tgui side
* corrected
* clientside maxing
* wip comparison base stats
* fix them issues
* .
---------
Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
---
.../preference_setup/general/01_basic.dm | 88 ++++++
.../bay_prefs/general/SubtabInfo.tsx | 53 +---
.../PreferencesMenu/bay_prefs/general/data.ts | 39 +++
.../bay_prefs/general/functions.ts | 108 +++++++
.../general/stats/SpeciesSpaceStats.tsx | 269 ++++++++++++++++++
5 files changed, 516 insertions(+), 41 deletions(-)
create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/functions.ts
create mode 100644 tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/stats/SpeciesSpaceStats.tsx
diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm
index 553ea81b803..4076cf18292 100644
--- a/code/modules/client/preference_setup/general/01_basic.dm
+++ b/code/modules/client/preference_setup/general/01_basic.dm
@@ -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)
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabInfo.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabInfo.tsx
index a88e939e601..219de37f74e 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabInfo.tsx
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/SubtabInfo.tsx
@@ -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: {
) : null}
-
-
-
+
Language Keys
{language_keys.map((key) => `${key} `)}
@@ -264,9 +230,7 @@ export const SubtabInfo = (props: {
{preferred_language}
-
-
-
+
Runechat Color
@@ -278,6 +242,13 @@ export const SubtabInfo = (props: {
+
+
+
+
);
};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/data.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/data.ts
index 0ca65f12a62..9aee4502cd0 100644
--- a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/data.ts
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/data.ts
@@ -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;
available_hair_styles: string[];
available_facial_styles: string[];
@@ -435,3 +437,40 @@ export type GeneralDataConstant = {
wing_styles: Record;
all_traits: Record;
};
+
+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;
+};
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/functions.ts b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/functions.ts
new file mode 100644
index 00000000000..ce7f4bf1130
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/functions.ts
@@ -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';
+}
diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/stats/SpeciesSpaceStats.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/stats/SpeciesSpaceStats.tsx
new file mode 100644
index 00000000000..39d9ab228ff
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/PreferencesMenu/bay_prefs/general/stats/SpeciesSpaceStats.tsx
@@ -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 (
+
+
+ Physiology
+
+
+
+ {speciesStats.total_health}
+
+
+
+
+ {speciesStats.breath_type || "N/A"}
+
+
+
+
+ {formatStat(speciesStats.body_temperature - T0C, ' °C')}
+
+
+
+
+ {formatStat(speciesStats.cold_level_1 - T0C, ' °C')}
+
+
+
+
+ {formatStat(speciesStats.heat_level_1 - T0C, ' °C')}
+
+
+
+
+ {formatStat(Math.max(0, speciesStats.hazard_low_pressure), ' kPa')}
+
+
+
+
+ {formatStat(Math.max(0, speciesStats.hazard_high_pressure), ' kPa')}
+
+
+
+
+
+
+ Abilities
+
+
+
+ {slowdownToString(speciesStats.slowdown)}
+
+
+
+
+ {darksightToString(speciesStats.darksight)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modifiers
+
+
+
+ {formatStat(speciesStats.brute_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.burn_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.toxins_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.oxy_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.radiation_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.siemens_coefficient, 'x')}
+
+
+
+
+ {formatStat(speciesStats.flash_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.pain_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.stun_mod, 'x')}
+
+
+
+
+ {formatStat(speciesStats.chem_strength_heal, 'x')}
+
+
+
+
+ {formatStat(speciesStats.chem_strength_tox, 'x')}
+
+
+
+
+ {formatStat(speciesStats.item_slowdown_mod, 'x')}
+
+
+
+
+
+ );
+};