Merge branch 'mass-system' into main

Weight system: cubic body mass for plants/animals (BaseMassKg), carry capacity,
Kleiber metabolism, mass-based corpse meat, and a dormant Carrier foundation for
future item hauling.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Leonid Pershin
2026-06-14 21:36:18 +03:00
co-authored by Claude Opus 4.8
13 changed files with 190 additions and 29 deletions
+4
View File
@@ -16,6 +16,7 @@
"body": "Quadruped",
"diet": ["plant"],
"spawnPer1000Cells": 2.5,
"baseMassKg": 70,
"genome": {
"GeneMaxBodySize": 1.5,
"GeneMetabolism": 1.0,
@@ -51,6 +52,7 @@
"attackDamage": 80,
"attackBleed": 3.0,
"attackBloodLoss": 5.0,
"baseMassKg": 45,
"genome": {
"GeneMaxBodySize": 1.6,
"GeneMetabolism": 1.1,
@@ -84,6 +86,7 @@
"attackDamage": 45,
"attackBleed": 1.8,
"attackBloodLoss": 3.0,
"baseMassKg": 80,
"genome": {
"GeneMaxBodySize": 1.3,
"GeneMetabolism": 1.2,
@@ -116,6 +119,7 @@
"spawnPer1000Cells": 1.5,
"baseSpeed": 22,
"visionCells": 10,
"baseMassKg": 2.5,
"genome": {
"GeneMaxBodySize": 0.5,
"GeneMetabolism": 1.3,
+1
View File
@@ -3,6 +3,7 @@
// Трава: быстрый почвопокров, основной скаттер лугов и леса.
"defs": [
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.5,
"baseMassKg": 0.4,
"harvestProduct": "ProductGrass",
"genome": { "optimalLight": 0.85, "lightTolerance": 0.35, "optimalTemperature": 19, "temperatureTolerance": 8,
"coldHardiness": 9, "heatHardiness": 6,
+8
View File
@@ -4,6 +4,7 @@
// через "parent" и лежат рядом в файлах по семействам (Trees, Bushes, Cacti, …).
"defs": [
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
"baseMassKg": 450,
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 14, "temperatureTolerance": 10,
"coldHardiness": 14, "heatHardiness": 12,
@@ -19,6 +20,7 @@
// Деревья тёплой и холодной температурных ниш (showcase температурных генов).
{ "defName": "BaseWarmTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
"baseMassKg": 420,
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
"genome": { "optimalLight": 0.65, "lightTolerance": 0.5, "optimalTemperature": 23, "temperatureTolerance": 9,
"coldHardiness": 8, "heatHardiness": 16,
@@ -32,6 +34,7 @@
{ "sizeCells": 2.0, "label": "plant.stage.mature" }
] },
{ "defName": "BaseColdTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.26,
"baseMassKg": 500,
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
"genome": { "optimalLight": 0.55, "lightTolerance": 0.5, "optimalTemperature": 8, "temperatureTolerance": 12,
"coldHardiness": 18, "heatHardiness": 8,
@@ -46,6 +49,7 @@
] },
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
"baseMassKg": 6,
"fruitProduct": "ProductBerry",
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 17, "temperatureTolerance": 9,
"coldHardiness": 10, "heatHardiness": 8,
@@ -60,6 +64,7 @@
// Кактусы: жаролюбивы, засухоустойчивы (низкий оптимум почвы), морозо-нестойки → гибнут в морозы.
{ "defName": "BaseCactus", "abstract": true, "label": "plant.cactus", "sizeCells": 1.2, "trunkRadiusCells": 0,
"baseMassKg": 35,
"harvestProduct": "ProductFiber",
"genome": { "optimalLight": 0.95, "lightTolerance": 0.35, "optimalTemperature": 26, "temperatureTolerance": 8,
"coldHardiness": 20, "heatHardiness": 18,
@@ -76,6 +81,7 @@
// Грибы: подлесок леса. Низкий оптимум света (0.15) → растут в тени крон, где другим темно
// (showcase световой пригодности). Богатая почва, короткая жизнь, быстрое спороношение.
{ "defName": "BaseMushroom", "abstract": true, "label": "plant.mushroom", "sizeCells": 0.9, "trunkRadiusCells": 0,
"baseMassKg": 0.3,
"harvestProduct": "ProductMushroom",
"genome": { "optimalLight": 0.15, "lightTolerance": 0.22, "optimalTemperature": 14, "temperatureTolerance": 11,
"coldHardiness": 12, "heatHardiness": 8,
@@ -91,6 +97,7 @@
// Луг: цветы (декоративные, быстрое расселение) и ягодный почвопокров (еда).
{ "defName": "BaseFlower", "abstract": true, "label": "plant.flower", "sizeCells": 1.0, "trunkRadiusCells": 0,
"baseMassKg": 0.15,
"genome": { "optimalLight": 0.9, "lightTolerance": 0.35, "optimalTemperature": 20, "temperatureTolerance": 9,
"coldHardiness": 8, "heatHardiness": 8,
"optimalFertility": 1.0, "fertilityTolerance": 1.1, "vigor": 1.4,
@@ -103,6 +110,7 @@
] },
{ "defName": "BaseBerryBush", "abstract": true, "label": "plant.berrybush", "sizeCells": 1.2, "trunkRadiusCells": 0,
"baseMassKg": 5,
"fruitProduct": "ProductBerry",
"genome": { "optimalLight": 0.8, "lightTolerance": 0.4, "optimalTemperature": 18, "temperatureTolerance": 9,
"coldHardiness": 10, "heatHardiness": 7,
+9 -9
View File
@@ -3,16 +3,16 @@
// Продукты сбора/плодоношения растений. На них ссылаются PlantDef.harvestProduct / fruitProduct;
// количество задаётся генами (harvestAmount / fruitYield).
"defs": [
{ "defName": "ProductWood", "label": "product.wood", "kind": "material" },
{ "defName": "ProductGrass", "label": "product.grass", "kind": "material" },
{ "defName": "ProductFiber", "label": "product.fiber", "kind": "material" },
{ "defName": "ProductBerry", "label": "product.berry", "kind": "food" },
{ "defName": "ProductAcorn", "label": "product.acorn", "kind": "food" },
{ "defName": "ProductMushroom", "label": "product.mushroom", "kind": "food" },
{ "defName": "ProductWood", "label": "product.wood", "kind": "material", "massKg": 1.0 },
{ "defName": "ProductGrass", "label": "product.grass", "kind": "material", "massKg": 0.1 },
{ "defName": "ProductFiber", "label": "product.fiber", "kind": "material", "massKg": 0.2 },
{ "defName": "ProductBerry", "label": "product.berry", "kind": "food", "massKg": 0.05 },
{ "defName": "ProductAcorn", "label": "product.acorn", "kind": "food", "massKg": 0.05 },
{ "defName": "ProductMushroom", "label": "product.mushroom", "kind": "food", "massKg": 0.1 },
// Животные продукты (фаза A5): добываются из трупа (разделка/гниение). Инвентарь/добыча — позже.
{ "defName": "ProductMeat", "label": "product.meat", "kind": "food" },
{ "defName": "ProductBone", "label": "product.bone", "kind": "material" },
{ "defName": "ProductLeather", "label": "product.leather", "kind": "material" }
{ "defName": "ProductMeat", "label": "product.meat", "kind": "food", "massKg": 0.5 },
{ "defName": "ProductBone", "label": "product.bone", "kind": "material", "massKg": 0.3 },
{ "defName": "ProductLeather", "label": "product.leather", "kind": "material", "massKg": 0.4 }
]
}
+2
View File
@@ -17,6 +17,8 @@
"inspect.growing": "Growth: {0:0}% to next stage",
"inspect.mature": "Fully grown",
"inspect.age": "Age: {0:0.0} d · lives to {1:0}",
"inspect.mass": "Weight: {0:0.#} kg · carries up to {1:0.#} kg",
"inspect.plantmass": "Weight: {0:0.##} kg",
"inspect.state": "State: {0}",
"inspect.state.growing": "growing",
"inspect.state.mature": "mature",
+2
View File
@@ -17,6 +17,8 @@
"inspect.growing": "Рост: {0:0}% до следующей стадии",
"inspect.mature": "Полностью выросло",
"inspect.age": "Возраст: {0:0.0} дн · живёт до {1:0}",
"inspect.mass": "Вес: {0:0.#} кг · несёт до {1:0.#} кг",
"inspect.plantmass": "Вес: {0:0.##} кг",
"inspect.state": "Состояние: {0}",
"inspect.state.growing": "растёт",
"inspect.state.mature": "созрело",
+9
View File
@@ -75,6 +75,9 @@ public sealed class ProductDef : Def
{
/// <summary>Категория продукта (например "material" или "food") — для будущей экономики/инвентаря.</summary>
public string Kind { get; init; } = "";
/// <summary>Масса одной единицы продукта (кг) — основа под перенос/инвентарь (грузоподъёмность).</summary>
public float MassKg { get; init; } = 1f;
}
/// <summary>
@@ -177,6 +180,9 @@ public sealed class PlantDef : Def
/// <summary>Имя <see cref="ProductDef"/>, выдаваемого плодами (ягоды/жёлудь); null — не плодоносит.</summary>
public string? FruitProduct { get; init; }
/// <summary>Эталонная масса (кг) зрелого растения базового размера; масса особи ∝ (размер/эталон)³.</summary>
public float BaseMassKg { get; init; } = 20f;
}
/// <summary>
@@ -264,6 +270,9 @@ public sealed class AnimalDef : PawnDef
/// <summary>Текстура яйца (тип рождения «яйцекладка»); по умолчанию — общий спрайт семени-точки.</summary>
public string EggTexture { get; init; } = "things/plant/seed_default";
/// <summary>Эталонная масса (кг) взрослой особи базового размера вида; масса особи ∝ (размер/эталон)³.</summary>
public float BaseMassKg { get; init; } = 40f;
/// <summary>Имя <see cref="BodyDef"/> — анатомия вида (части тела/органы); пусто — без частей тела.</summary>
public string Body { get; init; } = "";
+8 -3
View File
@@ -231,7 +231,7 @@ public sealed class WorldScene : Scene
// нужду (еда выедает растения — контур ёмкости среды). Внешний вид тускнеет с острой нуждой.
var shore = ComputeShore();
var thoughts = new ThoughtSet(content);
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs));
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs, _animals));
UpdateSystems.Add(
new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut"))
);
@@ -637,7 +637,7 @@ public sealed class WorldScene : Scene
X = transform.Position.X,
Y = transform.Position.Y,
RotDays = corpse.RotDays,
BodySize = corpse.Meat / AnimalFactory.MeatPerBodySize,
BodySize = corpse.BodySize,
Meat = corpse.Meat,
}
)
@@ -732,12 +732,12 @@ public sealed class WorldScene : Scene
_animals.IndexOf(def),
new Vector2(saved.X, saved.Y),
saved.BodySize,
saved.Meat,
CellSize
);
ref var data = ref corpse.GetComponent<Corpse>();
data.RotDays = saved.RotDays;
data.Stage = -1; // форсируем пере-тинт стадии в CorpseSystem на следующем тике
data.Meat = saved.Meat;
}
foreach (var saved in _save.Eggs)
@@ -1144,6 +1144,7 @@ public sealed class WorldScene : Scene
$" defense: toxicity {traits.Toxicity:0.##}, thorns {traits.Thorns:0.##}, "
+ $"palatability {traits.Palatability:0.##} (growth ×{traits.DefenseGrowthFactor():0.##})"
);
console.WriteLine($" mass (mature): {def.BaseMassKg:0.##} kg");
if (def.HarvestProduct is { } harvest)
{
@@ -1211,6 +1212,10 @@ public sealed class WorldScene : Scene
console.WriteLine(
$" behaviour: insulation {traits.Insulation:0.##}, sociability {traits.Sociability:0.##}"
);
var adultMass = Mass.OfAnimal(def, traits, 1f);
console.WriteLine(
$" mass: {adultMass:0.#} kg (adult), carries ~{Mass.CarryCapacity(adultMass, 1f):0.#} kg"
);
}
// Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory,
+12 -7
View File
@@ -75,19 +75,18 @@ public static class AnimalFactory
);
}
/// <summary>Сколько мяса даёт труп на единицу размера тела (для будущей разделки/падальщиков).</summary>
public const float MeatPerBodySize = 25f;
// Тинт свежего трупа (стадии гниения/скелета задаёт CorpseSystem).
private static readonly Color CorpseFreshTint = new(176, 148, 128);
/// <summary>Создаёт сущность-труп после смерти животного: спрайт-падаль, запас мяса (∝ размер тела).</summary>
/// <summary>Создаёт сущность-труп после смерти животного: спрайт-падаль (масштаб ∝ размер тела) и
/// запас мяса <paramref name="meatKg"/> (∝ масса тела, считается вызывающим через <see cref="Mass"/>).</summary>
public static Entity CreateCorpse(
EntityStore store,
AnimalSet animals,
int species,
Vector2 position,
float bodySize,
float meatKg,
int cellSize
)
{
@@ -105,7 +104,8 @@ public static class AnimalFactory
Species = species,
RotDays = 0f,
Stage = 0,
Meat = bodySize * MeatPerBodySize,
Meat = meatKg,
BodySize = bodySize,
}
);
}
@@ -184,7 +184,7 @@ public static class AnimalFactory
return false;
}
/// <summary>Гибель особи: оставляет труп (мясо ∝ размер тела) и удаляет сущность. Общий путь смерти.</summary>
/// <summary>Гибель особи: оставляет труп (мясо ∝ масса тела) и удаляет сущность. Общий путь смерти.</summary>
public static void Die(EntityStore store, AnimalSet animals, Entity entity, int cellSize)
{
if (entity.IsNull || !entity.HasComponent<AnimalOrganism>())
@@ -194,7 +194,12 @@ public static class AnimalFactory
ref readonly var org = ref entity.GetComponent<AnimalOrganism>();
var position = entity.GetComponent<Transform2D>().Position;
CreateCorpse(store, animals, org.Species, position, org.Traits.BodySize, cellSize);
var sp = animals[org.Species];
var stageScale = entity.TryGetComponent<AnimalGrowth>(out var grow)
? sp.Stages[Math.Clamp(grow.Stage, 0, sp.Stages.Length - 1)].Scale
: 1f;
var meat = Mass.OfAnimal(sp.Def, org.Traits, stageScale) * Mass.MeatFraction;
CreateCorpse(store, animals, org.Species, position, org.Traits.BodySize, meat, cellSize);
entity.DeleteEntity();
}
+15 -1
View File
@@ -464,8 +464,11 @@ public struct Corpse : IComponent
/// <summary>Стадия: 0 свежий, 1 гниющий, 2 скелет.</summary>
public int Stage;
/// <summary>Доступное мясо (∝ размер тела) — для разделки/падальщиков (фаза-горизонт).</summary>
/// <summary>Доступное мясо (∝ масса тела) — для разделки/падальщиков (фаза-горизонт).</summary>
public float Meat;
/// <summary>Линейный размер тела (для масштаба спрайта трупа), независимо от запаса мяса.</summary>
public float BodySize;
}
/// <summary>
@@ -488,6 +491,17 @@ public struct Egg : IComponent
public float IncubateDays;
}
/// <summary>
/// Ноша существа (задел под перенос предметов/хаул): суммарная масса переносимого груза в кг. Перегруз
/// сверх грузоподъёмности (<see cref="Sim.Mass.CarryCapacity"/>) замедляет движение. Компонент появляется
/// у существа, когда оно что-то несёт; пока источников груза нет — спящий каркас под будущий инвентарь.
/// </summary>
public struct Carrier : IComponent
{
/// <summary>Суммарная масса переносимого груза (кг).</summary>
public float CarriedKg;
}
/// <summary>
/// Проглоченное семя (эндозоохория): травоядное, съевшее плодоносящее растение, какое-то время несёт
/// его семя в кишечнике, затем «высаживает» его в другом месте (<see cref="Sim.SeedDispersalSystem"/>) —
+32 -9
View File
@@ -122,8 +122,12 @@ public readonly struct AnimalContext(float[] values, NeedSet needs, float intell
/// (× ген обмена веществ), drive-нужды растут под своим хедифом (гон) и спадают вне его. Летальные
/// нужды на нуле копят счётчик истощения. Полностью управляется данными <see cref="NeedSet"/>.
/// </summary>
public sealed class AnimalNeedsSystem(GameClock clock, float secondsPerDay, NeedSet needs)
: QuerySystem<AnimalNeeds, AnimalOrganism, Health>
public sealed class AnimalNeedsSystem(
GameClock clock,
float secondsPerDay,
NeedSet needs,
AnimalSet animals
) : QuerySystem<AnimalNeeds, AnimalOrganism, Health, AnimalGrowth>
{
// Прибавка к обмену веществ при устойчивости к яду 1.0 (цена детоксикации, коэволюция C2).
private const float ToleranceMetabolicCost = 0.5f;
@@ -136,11 +140,12 @@ public sealed class AnimalNeedsSystem(GameClock clock, float secondsPerDay, Need
return;
}
foreach (var (needsChunk, organisms, healths, _) in Query.Chunks)
foreach (var (needsChunk, organisms, healths, growths, _) in Query.Chunks)
{
var n = needsChunk.Span;
var o = organisms.Span;
var h = healths.Span;
var g = growths.Span;
for (var i = 0; i < n.Length; i++)
{
var values = n[i].Values;
@@ -149,14 +154,16 @@ public sealed class AnimalNeedsSystem(GameClock clock, float secondsPerDay, Need
continue; // защита от старого/пустого состояния
}
// Цена устойчивости к яду (коэволюция C2): детоксикация метаболически затратна — чем выше
// toxinTolerance, тем быстрее расходуются нужды (зверь голоднее). Без этой платы устойчивость
// ушла бы к максимуму у всех и гонка вооружений встала бы. Множитель к обмену веществ.
// Обмен веществ = ген × цена устойчивости к яду (C2) × множитель Клайбера по массе тела:
// расход ∝ масса^0.75, нормирован на эталон вида → молодняк экономнее, крупные особи
// прожорливее (масса растёт с размером/возрастом). Множитель к расходу убывающих нужд.
var sp = animals[o[i].Species];
var stageScale = sp.Stages[Math.Clamp(g[i].Stage, 0, sp.Stages.Length - 1)].Scale;
var mass = Mass.OfAnimal(sp.Def, o[i].Traits, stageScale);
var metabolism =
MathF.Max(0.1f, o[i].Traits.Metabolism)
* (
1f + ToleranceMetabolicCost * Math.Clamp(o[i].Traits.ToxinTolerance, 0f, 1f)
);
* (1f + ToleranceMetabolicCost * Math.Clamp(o[i].Traits.ToxinTolerance, 0f, 1f))
* Mass.KleiberFactor(mass, sp.Def.BaseMassKg);
var lethalEmpty = false;
for (var k = 0; k < needs.Count; k++)
{
@@ -965,6 +972,22 @@ public sealed class AnimalActionSystem : BaseSystem
// даёт сытость с корма; раненая челюсть/больной желудок → зверь голоднее (опция 1).
var eating = hh[i].State.Capacity(AnimalCapacities.Eating);
var foodEff = eating * hh[i].State.Capacity(AnimalCapacities.Digestion);
// Ноша замедляет (задел под перенос): перегруз сверх грузоподъёмности роняет скорость.
if (
entities.EntityAt(i).TryGetComponent<Carrier>(out var carrier)
&& carrier.CarriedKg > 0f
)
{
var sp = _animalSet[o[i].Species];
var stageScale = entities.EntityAt(i).TryGetComponent<AnimalGrowth>(out var gr)
? sp.Stages[Math.Clamp(gr.Stage, 0, sp.Stages.Length - 1)].Scale
: 1f;
var capacity = Mass.CarryCapacity(
Mass.OfAnimal(sp.Def, o[i].Traits, stageScale),
moving
);
speed *= Mass.LoadSpeedFactor(carrier.CarriedKg, capacity);
}
switch (brain.Action)
{
+70
View File
@@ -0,0 +1,70 @@
using LittleSim.Content;
namespace LittleSim.Sim;
/// <summary>
/// Система веса (масса тел и грузоподъёмность). Масса — производная от РАЗМЕРА по кубическому закону
/// (вдвое крупнее ⇒ в 8 раз тяжелее): <c>масса = BaseMassKg × (размер / эталон)³</c>, где эталон —
/// базовый размер вида. Так детёныш/росток легче взрослого/зрелого, а ген размера двигает и массу.
/// Грузоподъёмность — задел под перенос предметов: доля массы тела × способность (манипуляция/движение).
/// </summary>
public static class Mass
{
/// <summary>Какую долю собственной массы существо может нести (×способность). Человек (руки) — больше.</summary>
public const float CarryFraction = 0.35f;
/// <summary>Доля массы тела, превращающаяся в мясо трупа (для разделки/падальщиков).</summary>
public const float MeatFraction = 0.5f;
private static float Cube(float v) => v * v * v;
/// <summary>Масса особи-животного (кг): эталон вида × куб (текущий линейный размер / эталонный).</summary>
public static float OfAnimal(AnimalDef def, in AnimalPhenotype traits, float stageScale)
{
var reference = def.Genome.GetValueOrDefault("GeneMaxBodySize", 1f);
if (reference <= 0f)
{
reference = 1f;
}
var linear = MathF.Max(0.01f, traits.BodySize) * MathF.Max(0.05f, stageScale);
return def.BaseMassKg * Cube(linear / reference);
}
/// <summary>Масса растения (кг): эталон вида × куб (размер текущей стадии / размер зрелой).</summary>
public static float OfPlant(PlantDef def, float stageSizeCells, float matureSizeCells)
{
var ratio = matureSizeCells > 0f ? stageSizeCells / matureSizeCells : 1f;
return def.BaseMassKg * Cube(ratio);
}
/// <summary>Грузоподъёмность (кг): доля массы тела × способность нести (манипуляция/движение, 0..1+).</summary>
public static float CarryCapacity(float bodyMassKg, float capability) =>
bodyMassKg * CarryFraction * MathF.Max(0f, capability);
/// <summary>
/// Метаболический множитель по закону Клайбера: расход энергии ∝ масса^0.75. Нормирован на эталонную
/// массу вида (взрослый базового размера = 1), поэтому крупные особи прожорливее, а молодняк — экономнее,
/// без межвидового взрыва баланса.
/// </summary>
public static float KleiberFactor(float massKg, float referenceMassKg)
{
if (referenceMassKg <= 0f || massKg <= 0f)
{
return 1f;
}
return MathF.Pow(massKg / referenceMassKg, 0.75f);
}
/// <summary>Множитель скорости от ноши: в пределах грузоподъёмности — 1; перегруз тормозит (до 0.25).</summary>
public static float LoadSpeedFactor(float carriedKg, float capacityKg)
{
if (capacityKg <= 0f || carriedKg <= capacityKg)
{
return 1f;
}
return MathF.Max(0.25f, capacityKg / carriedKg);
}
}
+18
View File
@@ -291,6 +291,14 @@ internal sealed class InspectPanel
text.AppendLine(languages.Format("inspect.age", grow.AgeDays, traits.Lifespan));
// Вес растения (∝ кубу размера текущей стадии относительно зрелой).
var plantMass = Mass.OfPlant(
species.Def,
species.Stages[grow.Stage].SizeCells,
species.Stages[^1].SizeCells
);
text.AppendLine(languages.Format("inspect.plantmass", plantMass));
// Состояние по температуре (рост/покой) и накопленный стресс.
var (tMin, tLow, tHigh, tMax) = traits.TemperatureBand();
var temperature = _climate.Temperature;
@@ -400,6 +408,16 @@ internal sealed class InspectPanel
text.AppendLine(languages.Format("inspect.age", grow.AgeDays, org.Traits.Lifespan));
var sex = languages.Get(org.IsMale ? "inspect.sex.male" : "inspect.sex.female");
text.AppendLine(languages.Format("inspect.animal.sex", sex, org.Generation));
// Вес тела и грузоподъёмность (∝ массе × способность нести; четвероногие — через Moving).
var stageScale = sp.Stages[grow.Stage].Scale;
var mass = Mass.OfAnimal(sp.Def, org.Traits, stageScale);
var carryCap =
entity.TryGetComponent<Health>(out var health) && health.State is not null
? Mass.CarryCapacity(mass, health.State.Capacity(AnimalCapacities.Moving))
: Mass.CarryCapacity(mass, 1f);
text.AppendLine(languages.Format("inspect.mass", mass, carryCap));
if (entity.TryGetComponent<Pregnant>(out var preg))
{
text.AppendLine(languages.Format("inspect.animal.pregnant", preg.DueInDays));