Files
MVP1/Assets/Scripts/App/EconomyConfig.cs
T
vologdia 1f0676ffc9 Merge remote-tracking branch 'origin/feature/3d-settlement' into test/3d-together
# Conflicts:
#	Docs/Modules/Core.md
#	Docs/Modules/Population.md
#	Docs/Modules/README.md
#	Docs/Modules/Settlement.md
2026-11-05 11:47:41 +03:00

330 lines
18 KiB
C#

using System;
using UnityEngine;
// ТАБЛИЦА БАЛАНСА ЭКОНОМИКИ. Все числа экономики -- здесь, а не в коде модулей.
//
// Ассет: Assets/Resources/EconomyConfig.asset (выдели его в окне Project и меняй числа в Inspector,
// перекомпиляция не нужна). Создать заново: правый клик в Project -> Create -> Bogatyri -> Economy Config,
// имя файла -- EconomyConfig, папка -- Resources.
//
// Модули конфиг не видят: App/GameModules переводит его в правила модулей (например, правила Населения).
// Числа -- из дизайн-документа. Помеченные "не утверждено" -- временные, их решает команда.
[CreateAssetMenu(fileName = "EconomyConfig", menuName = "Bogatyri/Economy Config")]
public class EconomyConfig : ScriptableObject
{
[Serializable]
public class PopulationSettings
{
[Tooltip("Стартовый город T1: всего людей")]
public int startTotal = 300;
[Tooltip("Из них рабочих")]
public int startWorkers = 120;
[Tooltip("Из них воинов (выдаются в Армию пехотой при новой игре)")]
public int startSoldiers = 30;
[Tooltip("Еды в час на одного жителя")]
public float foodPerCivilianPerHour = 0.1f;
[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;
[Tooltip("Предел населения без Домов на 1 уровне Ратуши (решение команды от 19.10: 300 при 300 -- прирост стоит)")]
public int housingBase = 300;
[Tooltip("Прибавка к пределу за каждый следующий уровень Ратуши. Не утверждено")]
public int housingPerTownHallLevel = 50;
[Tooltip("Прибавка к пределу за каждый уровень Дома (здание \"Население\"). Не утверждено")]
public int housingPerHouseLevel = 50;
[Tooltip("Офлайн-прогресс считается не больше чем за столько часов. Не утверждено")]
public int maxOfflineHours = 48;
[Tooltip("Гражданский резерв по уровню Ратуши: [0] -- уровень 1, [1] -- уровень 2... Столько свободных жителей нельзя забрать в войско. Регламент 31.10, ТЕСТ: 1-2 -- 10, 3-5 -- 20, 6-10 -- 30")]
public int[] civilianReserveByTownHallLevel = { 10, 10, 20, 20, 20, 30, 30, 30, 30, 30 };
}
[Serializable]
public class ProductionSettings // этап 3: производство через рабочих
{
[Tooltip("Лесоруб T1: дерева в час")]
public float woodcutterPerHour = 2f;
[Tooltip("Фермер T1: еды в час")]
public float farmerPerHour = 2f;
[Tooltip("Каменотёс T1: камня в час")]
public float stonecutterPerHour = 1.5f;
[Tooltip("Шахтёр T1: металла в час")]
public float minerIronPerHour = 1f;
[Tooltip("Шахтёр T1: золота в час")]
public float minerGoldPerHour = 0.4f;
[Tooltip("Мест для рабочих в здании T1 (Лесопилка и т.п.)")]
public int workerSlotsT1 = 30;
[Tooltip("+ мест за каждый уровень здания выше первого. Не утверждено")]
public int workerSlotsPerLevel = 10;
[Tooltip("+ к норме за каждый уровень здания выше первого (0.1 = +10%). Не утверждено")]
public float efficiencyPerLevel = 0.1f;
[Tooltip("Голод: множитель добычи всех ресурсов, кроме еды (0.5 -- вдвое меньше). Еда добывается без штрафа. Регламент 31.10, ТЕСТ")]
[Range(0f, 1f)] public float famineNonFoodMultiplier = 0.5f;
}
[Serializable]
public class StorageSettings // этап 1: лимиты хранения
{
[Tooltip("Лимит без Складов: дерево, камень, металл, золото. Не утверждено")]
public float baseGoods = 1000f;
[Tooltip("Лимит еды без Амбаров. Не утверждено")]
public float baseFood = 1500f;
[Tooltip("+ к лимиту дерева, камня, металла, золота за каждый уровень Склада. Не утверждено")]
public float perWarehouseLevel = 1000f;
[Tooltip("+ к лимиту еды за каждый уровень Амбара. Не утверждено")]
public float perGranaryLevel = 1500f;
[Tooltip("Лимит синего кристалла (решение команды от 19.10: небольшой, не бесконечный). Число не утверждено")]
public float crystalCap = 50f;
[Tooltip("Цена Склада: дерево / камень. Не утверждено")]
public float warehouseWood = 200f;
public float warehouseStone = 100f;
[Tooltip("Цена Амбара: дерево / камень. Не утверждено")]
public float granaryWood = 150f;
public float granaryStone = 50f;
}
[Serializable]
public class UpgradeSettings // улучшение зданий (рабочие места, Дома, Склады, Амбары)
{
[Tooltip("Цена улучшения с 1 на 2 уровень: дерево / камень. Не утверждено")]
public float baseWood = 150f;
public float baseStone = 100f;
[Tooltip("Во сколько раз дорожает каждый следующий уровень. Не утверждено")]
public float multiplierPerLevel = 1.6f;
public int maxLevel = 10;
}
[Serializable]
public class DepositSettings // этап 4: качество месторождения (пока не используется)
{
public float poor = 0.6f;
public float normal = 1f;
public float rich = 1.5f;
}
[Serializable]
public class SoldierSettings // этап 5: цена солдата
{
[Tooltip("Сколько жителей стоит один воин (берутся из жителей при начале обучения)")]
public int people = 1;
public float food = 15f;
public float iron = 5f;
public float gold = 5f;
[Tooltip("Время подготовки, секунд. Не утверждено")]
public int trainSeconds = 60;
[Tooltip("Уровень Казарм, с которого открывается тип войск (порядок как в GameData.Troops: пехота, лучники, кавалерия, осадные). " +
"Нужен и уровень Ратуши. Считаются самые прокачанные Казармы. ТЕСТ, не утверждено")]
public int[] requiredBarracksLevel = { 1, 2, 3, 5 };
}
[Serializable]
public class ConstructionSettings // время стройки и улучшения, строители (01.11, ТЕСТ)
{
[Tooltip("Сколько строек и улучшений идёт одновременно (строители). ТЕСТ")]
public int builders = 2;
[Tooltip("Постройка нового здания, секунд. ТЕСТ")]
public int buildSeconds = 60;
[Tooltip("Улучшение до уровня 2, 3, 4... (секунд): [0] -- до 2 ур., [1] -- до 3 ур. ... ТЕСТ")]
public int[] upgradeSecondsToLevel = { 60, 120, 300, 600, 1200, 1800, 3600, 5400, 7200 };
[Tooltip("Ратуша строится дольше остальных: множитель времени улучшения. ТЕСТ")]
public float townHallTimeMultiplier = 2f;
[Tooltip("Ускорение: секунд стройки за 1 кристалл. ТЕСТ")]
public int secondsPerGem = 30;
// Сноса зданий нет (решение 04.11): только бесплатный перенос
}
[Serializable]
public class BuildingLimit
{
public string buildingName;
[Tooltip("Сколько таких зданий можно иметь на Ратуше 1, 2, 3 ... уровня. 0 -- нельзя")]
public int[] maxByTownHall;
public BuildingLimit() { }
public BuildingLimit(string name, params int[] max) { buildingName = name; maxByTownHall = max; }
}
[Serializable]
public class ProtectionSettings // защита ресурсов от грабежа (01.11, ТЕСТ)
{
[Tooltip("Защищено без Складов: дерево, камень, металл, золото. ТЕСТ")]
public float baseGoods = 100f;
[Tooltip("+ к защите дерева, камня, металла, золота за каждый уровень Склада. ТЕСТ")]
public float perWarehouseLevel = 200f;
[Tooltip("Защищено без Амбаров: еда. ТЕСТ")]
public float baseFood = 100f;
[Tooltip("+ к защите еды за каждый уровень Амбара. ТЕСТ")]
public float perGranaryLevel = 300f;
[Tooltip("Кристаллы: без технологии «Грабёж» нападающий не уносит ничего (решение 04.11). С ней -- не больше этой доли запаса. ТЕСТ, точные ограничения определят отдельно")]
[Range(0f, 1f)] public float plunderGemsShare = 0.1f;
}
[Serializable]
public class StartResources
{
[Tooltip("Ресурсы новой игры. Не утверждено")]
public float wood = 500f;
public float food = 500f;
public float stone = 200f;
public float iron = 100f;
public float gold = 100f;
public float gems = 0f;
}
[Header("Население (этап 2)")]
public PopulationSettings population = new PopulationSettings();
[Header("Стартовые ресурсы новой игры")]
public StartResources startResources = new StartResources();
[Header("Склады и лимиты (этап 1)")]
public StorageSettings storage = new StorageSettings();
[Header("Производство T1 (этап 3)")]
public ProductionSettings production = new ProductionSettings();
[Header("Улучшение зданий")]
public UpgradeSettings upgrade = new UpgradeSettings();
[Header("Месторождения (этап 4)")]
public DepositSettings deposits = new DepositSettings();
[Header("Солдат T1 (этап 5)")]
public SoldierSettings soldier = new SoldierSettings();
[Header("Стройка: время, строители, снос (ТЕСТ)")]
public ConstructionSettings construction = new ConstructionSettings();
[Header("Лимит зданий по уровню Ратуши (ТЕСТ). Здания не из списка -- без лимита")]
public BuildingLimit[] buildingLimits =
{
// Ратуша: 1 2 3 4 5 6 7 8 9 10
new BuildingLimit("Лес", 2, 3, 3, 4, 4, 5, 5, 6, 6, 7),
new BuildingLimit("Еда", 2, 3, 3, 4, 4, 5, 5, 6, 6, 7),
new BuildingLimit("Камень", 2, 2, 3, 3, 4, 4, 5, 5, 6, 6),
new BuildingLimit("Металл", 0, 0, 1, 1, 2, 2, 3, 3, 4, 4),
new BuildingLimit("Золото", 0, 1, 1, 2, 2, 3, 3, 3, 4, 4),
new BuildingLimit("Население", 3, 4, 5, 6, 7, 8, 9, 10, 11, 12),
new BuildingLimit("Склад", 1, 1, 2, 2, 2, 3, 3, 3, 4, 4),
new BuildingLimit("Амбар", 1, 1, 2, 2, 2, 3, 3, 3, 4, 4),
new BuildingLimit("Казармы", 1, 1, 2, 2, 3, 3, 3, 3, 3, 3),
new BuildingLimit("Госпиталь", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
new BuildingLimit("Терем богатыря", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1),
new BuildingLimit("Кристалл", 0, 0, 0, 0, 0, 1, 1, 1, 1, 1),
};
[Header("Защита ресурсов от грабежа (ТЕСТ)")]
public ProtectionSettings protection = new ProtectionSettings();
// ---------------- Загрузка ----------------
static EconomyConfig cached;
// Конфиг из Assets/Resources/EconomyConfig.asset. Нет ассета -- числа по умолчанию (из этого файла) и предупреждение.
public static EconomyConfig Load()
{
if (cached != null) return cached;
cached = Resources.Load<EconomyConfig>("EconomyConfig");
if (cached == null)
{
Debug.LogWarning("[ЭКОНОМИКА] Нет Assets/Resources/EconomyConfig.asset -- берутся числа по умолчанию из EconomyConfig.cs.");
cached = CreateInstance<EconomyConfig>();
}
return cached;
}
// Лимит склада для ресурса по уровням Складов и Амбаров. PositiveInfinity -- без лимита (люди, сила)
public float GetCapacity(ResourceType type, int warehouseLevels, int granaryLevels)
{
var s = storage;
switch (type)
{
case ResourceType.Wood:
case ResourceType.Stone:
case ResourceType.Iron:
case ResourceType.Gold: return s.baseGoods + s.perWarehouseLevel * warehouseLevels;
case ResourceType.Food: return s.baseFood + s.perGranaryLevel * granaryLevels;
case ResourceType.Gems: return s.crystalCap;
default: return float.PositiveInfinity;
}
}
// Сколько зданий с таким именем можно иметь на этом уровне Ратуши. -1 -- без лимита (здания нет в списке)
public int BuildingLimitFor(string buildingName, int townHallLevel)
{
if (buildingLimits == null || string.IsNullOrEmpty(buildingName)) return -1;
foreach (var l in buildingLimits)
{
if (l == null || l.buildingName != buildingName || l.maxByTownHall == null || l.maxByTownHall.Length == 0) continue;
return Mathf.Max(0, l.maxByTownHall[Mathf.Clamp(townHallLevel, 1, l.maxByTownHall.Length) - 1]);
}
return -1;
}
// Время стройки (targetLevel = 1) или улучшения до targetLevel, мс. Ратуша -- с множителем
public long ConstructionMs(bool townHall, int targetLevel)
{
var c = construction;
double seconds;
if (targetLevel <= 1) seconds = c.buildSeconds;
else
{
var t = c.upgradeSecondsToLevel;
seconds = t == null || t.Length == 0 ? c.buildSeconds : t[Mathf.Clamp(targetLevel - 2, 0, t.Length - 1)];
}
if (townHall) seconds *= Mathf.Max(0.01f, c.townHallTimeMultiplier);
return (long)(Math.Max(0, seconds) * 1000.0);
}
// Сколько ресурса не отнять при грабеже: часть запаса, которую прячут Склады и Амбары
public float GetProtected(ResourceType type, int warehouseLevels, int granaryLevels)
{
var p = protection;
switch (type)
{
case ResourceType.Wood:
case ResourceType.Stone:
case ResourceType.Iron:
case ResourceType.Gold: return p.baseGoods + p.perWarehouseLevel * warehouseLevels;
case ResourceType.Food: return p.baseFood + p.perGranaryLevel * granaryLevels;
case ResourceType.Gems: return float.PositiveInfinity; // без «Грабежа» кристаллы не грабятся (с ним -- ResourceProtection)
default: return 0f;
}
}
// Цена улучшения здания с уровня level на level + 1
public ResourceAmount[] UpgradeCost(int level)
{
float m = Mathf.Pow(upgrade.multiplierPerLevel, Mathf.Max(0, level - 1));
return new[]
{
new ResourceAmount(ResourceType.Wood, Mathf.Round(upgrade.baseWood * m)),
new ResourceAmount(ResourceType.Stone, Mathf.Round(upgrade.baseStone * m)),
};
}
public float GetStartResource(ResourceType type)
{
var s = startResources;
switch (type)
{
case ResourceType.Wood: return s.wood;
case ResourceType.Food: return s.food;
case ResourceType.Stone: return s.stone;
case ResourceType.Iron: return s.iron;
case ResourceType.Gold: return s.gold;
case ResourceType.Gems: return s.gems;
default: return 0f;
}
}
}