Compare commits
19
Commits
97d83606ce
...
347fac2c1b
@@ -16,6 +16,7 @@
|
|||||||
"body": "Quadruped",
|
"body": "Quadruped",
|
||||||
"diet": ["plant"],
|
"diet": ["plant"],
|
||||||
"spawnPer1000Cells": 2.5,
|
"spawnPer1000Cells": 2.5,
|
||||||
|
"baseMassKg": 70,
|
||||||
"genome": {
|
"genome": {
|
||||||
"GeneMaxBodySize": 1.5,
|
"GeneMaxBodySize": 1.5,
|
||||||
"GeneMetabolism": 1.0,
|
"GeneMetabolism": 1.0,
|
||||||
@@ -30,7 +31,9 @@
|
|||||||
"GeneBreedingSeason": 2,
|
"GeneBreedingSeason": 2,
|
||||||
"GeneGestationDays": 30,
|
"GeneGestationDays": 30,
|
||||||
"GeneLitterSize": 1,
|
"GeneLitterSize": 1,
|
||||||
"GeneHerbivory": 1.0
|
"GeneHerbivory": 1.0,
|
||||||
|
"GeneToxinTolerance": 0.1,
|
||||||
|
"GeneSociability": 0.8
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@
|
|||||||
"attackDamage": 80,
|
"attackDamage": 80,
|
||||||
"attackBleed": 3.0,
|
"attackBleed": 3.0,
|
||||||
"attackBloodLoss": 5.0,
|
"attackBloodLoss": 5.0,
|
||||||
|
"baseMassKg": 45,
|
||||||
"genome": {
|
"genome": {
|
||||||
"GeneMaxBodySize": 1.6,
|
"GeneMaxBodySize": 1.6,
|
||||||
"GeneMetabolism": 1.1,
|
"GeneMetabolism": 1.1,
|
||||||
@@ -63,7 +67,8 @@
|
|||||||
"GeneBreedingSeason": 3,
|
"GeneBreedingSeason": 3,
|
||||||
"GeneGestationDays": 35,
|
"GeneGestationDays": 35,
|
||||||
"GeneLitterSize": 4,
|
"GeneLitterSize": 4,
|
||||||
"GeneCarnivory": 1.0
|
"GeneCarnivory": 1.0,
|
||||||
|
"GeneSociability": 0.6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -81,6 +86,7 @@
|
|||||||
"attackDamage": 45,
|
"attackDamage": 45,
|
||||||
"attackBleed": 1.8,
|
"attackBleed": 1.8,
|
||||||
"attackBloodLoss": 3.0,
|
"attackBloodLoss": 3.0,
|
||||||
|
"baseMassKg": 80,
|
||||||
"genome": {
|
"genome": {
|
||||||
"GeneMaxBodySize": 1.3,
|
"GeneMaxBodySize": 1.3,
|
||||||
"GeneMetabolism": 1.2,
|
"GeneMetabolism": 1.2,
|
||||||
@@ -97,7 +103,41 @@
|
|||||||
"GeneLitterSize": 5,
|
"GeneLitterSize": 5,
|
||||||
"GeneHerbivory": 0.4,
|
"GeneHerbivory": 0.4,
|
||||||
"GeneCarnivory": 0.2,
|
"GeneCarnivory": 0.2,
|
||||||
"GeneOmnivory": 0.85
|
"GeneOmnivory": 0.85,
|
||||||
|
"GeneToxinTolerance": 0.25,
|
||||||
|
"GeneSociability": 0.3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"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,
|
||||||
|
"baseMassKg": 2.5,
|
||||||
|
"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,
|
||||||
|
"GeneSociability": 0.55
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,23 +7,36 @@
|
|||||||
{
|
{
|
||||||
"defName": "Quadruped",
|
"defName": "Quadruped",
|
||||||
"parts": [
|
"parts": [
|
||||||
{ "name": "torso", "coverage": 0.32, "maxHp": 40, "vital": true },
|
{ "name": "torso", "coverage": 0.3, "maxHp": 40, "vital": true },
|
||||||
{ "name": "heart", "parent": "torso", "coverage": 0.02, "maxHp": 12, "vital": true,
|
{ "name": "heart", "parent": "torso", "coverage": 0.02, "maxHp": 12, "vital": true,
|
||||||
"capacities": { "BloodPumping": 1.0 } },
|
"capacities": { "BloodPumping": 1.0 } },
|
||||||
{ "name": "lungLeft", "parent": "torso", "coverage": 0.03, "maxHp": 12,
|
{ "name": "lungLeft", "parent": "torso", "coverage": 0.03, "maxHp": 12,
|
||||||
"capacities": { "Breathing": 0.5 } },
|
"capacities": { "Breathing": 0.5 } },
|
||||||
{ "name": "lungRight", "parent": "torso", "coverage": 0.03, "maxHp": 12,
|
{ "name": "lungRight", "parent": "torso", "coverage": 0.03, "maxHp": 12,
|
||||||
"capacities": { "Breathing": 0.5 } },
|
"capacities": { "Breathing": 0.5 } },
|
||||||
{ "name": "liver", "parent": "torso", "coverage": 0.03, "maxHp": 14, "vital": true },
|
{ "name": "liver", "parent": "torso", "coverage": 0.03, "maxHp": 14, "vital": true,
|
||||||
|
"capacities": { "BloodFiltration": 0.4 } },
|
||||||
|
{ "name": "kidneyLeft", "parent": "torso", "coverage": 0.02, "maxHp": 10,
|
||||||
|
"capacities": { "BloodFiltration": 0.3 } },
|
||||||
|
{ "name": "kidneyRight", "parent": "torso", "coverage": 0.02, "maxHp": 10,
|
||||||
|
"capacities": { "BloodFiltration": 0.3 } },
|
||||||
{ "name": "stomach", "parent": "torso", "coverage": 0.03, "maxHp": 12,
|
{ "name": "stomach", "parent": "torso", "coverage": 0.03, "maxHp": 12,
|
||||||
"capacities": { "Digestion": 1.0 } },
|
"capacities": { "Digestion": 1.0 } },
|
||||||
{ "name": "head", "coverage": 0.1, "maxHp": 25 },
|
{ "name": "head", "coverage": 0.08, "maxHp": 25 },
|
||||||
{ "name": "brain", "parent": "head", "coverage": 0.02, "maxHp": 12, "vital": true,
|
{ "name": "brain", "parent": "head", "coverage": 0.02, "maxHp": 12, "vital": true,
|
||||||
"capacities": { "Consciousness": 1.0 } },
|
"capacities": { "Consciousness": 1.0 } },
|
||||||
{ "name": "eyeLeft", "parent": "head", "coverage": 0.015, "maxHp": 8,
|
{ "name": "eyeLeft", "parent": "head", "coverage": 0.015, "maxHp": 8,
|
||||||
"capacities": { "Sight": 0.5 } },
|
"capacities": { "Sight": 0.5 } },
|
||||||
{ "name": "eyeRight", "parent": "head", "coverage": 0.015, "maxHp": 8,
|
{ "name": "eyeRight", "parent": "head", "coverage": 0.015, "maxHp": 8,
|
||||||
"capacities": { "Sight": 0.5 } },
|
"capacities": { "Sight": 0.5 } },
|
||||||
|
{ "name": "earLeft", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
||||||
|
"capacities": { "Hearing": 0.5 } },
|
||||||
|
{ "name": "earRight", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
||||||
|
"capacities": { "Hearing": 0.5 } },
|
||||||
|
{ "name": "jaw", "parent": "head", "coverage": 0.02, "maxHp": 10,
|
||||||
|
"capacities": { "Eating": 1.0, "Talking": 0.5 } },
|
||||||
|
{ "name": "tongue", "parent": "head", "coverage": 0.01, "maxHp": 8,
|
||||||
|
"capacities": { "Talking": 0.5 } },
|
||||||
{ "name": "legFrontLeft", "coverage": 0.08, "maxHp": 15, "capacities": { "Moving": 0.25 } },
|
{ "name": "legFrontLeft", "coverage": 0.08, "maxHp": 15, "capacities": { "Moving": 0.25 } },
|
||||||
{ "name": "legFrontRight", "coverage": 0.08, "maxHp": 15, "capacities": { "Moving": 0.25 } },
|
{ "name": "legFrontRight", "coverage": 0.08, "maxHp": 15, "capacities": { "Moving": 0.25 } },
|
||||||
{ "name": "legBackLeft", "coverage": 0.08, "maxHp": 15, "capacities": { "Moving": 0.25 } },
|
{ "name": "legBackLeft", "coverage": 0.08, "maxHp": 15, "capacities": { "Moving": 0.25 } },
|
||||||
|
|||||||
@@ -46,12 +46,32 @@
|
|||||||
"default": 0.0, "min": 0.0, "max": 1.0, "tags": ["animal", "diet"],
|
"default": 0.0, "min": 0.0, "max": 1.0, "tags": ["animal", "diet"],
|
||||||
"effects": { "omnivory": "value" } },
|
"effects": { "omnivory": "value" } },
|
||||||
|
|
||||||
|
// Устойчивость к растительным ядам (коэволюция): снижает дозу отравления при поедании токсичных
|
||||||
|
// растений (effectivePoison = toxicity × (1 − toxinTolerance), см. AnimalActionSystem). Растёт под
|
||||||
|
// давлением ядовитого корма — другая сторона гонки вооружений с GeneToxicity растений.
|
||||||
|
{ "defName": "GeneToxinTolerance", "parent": "BaseNumericGene", "label": "gene.toxinTolerance",
|
||||||
|
"default": 0.0, "min": 0.0, "max": 1.0, "tags": ["animal", "diet"],
|
||||||
|
"effects": { "toxinTolerance": "value" } },
|
||||||
|
|
||||||
// Пол как локус (фаза A3): аллели {X=0, Y=1}; самка XX, самец XY. Эффекта нет — пол читается из
|
// Пол как локус (фаза A3): аллели {X=0, Y=1}; самка XX, самец XY. Эффекта нет — пол читается из
|
||||||
// самих аллелей (наличие Y), не из выраженного значения. mutationChance 0 (X не мутирует в Y).
|
// самих аллелей (наличие Y), не из выраженного значения. mutationChance 0 (X не мутирует в Y).
|
||||||
// Генерация особи-основателя задаёт пол явно (XX или XY), чтобы не возник невозможный YY.
|
// Генерация особи-основателя задаёт пол явно (XX или XY), чтобы не возник невозможный YY.
|
||||||
{ "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"] },
|
||||||
|
|
||||||
|
// Социальность [0..1]: стадные/стайные виды держатся вместе (когезия при блуждании) и спокойнее в
|
||||||
|
// группе («безопасность в числе» снижает дальность реакции на хищника). См. AnimalDecisionSystem.
|
||||||
|
{ "defName": "GeneSociability", "parent": "BaseNumericGene", "label": "gene.sociability",
|
||||||
|
"default": 0.3, "min": 0.0, "max": 1.0, "spread": 0.05, "tags": ["animal", "behaviour"],
|
||||||
|
"effects": { "sociability": "value" } },
|
||||||
|
|
||||||
|
// Тип рождения (цикл зачатия): живородящие (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"],
|
||||||
|
|||||||
@@ -15,6 +15,21 @@
|
|||||||
"default": 0.33, "min": 0, "max": 1, "spread": 0.04, "tags": ["morphology", "color"],
|
"default": 0.33, "min": 0, "max": 1, "spread": 0.04, "tags": ["morphology", "color"],
|
||||||
"effects": { "leafHue": "value" } },
|
"effects": { "leafHue": "value" } },
|
||||||
|
|
||||||
|
// --- Защита растения (коэволюция с травоядными) ---
|
||||||
|
// Токсичность отравляет поедателя (hediff Poisoned, тем сильнее, чем ниже его устойчивость к яду);
|
||||||
|
// шипы наносят поедателю лёгкую травму; вкусность (palatability) — обратная привлекательность для
|
||||||
|
// умных травоядных (избегание появится в C2). У защиты ЕСТЬ ЦЕНА: токсичность/шипы тормозят рост
|
||||||
|
// (см. PlantGrowthSystem), иначе все растения дошли бы до максимума и коэволюция бы встала.
|
||||||
|
{ "defName": "GeneToxicity", "parent": "BaseNumericGene", "label": "gene.toxicity",
|
||||||
|
"default": 0.0, "min": 0.0, "max": 1.0, "tags": ["defense"],
|
||||||
|
"effects": { "toxicity": "value" } },
|
||||||
|
{ "defName": "GeneThorns", "parent": "BaseNumericGene", "label": "gene.thorns",
|
||||||
|
"default": 0.0, "min": 0.0, "max": 1.0, "tags": ["defense"],
|
||||||
|
"effects": { "thorns": "value" } },
|
||||||
|
{ "defName": "GenePalatability", "parent": "BaseNumericGene", "label": "gene.palatability",
|
||||||
|
"default": 1.0, "min": 0.0, "max": 1.0, "tags": ["defense"],
|
||||||
|
"effects": { "palatability": "value" } },
|
||||||
|
|
||||||
// Производный ген: признак собирается группировкой по регэкспу — сумма всех генов-толерантностей
|
// Производный ген: признак собирается группировкой по регэкспу — сумма всех генов-толерантностей
|
||||||
// (демонстрация gsom-функций фазы G5). Собственное значение гена не используется.
|
// (демонстрация gsom-функций фазы G5). Собственное значение гена не используется.
|
||||||
{ "defName": "GeneHardiness", "parent": "BaseNumericGene", "label": "gene.hardiness",
|
{ "defName": "GeneHardiness", "parent": "BaseNumericGene", "label": "gene.hardiness",
|
||||||
|
|||||||
@@ -22,6 +22,39 @@
|
|||||||
"defName": "Bleeding", "label": "hediff.bleeding",
|
"defName": "Bleeding", "label": "hediff.bleeding",
|
||||||
"initialSeverity": 0.3, "immunityPerDay": 0.5, "bloodLossPerDay": 0.8,
|
"initialSeverity": 0.3, "immunityPerDay": 0.5, "bloodLossPerDay": 0.8,
|
||||||
"pain": 0.25, "capMods": { "Moving": 0.92, "Consciousness": 0.95 }
|
"pain": 0.25, "capMods": { "Moving": 0.92, "Consciousness": 0.95 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Отравление растительным ядом (коэволюция): сама не прогрессирует (severityPerDay 0) — тяжесть
|
||||||
|
// копится при поедании токсичных растений (HealthState.Intensify в AnimalActionSystem). Иммунитет
|
||||||
|
// её рассасывает (~1.5 дня), но если зверь ест яд быстрее, чем выводит, тяжесть доходит до 1 → смерть.
|
||||||
|
// Это селективное давление: ядовитый корм опасен для травоядных с низкой устойчивостью.
|
||||||
|
{
|
||||||
|
"defName": "Poisoned", "label": "hediff.poisoned",
|
||||||
|
"initialSeverity": 0.0, "immunityPerDay": 0.65, "lethalSeverity": 1.0, "pain": 0.2,
|
||||||
|
"capMods": { "Moving": 0.85, "Consciousness": 0.9, "Digestion": 0.7 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Терморегуляция (как Poisoned-паттерн): сами не прогрессируют — тяжесть копится, пока зверю
|
||||||
|
// холодно/жарко (см. AnimalHealthSystem, зависит от GeneInsulation + размера тела), а в комфорте
|
||||||
|
// immunityPerDay их рассасывает. Тяжесть 1 = смерть от переохлаждения/перегрева.
|
||||||
|
{
|
||||||
|
"defName": "Hypothermia", "label": "hediff.hypothermia",
|
||||||
|
"initialSeverity": 0.0, "immunityPerDay": 0.5, "lethalSeverity": 1.0, "pain": 0.15,
|
||||||
|
"capMods": { "Moving": 0.7, "Consciousness": 0.8 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"defName": "Heatstroke", "label": "hediff.heatstroke",
|
||||||
|
"initialSeverity": 0.0, "immunityPerDay": 0.5, "lethalSeverity": 1.0, "pain": 0.15,
|
||||||
|
"capMods": { "Moving": 0.75, "Consciousness": 0.8 }
|
||||||
|
},
|
||||||
|
|
||||||
|
// Заражение раны (предатор/шипы → кровотечение → инфекция): прогрессирует, иммунитет (× фильтрация
|
||||||
|
// крови) душит. Слабая фильтрация/много ран → выше риск и хуже исход.
|
||||||
|
{
|
||||||
|
"defName": "Infection", "label": "hediff.infection",
|
||||||
|
"initialSeverity": 0.08, "severityPerDay": 0.2, "immunityPerDay": 0.28,
|
||||||
|
"lethalSeverity": 1.0, "pain": 0.25,
|
||||||
|
"capMods": { "Moving": 0.8, "Consciousness": 0.85, "BloodFiltration": 0.8 }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// Трава: быстрый почвопокров, основной скаттер лугов и леса.
|
// Трава: быстрый почвопокров, основной скаттер лугов и леса.
|
||||||
"defs": [
|
"defs": [
|
||||||
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.5,
|
{ "defName": "GrassA", "label": "plant.grass", "texture": "things/plant/grass/grassa", "sizeCells": 1.5,
|
||||||
|
"baseMassKg": 0.4,
|
||||||
"harvestProduct": "ProductGrass",
|
"harvestProduct": "ProductGrass",
|
||||||
"genome": { "optimalLight": 0.85, "lightTolerance": 0.35, "optimalTemperature": 19, "temperatureTolerance": 8,
|
"genome": { "optimalLight": 0.85, "lightTolerance": 0.35, "optimalTemperature": 19, "temperatureTolerance": 8,
|
||||||
"coldHardiness": 9, "heatHardiness": 6,
|
"coldHardiness": 9, "heatHardiness": 6,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// через "parent" и лежат рядом в файлах по семействам (Trees, Bushes, Cacti, …).
|
// через "parent" и лежат рядом в файлах по семействам (Trees, Bushes, Cacti, …).
|
||||||
"defs": [
|
"defs": [
|
||||||
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
{ "defName": "BaseTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
||||||
|
"baseMassKg": 450,
|
||||||
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
|
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
|
||||||
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 14, "temperatureTolerance": 10,
|
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 14, "temperatureTolerance": 10,
|
||||||
"coldHardiness": 14, "heatHardiness": 12,
|
"coldHardiness": 14, "heatHardiness": 12,
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
|
|
||||||
// Деревья тёплой и холодной температурных ниш (showcase температурных генов).
|
// Деревья тёплой и холодной температурных ниш (showcase температурных генов).
|
||||||
{ "defName": "BaseWarmTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
{ "defName": "BaseWarmTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.28,
|
||||||
|
"baseMassKg": 420,
|
||||||
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
|
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
|
||||||
"genome": { "optimalLight": 0.65, "lightTolerance": 0.5, "optimalTemperature": 23, "temperatureTolerance": 9,
|
"genome": { "optimalLight": 0.65, "lightTolerance": 0.5, "optimalTemperature": 23, "temperatureTolerance": 9,
|
||||||
"coldHardiness": 8, "heatHardiness": 16,
|
"coldHardiness": 8, "heatHardiness": 16,
|
||||||
@@ -32,6 +34,7 @@
|
|||||||
{ "sizeCells": 2.0, "label": "plant.stage.mature" }
|
{ "sizeCells": 2.0, "label": "plant.stage.mature" }
|
||||||
] },
|
] },
|
||||||
{ "defName": "BaseColdTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.26,
|
{ "defName": "BaseColdTree", "abstract": true, "sizeCells": 2.0, "trunkRadiusCells": 0.26,
|
||||||
|
"baseMassKg": 500,
|
||||||
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
|
"harvestProduct": "ProductWood", "fruitProduct": "ProductAcorn",
|
||||||
"genome": { "optimalLight": 0.55, "lightTolerance": 0.5, "optimalTemperature": 8, "temperatureTolerance": 12,
|
"genome": { "optimalLight": 0.55, "lightTolerance": 0.5, "optimalTemperature": 8, "temperatureTolerance": 12,
|
||||||
"coldHardiness": 18, "heatHardiness": 8,
|
"coldHardiness": 18, "heatHardiness": 8,
|
||||||
@@ -46,6 +49,7 @@
|
|||||||
] },
|
] },
|
||||||
|
|
||||||
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
|
{ "defName": "BaseBush", "abstract": true, "label": "plant.bush", "sizeCells": 1.4,
|
||||||
|
"baseMassKg": 6,
|
||||||
"fruitProduct": "ProductBerry",
|
"fruitProduct": "ProductBerry",
|
||||||
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 17, "temperatureTolerance": 9,
|
"genome": { "optimalLight": 0.6, "lightTolerance": 0.5, "optimalTemperature": 17, "temperatureTolerance": 9,
|
||||||
"coldHardiness": 10, "heatHardiness": 8,
|
"coldHardiness": 10, "heatHardiness": 8,
|
||||||
@@ -60,13 +64,15 @@
|
|||||||
|
|
||||||
// Кактусы: жаролюбивы, засухоустойчивы (низкий оптимум почвы), морозо-нестойки → гибнут в морозы.
|
// Кактусы: жаролюбивы, засухоустойчивы (низкий оптимум почвы), морозо-нестойки → гибнут в морозы.
|
||||||
{ "defName": "BaseCactus", "abstract": true, "label": "plant.cactus", "sizeCells": 1.2, "trunkRadiusCells": 0,
|
{ "defName": "BaseCactus", "abstract": true, "label": "plant.cactus", "sizeCells": 1.2, "trunkRadiusCells": 0,
|
||||||
|
"baseMassKg": 35,
|
||||||
"harvestProduct": "ProductFiber",
|
"harvestProduct": "ProductFiber",
|
||||||
"genome": { "optimalLight": 0.95, "lightTolerance": 0.35, "optimalTemperature": 26, "temperatureTolerance": 8,
|
"genome": { "optimalLight": 0.95, "lightTolerance": 0.35, "optimalTemperature": 26, "temperatureTolerance": 8,
|
||||||
"coldHardiness": 20, "heatHardiness": 18,
|
"coldHardiness": 20, "heatHardiness": 18,
|
||||||
"optimalFertility": 0.4, "fertilityTolerance": 0.5, "vigor": 0.5,
|
"optimalFertility": 0.4, "fertilityTolerance": 0.5, "vigor": 0.5,
|
||||||
"lifespan": 300, "dispersalRange": 2, "reproduceInterval": 25, "selfPollination": 0.6,
|
"lifespan": 300, "dispersalRange": 2, "reproduceInterval": 25, "selfPollination": 0.6,
|
||||||
"mutationRate": 0.05, "variantChance": 0.15, "spread": 0.08,
|
"mutationRate": 0.05, "variantChance": 0.15, "spread": 0.08,
|
||||||
"fruitYield": 3, "fruitSeason": 1, "harvestAmount": 3, "leafHue": 0.5 },
|
"fruitYield": 3, "fruitSeason": 1, "harvestAmount": 3, "leafHue": 0.5,
|
||||||
|
"thorns": 0.6, "palatability": 0.5 },
|
||||||
"stages": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 5, "label": "plant.stage.sprout" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.5, "growDays": 5, "label": "plant.stage.sprout" },
|
||||||
{ "label": "plant.stage.mature" }
|
{ "label": "plant.stage.mature" }
|
||||||
@@ -75,13 +81,15 @@
|
|||||||
// Грибы: подлесок леса. Низкий оптимум света (0.15) → растут в тени крон, где другим темно
|
// Грибы: подлесок леса. Низкий оптимум света (0.15) → растут в тени крон, где другим темно
|
||||||
// (showcase световой пригодности). Богатая почва, короткая жизнь, быстрое спороношение.
|
// (showcase световой пригодности). Богатая почва, короткая жизнь, быстрое спороношение.
|
||||||
{ "defName": "BaseMushroom", "abstract": true, "label": "plant.mushroom", "sizeCells": 0.9, "trunkRadiusCells": 0,
|
{ "defName": "BaseMushroom", "abstract": true, "label": "plant.mushroom", "sizeCells": 0.9, "trunkRadiusCells": 0,
|
||||||
|
"baseMassKg": 0.3,
|
||||||
"harvestProduct": "ProductMushroom",
|
"harvestProduct": "ProductMushroom",
|
||||||
"genome": { "optimalLight": 0.15, "lightTolerance": 0.22, "optimalTemperature": 14, "temperatureTolerance": 11,
|
"genome": { "optimalLight": 0.15, "lightTolerance": 0.22, "optimalTemperature": 14, "temperatureTolerance": 11,
|
||||||
"coldHardiness": 12, "heatHardiness": 8,
|
"coldHardiness": 12, "heatHardiness": 8,
|
||||||
"optimalFertility": 1.6, "fertilityTolerance": 1.1, "vigor": 1.2,
|
"optimalFertility": 1.6, "fertilityTolerance": 1.1, "vigor": 1.2,
|
||||||
"lifespan": 25, "dispersalRange": 3, "reproduceInterval": 4, "selfPollination": 0.9,
|
"lifespan": 25, "dispersalRange": 3, "reproduceInterval": 4, "selfPollination": 0.9,
|
||||||
"mutationRate": 0.06, "variantChance": 0.2, "spread": 0.1,
|
"mutationRate": 0.06, "variantChance": 0.2, "spread": 0.1,
|
||||||
"fruitYield": 0, "fruitSeason": 1, "harvestAmount": 3, "leafHue": 0.5 },
|
"fruitYield": 0, "fruitSeason": 1, "harvestAmount": 3, "leafHue": 0.5,
|
||||||
|
"toxicity": 0.5, "palatability": 0.4 },
|
||||||
"stages": [
|
"stages": [
|
||||||
{ "texture": "things/plant/seed_default", "sizeCells": 0.4, "growDays": 2, "label": "plant.stage.sprout" },
|
{ "texture": "things/plant/seed_default", "sizeCells": 0.4, "growDays": 2, "label": "plant.stage.sprout" },
|
||||||
{ "label": "plant.stage.mature" }
|
{ "label": "plant.stage.mature" }
|
||||||
@@ -89,6 +97,7 @@
|
|||||||
|
|
||||||
// Луг: цветы (декоративные, быстрое расселение) и ягодный почвопокров (еда).
|
// Луг: цветы (декоративные, быстрое расселение) и ягодный почвопокров (еда).
|
||||||
{ "defName": "BaseFlower", "abstract": true, "label": "plant.flower", "sizeCells": 1.0, "trunkRadiusCells": 0,
|
{ "defName": "BaseFlower", "abstract": true, "label": "plant.flower", "sizeCells": 1.0, "trunkRadiusCells": 0,
|
||||||
|
"baseMassKg": 0.15,
|
||||||
"genome": { "optimalLight": 0.9, "lightTolerance": 0.35, "optimalTemperature": 20, "temperatureTolerance": 9,
|
"genome": { "optimalLight": 0.9, "lightTolerance": 0.35, "optimalTemperature": 20, "temperatureTolerance": 9,
|
||||||
"coldHardiness": 8, "heatHardiness": 8,
|
"coldHardiness": 8, "heatHardiness": 8,
|
||||||
"optimalFertility": 1.0, "fertilityTolerance": 1.1, "vigor": 1.4,
|
"optimalFertility": 1.0, "fertilityTolerance": 1.1, "vigor": 1.4,
|
||||||
@@ -101,6 +110,7 @@
|
|||||||
] },
|
] },
|
||||||
|
|
||||||
{ "defName": "BaseBerryBush", "abstract": true, "label": "plant.berrybush", "sizeCells": 1.2, "trunkRadiusCells": 0,
|
{ "defName": "BaseBerryBush", "abstract": true, "label": "plant.berrybush", "sizeCells": 1.2, "trunkRadiusCells": 0,
|
||||||
|
"baseMassKg": 5,
|
||||||
"fruitProduct": "ProductBerry",
|
"fruitProduct": "ProductBerry",
|
||||||
"genome": { "optimalLight": 0.8, "lightTolerance": 0.4, "optimalTemperature": 18, "temperatureTolerance": 9,
|
"genome": { "optimalLight": 0.8, "lightTolerance": 0.4, "optimalTemperature": 18, "temperatureTolerance": 9,
|
||||||
"coldHardiness": 10, "heatHardiness": 7,
|
"coldHardiness": 10, "heatHardiness": 7,
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
// Продукты сбора/плодоношения растений. На них ссылаются PlantDef.harvestProduct / fruitProduct;
|
// Продукты сбора/плодоношения растений. На них ссылаются PlantDef.harvestProduct / fruitProduct;
|
||||||
// количество задаётся генами (harvestAmount / fruitYield).
|
// количество задаётся генами (harvestAmount / fruitYield).
|
||||||
"defs": [
|
"defs": [
|
||||||
{ "defName": "ProductWood", "label": "product.wood", "kind": "material" },
|
{ "defName": "ProductWood", "label": "product.wood", "kind": "material", "massKg": 1.0 },
|
||||||
{ "defName": "ProductGrass", "label": "product.grass", "kind": "material" },
|
{ "defName": "ProductGrass", "label": "product.grass", "kind": "material", "massKg": 0.1 },
|
||||||
{ "defName": "ProductFiber", "label": "product.fiber", "kind": "material" },
|
{ "defName": "ProductFiber", "label": "product.fiber", "kind": "material", "massKg": 0.2 },
|
||||||
{ "defName": "ProductBerry", "label": "product.berry", "kind": "food" },
|
{ "defName": "ProductBerry", "label": "product.berry", "kind": "food", "massKg": 0.05 },
|
||||||
{ "defName": "ProductAcorn", "label": "product.acorn", "kind": "food" },
|
{ "defName": "ProductAcorn", "label": "product.acorn", "kind": "food", "massKg": 0.05 },
|
||||||
{ "defName": "ProductMushroom", "label": "product.mushroom", "kind": "food" },
|
{ "defName": "ProductMushroom", "label": "product.mushroom", "kind": "food", "massKg": 0.1 },
|
||||||
|
|
||||||
// Животные продукты (фаза A5): добываются из трупа (разделка/гниение). Инвентарь/добыча — позже.
|
// Животные продукты (фаза A5): добываются из трупа (разделка/гниение). Инвентарь/добыча — позже.
|
||||||
{ "defName": "ProductMeat", "label": "product.meat", "kind": "food" },
|
{ "defName": "ProductMeat", "label": "product.meat", "kind": "food", "massKg": 0.5 },
|
||||||
{ "defName": "ProductBone", "label": "product.bone", "kind": "material" },
|
{ "defName": "ProductBone", "label": "product.bone", "kind": "material", "massKg": 0.3 },
|
||||||
{ "defName": "ProductLeather", "label": "product.leather", "kind": "material" }
|
{ "defName": "ProductLeather", "label": "product.leather", "kind": "material", "massKg": 0.4 }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"type": "Skill",
|
||||||
|
// Навыки как данные: уровень особи [0..1] растёт от практики (XP при соответствующем действии,
|
||||||
|
// ускоряется страстью) и медленно угасает к floorLevel. Эффект навыка — код по его id (добыча→выпас,
|
||||||
|
// охота→урон, уклонение→бегство). Сейчас навыки у животных; те же дефы пригодятся будущему человеку.
|
||||||
|
"defs": [
|
||||||
|
{ "defName": "Foraging", "label": "skill.foraging", "category": "survival",
|
||||||
|
"learnRate": 0.6, "decayPerDay": 0.015, "floorLevel": 0.0 },
|
||||||
|
{ "defName": "Hunting", "label": "skill.hunting", "category": "survival",
|
||||||
|
"learnRate": 0.5, "decayPerDay": 0.02, "floorLevel": 0.0 },
|
||||||
|
{ "defName": "Evasion", "label": "skill.evasion", "category": "survival",
|
||||||
|
"learnRate": 0.7, "decayPerDay": 0.02, "floorLevel": 0.0 }
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -11,12 +11,15 @@
|
|||||||
"inspect.tab.genes": "Genes",
|
"inspect.tab.genes": "Genes",
|
||||||
"inspect.tab.products": "Products",
|
"inspect.tab.products": "Products",
|
||||||
"inspect.tab.needs": "Needs",
|
"inspect.tab.needs": "Needs",
|
||||||
|
"inspect.tab.skills": "Skills",
|
||||||
"inspect.tab.health": "Health",
|
"inspect.tab.health": "Health",
|
||||||
"inspect.tab.mood": "Mood",
|
"inspect.tab.mood": "Mood",
|
||||||
"inspect.stage": "Stage: {0} ({1}/{2})",
|
"inspect.stage": "Stage: {0} ({1}/{2})",
|
||||||
"inspect.growing": "Growth: {0:0}% to next stage",
|
"inspect.growing": "Growth: {0:0}% to next stage",
|
||||||
"inspect.mature": "Fully grown",
|
"inspect.mature": "Fully grown",
|
||||||
"inspect.age": "Age: {0:0.0} d · lives to {1:0}",
|
"inspect.age": "Age: {0:0.0} d · lives to {1:0}",
|
||||||
|
"inspect.mass": "Weight: {0:0.#} kg · carries up to {1:0.#} kg",
|
||||||
|
"inspect.plantmass": "Weight: {0:0.##} kg",
|
||||||
"inspect.state": "State: {0}",
|
"inspect.state": "State: {0}",
|
||||||
"inspect.state.growing": "growing",
|
"inspect.state.growing": "growing",
|
||||||
"inspect.state.mature": "mature",
|
"inspect.state.mature": "mature",
|
||||||
@@ -30,6 +33,7 @@
|
|||||||
"inspect.gene.repro": "Dispersal {0:0.0} · interval {1:0.0} d",
|
"inspect.gene.repro": "Dispersal {0:0.0} · interval {1:0.0} d",
|
||||||
"inspect.gene.repro2": "Self-pollin. {0:0.00} · mutation {1:0.00}",
|
"inspect.gene.repro2": "Self-pollin. {0:0.00} · mutation {1:0.00}",
|
||||||
"inspect.gene.hardy": "Cold/heat {0:0}/{1:0} · leaf hue {2:0.00}",
|
"inspect.gene.hardy": "Cold/heat {0:0}/{1:0} · leaf hue {2:0.00}",
|
||||||
|
"inspect.gene.defense": "Defense: toxin {0:0.00} · thorns {1:0.00}",
|
||||||
"inspect.gene.variant": "Morph: recessive variant",
|
"inspect.gene.variant": "Morph: recessive variant",
|
||||||
"inspect.harvest": "Harvest: {0} ×{1:0.#}",
|
"inspect.harvest": "Harvest: {0} ×{1:0.#}",
|
||||||
"inspect.fruit": "Fruit: {0} ×{1:0.#} ({2}) · ripe {3:0.#}",
|
"inspect.fruit": "Fruit: {0} ×{1:0.#} ({2}) · ripe {3:0.#}",
|
||||||
@@ -39,6 +43,10 @@
|
|||||||
"inspect.animal.sex": "Sex: {0} · generation {1}",
|
"inspect.animal.sex": "Sex: {0} · generation {1}",
|
||||||
"inspect.animal.pregnant": "Pregnant: {0:0.0} d to birth",
|
"inspect.animal.pregnant": "Pregnant: {0:0.0} d to birth",
|
||||||
"inspect.needline": "{0}: {1:0}%",
|
"inspect.needline": "{0}: {1:0}%",
|
||||||
|
"inspect.skillline": "{0}: {1:0}% {2}",
|
||||||
|
"skill.foraging": "foraging",
|
||||||
|
"skill.hunting": "hunting",
|
||||||
|
"skill.evasion": "evasion",
|
||||||
"inspect.health.blood": "Blood: {0:0}% · pain: {1:0}%",
|
"inspect.health.blood": "Blood: {0:0}% · pain: {1:0}%",
|
||||||
"inspect.health.caps": "— Capacities —",
|
"inspect.health.caps": "— Capacities —",
|
||||||
"inspect.health.cap": "{0}: {1:0}%",
|
"inspect.health.cap": "{0}: {1:0}%",
|
||||||
@@ -61,9 +69,20 @@
|
|||||||
"cap.consciousness": "consciousness",
|
"cap.consciousness": "consciousness",
|
||||||
"cap.moving": "moving",
|
"cap.moving": "moving",
|
||||||
"cap.sight": "sight",
|
"cap.sight": "sight",
|
||||||
|
"cap.hearing": "hearing",
|
||||||
|
"cap.talking": "talking",
|
||||||
|
"cap.eating": "eating",
|
||||||
|
"cap.breathing": "breathing",
|
||||||
|
"cap.bloodpumping": "blood pumping",
|
||||||
|
"cap.bloodfiltration": "blood filtration",
|
||||||
|
"cap.digestion": "digestion",
|
||||||
"hediff.rut": "rut",
|
"hediff.rut": "rut",
|
||||||
"hediff.fever": "fever",
|
"hediff.fever": "fever",
|
||||||
"hediff.bleeding": "bleeding",
|
"hediff.bleeding": "bleeding",
|
||||||
|
"hediff.poisoned": "poisoned",
|
||||||
|
"hediff.hypothermia": "hypothermia",
|
||||||
|
"hediff.heatstroke": "heatstroke",
|
||||||
|
"hediff.infection": "infection",
|
||||||
"inspect.hint": "LMB — select · RMB/Esc — clear",
|
"inspect.hint": "LMB — select · RMB/Esc — clear",
|
||||||
"hud.paused": "PAUSED",
|
"hud.paused": "PAUSED",
|
||||||
"menu.title": "LittleSim",
|
"menu.title": "LittleSim",
|
||||||
@@ -94,6 +113,16 @@
|
|||||||
"settings.resolution": "Resolution",
|
"settings.resolution": "Resolution",
|
||||||
"settings.volume": "Volume",
|
"settings.volume": "Volume",
|
||||||
"settings.uiscale": "UI scale",
|
"settings.uiscale": "UI scale",
|
||||||
|
"settings.devmode": "Developer mode",
|
||||||
|
"dev.title": "Dev spawner",
|
||||||
|
"dev.hint": "Pick an entry · F9 to hide",
|
||||||
|
"dev.armed": "In hand: {0} · LMB to spawn, RMB to clear",
|
||||||
|
"dev.tools": "Tools",
|
||||||
|
"dev.damage": "Injure selected",
|
||||||
|
"dev.kill": "Kill selected",
|
||||||
|
"dev.infect": "Infect selected",
|
||||||
|
"dev.animals": "Animals",
|
||||||
|
"dev.plants": "Plants",
|
||||||
"settings.apply": "Apply",
|
"settings.apply": "Apply",
|
||||||
"settings.back": "Back",
|
"settings.back": "Back",
|
||||||
"pause.title": "Paused",
|
"pause.title": "Paused",
|
||||||
@@ -154,6 +183,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",
|
||||||
|
|||||||
@@ -11,12 +11,15 @@
|
|||||||
"inspect.tab.genes": "Гены",
|
"inspect.tab.genes": "Гены",
|
||||||
"inspect.tab.products": "Продукты",
|
"inspect.tab.products": "Продукты",
|
||||||
"inspect.tab.needs": "Нужды",
|
"inspect.tab.needs": "Нужды",
|
||||||
|
"inspect.tab.skills": "Навыки",
|
||||||
"inspect.tab.health": "Здоровье",
|
"inspect.tab.health": "Здоровье",
|
||||||
"inspect.tab.mood": "Настроение",
|
"inspect.tab.mood": "Настроение",
|
||||||
"inspect.stage": "Стадия: {0} ({1}/{2})",
|
"inspect.stage": "Стадия: {0} ({1}/{2})",
|
||||||
"inspect.growing": "Рост: {0:0}% до следующей стадии",
|
"inspect.growing": "Рост: {0:0}% до следующей стадии",
|
||||||
"inspect.mature": "Полностью выросло",
|
"inspect.mature": "Полностью выросло",
|
||||||
"inspect.age": "Возраст: {0:0.0} дн · живёт до {1:0}",
|
"inspect.age": "Возраст: {0:0.0} дн · живёт до {1:0}",
|
||||||
|
"inspect.mass": "Вес: {0:0.#} кг · несёт до {1:0.#} кг",
|
||||||
|
"inspect.plantmass": "Вес: {0:0.##} кг",
|
||||||
"inspect.state": "Состояние: {0}",
|
"inspect.state": "Состояние: {0}",
|
||||||
"inspect.state.growing": "растёт",
|
"inspect.state.growing": "растёт",
|
||||||
"inspect.state.mature": "созрело",
|
"inspect.state.mature": "созрело",
|
||||||
@@ -30,6 +33,7 @@
|
|||||||
"inspect.gene.repro": "Расселение {0:0.0} кл · период {1:0.0} дн",
|
"inspect.gene.repro": "Расселение {0:0.0} кл · период {1:0.0} дн",
|
||||||
"inspect.gene.repro2": "Самоопыление {0:0.00} · мутации {1:0.00}",
|
"inspect.gene.repro2": "Самоопыление {0:0.00} · мутации {1:0.00}",
|
||||||
"inspect.gene.hardy": "Морозо/жаро {0:0}/{1:0} · оттенок {2:0.00}",
|
"inspect.gene.hardy": "Морозо/жаро {0:0}/{1:0} · оттенок {2:0.00}",
|
||||||
|
"inspect.gene.defense": "Защита: яд {0:0.00} · шипы {1:0.00}",
|
||||||
"inspect.gene.variant": "Морфа: рецессивный вариант",
|
"inspect.gene.variant": "Морфа: рецессивный вариант",
|
||||||
"inspect.harvest": "Сбор: {0} ×{1:0.#}",
|
"inspect.harvest": "Сбор: {0} ×{1:0.#}",
|
||||||
"inspect.fruit": "Плоды: {0} ×{1:0.#} ({2}) · зрелых {3:0.#}",
|
"inspect.fruit": "Плоды: {0} ×{1:0.#} ({2}) · зрелых {3:0.#}",
|
||||||
@@ -39,6 +43,10 @@
|
|||||||
"inspect.animal.sex": "Пол: {0} · поколение {1}",
|
"inspect.animal.sex": "Пол: {0} · поколение {1}",
|
||||||
"inspect.animal.pregnant": "Беременна: {0:0.0} дн до родов",
|
"inspect.animal.pregnant": "Беременна: {0:0.0} дн до родов",
|
||||||
"inspect.needline": "{0}: {1:0}%",
|
"inspect.needline": "{0}: {1:0}%",
|
||||||
|
"inspect.skillline": "{0}: {1:0}% {2}",
|
||||||
|
"skill.foraging": "добыча корма",
|
||||||
|
"skill.hunting": "охота",
|
||||||
|
"skill.evasion": "уклонение",
|
||||||
"inspect.health.blood": "Кровь: {0:0}% · боль: {1:0}%",
|
"inspect.health.blood": "Кровь: {0:0}% · боль: {1:0}%",
|
||||||
"inspect.health.caps": "— Способности —",
|
"inspect.health.caps": "— Способности —",
|
||||||
"inspect.health.cap": "{0}: {1:0}%",
|
"inspect.health.cap": "{0}: {1:0}%",
|
||||||
@@ -61,9 +69,20 @@
|
|||||||
"cap.consciousness": "сознание",
|
"cap.consciousness": "сознание",
|
||||||
"cap.moving": "движение",
|
"cap.moving": "движение",
|
||||||
"cap.sight": "зрение",
|
"cap.sight": "зрение",
|
||||||
|
"cap.hearing": "слух",
|
||||||
|
"cap.talking": "речь",
|
||||||
|
"cap.eating": "питание",
|
||||||
|
"cap.breathing": "дыхание",
|
||||||
|
"cap.bloodpumping": "кровоснабжение",
|
||||||
|
"cap.bloodfiltration": "фильтрация крови",
|
||||||
|
"cap.digestion": "пищеварение",
|
||||||
"hediff.rut": "гон",
|
"hediff.rut": "гон",
|
||||||
"hediff.fever": "лихорадка",
|
"hediff.fever": "лихорадка",
|
||||||
"hediff.bleeding": "кровотечение",
|
"hediff.bleeding": "кровотечение",
|
||||||
|
"hediff.poisoned": "отравление",
|
||||||
|
"hediff.hypothermia": "переохлаждение",
|
||||||
|
"hediff.heatstroke": "тепловой удар",
|
||||||
|
"hediff.infection": "заражение",
|
||||||
"inspect.hint": "ЛКМ — выбрать · ПКМ/Esc — снять",
|
"inspect.hint": "ЛКМ — выбрать · ПКМ/Esc — снять",
|
||||||
"hud.paused": "ПАУЗА",
|
"hud.paused": "ПАУЗА",
|
||||||
"menu.title": "LittleSim",
|
"menu.title": "LittleSim",
|
||||||
@@ -94,6 +113,16 @@
|
|||||||
"settings.resolution": "Разрешение",
|
"settings.resolution": "Разрешение",
|
||||||
"settings.volume": "Громкость",
|
"settings.volume": "Громкость",
|
||||||
"settings.uiscale": "Масштаб UI",
|
"settings.uiscale": "Масштаб UI",
|
||||||
|
"settings.devmode": "Режим разработчика",
|
||||||
|
"dev.title": "Дев-спавнер",
|
||||||
|
"dev.hint": "Выбери пункт · F9 — скрыть",
|
||||||
|
"dev.armed": "В руке: {0} · ЛКМ — спавн, ПКМ — снять",
|
||||||
|
"dev.tools": "Инструменты",
|
||||||
|
"dev.damage": "Ранить выбранного",
|
||||||
|
"dev.kill": "Убить выбранного",
|
||||||
|
"dev.infect": "Заразить выбранного",
|
||||||
|
"dev.animals": "Животные",
|
||||||
|
"dev.plants": "Растения",
|
||||||
"settings.apply": "Применить",
|
"settings.apply": "Применить",
|
||||||
"settings.back": "Назад",
|
"settings.back": "Назад",
|
||||||
"pause.title": "Пауза",
|
"pause.title": "Пауза",
|
||||||
@@ -154,6 +183,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": "родила потомство",
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ public sealed class GameSettings
|
|||||||
|
|
||||||
/// <summary>Масштаб интерфейса (1 — обычный); применяется к Myra-десктопу всех сцен.</summary>
|
/// <summary>Масштаб интерфейса (1 — обычный); применяется к Myra-десктопу всех сцен.</summary>
|
||||||
public float UiScale { get; set; } = 1f;
|
public float UiScale { get; set; } = 1f;
|
||||||
|
|
||||||
|
/// <summary>Режим разработчика: открывает дев-консоль (backtick) и дев-спавнер. По умолчанию включён.</summary>
|
||||||
|
public bool DeveloperMode { get; set; } = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Чтение/запись <see cref="GameSettings"/> и применение их к движку.</summary>
|
/// <summary>Чтение/запись <see cref="GameSettings"/> и применение их к движку.</summary>
|
||||||
|
|||||||
@@ -149,6 +149,12 @@ public sealed class AnimalSave
|
|||||||
|
|
||||||
/// <summary>Размер помёта.</summary>
|
/// <summary>Размер помёта.</summary>
|
||||||
public int Litter { get; set; }
|
public int Litter { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Уровни навыков [0..1] по индексам SkillSet.</summary>
|
||||||
|
public float[] SkillLevels { get; set; } = [];
|
||||||
|
|
||||||
|
/// <summary>Страсть к навыкам (0/1/2) по индексам SkillSet.</summary>
|
||||||
|
public int[] SkillPassions { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Сериализуемый труп (фаза A5): вид (для спрайта), позиция, таймер/стадия разложения, мясо.</summary>
|
/// <summary>Сериализуемый труп (фаза A5): вид (для спрайта), позиция, таймер/стадия разложения, мясо.</summary>
|
||||||
@@ -173,6 +179,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 +244,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; }
|
||||||
|
|
||||||
@@ -38,15 +41,49 @@ public sealed class AnimalSet
|
|||||||
|
|
||||||
/// <summary>Набор генов вида: базовые значения для генерации особи.</summary>
|
/// <summary>Набор генов вида: базовые значения для генерации особи.</summary>
|
||||||
public required GenomeTemplate Template { get; init; }
|
public required GenomeTemplate Template { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Эталонный размер тела вида (база GeneMaxBodySize) — предпосчитан для горячих путей
|
||||||
|
/// (масса/метаболизм Клайбера каждый кадр), чтобы не делать строковый словарный лукап в цикле.</summary>
|
||||||
|
public required float RefBodySize { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Встроенный набор стадий по умолчанию (если вид не задал свой): как было до выноса в данные.
|
// Встроенный набор стадий по умолчанию (если вид не задал свой): как было до выноса в данные.
|
||||||
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,9 +119,16 @@ 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),
|
||||||
|
RefBodySize =
|
||||||
|
def.Genome.TryGetValue("GeneMaxBodySize", out var refSize) && refSize > 0f
|
||||||
|
? refSize
|
||||||
|
: 1f,
|
||||||
};
|
};
|
||||||
_index[def] = i;
|
_index[def] = i;
|
||||||
}
|
}
|
||||||
@@ -111,6 +155,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) тоже поддержаны.
|
||||||
|
|||||||
@@ -68,6 +68,8 @@ public sealed class GameContent
|
|||||||
defs.RegisterType<NeedDef>("Need");
|
defs.RegisterType<NeedDef>("Need");
|
||||||
defs.RegisterType<ThoughtDef>("Thought");
|
defs.RegisterType<ThoughtDef>("Thought");
|
||||||
defs.RegisterType<BodyDef>("Body");
|
defs.RegisterType<BodyDef>("Body");
|
||||||
|
defs.RegisterType<SkillDef>("Skill");
|
||||||
|
defs.RegisterType<DevToolDef>("DevTool");
|
||||||
defs.RegisterType<WorldPresetDef>("WorldPreset");
|
defs.RegisterType<WorldPresetDef>("WorldPreset");
|
||||||
// Валидация имён при загрузке (фаза G5): гены и продукты обязаны иметь префикс типа.
|
// Валидация имён при загрузке (фаза G5): гены и продукты обязаны иметь префикс типа.
|
||||||
defs.RegisterValidator("Gene", "defName", "^Gene", "gene defName must start with 'Gene'");
|
defs.RegisterValidator("Gene", "defName", "^Gene", "gene defName must start with 'Gene'");
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ public sealed class ProductDef : Def
|
|||||||
{
|
{
|
||||||
/// <summary>Категория продукта (например "material" или "food") — для будущей экономики/инвентаря.</summary>
|
/// <summary>Категория продукта (например "material" или "food") — для будущей экономики/инвентаря.</summary>
|
||||||
public string Kind { get; init; } = "";
|
public string Kind { get; init; } = "";
|
||||||
|
|
||||||
|
/// <summary>Масса одной единицы продукта (кг) — основа под перенос/инвентарь (грузоподъёмность).</summary>
|
||||||
|
public float MassKg { get; init; } = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -143,6 +146,15 @@ public sealed class GenomeDef
|
|||||||
|
|
||||||
/// <summary>Оттенок листвы (0..1) — сдвиг тинта спрайта; ген цвета.</summary>
|
/// <summary>Оттенок листвы (0..1) — сдвиг тинта спрайта; ген цвета.</summary>
|
||||||
public float LeafHue { get; init; } = 0.33f;
|
public float LeafHue { get; init; } = 0.33f;
|
||||||
|
|
||||||
|
/// <summary>Токсичность (0..1): отравляет поедателя (коэволюция); тормозит рост (цена защиты).</summary>
|
||||||
|
public float Toxicity { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Шипы (0..1): травмируют поедателя; тормозят рост (цена защиты).</summary>
|
||||||
|
public float Thorns { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Вкусность (0..1): обратная привлекательность для умных травоядных (избегание в C2).</summary>
|
||||||
|
public float Palatability { get; init; } = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Растение (Defs/Plants/): текстура, размер, опциональный ствол-препятствие, стадии роста, геном.</summary>
|
/// <summary>Растение (Defs/Plants/): текстура, размер, опциональный ствол-препятствие, стадии роста, геном.</summary>
|
||||||
@@ -168,6 +180,9 @@ public sealed class PlantDef : Def
|
|||||||
|
|
||||||
/// <summary>Имя <see cref="ProductDef"/>, выдаваемого плодами (ягоды/жёлудь); null — не плодоносит.</summary>
|
/// <summary>Имя <see cref="ProductDef"/>, выдаваемого плодами (ягоды/жёлудь); null — не плодоносит.</summary>
|
||||||
public string? FruitProduct { get; init; }
|
public string? FruitProduct { get; init; }
|
||||||
|
|
||||||
|
/// <summary>Эталонная масса (кг) зрелого растения базового размера; масса особи ∝ (размер/эталон)³.</summary>
|
||||||
|
public float BaseMassKg { get; init; } = 20f;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -252,6 +267,12 @@ 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>Эталонная масса (кг) взрослой особи базового размера вида; масса особи ∝ (размер/эталон)³.</summary>
|
||||||
|
public float BaseMassKg { get; init; } = 40f;
|
||||||
|
|
||||||
/// <summary>Имя <see cref="BodyDef"/> — анатомия вида (части тела/органы); пусто — без частей тела.</summary>
|
/// <summary>Имя <see cref="BodyDef"/> — анатомия вида (части тела/органы); пусто — без частей тела.</summary>
|
||||||
public string Body { get; init; } = "";
|
public string Body { get; init; } = "";
|
||||||
|
|
||||||
@@ -347,6 +368,45 @@ public sealed class ThoughtDef : Def
|
|||||||
public float DurationDays { get; init; } = 1f;
|
public float DurationDays { get; init; } = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Пункт дев-инструмента (Defs/DevTools/) — ЧТОБЫ МОДЫ ДОБАВЛЯЛИ кнопки в дев-спавнер без правки кода.
|
||||||
|
/// <see cref="Kind"/> — id обработчика из реестра (spawn-animal/spawn-plant/infect/damage/kill/…),
|
||||||
|
/// <see cref="Target"/> — параметр (defName вида/хедифа). Виды животных/растений спавнер берёт из их
|
||||||
|
/// дефов сам (модовый контент появляется автоматически); DevToolDef добавляет ПРОЧИЕ действия.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DevToolDef : Def
|
||||||
|
{
|
||||||
|
/// <summary>Категория для группировки в панели (например "tool").</summary>
|
||||||
|
public string Category { get; init; } = "tool";
|
||||||
|
|
||||||
|
/// <summary>Id обработчика-инструмента из реестра кода (spawn-animal/spawn-plant/infect/damage/kill).</summary>
|
||||||
|
public string Kind { get; init; } = "";
|
||||||
|
|
||||||
|
/// <summary>Параметр инструмента: defName вида/хедифа и т.п. (по смыслу Kind).</summary>
|
||||||
|
public string Target { get; init; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Навык (Defs/Skills/) как ДАННЫЕ: уровень особи [0..1] растёт от практики (XP при действии,
|
||||||
|
/// ускоряется страстью) и медленно угасает без неё к полу <see cref="FloorLevel"/>. Эффект навыка —
|
||||||
|
/// код по его id (как у нужд/действий): добыча→выпас, охота→урон, уклонение→бегство. Модер добавляет
|
||||||
|
/// навык строкой JSON; новый эффект — код. <see cref="Def.Label"/> — ключ локализации (skill.*).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SkillDef : Def
|
||||||
|
{
|
||||||
|
/// <summary>Категория (для группировки в UI), напр. "survival".</summary>
|
||||||
|
public string Category { get; init; } = "";
|
||||||
|
|
||||||
|
/// <summary>Скорость обучения за игровой день непрерывной практики (при страсти ×1).</summary>
|
||||||
|
public float LearnRate { get; init; } = 0.5f;
|
||||||
|
|
||||||
|
/// <summary>Угасание уровня за игровой день без практики.</summary>
|
||||||
|
public float DecayPerDay { get; init; } = 0.02f;
|
||||||
|
|
||||||
|
/// <summary>Пол угасания (ниже не падает) — «не забывается совсем».</summary>
|
||||||
|
public float FloorLevel { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Часть тела/орган (вложенный объект <see cref="BodyDef.Parts"/>) как ДАННЫЕ: иерархия (родитель),
|
/// Часть тела/орган (вложенный объект <see cref="BodyDef.Parts"/>) как ДАННЫЕ: иерархия (родитель),
|
||||||
/// вес попадания, максимум HP (масштабируется размером тела), флаг жизненной важности и вклады в
|
/// вес попадания, максимум HP (масштабируется размером тела), флаг жизненной важности и вклады в
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ public sealed class PlantSet
|
|||||||
new GenomeTemplate.Entry(Gene("GeneFruitSeason"), g.FruitSeason, 0f), // сезон фиксирован по виду
|
new GenomeTemplate.Entry(Gene("GeneFruitSeason"), g.FruitSeason, 0f), // сезон фиксирован по виду
|
||||||
Numeric("GeneHarvestAmount", g.HarvestAmount),
|
Numeric("GeneHarvestAmount", g.HarvestAmount),
|
||||||
new GenomeTemplate.Entry(Gene("GeneLeafHue"), g.LeafHue, 0.04f),
|
new GenomeTemplate.Entry(Gene("GeneLeafHue"), g.LeafHue, 0.04f),
|
||||||
|
// Защита (коэволюция с травоядными): токсичность/шипы/вкусность.
|
||||||
|
Numeric("GeneToxicity", g.Toxicity),
|
||||||
|
Numeric("GeneThorns", g.Thorns),
|
||||||
|
Numeric("GenePalatability", g.Palatability),
|
||||||
new GenomeTemplate.Entry(
|
new GenomeTemplate.Entry(
|
||||||
Gene("GeneMorph"),
|
Gene("GeneMorph"),
|
||||||
0f,
|
0f,
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
namespace LittleSim.Content;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Готовый реестр навыков из <see cref="SkillDef"/>: каждому навыку — индекс (по нему системы читают
|
||||||
|
/// уровни в <c>Skills.Levels</c> без словарей), плюс генерация стартовых уровней и страсти особи.
|
||||||
|
/// Делает набор навыков расширяемым данными (как <see cref="NeedSet"/>): добавил <see cref="SkillDef"/> —
|
||||||
|
/// у особей появляется навык, без правки кода (если его эффект/начисление XP уже есть в коде).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class SkillSet
|
||||||
|
{
|
||||||
|
// Веса страсти при генерации особи: нет / интерес / страсть (множители обучения см. PassionMultiplier).
|
||||||
|
private static readonly float[] PassionWeights = [0.6f, 0.3f, 0.1f];
|
||||||
|
|
||||||
|
private readonly SkillDef[] _defs;
|
||||||
|
private readonly Dictionary<string, int> _byId = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
/// <summary>Строит реестр из всех <see cref="SkillDef"/> мода (порядок = индексы навыков).</summary>
|
||||||
|
public SkillSet(GameContent content)
|
||||||
|
{
|
||||||
|
_defs = content.Defs.All<SkillDef>().ToArray();
|
||||||
|
for (var i = 0; i < _defs.Length; i++)
|
||||||
|
{
|
||||||
|
_byId[_defs[i].DefName] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Число навыков.</summary>
|
||||||
|
public int Count => _defs.Length;
|
||||||
|
|
||||||
|
/// <summary>Деф навыка по индексу.</summary>
|
||||||
|
public SkillDef this[int index] => _defs[index];
|
||||||
|
|
||||||
|
/// <summary>Индекс навыка по id, или -1.</summary>
|
||||||
|
public int IndexOf(string id) => _byId.TryGetValue(id, out var index) ? index : -1;
|
||||||
|
|
||||||
|
/// <summary>Стартовые уровни особи (с пола угасания каждого навыка).</summary>
|
||||||
|
public float[] NewLevels()
|
||||||
|
{
|
||||||
|
var levels = new float[_defs.Length];
|
||||||
|
for (var i = 0; i < levels.Length; i++)
|
||||||
|
{
|
||||||
|
levels[i] = _defs[i].FloorLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
return levels;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Бросает страсть к каждому навыку (0 нет / 1 интерес / 2 страсть) детерминированно по rng.</summary>
|
||||||
|
public byte[] RollPassions(Random rng)
|
||||||
|
{
|
||||||
|
var passions = new byte[_defs.Length];
|
||||||
|
for (var i = 0; i < passions.Length; i++)
|
||||||
|
{
|
||||||
|
var roll = rng.NextSingle();
|
||||||
|
byte p = 0;
|
||||||
|
var acc = 0f;
|
||||||
|
for (byte k = 0; k < PassionWeights.Length; k++)
|
||||||
|
{
|
||||||
|
acc += PassionWeights[k];
|
||||||
|
if (roll < acc)
|
||||||
|
{
|
||||||
|
p = k;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
passions[i] = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return passions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Множитель скорости обучения по уровню страсти (нет/интерес/страсть).</summary>
|
||||||
|
public static float PassionMultiplier(byte passion) =>
|
||||||
|
passion switch
|
||||||
|
{
|
||||||
|
0 => 0.35f,
|
||||||
|
1 => 1.0f,
|
||||||
|
_ => 1.7f,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -67,6 +67,7 @@ public sealed class WorldScene : Scene
|
|||||||
private PlantSet _plants = null!;
|
private PlantSet _plants = null!;
|
||||||
private AnimalSet _animals = null!;
|
private AnimalSet _animals = null!;
|
||||||
private NeedSet _needs = null!;
|
private NeedSet _needs = null!;
|
||||||
|
private SkillSet _skills = null!;
|
||||||
private float[] _cellFertility = [];
|
private float[] _cellFertility = [];
|
||||||
private bool[] _cellLand = [];
|
private bool[] _cellLand = [];
|
||||||
private bool[] _cellOccluderBase = []; // горы (статично из террейна)
|
private bool[] _cellOccluderBase = []; // горы (статично из террейна)
|
||||||
@@ -113,6 +114,7 @@ public sealed class WorldScene : Scene
|
|||||||
_plants = new PlantSet(content, atlases, device);
|
_plants = new PlantSet(content, atlases, device);
|
||||||
_animals = new AnimalSet(content, atlases, device);
|
_animals = new AnimalSet(content, atlases, device);
|
||||||
_needs = new NeedSet(content);
|
_needs = new NeedSet(content);
|
||||||
|
_skills = new SkillSet(content);
|
||||||
var loadingPlants = _save?.Plants is { Count: > 0 };
|
var loadingPlants = _save?.Plants is { Count: > 0 };
|
||||||
BuildTerrain(
|
BuildTerrain(
|
||||||
content,
|
content,
|
||||||
@@ -175,39 +177,74 @@ public sealed class WorldScene : Scene
|
|||||||
_plants,
|
_plants,
|
||||||
_animals,
|
_animals,
|
||||||
_needs,
|
_needs,
|
||||||
|
_skills,
|
||||||
content,
|
content,
|
||||||
climate,
|
climate,
|
||||||
_selection,
|
_selection,
|
||||||
brackets
|
brackets
|
||||||
);
|
);
|
||||||
desktop.Root = Ui.Screen(hudLabel, _inspect.Panel, speedBar, _pause.Root);
|
// Режим разработчика (настройка, по умолчанию вкл): без него дев-консоль и спавнер не поднимаются.
|
||||||
|
var devMode = GameSettingsStore.Load().DeveloperMode;
|
||||||
|
var spawner = devMode
|
||||||
|
? new DevSpawner(
|
||||||
|
Store,
|
||||||
|
_animals,
|
||||||
|
_plants,
|
||||||
|
_needs,
|
||||||
|
_skills,
|
||||||
|
content,
|
||||||
|
_selection,
|
||||||
|
_cellFertility,
|
||||||
|
_config.Width,
|
||||||
|
_config.Height,
|
||||||
|
CellSize
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
desktop.Root = spawner is null
|
||||||
|
? Ui.Screen(hudLabel, _inspect.Panel, speedBar, _pause.Root)
|
||||||
|
: Ui.Screen(hudLabel, _inspect.Panel, speedBar, _pause.Root, spawner.Panel);
|
||||||
|
|
||||||
this.UseInspector(renderer);
|
this.UseInspector(renderer);
|
||||||
var console = this.UseDevConsole();
|
|
||||||
RegisterCommands(console, content, atlases);
|
MrGameEng.DevConsole.DevConsole? console = null;
|
||||||
console.Register(
|
if (devMode)
|
||||||
"light",
|
{
|
||||||
"light [radius] — place a point light at the cursor (night shadows demo)",
|
console = this.UseDevConsole();
|
||||||
(c, args) =>
|
RegisterCommands(console, content, atlases);
|
||||||
{
|
console.Register(
|
||||||
var radius =
|
"light",
|
||||||
args.Length > 0
|
"light [radius] — place a point light at the cursor (night shadows demo)",
|
||||||
? float.Parse(args[0], System.Globalization.CultureInfo.InvariantCulture)
|
(c, args) =>
|
||||||
: 120f;
|
{
|
||||||
var mouse = Mouse.GetState();
|
var radius =
|
||||||
var world = renderer.ScreenToWorld(new Vector2(mouse.X, mouse.Y));
|
args.Length > 0
|
||||||
Store.CreateEntity(
|
? float.Parse(
|
||||||
Transform2D.At(world),
|
args[0],
|
||||||
new PointLight
|
System.Globalization.CultureInfo.InvariantCulture
|
||||||
{
|
)
|
||||||
Radius = radius,
|
: 120f;
|
||||||
Color = Color.White,
|
var mouse = Mouse.GetState();
|
||||||
Intensity = 0.9f,
|
var world = renderer.ScreenToWorld(new Vector2(mouse.X, mouse.Y));
|
||||||
}
|
Store.CreateEntity(
|
||||||
);
|
Transform2D.At(world),
|
||||||
c.WriteLine($"light at {world.X:0},{world.Y:0} r{radius:0}");
|
new PointLight
|
||||||
}
|
{
|
||||||
);
|
Radius = radius,
|
||||||
|
Color = Color.White,
|
||||||
|
Intensity = 0.9f,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
c.WriteLine($"light at {world.X:0},{world.Y:0} r{radius:0}");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spawner is not null)
|
||||||
|
{
|
||||||
|
UpdateSystems.Add(
|
||||||
|
new DevSpawnerSystem(renderer, spawner, () => desktop.IsMouseOverGUI)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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(new PlantFruitingSystem(_plants, calendar, climate));
|
||||||
@@ -231,7 +268,8 @@ public sealed class WorldScene : Scene
|
|||||||
// нужду (еда выедает растения — контур ёмкости среды). Внешний вид тускнеет с острой нуждой.
|
// нужду (еда выедает растения — контур ёмкости среды). Внешний вид тускнеет с острой нуждой.
|
||||||
var shore = ComputeShore();
|
var shore = ComputeShore();
|
||||||
var thoughts = new ThoughtSet(content);
|
var thoughts = new ThoughtSet(content);
|
||||||
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs));
|
UpdateSystems.Add(new AnimalNeedsSystem(Context.Clock, SecondsPerDay, _needs, _animals));
|
||||||
|
UpdateSystems.Add(new AnimalSkillSystem(Context.Clock, SecondsPerDay, _skills));
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut"))
|
new AnimalRutSystem(_animals, climate, content.Defs.Get<HediffDef>("Rut"))
|
||||||
);
|
);
|
||||||
@@ -242,7 +280,11 @@ public sealed class WorldScene : Scene
|
|||||||
CellSize,
|
CellSize,
|
||||||
Context.Clock,
|
Context.Clock,
|
||||||
SecondsPerDay,
|
SecondsPerDay,
|
||||||
|
climate,
|
||||||
content.Defs.All<HediffDef>().Where(h => h.AmbientPerDay > 0f).ToArray(),
|
content.Defs.All<HediffDef>().Where(h => h.AmbientPerDay > 0f).ToArray(),
|
||||||
|
content.Defs.TryGet<HediffDef>("Hypothermia", out var hypo) ? hypo : null,
|
||||||
|
content.Defs.TryGet<HediffDef>("Heatstroke", out var heat) ? heat : null,
|
||||||
|
content.Defs.TryGet<HediffDef>("Infection", out var infe) ? infe : null,
|
||||||
_config.Seed + 0x3EAD
|
_config.Seed + 0x3EAD
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -260,18 +302,21 @@ public sealed class WorldScene : Scene
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
var bleeding = content.Defs.TryGet<HediffDef>("Bleeding", out var bl) ? bl : null;
|
var bleeding = content.Defs.TryGet<HediffDef>("Bleeding", out var bl) ? bl : null;
|
||||||
|
var poisoned = content.Defs.TryGet<HediffDef>("Poisoned", out var ps) ? ps : null;
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new AnimalActionSystem(
|
new AnimalActionSystem(
|
||||||
Store,
|
Store,
|
||||||
_plants,
|
_plants,
|
||||||
_animals,
|
_animals,
|
||||||
_needs,
|
_needs,
|
||||||
|
_skills,
|
||||||
thoughts,
|
thoughts,
|
||||||
Context.Clock,
|
Context.Clock,
|
||||||
SecondsPerDay,
|
SecondsPerDay,
|
||||||
CellSize,
|
CellSize,
|
||||||
_bounds,
|
_bounds,
|
||||||
bleeding,
|
bleeding,
|
||||||
|
poisoned,
|
||||||
_config.Seed + 0x1B17
|
_config.Seed + 0x1B17
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -284,6 +329,7 @@ public sealed class WorldScene : Scene
|
|||||||
Store,
|
Store,
|
||||||
_animals,
|
_animals,
|
||||||
_needs,
|
_needs,
|
||||||
|
_skills,
|
||||||
thoughts,
|
thoughts,
|
||||||
Context.Clock,
|
Context.Clock,
|
||||||
SecondsPerDay,
|
SecondsPerDay,
|
||||||
@@ -292,6 +338,31 @@ public sealed class WorldScene : Scene
|
|||||||
_config.Seed + 0x4BED
|
_config.Seed + 0x4BED
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
UpdateSystems.Add(
|
||||||
|
new EggSystem(
|
||||||
|
Store,
|
||||||
|
_animals,
|
||||||
|
_needs,
|
||||||
|
_skills,
|
||||||
|
Context.Clock,
|
||||||
|
SecondsPerDay,
|
||||||
|
CellSize,
|
||||||
|
_config.Seed + 0x6E66
|
||||||
|
)
|
||||||
|
);
|
||||||
|
UpdateSystems.Add(
|
||||||
|
new SeedDispersalSystem(
|
||||||
|
Store,
|
||||||
|
_plants,
|
||||||
|
Context.Clock,
|
||||||
|
SecondsPerDay,
|
||||||
|
_config.Width,
|
||||||
|
_config.Height,
|
||||||
|
CellSize,
|
||||||
|
_cellFertility,
|
||||||
|
_cellLand
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
UpdateSystems.Add(
|
UpdateSystems.Add(
|
||||||
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
new GodCameraSystem(camera, input, renderer, console, () => _pause.IsOpen)
|
||||||
@@ -517,7 +588,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;
|
||||||
}
|
}
|
||||||
@@ -592,6 +663,14 @@ public sealed class WorldScene : Scene
|
|||||||
record.Litter = preg.Litter;
|
record.Litter = preg.Litter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (entity.TryGetComponent<Skills>(out var sk))
|
||||||
|
{
|
||||||
|
record.SkillLevels = sk.Levels ?? [];
|
||||||
|
record.SkillPassions = sk.Passions is null
|
||||||
|
? []
|
||||||
|
: Array.ConvertAll(sk.Passions, p => (int)p);
|
||||||
|
}
|
||||||
|
|
||||||
save.Animals.Add(record);
|
save.Animals.Add(record);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -607,11 +686,28 @@ public sealed class WorldScene : Scene
|
|||||||
X = transform.Position.X,
|
X = transform.Position.X,
|
||||||
Y = transform.Position.Y,
|
Y = transform.Position.Y,
|
||||||
RotDays = corpse.RotDays,
|
RotDays = corpse.RotDays,
|
||||||
BodySize = corpse.Meat / AnimalFactory.MeatPerBodySize,
|
BodySize = corpse.BodySize,
|
||||||
Meat = corpse.Meat,
|
Meat = corpse.Meat,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -662,6 +758,7 @@ public sealed class WorldScene : Scene
|
|||||||
Store,
|
Store,
|
||||||
_animals,
|
_animals,
|
||||||
_needs,
|
_needs,
|
||||||
|
_skills,
|
||||||
index,
|
index,
|
||||||
new Vector2(saved.X, saved.Y),
|
new Vector2(saved.X, saved.Y),
|
||||||
saved.AgeDays,
|
saved.AgeDays,
|
||||||
@@ -685,16 +782,39 @@ public sealed class WorldScene : Scene
|
|||||||
_animals.IndexOf(def),
|
_animals.IndexOf(def),
|
||||||
new Vector2(saved.X, saved.Y),
|
new Vector2(saved.X, saved.Y),
|
||||||
saved.BodySize,
|
saved.BodySize,
|
||||||
|
saved.Meat,
|
||||||
CellSize
|
CellSize
|
||||||
);
|
);
|
||||||
ref var data = ref corpse.GetComponent<Corpse>();
|
ref var data = ref corpse.GetComponent<Corpse>();
|
||||||
data.RotDays = saved.RotDays;
|
data.RotDays = saved.RotDays;
|
||||||
data.Stage = -1; // форсируем пере-тинт стадии в CorpseSystem на следующем тике
|
data.Stage = -1; // форсируем пере-тинт стадии в CorpseSystem на следующем тике
|
||||||
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"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,6 +875,23 @@ public sealed class WorldScene : Scene
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Навыки: накладываем сохранённые уровни/страсть, если состав совпал (иначе — свежие из фабрики).
|
||||||
|
if (entity.TryGetComponent<Skills>(out var skills) && skills.Levels is not null)
|
||||||
|
{
|
||||||
|
if (saved.SkillLevels.Length == skills.Levels.Length)
|
||||||
|
{
|
||||||
|
saved.SkillLevels.CopyTo(skills.Levels, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skills.Passions is not null && saved.SkillPassions.Length == skills.Passions.Length)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < skills.Passions.Length; i++)
|
||||||
|
{
|
||||||
|
skills.Passions[i] = (byte)Math.Clamp(saved.SkillPassions[i], 0, 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Детерминированный начальный спавн животных по данным: каждый вид с SpawnPer1000Cells > 0
|
// Детерминированный начальный спавн животных по данным: каждый вид с SpawnPer1000Cells > 0
|
||||||
@@ -808,6 +945,7 @@ public sealed class WorldScene : Scene
|
|||||||
Store,
|
Store,
|
||||||
_animals,
|
_animals,
|
||||||
_needs,
|
_needs,
|
||||||
|
_skills,
|
||||||
s,
|
s,
|
||||||
new Vector2(px, py),
|
new Vector2(px, py),
|
||||||
ageDays: random.NextSingle() * maxAge,
|
ageDays: random.NextSingle() * maxAge,
|
||||||
@@ -986,6 +1124,11 @@ public sealed class WorldScene : Scene
|
|||||||
"mood [species] — live population mood (avg/min/max) and content/stressed counts",
|
"mood [species] — live population mood (avg/min/max) and content/stressed counts",
|
||||||
(c, args) => RunMood(c, args)
|
(c, args) => RunMood(c, args)
|
||||||
);
|
);
|
||||||
|
console.Register(
|
||||||
|
"skills",
|
||||||
|
"skills — mean skill levels across living animals (grow from practice, decay unused)",
|
||||||
|
(c, _) => RunSkills(c)
|
||||||
|
);
|
||||||
console.Register(
|
console.Register(
|
||||||
"menu",
|
"menu",
|
||||||
"menu — return to the main menu",
|
"menu — return to the main menu",
|
||||||
@@ -1070,6 +1213,11 @@ public sealed class WorldScene : Scene
|
|||||||
$" temp: grows {tMin:0.#}..{tMax:0.#}°C, optimal {tLow:0.#}..{tHigh:0.#}°C "
|
$" temp: grows {tMin:0.#}..{tMax:0.#}°C, optimal {tLow:0.#}..{tHigh:0.#}°C "
|
||||||
+ $"(cold {traits.ColdHardiness:0.#}, heat {traits.HeatHardiness:0.#})"
|
+ $"(cold {traits.ColdHardiness:0.#}, heat {traits.HeatHardiness:0.#})"
|
||||||
);
|
);
|
||||||
|
console.WriteLine(
|
||||||
|
$" defense: toxicity {traits.Toxicity:0.##}, thorns {traits.Thorns:0.##}, "
|
||||||
|
+ $"palatability {traits.Palatability:0.##} (growth ×{traits.DefenseGrowthFactor():0.##})"
|
||||||
|
);
|
||||||
|
console.WriteLine($" mass (mature): {def.BaseMassKg:0.##} kg");
|
||||||
|
|
||||||
if (def.HarvestProduct is { } harvest)
|
if (def.HarvestProduct is { } harvest)
|
||||||
{
|
{
|
||||||
@@ -1126,6 +1274,21 @@ public sealed class WorldScene : Scene
|
|||||||
$" move {traits.MoveSpeed:0.##}, vision {traits.Vision:0.##}, blood {traits.BloodVolume:0.##}, "
|
$" move {traits.MoveSpeed:0.##}, vision {traits.Vision:0.##}, blood {traits.BloodVolume:0.##}, "
|
||||||
+ $"insulation {traits.Insulation:0.##}, furHue {traits.FurHue:0.##}"
|
+ $"insulation {traits.Insulation:0.##}, furHue {traits.FurHue:0.##}"
|
||||||
);
|
);
|
||||||
|
console.WriteLine(
|
||||||
|
$" diet: herb {traits.Herbivory:0.##} / carn {traits.Carnivory:0.##} / omni {traits.Omnivory: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.#}"
|
||||||
|
);
|
||||||
|
console.WriteLine(
|
||||||
|
$" behaviour: insulation {traits.Insulation:0.##}, sociability {traits.Sociability:0.##}"
|
||||||
|
);
|
||||||
|
var adultMass = Mass.OfAnimal(def, traits, 1f);
|
||||||
|
console.WriteLine(
|
||||||
|
$" mass: {adultMass:0.#} kg (adult), carries ~{Mass.CarryCapacity(adultMass, 1f):0.#} kg"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory,
|
// Диета из генов (предатор-кластер): по каждому виду печатает herbivory/carnivory/omnivory,
|
||||||
@@ -1187,6 +1350,7 @@ public sealed class WorldScene : Scene
|
|||||||
var brain = new double[_animals.Count];
|
var brain = new double[_animals.Count];
|
||||||
var life = new double[_animals.Count];
|
var life = new double[_animals.Count];
|
||||||
var move = new double[_animals.Count];
|
var move = new double[_animals.Count];
|
||||||
|
var tox = new double[_animals.Count];
|
||||||
var maxGen = new int[_animals.Count];
|
var maxGen = new int[_animals.Count];
|
||||||
|
|
||||||
Store
|
Store
|
||||||
@@ -1200,6 +1364,7 @@ public sealed class WorldScene : Scene
|
|||||||
brain[s] += o.Traits.BrainSize;
|
brain[s] += o.Traits.BrainSize;
|
||||||
life[s] += o.Traits.Lifespan;
|
life[s] += o.Traits.Lifespan;
|
||||||
move[s] += o.Traits.MoveSpeed;
|
move[s] += o.Traits.MoveSpeed;
|
||||||
|
tox[s] += o.Traits.ToxinTolerance;
|
||||||
if (o.Generation > maxGen[s])
|
if (o.Generation > maxGen[s])
|
||||||
{
|
{
|
||||||
maxGen[s] = o.Generation;
|
maxGen[s] = o.Generation;
|
||||||
@@ -1227,7 +1392,8 @@ public sealed class WorldScene : Scene
|
|||||||
var c = count[s];
|
var c = count[s];
|
||||||
console.WriteLine(
|
console.WriteLine(
|
||||||
$"{name}: n={c}, gen 0..{maxGen[s]}, body {body[s] / c:0.##}, "
|
$"{name}: n={c}, gen 0..{maxGen[s]}, body {body[s] / c:0.##}, "
|
||||||
+ $"brain {brain[s] / c:0.##}, lifespan {life[s] / c:0} d, move {move[s] / c:0.##}"
|
+ $"brain {brain[s] / c:0.##}, lifespan {life[s] / c:0} d, move {move[s] / c:0.##}, "
|
||||||
|
+ $"toxinTol {tox[s] / c:0.##}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1235,6 +1401,33 @@ public sealed class WorldScene : Scene
|
|||||||
{
|
{
|
||||||
console.WriteLine(filter is null ? "no animals alive" : $"no '{filter}' alive");
|
console.WriteLine(filter is null ? "no animals alive" : $"no '{filter}' alive");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Другая сторона гонки вооружений: средняя защита живых растений (коэволюция C2). Без фильтра.
|
||||||
|
if (filter is null)
|
||||||
|
{
|
||||||
|
var pn = 0;
|
||||||
|
double pTox = 0,
|
||||||
|
pThorn = 0,
|
||||||
|
pPalat = 0;
|
||||||
|
Store
|
||||||
|
.Query<PlantOrganism>()
|
||||||
|
.ForEachEntity(
|
||||||
|
(ref PlantOrganism o, Entity _) =>
|
||||||
|
{
|
||||||
|
pn++;
|
||||||
|
pTox += o.Traits.Toxicity;
|
||||||
|
pThorn += o.Traits.Thorns;
|
||||||
|
pPalat += o.Traits.Palatability;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (pn > 0)
|
||||||
|
{
|
||||||
|
console.WriteLine(
|
||||||
|
$"plants: n={pn}, toxicity {pTox / pn:0.###}, thorns {pThorn / pn:0.###}, "
|
||||||
|
+ $"palatability {pPalat / pn:0.###}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Наблюдаемость здоровья (фаза A5): дерево частей тела вида и способности здоровой особи.
|
// Наблюдаемость здоровья (фаза A5): дерево частей тела вида и способности здоровой особи.
|
||||||
@@ -1436,6 +1629,45 @@ public sealed class WorldScene : Scene
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Наблюдаемость навыков: средний уровень каждого навыка по живым животным (растут от практики).
|
||||||
|
private void RunSkills(DevConsole console)
|
||||||
|
{
|
||||||
|
if (_skills.Count == 0)
|
||||||
|
{
|
||||||
|
console.WriteLine("no skill defs loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sum = new double[_skills.Count];
|
||||||
|
var n = 0;
|
||||||
|
Store
|
||||||
|
.Query<Skills>()
|
||||||
|
.ForEachEntity(
|
||||||
|
(ref Skills s, Entity _) =>
|
||||||
|
{
|
||||||
|
if (s.Levels is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
n++;
|
||||||
|
for (var i = 0; i < _skills.Count && i < s.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
sum[i] += s.Levels[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.WriteLine($"skills over {n} animals (mean level):");
|
||||||
|
for (var i = 0; i < _skills.Count; i++)
|
||||||
|
{
|
||||||
|
console.WriteLine(
|
||||||
|
$" {_skills[i].DefName}: {(n > 0 ? sum[i] / n : 0):0.###} "
|
||||||
|
+ $"(learn {_skills[i].LearnRate:0.##}/d, decay {_skills[i].DecayPerDay:0.###}/d)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static string ProductLabel(GameContent content, string productDefName) =>
|
private static string ProductLabel(GameContent content, string productDefName) =>
|
||||||
content.Defs.TryGet<ProductDef>(productDefName, out var product)
|
content.Defs.TryGet<ProductDef>(productDefName, out var product)
|
||||||
? content.Languages.Get(product.Label)
|
? content.Languages.Get(product.Label)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public static class AnimalFactory
|
|||||||
EntityStore store,
|
EntityStore store,
|
||||||
AnimalSet animals,
|
AnimalSet animals,
|
||||||
NeedSet needs,
|
NeedSet needs,
|
||||||
|
SkillSet skills,
|
||||||
int species,
|
int species,
|
||||||
Vector2 position,
|
Vector2 position,
|
||||||
float ageDays,
|
float ageDays,
|
||||||
@@ -41,7 +42,7 @@ public static class AnimalFactory
|
|||||||
sprite.CenterOrigin();
|
sprite.CenterOrigin();
|
||||||
sprite.Color = FurTint(traits);
|
sprite.Color = FurTint(traits);
|
||||||
|
|
||||||
return store.CreateEntity(
|
var entity = store.CreateEntity(
|
||||||
new Transform2D(
|
new Transform2D(
|
||||||
position,
|
position,
|
||||||
scale: new Vector2(Scale(sp, traits, stage, region, cellSize))
|
scale: new Vector2(Scale(sp, traits, stage, region, cellSize))
|
||||||
@@ -73,21 +74,42 @@ public static class AnimalFactory
|
|||||||
Thoughts = [],
|
Thoughts = [],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Навыки: стартовые уровни + страсть (детерминированно по геному — воспроизводимо без RNG-параметра).
|
||||||
|
entity.AddComponent(
|
||||||
|
new Skills
|
||||||
|
{
|
||||||
|
Levels = skills.NewLevels(),
|
||||||
|
Passions = skills.RollPassions(new Random(SkillSeed(genome) ^ species)),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Сколько мяса даёт труп на единицу размера тела (для будущей разделки/падальщиков).</summary>
|
// Стабильный сид из генома (сумма аллелей — порядконезависима) для детерминированной страсти к навыкам.
|
||||||
public const float MeatPerBodySize = 25f;
|
private static int SkillSeed(Genome genome)
|
||||||
|
{
|
||||||
|
var acc = 0;
|
||||||
|
foreach (var (_, allele) in genome.Alleles) // IReadOnlyDictionary без копии — без аллокации
|
||||||
|
{
|
||||||
|
acc += (int)(allele.A * 7919f) + (int)(allele.B * 104729f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
// Тинт свежего трупа (стадии гниения/скелета задаёт CorpseSystem).
|
// Тинт свежего трупа (стадии гниения/скелета задаёт CorpseSystem).
|
||||||
private static readonly Color CorpseFreshTint = new(176, 148, 128);
|
private static readonly Color CorpseFreshTint = new(176, 148, 128);
|
||||||
|
|
||||||
/// <summary>Создаёт сущность-труп после смерти животного: спрайт-падаль, запас мяса (∝ размер тела).</summary>
|
/// <summary>Создаёт сущность-труп после смерти животного: спрайт-падаль (масштаб ∝ размер тела) и
|
||||||
|
/// запас мяса <paramref name="meatKg"/> (∝ масса тела, считается вызывающим через <see cref="Mass"/>).</summary>
|
||||||
public static Entity CreateCorpse(
|
public static Entity CreateCorpse(
|
||||||
EntityStore store,
|
EntityStore store,
|
||||||
AnimalSet animals,
|
AnimalSet animals,
|
||||||
int species,
|
int species,
|
||||||
Vector2 position,
|
Vector2 position,
|
||||||
float bodySize,
|
float bodySize,
|
||||||
|
float meatKg,
|
||||||
int cellSize
|
int cellSize
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -105,7 +127,48 @@ public static class AnimalFactory
|
|||||||
Species = species,
|
Species = species,
|
||||||
RotDays = 0f,
|
RotDays = 0f,
|
||||||
Stage = 0,
|
Stage = 0,
|
||||||
Meat = bodySize * MeatPerBodySize,
|
Meat = meatKg,
|
||||||
|
BodySize = bodySize,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Тинт яйца (бледно-кремовый) и доля размера клетки, до которой масштабируется спрайт яйца.
|
||||||
|
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,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -144,7 +207,7 @@ public static class AnimalFactory
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Гибель особи: оставляет труп (мясо ∝ размер тела) и удаляет сущность. Общий путь смерти.</summary>
|
/// <summary>Гибель особи: оставляет труп (мясо ∝ масса тела) и удаляет сущность. Общий путь смерти.</summary>
|
||||||
public static void Die(EntityStore store, AnimalSet animals, Entity entity, int cellSize)
|
public static void Die(EntityStore store, AnimalSet animals, Entity entity, int cellSize)
|
||||||
{
|
{
|
||||||
if (entity.IsNull || !entity.HasComponent<AnimalOrganism>())
|
if (entity.IsNull || !entity.HasComponent<AnimalOrganism>())
|
||||||
@@ -154,7 +217,12 @@ public static class AnimalFactory
|
|||||||
|
|
||||||
ref readonly var org = ref entity.GetComponent<AnimalOrganism>();
|
ref readonly var org = ref entity.GetComponent<AnimalOrganism>();
|
||||||
var position = entity.GetComponent<Transform2D>().Position;
|
var position = entity.GetComponent<Transform2D>().Position;
|
||||||
CreateCorpse(store, animals, org.Species, position, org.Traits.BodySize, cellSize);
|
var sp = animals[org.Species];
|
||||||
|
var stageScale = entity.TryGetComponent<AnimalGrowth>(out var grow)
|
||||||
|
? sp.Stages[Math.Clamp(grow.Stage, 0, sp.Stages.Length - 1)].Scale
|
||||||
|
: 1f;
|
||||||
|
var meat = Mass.OfAnimal(sp.Def, org.Traits, stageScale) * Mass.MeatFraction;
|
||||||
|
CreateCorpse(store, animals, org.Species, position, org.Traits.BodySize, meat, cellSize);
|
||||||
entity.DeleteEntity();
|
entity.DeleteEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,12 @@ public struct AnimalPhenotype
|
|||||||
/// <summary>Всеядность [0..1] — генералист: включает оба источника пищи (растения и мясо).</summary>
|
/// <summary>Всеядность [0..1] — генералист: включает оба источника пищи (растения и мясо).</summary>
|
||||||
public float Omnivory;
|
public float Omnivory;
|
||||||
|
|
||||||
|
/// <summary>Устойчивость к растительным ядам [0..1] — снижает дозу отравления токсичным кормом.</summary>
|
||||||
|
public float ToxinTolerance;
|
||||||
|
|
||||||
|
/// <summary>Социальность [0..1] — тяга держаться стаи/стада (когезия + спокойствие в группе).</summary>
|
||||||
|
public float Sociability;
|
||||||
|
|
||||||
/// <summary>Продолжительность жизни (игровых дней).</summary>
|
/// <summary>Продолжительность жизни (игровых дней).</summary>
|
||||||
public float Lifespan;
|
public float Lifespan;
|
||||||
|
|
||||||
@@ -60,6 +66,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)
|
||||||
{
|
{
|
||||||
@@ -78,10 +90,13 @@ public struct AnimalPhenotype
|
|||||||
Herbivory = T("herbivory"),
|
Herbivory = T("herbivory"),
|
||||||
Carnivory = T("carnivory"),
|
Carnivory = T("carnivory"),
|
||||||
Omnivory = T("omnivory"),
|
Omnivory = T("omnivory"),
|
||||||
|
ToxinTolerance = T("toxinTolerance"),
|
||||||
|
Sociability = T("sociability"),
|
||||||
Lifespan = T("lifespan"),
|
Lifespan = T("lifespan"),
|
||||||
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"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,9 +202,11 @@ public sealed class HealthState
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Пересчитывает боль (из хедифов) и способности: базовые из частей/крови/боли, затем модификаторы
|
/// Пересчитывает боль (из хедифов) и способности: базовые из частей/крови/боли, затем модификаторы
|
||||||
/// способностей от хедифов (лерп 1→factor по тяжести). Вызывать после изменения частей/крови/хедифов.
|
/// способностей от хедифов (лерп 1→factor по тяжести), затем общий множитель <paramref name="capacityScale"/>
|
||||||
|
/// (старение/сенесценс: дряхлеющий организм слабее по всем способностям). Вызывать после изменения
|
||||||
|
/// частей/крови/хедифов; <paramref name="capacityScale"/>=1 — без возрастного спада.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void RecomputeCapacities()
|
public void RecomputeCapacities(float capacityScale = 1f)
|
||||||
{
|
{
|
||||||
var pain = 0f;
|
var pain = 0f;
|
||||||
foreach (var h in _hediffs)
|
foreach (var h in _hediffs)
|
||||||
@@ -199,7 +216,8 @@ public sealed class HealthState
|
|||||||
|
|
||||||
Pain = Math.Clamp(pain, 0f, 1f);
|
Pain = Math.Clamp(pain, 0f, 1f);
|
||||||
|
|
||||||
var caps = CapacityCalc.Compute(Parts, BloodLevel, Pain);
|
// Множитель старения применяется внутри Compute (без второго прохода/аллокации списка ключей).
|
||||||
|
var caps = CapacityCalc.Compute(Parts, BloodLevel, Pain, capacityScale);
|
||||||
foreach (var h in _hediffs)
|
foreach (var h in _hediffs)
|
||||||
{
|
{
|
||||||
if (h.Def.CapMods.Count == 0)
|
if (h.Def.CapMods.Count == 0)
|
||||||
@@ -220,8 +238,10 @@ public sealed class HealthState
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Прогрессирует болезни (тяжесть и иммунитет растут со временем); снимает выздоровевшие
|
/// Прогрессирует болезни (тяжесть и иммунитет растут со временем); снимает выздоровевшие
|
||||||
/// (иммунитет ≥ 1); возвращает true, если какая-то болезнь достигла летальной тяжести.
|
/// (иммунитет ≥ 1); возвращает true, если какая-то болезнь достигла летальной тяжести.
|
||||||
|
/// <paramref name="immunityScale"/> — множитель набора иммунитета (способность «фильтрация крови»:
|
||||||
|
/// почки/печень выводят токсины и помогают бороться с болезнью; повреждённые → медленнее).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool AdvanceHediffs(float days)
|
public bool AdvanceHediffs(float days, float immunityScale = 1f)
|
||||||
{
|
{
|
||||||
var lethal = false;
|
var lethal = false;
|
||||||
for (var i = _hediffs.Count - 1; i >= 0; i--)
|
for (var i = _hediffs.Count - 1; i >= 0; i--)
|
||||||
@@ -233,7 +253,7 @@ public sealed class HealthState
|
|||||||
}
|
}
|
||||||
|
|
||||||
h.Severity += h.Def.SeverityPerDay * days;
|
h.Severity += h.Def.SeverityPerDay * days;
|
||||||
h.Immunity += h.Def.ImmunityPerDay * days;
|
h.Immunity += h.Def.ImmunityPerDay * days * immunityScale;
|
||||||
if (h.Immunity >= 1f)
|
if (h.Immunity >= 1f)
|
||||||
{
|
{
|
||||||
_hediffs.RemoveAt(i); // иммунитет победил — выздоровление
|
_hediffs.RemoveAt(i); // иммунитет победил — выздоровление
|
||||||
@@ -436,8 +456,86 @@ public struct Corpse : IComponent
|
|||||||
/// <summary>Стадия: 0 свежий, 1 гниющий, 2 скелет.</summary>
|
/// <summary>Стадия: 0 свежий, 1 гниющий, 2 скелет.</summary>
|
||||||
public int Stage;
|
public int Stage;
|
||||||
|
|
||||||
/// <summary>Доступное мясо (∝ размер тела) — для разделки/падальщиков (фаза-горизонт).</summary>
|
/// <summary>Доступное мясо (∝ масса тела) — для разделки/падальщиков (фаза-горизонт).</summary>
|
||||||
public float Meat;
|
public float Meat;
|
||||||
|
|
||||||
|
/// <summary>Линейный размер тела (для масштаба спрайта трупа), независимо от запаса мяса.</summary>
|
||||||
|
public float BodySize;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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>Id навыков-эффектов (на них ссылается код начисления XP и применения бонуса).</summary>
|
||||||
|
public static class AnimalSkills
|
||||||
|
{
|
||||||
|
/// <summary>Добыча корма: эффективность выпаса/поедания растений.</summary>
|
||||||
|
public const string Foraging = "Foraging";
|
||||||
|
|
||||||
|
/// <summary>Охота: урон по добыче.</summary>
|
||||||
|
public const string Hunting = "Hunting";
|
||||||
|
|
||||||
|
/// <summary>Уклонение: скорость бегства от хищника.</summary>
|
||||||
|
public const string Evasion = "Evasion";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Навыки особи (как нужды/способности — managed-массивы по индексам <see cref="Content.SkillSet"/>):
|
||||||
|
/// уровни [0..1] (растут от практики, угасают без неё) и страсть к каждому навыку (скорость обучения).
|
||||||
|
/// Есть у животных; те же навыки получат будущие люди. Добавление навыка не меняет структуру.
|
||||||
|
/// </summary>
|
||||||
|
public struct Skills : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Уровни навыков [0..1] по индексам <see cref="Content.SkillSet"/>.</summary>
|
||||||
|
public float[] Levels;
|
||||||
|
|
||||||
|
/// <summary>Страсть к каждому навыку (0 нет / 1 интерес / 2 страсть) — множитель обучения.</summary>
|
||||||
|
public byte[] Passions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ноша существа (задел под перенос предметов/хаул): суммарная масса переносимого груза в кг. Перегруз
|
||||||
|
/// сверх грузоподъёмности (<see cref="Sim.Mass.CarryCapacity"/>) замедляет движение. Компонент появляется
|
||||||
|
/// у существа, когда оно что-то несёт; пока источников груза нет — спящий каркас под будущий инвентарь.
|
||||||
|
/// </summary>
|
||||||
|
public struct Carrier : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Суммарная масса переносимого груза (кг).</summary>
|
||||||
|
public float CarriedKg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Проглоченное семя (эндозоохория): травоядное, съевшее плодоносящее растение, какое-то время несёт
|
||||||
|
/// его семя в кишечнике, затем «высаживает» его в другом месте (<see cref="Sim.SeedDispersalSystem"/>) —
|
||||||
|
/// зверь становится разносчиком флоры. Геном — managed-ссылка (клон родительского растения).
|
||||||
|
/// </summary>
|
||||||
|
public struct GutSeed : IComponent
|
||||||
|
{
|
||||||
|
/// <summary>Индекс вида растения в <see cref="Content.PlantSet"/>.</summary>
|
||||||
|
public int PlantSpecies;
|
||||||
|
|
||||||
|
/// <summary>Геном будущего ростка (клон съеденного растения).</summary>
|
||||||
|
public Genome Genome;
|
||||||
|
|
||||||
|
/// <summary>Остаток времени до высадки в игровых днях; ≤0 — росток падает на землю.</summary>
|
||||||
|
public float DropInDays;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Инстанс мысли настроения на особи (фаза A8): деф события + остаток времени до угасания.</summary>
|
/// <summary>Инстанс мысли настроения на особи (фаза A8): деф события + остаток времени до угасания.</summary>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,27 @@ public static class AnimalCapacities
|
|||||||
public const string Moving = "Moving";
|
public const string Moving = "Moving";
|
||||||
public const string Breathing = "Breathing";
|
public const string Breathing = "Breathing";
|
||||||
public const string BloodPumping = "BloodPumping";
|
public const string BloodPumping = "BloodPumping";
|
||||||
|
public const string BloodFiltration = "BloodFiltration";
|
||||||
public const string Sight = "Sight";
|
public const string Sight = "Sight";
|
||||||
|
public const string Hearing = "Hearing";
|
||||||
|
public const string Talking = "Talking";
|
||||||
|
public const string Eating = "Eating";
|
||||||
public const string Digestion = "Digestion";
|
public const string Digestion = "Digestion";
|
||||||
|
|
||||||
|
/// <summary>Порядок отображения способностей в UI (сверху вниз). Прочие модовые — после, как есть.</summary>
|
||||||
|
public static readonly string[] DisplayOrder =
|
||||||
|
[
|
||||||
|
Consciousness,
|
||||||
|
Moving,
|
||||||
|
Sight,
|
||||||
|
Hearing,
|
||||||
|
Talking,
|
||||||
|
Eating,
|
||||||
|
Breathing,
|
||||||
|
BloodPumping,
|
||||||
|
BloodFiltration,
|
||||||
|
Digestion,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -21,10 +40,20 @@ public static class AnimalCapacities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CapacityCalc
|
public static class CapacityCalc
|
||||||
{
|
{
|
||||||
/// <summary>Считает карту способностей из инстансов частей, уровня крови (0..1) и боли (0..1).</summary>
|
/// <summary>Считает карту способностей из инстансов частей, уровня крови (0..1) и боли (0..1);
|
||||||
public static Dictionary<string, float> Compute(PartInstance[] parts, float blood, float pain)
|
/// <paramref name="capacityScale"/> — общий множитель (старение), применяется к итогу без лишних аллокаций.</summary>
|
||||||
|
public static Dictionary<string, float> Compute(
|
||||||
|
PartInstance[] parts,
|
||||||
|
float blood,
|
||||||
|
float pain,
|
||||||
|
float capacityScale = 1f
|
||||||
|
)
|
||||||
{
|
{
|
||||||
|
// Сырой вклад каждой способности (Σ доля·HP) и НАБОР объявленных телом способностей: вид имеет
|
||||||
|
// только те, к которым его части вообще причастны (у оленя нет манипуляции). Объявленность —
|
||||||
|
// статична (по дефам частей), поэтому уничтоженный орган всё равно покажет способность на 0%.
|
||||||
var raw = new Dictionary<string, float>(System.StringComparer.Ordinal);
|
var raw = new Dictionary<string, float>(System.StringComparer.Ordinal);
|
||||||
|
var declared = new HashSet<string>(System.StringComparer.Ordinal);
|
||||||
foreach (var part in parts)
|
foreach (var part in parts)
|
||||||
{
|
{
|
||||||
if (part.Def is null)
|
if (part.Def is null)
|
||||||
@@ -35,36 +64,54 @@ public static class CapacityCalc
|
|||||||
var fraction = part.Fraction;
|
var fraction = part.Fraction;
|
||||||
foreach (var (capacity, contribution) in part.Def.Capacities)
|
foreach (var (capacity, contribution) in part.Def.Capacities)
|
||||||
{
|
{
|
||||||
|
declared.Add(capacity);
|
||||||
raw[capacity] = raw.GetValueOrDefault(capacity) + contribution * fraction;
|
raw[capacity] = raw.GetValueOrDefault(capacity) + contribution * fraction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float Raw(string id) => raw.GetValueOrDefault(id);
|
float Raw(string id) => raw.GetValueOrDefault(id);
|
||||||
static float Clamp01(float v) => v < 0f ? 0f : v > 1f ? 1f : v;
|
static float Clamp01(float v) =>
|
||||||
|
v < 0f ? 0f
|
||||||
|
: v > 1f ? 1f
|
||||||
|
: v;
|
||||||
|
|
||||||
|
// Базовые (независимые) способности: кровоснабжение/дыхание/фильтрация × уровень крови.
|
||||||
var bloodLevel = Clamp01(blood);
|
var bloodLevel = Clamp01(blood);
|
||||||
var bloodPumping = Raw(AnimalCapacities.BloodPumping) * bloodLevel;
|
var bloodPumping = Raw(AnimalCapacities.BloodPumping) * bloodLevel;
|
||||||
var breathing = Raw(AnimalCapacities.Breathing) * bloodLevel;
|
var breathing = Raw(AnimalCapacities.Breathing) * bloodLevel;
|
||||||
|
var bloodFiltration = Raw(AnimalCapacities.BloodFiltration) * bloodLevel;
|
||||||
|
// Сознание зависит от кровоснабжения, дыхания и боли (модель RimWorld).
|
||||||
var consciousness =
|
var consciousness =
|
||||||
Raw(AnimalCapacities.Consciousness)
|
Raw(AnimalCapacities.Consciousness)
|
||||||
* Clamp01(bloodPumping)
|
* Clamp01(bloodPumping)
|
||||||
* Clamp01(breathing)
|
* Clamp01(breathing)
|
||||||
* (1f - Clamp01(pain));
|
* (1f - Clamp01(pain));
|
||||||
|
var cons = Clamp01(consciousness);
|
||||||
|
var pump = Clamp01(bloodPumping);
|
||||||
|
|
||||||
var result = new Dictionary<string, float>(System.StringComparer.Ordinal)
|
// Производные способности зависят от сознания (и кровоснабжения для движения/пищеварения).
|
||||||
|
var computed = new Dictionary<string, float>(System.StringComparer.Ordinal)
|
||||||
{
|
{
|
||||||
[AnimalCapacities.BloodPumping] = bloodPumping,
|
[AnimalCapacities.BloodPumping] = bloodPumping,
|
||||||
[AnimalCapacities.Breathing] = breathing,
|
[AnimalCapacities.Breathing] = breathing,
|
||||||
|
[AnimalCapacities.BloodFiltration] = bloodFiltration,
|
||||||
[AnimalCapacities.Consciousness] = consciousness,
|
[AnimalCapacities.Consciousness] = consciousness,
|
||||||
[AnimalCapacities.Moving] = Raw(AnimalCapacities.Moving) * consciousness * Clamp01(bloodPumping),
|
[AnimalCapacities.Moving] = Raw(AnimalCapacities.Moving) * cons * pump,
|
||||||
[AnimalCapacities.Sight] = Raw(AnimalCapacities.Sight) * consciousness,
|
[AnimalCapacities.Sight] = Raw(AnimalCapacities.Sight) * cons,
|
||||||
[AnimalCapacities.Digestion] = Raw(AnimalCapacities.Digestion) * Clamp01(bloodPumping),
|
[AnimalCapacities.Hearing] = Raw(AnimalCapacities.Hearing) * cons,
|
||||||
|
[AnimalCapacities.Talking] = Raw(AnimalCapacities.Talking) * cons,
|
||||||
|
[AnimalCapacities.Eating] = Raw(AnimalCapacities.Eating) * cons,
|
||||||
|
[AnimalCapacities.Digestion] = Raw(AnimalCapacities.Digestion) * pump,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Модовые способности, не охваченные зависимостями, — сырыми.
|
// В результат — только объявленные телом способности: известные по формуле зависимостей, прочие
|
||||||
foreach (var (capacity, value) in raw)
|
// (модовые) — сырым вкладом. Так у вида видны ровно его способности, а не весь каталог.
|
||||||
|
var scale = Clamp01(capacityScale);
|
||||||
|
var result = new Dictionary<string, float>(System.StringComparer.Ordinal);
|
||||||
|
foreach (var capacity in declared)
|
||||||
{
|
{
|
||||||
result.TryAdd(capacity, value);
|
var value = computed.TryGetValue(capacity, out var v) ? v : Raw(capacity);
|
||||||
|
result[capacity] = scale < 1f ? value * scale : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
using LittleSim.Content;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Система веса (масса тел и грузоподъёмность). Масса — производная от РАЗМЕРА по кубическому закону
|
||||||
|
/// (вдвое крупнее ⇒ в 8 раз тяжелее): <c>масса = BaseMassKg × (размер / эталон)³</c>, где эталон —
|
||||||
|
/// базовый размер вида. Так детёныш/росток легче взрослого/зрелого, а ген размера двигает и массу.
|
||||||
|
/// Грузоподъёмность — задел под перенос предметов: доля массы тела × способность (манипуляция/движение).
|
||||||
|
/// </summary>
|
||||||
|
public static class Mass
|
||||||
|
{
|
||||||
|
/// <summary>Какую долю собственной массы существо может нести (×способность). Человек (руки) — больше.</summary>
|
||||||
|
public const float CarryFraction = 0.35f;
|
||||||
|
|
||||||
|
/// <summary>Доля массы тела, превращающаяся в мясо трупа (для разделки/падальщиков).</summary>
|
||||||
|
public const float MeatFraction = 0.5f;
|
||||||
|
|
||||||
|
private static float Cube(float v) => v * v * v;
|
||||||
|
|
||||||
|
/// <summary>Масса особи-животного (кг): эталон вида × куб (текущий линейный размер / эталонный).</summary>
|
||||||
|
public static float OfAnimal(AnimalDef def, in AnimalPhenotype traits, float stageScale)
|
||||||
|
{
|
||||||
|
var reference = def.Genome.GetValueOrDefault("GeneMaxBodySize", 1f);
|
||||||
|
if (reference <= 0f)
|
||||||
|
{
|
||||||
|
reference = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
var linear = MathF.Max(0.01f, traits.BodySize) * MathF.Max(0.05f, stageScale);
|
||||||
|
return def.BaseMassKg * Cube(linear / reference);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Масса растения (кг): эталон вида × куб (размер текущей стадии / размер зрелой).</summary>
|
||||||
|
public static float OfPlant(PlantDef def, float stageSizeCells, float matureSizeCells)
|
||||||
|
{
|
||||||
|
var ratio = matureSizeCells > 0f ? stageSizeCells / matureSizeCells : 1f;
|
||||||
|
return def.BaseMassKg * Cube(ratio);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Грузоподъёмность (кг): доля массы тела × способность нести (манипуляция/движение, 0..1+).</summary>
|
||||||
|
public static float CarryCapacity(float bodyMassKg, float capability) =>
|
||||||
|
bodyMassKg * CarryFraction * MathF.Max(0f, capability);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Метаболический множитель по закону Клайбера: расход энергии ∝ масса^0.75. Нормирован на эталонную
|
||||||
|
/// массу вида (взрослый базового размера = 1), поэтому крупные особи прожорливее, а молодняк — экономнее,
|
||||||
|
/// без межвидового взрыва баланса.
|
||||||
|
/// </summary>
|
||||||
|
public static float KleiberFactor(float massKg, float referenceMassKg)
|
||||||
|
{
|
||||||
|
if (referenceMassKg <= 0f || massKg <= 0f)
|
||||||
|
{
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MathF.Pow(massKg / referenceMassKg, 0.75f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Множитель скорости от ноши: в пределах грузоподъёмности — 1; перегруз тормозит (до 0.25).</summary>
|
||||||
|
public static float LoadSpeedFactor(float carriedKg, float capacityKg)
|
||||||
|
{
|
||||||
|
if (capacityKg <= 0f || carriedKg <= capacityKg)
|
||||||
|
{
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return MathF.Max(0.25f, capacityKg / carriedKg);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace LittleSim.Sim;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Пространственный индекс растений (равномерная сетка ячеек) для радиус-запросов поиска корма: вместо
|
||||||
|
/// прохода по ВСЕМ растениям на каждое решение травоядного (O(растений) на особь) — только растения в
|
||||||
|
/// ближних ячейках. Перестраивается раз в кадр из позиций растений (O(растений) линейно). В ячейке лежит
|
||||||
|
/// то, что нужно поиску — позиция, id, токсичность и вкусность — без дорефетча компонентов. Списки ячеек
|
||||||
|
/// переиспользуются (пул), чтобы перестройка не аллоцировала. Порядок обхода детерминирован (вложенные
|
||||||
|
/// циклы по ячейкам + порядок вставки), а выбор всё равно ломает ничьи по меньшему id — детерминизм цел.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class PlantGrid
|
||||||
|
{
|
||||||
|
/// <summary>Запись растения в ячейке: id, позиция и признаки, нужные поиску корма.</summary>
|
||||||
|
public readonly record struct Entry(int Id, Vector2 Pos, float Toxicity, float Palatability);
|
||||||
|
|
||||||
|
private readonly float _cell;
|
||||||
|
private readonly Dictionary<(int, int), List<Entry>> _cells = new();
|
||||||
|
private readonly Stack<List<Entry>> _pool = new();
|
||||||
|
|
||||||
|
public PlantGrid(float cellSize) => _cell = cellSize > 0f ? cellSize : 1f;
|
||||||
|
|
||||||
|
/// <summary>Очищает сетку (списки ячеек возвращаются в пул для переиспользования).</summary>
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
foreach (var list in _cells.Values)
|
||||||
|
{
|
||||||
|
list.Clear();
|
||||||
|
_pool.Push(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
_cells.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Добавляет растение в его ячейку.</summary>
|
||||||
|
public void Add(int id, Vector2 pos, float toxicity, float palatability)
|
||||||
|
{
|
||||||
|
var key = CellOf(pos);
|
||||||
|
if (!_cells.TryGetValue(key, out var list))
|
||||||
|
{
|
||||||
|
list = _pool.Count > 0 ? _pool.Pop() : new List<Entry>();
|
||||||
|
_cells[key] = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(new Entry(id, pos, toxicity, palatability));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Собирает в <paramref name="results"/> растения из ячеек, перекрывающих радиус (точную
|
||||||
|
/// дистанцию проверяет вызывающий). Буфер переиспользуется — без аллокаций на запрос.</summary>
|
||||||
|
public void Collect(Vector2 center, float radius, List<Entry> results)
|
||||||
|
{
|
||||||
|
results.Clear();
|
||||||
|
var minX = (int)MathF.Floor((center.X - radius) / _cell);
|
||||||
|
var maxX = (int)MathF.Floor((center.X + radius) / _cell);
|
||||||
|
var minY = (int)MathF.Floor((center.Y - radius) / _cell);
|
||||||
|
var maxY = (int)MathF.Floor((center.Y + radius) / _cell);
|
||||||
|
for (var cx = minX; cx <= maxX; cx++)
|
||||||
|
{
|
||||||
|
for (var cy = minY; cy <= maxY; cy++)
|
||||||
|
{
|
||||||
|
if (_cells.TryGetValue((cx, cy), out var list))
|
||||||
|
{
|
||||||
|
results.AddRange(list);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (int, int) CellOf(Vector2 p) =>
|
||||||
|
((int)MathF.Floor(p.X / _cell), (int)MathF.Floor(p.Y / _cell));
|
||||||
|
}
|
||||||
@@ -57,6 +57,7 @@ public sealed class PlantGrowthSystem(
|
|||||||
var (tMin, tLow, tHigh, tMax) = traits.TemperatureBand();
|
var (tMin, tLow, tHigh, tMax) = traits.TemperatureBand();
|
||||||
var rate =
|
var rate =
|
||||||
traits.Vigor
|
traits.Vigor
|
||||||
|
* traits.DefenseGrowthFactor() // цена защиты: токсичные/колючие растут медленнее
|
||||||
* Suitability.Gaussian(light, traits.OptimalLight, traits.LightTolerance)
|
* Suitability.Gaussian(light, traits.OptimalLight, traits.LightTolerance)
|
||||||
* Suitability.Trapezoid(temperature, tMin, tLow, tHigh, tMax)
|
* Suitability.Trapezoid(temperature, tMin, tLow, tHigh, tMax)
|
||||||
* Suitability.Gaussian(
|
* Suitability.Gaussian(
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ public struct PlantPhenotype
|
|||||||
public float HarvestAmount;
|
public float HarvestAmount;
|
||||||
public float LeafHue;
|
public float LeafHue;
|
||||||
|
|
||||||
|
// --- Защита (коэволюция с травоядными) ---
|
||||||
|
/// <summary>Токсичность [0..1]: отравляет поедателя; цена — замедление роста.</summary>
|
||||||
|
public float Toxicity;
|
||||||
|
|
||||||
|
/// <summary>Шипы [0..1]: травмируют поедателя; цена — замедление роста.</summary>
|
||||||
|
public float Thorns;
|
||||||
|
|
||||||
|
/// <summary>Вкусность [0..1]: обратная привлекательность для умных травоядных (избегание в C2).</summary>
|
||||||
|
public float Palatability;
|
||||||
|
|
||||||
/// <summary>Морфа: рецессивный вариант (выраженное значение гена морфы ≈ 1) — другой тинт спрайта.</summary>
|
/// <summary>Морфа: рецессивный вариант (выраженное значение гена морфы ≈ 1) — другой тинт спрайта.</summary>
|
||||||
public bool IsVariant;
|
public bool IsVariant;
|
||||||
|
|
||||||
@@ -71,9 +81,26 @@ public struct PlantPhenotype
|
|||||||
FruitSeason = (int)MathF.Round(Math.Clamp(T("fruitSeason"), 0f, 3f)),
|
FruitSeason = (int)MathF.Round(Math.Clamp(T("fruitSeason"), 0f, 3f)),
|
||||||
HarvestAmount = T("harvestAmount"),
|
HarvestAmount = T("harvestAmount"),
|
||||||
LeafHue = T("leafHue"),
|
LeafHue = T("leafHue"),
|
||||||
|
Toxicity = T("toxicity"),
|
||||||
|
Thorns = T("thorns"),
|
||||||
|
Palatability = T("palatability"),
|
||||||
IsVariant = T("variant") >= 0.5f,
|
IsVariant = T("variant") >= 0.5f,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Цена защиты: токсичность и шипы отнимают ресурсы у роста. Без этой платы отбор гнал бы защиту к
|
||||||
|
// максимуму у всех растений и коэволюция бы встала — а так под слабым выпасом выгоднее расти быстрее.
|
||||||
|
private const float ToxicityGrowthCost = 0.4f;
|
||||||
|
private const float ThornsGrowthCost = 0.3f;
|
||||||
|
private const float MinGrowthFactor = 0.2f;
|
||||||
|
|
||||||
|
/// <summary>Множитель скорости роста с учётом цены защиты (1 — без защиты, ≥ <c>MinGrowthFactor</c>).</summary>
|
||||||
|
public readonly float DefenseGrowthFactor() =>
|
||||||
|
Math.Clamp(
|
||||||
|
1f - ToxicityGrowthCost * Toxicity - ThornsGrowthCost * Thorns,
|
||||||
|
MinGrowthFactor,
|
||||||
|
1f
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -125,13 +125,13 @@ public sealed class GodCameraSystem(
|
|||||||
Entity cameraEntity,
|
Entity cameraEntity,
|
||||||
InputManager input,
|
InputManager input,
|
||||||
Renderer2D renderer,
|
Renderer2D renderer,
|
||||||
MrGameEng.DevConsole.DevConsole console,
|
MrGameEng.DevConsole.DevConsole? console,
|
||||||
Func<bool>? blocked = null
|
Func<bool>? blocked = null
|
||||||
) : BaseSystem
|
) : BaseSystem
|
||||||
{
|
{
|
||||||
protected override void OnUpdateGroup()
|
protected override void OnUpdateGroup()
|
||||||
{
|
{
|
||||||
if (console.IsOpen || blocked?.Invoke() == true)
|
if (console?.IsOpen == true || blocked?.Invoke() == true)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Friflo.Engine.ECS;
|
||||||
|
using Friflo.Engine.ECS.Systems;
|
||||||
|
using LittleSim.Content;
|
||||||
|
using LittleSim.Sim;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using MrGameEng.Graphics;
|
||||||
|
using Myra.Graphics2D;
|
||||||
|
using Myra.Graphics2D.Brushes;
|
||||||
|
using Myra.Graphics2D.UI;
|
||||||
|
|
||||||
|
namespace LittleSim.UI;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дев-спавнер в духе RimWorld (только в режиме разработчика): боковая панель со списком по категориям.
|
||||||
|
/// Виды животных/растений берутся ИЗ ДЕФОВ (модовый контент появляется сам); «инструменты» (урон/смерть/
|
||||||
|
/// болезнь) и прочие модовые пункты — из <see cref="DevToolDef"/>. Спавн-пункт «берётся в руку»: клик по
|
||||||
|
/// миру создаёт сущность в курсоре (ПКМ снимает). Инструменты-действия применяются к выбранной особи
|
||||||
|
/// сразу по нажатию. Так удобно сравнивать сущности и наносить им урон. Управляет <see cref="DevSpawnerSystem"/>.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DevSpawner
|
||||||
|
{
|
||||||
|
private readonly EntityStore _store;
|
||||||
|
private readonly AnimalSet _animals;
|
||||||
|
private readonly PlantSet _plants;
|
||||||
|
private readonly NeedSet _needs;
|
||||||
|
private readonly SkillSet _skills;
|
||||||
|
private readonly GameContent _content;
|
||||||
|
private readonly Selection _selection;
|
||||||
|
private readonly float[] _cellFertility;
|
||||||
|
private readonly int _width;
|
||||||
|
private readonly int _height;
|
||||||
|
private readonly int _cellSize;
|
||||||
|
private readonly HediffDef? _fever;
|
||||||
|
private readonly HediffDef? _bleeding;
|
||||||
|
private readonly Random _rng = new(0x5A11);
|
||||||
|
|
||||||
|
private readonly VerticalStackPanel _panel;
|
||||||
|
private readonly Label _title;
|
||||||
|
private string? _armedKind;
|
||||||
|
private string? _armedTarget;
|
||||||
|
|
||||||
|
public DevSpawner(
|
||||||
|
EntityStore store,
|
||||||
|
AnimalSet animals,
|
||||||
|
PlantSet plants,
|
||||||
|
NeedSet needs,
|
||||||
|
SkillSet skills,
|
||||||
|
GameContent content,
|
||||||
|
Selection selection,
|
||||||
|
float[] cellFertility,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int cellSize
|
||||||
|
)
|
||||||
|
{
|
||||||
|
_store = store;
|
||||||
|
_animals = animals;
|
||||||
|
_plants = plants;
|
||||||
|
_needs = needs;
|
||||||
|
_skills = skills;
|
||||||
|
_content = content;
|
||||||
|
_selection = selection;
|
||||||
|
_cellFertility = cellFertility;
|
||||||
|
_width = width;
|
||||||
|
_height = height;
|
||||||
|
_cellSize = cellSize;
|
||||||
|
_fever = content.Defs.TryGet<HediffDef>("Fever", out var f) ? f : null;
|
||||||
|
_bleeding = content.Defs.TryGet<HediffDef>("Bleeding", out var b) ? b : null;
|
||||||
|
|
||||||
|
var lang = content.Languages;
|
||||||
|
_title = new Label { TextColor = Ui.Accent, Wrap = true };
|
||||||
|
|
||||||
|
var list = new VerticalStackPanel { Spacing = 2 };
|
||||||
|
|
||||||
|
// Инструменты-действия (по выбранной особи) + модовые DevToolDef.
|
||||||
|
Header(list, lang.Get("dev.tools"));
|
||||||
|
Entry(list, lang.Get("dev.damage"), () => ActOnSelection("damage", ""));
|
||||||
|
Entry(list, lang.Get("dev.kill"), () => ActOnSelection("kill", ""));
|
||||||
|
Entry(list, lang.Get("dev.infect"), () => ActOnSelection("infect", ""));
|
||||||
|
foreach (var tool in content.Defs.All<DevToolDef>())
|
||||||
|
{
|
||||||
|
var kind = tool.Kind;
|
||||||
|
var target = tool.Target;
|
||||||
|
var label = lang.Get(tool.Label);
|
||||||
|
if (kind is "spawn-animal" or "spawn-plant")
|
||||||
|
{
|
||||||
|
Entry(list, label, () => Arm(kind, target));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Entry(list, label, () => ActOnSelection(kind, target));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Виды — автоматически из дефов (включая модовые): «взять в руку» спавн.
|
||||||
|
Header(list, lang.Get("dev.animals"));
|
||||||
|
foreach (var name in content.Defs.NamesOf("Animal"))
|
||||||
|
{
|
||||||
|
var defName = name;
|
||||||
|
Entry(list, "+ " + lang.Get(AnimalLabel(defName)), () => Arm("spawn-animal", defName));
|
||||||
|
}
|
||||||
|
|
||||||
|
Header(list, lang.Get("dev.plants"));
|
||||||
|
foreach (var name in content.Defs.NamesOf("Plant"))
|
||||||
|
{
|
||||||
|
var defName = name;
|
||||||
|
Entry(list, "+ " + lang.Get(PlantLabel(defName)), () => Arm("spawn-plant", defName));
|
||||||
|
}
|
||||||
|
|
||||||
|
_panel = new VerticalStackPanel
|
||||||
|
{
|
||||||
|
Spacing = 6,
|
||||||
|
Padding = new Thickness(12),
|
||||||
|
Width = 250,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Right,
|
||||||
|
VerticalAlignment = VerticalAlignment.Top,
|
||||||
|
Margin = new Thickness(0, 14, 12, 0),
|
||||||
|
Background = new SolidBrush(new Color(10, 13, 18, 240)),
|
||||||
|
Border = new SolidBrush(new Color(96, 134, 168)),
|
||||||
|
BorderThickness = new Thickness(2),
|
||||||
|
Visible = false,
|
||||||
|
};
|
||||||
|
_panel.Widgets.Add(new Label { Text = lang.Get("dev.title"), TextColor = Ui.Accent });
|
||||||
|
_panel.Widgets.Add(_title);
|
||||||
|
_panel.Widgets.Add(new HorizontalSeparator());
|
||||||
|
_panel.Widgets.Add(new ScrollViewer { Height = 460, Content = list });
|
||||||
|
UpdateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Виджет панели (добавляется в корень экрана сцены).</summary>
|
||||||
|
public Widget Panel => _panel;
|
||||||
|
|
||||||
|
/// <summary>Взведён ли спавн-инструмент (клик по миру создаёт сущность).</summary>
|
||||||
|
public bool ArmedSpawn => _armedKind is "spawn-animal" or "spawn-plant";
|
||||||
|
|
||||||
|
/// <summary>Показать/скрыть панель (горячая клавиша из системы).</summary>
|
||||||
|
public void Toggle() => _panel.Visible = !_panel.Visible;
|
||||||
|
|
||||||
|
/// <summary>Снять «инструмент с руки» (ПКМ).</summary>
|
||||||
|
public void Disarm()
|
||||||
|
{
|
||||||
|
_armedKind = null;
|
||||||
|
_armedTarget = null;
|
||||||
|
UpdateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Создаёт взведённую сущность в точке мира (вызывает система по клику ЛКМ).</summary>
|
||||||
|
public void SpawnAt(Vector2 world)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
_armedKind == "spawn-animal"
|
||||||
|
&& _content.Defs.TryGet<AnimalDef>(_armedTarget ?? "", out var adef)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var idx = _animals.IndexOf(adef);
|
||||||
|
var age = adef.Genome.GetValueOrDefault("GeneMaturityAge", 90f); // взрослый
|
||||||
|
AnimalFactory.Create(
|
||||||
|
_store,
|
||||||
|
_animals,
|
||||||
|
_needs,
|
||||||
|
_skills,
|
||||||
|
idx,
|
||||||
|
world,
|
||||||
|
age,
|
||||||
|
_animals.GenerateGenome(idx, _rng),
|
||||||
|
generation: 0,
|
||||||
|
_cellSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
_armedKind == "spawn-plant"
|
||||||
|
&& _content.Defs.TryGet<PlantDef>(_armedTarget ?? "", out var pdef)
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var idx = _plants.IndexOf(pdef);
|
||||||
|
var cx = Math.Clamp((int)(world.X / _cellSize), 0, _width - 1);
|
||||||
|
var cy = Math.Clamp((int)(world.Y / _cellSize), 0, _height - 1);
|
||||||
|
var cell = cy * _width + cx;
|
||||||
|
var fertility = cell >= 0 && cell < _cellFertility.Length ? _cellFertility[cell] : 1f;
|
||||||
|
PlantFactory.Create(
|
||||||
|
_store,
|
||||||
|
_plants,
|
||||||
|
idx,
|
||||||
|
world,
|
||||||
|
_plants[idx].MaturityDays, // зрелое
|
||||||
|
_plants[idx].Template.Generate(_rng),
|
||||||
|
fertility,
|
||||||
|
_cellSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяет инструмент-действие к выбранной особи (урон/смерть/болезнь) — для сравнения/тестов.
|
||||||
|
private void ActOnSelection(string kind, string target)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
!_selection.HasSelection
|
||||||
|
|| !_store.TryGetEntityById(_selection.EntityId, out var e)
|
||||||
|
|| e.IsNull
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (kind)
|
||||||
|
{
|
||||||
|
case "kill" when e.HasComponent<AnimalOrganism>():
|
||||||
|
AnimalFactory.Die(_store, _animals, e, _cellSize);
|
||||||
|
break;
|
||||||
|
case "damage" when e.TryGetComponent<Health>(out var h) && h.State is not null:
|
||||||
|
h.State.ApplyInjury(15f, 0.08f, _bleeding, 1.5f, _rng);
|
||||||
|
break;
|
||||||
|
case "infect" when e.TryGetComponent<Health>(out var h2) && h2.State is not null:
|
||||||
|
var def =
|
||||||
|
target.Length > 0 && _content.Defs.TryGet<HediffDef>(target, out var hd)
|
||||||
|
? hd
|
||||||
|
: _fever;
|
||||||
|
if (def is not null)
|
||||||
|
{
|
||||||
|
h2.State.Add(def);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Arm(string kind, string target)
|
||||||
|
{
|
||||||
|
_armedKind = kind;
|
||||||
|
_armedTarget = target;
|
||||||
|
UpdateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTitle() =>
|
||||||
|
_title.Text = _armedKind is null
|
||||||
|
? _content.Languages.Get("dev.hint")
|
||||||
|
: _content.Languages.Format("dev.armed", _armedTarget ?? "");
|
||||||
|
|
||||||
|
private static void Header(VerticalStackPanel list, string text) =>
|
||||||
|
list.Widgets.Add(new Label { Text = text, TextColor = Ui.Muted });
|
||||||
|
|
||||||
|
private static void Entry(VerticalStackPanel list, string text, Action onClick)
|
||||||
|
{
|
||||||
|
var button = new TextButton { Text = text };
|
||||||
|
button.Click += (_, _) => onClick();
|
||||||
|
list.Widgets.Add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string AnimalLabel(string defName) =>
|
||||||
|
_content.Defs.TryGet<AnimalDef>(defName, out var def) ? def.Label : defName;
|
||||||
|
|
||||||
|
private string PlantLabel(string defName) =>
|
||||||
|
_content.Defs.TryGet<PlantDef>(defName, out var def) ? def.Label : defName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Управление дев-спавнером: горячая клавиша F9 открывает/закрывает панель; при взведённом спавн-
|
||||||
|
/// инструменте ЛКМ по миру (не над UI) создаёт сущность в курсоре, ПКМ снимает инструмент.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DevSpawnerSystem(Renderer2D renderer, DevSpawner spawner, Func<bool> overUi)
|
||||||
|
: BaseSystem
|
||||||
|
{
|
||||||
|
private bool _prevToggle;
|
||||||
|
private bool _prevLeft;
|
||||||
|
private bool _prevRight;
|
||||||
|
|
||||||
|
protected override void OnUpdateGroup()
|
||||||
|
{
|
||||||
|
var keyboard = Keyboard.GetState();
|
||||||
|
var toggle = keyboard.IsKeyDown(Keys.F9);
|
||||||
|
if (toggle && !_prevToggle)
|
||||||
|
{
|
||||||
|
spawner.Toggle();
|
||||||
|
}
|
||||||
|
|
||||||
|
_prevToggle = toggle;
|
||||||
|
|
||||||
|
var mouse = Mouse.GetState();
|
||||||
|
var left = mouse.LeftButton == ButtonState.Pressed;
|
||||||
|
var right = mouse.RightButton == ButtonState.Pressed;
|
||||||
|
|
||||||
|
if (spawner.ArmedSpawn && left && !_prevLeft && !overUi())
|
||||||
|
{
|
||||||
|
var world = renderer.ScreenToWorld(new Vector2(mouse.X, mouse.Y));
|
||||||
|
spawner.SpawnAt(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (right && !_prevRight)
|
||||||
|
{
|
||||||
|
spawner.Disarm();
|
||||||
|
}
|
||||||
|
|
||||||
|
_prevLeft = left;
|
||||||
|
_prevRight = right;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,17 +31,26 @@ internal sealed class InspectPanel
|
|||||||
Genes,
|
Genes,
|
||||||
Products,
|
Products,
|
||||||
Needs,
|
Needs,
|
||||||
|
Skills,
|
||||||
Health,
|
Health,
|
||||||
Mood,
|
Mood,
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly Tab[] PlantTabs = [Tab.Overview, Tab.Genes, Tab.Products];
|
private static readonly Tab[] PlantTabs = [Tab.Overview, Tab.Genes, Tab.Products];
|
||||||
private static readonly Tab[] AnimalTabs = [Tab.Overview, Tab.Needs, Tab.Health, Tab.Mood];
|
private static readonly Tab[] AnimalTabs =
|
||||||
|
[
|
||||||
|
Tab.Overview,
|
||||||
|
Tab.Needs,
|
||||||
|
Tab.Skills,
|
||||||
|
Tab.Health,
|
||||||
|
Tab.Mood,
|
||||||
|
];
|
||||||
|
|
||||||
private readonly EntityStore _store;
|
private readonly EntityStore _store;
|
||||||
private readonly PlantSet _plants;
|
private readonly PlantSet _plants;
|
||||||
private readonly AnimalSet _animals;
|
private readonly AnimalSet _animals;
|
||||||
private readonly NeedSet _needs;
|
private readonly NeedSet _needs;
|
||||||
|
private readonly SkillSet _skills;
|
||||||
private readonly GameContent _content;
|
private readonly GameContent _content;
|
||||||
private readonly Climate _climate;
|
private readonly Climate _climate;
|
||||||
private readonly Selection _selection;
|
private readonly Selection _selection;
|
||||||
@@ -58,6 +67,7 @@ internal sealed class InspectPanel
|
|||||||
PlantSet plants,
|
PlantSet plants,
|
||||||
AnimalSet animals,
|
AnimalSet animals,
|
||||||
NeedSet needs,
|
NeedSet needs,
|
||||||
|
SkillSet skills,
|
||||||
GameContent content,
|
GameContent content,
|
||||||
Climate climate,
|
Climate climate,
|
||||||
Selection selection,
|
Selection selection,
|
||||||
@@ -68,6 +78,7 @@ internal sealed class InspectPanel
|
|||||||
_plants = plants;
|
_plants = plants;
|
||||||
_animals = animals;
|
_animals = animals;
|
||||||
_needs = needs;
|
_needs = needs;
|
||||||
|
_skills = skills;
|
||||||
_content = content;
|
_content = content;
|
||||||
_climate = climate;
|
_climate = climate;
|
||||||
_selection = selection;
|
_selection = selection;
|
||||||
@@ -81,6 +92,7 @@ internal sealed class InspectPanel
|
|||||||
AddTab(tabBar, Tab.Genes, "inspect.tab.genes");
|
AddTab(tabBar, Tab.Genes, "inspect.tab.genes");
|
||||||
AddTab(tabBar, Tab.Products, "inspect.tab.products");
|
AddTab(tabBar, Tab.Products, "inspect.tab.products");
|
||||||
AddTab(tabBar, Tab.Needs, "inspect.tab.needs");
|
AddTab(tabBar, Tab.Needs, "inspect.tab.needs");
|
||||||
|
AddTab(tabBar, Tab.Skills, "inspect.tab.skills");
|
||||||
AddTab(tabBar, Tab.Health, "inspect.tab.health");
|
AddTab(tabBar, Tab.Health, "inspect.tab.health");
|
||||||
AddTab(tabBar, Tab.Mood, "inspect.tab.mood");
|
AddTab(tabBar, Tab.Mood, "inspect.tab.mood");
|
||||||
|
|
||||||
@@ -246,6 +258,9 @@ internal sealed class InspectPanel
|
|||||||
case Tab.Needs:
|
case Tab.Needs:
|
||||||
BuildAnimalNeeds(text, entity);
|
BuildAnimalNeeds(text, entity);
|
||||||
break;
|
break;
|
||||||
|
case Tab.Skills:
|
||||||
|
BuildAnimalSkills(text, entity);
|
||||||
|
break;
|
||||||
case Tab.Health:
|
case Tab.Health:
|
||||||
BuildAnimalHealth(text, entity);
|
BuildAnimalHealth(text, entity);
|
||||||
break;
|
break;
|
||||||
@@ -291,6 +306,14 @@ internal sealed class InspectPanel
|
|||||||
|
|
||||||
text.AppendLine(languages.Format("inspect.age", grow.AgeDays, traits.Lifespan));
|
text.AppendLine(languages.Format("inspect.age", grow.AgeDays, traits.Lifespan));
|
||||||
|
|
||||||
|
// Вес растения (∝ кубу размера текущей стадии относительно зрелой).
|
||||||
|
var plantMass = Mass.OfPlant(
|
||||||
|
species.Def,
|
||||||
|
species.Stages[grow.Stage].SizeCells,
|
||||||
|
species.Stages[^1].SizeCells
|
||||||
|
);
|
||||||
|
text.AppendLine(languages.Format("inspect.plantmass", plantMass));
|
||||||
|
|
||||||
// Состояние по температуре (рост/покой) и накопленный стресс.
|
// Состояние по температуре (рост/покой) и накопленный стресс.
|
||||||
var (tMin, tLow, tHigh, tMax) = traits.TemperatureBand();
|
var (tMin, tLow, tHigh, tMax) = traits.TemperatureBand();
|
||||||
var temperature = _climate.Temperature;
|
var temperature = _climate.Temperature;
|
||||||
@@ -331,6 +354,13 @@ internal sealed class InspectPanel
|
|||||||
traits.LeafHue
|
traits.LeafHue
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
if (traits.Toxicity > 0.01f || traits.Thorns > 0.01f)
|
||||||
|
{
|
||||||
|
text.AppendLine(
|
||||||
|
languages.Format("inspect.gene.defense", traits.Toxicity, traits.Thorns)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (traits.IsVariant)
|
if (traits.IsVariant)
|
||||||
{
|
{
|
||||||
text.AppendLine(languages.Get("inspect.gene.variant"));
|
text.AppendLine(languages.Get("inspect.gene.variant"));
|
||||||
@@ -393,6 +423,16 @@ internal sealed class InspectPanel
|
|||||||
text.AppendLine(languages.Format("inspect.age", grow.AgeDays, org.Traits.Lifespan));
|
text.AppendLine(languages.Format("inspect.age", grow.AgeDays, org.Traits.Lifespan));
|
||||||
var sex = languages.Get(org.IsMale ? "inspect.sex.male" : "inspect.sex.female");
|
var sex = languages.Get(org.IsMale ? "inspect.sex.male" : "inspect.sex.female");
|
||||||
text.AppendLine(languages.Format("inspect.animal.sex", sex, org.Generation));
|
text.AppendLine(languages.Format("inspect.animal.sex", sex, org.Generation));
|
||||||
|
|
||||||
|
// Вес тела и грузоподъёмность (∝ массе × способность нести; четвероногие — через Moving).
|
||||||
|
var stageScale = sp.Stages[grow.Stage].Scale;
|
||||||
|
var mass = Mass.OfAnimal(sp.Def, org.Traits, stageScale);
|
||||||
|
var carryCap =
|
||||||
|
entity.TryGetComponent<Health>(out var health) && health.State is not null
|
||||||
|
? Mass.CarryCapacity(mass, health.State.Capacity(AnimalCapacities.Moving))
|
||||||
|
: Mass.CarryCapacity(mass, 1f);
|
||||||
|
text.AppendLine(languages.Format("inspect.mass", mass, carryCap));
|
||||||
|
|
||||||
if (entity.TryGetComponent<Pregnant>(out var preg))
|
if (entity.TryGetComponent<Pregnant>(out var preg))
|
||||||
{
|
{
|
||||||
text.AppendLine(languages.Format("inspect.animal.pregnant", preg.DueInDays));
|
text.AppendLine(languages.Format("inspect.animal.pregnant", preg.DueInDays));
|
||||||
@@ -421,6 +461,37 @@ internal sealed class InspectPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// — Животное: Навыки — уровень каждого навыка (%) и страсть (★/★★), растут от практики.
|
||||||
|
private void BuildAnimalSkills(StringBuilder text, Entity entity)
|
||||||
|
{
|
||||||
|
var languages = _content.Languages;
|
||||||
|
if (!entity.TryGetComponent<Skills>(out var sk) || sk.Levels is null)
|
||||||
|
{
|
||||||
|
text.AppendLine(languages.Get("inspect.health.none"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < _skills.Count && i < sk.Levels.Length; i++)
|
||||||
|
{
|
||||||
|
var passion =
|
||||||
|
sk.Passions is not null && i < sk.Passions.Length ? sk.Passions[i] : (byte)0;
|
||||||
|
var stars = passion switch
|
||||||
|
{
|
||||||
|
2 => "★★",
|
||||||
|
1 => "★",
|
||||||
|
_ => "",
|
||||||
|
};
|
||||||
|
text.AppendLine(
|
||||||
|
languages.Format(
|
||||||
|
"inspect.skillline",
|
||||||
|
languages.Get(_skills[i].Label),
|
||||||
|
sk.Levels[i] * 100f,
|
||||||
|
stars
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// — Животное: Здоровье — кровь/боль, ключевые способности, активные хедифы (раны/болезни/гон).
|
// — Животное: Здоровье — кровь/боль, ключевые способности, активные хедифы (раны/болезни/гон).
|
||||||
private void BuildAnimalHealth(StringBuilder text, Entity entity)
|
private void BuildAnimalHealth(StringBuilder text, Entity entity)
|
||||||
{
|
{
|
||||||
@@ -435,12 +506,27 @@ internal sealed class InspectPanel
|
|||||||
text.AppendLine(
|
text.AppendLine(
|
||||||
languages.Format("inspect.health.blood", state.BloodLevel * 100f, state.Pain * 100f)
|
languages.Format("inspect.health.blood", state.BloodLevel * 100f, state.Pain * 100f)
|
||||||
);
|
);
|
||||||
if (state.Parts.Length > 0)
|
if (state.Capacities.Count > 0)
|
||||||
{
|
{
|
||||||
text.AppendLine(languages.Get("inspect.health.caps"));
|
text.AppendLine(languages.Get("inspect.health.caps"));
|
||||||
AppendCapacity(text, state, AnimalCapacities.Consciousness, "cap.consciousness");
|
// Сначала известные способности в порядке отображения, затем прочие (модовые) — как есть.
|
||||||
AppendCapacity(text, state, AnimalCapacities.Moving, "cap.moving");
|
var shown = new HashSet<string>(StringComparer.Ordinal);
|
||||||
AppendCapacity(text, state, AnimalCapacities.Sight, "cap.sight");
|
foreach (var capId in AnimalCapacities.DisplayOrder)
|
||||||
|
{
|
||||||
|
if (state.Capacities.ContainsKey(capId))
|
||||||
|
{
|
||||||
|
AppendCapacity(text, state, capId, CapacityLabel(capId));
|
||||||
|
shown.Add(capId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var capId in state.Capacities.Keys)
|
||||||
|
{
|
||||||
|
if (shown.Add(capId))
|
||||||
|
{
|
||||||
|
AppendCapacity(text, state, capId, CapacityLabel(capId));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.Hediffs.Count > 0)
|
if (state.Hediffs.Count > 0)
|
||||||
@@ -463,6 +549,9 @@ internal sealed class InspectPanel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ключ локализации метки способности: cap.<id в нижнем регистре> (cap.consciousness, cap.bloodpumping…).
|
||||||
|
private static string CapacityLabel(string capId) => "cap." + capId.ToLowerInvariant();
|
||||||
|
|
||||||
private void AppendCapacity(
|
private void AppendCapacity(
|
||||||
StringBuilder text,
|
StringBuilder text,
|
||||||
HealthState state,
|
HealthState state,
|
||||||
|
|||||||
@@ -98,6 +98,16 @@ internal static class SettingsPanel
|
|||||||
// Громкость — кнопки −/+ с подписью процента.
|
// Громкость — кнопки −/+ с подписью процента.
|
||||||
column.Widgets.Add(Volume(lang.Get("settings.volume"), settings, refreshers));
|
column.Widgets.Add(Volume(lang.Get("settings.volume"), settings, refreshers));
|
||||||
|
|
||||||
|
// Режим разработчика — тумблер (дев-консоль и спавнер; по умолчанию включён).
|
||||||
|
column.Widgets.Add(
|
||||||
|
Toggle(
|
||||||
|
lang.Get("settings.devmode"),
|
||||||
|
() => settings.DeveloperMode,
|
||||||
|
v => settings.DeveloperMode = v,
|
||||||
|
refreshers
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
column.Widgets.Add(new Label { Height = 8 });
|
column.Widgets.Add(new Label { Height = 8 });
|
||||||
var buttons = Ui.Row(10);
|
var buttons = Ui.Row(10);
|
||||||
buttons.Widgets.Add(Ui.Button(lang.Get("settings.apply"), onApply, width: 150));
|
buttons.Widgets.Add(Ui.Button(lang.Get("settings.apply"), onApply, width: 150));
|
||||||
|
|||||||
Reference in New Issue
Block a user