Хранилище синих кристаллов: платное расширение 50 -> 75/100/150/200 на Ратуше 3/5/7/9 (регламент валют, разд. 8)

- CrystalStorage (без Unity) + 5 тестов из раздела 12
- Ступени и цены -- EconomyConfig.crystalExpansions (ТЕСТ), купленная ступень -- в секции economy
- Окно Ратуши: «Хранилище синих кристаллов», «Расширить до N»; цена списывается один раз

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alexander
2026-11-06 16:13:43 +01:00
co-authored by Claude Opus 5.5
parent 98cbe85481
commit a2d85f56c3
7 changed files with 277 additions and 5 deletions
@@ -429,3 +429,88 @@ public class CrystalRewardsTests
Assert.AreEqual(10, CrystalRewards.Fits(10, 0, float.PositiveInfinity), "без предела -- целиком");
}
}
// Хранилище синих кристаллов: платное расширение по уровню Ратуши (регламент валют 05.11, разд. 8 и 12)
public class CrystalStorageTests
{
static CrystalStorage Storage() => new CrystalStorage(50, new[]
{
new CrystalStorage.Tier { TownHallLevel = 3, Capacity = 75, Cost = new[] { new ResourceAmount(ResourceType.Gold, 2000) } },
new CrystalStorage.Tier { TownHallLevel = 5, Capacity = 100, Cost = new[] { new ResourceAmount(ResourceType.Gold, 4000) } },
new CrystalStorage.Tier { TownHallLevel = 7, Capacity = 150, Cost = new[] { new ResourceAmount(ResourceType.Gold, 7000) } },
new CrystalStorage.Tier { TownHallLevel = 9, Capacity = 200, Cost = new[] { new ResourceAmount(ResourceType.Gold, 10000) } },
});
[Test]
public void Storage_NotAutomatic_TownHallOnlyAllows()
{
var s = Storage();
Assert.AreEqual(50, s.Capacity, "старт 50");
int spent = 0;
Assert.IsFalse(s.TryExpand(2, c => { spent++; return true; }), "Ратуша 2 -- расширения нет");
Assert.AreEqual(0, spent, "ничего не списано");
Assert.AreEqual(50, s.Capacity, "Ратуша 3 сама вместимость не меняет -- только разрешает");
Assert.IsNull(s.WhyNot(3));
}
[Test]
public void Storage_50to75_PaidOnce_AndKeptAfterSaveLoad()
{
var s = Storage();
float gold = 2500;
System.Func<IReadOnlyList<ResourceAmount>, bool> pay = cost =>
{
float need = 0;
foreach (var c in cost) if (c.Type == ResourceType.Gold) need += c.Amount;
if (gold < need) return false;
gold -= need;
return true;
};
Assert.IsTrue(s.TryExpand(3, pay));
Assert.AreEqual(75, s.Capacity, "50 -> 75 после оплаты");
Assert.AreEqual(500, gold, 0.01, "списано один раз");
Assert.IsFalse(s.TryExpand(3, pay), "повторное нажатие на Ратуше 3 -- следующая ступень ещё закрыта");
Assert.AreEqual(500, gold, 0.01, "второй раз не списано");
var loaded = Storage();
loaded.SetLevel(s.Level);
Assert.AreEqual(75, loaded.Capacity, "после сохранения и загрузки -- 75");
}
[Test]
public void Storage_NotEnoughResources_NothingChanges()
{
var s = Storage();
Assert.IsFalse(s.TryExpand(9, c => false), "не хватило -- списание не прошло");
Assert.AreEqual(0, s.Level);
Assert.AreEqual(50, s.Capacity);
}
[Test]
public void Storage_TiersInOrder_UpTo200()
{
var s = Storage();
for (int i = 0; i < 4; i++) Assert.IsTrue(s.TryExpand(10, c => true));
Assert.AreEqual(200, s.Capacity);
Assert.IsNull(s.Next);
Assert.IsFalse(s.TryExpand(10, c => true), "дальше 200 -- нет");
s.SetLevel(99);
Assert.AreEqual(4, s.Level, "лишние ступени из сохранения обрезаются");
}
[Test]
public void Storage_Waiting_ClaimedOnceAfterExpansion()
{
// 49/50 + допустимая награда 2 -> 1 сразу и 1 ждёт; после расширения ждущий забирается один раз
var s = Storage();
var r = new CrystalRewards();
float have = 49;
have += r.Claim("q", 2, have, s.Capacity);
Assert.AreEqual(50, have, 0.01);
Assert.AreEqual(1, r.Left("q"));
s.TryExpand(3, c => true);
have += r.Claim("q", 2, have, s.Capacity);
Assert.AreEqual(51, have, 0.01, "после расширения выдан ждущий 1");
Assert.AreEqual(0, r.Left("q"), "больше ничего не ждёт -- выдан ровно один раз");
}
}