Feature/3d hud #18

Merged
Alexander merged 59 commits from feature/3d-hud into develop 2026-11-06 18:09:33 +03:00
11 changed files with 70 additions and 2 deletions
Showing only changes of commit cfbdfd9876 - Show all commits
+5
View File
@@ -18,6 +18,11 @@ MonoBehaviour:
startSoldiers: 30
foodPerCivilianPerHour: 0.1
foodPerSoldierPerHour: 0.2
foodPerTroopTypePerHour:
- 0.15
- 0.2
- 0.15
- 0.15
growthPerHour: 2.5
housingBase: 300
housingPerTownHallLevel: 50
+4 -1
View File
@@ -24,8 +24,11 @@ public class EconomyConfig : ScriptableObject
[Tooltip("Еды в час на одного жителя")]
public float foodPerCivilianPerHour = 0.1f;
[Tooltip("Еды в час на одного воина")]
[Tooltip("Еды в час на одного воина (и новобранца в Казармах), если норма по типам не задана")]
public float foodPerSoldierPerHour = 0.2f;
[Tooltip("Еды в час на воина по типам: пехота, лучники, кавалерия, осадные. Решения 01.11, п. 4: 0,15 / 0,20 / 0,15 / 0,15. " +
"Раненые едят так же, даже ожидая койку. Пусто -- все по foodPerSoldierPerHour")]
public float[] foodPerTroopTypePerHour = { 0.15f, 0.2f, 0.15f, 0.15f };
[Tooltip("Прирост людей в час, если есть еда (документ: 2–3)")]
public float growthPerHour = 2.5f;
+2
View File
@@ -209,6 +209,8 @@ public static class GameModules
StartSoldiers = p.startSoldiers,
FoodPerCivilianPerHour = p.foodPerCivilianPerHour,
FoodPerSoldierPerHour = p.foodPerSoldierPerHour,
FoodPerTroopTypePerHour = p.foodPerTroopTypePerHour != null && p.foodPerTroopTypePerHour.Length > 0
? System.Array.ConvertAll(p.foodPerTroopTypePerHour, f => (double)f) : null,
GrowthPerHour = p.growthPerHour,
HousingBase = p.housingBase,
HousingPerTownHallLevel = p.housingPerTownHallLevel,
@@ -238,6 +238,15 @@ public sealed class ArmyService : IArmy, ISaveParticipant
}
}
public int GetAllTroops(int type)
{
if (type < 0 || type >= Types) return 0;
int n = troops[type] + woundedWaiting[type];
foreach (var m in marches) n += m.units[type];
foreach (var b in healing) if (b.type == type) n += b.count;
return n;
}
public int GetWoundedWaiting(int type)
{
return type >= 0 && type < Types ? woundedWaiting[type] : 0;
@@ -150,6 +150,7 @@ public interface IArmy
// Бой считает раненых, Армия хранит их и лечит, места -- от госпиталя (IHospital), цену лечения списывает Экономика.
// Раненые -- люди: Население считает их вместе с воинами. Лишние раненые ждут места (ТЕСТ, решение Саши 01.11).
int WoundedTotal { get; } // раненых всего: ждут места + лечатся
int GetAllTroops(int type); // все воины типа: в городе, в походах и раненые (для еды -- решения 01.11, п. 4)
int GetWoundedWaiting(int type); // ждут места, по типам
IReadOnlyList<HealingInfo> Healing { get; } // лечатся сейчас
int HospitalBeds { get; } // мест в госпитале сейчас
@@ -12,6 +12,10 @@ public sealed class PopulationRules
// Еда в час
public double FoodPerCivilianPerHour = 0.1;
public double FoodPerSoldierPerHour = 0.2;
// Еда воина по типам войск: пехота, лучники, кавалерия, осадные (решения по отчёту Саши, 01.11, п. 4: 0,15 / 0,20 / 0,15 / 0,15).
// Раненые едят полную норму своего типа, даже ожидая койку. null -- все воины едят FoodPerSoldierPerHour (как раньше).
// Новобранцы в Казармах -- по FoodPerSoldierPerHour (их норма не задана).
public double[] FoodPerTroopTypePerHour = null;
// Прирост +2–3 человека в час, если есть еда
public double GrowthPerHour = 2.5;
@@ -182,7 +182,18 @@ public sealed class PopulationService : IPopulation, ISaveParticipant
double FoodPerHourExact(int soldiers)
{
// Обучающиеся в Казармах едят как воины
return civilians * rules.FoodPerCivilianPerHour + (soldiers + inTraining) * rules.FoodPerSoldierPerHour;
return civilians * rules.FoodPerCivilianPerHour + SoldiersFood(soldiers) + inTraining * rules.FoodPerSoldierPerHour;
}
// Воины (в городе, в походах и раненые) едят по норме своего типа, если она задана (решения 01.11, п. 4)
double SoldiersFood(int soldiers)
{
var table = rules.FoodPerTroopTypePerHour;
if (table == null || table.Length == 0 || !GameServices.TryGet(out IArmy army)) return soldiers * rules.FoodPerSoldierPerHour;
double food = 0;
for (int t = 0; t < army.TypeCount; t++)
food += army.GetAllTroops(t) * (t < table.Length ? table[t] : rules.FoodPerSoldierPerHour);
return food;
}
public int GetWorkers(WorkJob job)
@@ -1949,4 +1949,34 @@ public class ModuleTests
Assert.AreEqual(20, army.GetWoundedWaiting(0) + 10, "ещё 20 легли сразу после улучшения: ждут 10");
Assert.AreEqual(40, army.Healing[0].Count + army.Healing[1].Count);
}
// Решения 01.11, п. 4: воины и раненые едят по норме своего типа, раненые -- даже ожидая койку
[Test]
public void Population_FoodByTroopType_IncludesWounded()
{
var army = new ArmyService(2, new HospitalRules { TemporaryBeds = 0 }); // мест нет -- все раненые ждут
GameServices.Register<IArmy>(army);
GameServices.Register<IEconomy>(new PopulationTests.FakeEconomy());
GameServices.Register<ISettlement>(new PopulationTests.FakeSettlement { TownHallLevel = 1 });
var population = new PopulationService(new PopulationRules { FoodPerTroopTypePerHour = new[] { 0.15, 0.2, 0.15, 0.15 } });
GameServices.Register<IPopulation>(population);
double civilians = population.Civilians * 0.1;
army.AddTroops(0, 20); // пехота 20 x 0,15 = 3
army.AddTroops(1, 10); // лучники 10 x 0,20 = 2
army.AddTroops(3, 4); // осадные 4 x 0,15 = 0,6
Assert.AreEqual(civilians + 5.6, population.FoodPerHour, 0.001);
army.TakeForMarch(1, 14); // в походе -- тоже едят
Assert.AreEqual(civilians + 5.6, population.FoodPerHour, 0.001, "поход не меняет еду");
int[] sent = army.GetMarchUnits(1);
var wounded = new int[4];
for (int t = 0; t < 4; t++) wounded[t] = sent[t] / 2; // половина ранена, остальные погибли
army.ReturnUnits(1, null, wounded, 0);
Assert.AreEqual(0, army.Healing.Count, "коек нет -- раненые ждут");
double expected = 0;
for (int t = 0; t < 4; t++) expected += army.GetAllTroops(t) * new[] { 0.15, 0.2, 0.15, 0.15 }[t];
Assert.AreEqual(civilians + expected, population.FoodPerHour, 0.001, "ждущие койку раненые едят полную норму");
Assert.Less(expected, 5.6, "погибшие больше не едят");
}
}
+1
View File
@@ -148,6 +148,7 @@ healNext=<номер следующей группы лечения>
| Дата | Кто | Что |
| --- | --- | --- |
| 04.11.2026 | Claude | `IArmy.GetAllTroops(type)` — все воины типа: в городе, в походах и раненые (для еды по типам, решения 01.11, п. 4). |
| 04.11.2026 | Claude | Решения по отчёту Саши от 01.11, п. 3: 5 временных мест до постройки госпиталя, цена лечения 1 / 1 / 2 / 3 золота за воина. |
| 01.11.2026 | Claude | Здание «Госпиталь» в городе: уровни I–V, 20–60 мест, окно с ранеными и улучшением; места Армии — через `IHospital` (`HospitalBeds`). Без госпиталя мест нет (`temporaryBeds` = 0). Тест `Hospital_BedsFromBuilding_UpgradeFreesWaiting`. |
| 01.11.2026 | Claude | Решения 31.10, п. 7: раненые и лечение у Армии — `WoundedTotal`, `Healing`, `HospitalBeds`, `ReturnUnits(…, woundedUnits, nowMs)`, `ProcessHealing`, `SoldiersHealedEvent`; места от `IHospital` (пока нет здания — `HospitalConfig.temporaryBeds`), лишние ждут, цена через Экономику (не утверждена). Сохранение `wounded=`, `heal=`. Тесты: места и сроки, однократность, люди в Населении, сохранение и цена. |
+1
View File
@@ -112,6 +112,7 @@
| Дата | Кто | Что |
| --- | --- | --- |
| 04.11.2026 | Claude (Саша) | Решения по отчёту Саши от 01.11, п. 4: воины едят по норме типа — пехота 0,15, лучники 0,20, кавалерия 0,15, осадные 0,15 в час (`PopulationRules.FoodPerTroopTypePerHour`, `EconomyConfig.population.foodPerTroopTypePerHour`); раненые — полную норму своего типа, даже ожидая койку (`IArmy.GetAllTroops`). Новобранцы — по `foodPerSoldierPerHour`. Без таблицы — как раньше (тесты Населения не менялись). Час — тот же, что у остальной еды (переход еды на игровые часы — открытый вопрос). |
| 01.11.2026 | Claude (Саша) | Раненые считаются людьми: `Soldiers` = в городе + в походах + `IArmy.WoundedTotal` (решения 31.10, п. 7). Погибшие уходят из людей, вылеченные не появляются заново. Еда раненых — как у воинов (нормы четырёх групп — открытый вопрос). |
| 01.11.2026 | Claude (Саша) | Очередь обучения перенесена в Армию (решения 31.10, п. 8). Население по-прежнему отдаёт жителей (`TryRecruit`) и принимает новобранцев в воины (`CompleteTraining`) — его код не менялся, зовёт их теперь Армия. `RecruitsTrained` больше никто не вызывает. |
| 31.10.2026 | Claude | `CompleteTraining`: новобранцы переходят в Армию одним действием с откатом, если Армия не приняла. Заказ обучения помечает, брали ли под него людей (`TrainingOrder.recruited`); старые заказы — по-старому. Интеграционные тесты с настоящей Армией: переход без потерь, отказ Армии, повтор, нет Армии, часть заказа. |
+1
View File
@@ -105,6 +105,7 @@ EditMode-тесты: `Assets/Scripts/Modules/Tests/ModuleTests.cs`. Запуск
| Дата | Кто | Что |
| --- | --- | --- |
| 04.11.2026 | Claude | Еда воинов и раненых по типам (решения 01.11, п. 4). Обновлены Population, Army. |
| 04.11.2026 | Claude | Карта мира в 2.5D на примитивах. Обновлены World, Intelligence. |
| 04.11.2026 | Claude | 3D-поселение, этап I — прототип на примитивах (ТЗ от 04.11). Обновлён Settlement. |
| 04.11.2026 | Claude | Решения по отчёту Саши от 01.11: потери сильной стороны до 2% при 10:1, 5 временных мест госпиталя, цена лечения 1 / 1 / 2 / 3 золота. Обновлены Battle, Army. |