Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f38d949ab | ||
|
|
cc1841c2e2 | ||
|
|
4fda693994 | ||
|
|
d712afa518 |
@@ -19,6 +19,15 @@
|
||||
{ "defName": "GeneTemperatureTolerance", "parent": "BaseNumericGene", "label": "gene.temperatureTolerance",
|
||||
"default": 16, "min": 2, "max": 40, "tags": ["environment", "temperature"],
|
||||
"effects": { "temperatureTolerance": "value" } },
|
||||
// Жёсткие края диапазона роста (модель RimWorld): на сколько °C ниже/выше плато оптимума рост
|
||||
// спадает до нуля. Ниже (optimal-tolerance-coldHardiness) или выше (optimal+tolerance+heatHardiness)
|
||||
// растение дормантно и копит температурный стресс. Асимметрично: вид может терпеть жару лучше холода.
|
||||
{ "defName": "GeneColdHardiness", "parent": "BaseNumericGene", "label": "gene.coldHardiness",
|
||||
"default": 8, "min": 0, "max": 40, "tags": ["environment", "temperature"],
|
||||
"effects": { "coldHardiness": "value" } },
|
||||
{ "defName": "GeneHeatHardiness", "parent": "BaseNumericGene", "label": "gene.heatHardiness",
|
||||
"default": 10, "min": 0, "max": 40, "tags": ["environment", "temperature"],
|
||||
"effects": { "heatHardiness": "value" } },
|
||||
{ "defName": "GeneOptimalFertility", "parent": "BaseNumericGene", "label": "gene.optimalFertility",
|
||||
"default": 1.4, "min": 0.1, "max": 3.0, "tags": ["environment", "soil"],
|
||||
"effects": { "optimalFertility": "value" } },
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
"defs": [
|
||||
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
||||
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
|
||||
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 14, "temperatureTolerance": 16,
|
||||
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 14, "temperatureTolerance": 10,
|
||||
"coldHardiness": 14, "heatHardiness": 12,
|
||||
"optimalFertility": 1.4, "fertilityTolerance": 0.9, "vigor": 1.0,
|
||||
"lifespan": 160, "dispersalRange": 2, "reproduceInterval": 14, "selfPollination": 0.2,
|
||||
"mutationRate": 0.05, "variantChance": 0.15, "spread": 0.08,
|
||||
@@ -20,7 +21,8 @@
|
||||
|
||||
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.2,
|
||||
"harvestProduct": "ProductGrass",
|
||||
"genome": { "optimalLight": 0.85, "lightTolerance": 0.35, "optimalTemperature": 20, "temperatureTolerance": 12,
|
||||
"genome": { "optimalLight": 0.85, "lightTolerance": 0.35, "optimalTemperature": 22, "temperatureTolerance": 8,
|
||||
"coldHardiness": 8, "heatHardiness": 6,
|
||||
"optimalFertility": 1.0, "fertilityTolerance": 1.0, "vigor": 1.3,
|
||||
"lifespan": 22, "dispersalRange": 3, "reproduceInterval": 3, "selfPollination": 0.8,
|
||||
"mutationRate": 0.06, "variantChance": 0.2, "spread": 0.1,
|
||||
@@ -32,7 +34,8 @@
|
||||
|
||||
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
|
||||
"fruitProduct": "ProductBerry",
|
||||
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 17, "temperatureTolerance": 13,
|
||||
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 17, "temperatureTolerance": 9,
|
||||
"coldHardiness": 10, "heatHardiness": 8,
|
||||
"optimalFertility": 1.1, "fertilityTolerance": 1.0, "vigor": 1.0,
|
||||
"lifespan": 60, "dispersalRange": 2, "reproduceInterval": 7, "selfPollination": 0.5,
|
||||
"mutationRate": 0.05, "variantChance": 0.18, "spread": 0.1,
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# Roadmap LittleSim
|
||||
|
||||
Этап закрыт, когда механика работает, наблюдаема (HUD/консоль/визуал)
|
||||
и управляется командой консоли.
|
||||
|
||||
## Этап 0 — Каркас ✅
|
||||
- [x] Репозиторий с движком-сабмодулем, общий solution
|
||||
- [x] Генерация рельефа по сиду (вода/песок/трава/лес/горы)
|
||||
- [x] Жители-заглушки (блуждание), камера бога, HUD, консоль (`regen`)
|
||||
|
||||
## Этап 1 — Жители как агенты
|
||||
- [~] Потребности: голод, усталость (тикают со временем) — усталость/энергия готова (`PawnNeeds`), голод впереди
|
||||
- [~] Utility-выбор действия: искать еду / отдыхать / бродить — каркас на движке (`MrGameEng.AI`), выбор «отдыхать/бродить» работает; «искать еду» впереди
|
||||
- [ ] Еда на траве/в лесу: растёт, собирается, истощается
|
||||
- [ ] Смерть от голода; визуальное состояние жителя (цвет/прозрачность)
|
||||
- [ ] Консоль: `spawn <n>`, `feed`, `starve` для тестов
|
||||
|
||||
## Этап 2 — Общество
|
||||
- [ ] Дома и поселения (житель несёт еду домой, спит дома)
|
||||
- [ ] Размножение при сытости; рост населения
|
||||
- [ ] Простые профессии: собиратель, строитель
|
||||
|
||||
## Этап 3 — Бог
|
||||
- [ ] Вера как ресурс (генерируется жителями)
|
||||
- [ ] Первые силы: дождь (ускоряет рост еды), молния (поджигает лес)
|
||||
- [ ] Терраформинг клеток
|
||||
|
||||
## Этап 4 — Экология и время
|
||||
- [ ] День/ночь, сезоны
|
||||
- [ ] Восстановление/деградация ресурсов, пожары
|
||||
- [ ] Миграции при нехватке ресурсов
|
||||
|
||||
## Бэклог
|
||||
- Сохранение/загрузка мира (сериализация Friflo)
|
||||
- Фиксированный тик симуляции, независимый от рендера
|
||||
- Исторические графики (население, еда) в дев-оверлее
|
||||
@@ -1,84 +0,0 @@
|
||||
# Растения как экосистема
|
||||
|
||||
Дизайн системы растений: рост зависит от среды, среда описана факторами, реакция растения на
|
||||
факторы задаётся **генами**, а размножение передаёт гены потомкам с мутацией — появляется
|
||||
естественный отбор. Документ — единый ориентир для фаз A–D; детали каждой фазы уточняются её
|
||||
отдельным планом.
|
||||
|
||||
## Модель роста
|
||||
|
||||
Скорость роста растения в данный момент:
|
||||
|
||||
```
|
||||
скорость = база × пригодность(свет) × пригодность(температура) × пригодность(плодородность)
|
||||
```
|
||||
|
||||
Каждая «пригодность» — число в [0..1] из кривой отклика (движковый `ResponseCurve`, модуль
|
||||
`MrGameEng.AI`) с **оптимумом** и **шириной толерантности**, которые берутся из генов растения.
|
||||
Растение вне своих оптимумов растёт медленно или стоит; в своих — быстро. Так все факторы среды
|
||||
сводятся к одному механизму, а различие видов/особей — целиком в генах.
|
||||
|
||||
Фактор «возраст/стадия» остаётся как сейчас (`PlantGrowthStage` в дефах): пригодности умножают
|
||||
прирост `AgeDays`, стадии переключают спрайт/размер по накопленному возрасту.
|
||||
|
||||
## Факторы среды
|
||||
|
||||
- **Свет** — амбиент день/ночь (`MrGameEng.Lighting.DayNight`), сэмплируемый в точке
|
||||
(`SampleAt(world)`); глобальный в фазе A, локальный (с тенями) в фазе D.
|
||||
- **Температура** — непрерывная, из климата (`MrGameEng.Core.Climate`): сезонный синус по дню года
|
||||
+ суточные колебания (холоднее ночью).
|
||||
- **Плодородность** — пока константа типа террейна (`TerrainDef.Fertility`); позже возможна
|
||||
по-клеточная почва с истощением/восстановлением.
|
||||
|
||||
## Гены (диплоидный геном, гибридная экспрессия)
|
||||
|
||||
Каждое растение несёт **геном**: по паре аллелей на ген. Экспрессия гибридная:
|
||||
|
||||
- **Числовые гены** (оптимум света, толерантность к свету, оптимум температуры, толерантность,
|
||||
потребность в плодородности, макс. размер, базовая скорость, продолжительность жизни, дальность
|
||||
расселения, плодовитость, темп мутаций, способность к самоопылению) — фенотип = **смешение**
|
||||
(среднее двух аллелей). Реализм отбора: промежуточные значения, плавный дрейф.
|
||||
- **Дискретные гены** (например, цвет/морфа, наличие признака) — **доминант/рецессив**: рецессивный
|
||||
фенотип виден только в гомозиготе (aa).
|
||||
|
||||
Наследование (мейоз): каждый родитель отдаёт по одному случайному аллелю на ген; потомок получает
|
||||
по одному от каждого. Затем — мутация (с вероятностью из гена темпа мутаций сдвигаем числовой
|
||||
аллель/переключаем дискретный). Всё детерминировано сидом мира.
|
||||
|
||||
Стартовая популяция: геномы генерируются из сида (вид задаёт базовый геном и разброс аллелей в
|
||||
дефе; особи отклоняются).
|
||||
|
||||
## Размножение (полуреалистичное)
|
||||
|
||||
- **Перекрёстное опыление (приоритет):** зрелое растение ищет зрелого соседа того же вида в радиусе
|
||||
(ген дальности; запрос через spatial-hash движка). Есть партнёр → семя с геномом от двух
|
||||
родителей (Мендель + мутация).
|
||||
- **Самоопыление (запас):** нет партнёра и ген «способность к самоопылению» позволяет → семя из
|
||||
одного родителя (само-скрещивание аллелей + мутация).
|
||||
- **Расселение:** семя падает в клетку в радиусе расселения; прорастает, если клетка подходит
|
||||
(суша, не занято сверх лимита, пригодность не нулевая).
|
||||
- **Лимит плотности:** не больше N растений на клетку/площадь — иначе взрыв численности.
|
||||
- **Старение/смерть:** возраст превышает ген жизни → смерть; или длительный стресс (пригодность
|
||||
около нуля) → гибель. Смерть освобождает место — баланс с размножением и основа отбора.
|
||||
|
||||
## Производительность и сейв
|
||||
|
||||
- Представление — **по сущности на растение** (видно в рендере и ECS-инспекторе, индивидуальные
|
||||
гены). Защита от взрыва: лимит плотности, смерть, разумные радиусы, проверки размножения по
|
||||
таймеру/сезону (не каждый кадр на каждое растение), spatial-hash для соседей.
|
||||
- **Персист:** геномы, позиции, возраст и стадия растений сохраняются в `WorldSave` (мир больше не
|
||||
воспроизводится только из сида — эволюция переживает загрузку).
|
||||
- **Детерминизм:** мутации/расселение/опыление — на сидированном `Random`, как в остальной симуляции.
|
||||
|
||||
## Фазы реализации
|
||||
|
||||
- **A — климат и день/ночь (движок).** `Climate` (температура/сезон поверх `Calendar`),
|
||||
`DayNight` (амбиент, `Renderer2D.AmbientLight` на World-слоях, сэмплируемый `SampleAt`),
|
||||
затемнение мира ночью, HUD: сезон/температура/время. Фундамент для влияния среды на рост.
|
||||
- **B — гены и рост по пригодностям (игра).** Компонент `PlantGenome`, расчёт пригодностей
|
||||
(свет из `DayNight.SampleAt`, температура из `Climate`, плодородность из террейна), замена
|
||||
плоского `GrowthRate`. Геном виден/правится в инспекторе.
|
||||
- **C — размножение и смерть (игра).** Перекрёстное/самоопыление, расселение, лимит плотности,
|
||||
старение/стресс-смерть, персист популяции в сейв.
|
||||
- **D — полноценное 2D-освещение (движок).** Точечные источники + тени (окклюзия от препятствий);
|
||||
`DayNight.SampleAt` начинает возвращать локальную освещённость, и затенённые растения растут хуже.
|
||||
+1
-1
Submodule engine updated: d044cafad9...d360093be1
@@ -93,9 +93,15 @@ public sealed class GenomeDef
|
||||
/// <summary>Оптимальная температура (°C) и её толерантность.</summary>
|
||||
public float OptimalTemperature { get; init; } = 18f;
|
||||
|
||||
/// <summary>Ширина толерантности по температуре (°C).</summary>
|
||||
/// <summary>Ширина толерантности по температуре (°C) — половина плато полного роста вокруг оптимума.</summary>
|
||||
public float TemperatureTolerance { get; init; } = 12f;
|
||||
|
||||
/// <summary>Морозостойкость (°C): на сколько ниже плато рост спадает до нуля (жёсткая нижняя граница).</summary>
|
||||
public float ColdHardiness { get; init; } = 8f;
|
||||
|
||||
/// <summary>Жаростойкость (°C): на сколько выше плато рост спадает до нуля (жёсткая верхняя граница).</summary>
|
||||
public float HeatHardiness { get; init; } = 10f;
|
||||
|
||||
/// <summary>Оптимальная плодородность почвы и её толерантность.</summary>
|
||||
public float OptimalFertility { get; init; } = 1f;
|
||||
|
||||
|
||||
@@ -103,6 +103,8 @@ public sealed class PlantSet
|
||||
Numeric("GeneLightTolerance", g.LightTolerance),
|
||||
Numeric("GeneOptimalTemperature", g.OptimalTemperature),
|
||||
Numeric("GeneTemperatureTolerance", g.TemperatureTolerance),
|
||||
Numeric("GeneColdHardiness", g.ColdHardiness),
|
||||
Numeric("GeneHeatHardiness", g.HeatHardiness),
|
||||
Numeric("GeneOptimalFertility", g.OptimalFertility),
|
||||
Numeric("GeneFertilityTolerance", g.FertilityTolerance),
|
||||
Numeric("GeneVigor", g.Vigor),
|
||||
|
||||
@@ -168,6 +168,7 @@ public sealed class WorldScene : Scene
|
||||
Store,
|
||||
_plants,
|
||||
calendar,
|
||||
climate,
|
||||
Context.Clock,
|
||||
_config.Width,
|
||||
_config.Height,
|
||||
@@ -587,6 +588,11 @@ public sealed class WorldScene : Scene
|
||||
$" vigor {traits.Vigor:0.##}, lifespan {traits.Lifespan:0} d, "
|
||||
+ $"optimalLight {traits.OptimalLight:0.##}, leafHue {traits.LeafHue:0.##}"
|
||||
);
|
||||
var (tMin, tLow, tHigh, tMax) = traits.TemperatureBand();
|
||||
console.WriteLine(
|
||||
$" temp: grows {tMin:0.#}..{tMax:0.#}°C, optimal {tLow:0.#}..{tHigh:0.#}°C "
|
||||
+ $"(cold {traits.ColdHardiness:0.#}, heat {traits.HeatHardiness:0.#})"
|
||||
);
|
||||
|
||||
if (def.HarvestProduct is { } harvest)
|
||||
{
|
||||
|
||||
@@ -56,7 +56,8 @@ public static class PlantFactory
|
||||
CellFertility = cellFertility,
|
||||
},
|
||||
new PlantOrganism { Genome = genome, Traits = traits },
|
||||
new Fruiting()
|
||||
new Fruiting(),
|
||||
new TemperatureStress()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,14 +53,12 @@ public sealed class PlantGrowthSystem(
|
||||
|
||||
ref var traits = ref orgs[i].Traits;
|
||||
var light = lighting.SampleAt(t[i].Position);
|
||||
// Температура — трапеция RimWorld: плато полного роста, жёсткие края (дормантность вне).
|
||||
var (tMin, tLow, tHigh, tMax) = traits.TemperatureBand();
|
||||
var rate =
|
||||
traits.Vigor
|
||||
* Suitability.Gaussian(light, traits.OptimalLight, traits.LightTolerance)
|
||||
* Suitability.Gaussian(
|
||||
temperature,
|
||||
traits.OptimalTemperature,
|
||||
traits.TemperatureTolerance
|
||||
)
|
||||
* Suitability.Trapezoid(temperature, tMin, tLow, tHigh, tMax)
|
||||
* Suitability.Gaussian(
|
||||
grow.CellFertility,
|
||||
traits.OptimalFertility,
|
||||
|
||||
@@ -2,6 +2,7 @@ using Friflo.Engine.ECS;
|
||||
using Friflo.Engine.ECS.Systems;
|
||||
using LittleSim.Content;
|
||||
using Microsoft.Xna.Framework;
|
||||
using MrGameEng.AI;
|
||||
using MrGameEng.Core;
|
||||
using MrGameEng.Genetics;
|
||||
using MrGameEng.Graphics;
|
||||
@@ -9,21 +10,30 @@ using MrGameEng.Graphics;
|
||||
namespace LittleSim.Sim;
|
||||
|
||||
/// <summary>
|
||||
/// Жизненный цикл растений: размножение (наследование по Менделю) и смерть от старости. Запускается
|
||||
/// не каждый кадр, а раз в <see cref="IntervalDays"/> игрового дня. За тик: строит по-клеточную сетку
|
||||
/// (счётчик плотности + «представитель» вида/генома), собирает умерших по возрасту, затем зрелые с
|
||||
/// вероятностью <c>elapsed/ReproduceInterval</c> сеют потомка — приоритет перекрёстного опыления
|
||||
/// (зрелый сосед того же вида в радиусе расселения), иначе самоопыление по гену. Семя падает в
|
||||
/// случайную клетку радиуса, если это суша и не превышен лимит плотности. Структурные изменения
|
||||
/// (создание/удаление сущностей) применяются после проходов по запросу — это безопасно.
|
||||
/// Жизненный цикл растений: размножение (наследование по Менделю), смерть от старости и от
|
||||
/// температурного стресса (вымерзание/перегрев). Запускается не каждый кадр, а раз в
|
||||
/// <see cref="IntervalDays"/> игрового дня. За тик: строит по-клеточную сетку (счётчик плотности +
|
||||
/// «представитель» вида/генома), копит/рассасывает температурный стресс по текущей температуре,
|
||||
/// собирает умерших (старость или превышенный стресс), затем зрелые с вероятностью
|
||||
/// <c>elapsed/ReproduceInterval</c> сеют потомка — приоритет перекрёстного опыления (зрелый сосед
|
||||
/// того же вида в радиусе расселения), иначе самоопыление по гену. Семя падает в случайную клетку
|
||||
/// радиуса, если это суша и не превышен лимит плотности. Структурные изменения (создание/удаление
|
||||
/// сущностей) применяются после проходов по запросу — это безопасно.
|
||||
/// </summary>
|
||||
public sealed class PlantLifecycleSystem : BaseSystem
|
||||
{
|
||||
private const float IntervalDays = 0.25f;
|
||||
|
||||
/// <summary>Сколько игровых дней дормантности вне диапазона роста убивают растение.</summary>
|
||||
private const float LethalStressDays = 6f;
|
||||
|
||||
/// <summary>Во сколько раз стресс рассасывается быстрее в пригодной температуре, чем копится вне.</summary>
|
||||
private const float StressRecovery = 1.5f;
|
||||
|
||||
private readonly EntityStore _store;
|
||||
private readonly PlantSet _plants;
|
||||
private readonly Calendar _calendar;
|
||||
private readonly Climate _climate;
|
||||
private readonly GameClock _clock;
|
||||
private readonly int _width;
|
||||
private readonly int _height;
|
||||
@@ -32,7 +42,12 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
||||
private readonly bool[] _cellLand;
|
||||
private readonly int _densityCap;
|
||||
private readonly Random _rng;
|
||||
private readonly ArchetypeQuery<PlantGrowth, PlantOrganism, Transform2D> _query;
|
||||
private readonly ArchetypeQuery<
|
||||
PlantGrowth,
|
||||
PlantOrganism,
|
||||
Transform2D,
|
||||
TemperatureStress
|
||||
> _query;
|
||||
|
||||
private readonly int[] _count;
|
||||
private readonly CellRep[] _rep;
|
||||
@@ -45,6 +60,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
||||
EntityStore store,
|
||||
PlantSet plants,
|
||||
Calendar calendar,
|
||||
Climate climate,
|
||||
GameClock clock,
|
||||
int width,
|
||||
int height,
|
||||
@@ -58,6 +74,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
||||
_store = store;
|
||||
_plants = plants;
|
||||
_calendar = calendar;
|
||||
_climate = climate;
|
||||
_clock = clock;
|
||||
_width = width;
|
||||
_height = height;
|
||||
@@ -66,7 +83,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
||||
_cellLand = cellLand;
|
||||
_densityCap = densityCap;
|
||||
_rng = new Random(seed);
|
||||
_query = store.Query<PlantGrowth, PlantOrganism, Transform2D>();
|
||||
_query = store.Query<PlantGrowth, PlantOrganism, Transform2D, TemperatureStress>();
|
||||
_count = new int[width * height];
|
||||
_rep = new CellRep[width * height];
|
||||
}
|
||||
@@ -82,13 +99,13 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
||||
var elapsed = _accumulator;
|
||||
_accumulator = 0f;
|
||||
|
||||
BuildGridAndCollect();
|
||||
BuildGridAndCollect(elapsed);
|
||||
Reproduce(elapsed);
|
||||
Apply();
|
||||
}
|
||||
|
||||
// Проход 1: сетка плотности + представители, сбор умерших и зрелых-кандидатов.
|
||||
private void BuildGridAndCollect()
|
||||
// Проход 1: сетка плотности + представители, температурный стресс, сбор умерших и зрелых-кандидатов.
|
||||
private void BuildGridAndCollect(float elapsed)
|
||||
{
|
||||
Array.Clear(_count);
|
||||
for (var c = 0; c < _rep.Length; c++)
|
||||
@@ -100,11 +117,14 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
||||
_candidates.Clear();
|
||||
_births.Clear();
|
||||
|
||||
foreach (var (growths, organisms, transforms, entities) in _query.Chunks)
|
||||
var temperature = _climate.Temperature; // глобальная — едина для всех растений в этот тик
|
||||
|
||||
foreach (var (growths, organisms, transforms, stresses, entities) in _query.Chunks)
|
||||
{
|
||||
var g = growths.Span;
|
||||
var orgs = organisms.Span;
|
||||
var t = transforms.Span;
|
||||
var st = stresses.Span;
|
||||
for (var i = 0; i < g.Length; i++)
|
||||
{
|
||||
ref var grow = ref g[i];
|
||||
@@ -115,6 +135,23 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
||||
continue;
|
||||
}
|
||||
|
||||
// Температурный стресс: вне диапазона роста (трапеция = 0) копится, внутри — рассасывается.
|
||||
var (tMin, tLow, tHigh, tMax) = org.Traits.TemperatureBand();
|
||||
ref var stress = ref st[i].Days;
|
||||
if (Suitability.Trapezoid(temperature, tMin, tLow, tHigh, tMax) <= 0f)
|
||||
{
|
||||
stress += elapsed;
|
||||
if (stress >= LethalStressDays)
|
||||
{
|
||||
_deaths.Add(entities.EntityAt(i)); // вымерзло/перегрелось
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stress = MathF.Max(0f, stress - elapsed * StressRecovery);
|
||||
}
|
||||
|
||||
var pos = t[i].Position;
|
||||
var cx = Math.Clamp((int)(pos.X / _cellSize), 0, _width - 1);
|
||||
var cy = Math.Clamp((int)(pos.Y / _cellSize), 0, _height - 1);
|
||||
|
||||
@@ -15,6 +15,8 @@ public struct PlantPhenotype
|
||||
public float LightTolerance;
|
||||
public float OptimalTemperature;
|
||||
public float TemperatureTolerance;
|
||||
public float ColdHardiness;
|
||||
public float HeatHardiness;
|
||||
public float OptimalFertility;
|
||||
public float FertilityTolerance;
|
||||
public float Vigor;
|
||||
@@ -33,6 +35,18 @@ public struct PlantPhenotype
|
||||
/// <summary>Морфа: рецессивный вариант (выраженное значение гена морфы ≈ 1) — другой тинт спрайта.</summary>
|
||||
public bool IsVariant;
|
||||
|
||||
/// <summary>
|
||||
/// Четыре точки температурной трапеции роста (модель RimWorld): жёсткий минимум, нижний край плато,
|
||||
/// верхний край плато, жёсткий максимум. Плато — оптимум ± толерантность; жёсткие края отстоят от
|
||||
/// плато на морозо-/жаростойкость. Кормит <see cref="MrGameEng.AI.Suitability.Trapezoid"/>.
|
||||
/// </summary>
|
||||
public readonly (float Min, float OptimalLow, float OptimalHigh, float Max) TemperatureBand()
|
||||
{
|
||||
var low = OptimalTemperature - TemperatureTolerance;
|
||||
var high = OptimalTemperature + TemperatureTolerance;
|
||||
return (low - ColdHardiness, low, high, high + HeatHardiness);
|
||||
}
|
||||
|
||||
/// <summary>Собирает фенотип из карты признаков, посчитанной <see cref="Phenotype.Compute"/>.</summary>
|
||||
public static PlantPhenotype FromTraits(IReadOnlyDictionary<string, float> traits)
|
||||
{
|
||||
@@ -43,6 +57,8 @@ public struct PlantPhenotype
|
||||
LightTolerance = T("lightTolerance"),
|
||||
OptimalTemperature = T("optimalTemperature"),
|
||||
TemperatureTolerance = T("temperatureTolerance"),
|
||||
ColdHardiness = T("coldHardiness"),
|
||||
HeatHardiness = T("heatHardiness"),
|
||||
OptimalFertility = T("optimalFertility"),
|
||||
FertilityTolerance = T("fertilityTolerance"),
|
||||
Vigor = T("vigor"),
|
||||
@@ -85,3 +101,15 @@ public struct Fruiting : IComponent
|
||||
/// <summary>Накоплено зрелых плодов (0..FruitYield).</summary>
|
||||
public float RipeFruit;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Накопленный температурный стресс растения в игровых днях. Пока температура вне диапазона роста
|
||||
/// (трапеция <see cref="PlantPhenotype.TemperatureBand"/> даёт ноль — слишком холодно или жарко),
|
||||
/// стресс копится; внутри диапазона — рассасывается. Превышение порога убивает растение
|
||||
/// (вымерзание/перегрев). Управляется <c>PlantLifecycleSystem</c>.
|
||||
/// </summary>
|
||||
public struct TemperatureStress : IComponent
|
||||
{
|
||||
/// <summary>Накоплено дней дормантности вне диапазона роста; при превышении лимита — гибель.</summary>
|
||||
public float Days;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user