using System; using System.Collections.Generic; using System.Globalization; // СИНИЕ КРИСТАЛЛЫ ПРИ ПРЕДЕЛЕ ХРАНИЛИЩА (Регламент редкости синих кристаллов от 05.11.2026, ред. 1.1, раздел 1). // Документ: Docs/Modules/Economy.md, раздел «Синие кристаллы». // // - Начислить сверх предела нельзя, «тихо потерять» излишек -- тоже нельзя. // - Награда, которая не помещается, выдаётся частично (сколько есть места), остаток ждёт («Можно забрать») // и выдаётся позже -- не сгорает. Повторное «Забрать», загрузка, перезапуск не выдают кристалл дважды: // каждая награда -- по своему id, остаток хранится вместе с id. // - Одновременно ждут не больше MaxWaiting наград. Пока оба места заняты, новая награда, которая не помещается // целиком, не выдаётся совсем (задание остаётся «Забрать») -- ничего не теряется. // Без Unity: те же правила будут на сервере и в тестах. Сколько кристаллов есть и предел -- даёт вызывающий. public sealed class CrystalRewards { public const int MaxWaiting = 2; readonly Dictionary waiting = new Dictionary(); public int WaitingCount => waiting.Count; public IEnumerable> Waiting => waiting; // Сколько ещё помещается: целые кристаллы до предела. Предела нет -- сколько угодно public static int Free(float have, float cap) { if (float.IsInfinity(cap) || float.IsNaN(cap)) return int.MaxValue; return Math.Max(0, (int)Math.Floor(cap - have + 0.0001f)); } public static int Fits(int amount, float have, float cap) => Math.Max(0, Math.Min(amount, Free(have, cap))); // Сколько кристаллов этой награды ещё ждут места (0 -- не ждут) public int Left(string id) => id != null && waiting.TryGetValue(id, out int n) ? n : 0; // Можно ли начать выдачу новой награды: поместится целиком, уже ждёт или есть свободное место ожидания public bool CanStart(string id, int amount, float have, float cap) { if (amount <= 0 || Fits(amount, have, cap) >= amount) return true; return Left(id) > 0 || waiting.Count < MaxWaiting; } // Выдать награду id: возвращает, сколько кристаллов начислить сейчас; остаток ждёт под этим id. // Если награда уже ждёт -- выдаётся её остаток (amount не важен: награда одна, повтор её не увеличит). // Перед новой наградой вызывающий проверяет CanStart public int Claim(string id, int amount, float have, float cap) { if (id == null) id = ""; int need = waiting.TryGetValue(id, out int left) ? left : Math.Max(0, amount); if (need <= 0) return 0; int give = Fits(need, have, cap); if (need - give > 0) waiting[id] = need - give; else waiting.Remove(id); return give; } // ---------------- Сохранение: строки "id|сколько" ---------------- public List ToLines() { var lines = new List(); foreach (var pair in waiting) lines.Add(pair.Key + "|" + pair.Value.ToString(CultureInfo.InvariantCulture)); return lines; } public void Load(IEnumerable lines) { waiting.Clear(); if (lines == null) return; foreach (var line in lines) { if (string.IsNullOrEmpty(line)) continue; int bar = line.LastIndexOf('|'); if (bar <= 0) continue; if (int.TryParse(line.Substring(bar + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out int n) && n > 0) waiting[line.Substring(0, bar)] = n; } } }