Files
AlexanderandClaude Opus 5.5 157d3ba510 Кристальная шахта: 3 синих кристалла за 7 реальных суток на шахту, до двух, пауза при полном хранилище (ответ 06.11, п. 3)
- CrystalMine (без Unity) + 5 тестов: ровно 3 за 7 суток, ×12 не действует, 49/50 -> 1, пауза, перезагрузка и часы назад без двойного начисления, офлайн-стройка
- Работает с конца стройки, офлайн досчитывается; вне недельного PvE-предела
- Вторая шахта -- Ратуша 9 (ТЕСТ); окно здания: следующий кристалл / пауза
- DEV «Ресурсы 100 000» -- обычные ресурсы мимо лимита склада

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 16:31:47 +01:00

84 lines
5.0 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
// КРИСТАЛЬНАЯ ШАХТА: выработка синих кристаллов (Регламент редкости синих кристаллов от 05.11.2026, разд. 2.1;
// Регламент валют от 05.11, разд. 5.2 и 8.1; «Ответ Володе» от 06.11, п. 3). Документ: Docs/Modules/Economy.md.
//
// - Каждая работающая шахта -- PerPeriod (ТЕСТ 3) кристаллов за PeriodMs (7 РЕАЛЬНЫХ суток; не игровые часы ×12),
// равномерно: один кристалл за PeriodMs / PerPeriod. Шахты (до двух) складываются.
// - Шахта работает с момента, когда достроена (activeFromMs): стройка, закончившаяся офлайн, добывает с её конца.
// - Хранилище полное -- пауза: прогресс не растёт, скрытого запаса нет. Хранилище заполнилось в ходе расчёта --
// шахта встаёт сразу после последнего поместившегося кристалла (прогресс 0). Освободилось место -- выработка идёт
// дальше с оставшегося прогресса, без начисления за время паузы.
// - Офлайн: досчёт по меткам при входе; уже посчитанное время второй раз не начисляется; часы назад (сброс
// DEV-перемотки) -- метка не уходит назад, ничего не начисляется.
// - В недельный PvE-предел не входит (отдельный учёт).
// Без Unity: те же правила будут на сервере и в тестах.
public sealed class CrystalMine
{
public readonly int PerPeriod;
public readonly long PeriodMs;
// Прогресс к следующему кристаллу (мс работы одной шахты) и до какого момента посчитано
public long ProgressMs { get; private set; }
public long LastMs { get; private set; }
public CrystalMine(int perPeriod = 3, long periodMs = 7 * CrystalQuota.Day)
{
PerPeriod = Math.Max(1, perPeriod);
PeriodMs = Math.Max(1, periodMs);
}
public long MsPerCrystal => Math.Max(1, PeriodMs / PerPeriod);
// Досчитать до nowMs. mineActiveFromMs -- по шахте: с какого момента она работает (long.MinValue -- давно).
// freeSpace -- сколько кристаллов помещается в хранилище. Возвращает, сколько кристаллов начислить
public int Advance(long nowMs, IEnumerable<long> mineActiveFromMs, int freeSpace)
{
if (LastMs == 0) { LastMs = nowMs; return 0; } // первый расчёт: отсюда и считаем
if (nowMs <= LastMs) return 0; // часы назад или то же время -- ничего
long from = LastMs;
LastMs = nowMs;
if (freeSpace <= 0) return 0; // пауза: хранилище полное, прогресс не растёт
long work = 0;
if (mineActiveFromMs != null)
foreach (long active in mineActiveFromMs)
{
long start = Math.Max(from, active);
if (start < nowMs) work += nowMs - start;
}
if (work <= 0) return 0;
long total = ProgressMs + work;
long made = total / MsPerCrystal;
if (made <= freeSpace)
{
ProgressMs = total - made * MsPerCrystal;
return (int)made;
}
ProgressMs = 0; // хранилище заполнилось: шахта встала сразу после последнего кристалла, лишнее не копится
return freeSpace;
}
// Через сколько мс следующий кристалл при count работающих шахтах (0 шахт -- -1)
public long MsToNext(int count) => count <= 0 ? -1 : Math.Max(0, (MsPerCrystal - ProgressMs + count - 1) / count);
public void Load(string line)
{
ProgressMs = 0; LastMs = 0;
if (string.IsNullOrEmpty(line)) return;
var p = line.Split('|');
if (p.Length >= 2
&& long.TryParse(p[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out long progress)
&& long.TryParse(p[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out long last))
{
ProgressMs = Math.Max(0, Math.Min(progress, MsPerCrystal - 1));
LastMs = Math.Max(0, last);
}
}
public string ToLine() => ProgressMs.ToString(CultureInfo.InvariantCulture) + "|" + LastMs.ToString(CultureInfo.InvariantCulture);
}