Game/Idle/IdleSave.cs
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// Persistent state for Reactor (Idle) mode.
/// Serialized as a nested object inside PlayerSave — see PlayerSave.Idle.
/// Everything here survives a Meltdown except what ResetForMeltdown() clears.
/// </summary>
public class IdleSave
{
// ── Unlock / onboarding ───────────────────────────────────────
public bool Unlocked { get; set; } = false; // granted after the first finished run
public bool IntroShown { get; set; } = false; // first-entry explainer
public bool MeltdownSeen { get; set; } = false; // has the prestige panel ever been opened
/// <summary>
/// The guided walkthrough has run to the end, or been skipped. Separate from
/// IntroShown so an existing save — which already has IntroShown set and has
/// therefore never seen the walkthrough — still gets offered it once.
/// </summary>
public bool TutorialDone { get; set; } = false;
/// <summary>The walkthrough has been offered and declined; do not re-prompt.</summary>
public bool TutorialOffered { get; set; } = false;
// ── Currencies ────────────────────────────────────────────────
public double Cores { get; set; } = 0; // spendable, wiped by Meltdown
public double CycleCores { get; set; } = 0; // earned since last Meltdown — drives Flux payout
public double AllTimeCores { get; set; } = 0; // never resets
public int Flux { get; set; } = 0; // unspent prestige currency
public int FluxEarned { get; set; } = 0; // lifetime, for milestones
public int Meltdowns { get; set; } = 0;
public int LastReactorRank { get; set; } = 0; // 0 == never ranked; drives the "up N places" note
// ── Singularity (second prestige) ─────────────────────────────
public int Shards { get; set; } = 0; // unspent, buys Powers
public int ShardsEarned { get; set; } = 0; // lifetime — drives the passive multiplier
public int Singularities { get; set; } = 0;
public bool SingularitySeen { get; set; } = false; // has the collapse panel ever been opened
// ── Upgrade levels ────────────────────────────────────────────
// Keys are IdleUpgradeId.ToString(); missing key == level 0.
public Dictionary<string, int> Upgrades { get; set; } = new();
public Dictionary<string, int> Perks { get; set; } = new();
public Dictionary<string, int> Powers { get; set; } = new();
// ── Offline bookkeeping ───────────────────────────────────────
public long LastSeenUnix { get; set; } = 0;
public double MeasuredCps { get; set; } = 0; // smoothed cores/sec, used for offline payout
public double BestCps { get; set; } = 0;
// Pending offline haul, staged so the welcome-back panel can show it
public double PendingOfflineCores { get; set; } = 0;
public double PendingOfflineSeconds { get; set; } = 0;
public int PendingOfflineFrags { get; set; } = 0;
// ── Daily loop ────────────────────────────────────────────────
public string LastDailyDate { get; set; } = "";
public int DailyStreak { get; set; } = 0;
public bool DailyClaimed { get; set; } = false;
// ── Fragment drip (own bucket — does not touch the pet farm cap) ──
public int TodayReactorFragments { get; set; } = 0;
public string LastFragDate { get; set; } = "";
public double FragProgress { get; set; } = 0; // cores banked toward the next fragment
// ── Records / milestones ──────────────────────────────────────
public int TotalDetonations { get; set; } = 0;
public long TotalChestsBlown { get; set; } = 0;
public int BestChain { get; set; } = 0;
public double BestSingleBlast { get; set; } = 0;
public int GoldenGrabbed { get; set; } = 0;
public double SecondsRun { get; set; } = 0;
public List<string> Achievements { get; set; } = new();
// Counters that exist purely so Daily Contracts have something to watch.
// They are lifetime totals; a contract stores its own baseline at issue.
public int TotalOverloads { get; set; } = 0;
public int TotalSurges { get; set; } = 0;
public int TotalUpgradesBought { get; set; } = 0;
/// <summary>Longest chain since the contract set was last issued. Reset with it.</summary>
public int BestChainToday { get; set; } = 0;
// ── Daily contracts ───────────────────────────────────────────
// Parallel lists rather than a list of objects: the save is plain JSON and
// a schema change to a nested type would break every existing file.
public string ContractDate { get; set; } = "";
public List<string> ContractIds { get; set; } = new();
public List<double> ContractTargets { get; set; } = new();
public List<double> ContractBaseline { get; set; } = new();
public List<bool> ContractClaimed { get; set; } = new();
/// <summary>Consecutive days on which all three contracts were cleared.</summary>
public int ContractStreak { get; set; } = 0;
public string ContractStreakDate { get; set; } = "";
// ── Upgrade helpers ───────────────────────────────────────────
public int Level( IdleUpgradeId id )
=> Upgrades.TryGetValue( id.ToString(), out int v ) ? v : 0;
public void SetLevel( IdleUpgradeId id, int level )
=> Upgrades[id.ToString()] = Math.Max( 0, level );
public int PerkLevel( IdlePerkId id )
=> Perks.TryGetValue( id.ToString(), out int v ) ? v : 0;
public void SetPerkLevel( IdlePerkId id, int level )
=> Perks[id.ToString()] = Math.Max( 0, level );
public int PowerLevel( IdlePowerId id )
=> Powers != null && Powers.TryGetValue( id.ToString(), out int v ) ? v : 0;
public void SetPowerLevel( IdlePowerId id, int level )
{
Powers ??= new();
Powers[id.ToString()] = Math.Max( 0, level );
}
public bool HasAchievement( string id ) => Achievements.Contains( id );
// ── Meltdown (prestige) ───────────────────────────────────────
/// <summary>Wipes the run-scoped economy. Flux, perks, records and achievements stay.</summary>
public void ResetForMeltdown()
{
Cores = 0;
CycleCores = 0;
Upgrades.Clear();
Meltdowns++;
// Kickstart perk hands back a head start so the early grind shrinks each time
int kick = PerkLevel( IdlePerkId.Kickstart );
if ( kick > 0 )
{
SetLevel( IdleUpgradeId.ChestValue, kick * 6 );
SetLevel( IdleUpgradeId.SpawnRate, kick * 4 );
SetLevel( IdleUpgradeId.CycleSpeed, kick * 4 );
SetLevel( IdleUpgradeId.DroneCount, Math.Min( kick, 3 ) );
}
}
// ── Singularity (second prestige) ─────────────────────────────
/// <summary>
/// Collapses everything a Meltdown normally protects — Flux, lifetime Flux
/// and the whole perk tree. Shards, Powers, records, achievements, Fragments
/// and the pet survive; those are what carry the run forward.
///
/// Meltdowns is deliberately kept: the Reactor Log counts it, and wiping a
/// lifetime tally the player earned honestly reads as a bug, not a cost.
/// </summary>
public void ResetForSingularity()
{
Flux = 0;
FluxEarned = 0;
Perks.Clear();
// Kickstart is gone with the perks, so ResetForMeltdown lands us at a
// genuine level-zero board — which is the point of the layer.
ResetForMeltdown();
Meltdowns--; // ResetForMeltdown counts a Meltdown that did not happen
Singularities++;
}
// ── Day rollover for the fragment bucket ──────────────────────
public void EnsureFragDay()
{
string today = DateTime.UtcNow.ToString( "yyyy-MM-dd" );
if ( LastFragDate == today ) return;
LastFragDate = today;
TodayReactorFragments = 0;
}
public int FragmentsToday
{
get { EnsureFragDay(); return TodayReactorFragments; }
}
}