Compare commits
6
Commits
49936a1de3
...
d52eff07f8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d52eff07f8 | ||
|
|
37d8c976a3 | ||
|
|
7fc4bcd301 | ||
|
|
9c2c2d7fd0 | ||
|
|
91a00b4305 | ||
|
|
32bae03869 |
@@ -41,6 +41,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.UI.Tests", "engin
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Audio.Tests", "engine\tests\MrGameEng.Audio.Tests\MrGameEng.Audio.Tests.csproj", "{E0E37D87-4F62-41E9-9CD1-3E7432301508}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Audio.Tests", "engine\tests\MrGameEng.Audio.Tests\MrGameEng.Audio.Tests.csproj", "{E0E37D87-4F62-41E9-9CD1-3E7432301508}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Host", "engine\src\MrGameEng.Host\MrGameEng.Host.csproj", "{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MrGameEng.Host.Tests", "engine\tests\MrGameEng.Host.Tests\MrGameEng.Host.Tests.csproj", "{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -231,6 +235,30 @@ Global
|
|||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x64.Build.0 = Release|Any CPU
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.ActiveCfg = Release|Any CPU
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.Build.0 = Release|Any CPU
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@@ -253,5 +281,7 @@ Global
|
|||||||
{86ECA4A0-46A2-45F4-8CF4-8FB8BB5DC050} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
{86ECA4A0-46A2-45F4-8CF4-8FB8BB5DC050} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
{BC64F7AF-0FC3-4608-A4FF-1273AB8F2ABD} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
{BC64F7AF-0FC3-4608-A4FF-1273AB8F2ABD} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
{E0E37D87-4F62-41E9-9CD1-3E7432301508} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
{E0E37D87-4F62-41E9-9CD1-3E7432301508} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
|
{6EFAFBB4-F11A-42B9-A8AB-B98E735431F6} = {F4A69A46-AF86-AB4E-7D23-4CCCB7B9A519}
|
||||||
|
{BE18D2E0-B8AD-4632-AA0F-F9C80BE5D83A} = {CD3B5CE5-C23F-38B4-04AA-A303F94731A5}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
{
|
||||||
|
"type": "Gene",
|
||||||
|
// Организм-агностичные гены: каждый ген задаёт, как генерируются/мутируют аллели и как ген
|
||||||
|
// вкладывается в признаки (effects: имя признака → формула; value — выраженное значение гена).
|
||||||
|
// Этот набор покрывает измерения генома растения (его используют системы роста/жизненного цикла).
|
||||||
|
"defs": [
|
||||||
|
{ "defName": "BaseNumericGene", "abstract": true, "kind": "Numeric",
|
||||||
|
"spread": 0.08, "mutationChance": 0.05, "mutationMagnitude": 0.12 },
|
||||||
|
|
||||||
|
{ "defName": "GeneOptimalLight", "parent": "BaseNumericGene", "label": "gene.optimalLight",
|
||||||
|
"default": 0.6, "min": 0.0, "max": 1.0, "tags": ["environment", "light"],
|
||||||
|
"effects": { "optimalLight": "value" } },
|
||||||
|
{ "defName": "GeneLightTolerance", "parent": "BaseNumericGene", "label": "gene.lightTolerance",
|
||||||
|
"default": 0.5, "min": 0.05, "max": 1.0, "tags": ["environment", "light"],
|
||||||
|
"effects": { "lightTolerance": "value" } },
|
||||||
|
{ "defName": "GeneOptimalTemperature", "parent": "BaseNumericGene", "label": "gene.optimalTemperature",
|
||||||
|
"default": 14, "min": -20, "max": 45, "tags": ["environment", "temperature"],
|
||||||
|
"effects": { "optimalTemperature": "value" } },
|
||||||
|
{ "defName": "GeneTemperatureTolerance", "parent": "BaseNumericGene", "label": "gene.temperatureTolerance",
|
||||||
|
"default": 16, "min": 2, "max": 40, "tags": ["environment", "temperature"],
|
||||||
|
"effects": { "temperatureTolerance": "value" } },
|
||||||
|
{ "defName": "GeneOptimalFertility", "parent": "BaseNumericGene", "label": "gene.optimalFertility",
|
||||||
|
"default": 1.4, "min": 0.1, "max": 3.0, "tags": ["environment", "soil"],
|
||||||
|
"effects": { "optimalFertility": "value" } },
|
||||||
|
{ "defName": "GeneFertilityTolerance", "parent": "BaseNumericGene", "label": "gene.fertilityTolerance",
|
||||||
|
"default": 0.9, "min": 0.1, "max": 3.0, "tags": ["environment", "soil"],
|
||||||
|
"effects": { "fertilityTolerance": "value" } },
|
||||||
|
|
||||||
|
{ "defName": "GeneVigor", "parent": "BaseNumericGene", "label": "gene.vigor",
|
||||||
|
"default": 1.0, "min": 0.1, "max": 3.0, "tags": ["growth"],
|
||||||
|
"effects": { "vigor": "value" } },
|
||||||
|
{ "defName": "GeneLifespan", "parent": "BaseNumericGene", "label": "gene.lifespan",
|
||||||
|
"default": 160, "min": 5, "max": 500, "tags": ["lifecycle"],
|
||||||
|
"effects": { "lifespan": "value" } },
|
||||||
|
{ "defName": "GeneDispersalRange", "parent": "BaseNumericGene", "label": "gene.dispersalRange",
|
||||||
|
"default": 2, "min": 1, "max": 8, "tags": ["lifecycle", "reproduction"],
|
||||||
|
"effects": { "dispersalRange": "value" } },
|
||||||
|
{ "defName": "GeneReproduceInterval", "parent": "BaseNumericGene", "label": "gene.reproduceInterval",
|
||||||
|
"default": 14, "min": 1, "max": 60, "tags": ["lifecycle", "reproduction"],
|
||||||
|
"effects": { "reproduceInterval": "value" } },
|
||||||
|
{ "defName": "GeneSelfPollination", "parent": "BaseNumericGene", "label": "gene.selfPollination",
|
||||||
|
"default": 0.2, "min": 0.0, "max": 1.0, "tags": ["reproduction"],
|
||||||
|
"effects": { "selfPollination": "value" } },
|
||||||
|
{ "defName": "GeneMutationRate", "parent": "BaseNumericGene", "label": "gene.mutationRate",
|
||||||
|
"default": 0.05, "min": 0.0, "max": 1.0, "tags": ["reproduction"],
|
||||||
|
"effects": { "mutationRate": "value" } },
|
||||||
|
|
||||||
|
// --- Контент (фаза G4): плодоношение, добыча, цвет ---
|
||||||
|
{ "defName": "GeneFruitYield", "parent": "BaseNumericGene", "label": "gene.fruitYield",
|
||||||
|
"default": 0, "min": 0, "max": 12, "tags": ["fruiting"],
|
||||||
|
"effects": { "fruitYield": "value" } },
|
||||||
|
{ "defName": "GeneFruitSeason", "parent": "BaseNumericGene", "label": "gene.fruitSeason",
|
||||||
|
"default": 1, "min": 0, "max": 3, "spread": 0, "mutationChance": 0, "tags": ["fruiting"],
|
||||||
|
"effects": { "fruitSeason": "value" } },
|
||||||
|
{ "defName": "GeneHarvestAmount", "parent": "BaseNumericGene", "label": "gene.harvestAmount",
|
||||||
|
"default": 1, "min": 0, "max": 50, "tags": ["harvest"],
|
||||||
|
"effects": { "harvestAmount": "value" } },
|
||||||
|
{ "defName": "GeneLeafHue", "parent": "BaseNumericGene", "label": "gene.leafHue",
|
||||||
|
"default": 0.33, "min": 0, "max": 1, "spread": 0.04, "tags": ["morphology", "color"],
|
||||||
|
"effects": { "leafHue": "value" } },
|
||||||
|
|
||||||
|
// Производный ген: признак собирается группировкой по регэкспу — сумма всех генов-толерантностей
|
||||||
|
// (демонстрация gsom-функций фазы G5). Собственное значение гена не используется.
|
||||||
|
{ "defName": "GeneHardiness", "parent": "BaseNumericGene", "label": "gene.hardiness",
|
||||||
|
"default": 0, "min": 0, "max": 1, "spread": 0, "mutationChance": 0, "tags": ["derived"],
|
||||||
|
"effects": { "hardiness": "gsum('Gene.*Tolerance')" } },
|
||||||
|
|
||||||
|
// Дискретный ген морфы: вариант 0 доминирует, 1 (рецессивный) виден только в гомозиготе.
|
||||||
|
{ "defName": "GeneMorph", "kind": "Discrete", "label": "gene.morph",
|
||||||
|
"variants": 2, "variantWeights": [0.82, 0.18], "mutationChance": 0.05, "tags": ["morphology"],
|
||||||
|
"effects": { "variant": "value" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"type": "Patch",
|
||||||
|
// Контент-патчи (фаза G5): применяются ко всем дефам типа defType, чьё имя подходит под regex match,
|
||||||
|
// проставляя поля set. Демонстрация: даём кустам древесину при сборе (веточки).
|
||||||
|
"patches": [
|
||||||
|
{ "defType": "Plant", "match": "Bush.*", "set": { "harvestProduct": "ProductWood" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,10 +2,12 @@
|
|||||||
"type": "Plant",
|
"type": "Plant",
|
||||||
"defs": [
|
"defs": [
|
||||||
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
{ "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": 16,
|
||||||
"optimalFertility": 1.4, "fertilityTolerance": 0.9, "vigor": 1.0,
|
"optimalFertility": 1.4, "fertilityTolerance": 0.9, "vigor": 1.0,
|
||||||
"lifespan": 160, "dispersalRange": 2, "reproduceInterval": 14, "selfPollination": 0.2,
|
"lifespan": 160, "dispersalRange": 2, "reproduceInterval": 14, "selfPollination": 0.2,
|
||||||
"mutationRate": 0.05, "variantChance": 0.15, "spread": 0.08 },
|
"mutationRate": 0.05, "variantChance": 0.15, "spread": 0.08,
|
||||||
|
"fruitYield": 5, "fruitSeason": 2, "harvestAmount": 10, "leafHue": 0.30 },
|
||||||
"stages": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 4, "label": "plant.stage.seedling" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 4, "label": "plant.stage.seedling" },
|
||||||
{ "sizeCells": 1.2, "growDays": 9, "label": "plant.stage.sapling" },
|
{ "sizeCells": 1.2, "growDays": 9, "label": "plant.stage.sapling" },
|
||||||
@@ -17,20 +19,24 @@
|
|||||||
{ "defName": "TreeGrayPineA", "parent": "BaseTree", "label": "plant.pine", "texture": "things/plant/treegraypine/TreeGrayPineA" },
|
{ "defName": "TreeGrayPineA", "parent": "BaseTree", "label": "plant.pine", "texture": "things/plant/treegraypine/TreeGrayPineA" },
|
||||||
|
|
||||||
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.2,
|
{ "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": 20, "temperatureTolerance": 12,
|
||||||
"optimalFertility": 1.0, "fertilityTolerance": 1.0, "vigor": 1.3,
|
"optimalFertility": 1.0, "fertilityTolerance": 1.0, "vigor": 1.3,
|
||||||
"lifespan": 22, "dispersalRange": 3, "reproduceInterval": 3, "selfPollination": 0.8,
|
"lifespan": 22, "dispersalRange": 3, "reproduceInterval": 3, "selfPollination": 0.8,
|
||||||
"mutationRate": 0.06, "variantChance": 0.2, "spread": 0.1 },
|
"mutationRate": 0.06, "variantChance": 0.2, "spread": 0.1,
|
||||||
|
"harvestAmount": 2, "leafHue": 0.36 },
|
||||||
"stages": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 2, "label": "plant.stage.sprout" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 2, "label": "plant.stage.sprout" },
|
||||||
{ "sizeCells": 1.2, "label": "plant.stage.mature" }
|
{ "sizeCells": 1.2, "label": "plant.stage.mature" }
|
||||||
] },
|
] },
|
||||||
|
|
||||||
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
|
{ "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": 13,
|
||||||
"optimalFertility": 1.1, "fertilityTolerance": 1.0, "vigor": 1.0,
|
"optimalFertility": 1.1, "fertilityTolerance": 1.0, "vigor": 1.0,
|
||||||
"lifespan": 60, "dispersalRange": 2, "reproduceInterval": 7, "selfPollination": 0.5,
|
"lifespan": 60, "dispersalRange": 2, "reproduceInterval": 7, "selfPollination": 0.5,
|
||||||
"mutationRate": 0.05, "variantChance": 0.18, "spread": 0.1 },
|
"mutationRate": 0.05, "variantChance": 0.18, "spread": 0.1,
|
||||||
|
"fruitYield": 3, "fruitSeason": 1, "harvestAmount": 3, "leafHue": 0.32 },
|
||||||
"stages": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 3, "label": "plant.stage.sprout" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.6, "growDays": 3, "label": "plant.stage.sprout" },
|
||||||
{ "sizeCells": 1.4, "label": "plant.stage.mature" }
|
{ "sizeCells": 1.4, "label": "plant.stage.mature" }
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "Product",
|
||||||
|
// Продукты сбора/плодоношения растений. На них ссылаются PlantDef.harvestProduct / fruitProduct;
|
||||||
|
// количество задаётся генами (harvestAmount / fruitYield).
|
||||||
|
"defs": [
|
||||||
|
{ "defName": "ProductWood", "label": "product.wood", "kind": "material" },
|
||||||
|
{ "defName": "ProductGrass", "label": "product.grass", "kind": "material" },
|
||||||
|
{ "defName": "ProductBerry", "label": "product.berry", "kind": "food" },
|
||||||
|
{ "defName": "ProductAcorn", "label": "product.acorn", "kind": "food" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -78,6 +78,11 @@
|
|||||||
"plant.stage.sapling": "sapling",
|
"plant.stage.sapling": "sapling",
|
||||||
"plant.stage.mature": "mature",
|
"plant.stage.mature": "mature",
|
||||||
|
|
||||||
|
"product.wood": "wood",
|
||||||
|
"product.grass": "grass",
|
||||||
|
"product.berry": "berries",
|
||||||
|
"product.acorn": "acorn",
|
||||||
|
|
||||||
"pawn.being": "being",
|
"pawn.being": "being",
|
||||||
"pawn.bear": "bear",
|
"pawn.bear": "bear",
|
||||||
"pawn.deer": "deer",
|
"pawn.deer": "deer",
|
||||||
|
|||||||
@@ -78,6 +78,11 @@
|
|||||||
"plant.stage.sapling": "саженец",
|
"plant.stage.sapling": "саженец",
|
||||||
"plant.stage.mature": "взрослое",
|
"plant.stage.mature": "взрослое",
|
||||||
|
|
||||||
|
"product.wood": "древесина",
|
||||||
|
"product.grass": "трава",
|
||||||
|
"product.berry": "ягоды",
|
||||||
|
"product.acorn": "жёлудь",
|
||||||
|
|
||||||
"pawn.being": "житель",
|
"pawn.being": "житель",
|
||||||
"pawn.bear": "медведь",
|
"pawn.bear": "медведь",
|
||||||
"pawn.deer": "олень",
|
"pawn.deer": "олень",
|
||||||
|
|||||||
+1
-1
Submodule engine updated: 79d406a9f4...d044cafad9
@@ -1,5 +1,5 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using LittleSim.Sim;
|
using MrGameEng.Genetics;
|
||||||
|
|
||||||
namespace LittleSim.App;
|
namespace LittleSim.App;
|
||||||
|
|
||||||
@@ -24,8 +24,8 @@ public sealed class PlantSave
|
|||||||
/// <summary>Плодородность клетки.</summary>
|
/// <summary>Плодородность клетки.</summary>
|
||||||
public float CellFertility { get; set; }
|
public float CellFertility { get; set; }
|
||||||
|
|
||||||
/// <summary>Геном особи (аллели сериализуются как поля структуры).</summary>
|
/// <summary>Геном особи: аллели по id генов (переменного состава).</summary>
|
||||||
public PlantGenome Genome { get; set; }
|
public Dictionary<string, Allele> Genome { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Сериализуемое состояние одного жителя (sim-компоненты; спрайт пересобирается из дефа по <see cref="DefName"/>).</summary>
|
/// <summary>Сериализуемое состояние одного жителя (sim-компоненты; спрайт пересобирается из дефа по <see cref="DefName"/>).</summary>
|
||||||
@@ -119,7 +119,7 @@ public sealed class SaveStore
|
|||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
PropertyNameCaseInsensitive = true,
|
PropertyNameCaseInsensitive = true,
|
||||||
IncludeFields = true, // аллели генома (PlantGenome) — публичные поля структуры
|
IncludeFields = true, // на случай публичных полей в сохраняемых sim-структурах
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly string _directory;
|
private readonly string _directory;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using MrGameEng.Atlases;
|
using MrGameEng.Atlases;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Mods;
|
using MrGameEng.Mods;
|
||||||
|
|
||||||
namespace LittleSim.Content;
|
namespace LittleSim.Content;
|
||||||
@@ -56,9 +57,19 @@ public sealed class GameContent
|
|||||||
|
|
||||||
var defs = new DefDatabase();
|
var defs = new DefDatabase();
|
||||||
defs.RegisterType<TerrainDef>("Terrain");
|
defs.RegisterType<TerrainDef>("Terrain");
|
||||||
|
defs.RegisterType<GeneDef>("Gene");
|
||||||
|
defs.RegisterType<ProductDef>("Product");
|
||||||
defs.RegisterType<PlantDef>("Plant");
|
defs.RegisterType<PlantDef>("Plant");
|
||||||
defs.RegisterType<PawnDef>("Pawn");
|
defs.RegisterType<PawnDef>("Pawn");
|
||||||
defs.RegisterType<WorldPresetDef>("WorldPreset");
|
defs.RegisterType<WorldPresetDef>("WorldPreset");
|
||||||
|
// Валидация имён при загрузке (фаза G5): гены и продукты обязаны иметь префикс типа.
|
||||||
|
defs.RegisterValidator("Gene", "defName", "^Gene", "gene defName must start with 'Gene'");
|
||||||
|
defs.RegisterValidator(
|
||||||
|
"Product",
|
||||||
|
"defName",
|
||||||
|
"^Product",
|
||||||
|
"product defName must start with 'Product'"
|
||||||
|
);
|
||||||
defs.Load(mods);
|
defs.Load(mods);
|
||||||
|
|
||||||
var languages = new LanguageManager(defaultLanguage: "ru");
|
var languages = new LanguageManager(defaultLanguage: "ru");
|
||||||
|
|||||||
@@ -66,6 +66,17 @@ public sealed class PlantGrowthStage
|
|||||||
public string? Label { get; init; }
|
public string? Label { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Продукт (Defs/products.json): что растение даёт при сборе (древесина, трава) или плодоношении
|
||||||
|
/// (ягоды, жёлудь). Источник истины — деф; растения ссылаются на него по имени, а количество
|
||||||
|
/// определяют гены. <see cref="Def.Label"/> — ключ локализации (product.*).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ProductDef : Def
|
||||||
|
{
|
||||||
|
/// <summary>Категория продукта (например "material" или "food") — для будущей экономики/инвентаря.</summary>
|
||||||
|
public string Kind { get; init; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Базовый геном вида (вложенный объект <see cref="PlantDef.Genome"/>): оптимумы и толерантности по
|
/// Базовый геном вида (вложенный объект <see cref="PlantDef.Genome"/>): оптимумы и толерантности по
|
||||||
/// факторам среды + базовая бодрость роста. Особь при спавне получает аллели рядом с этими базами
|
/// факторам среды + базовая бодрость роста. Особь при спавне получает аллели рядом с этими базами
|
||||||
@@ -114,6 +125,18 @@ public sealed class GenomeDef
|
|||||||
|
|
||||||
/// <summary>Доля разброса аллелей вокруг базы при генерации особи.</summary>
|
/// <summary>Доля разброса аллелей вокруг базы при генерации особи.</summary>
|
||||||
public float Spread { get; init; } = 0.08f;
|
public float Spread { get; init; } = 0.08f;
|
||||||
|
|
||||||
|
/// <summary>Плодовитость: сколько плодов несёт зрелое растение в сезон (0 — не плодоносит).</summary>
|
||||||
|
public float FruitYield { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Сезон плодоношения (0 — весна, 1 — лето, 2 — осень, 3 — зима).</summary>
|
||||||
|
public float FruitSeason { get; init; } = 1f;
|
||||||
|
|
||||||
|
/// <summary>Сколько продукта даёт сбор растения (масштаб <see cref="PlantDef.HarvestProduct"/>).</summary>
|
||||||
|
public float HarvestAmount { get; init; } = 1f;
|
||||||
|
|
||||||
|
/// <summary>Оттенок листвы (0..1) — сдвиг тинта спрайта; ген цвета.</summary>
|
||||||
|
public float LeafHue { get; init; } = 0.33f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста, геном.</summary>
|
/// <summary>Растение (Defs/plants.json): текстура, размер, опциональный ствол-препятствие, стадии роста, геном.</summary>
|
||||||
@@ -133,6 +156,12 @@ public sealed class PlantDef : Def
|
|||||||
|
|
||||||
/// <summary>Базовый геном вида: оптимумы/толерантности по среде и бодрость роста.</summary>
|
/// <summary>Базовый геном вида: оптимумы/толерантности по среде и бодрость роста.</summary>
|
||||||
public GenomeDef Genome { get; init; } = new();
|
public GenomeDef Genome { get; init; } = new();
|
||||||
|
|
||||||
|
/// <summary>Имя <see cref="ProductDef"/>, выдаваемого при сборе растения (дерево/трава); null — несборное.</summary>
|
||||||
|
public string? HarvestProduct { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Имя <see cref="ProductDef"/>, выдаваемого плодами (ягоды/жёлудь); null — не плодоносит.</summary>
|
||||||
|
public string? FruitProduct { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
namespace LittleSim.Content;
|
namespace LittleSim.Content;
|
||||||
@@ -23,6 +24,9 @@ public sealed class PlantSet
|
|||||||
/// <summary>Стадии роста по порядку; последняя — терминальная.</summary>
|
/// <summary>Стадии роста по порядку; последняя — терминальная.</summary>
|
||||||
public required Stage[] Stages { get; init; }
|
public required Stage[] Stages { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Набор генов вида: базовые значения для генерации особи (геном из общих <see cref="GeneDef"/>).</summary>
|
||||||
|
public required GenomeTemplate Template { get; init; }
|
||||||
|
|
||||||
/// <summary>День, с которого растение достигает последней (зрелой) стадии.</summary>
|
/// <summary>День, с которого растение достигает последней (зрелой) стадии.</summary>
|
||||||
public float MaturityDays => Stages[^1].EnterDay;
|
public float MaturityDays => Stages[^1].EnterDay;
|
||||||
|
|
||||||
@@ -42,15 +46,28 @@ public sealed class PlantSet
|
|||||||
private readonly Species[] _species;
|
private readonly Species[] _species;
|
||||||
private readonly Dictionary<PlantDef, int> _index = new();
|
private readonly Dictionary<PlantDef, int> _index = new();
|
||||||
|
|
||||||
|
/// <summary>Общий реестр генов (по id) для скрещивания и вычисления признаков растений.</summary>
|
||||||
|
public IReadOnlyDictionary<string, GeneDef> GeneRegistry { get; }
|
||||||
|
|
||||||
/// <summary>Строит таблицу из всех (не-abstract) <see cref="PlantDef"/> мода.</summary>
|
/// <summary>Строит таблицу из всех (не-abstract) <see cref="PlantDef"/> мода.</summary>
|
||||||
public PlantSet(GameContent content, ModAtlases atlases, GraphicsDevice device)
|
public PlantSet(GameContent content, ModAtlases atlases, GraphicsDevice device)
|
||||||
{
|
{
|
||||||
|
var genes = content
|
||||||
|
.Defs.All<GeneDef>()
|
||||||
|
.ToDictionary(g => g.DefName, g => g, StringComparer.Ordinal);
|
||||||
|
GeneRegistry = genes;
|
||||||
|
|
||||||
var defs = content.Defs.All<PlantDef>().ToArray();
|
var defs = content.Defs.All<PlantDef>().ToArray();
|
||||||
_species = new Species[defs.Length];
|
_species = new Species[defs.Length];
|
||||||
for (var i = 0; i < defs.Length; i++)
|
for (var i = 0; i < defs.Length; i++)
|
||||||
{
|
{
|
||||||
var def = defs[i];
|
var def = defs[i];
|
||||||
_species[i] = new Species { Def = def, Stages = BuildStages(def, atlases, device) };
|
_species[i] = new Species
|
||||||
|
{
|
||||||
|
Def = def,
|
||||||
|
Stages = BuildStages(def, atlases, device),
|
||||||
|
Template = BuildTemplate(def.Genome, genes),
|
||||||
|
};
|
||||||
_index[def] = i;
|
_index[def] = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,6 +81,50 @@ public sealed class PlantSet
|
|||||||
/// <summary>Индекс вида по дефу.</summary>
|
/// <summary>Индекс вида по дефу.</summary>
|
||||||
public int IndexOf(PlantDef def) => _index[def];
|
public int IndexOf(PlantDef def) => _index[def];
|
||||||
|
|
||||||
|
// Переводит базовый геном вида (числа из plants.json) в набор генов: каждое число — центр
|
||||||
|
// аллелей соответствующего общего GeneDef; морфа — дискретный ген с долей рецессива из вида.
|
||||||
|
private static GenomeTemplate BuildTemplate(
|
||||||
|
GenomeDef g,
|
||||||
|
IReadOnlyDictionary<string, GeneDef> genes
|
||||||
|
)
|
||||||
|
{
|
||||||
|
GeneDef Gene(string id) =>
|
||||||
|
genes.TryGetValue(id, out var def)
|
||||||
|
? def
|
||||||
|
: throw new KeyNotFoundException(
|
||||||
|
$"Gene def '{id}' not found — is Mods/Core/Defs/genes.json loaded?"
|
||||||
|
);
|
||||||
|
|
||||||
|
GenomeTemplate.Entry Numeric(string id, float baseValue) =>
|
||||||
|
new(Gene(id), baseValue, g.Spread);
|
||||||
|
|
||||||
|
return new GenomeTemplate([
|
||||||
|
Numeric("GeneOptimalLight", g.OptimalLight),
|
||||||
|
Numeric("GeneLightTolerance", g.LightTolerance),
|
||||||
|
Numeric("GeneOptimalTemperature", g.OptimalTemperature),
|
||||||
|
Numeric("GeneTemperatureTolerance", g.TemperatureTolerance),
|
||||||
|
Numeric("GeneOptimalFertility", g.OptimalFertility),
|
||||||
|
Numeric("GeneFertilityTolerance", g.FertilityTolerance),
|
||||||
|
Numeric("GeneVigor", g.Vigor),
|
||||||
|
Numeric("GeneLifespan", g.Lifespan),
|
||||||
|
Numeric("GeneDispersalRange", g.DispersalRange),
|
||||||
|
Numeric("GeneReproduceInterval", g.ReproduceInterval),
|
||||||
|
Numeric("GeneSelfPollination", g.SelfPollination),
|
||||||
|
Numeric("GeneMutationRate", g.MutationRate),
|
||||||
|
// Контент (G4): плодоношение, добыча, цвет.
|
||||||
|
Numeric("GeneFruitYield", g.FruitYield),
|
||||||
|
new GenomeTemplate.Entry(Gene("GeneFruitSeason"), g.FruitSeason, 0f), // сезон фиксирован по виду
|
||||||
|
Numeric("GeneHarvestAmount", g.HarvestAmount),
|
||||||
|
new GenomeTemplate.Entry(Gene("GeneLeafHue"), g.LeafHue, 0.04f),
|
||||||
|
new GenomeTemplate.Entry(
|
||||||
|
Gene("GeneMorph"),
|
||||||
|
0f,
|
||||||
|
0f,
|
||||||
|
[1f - g.VariantChance, g.VariantChance]
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
private static Stage[] BuildStages(PlantDef def, ModAtlases atlases, GraphicsDevice device)
|
private static Stage[] BuildStages(PlantDef def, ModAtlases atlases, GraphicsDevice device)
|
||||||
{
|
{
|
||||||
// Без явных стадий растение существует как одна терминальная стадия из базовой текстуры.
|
// Без явных стадий растение существует как одна терминальная стадия из базовой текстуры.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Core\MrGameEng.Core.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Core\MrGameEng.Core.csproj" />
|
||||||
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Host\MrGameEng.Host.csproj" />
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Graphics\MrGameEng.Graphics.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Graphics\MrGameEng.Graphics.csproj" />
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Audio\MrGameEng.Audio.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Audio\MrGameEng.Audio.csproj" />
|
||||||
<ProjectReference Include="..\..\engine\src\MrGameEng.Content\MrGameEng.Content.csproj" />
|
<ProjectReference Include="..\..\engine\src\MrGameEng.Content\MrGameEng.Content.csproj" />
|
||||||
|
|||||||
@@ -1,6 +1,34 @@
|
|||||||
|
using LittleSim.Content;
|
||||||
using LittleSim.Scenes;
|
using LittleSim.Scenes;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
|
using MrGameEng.Host;
|
||||||
|
|
||||||
|
// Контент-линт без окна/GPU: грузит моды/дефы (патчи и валидаторы тоже), компилирует формулы генов
|
||||||
|
// (включая группировку gsum) и выходит. Удобно для CI.
|
||||||
|
if (args.Contains("--check-content"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var content = GameContent.Load();
|
||||||
|
var genes = content.Defs.All<GeneDef>();
|
||||||
|
var registry = genes.ToDictionary(g => g.DefName, g => g, StringComparer.Ordinal);
|
||||||
|
var traits = Phenotype.Compute(Genome.Generate(genes, new Random(1)), registry);
|
||||||
|
Console.WriteLine(
|
||||||
|
$"content ok: {content.Defs.TypeKeys.Count} def types, {genes.Count} genes, "
|
||||||
|
+ $"{content.Defs.NamesOf("Plant").Count} plants, {traits.Count} traits "
|
||||||
|
+ $"(hardiness={traits.GetValueOrDefault("hardiness"):0.##})"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine($"content FAILED: {error.Message}");
|
||||||
|
Environment.Exit(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Контент Core-мода грузится уже в окне — на загрузочном экране (BootScene), в фоне.
|
// Контент Core-мода грузится уже в окне — на загрузочном экране (BootScene), в фоне.
|
||||||
using var host = new GameHost(
|
using var host = new GameHost(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using LittleSim.UI;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Audio;
|
using MrGameEng.Audio;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Host;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.UI;
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
|
|
||||||
@@ -73,6 +74,6 @@ public sealed class BootScene : Scene
|
|||||||
Context.Services.Add(content);
|
Context.Services.Add(content);
|
||||||
Context.Services.Add(new ModAtlases(content.AtlasCacheDirectory));
|
Context.Services.Add(new ModAtlases(content.AtlasCacheDirectory));
|
||||||
content.Languages.SetLanguage(_settings.Language);
|
content.Languages.SetLanguage(_settings.Language);
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.6f));
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.6f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ internal static class ScatterSpawner
|
|||||||
index,
|
index,
|
||||||
new Vector2(px, py),
|
new Vector2(px, py),
|
||||||
age,
|
age,
|
||||||
PlantGenome.FromDef(def.Genome, random),
|
plants[index].Template.Generate(random),
|
||||||
terrain.Fertility,
|
terrain.Fertility,
|
||||||
cellSize
|
cellSize
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using LittleSim.Content;
|
|||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Host;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Input;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.UI;
|
||||||
|
|
||||||
@@ -40,7 +41,7 @@ public sealed class CreditsScene : Scene
|
|||||||
{
|
{
|
||||||
if (!Context.Scenes.IsTransitioning)
|
if (!Context.Scenes.IsTransitioning)
|
||||||
{
|
{
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using LittleSim.Content;
|
|||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Host;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Input;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.UI;
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
@@ -73,7 +74,7 @@ public sealed class LoadGameScene : Scene
|
|||||||
{
|
{
|
||||||
Context.Scenes.Switch(
|
Context.Scenes.Switch(
|
||||||
new WorldScene(save.ToConfig(), save),
|
new WorldScene(save.ToConfig(), save),
|
||||||
Transition.Fade(0.6f)
|
Transitions.Fade(0.6f)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -104,7 +105,7 @@ public sealed class LoadGameScene : Scene
|
|||||||
{
|
{
|
||||||
if (!Context.Scenes.IsTransitioning)
|
if (!Context.Scenes.IsTransitioning)
|
||||||
{
|
{
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using LittleSim.Content;
|
|||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Host;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.UI;
|
||||||
|
|
||||||
namespace LittleSim.Scenes;
|
namespace LittleSim.Scenes;
|
||||||
@@ -37,7 +38,7 @@ public sealed class MainMenuScene : Scene
|
|||||||
{
|
{
|
||||||
if (!Context.Scenes.IsTransitioning)
|
if (!Context.Scenes.IsTransitioning)
|
||||||
{
|
{
|
||||||
Context.Scenes.Switch(scene, Transition.Fade(0.4f));
|
Context.Scenes.Switch(scene, Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using LittleSim.Content;
|
|||||||
using LittleSim.UI;
|
using LittleSim.UI;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Host;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Input;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.UI;
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
@@ -153,14 +154,14 @@ public sealed class NewWorldScene : Scene
|
|||||||
Seed = seed,
|
Seed = seed,
|
||||||
SmoothPasses = _smoothing,
|
SmoothPasses = _smoothing,
|
||||||
};
|
};
|
||||||
Context.Scenes.Switch(new WorldScene(config), Transition.Fade(0.6f));
|
Context.Scenes.Switch(new WorldScene(config), Transitions.Fade(0.6f));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Back()
|
private void Back()
|
||||||
{
|
{
|
||||||
if (!Context.Scenes.IsTransitioning)
|
if (!Context.Scenes.IsTransitioning)
|
||||||
{
|
{
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using LittleSim.UI;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Audio;
|
using MrGameEng.Audio;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Host;
|
||||||
using Myra.Graphics2D.Brushes;
|
using Myra.Graphics2D.Brushes;
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
|||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using MrGameEng.Audio;
|
using MrGameEng.Audio;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Host;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Input;
|
||||||
using MrGameEng.UI;
|
using MrGameEng.UI;
|
||||||
using Myra.Graphics2D.UI;
|
using Myra.Graphics2D.UI;
|
||||||
@@ -59,7 +60,7 @@ public sealed class SettingsScene : Scene
|
|||||||
{
|
{
|
||||||
if (!Context.Scenes.IsTransitioning)
|
if (!Context.Scenes.IsTransitioning)
|
||||||
{
|
{
|
||||||
Context.Scenes.Switch(new MainMenuScene(), Transition.Fade(0.4f));
|
Context.Scenes.Switch(new MainMenuScene(), Transitions.Fade(0.4f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ using Microsoft.Xna.Framework.Input;
|
|||||||
using MrGameEng.Assets;
|
using MrGameEng.Assets;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
using MrGameEng.DevConsole;
|
using MrGameEng.DevConsole;
|
||||||
|
using MrGameEng.Formulas;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
using MrGameEng.Host;
|
||||||
using MrGameEng.Input;
|
using MrGameEng.Input;
|
||||||
using MrGameEng.Inspector;
|
using MrGameEng.Inspector;
|
||||||
using MrGameEng.Lighting;
|
using MrGameEng.Lighting;
|
||||||
@@ -73,7 +76,7 @@ public sealed class WorldScene : Scene
|
|||||||
var assets = Context.Services.GetOrDefault<AssetManager>() ?? Context.UseAssets();
|
var assets = Context.Services.GetOrDefault<AssetManager>() ?? Context.UseAssets();
|
||||||
var content = Context.Services.Get<GameContent>();
|
var content = Context.Services.Get<GameContent>();
|
||||||
var atlases = Context.Services.Get<ModAtlases>();
|
var atlases = Context.Services.Get<ModAtlases>();
|
||||||
var device = Context.GraphicsDevice;
|
var device = Context.GetGraphicsDevice();
|
||||||
var input = this.UseInput();
|
var input = this.UseInput();
|
||||||
_speed = Context.Services.Get<GameSpeed>();
|
_speed = Context.Services.Get<GameSpeed>();
|
||||||
_speed.SetStep(0); // новый мир/загрузка стартуют на x1
|
_speed.SetStep(0); // новый мир/загрузка стартуют на x1
|
||||||
@@ -159,6 +162,7 @@ public sealed class WorldScene : Scene
|
|||||||
);
|
);
|
||||||
|
|
||||||
UpdateSystems.Add(new PlantGrowthSystem(_plants, calendar, climate, lighting, CellSize));
|
UpdateSystems.Add(new PlantGrowthSystem(_plants, calendar, climate, lighting, CellSize));
|
||||||
|
UpdateSystems.Add(new PlantFruitingSystem(_plants, calendar, climate));
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new PlantLifecycleSystem(
|
new PlantLifecycleSystem(
|
||||||
Store,
|
Store,
|
||||||
@@ -350,9 +354,14 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
// Снимок всей популяции растений: вид (деф), позиция, возраст, стадия, почва и геном.
|
// Снимок всей популяции растений: вид (деф), позиция, возраст, стадия, почва и геном.
|
||||||
Store
|
Store
|
||||||
.Query<PlantGrowth, PlantGenome, Transform2D>()
|
.Query<PlantGrowth, PlantOrganism, Transform2D>()
|
||||||
.ForEachEntity(
|
.ForEachEntity(
|
||||||
(ref PlantGrowth grow, ref PlantGenome gene, ref Transform2D transform, Entity _) =>
|
(
|
||||||
|
ref PlantGrowth grow,
|
||||||
|
ref PlantOrganism org,
|
||||||
|
ref Transform2D transform,
|
||||||
|
Entity _
|
||||||
|
) =>
|
||||||
{
|
{
|
||||||
save.Plants.Add(
|
save.Plants.Add(
|
||||||
new PlantSave
|
new PlantSave
|
||||||
@@ -363,7 +372,7 @@ public sealed class WorldScene : Scene
|
|||||||
AgeDays = grow.AgeDays,
|
AgeDays = grow.AgeDays,
|
||||||
Stage = grow.Stage,
|
Stage = grow.Stage,
|
||||||
CellFertility = grow.CellFertility,
|
CellFertility = grow.CellFertility,
|
||||||
Genome = gene,
|
Genome = org.Genome.ToDictionary(),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -376,6 +385,7 @@ public sealed class WorldScene : Scene
|
|||||||
|
|
||||||
private void RestorePlants(GameContent content)
|
private void RestorePlants(GameContent content)
|
||||||
{
|
{
|
||||||
|
var fallback = new Random(_config.Seed); // для старых/битых сейвов без генома
|
||||||
foreach (var plant in _save!.Plants)
|
foreach (var plant in _save!.Plants)
|
||||||
{
|
{
|
||||||
if (!content.Defs.TryGet<PlantDef>(plant.Species, out var def))
|
if (!content.Defs.TryGet<PlantDef>(plant.Species, out var def))
|
||||||
@@ -383,13 +393,17 @@ public sealed class WorldScene : Scene
|
|||||||
continue; // вид пропал (мод убрали) — пропускаем растение
|
continue; // вид пропал (мод убрали) — пропускаем растение
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var index = _plants.IndexOf(def);
|
||||||
|
var genome = plant.Genome is { Count: > 0 }
|
||||||
|
? new Genome(plant.Genome)
|
||||||
|
: _plants[index].Template.Generate(fallback);
|
||||||
PlantFactory.Create(
|
PlantFactory.Create(
|
||||||
Store,
|
Store,
|
||||||
_plants,
|
_plants,
|
||||||
_plants.IndexOf(def),
|
index,
|
||||||
new Vector2(plant.X, plant.Y),
|
new Vector2(plant.X, plant.Y),
|
||||||
plant.AgeDays,
|
plant.AgeDays,
|
||||||
plant.Genome,
|
genome,
|
||||||
plant.CellFertility,
|
plant.CellFertility,
|
||||||
CellSize
|
CellSize
|
||||||
);
|
);
|
||||||
@@ -435,7 +449,7 @@ public sealed class WorldScene : Scene
|
|||||||
if (!Context.Scenes.IsTransitioning)
|
if (!Context.Scenes.IsTransitioning)
|
||||||
{
|
{
|
||||||
_speed.Resume();
|
_speed.Resume();
|
||||||
Context.Scenes.Switch(scene, Transition.Fade(0.5f));
|
Context.Scenes.Switch(scene, Transitions.Fade(0.5f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,14 +470,148 @@ public sealed class WorldScene : Scene
|
|||||||
c.WriteLine($"regenerating world, seed {seed}");
|
c.WriteLine($"regenerating world, seed {seed}");
|
||||||
Context.Scenes.Switch(
|
Context.Scenes.Switch(
|
||||||
new WorldScene(_config with { Seed = seed }),
|
new WorldScene(_config with { Seed = seed }),
|
||||||
Transition.Fade(0.6f)
|
Transitions.Fade(0.6f)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
console.Register(
|
||||||
|
"formula",
|
||||||
|
"formula <expr> — compile and evaluate an expression (gene-formula engine demo)",
|
||||||
|
(c, args) =>
|
||||||
|
{
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
c.WriteLine(
|
||||||
|
"usage: formula <expr> e.g. formula clamp(lerp(0, 10, 0.5), 0, 8)"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var expression = string.Join(' ', args);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
c.WriteLine($"{expression} = {Formula.Compile(expression).Evaluate()}");
|
||||||
|
}
|
||||||
|
catch (FormulaException e)
|
||||||
|
{
|
||||||
|
c.WriteLine($"error: {e.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.Register(
|
||||||
|
"gene",
|
||||||
|
"gene [seed] — generate a genome from Gene defs, show alleles/traits and a bred child",
|
||||||
|
(c, args) => RunGeneDemo(c, content, args)
|
||||||
|
);
|
||||||
|
console.Register(
|
||||||
|
"plant",
|
||||||
|
"plant <species> [seed] — sample a species genome and show its gene-driven traits/products",
|
||||||
|
(c, args) => RunPlantDemo(c, content, args)
|
||||||
|
);
|
||||||
console.Register(
|
console.Register(
|
||||||
"menu",
|
"menu",
|
||||||
"menu — return to the main menu",
|
"menu — return to the main menu",
|
||||||
(_, _) => Switch(new MainMenuScene())
|
(_, _) => Switch(new MainMenuScene())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Демонстрация генной системы (фаза G2): из Gene-дефов генерируем геном, печатаем аллели,
|
||||||
|
// выраженные значения и признаки (вычисленные формулами), затем скрещиваем двух особей.
|
||||||
|
private static void RunGeneDemo(DevConsole console, GameContent content, string[] args)
|
||||||
|
{
|
||||||
|
var genes = content.Defs.All<GeneDef>();
|
||||||
|
if (genes.Count == 0)
|
||||||
|
{
|
||||||
|
console.WriteLine("no Gene defs loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var registry = genes.ToDictionary(g => g.DefName, g => g, StringComparer.Ordinal);
|
||||||
|
var seed = args.Length > 0 ? int.Parse(args[0]) : Random.Shared.Next();
|
||||||
|
var random = new Random(seed);
|
||||||
|
|
||||||
|
console.WriteLine($"genome from {genes.Count} genes, seed {seed}:");
|
||||||
|
var parentA = Genome.Generate(genes, random);
|
||||||
|
foreach (var gene in genes)
|
||||||
|
{
|
||||||
|
var allele = parentA[gene.DefName];
|
||||||
|
console.WriteLine(
|
||||||
|
$" {gene.DefName}: [{allele.A:0.##}, {allele.B:0.##}] -> {parentA.Express(gene):0.###}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.WriteLine("traits:");
|
||||||
|
foreach (var (trait, value) in Phenotype.Compute(parentA, registry).OrderBy(t => t.Key))
|
||||||
|
{
|
||||||
|
console.WriteLine($" {trait} = {value:0.###}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var parentB = Genome.Generate(genes, random);
|
||||||
|
var child = Genome.Breed(parentA, parentB, registry, random);
|
||||||
|
var childTraits = Phenotype.Compute(child, registry);
|
||||||
|
console.WriteLine(
|
||||||
|
$"bred child: {child.Alleles.Count} genes, "
|
||||||
|
+ $"vigor={childTraits.GetValueOrDefault("vigor"):0.###}, "
|
||||||
|
+ $"lifespan={childTraits.GetValueOrDefault("lifespan"):0.#}, "
|
||||||
|
+ $"variant={childTraits.GetValueOrDefault("variant"):0}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Демонстрация генного контента (фаза G4): по виду берём его набор генов, генерируем особь и
|
||||||
|
// печатаем признаки роста/жизни и продукты (плоды/добыча с количеством от генов).
|
||||||
|
private void RunPlantDemo(DevConsole console, GameContent content, string[] args)
|
||||||
|
{
|
||||||
|
var species = content.Defs.NamesOf("Plant");
|
||||||
|
if (args.Length == 0)
|
||||||
|
{
|
||||||
|
console.WriteLine("usage: plant <species> [seed] e.g. plant TreeOakA");
|
||||||
|
console.WriteLine($"species: {string.Join(", ", species)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!content.Defs.TryGet<PlantDef>(args[0], out var def))
|
||||||
|
{
|
||||||
|
console.WriteLine($"no plant '{args[0]}'; species: {string.Join(", ", species)}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var index = _plants.IndexOf(def);
|
||||||
|
var seed = args.Length > 1 ? int.Parse(args[1]) : Random.Shared.Next();
|
||||||
|
var genome = _plants[index].Template.Generate(new Random(seed));
|
||||||
|
var traits = PlantPhenotype.FromTraits(Phenotype.Compute(genome, _plants.GeneRegistry));
|
||||||
|
|
||||||
|
console.WriteLine(
|
||||||
|
$"{def.DefName} (seed {seed}){(traits.IsVariant ? " [variant morph]" : "")}:"
|
||||||
|
);
|
||||||
|
console.WriteLine(
|
||||||
|
$" vigor {traits.Vigor:0.##}, lifespan {traits.Lifespan:0} d, "
|
||||||
|
+ $"optimalLight {traits.OptimalLight:0.##}, leafHue {traits.LeafHue:0.##}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (def.HarvestProduct is { } harvest)
|
||||||
|
{
|
||||||
|
console.WriteLine(
|
||||||
|
$" harvest: {ProductLabel(content, harvest)} x{traits.HarvestAmount:0.#}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (def.FruitProduct is { } fruit && traits.FruitYield >= 1f)
|
||||||
|
{
|
||||||
|
var season = content.Languages.Get(
|
||||||
|
$"season.{((Season)traits.FruitSeason).ToString().ToLowerInvariant()}"
|
||||||
|
);
|
||||||
|
console.WriteLine(
|
||||||
|
$" fruit: {ProductLabel(content, fruit)} x{traits.FruitYield:0.#} in {season}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
console.WriteLine(" barren (no fruit)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ProductLabel(GameContent content, string productDefName) =>
|
||||||
|
content.Defs.TryGet<ProductDef>(productDefName, out var product)
|
||||||
|
? content.Languages.Get(product.Label)
|
||||||
|
: productDefName;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,25 @@ using Friflo.Engine.ECS;
|
|||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using LittleSim.Scenes;
|
using LittleSim.Scenes;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Единая точка создания сущности-растения: спрайт стадии по возрасту, тинт по морфе генома и
|
/// Единая точка создания сущности-растения: спрайт стадии по возрасту, признаки из генома формулами
|
||||||
/// компоненты роста/генома. Переиспользуется начальным скаттером, размножением и загрузкой сейва —
|
/// генов, тинт по морфе и компоненты роста/организма. Переиспользуется начальным скаттером,
|
||||||
/// чтобы все растения собирались одинаково.
|
/// размножением и загрузкой сейва — чтобы все растения собирались одинаково.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class PlantFactory
|
public static class PlantFactory
|
||||||
{
|
{
|
||||||
// Тинт рецессивной морфы (домножается на текстуру) — делает вариант визуально отличимым.
|
// Тинт рецессивной морфы (домножается на текстуру) — делает вариант визуально отличимым.
|
||||||
private static readonly Color VariantTint = new(214, 150, 176);
|
private static readonly Color VariantTint = new(214, 150, 176);
|
||||||
|
|
||||||
|
// Якоря оттенка листвы: тёплый жёлто-зелёный → холодный сине-зелёный (по гену leafHue).
|
||||||
|
private static readonly Color LeafWarm = new(170, 200, 90);
|
||||||
|
private static readonly Color LeafCool = new(90, 170, 150);
|
||||||
|
|
||||||
/// <summary>Создаёт растение вида <paramref name="species"/> с заданным возрастом и геномом.</summary>
|
/// <summary>Создаёт растение вида <paramref name="species"/> с заданным возрастом и геномом.</summary>
|
||||||
public static Entity Create(
|
public static Entity Create(
|
||||||
EntityStore store,
|
EntityStore store,
|
||||||
@@ -23,7 +28,7 @@ public static class PlantFactory
|
|||||||
int species,
|
int species,
|
||||||
Vector2 position,
|
Vector2 position,
|
||||||
float ageDays,
|
float ageDays,
|
||||||
in PlantGenome genome,
|
Genome genome,
|
||||||
float cellFertility,
|
float cellFertility,
|
||||||
int cellSize
|
int cellSize
|
||||||
)
|
)
|
||||||
@@ -31,10 +36,11 @@ public static class PlantFactory
|
|||||||
var sp = plants[species];
|
var sp = plants[species];
|
||||||
var stage = sp.StageAt(ageDays);
|
var stage = sp.StageAt(ageDays);
|
||||||
var resolved = sp.Stages[stage];
|
var resolved = sp.Stages[stage];
|
||||||
|
var traits = PlantPhenotype.FromTraits(Phenotype.Compute(genome, plants.GeneRegistry));
|
||||||
|
|
||||||
var sprite = new Sprite(resolved.Region, GameLayers.Beings);
|
var sprite = new Sprite(resolved.Region, GameLayers.Beings);
|
||||||
sprite.CenterOrigin();
|
sprite.CenterOrigin();
|
||||||
sprite.Color = genome.IsVariant ? VariantTint : Color.White;
|
sprite.Color = Tint(traits);
|
||||||
|
|
||||||
return store.CreateEntity(
|
return store.CreateEntity(
|
||||||
new Transform2D(
|
new Transform2D(
|
||||||
@@ -49,7 +55,17 @@ public static class PlantFactory
|
|||||||
AgeDays = ageDays,
|
AgeDays = ageDays,
|
||||||
CellFertility = cellFertility,
|
CellFertility = cellFertility,
|
||||||
},
|
},
|
||||||
genome
|
new PlantOrganism { Genome = genome, Traits = traits },
|
||||||
|
new Fruiting()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Цвет растения из генов: лёгкий оттенок листвы (ген leafHue) поверх текстуры; рецессивная морфа
|
||||||
|
// дополнительно сдвигает тинт в свою сторону.
|
||||||
|
private static Color Tint(in PlantPhenotype traits)
|
||||||
|
{
|
||||||
|
var leaf = Color.Lerp(LeafWarm, LeafCool, Math.Clamp(traits.LeafHue, 0f, 1f));
|
||||||
|
var tint = Color.Lerp(Color.White, leaf, 0.35f);
|
||||||
|
return traits.IsVariant ? Color.Lerp(tint, VariantTint, 0.6f) : tint;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
using Friflo.Engine.ECS.Systems;
|
||||||
|
using LittleSim.Content;
|
||||||
|
using MrGameEng.AI;
|
||||||
|
using MrGameEng.Core;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Плодоношение (фаза G4): у зрелого растения с признаком <see cref="PlantPhenotype.FruitYield"/> > 0
|
||||||
|
/// в его генетический сезон (<see cref="PlantPhenotype.FruitSeason"/>) копятся зрелые плоды до потолка
|
||||||
|
/// <see cref="PlantPhenotype.FruitYield"/>; вне сезона плоды опадают. Полностью управляется генами через
|
||||||
|
/// признаки — система лишь применяет их к компоненту <see cref="Fruiting"/>. Незрелые/бесплодные
|
||||||
|
/// растения остаются с нулём.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PlantFruitingSystem(PlantSet plants, Calendar calendar, Climate climate)
|
||||||
|
: QuerySystem<PlantGrowth, PlantOrganism, Fruiting>
|
||||||
|
{
|
||||||
|
// За сколько игровых дней набирается/опадает полный урожай.
|
||||||
|
private const float RipenDays = 10f;
|
||||||
|
private const float DropDays = 20f;
|
||||||
|
|
||||||
|
protected override void OnUpdate()
|
||||||
|
{
|
||||||
|
var deltaDays = Tick.deltaTime / calendar.SecondsPerDay;
|
||||||
|
if (deltaDays <= 0f)
|
||||||
|
{
|
||||||
|
return; // пауза
|
||||||
|
}
|
||||||
|
|
||||||
|
var season = (int)climate.Season;
|
||||||
|
|
||||||
|
foreach (var (growths, organisms, fruits, _) in Query.Chunks)
|
||||||
|
{
|
||||||
|
var g = growths.Span;
|
||||||
|
var orgs = organisms.Span;
|
||||||
|
var f = fruits.Span;
|
||||||
|
for (var i = 0; i < g.Length; i++)
|
||||||
|
{
|
||||||
|
ref var traits = ref orgs[i].Traits;
|
||||||
|
var mature = g[i].Stage >= plants[g[i].Species].Stages.Length - 1;
|
||||||
|
if (!mature || traits.FruitYield < 1f)
|
||||||
|
{
|
||||||
|
f[i].RipeFruit = 0f; // незрелое или бесплодное — плодов нет
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var ripe = ref f[i].RipeFruit;
|
||||||
|
if (season == traits.FruitSeason)
|
||||||
|
{
|
||||||
|
ripe = MathF.Min(
|
||||||
|
traits.FruitYield,
|
||||||
|
ripe + deltaDays * (traits.FruitYield / RipenDays)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ripe = MathF.Max(0f, ripe - deltaDays * (traits.FruitYield / DropDays));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
using Friflo.Engine.ECS;
|
|
||||||
using LittleSim.Content;
|
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Диплоидный геном растения: по паре аллелей на ген. Числовые гены экспрессируются гибридно —
|
|
||||||
/// фенотип есть среднее двух аллелей; дискретный ген морфы — по Менделю (рецессив виден только в
|
|
||||||
/// гомозиготе). Аллели наследуются (правятся в инспекторе); фенотип-свойства читают системы роста и
|
|
||||||
/// жизненного цикла. <see cref="Breed"/> моделирует мейоз и мутацию.
|
|
||||||
/// </summary>
|
|
||||||
public struct PlantGenome : IComponent
|
|
||||||
{
|
|
||||||
// --- Числовые гены (аллель A/B), фенотип = среднее ------------------------------------------
|
|
||||||
public float OptimalLightA;
|
|
||||||
public float OptimalLightB;
|
|
||||||
public float LightToleranceA;
|
|
||||||
public float LightToleranceB;
|
|
||||||
public float OptimalTemperatureA;
|
|
||||||
public float OptimalTemperatureB;
|
|
||||||
public float TemperatureToleranceA;
|
|
||||||
public float TemperatureToleranceB;
|
|
||||||
public float OptimalFertilityA;
|
|
||||||
public float OptimalFertilityB;
|
|
||||||
public float FertilityToleranceA;
|
|
||||||
public float FertilityToleranceB;
|
|
||||||
public float VigorA;
|
|
||||||
public float VigorB;
|
|
||||||
public float LifespanA;
|
|
||||||
public float LifespanB;
|
|
||||||
public float DispersalRangeA;
|
|
||||||
public float DispersalRangeB;
|
|
||||||
public float ReproduceIntervalA;
|
|
||||||
public float ReproduceIntervalB;
|
|
||||||
public float SelfPollinationA;
|
|
||||||
public float SelfPollinationB;
|
|
||||||
public float MutationRateA;
|
|
||||||
public float MutationRateB;
|
|
||||||
|
|
||||||
// --- Дискретный ген морфы (0 — доминантный, 1 — рецессивный) --------------------------------
|
|
||||||
public int MorphA;
|
|
||||||
public int MorphB;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: оптимальная освещённость.</summary>
|
|
||||||
public readonly float OptimalLight => (OptimalLightA + OptimalLightB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: толерантность по свету.</summary>
|
|
||||||
public readonly float LightTolerance => (LightToleranceA + LightToleranceB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: оптимальная температура.</summary>
|
|
||||||
public readonly float OptimalTemperature => (OptimalTemperatureA + OptimalTemperatureB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: толерантность по температуре.</summary>
|
|
||||||
public readonly float TemperatureTolerance =>
|
|
||||||
(TemperatureToleranceA + TemperatureToleranceB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: оптимальная плодородность.</summary>
|
|
||||||
public readonly float OptimalFertility => (OptimalFertilityA + OptimalFertilityB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: толерантность по плодородности.</summary>
|
|
||||||
public readonly float FertilityTolerance => (FertilityToleranceA + FertilityToleranceB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: бодрость роста.</summary>
|
|
||||||
public readonly float Vigor => (VigorA + VigorB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: продолжительность жизни (дней).</summary>
|
|
||||||
public readonly float Lifespan => (LifespanA + LifespanB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: дальность расселения (клеток).</summary>
|
|
||||||
public readonly float DispersalRange => (DispersalRangeA + DispersalRangeB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: интервал размножения (дней).</summary>
|
|
||||||
public readonly float ReproduceInterval => (ReproduceIntervalA + ReproduceIntervalB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: способность к самоопылению (0..1).</summary>
|
|
||||||
public readonly float SelfPollination => (SelfPollinationA + SelfPollinationB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип: темп мутаций (0..1).</summary>
|
|
||||||
public readonly float MutationRate => (MutationRateA + MutationRateB) * 0.5f;
|
|
||||||
|
|
||||||
/// <summary>Фенотип морфы: рецессивный вариант проявляется только в гомозиготе (1,1).</summary>
|
|
||||||
public readonly bool IsVariant => MorphA == 1 && MorphB == 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Генерирует особь из базового генома вида: числовые аллели — база ± относительный разброс
|
|
||||||
/// (<see cref="GenomeDef.Spread"/>); аллель морфы = рецессивный (1) с вероятностью
|
|
||||||
/// <see cref="GenomeDef.VariantChance"/>. Сидируется <paramref name="random"/> — детерминизм.
|
|
||||||
/// </summary>
|
|
||||||
public static PlantGenome FromDef(GenomeDef def, Random random)
|
|
||||||
{
|
|
||||||
float Allele(float baseValue) =>
|
|
||||||
baseValue + (random.NextSingle() * 2f - 1f) * def.Spread * baseValue;
|
|
||||||
|
|
||||||
int MorphAllele() => random.NextSingle() < def.VariantChance ? 1 : 0;
|
|
||||||
|
|
||||||
return new PlantGenome
|
|
||||||
{
|
|
||||||
OptimalLightA = Allele(def.OptimalLight),
|
|
||||||
OptimalLightB = Allele(def.OptimalLight),
|
|
||||||
LightToleranceA = Allele(def.LightTolerance),
|
|
||||||
LightToleranceB = Allele(def.LightTolerance),
|
|
||||||
OptimalTemperatureA = Allele(def.OptimalTemperature),
|
|
||||||
OptimalTemperatureB = Allele(def.OptimalTemperature),
|
|
||||||
TemperatureToleranceA = Allele(def.TemperatureTolerance),
|
|
||||||
TemperatureToleranceB = Allele(def.TemperatureTolerance),
|
|
||||||
OptimalFertilityA = Allele(def.OptimalFertility),
|
|
||||||
OptimalFertilityB = Allele(def.OptimalFertility),
|
|
||||||
FertilityToleranceA = Allele(def.FertilityTolerance),
|
|
||||||
FertilityToleranceB = Allele(def.FertilityTolerance),
|
|
||||||
VigorA = Allele(def.Vigor),
|
|
||||||
VigorB = Allele(def.Vigor),
|
|
||||||
LifespanA = Allele(def.Lifespan),
|
|
||||||
LifespanB = Allele(def.Lifespan),
|
|
||||||
DispersalRangeA = Allele(def.DispersalRange),
|
|
||||||
DispersalRangeB = Allele(def.DispersalRange),
|
|
||||||
ReproduceIntervalA = Allele(def.ReproduceInterval),
|
|
||||||
ReproduceIntervalB = Allele(def.ReproduceInterval),
|
|
||||||
SelfPollinationA = Allele(def.SelfPollination),
|
|
||||||
SelfPollinationB = Allele(def.SelfPollination),
|
|
||||||
MutationRateA = Allele(def.MutationRate),
|
|
||||||
MutationRateB = Allele(def.MutationRate),
|
|
||||||
MorphA = MorphAllele(),
|
|
||||||
MorphB = MorphAllele(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Потомок двух родителей (мейоз): аллель A — случайный из родителя <paramref name="a"/>, аллель
|
|
||||||
/// B — из <paramref name="b"/>; затем мутация (с вероятностью среднего темпа мутаций родителей —
|
|
||||||
/// сдвиг числового аллеля на малую долю / переключение морфы). Самоопыление = <c>Breed(self, self)</c>.
|
|
||||||
/// </summary>
|
|
||||||
public static PlantGenome Breed(in PlantGenome a, in PlantGenome b, Random rng)
|
|
||||||
{
|
|
||||||
var rate = (a.MutationRate + b.MutationRate) * 0.5f;
|
|
||||||
|
|
||||||
float Mut(float v) =>
|
|
||||||
rng.NextSingle() < rate ? v + (rng.NextSingle() * 2f - 1f) * 0.12f * v : v;
|
|
||||||
float FromA(float x, float y) => Mut(rng.NextSingle() < 0.5f ? x : y);
|
|
||||||
int MutMorph(int m) => rng.NextSingle() < rate ? 1 - m : m;
|
|
||||||
|
|
||||||
return new PlantGenome
|
|
||||||
{
|
|
||||||
OptimalLightA = FromA(a.OptimalLightA, a.OptimalLightB),
|
|
||||||
OptimalLightB = FromA(b.OptimalLightA, b.OptimalLightB),
|
|
||||||
LightToleranceA = FromA(a.LightToleranceA, a.LightToleranceB),
|
|
||||||
LightToleranceB = FromA(b.LightToleranceA, b.LightToleranceB),
|
|
||||||
OptimalTemperatureA = FromA(a.OptimalTemperatureA, a.OptimalTemperatureB),
|
|
||||||
OptimalTemperatureB = FromA(b.OptimalTemperatureA, b.OptimalTemperatureB),
|
|
||||||
TemperatureToleranceA = FromA(a.TemperatureToleranceA, a.TemperatureToleranceB),
|
|
||||||
TemperatureToleranceB = FromA(b.TemperatureToleranceA, b.TemperatureToleranceB),
|
|
||||||
OptimalFertilityA = FromA(a.OptimalFertilityA, a.OptimalFertilityB),
|
|
||||||
OptimalFertilityB = FromA(b.OptimalFertilityA, b.OptimalFertilityB),
|
|
||||||
FertilityToleranceA = FromA(a.FertilityToleranceA, a.FertilityToleranceB),
|
|
||||||
FertilityToleranceB = FromA(b.FertilityToleranceA, b.FertilityToleranceB),
|
|
||||||
VigorA = FromA(a.VigorA, a.VigorB),
|
|
||||||
VigorB = FromA(b.VigorA, b.VigorB),
|
|
||||||
LifespanA = FromA(a.LifespanA, a.LifespanB),
|
|
||||||
LifespanB = FromA(b.LifespanA, b.LifespanB),
|
|
||||||
DispersalRangeA = FromA(a.DispersalRangeA, a.DispersalRangeB),
|
|
||||||
DispersalRangeB = FromA(b.DispersalRangeA, b.DispersalRangeB),
|
|
||||||
ReproduceIntervalA = FromA(a.ReproduceIntervalA, a.ReproduceIntervalB),
|
|
||||||
ReproduceIntervalB = FromA(b.ReproduceIntervalA, b.ReproduceIntervalB),
|
|
||||||
SelfPollinationA = FromA(a.SelfPollinationA, a.SelfPollinationB),
|
|
||||||
SelfPollinationB = FromA(b.SelfPollinationA, b.SelfPollinationB),
|
|
||||||
MutationRateA = FromA(a.MutationRateA, a.MutationRateB),
|
|
||||||
MutationRateB = FromA(b.MutationRateA, b.MutationRateB),
|
|
||||||
MorphA = MutMorph(rng.NextSingle() < 0.5f ? a.MorphA : a.MorphB),
|
|
||||||
MorphB = MutMorph(rng.NextSingle() < 0.5f ? b.MorphA : b.MorphB),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,9 +11,10 @@ namespace LittleSim.Sim;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
|
/// Двигает рост растений по игровому календарю: прирост возраста = пригодность среды для генов
|
||||||
/// растения. Скорость = <c>бодрость · пригодность(свет) · пригодность(температура) ·
|
/// растения. Скорость = <c>бодрость · пригодность(свет) · пригодность(температура) ·
|
||||||
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из генов
|
/// пригодность(плодородность)</c>, каждая пригодность — гауссов «колокол» вокруг оптимума из
|
||||||
/// (см. <see cref="PlantGenome"/>). Свет/температура в фазе B глобальные (день/ночь и сезон); стадии
|
/// признаков (<see cref="PlantPhenotype"/>), вычисленных формулами генов. Свет локальный (лайтмап),
|
||||||
/// и спрайт переключаются по накопленному возрасту как прежде. Терминальную стадию пропускает.
|
/// температура глобальная (сезон/день-ночь); стадии и спрайт переключаются по накопленному
|
||||||
|
/// возрасту как прежде. Терминальную стадию пропускает.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class PlantGrowthSystem(
|
public sealed class PlantGrowthSystem(
|
||||||
PlantSet plants,
|
PlantSet plants,
|
||||||
@@ -21,7 +22,7 @@ public sealed class PlantGrowthSystem(
|
|||||||
Climate climate,
|
Climate climate,
|
||||||
Lighting lighting,
|
Lighting lighting,
|
||||||
int cellSize
|
int cellSize
|
||||||
) : QuerySystem<PlantGrowth, PlantGenome, Sprite, Transform2D>
|
) : QuerySystem<PlantGrowth, PlantOrganism, Sprite, Transform2D>
|
||||||
{
|
{
|
||||||
protected override void OnUpdate()
|
protected override void OnUpdate()
|
||||||
{
|
{
|
||||||
@@ -34,10 +35,10 @@ public sealed class PlantGrowthSystem(
|
|||||||
// Температура глобальная; свет — локальный (лайтмап: подлесок под кронами темнее).
|
// Температура глобальная; свет — локальный (лайтмап: подлесок под кронами темнее).
|
||||||
var temperature = climate.Temperature;
|
var temperature = climate.Temperature;
|
||||||
|
|
||||||
foreach (var (growths, genomes, sprites, transforms, _) in Query.Chunks)
|
foreach (var (growths, organisms, sprites, transforms, _) in Query.Chunks)
|
||||||
{
|
{
|
||||||
var g = growths.Span;
|
var g = growths.Span;
|
||||||
var dna = genomes.Span;
|
var orgs = organisms.Span;
|
||||||
var s = sprites.Span;
|
var s = sprites.Span;
|
||||||
var t = transforms.Span;
|
var t = transforms.Span;
|
||||||
for (var i = 0; i < g.Length; i++)
|
for (var i = 0; i < g.Length; i++)
|
||||||
@@ -50,20 +51,20 @@ public sealed class PlantGrowthSystem(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
ref var gene = ref dna[i];
|
ref var traits = ref orgs[i].Traits;
|
||||||
var light = lighting.SampleAt(t[i].Position);
|
var light = lighting.SampleAt(t[i].Position);
|
||||||
var rate =
|
var rate =
|
||||||
gene.Vigor
|
traits.Vigor
|
||||||
* Suitability.Gaussian(light, gene.OptimalLight, gene.LightTolerance)
|
* Suitability.Gaussian(light, traits.OptimalLight, traits.LightTolerance)
|
||||||
* Suitability.Gaussian(
|
* Suitability.Gaussian(
|
||||||
temperature,
|
temperature,
|
||||||
gene.OptimalTemperature,
|
traits.OptimalTemperature,
|
||||||
gene.TemperatureTolerance
|
traits.TemperatureTolerance
|
||||||
)
|
)
|
||||||
* Suitability.Gaussian(
|
* Suitability.Gaussian(
|
||||||
grow.CellFertility,
|
grow.CellFertility,
|
||||||
gene.OptimalFertility,
|
traits.OptimalFertility,
|
||||||
gene.FertilityTolerance
|
traits.FertilityTolerance
|
||||||
);
|
);
|
||||||
|
|
||||||
grow.AgeDays += deltaDays * rate;
|
grow.AgeDays += deltaDays * rate;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Friflo.Engine.ECS.Systems;
|
|||||||
using LittleSim.Content;
|
using LittleSim.Content;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using MrGameEng.Core;
|
using MrGameEng.Core;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
using MrGameEng.Graphics;
|
using MrGameEng.Graphics;
|
||||||
|
|
||||||
namespace LittleSim.Sim;
|
namespace LittleSim.Sim;
|
||||||
@@ -31,7 +32,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
private readonly bool[] _cellLand;
|
private readonly bool[] _cellLand;
|
||||||
private readonly int _densityCap;
|
private readonly int _densityCap;
|
||||||
private readonly Random _rng;
|
private readonly Random _rng;
|
||||||
private readonly ArchetypeQuery<PlantGrowth, PlantGenome, Transform2D> _query;
|
private readonly ArchetypeQuery<PlantGrowth, PlantOrganism, Transform2D> _query;
|
||||||
|
|
||||||
private readonly int[] _count;
|
private readonly int[] _count;
|
||||||
private readonly CellRep[] _rep;
|
private readonly CellRep[] _rep;
|
||||||
@@ -65,7 +66,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
_cellLand = cellLand;
|
_cellLand = cellLand;
|
||||||
_densityCap = densityCap;
|
_densityCap = densityCap;
|
||||||
_rng = new Random(seed);
|
_rng = new Random(seed);
|
||||||
_query = store.Query<PlantGrowth, PlantGenome, Transform2D>();
|
_query = store.Query<PlantGrowth, PlantOrganism, Transform2D>();
|
||||||
_count = new int[width * height];
|
_count = new int[width * height];
|
||||||
_rep = new CellRep[width * height];
|
_rep = new CellRep[width * height];
|
||||||
}
|
}
|
||||||
@@ -99,16 +100,16 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
_candidates.Clear();
|
_candidates.Clear();
|
||||||
_births.Clear();
|
_births.Clear();
|
||||||
|
|
||||||
foreach (var (growths, genomes, transforms, entities) in _query.Chunks)
|
foreach (var (growths, organisms, transforms, entities) in _query.Chunks)
|
||||||
{
|
{
|
||||||
var g = growths.Span;
|
var g = growths.Span;
|
||||||
var dna = genomes.Span;
|
var orgs = organisms.Span;
|
||||||
var t = transforms.Span;
|
var t = transforms.Span;
|
||||||
for (var i = 0; i < g.Length; i++)
|
for (var i = 0; i < g.Length; i++)
|
||||||
{
|
{
|
||||||
ref var grow = ref g[i];
|
ref var grow = ref g[i];
|
||||||
ref var gene = ref dna[i];
|
ref var org = ref orgs[i];
|
||||||
if (grow.AgeDays > gene.Lifespan)
|
if (grow.AgeDays > org.Traits.Lifespan)
|
||||||
{
|
{
|
||||||
_deaths.Add(entities.EntityAt(i)); // умер от старости
|
_deaths.Add(entities.EntityAt(i)); // умер от старости
|
||||||
continue;
|
continue;
|
||||||
@@ -125,7 +126,8 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
Species = grow.Species,
|
Species = grow.Species,
|
||||||
Mature = mature,
|
Mature = mature,
|
||||||
Genome = gene,
|
Genome = org.Genome,
|
||||||
|
MutationRate = org.Traits.MutationRate,
|
||||||
};
|
};
|
||||||
if (mature)
|
if (mature)
|
||||||
{
|
{
|
||||||
@@ -134,7 +136,8 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
Cell = cell,
|
Cell = cell,
|
||||||
Species = grow.Species,
|
Species = grow.Species,
|
||||||
Genome = gene,
|
Genome = org.Genome,
|
||||||
|
Traits = org.Traits,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -147,24 +150,29 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
foreach (var cand in _candidates)
|
foreach (var cand in _candidates)
|
||||||
{
|
{
|
||||||
var interval = MathF.Max(0.5f, cand.Genome.ReproduceInterval);
|
var interval = MathF.Max(0.5f, cand.Traits.ReproduceInterval);
|
||||||
if (_rng.NextSingle() >= elapsed / interval)
|
if (_rng.NextSingle() >= elapsed / interval)
|
||||||
{
|
{
|
||||||
continue; // в этот тик не сеет
|
continue; // в этот тик не сеет
|
||||||
}
|
}
|
||||||
|
|
||||||
var range = Math.Clamp((int)MathF.Round(cand.Genome.DispersalRange), 1, 6);
|
var range = Math.Clamp((int)MathF.Round(cand.Traits.DispersalRange), 1, 6);
|
||||||
var cx = cand.Cell % _width;
|
var cx = cand.Cell % _width;
|
||||||
var cy = cand.Cell / _width;
|
var cy = cand.Cell / _width;
|
||||||
|
|
||||||
if (!TryFindPartner(cx, cy, range, cand.Species, out var partner))
|
if (!TryFindPartner(cx, cy, range, cand.Species, out var partner))
|
||||||
{
|
{
|
||||||
if (_rng.NextSingle() >= cand.Genome.SelfPollination)
|
if (_rng.NextSingle() >= cand.Traits.SelfPollination)
|
||||||
{
|
{
|
||||||
continue; // нет партнёра и самоопыление не удалось
|
continue; // нет партнёра и самоопыление не удалось
|
||||||
}
|
}
|
||||||
|
|
||||||
partner = cand.Genome; // самоопыление
|
// самоопыление — партнёр это сам кандидат
|
||||||
|
partner = new Partner
|
||||||
|
{
|
||||||
|
Genome = cand.Genome,
|
||||||
|
MutationRate = cand.Traits.MutationRate,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryPickTarget(cx, cy, range, out var tcell))
|
if (!TryPickTarget(cx, cy, range, out var tcell))
|
||||||
@@ -172,7 +180,15 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
continue; // некуда сеять (нет суши/свободного места рядом)
|
continue; // некуда сеять (нет суши/свободного места рядом)
|
||||||
}
|
}
|
||||||
|
|
||||||
var child = PlantGenome.Breed(cand.Genome, partner, _rng);
|
// Темп мутаций потомка — средний эволюционирующий признак родителей (ген управляет мутацией).
|
||||||
|
var mutationChance = (cand.Traits.MutationRate + partner.MutationRate) * 0.5f;
|
||||||
|
var child = Genome.Breed(
|
||||||
|
cand.Genome,
|
||||||
|
partner.Genome,
|
||||||
|
_plants.GeneRegistry,
|
||||||
|
_rng,
|
||||||
|
mutationChance
|
||||||
|
);
|
||||||
var tx = tcell % _width;
|
var tx = tcell % _width;
|
||||||
var ty = tcell / _width;
|
var ty = tcell / _width;
|
||||||
var position = new Vector2(
|
var position = new Vector2(
|
||||||
@@ -192,7 +208,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool TryFindPartner(int cx, int cy, int range, int species, out PlantGenome partner)
|
private bool TryFindPartner(int cx, int cy, int range, int species, out Partner partner)
|
||||||
{
|
{
|
||||||
for (var dy = -range; dy <= range; dy++)
|
for (var dy = -range; dy <= range; dy++)
|
||||||
{
|
{
|
||||||
@@ -213,7 +229,7 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
ref var rep = ref _rep[ny * _width + nx];
|
ref var rep = ref _rep[ny * _width + nx];
|
||||||
if (rep.Species == species && rep.Mature)
|
if (rep.Species == species && rep.Mature)
|
||||||
{
|
{
|
||||||
partner = rep.Genome;
|
partner = new Partner { Genome = rep.Genome, MutationRate = rep.MutationRate };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -272,21 +288,29 @@ public sealed class PlantLifecycleSystem : BaseSystem
|
|||||||
{
|
{
|
||||||
public int Species;
|
public int Species;
|
||||||
public bool Mature;
|
public bool Mature;
|
||||||
public PlantGenome Genome;
|
public Genome Genome;
|
||||||
|
public float MutationRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Candidate
|
private struct Candidate
|
||||||
{
|
{
|
||||||
public int Cell;
|
public int Cell;
|
||||||
public int Species;
|
public int Species;
|
||||||
public PlantGenome Genome;
|
public Genome Genome;
|
||||||
|
public PlantPhenotype Traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Partner
|
||||||
|
{
|
||||||
|
public Genome Genome;
|
||||||
|
public float MutationRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct Birth
|
private struct Birth
|
||||||
{
|
{
|
||||||
public int Species;
|
public int Species;
|
||||||
public Vector2 Position;
|
public Vector2 Position;
|
||||||
public PlantGenome Genome;
|
public Genome Genome;
|
||||||
public float Fertility;
|
public float Fertility;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Friflo.Engine.ECS;
|
||||||
|
using MrGameEng.Genetics;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Растительные признаки (фенотип), вычисленные из генома формулами генов (см. <c>genes.json</c>):
|
||||||
|
/// готовые значения, которые читают системы роста и жизненного цикла, не касаясь генов напрямую.
|
||||||
|
/// Имена полей соответствуют именам признаков в эффектах генов.
|
||||||
|
/// </summary>
|
||||||
|
public struct PlantPhenotype
|
||||||
|
{
|
||||||
|
public float OptimalLight;
|
||||||
|
public float LightTolerance;
|
||||||
|
public float OptimalTemperature;
|
||||||
|
public float TemperatureTolerance;
|
||||||
|
public float OptimalFertility;
|
||||||
|
public float FertilityTolerance;
|
||||||
|
public float Vigor;
|
||||||
|
public float Lifespan;
|
||||||
|
public float DispersalRange;
|
||||||
|
public float ReproduceInterval;
|
||||||
|
public float SelfPollination;
|
||||||
|
public float MutationRate;
|
||||||
|
|
||||||
|
// --- Контент (G4): плодоношение, добыча, цвет ---
|
||||||
|
public float FruitYield;
|
||||||
|
public int FruitSeason;
|
||||||
|
public float HarvestAmount;
|
||||||
|
public float LeafHue;
|
||||||
|
|
||||||
|
/// <summary>Морфа: рецессивный вариант (выраженное значение гена морфы ≈ 1) — другой тинт спрайта.</summary>
|
||||||
|
public bool IsVariant;
|
||||||
|
|
||||||
|
/// <summary>Собирает фенотип из карты признаков, посчитанной <see cref="Phenotype.Compute"/>.</summary>
|
||||||
|
public static PlantPhenotype FromTraits(IReadOnlyDictionary<string, float> traits)
|
||||||
|
{
|
||||||
|
float T(string name) => traits.GetValueOrDefault(name);
|
||||||
|
return new PlantPhenotype
|
||||||
|
{
|
||||||
|
OptimalLight = T("optimalLight"),
|
||||||
|
LightTolerance = T("lightTolerance"),
|
||||||
|
OptimalTemperature = T("optimalTemperature"),
|
||||||
|
TemperatureTolerance = T("temperatureTolerance"),
|
||||||
|
OptimalFertility = T("optimalFertility"),
|
||||||
|
FertilityTolerance = T("fertilityTolerance"),
|
||||||
|
Vigor = T("vigor"),
|
||||||
|
Lifespan = T("lifespan"),
|
||||||
|
DispersalRange = T("dispersalRange"),
|
||||||
|
ReproduceInterval = T("reproduceInterval"),
|
||||||
|
SelfPollination = T("selfPollination"),
|
||||||
|
MutationRate = T("mutationRate"),
|
||||||
|
FruitYield = T("fruitYield"),
|
||||||
|
FruitSeason = (int)MathF.Round(Math.Clamp(T("fruitSeason"), 0f, 3f)),
|
||||||
|
HarvestAmount = T("harvestAmount"),
|
||||||
|
LeafHue = T("leafHue"),
|
||||||
|
IsVariant = T("variant") >= 0.5f,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Компонент-организм растения: его управляемый <see cref="Genome"/> (для размножения, сейва и
|
||||||
|
/// пересчёта) и выраженный фенотип <see cref="Traits"/>, по которому работают системы. Заменяет
|
||||||
|
/// прежний фиксированный <c>PlantGenome</c> — теперь геном переменного состава и признаки из формул
|
||||||
|
/// генов (фаза G3). Геном — ссылочный объект, поэтому компонент несёт ссылку, а не копию.
|
||||||
|
/// </summary>
|
||||||
|
public struct PlantOrganism : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Геном особи (аллели по генам); общий источник истины для признаков и размножения.</summary>
|
||||||
|
public Genome Genome;
|
||||||
|
|
||||||
|
/// <summary>Выраженные признаки — то, что читают системы роста/жизненного цикла.</summary>
|
||||||
|
public PlantPhenotype Traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Плодоношение растения: сколько зрелых плодов сейчас на нём. Копится у зрелого растения в его
|
||||||
|
/// сезон плодоношения (признак <see cref="PlantPhenotype.FruitSeason"/>) до потолка
|
||||||
|
/// <see cref="PlantPhenotype.FruitYield"/>, вне сезона опадает. Управляется <c>PlantFruitingSystem</c>.
|
||||||
|
/// </summary>
|
||||||
|
public struct Fruiting : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Накоплено зрелых плодов (0..FruitYield).</summary>
|
||||||
|
public float RipeFruit;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user