From fe02e8722577948d006265a5a2e26375b663f615 Mon Sep 17 00:00:00 2001 From: Alexander Date: Fri, 6 Nov 2026 16:00:31 +0100 Subject: [PATCH] =?UTF-8?q?=D0=A1=D0=B8=D0=BD=D0=B8=D0=B5=20=D0=BA=D1=80?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B0=D0=BB=D0=BB=D1=8B=20=D0=BF=D0=BE=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B3=D0=BB=D0=B0=D0=BC=D0=B5=D0=BD=D1=82=D1=83=20?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=D1=8E=D1=82=2005.11:=20=D1=83=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D1=8B=20=D1=81=D1=82=D0=B0=D1=80=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B4=D0=B0=D1=87=D0=B8,=20=D0=BC=D0=B0?= =?UTF-8?q?=D0=B3=D0=B0=D0=B7=D0=B8=D0=BD=20=D0=B7=D0=B0=20=D0=B7=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D1=82=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Кристаллы только: верхняя ступень недельного события (1), каждый 10-й этап боя (1) -- ТЕСТ - Задания, вход, письма, подарок за уровень, арена, сундук -- золото и ресурсы - Магазин: пакеты за золото; убраны пакеты за кристаллы, обмен золота на кристаллы, «Горсть» и «Сундук» Co-Authored-By: Claude Opus 5.5 --- Assets/Scripts/Meta/Core/GameData.cs | 64 +++++++++++++----------- Assets/Scripts/Meta/Core/GameProgress.cs | 8 +-- Docs/Modules/Economy.md | 23 ++++++--- 3 files changed, 53 insertions(+), 42 deletions(-) diff --git a/Assets/Scripts/Meta/Core/GameData.cs b/Assets/Scripts/Meta/Core/GameData.cs index e03efee..59f3c45 100644 --- a/Assets/Scripts/Meta/Core/GameData.cs +++ b/Assets/Scripts/Meta/Core/GameData.cs @@ -123,8 +123,9 @@ public static class GameData public const int MaxPlayerLevel = 60; // ---------- Основные задания (цепочка) ---------- - // Синие кристаллы (регламент 05.11, разд. 3): простое задание -- 1, сложное -- 2 (было 20-250). В ежедневных - // кристаллов нет: кристальное задание -- не чаще раза в 7-14 дней. Числа ТЕСТ + // Синие кристаллы (Регламент валют, кристаллов и торговли от 05.11, разд. 5.1): стандартные задания дают золото и + // обычные ресурсы, не синие кристаллы (было 20-250, 05.11 -- 1-2). Кристаллы -- только сложная недельная цепочка + // (ещё не сделана). Числа ТЕСТ public static readonly List MainQuests = new List { new QuestDef("m_collect_wood_1", "Собрать 200 дерева", QuestKind.Collect, 200, 30, R(ResourceType.Gold, 200)).Res(ResourceType.Wood), @@ -132,21 +133,21 @@ public static class GameData new QuestDef("m_build_1", "Построить первое здание", QuestKind.BuildingsBuilt, 1, 50, R(ResourceType.Wood, 300), R(ResourceType.Stone, 200)), new QuestDef("m_barracks", "Построить Казармы", QuestKind.BarracksBuilt, 1, 50, R(ResourceType.Food, 300), R(ResourceType.Wood, 200)), new QuestDef("m_train_10", "Обучить 10 воинов", QuestKind.TroopsTrained, 10, 50, R(ResourceType.Food, 400)), - new QuestDef("m_battle_1", "Выиграть первый бой", QuestKind.BattlesWon, 1, 60, R(ResourceType.Gems, 1)), + new QuestDef("m_battle_1", "Выиграть первый бой", QuestKind.BattlesWon, 1, 60, R(ResourceType.Gold, 300)), new QuestDef("m_world_send", "Отправить армию в поход на карте мира", QuestKind.WorldMarches, 1, 60, R(ResourceType.Food, 500)), - new QuestDef("m_world_win", "Разграбить поселение на карте мира", QuestKind.WorldWins, 1, 100, R(ResourceType.Gems, 2), R(ResourceType.Gold, 500)), + new QuestDef("m_world_win", "Разграбить поселение на карте мира", QuestKind.WorldWins, 1, 100, R(ResourceType.Gold, 1000)), new QuestDef("m_hero_3", "Герой 3 уровня", QuestKind.HeroLevel, 3, 60, R(ResourceType.Gold, 500)), - new QuestDef("m_th_2", "Ратуша 2 уровня", QuestKind.TownHallLevel, 2, 100, R(ResourceType.Gems, 1), R(ResourceType.Stone, 500)), + new QuestDef("m_th_2", "Ратуша 2 уровня", QuestKind.TownHallLevel, 2, 100, R(ResourceType.Gold, 500), R(ResourceType.Stone, 500)), new QuestDef("m_build_3", "Построить 3 здания", QuestKind.BuildingsBuilt, 3, 80, R(ResourceType.Wood, 600), R(ResourceType.Iron, 200)), - new QuestDef("m_arena_1", "Сразиться на арене", QuestKind.ArenaFights, 1, 60, R(ResourceType.Gems, 1)), + new QuestDef("m_arena_1", "Сразиться на арене", QuestKind.ArenaFights, 1, 60, R(ResourceType.Gold, 300)), new QuestDef("m_collect_stone_1", "Собрать 1000 камня", QuestKind.Collect, 1000, 80, R(ResourceType.Gold, 800)).Res(ResourceType.Stone), - new QuestDef("m_train_100", "Обучить 100 воинов", QuestKind.TroopsTrained, 100, 120, R(ResourceType.Food, 1500), R(ResourceType.Gems, 1)), - new QuestDef("m_battle_5", "Выиграть 5 боёв", QuestKind.BattlesWon, 5, 150, R(ResourceType.Gems, 1)), - new QuestDef("m_player_5", "Достигнуть 5 уровня", QuestKind.PlayerLevel, 5, 100, R(ResourceType.Gems, 1)), - new QuestDef("m_th_3", "Ратуша 3 уровня", QuestKind.TownHallLevel, 3, 200, R(ResourceType.Gems, 2), R(ResourceType.Iron, 800)), + new QuestDef("m_train_100", "Обучить 100 воинов", QuestKind.TroopsTrained, 100, 120, R(ResourceType.Food, 1500), R(ResourceType.Gold, 500)), + new QuestDef("m_battle_5", "Выиграть 5 боёв", QuestKind.BattlesWon, 5, 150, R(ResourceType.Gold, 800)), + new QuestDef("m_player_5", "Достигнуть 5 уровня", QuestKind.PlayerLevel, 5, 100, R(ResourceType.Gold, 1000)), + new QuestDef("m_th_3", "Ратуша 3 уровня", QuestKind.TownHallLevel, 3, 200, R(ResourceType.Gold, 1500), R(ResourceType.Iron, 800)), new QuestDef("m_hero_10", "Герой 10 уровня", QuestKind.HeroLevel, 10, 200, R(ResourceType.Gold, 3000)), - new QuestDef("m_battle_15", "Выиграть 15 боёв", QuestKind.BattlesWon, 15, 300, R(ResourceType.Gems, 2)), - new QuestDef("m_th_5", "Ратуша 5 уровня", QuestKind.TownHallLevel, 5, 400, R(ResourceType.Gems, 2)), + new QuestDef("m_battle_15", "Выиграть 15 боёв", QuestKind.BattlesWon, 15, 300, R(ResourceType.Gold, 2000)), + new QuestDef("m_th_5", "Ратуша 5 уровня", QuestKind.TownHallLevel, 5, 400, R(ResourceType.Gold, 3000)), }; // ---------- Ежедневные задания (сбрасываются каждый день) ---------- @@ -214,7 +215,10 @@ public static class GameData }; } public static int StageXp(int stage) { return 20 + stage * 6; } - public static int StageGems(int stage) { return stage % 5 == 0 ? 25 : 0; } // каждый 5-й этап -- кристаллы + // Синие кристаллы за бой -- только за сильных противников, редко (регламент валют 05.11, разд. 5.1): каждый + // StageGemsEvery-й этап -- StageGemsAmount (было: каждый 5-й -- 25). ТЕСТ + public const int StageGemsEvery = 10, StageGemsAmount = 1; + public static int StageGems(int stage) { return stage % StageGemsEvery == 0 ? StageGemsAmount : 0; } // ---------- Арена ---------- public const int ArenaMaxTickets = 5; @@ -234,43 +238,41 @@ public static class GameData } // ---------- Ежедневная награда за вход (7 дней) ---------- + // Без синих кристаллов (регламент валют 05.11, разд. 5.1; было 50 на 4-й и 200 на 7-й день). ТЕСТ public static readonly RewardItem[][] LoginRewards = { new[] { R(ResourceType.Gold, 500) }, new[] { R(ResourceType.Food, 1000) }, new[] { R(ResourceType.Wood, 800), R(ResourceType.Stone, 500) }, - new[] { R(ResourceType.Gems, 50) }, + new[] { R(ResourceType.Gold, 1000) }, new[] { R(ResourceType.Iron, 600) }, new[] { R(ResourceType.Gold, 2000) }, - new[] { R(ResourceType.Gems, 200) }, + new[] { R(ResourceType.Gold, 3000), R(ResourceType.Stone, 1000) }, }; // ---------- Магазин ---------- public static readonly List ShopItems = BuildShop(); static List BuildShop() { + // Обычный магазин -- за золото: основные ресурсы и хозяйственные товары (регламент валют 05.11, разд. 2). + // Убраны: пакеты за 50-80 синих кристаллов (больше хранилища), «Мешок золота» за кристаллы, обмен золота на + // кристаллы (будет у редкого PvE-торговца: 30 000 / 60 000 золота, не больше 2 за 7 дней), «Горсть» (500) и + // «Сундук» (3000) кристаллов за деньги (за деньги будут космо-кристаллы). Цены ТЕСТ var list = new List { - new ShopItemDef("free_chest", "Бесплатный сундук (раз в день)", ResourceType.Gems, 0, 1), - new ShopItemDef("wood_1k", "Пакет дерева", ResourceType.Gems, 50, 5, R(ResourceType.Wood, 1000)), - new ShopItemDef("stone_1k", "Пакет камня", ResourceType.Gems, 50, 5, R(ResourceType.Stone, 1000)), - new ShopItemDef("food_15", "Пакет еды", ResourceType.Gems, 50, 5, R(ResourceType.Food, 1500)), - new ShopItemDef("iron_500", "Пакет железа", ResourceType.Gems, 60, 5, R(ResourceType.Iron, 500)), - new ShopItemDef("gold_1k", "Мешок золота", ResourceType.Gems, 80, 3, R(ResourceType.Gold, 1000)), - new ShopItemDef("gems_10", "Обмен золота на кристаллы", ResourceType.Gold, 1000, 3, R(ResourceType.Gems, 10)), + new ShopItemDef("free_chest", "Бесплатный сундук (раз в день)", ResourceType.Gold, 0, 1), + new ShopItemDef("wood_1k", "Пакет дерева", ResourceType.Gold, 600, 5, R(ResourceType.Wood, 1000)), + new ShopItemDef("stone_1k", "Пакет камня", ResourceType.Gold, 700, 5, R(ResourceType.Stone, 1000)), + new ShopItemDef("food_15", "Пакет еды", ResourceType.Gold, 600, 5, R(ResourceType.Food, 1500)), + new ShopItemDef("iron_500", "Пакет железа", ResourceType.Gold, 900, 5, R(ResourceType.Iron, 500)), }; - var p1 = new ShopItemDef("iap_small", "Горсть кристаллов", ResourceType.Gems, 0, 0, R(ResourceType.Gems, 500)); - p1.comingSoon = true; p1.priceLabel = "99 руб."; - var p2 = new ShopItemDef("iap_big", "Сундук кристаллов", ResourceType.Gems, 0, 0, R(ResourceType.Gems, 3000)); - p2.comingSoon = true; p2.priceLabel = "499 руб."; - list.Add(p1); list.Add(p2); return list; } // Содержимое бесплатного сундука (выбирается случайно) public static readonly RewardItem[] FreeChestPool = { R(ResourceType.Wood, 500), R(ResourceType.Stone, 400), R(ResourceType.Food, 600), - R(ResourceType.Iron, 250), R(ResourceType.Gold, 400), R(ResourceType.Gems, 15), + R(ResourceType.Iron, 250), R(ResourceType.Gold, 400), // без синих кристаллов (было 15; регламент валют 05.11) }; // ---------- Еженедельное событие ---------- @@ -282,9 +284,11 @@ public static class GameData { new[] { R(ResourceType.Gold, 500) }, new[] { R(ResourceType.Food, 1500), R(ResourceType.Wood, 1000) }, - new[] { R(ResourceType.Gems, 50) }, + new[] { R(ResourceType.Gold, 1500), R(ResourceType.Stone, 1000) }, new[] { R(ResourceType.Iron, 1500), R(ResourceType.Gold, 2000) }, - new[] { R(ResourceType.Gems, 200) }, + // Верхняя ступень недельного события -- сложная недельная цель: 1 синий кристалл (было 50 и 200; + // регламент валют 05.11, разд. 5.1 -- «сложная недельная цепочка: 1»). ТЕСТ + new[] { R(ResourceType.Gold, 3000), R(ResourceType.Gems, 1) }, }; // ---------- Текст наград ---------- diff --git a/Assets/Scripts/Meta/Core/GameProgress.cs b/Assets/Scripts/Meta/Core/GameProgress.cs index 0804102..15e39f7 100644 --- a/Assets/Scripts/Meta/Core/GameProgress.cs +++ b/Assets/Scripts/Meta/Core/GameProgress.cs @@ -434,10 +434,10 @@ public class GameProgress : MonoBehaviour { Data.xp -= GameData.XpToNextLevel(Data.level); Data.level++; - int gems = 10 * Data.level; + // Подарок за уровень -- золото (было ещё 10 × уровень синих кристаллов; регламент валют 05.11, разд. 5.1) SendMail("Новый уровень: " + Data.level + "!", "Поздравляем, командир! Ваша слава растёт. Примите подарок от совета королевства.", - new RewardItem(ResourceType.Gems, gems), new RewardItem(ResourceType.Gold, 300 * Data.level)); + new RewardItem(ResourceType.Gold, 300 * Data.level)); UIKit.Toast("Новый уровень: " + Data.level + "! Подарок в почте."); Vibrate(); } @@ -602,7 +602,7 @@ public class GameProgress : MonoBehaviour SendMail("Добро пожаловать, командир!", "Королевство ждёт своего правителя. Примите стартовый подарок, чтобы начать строительство.", new RewardItem(ResourceType.Gold, 500), new RewardItem(ResourceType.Wood, 500), - new RewardItem(ResourceType.Food, 500), new RewardItem(ResourceType.Gems, 100)); + new RewardItem(ResourceType.Food, 500)); // без 100 синих кристаллов (регламент валют 05.11) } public int UnreadMailCount @@ -990,7 +990,7 @@ public class GameProgress : MonoBehaviour if (res.win) { res.ratingChange = 15 + Mathf.Clamp((opp.rating - Data.arenaRating) / 10, 0, 15) + UnityEngine.Random.Range(0, 6); - res.rewards = new[] { new RewardItem(ResourceType.Gold, 150), new RewardItem(ResourceType.Gems, 5) }; + res.rewards = new[] { new RewardItem(ResourceType.Gold, 200) }; // без 5 синих кристаллов (регламент валют 05.11) Data.arenaWins++; Grant(res.rewards, 15, false); } diff --git a/Docs/Modules/Economy.md b/Docs/Modules/Economy.md index 06259b1..ea66a2f 100644 --- a/Docs/Modules/Economy.md +++ b/Docs/Modules/Economy.md @@ -88,16 +88,22 @@ | `App/BuildingWindow.cs` | Окно здания и рабочих. | | `App/StorageBuilding.cs`, `App/StorageBootstrap.cs` | Склад и Амбар. | -## Синие кристаллы при пределе 50 (регламент 05.11.2026, ред. 1.1) +## Синие кристаллы -Предел — строго 50, не повышается. Правила — `Modules/Economy/Rules/CrystalRewards.cs` (без Unity), тесты — `CrystalRewardsTests`. +Действующие документы: «Регламент валют, кристаллов и торговли» от 05.11.2026 и «Ответ Володе» от 06.11.2026 — они приоритетнее «Регламента редкости синих кристаллов» от 05.11 (ред. 1.1). Правила выдачи — `Modules/Economy/Rules/CrystalRewards.cs` (без Unity), тесты — `CrystalRewardsTests`. -- **Не сверх предела и без потерь.** Кристаллы, которым нет места, не начисляются и не пропадают. -- **Задания.** Выдаётся, сколько помещается; остаток ждёт в задании (в окне заданий — «Ждут места: N кристаллов», кнопка «Забрать»), не сгорает. Одновременно ждут не больше **2** наград; пока оба места заняты, задание, кристаллы которого не помещаются целиком, не выдаётся совсем — остаётся «Забрать». Сохраняется в `progress.json`: `crystalWaiting` («m:<задание>|сколько»). -- **Остальные награды** (вход, события, бои, почта уровня): то, что не поместилось, приходит письмом «Кристаллы ждут места». Из письма забирается, сколько помещается, остаток остаётся в письме. -- **Числа заданий (разд. 3):** простое — 1 кристалл, сложное — 2 (было 20–250). В ежедневных заданиях кристаллов нет (было 10) — вместо них 200 золота. ТЕСТ. -- **Ещё не сделано:** Кристальная шахта (3 за 7 реальных суток), недельный PvE-предел 6, точки на карте, торговля, грабёж с пределом получателя. -- **Вопросы команде:** источники, которых нет в регламенте, — награды за вход (50 и 200), письмо-приветствие (100), подарок за уровень (10 × уровень), события, обмен золота на кристаллы и покупки в магазине. Сейчас они не теряют кристаллы, но дают их много. +**Сделано:** +- **Не сверх вместимости и без потерь.** Задания: сколько помещается, остаток ждёт в задании (не больше 2 ждущих наград). Остальное — письмом «Кристаллы ждут места», из письма забирается сколько помещается. Сохранение — `progress.json`, `crystalWaiting`. +- **Источники (06.11, разд. 5.1 регламента валют).** Синие кристаллы дают только: + - верхняя ступень недельного события — 1 (`GameData.EventTierRewards`); + - каждый 10-й этап боя — 1 (`StageGemsEvery`, `StageGemsAmount`). + + Всё ТЕСТ. Без кристаллов теперь: задания (основные и ежедневные), награды за вход (было 50 и 200), письмо-приветствие (было 100), подарок за уровень (было 10 × уровень), арена (было 5), этапы боя (было 25 на каждом 5-м), бесплатный сундук (было 15). Вместо них — золото и ресурсы. +- **Магазин — за золото** (пакеты дерева, камня, еды, железа: 600 / 700 / 600 / 900 золота, ТЕСТ). Убраны пакеты за 50–80 кристаллов, «Мешок золота», обмен золота на кристаллы, «Горсть» (500) и «Сундук» (3000) кристаллов. + +**Ещё не сделано (после вливания `feature/3d-hud` в `develop`):** вместимость 50 → 75 / 100 / 150 / 200 с платным расширением на Ратуше 3 / 5 / 7 / 9 (сейчас константа `crystalCap` 50); недельный PvE-предел 6 с заменой излишка золотом; редкий PvE-торговец (30 000 / 60 000 золота, не больше 2 за 7 суток); Кристальная шахта (до двух, 3 за 7 реальных суток, пауза при полном хранилище); космо-кристаллы; торговля и военная добыча — этап мультиплеера. + +**Открыто:** цены расширения хранилища, компенсация за кристалл сверх недельного предела, уровень Ратуши для второй шахты, ускорения за кристаллы (обучение — 1 кристалл за 30 с, стройка) — в регламенте среди трат не названы. ## Таблица баланса `EconomyConfig` @@ -120,6 +126,7 @@ | Дата | Кто | Что | | --- | --- | --- | +| 06.11.2026 | Claude (Саша) | Регламент валют 05.11, разд. 2 и 5.1: синие кристаллы только за верхнюю ступень недельного события (1) и каждый 10-й этап боя (1); задания, вход, письма, уровень, арена, сундук — золото и ресурсы; магазин за золото, убраны пакеты за кристаллы, обмен золота на кристаллы, «Горсть» и «Сундук». | | 05.11.2026 | Claude (Саша) | Регламент синих кристаллов, пп. 1 и 3: кристаллы не начисляются сверх 50 и не теряются — награды заданий ждут (не больше 2), остальные — письмом «Кристаллы ждут места», из письма забирается сколько помещается (`CrystalRewards`, `GameProgress.Grant/ClaimQuest/ClaimMail`, 6 тестов). Задания — 1–2 кристалла вместо 20–250, ежедневные — без кристаллов. | | 04.11.2026 | Claude | Решения 04.11: кристаллы грабятся только нападающим с технологией «Грабёж» (`IResourceProtection.Lootable(type, attackerHasPlunder)`; с ней — не больше `plunderGemsShare` запаса, ТЕСТ 10%, точные ограничения — позже). Защита остальных ресурсов без изменений. Из `EconomyConfig` убран возврат при сносе. Офлайн: добыча здания, достроенного в офлайне, — с момента завершения; уже посчитанное время второй раз не начисляется (тесты в `ProductionTests`). | | 01.11.2026 | Claude | `EconomyConfig`: разделы «Стройка» (время, строители, ускорение, возврат при сносе), «Лимит зданий по уровню Ратуши», «Защита ресурсов от грабежа» — всё ТЕСТ. Защита: без Складов 100 каждого ресурса, +200 за уровень Склада; еда 100, +300 за уровень Амбара; кристаллы не грабятся. Контракт `IResourceProtection` (`App/ResourceProtection`), в окне Склада и Амбара — «спрятано / можно унести». |