- CrystalRewards (без Unity) + 6 тестов из раздела 6 регламента - Задания: сколько помещается, остаток ждёт в задании («Забрать»); третья не помещающаяся награда не выдаётся - Остальные награды: не поместившееся -- письмом «Кристаллы ждут места», из письма -- сколько помещается - Награды заданий 1-2 вместо 20-250, ежедневные -- без кристаллов Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
80 lines
4.4 KiB
C#
80 lines
4.4 KiB
C#
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<string, int> waiting = new Dictionary<string, int>();
|
|
|
|
public int WaitingCount => waiting.Count;
|
|
public IEnumerable<KeyValuePair<string, int>> 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<string> ToLines()
|
|
{
|
|
var lines = new List<string>();
|
|
foreach (var pair in waiting) lines.Add(pair.Key + "|" + pair.Value.ToString(CultureInfo.InvariantCulture));
|
|
return lines;
|
|
}
|
|
|
|
public void Load(IEnumerable<string> 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;
|
|
}
|
|
}
|
|
}
|