Birth type gene (live-bearing vs egg-laying) + hybrid-creation foundation
GeneEggLaying (species-fixed) selects the conception cycle: viviparous animals carry to a live birth (existing path); oviparous animals, after the same gravid period, LAY eggs instead. New Egg entity (carries the already-bred offspring genome) + EggSystem incubates and hatches it into a baby. AnimalPregnancySystem branches on the mother's birth type. Adds an oviparous showcase species, Chicken. Eggs are serialized (WorldSave.Eggs) like corpses, so clutches survive save/load. Also lays the foundation for creating any species or hybrid (full command/UI deferred): AnimalSet.HybridGenome(base, other) produces a cross anchored to a base species' body/gene-set with shared genes blended Mendelian-style — so Create(species, GenerateGenome) makes any species and Create(species, HybridGenome) makes any hybrid. EggTexture on AnimalDef (defaults to the seed dot placeholder); reproduction line added to the `animal` console command. Build + --check-content clean (41 genes, 40 traits). Not GUI-verified. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
e6b1367a3a
commit
b2e3eb490e
@@ -101,6 +101,36 @@
|
|||||||
"GeneOmnivory": 0.85,
|
"GeneOmnivory": 0.85,
|
||||||
"GeneToxinTolerance": 0.25
|
"GeneToxinTolerance": 0.25
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"defName": "Chicken",
|
||||||
|
"label": "pawn.chicken",
|
||||||
|
"kind": "animal",
|
||||||
|
"texture": "things/pawn/animal/chicken/Chicken_east",
|
||||||
|
"body": "Quadruped",
|
||||||
|
"diet": ["plant"],
|
||||||
|
"spawnPer1000Cells": 1.5,
|
||||||
|
"baseSpeed": 22,
|
||||||
|
"visionCells": 10,
|
||||||
|
"genome": {
|
||||||
|
"GeneMaxBodySize": 0.5,
|
||||||
|
"GeneMetabolism": 1.3,
|
||||||
|
"GeneMoveSpeed": 0.9,
|
||||||
|
"GeneBloodVolume": 0.7,
|
||||||
|
"GeneVision": 1.1,
|
||||||
|
"GeneBrainSize": 0.3,
|
||||||
|
"GeneInsulation": 0.45,
|
||||||
|
"GeneFurColor": 0.6,
|
||||||
|
"GeneMaturityAge": 50,
|
||||||
|
"GeneLifespan": 180,
|
||||||
|
"GeneBreedingSeason": 1,
|
||||||
|
"GeneGestationDays": 12,
|
||||||
|
"GeneLitterSize": 6,
|
||||||
|
"GeneHerbivory": 0.8,
|
||||||
|
"GeneToxinTolerance": 0.15,
|
||||||
|
"GeneEggLaying": 1.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,13 @@
|
|||||||
{ "defName": "GeneSex", "kind": "Discrete", "label": "gene.sex",
|
{ "defName": "GeneSex", "kind": "Discrete", "label": "gene.sex",
|
||||||
"variants": 2, "variantWeights": [0.5, 0.5], "mutationChance": 0, "tags": ["animal", "sex"] },
|
"variants": 2, "variantWeights": [0.5, 0.5], "mutationChance": 0, "tags": ["animal", "sex"] },
|
||||||
|
|
||||||
|
// Тип рождения (цикл зачатия): живородящие (eggLaying < 0.5) вынашивают и рожают живых детёнышей;
|
||||||
|
// яйцекладущие (≥ 0.5) после вынашивания ОТКЛАДЫВАЮТ яйца-сущности, которые инкубируются и
|
||||||
|
// вылупляются (см. AnimalPregnancySystem/EggSystem). Признак фиксирован по виду (spread/mutation 0).
|
||||||
|
{ "defName": "GeneEggLaying", "parent": "BaseNumericGene", "label": "gene.eggLaying",
|
||||||
|
"default": 0.0, "min": 0.0, "max": 1.0, "spread": 0, "mutationChance": 0, "tags": ["animal", "reproduction"],
|
||||||
|
"effects": { "eggLaying": "value" } },
|
||||||
|
|
||||||
// Размножение (фаза A4): сезон гона фиксирован по виду (0 весна … 3 зима), срок вынашивания и помёт.
|
// Размножение (фаза A4): сезон гона фиксирован по виду (0 весна … 3 зима), срок вынашивания и помёт.
|
||||||
{ "defName": "GeneBreedingSeason", "parent": "BaseNumericGene", "label": "gene.breedingSeason",
|
{ "defName": "GeneBreedingSeason", "parent": "BaseNumericGene", "label": "gene.breedingSeason",
|
||||||
"default": 2, "min": 0, "max": 3, "spread": 0, "mutationChance": 0, "tags": ["animal", "reproduction"],
|
"default": 2, "min": 0, "max": 3, "spread": 0, "mutationChance": 0, "tags": ["animal", "reproduction"],
|
||||||
|
|||||||
@@ -163,6 +163,7 @@
|
|||||||
"pawn.hare": "hare",
|
"pawn.hare": "hare",
|
||||||
"pawn.boar": "boar",
|
"pawn.boar": "boar",
|
||||||
"pawn.wolf": "wolf",
|
"pawn.wolf": "wolf",
|
||||||
|
"pawn.chicken": "chicken",
|
||||||
"pawn.muffalo": "muffalo",
|
"pawn.muffalo": "muffalo",
|
||||||
"pawn.squirrel": "squirrel",
|
"pawn.squirrel": "squirrel",
|
||||||
"thought.gaveBirth": "gave birth",
|
"thought.gaveBirth": "gave birth",
|
||||||
|
|||||||
@@ -163,6 +163,7 @@
|
|||||||
"pawn.hare": "заяц",
|
"pawn.hare": "заяц",
|
||||||
"pawn.boar": "кабан",
|
"pawn.boar": "кабан",
|
||||||
"pawn.wolf": "волк",
|
"pawn.wolf": "волк",
|
||||||
|
"pawn.chicken": "курица",
|
||||||
"pawn.muffalo": "муффало",
|
"pawn.muffalo": "муффало",
|
||||||
"pawn.squirrel": "белка",
|
"pawn.squirrel": "белка",
|
||||||
"thought.gaveBirth": "родила потомство",
|
"thought.gaveBirth": "родила потомство",
|
||||||
|
|||||||
@@ -173,6 +173,28 @@ public sealed class CorpseSave
|
|||||||
public float Meat { get; set; }
|
public float Meat { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Сериализуемое яйцо (тип рождения «яйцекладка»): вид, позиция, геном детёныша, поколение, инкубация.</summary>
|
||||||
|
public sealed class EggSave
|
||||||
|
{
|
||||||
|
/// <summary>Имя дефа вида — по нему берётся индекс/спрайт/тело при вылуплении.</summary>
|
||||||
|
public string Species { get; set; } = "";
|
||||||
|
|
||||||
|
/// <summary>Позиция X в мировых координатах.</summary>
|
||||||
|
public float X { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Позиция Y в мировых координатах.</summary>
|
||||||
|
public float Y { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Поколение будущего детёныша.</summary>
|
||||||
|
public int Generation { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Остаток инкубации в игровых днях.</summary>
|
||||||
|
public float IncubateDays { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Геном будущего детёныша (уже скрещён на момент кладки).</summary>
|
||||||
|
public Dictionary<string, Allele> Genome { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Полное состояние мира для сохранения/загрузки: конфиг мира (имя/размер/сид/сглаживание),
|
/// Полное состояние мира для сохранения/загрузки: конфиг мира (имя/размер/сид/сглаживание),
|
||||||
/// прошедшее время симуляции и снимок всех жителей. Рельеф не сохраняется — он
|
/// прошедшее время симуляции и снимок всех жителей. Рельеф не сохраняется — он
|
||||||
@@ -216,6 +238,9 @@ public sealed class WorldSave
|
|||||||
/// <summary>Снимок трупов (вид, позиция, таймер/стадия разложения, мясо).</summary>
|
/// <summary>Снимок трупов (вид, позиция, таймер/стадия разложения, мясо).</summary>
|
||||||
public List<CorpseSave> Corpses { get; set; } = [];
|
public List<CorpseSave> Corpses { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Снимок яиц (вид, позиция, геном детёныша, поколение, инкубация).</summary>
|
||||||
|
public List<EggSave> Eggs { get; set; } = [];
|
||||||
|
|
||||||
/// <summary>Конфиг мира для пересоздания сцены.</summary>
|
/// <summary>Конфиг мира для пересоздания сцены.</summary>
|
||||||
public WorldConfig ToConfig() =>
|
public WorldConfig ToConfig() =>
|
||||||
new()
|
new()
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ public sealed class AnimalSet
|
|||||||
/// <summary>Спрайт трупа (fallback — самка).</summary>
|
/// <summary>Спрайт трупа (fallback — самка).</summary>
|
||||||
public required Texture2DRegion Corpse { get; init; }
|
public required Texture2DRegion Corpse { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Спрайт яйца (тип рождения «яйцекладка»); по умолчанию — общий спрайт-точка.</summary>
|
||||||
|
public required Texture2DRegion Egg { get; init; }
|
||||||
|
|
||||||
/// <summary>Стадии роста (из дефа или встроенный дефолт), по возрастанию возраста входа.</summary>
|
/// <summary>Стадии роста (из дефа или встроенный дефолт), по возрастанию возраста входа.</summary>
|
||||||
public required AnimalStageDef[] Stages { get; init; }
|
public required AnimalStageDef[] Stages { get; init; }
|
||||||
|
|
||||||
@@ -43,10 +46,40 @@ public sealed class AnimalSet
|
|||||||
// Встроенный набор стадий по умолчанию (если вид не задал свой): как было до выноса в данные.
|
// Встроенный набор стадий по умолчанию (если вид не задал свой): как было до выноса в данные.
|
||||||
private static readonly AnimalStageDef[] DefaultStages =
|
private static readonly AnimalStageDef[] DefaultStages =
|
||||||
[
|
[
|
||||||
new() { Name = "Baby", EnterAt = 0f, RelativeTo = "maturity", Scale = 0.45f, Texture = "baby" },
|
new()
|
||||||
new() { Name = "Juvenile", EnterAt = 0.22f, RelativeTo = "maturity", Scale = 0.7f, Texture = "baby" },
|
{
|
||||||
new() { Name = "Adult", EnterAt = 1f, RelativeTo = "maturity", Scale = 1f, Texture = "adult", Adult = true },
|
Name = "Baby",
|
||||||
new() { Name = "Senior", EnterAt = 0.8f, RelativeTo = "lifespan", Scale = 0.92f, Texture = "adult", Adult = true },
|
EnterAt = 0f,
|
||||||
|
RelativeTo = "maturity",
|
||||||
|
Scale = 0.45f,
|
||||||
|
Texture = "baby",
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = "Juvenile",
|
||||||
|
EnterAt = 0.22f,
|
||||||
|
RelativeTo = "maturity",
|
||||||
|
Scale = 0.7f,
|
||||||
|
Texture = "baby",
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = "Adult",
|
||||||
|
EnterAt = 1f,
|
||||||
|
RelativeTo = "maturity",
|
||||||
|
Scale = 1f,
|
||||||
|
Texture = "adult",
|
||||||
|
Adult = true,
|
||||||
|
},
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Name = "Senior",
|
||||||
|
EnterAt = 0.8f,
|
||||||
|
RelativeTo = "lifespan",
|
||||||
|
Scale = 0.92f,
|
||||||
|
Texture = "adult",
|
||||||
|
Adult = true,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
private readonly Species[] _species;
|
private readonly Species[] _species;
|
||||||
@@ -82,6 +115,9 @@ public sealed class AnimalSet
|
|||||||
Corpse = string.IsNullOrEmpty(def.CorpseTexture)
|
Corpse = string.IsNullOrEmpty(def.CorpseTexture)
|
||||||
? female
|
? female
|
||||||
: atlases.GetRegion(device, def.CorpseTexture),
|
: atlases.GetRegion(device, def.CorpseTexture),
|
||||||
|
Egg = string.IsNullOrEmpty(def.EggTexture)
|
||||||
|
? female
|
||||||
|
: atlases.GetRegion(device, def.EggTexture),
|
||||||
Stages = def.Stages.Length > 0 ? def.Stages : DefaultStages,
|
Stages = def.Stages.Length > 0 ? def.Stages : DefaultStages,
|
||||||
Body = content.Defs.TryGet<BodyDef>(def.Body, out var body) ? body : null,
|
Body = content.Defs.TryGet<BodyDef>(def.Body, out var body) ? body : null,
|
||||||
Template = BuildTemplate(def.Genome, genes),
|
Template = BuildTemplate(def.Genome, genes),
|
||||||
@@ -111,6 +147,35 @@ public sealed class AnimalSet
|
|||||||
return genome;
|
return genome;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ОСНОВА создания гибрида (полноценная команда/UI — позже): геном помеси, привязанный к базовому виду
|
||||||
|
/// <paramref name="baseSpecies"/> (его тело/спрайт/набор генов), но с общими генами, скрещёнными с
|
||||||
|
/// <paramref name="otherSpecies"/> (по одной аллели-гамете от каждого родителя, как при размножении).
|
||||||
|
/// Пол берётся от базового вида; гены, которых нет у базового, игнорируются (его тело их не читает).
|
||||||
|
/// Сущность создаётся обычной <see cref="Sim.AnimalFactory.Create"/> с этим геномом и базовым видом —
|
||||||
|
/// то есть «создать любой вид» = Create(вид, GenerateGenome), «любой гибрид» = Create(вид, HybridGenome).
|
||||||
|
/// </summary>
|
||||||
|
public Genome HybridGenome(int baseSpecies, int otherSpecies, Random random)
|
||||||
|
{
|
||||||
|
var hybrid = GenerateGenome(baseSpecies, random);
|
||||||
|
var other = _species[otherSpecies].Template.Generate(random);
|
||||||
|
foreach (var (geneId, allele) in other.ToDictionary())
|
||||||
|
{
|
||||||
|
if (string.Equals(geneId, "GeneSex", StringComparison.Ordinal) || !hybrid.Has(geneId))
|
||||||
|
{
|
||||||
|
continue; // пол — от базового вида; чужие гены базовое тело не читает
|
||||||
|
}
|
||||||
|
|
||||||
|
var mine = hybrid[geneId];
|
||||||
|
hybrid[geneId] = new Allele(
|
||||||
|
random.NextSingle() < 0.5f ? mine.A : mine.B,
|
||||||
|
random.NextSingle() < 0.5f ? allele.A : allele.B
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hybrid;
|
||||||
|
}
|
||||||
|
|
||||||
// Строит шаблон генома вида из ДАННЫХ (geneId→base из Defs/Animals/) generically: разброс берётся из
|
// Строит шаблон генома вида из ДАННЫХ (geneId→base из Defs/Animals/) generically: разброс берётся из
|
||||||
// самого гена. Состав открыт — модер добавляет ген строкой JSON, без правки кода. Дискретные гены
|
// самого гена. Состав открыт — модер добавляет ген строкой JSON, без правки кода. Дискретные гены
|
||||||
// (база/разброс не нужны — варианты из GeneDef) тоже поддержаны.
|
// (база/разброс не нужны — варианты из GeneDef) тоже поддержаны.
|
||||||
|
|||||||
@@ -261,6 +261,9 @@ public sealed class AnimalDef : PawnDef
|
|||||||
/// <summary>Текстура трупа (фаза A5); пусто — берётся <see cref="PawnDef.Texture"/>.</summary>
|
/// <summary>Текстура трупа (фаза A5); пусто — берётся <see cref="PawnDef.Texture"/>.</summary>
|
||||||
public string CorpseTexture { get; init; } = "";
|
public string CorpseTexture { get; init; } = "";
|
||||||
|
|
||||||
|
/// <summary>Текстура яйца (тип рождения «яйцекладка»); по умолчанию — общий спрайт семени-точки.</summary>
|
||||||
|
public string EggTexture { get; init; } = "things/plant/seed_default";
|
||||||
|
|
||||||
/// <summary>Имя <see cref="BodyDef"/> — анатомия вида (части тела/органы); пусто — без частей тела.</summary>
|
/// <summary>Имя <see cref="BodyDef"/> — анатомия вида (части тела/органы); пусто — без частей тела.</summary>
|
||||||
public string Body { get; init; } = "";
|
public string Body { get; init; } = "";
|
||||||
|
|
||||||
|
|||||||
@@ -294,6 +294,17 @@ public sealed class WorldScene : Scene
|
|||||||
_config.Seed + 0x4BED
|
_config.Seed + 0x4BED
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
UpdateSystems.Add(
|
||||||
|
new EggSystem(
|
||||||
|
Store,
|
||||||
|
_animals,
|
||||||
|
_needs,
|
||||||
|
Context.Clock,
|
||||||
|
SecondsPerDay,
|
||||||
|
CellSize,
|
||||||
|
_config.Seed + 0x6E66
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
||||||
@@ -519,7 +530,7 @@ public sealed class WorldScene : Scene
|
|||||||
new SaveStore().Write(save);
|
new SaveStore().Write(save);
|
||||||
Log.Info(
|
Log.Info(
|
||||||
$"World '{_config.Name}' saved ({save.Plants.Count} plants, "
|
$"World '{_config.Name}' saved ({save.Plants.Count} plants, "
|
||||||
+ $"{save.Animals.Count} animals, {save.Corpses.Count} corpses)"
|
+ $"{save.Animals.Count} animals, {save.Corpses.Count} corpses, {save.Eggs.Count} eggs)"
|
||||||
);
|
);
|
||||||
return _config.Name;
|
return _config.Name;
|
||||||
}
|
}
|
||||||
@@ -614,6 +625,23 @@ public sealed class WorldScene : Scene
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Store
|
||||||
|
.Query<Egg, Transform2D>()
|
||||||
|
.ForEachEntity(
|
||||||
|
(ref Egg egg, ref Transform2D transform, Entity _) =>
|
||||||
|
save.Eggs.Add(
|
||||||
|
new EggSave
|
||||||
|
{
|
||||||
|
Species = _animals[egg.Species].Def.DefName,
|
||||||
|
X = transform.Position.X,
|
||||||
|
Y = transform.Position.Y,
|
||||||
|
Generation = egg.Generation,
|
||||||
|
IncubateDays = egg.IncubateDays,
|
||||||
|
Genome = egg.Genome?.ToDictionary() ?? new(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RestorePlants(GameContent content)
|
private void RestorePlants(GameContent content)
|
||||||
@@ -695,8 +723,31 @@ public sealed class WorldScene : Scene
|
|||||||
data.Meat = saved.Meat;
|
data.Meat = saved.Meat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var saved in _save.Eggs)
|
||||||
|
{
|
||||||
|
if (!content.Defs.TryGet<AnimalDef>(saved.Species, out var def))
|
||||||
|
{
|
||||||
|
continue; // вид пропал (мод убрали) — пропускаем яйцо
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = _animals.IndexOf(def);
|
||||||
|
var genome = saved.Genome is { Count: > 0 }
|
||||||
|
? new Genome(saved.Genome)
|
||||||
|
: _animals.GenerateGenome(index, fallback);
|
||||||
|
AnimalFactory.CreateEgg(
|
||||||
|
Store,
|
||||||
|
_animals,
|
||||||
|
index,
|
||||||
|
new Vector2(saved.X, saved.Y),
|
||||||
|
genome,
|
||||||
|
saved.Generation,
|
||||||
|
saved.IncubateDays,
|
||||||
|
CellSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Log.Info(
|
Log.Info(
|
||||||
$"Restored {_save.Animals.Count} animals, {_save.Corpses.Count} corpses from save"
|
$"Restored {_save.Animals.Count} animals, {_save.Corpses.Count} corpses, {_save.Eggs.Count} eggs from save"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1136,6 +1187,10 @@ public sealed class WorldScene : Scene
|
|||||||
$" diet: herb {traits.Herbivory:0.##} / carn {traits.Carnivory:0.##} / omni {traits.Omnivory:0.##}, "
|
$" diet: herb {traits.Herbivory:0.##} / carn {traits.Carnivory:0.##} / omni {traits.Omnivory:0.##}, "
|
||||||
+ $"toxinTolerance {traits.ToxinTolerance:0.##}"
|
+ $"toxinTolerance {traits.ToxinTolerance:0.##}"
|
||||||
);
|
);
|
||||||
|
console.WriteLine(
|
||||||
|
$" reproduction: {(traits.Oviparous ? "oviparous (lays eggs)" : "viviparous (live birth)")}, "
|
||||||
|
+ $"gestation {traits.GestationDays:0} d, litter {traits.LitterSize:0.#}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory,
|
// Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory,
|
||||||
|
|||||||
@@ -110,6 +110,46 @@ public static class AnimalFactory
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Тинт яйца (бледно-кремовый) и доля размера клетки, до которой масштабируется спрайт яйца.
|
||||||
|
private static readonly Color EggTint = new(238, 230, 208);
|
||||||
|
private const float EggSizeFraction = 0.45f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Создаёт сущность-яйцо (тип рождения «яйцекладка»): спрайт-яйцо и компонент <see cref="Egg"/> с уже
|
||||||
|
/// скрещённым геномом будущего детёныша. Инкубируется и вылупляется в <see cref="EggSystem"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static Entity CreateEgg(
|
||||||
|
EntityStore store,
|
||||||
|
AnimalSet animals,
|
||||||
|
int species,
|
||||||
|
Vector2 position,
|
||||||
|
Genome genome,
|
||||||
|
int generation,
|
||||||
|
float incubateDays,
|
||||||
|
int cellSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var sp = animals[species];
|
||||||
|
var region = sp.Egg;
|
||||||
|
var sprite = new Sprite(region, GameLayers.Beings);
|
||||||
|
sprite.CenterOrigin();
|
||||||
|
sprite.Color = EggTint;
|
||||||
|
return store.CreateEntity(
|
||||||
|
new Transform2D(
|
||||||
|
position,
|
||||||
|
scale: new Vector2(cellSize * EggSizeFraction / region.Width)
|
||||||
|
),
|
||||||
|
sprite,
|
||||||
|
new Egg
|
||||||
|
{
|
||||||
|
Species = species,
|
||||||
|
Genome = genome,
|
||||||
|
Generation = generation,
|
||||||
|
IncubateDays = incubateDays,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Порог выраженности признака диеты, с которого вид реально ест данный корм.</summary>
|
/// <summary>Порог выраженности признака диеты, с которого вид реально ест данный корм.</summary>
|
||||||
public const float DietThreshold = 0.5f;
|
public const float DietThreshold = 0.5f;
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,12 @@ public struct AnimalPhenotype
|
|||||||
/// <summary>Размер помёта.</summary>
|
/// <summary>Размер помёта.</summary>
|
||||||
public float LitterSize;
|
public float LitterSize;
|
||||||
|
|
||||||
|
/// <summary>Тип рождения [0..1]: ≥0.5 — яйцекладущий (откладывает яйца), иначе живородящий.</summary>
|
||||||
|
public float EggLaying;
|
||||||
|
|
||||||
|
/// <summary>Яйцекладущий ли вид (тип рождения) — определяет цикл зачатия.</summary>
|
||||||
|
public readonly bool Oviparous => EggLaying >= 0.5f;
|
||||||
|
|
||||||
/// <summary>Собирает фенотип из карты признаков, посчитанной <see cref="Phenotype.Compute"/>.</summary>
|
/// <summary>Собирает фенотип из карты признаков, посчитанной <see cref="Phenotype.Compute"/>.</summary>
|
||||||
public static AnimalPhenotype FromTraits(IReadOnlyDictionary<string, float> traits)
|
public static AnimalPhenotype FromTraits(IReadOnlyDictionary<string, float> traits)
|
||||||
{
|
{
|
||||||
@@ -86,6 +92,7 @@ public struct AnimalPhenotype
|
|||||||
BreedingSeason = (int)MathF.Round(Math.Clamp(T("breedingSeason"), 0f, 3f)),
|
BreedingSeason = (int)MathF.Round(Math.Clamp(T("breedingSeason"), 0f, 3f)),
|
||||||
GestationDays = T("gestationDays"),
|
GestationDays = T("gestationDays"),
|
||||||
LitterSize = T("litterSize"),
|
LitterSize = T("litterSize"),
|
||||||
|
EggLaying = T("eggLaying"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,6 +464,26 @@ public struct Corpse : IComponent
|
|||||||
public float Meat;
|
public float Meat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Яйцо (тип рождения «яйцекладка»): отдельная сущность, отложенная яйцекладущей самкой вместо живых
|
||||||
|
/// родов. Несёт уже скрещённый геном будущего детёныша, вид и поколение; инкубируется во времени и
|
||||||
|
/// вылупляется в детёныша (<see cref="Sim.EggSystem"/>). Геном — managed-ссылка (как у трупа/беременности).
|
||||||
|
/// </summary>
|
||||||
|
public struct Egg : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Индекс вида в <see cref="Content.AnimalSet"/> (для спрайта при вылуплении и сейва).</summary>
|
||||||
|
public int Species;
|
||||||
|
|
||||||
|
/// <summary>Геном будущего детёныша (уже скрещён из родителей на момент кладки).</summary>
|
||||||
|
public Genome Genome;
|
||||||
|
|
||||||
|
/// <summary>Поколение будущего детёныша.</summary>
|
||||||
|
public int Generation;
|
||||||
|
|
||||||
|
/// <summary>Остаток инкубации в игровых днях; ≤0 — вылупление.</summary>
|
||||||
|
public float IncubateDays;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Инстанс мысли настроения на особи (фаза A8): деф события + остаток времени до угасания.</summary>
|
/// <summary>Инстанс мысли настроения на особи (фаза A8): деф события + остаток времени до угасания.</summary>
|
||||||
public struct ThoughtInstance
|
public struct ThoughtInstance
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1656,6 +1656,8 @@ public sealed class AnimalPregnancySystem : BaseSystem
|
|||||||
Litter = preg.Litter,
|
Litter = preg.Litter,
|
||||||
Generation = o[i].Generation + 1,
|
Generation = o[i].Generation + 1,
|
||||||
Position = t[i].Position,
|
Position = t[i].Position,
|
||||||
|
Oviparous = o[i].Traits.Oviparous,
|
||||||
|
IncubateDays = MathF.Max(1f, o[i].Traits.GestationDays),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1685,17 +1687,35 @@ public sealed class AnimalPregnancySystem : BaseSystem
|
|||||||
);
|
);
|
||||||
var offset =
|
var offset =
|
||||||
new Vector2(_rng.NextSingle() - 0.5f, _rng.NextSingle() - 0.5f) * _cellSize;
|
new Vector2(_rng.NextSingle() - 0.5f, _rng.NextSingle() - 0.5f) * _cellSize;
|
||||||
AnimalFactory.Create(
|
if (birth.Oviparous)
|
||||||
_store,
|
{
|
||||||
_animals,
|
// Яйцекладка: вместо живого детёныша откладываем яйцо с его геномом — вылупится позже.
|
||||||
_needs,
|
AnimalFactory.CreateEgg(
|
||||||
birth.Species,
|
_store,
|
||||||
birth.Position + offset,
|
_animals,
|
||||||
ageDays: 0f,
|
birth.Species,
|
||||||
child,
|
birth.Position + offset,
|
||||||
birth.Generation,
|
child,
|
||||||
_cellSize
|
birth.Generation,
|
||||||
);
|
birth.IncubateDays,
|
||||||
|
_cellSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AnimalFactory.Create(
|
||||||
|
_store,
|
||||||
|
_animals,
|
||||||
|
_needs,
|
||||||
|
birth.Species,
|
||||||
|
birth.Position + offset,
|
||||||
|
ageDays: 0f,
|
||||||
|
child,
|
||||||
|
birth.Generation,
|
||||||
|
_cellSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1710,6 +1730,90 @@ public sealed class AnimalPregnancySystem : BaseSystem
|
|||||||
public int Litter;
|
public int Litter;
|
||||||
public int Generation;
|
public int Generation;
|
||||||
public Vector2 Position;
|
public Vector2 Position;
|
||||||
|
public bool Oviparous;
|
||||||
|
public float IncubateDays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Инкубация яиц (тип рождения «яйцекладка»): тикает остаток инкубации каждого яйца; по истечении
|
||||||
|
/// вылупляет детёныша (<see cref="AnimalFactory.Create"/> из хранимого генома, стадия Baby) рядом с
|
||||||
|
/// яйцом и удаляет яйцо. Зеркало родов для яйцекладущих видов.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class EggSystem : BaseSystem
|
||||||
|
{
|
||||||
|
private readonly EntityStore _store;
|
||||||
|
private readonly AnimalSet _animals;
|
||||||
|
private readonly NeedSet _needs;
|
||||||
|
private readonly GameClock _clock;
|
||||||
|
private readonly float _secondsPerDay;
|
||||||
|
private readonly int _cellSize;
|
||||||
|
private readonly Random _rng;
|
||||||
|
private readonly ArchetypeQuery<Egg, Transform2D> _query;
|
||||||
|
private readonly List<Entity> _hatched = [];
|
||||||
|
|
||||||
|
public EggSystem(
|
||||||
|
EntityStore store,
|
||||||
|
AnimalSet animals,
|
||||||
|
NeedSet needs,
|
||||||
|
GameClock clock,
|
||||||
|
float secondsPerDay,
|
||||||
|
int cellSize,
|
||||||
|
int seed
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_store = store;
|
||||||
|
_animals = animals;
|
||||||
|
_needs = needs;
|
||||||
|
_clock = clock;
|
||||||
|
_secondsPerDay = secondsPerDay;
|
||||||
|
_cellSize = cellSize;
|
||||||
|
_rng = new Random(seed);
|
||||||
|
_query = store.Query<Egg, Transform2D>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUpdateGroup()
|
||||||
|
{
|
||||||
|
var days = _clock.DeltaTime / _secondsPerDay;
|
||||||
|
if (days <= 0f)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hatched.Clear();
|
||||||
|
foreach (var (eggs, transforms, entities) in _query.Chunks)
|
||||||
|
{
|
||||||
|
var e = eggs.Span;
|
||||||
|
for (var i = 0; i < e.Length; i++)
|
||||||
|
{
|
||||||
|
ref var egg = ref e[i];
|
||||||
|
egg.IncubateDays -= days;
|
||||||
|
if (egg.IncubateDays > 0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset =
|
||||||
|
new Vector2(_rng.NextSingle() - 0.5f, _rng.NextSingle() - 0.5f) * _cellSize;
|
||||||
|
AnimalFactory.Create(
|
||||||
|
_store,
|
||||||
|
_animals,
|
||||||
|
_needs,
|
||||||
|
egg.Species,
|
||||||
|
transforms.Span[i].Position + offset,
|
||||||
|
ageDays: 0f,
|
||||||
|
egg.Genome,
|
||||||
|
egg.Generation,
|
||||||
|
_cellSize
|
||||||
|
);
|
||||||
|
_hatched.Add(entities.EntityAt(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var egg in _hatched)
|
||||||
|
{
|
||||||
|
egg.DeleteEntity();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user