Game/Idle/IdleUpgradeCatalog.cs
using System;
using System.Collections.Generic;
using System.Linq;
public enum IdleUpgradeCategory { Reactor, Chains, Systems }
public enum IdleUpgradeId
{
// Reactor — how much material the board produces
SpawnRate,
SpawnCount,
ChestValue,
GemChance,
BombChestChance,
GridSize,
// Chains — how hard the drones hit
DroneCount,
CycleSpeed,
BlastSize,
ChainPower,
CritChance,
CritPower,
// Systems — offline, manual play, meta payout
OfflineEfficiency,
OfflineCap,
OverloadCooldown,
ChargeGain,
GoldenChestRate,
FragmentYield,
}
public enum IdlePerkId
{
CoreEfficiency,
Kickstart,
DeepStorage,
NightShift,
FragmentCondenser,
ChainTheory,
AutoOverload,
Momentum,
PetLink,
Containment,
}
/// <summary>
/// Singularity powers — bought with Shards, survive every Meltdown AND every
/// Singularity. This is the layer above Flux perks: where perks multiply the
/// numbers, powers change the shape of a cycle.
/// </summary>
public enum IdlePowerId
{
Overclock,
Cascade,
TierSkip,
TwinReactor,
ChainMemory,
FluxCatalyst,
}
public class IdleUpgrade
{
public IdleUpgradeId Id { get; set; }
public IdleUpgradeCategory Category { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Desc { get; set; }
public double BaseCost { get; set; }
public double CostGrowth { get; set; }
public int MaxLevel { get; set; }
/// <summary>Cores needed to buy the level after <paramref name="level"/>.</summary>
public double CostAt( int level ) => BaseCost * Math.Pow( CostGrowth, level );
/// <summary>Human readable effect at a given level, e.g. "1.8 /s".</summary>
public Func<int, string> Effect { get; set; }
/// <summary>Cores this upgrade only becomes visible at — keeps the early list short.</summary>
public double RevealAt { get; set; } = 0;
}
public class IdlePerk
{
public IdlePerkId Id { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Desc { get; set; }
public int BaseCost { get; set; }
public int CostStep { get; set; }
public int MaxLevel { get; set; }
/// <summary>Has no real ceiling — the level readout drops the "/ max" half.</summary>
public bool Uncapped { get; set; }
/// <summary>Only offered once the reactor has reached this lifetime Flux.</summary>
public int RevealAtFluxEarned { get; set; }
public int CostAt( int level ) => BaseCost + CostStep * level;
public Func<int, string> Effect { get; set; }
}
public class IdlePower
{
public IdlePowerId Id { get; set; }
public string Name { get; set; }
public string Icon { get; set; }
public string Desc { get; set; }
public int BaseCost { get; set; }
public int CostStep { get; set; }
public int MaxLevel { get; set; }
public int CostAt( int level ) => BaseCost + CostStep * level;
public Func<int, string> Effect { get; set; }
}
public static class IdleUpgradeCatalog
{
// ── Core upgrades — bought with Cores, wiped on Meltdown ───────────────
public static readonly List<IdleUpgrade> All = new()
{
// ───────────── Reactor ─────────────
new IdleUpgrade {
Id = IdleUpgradeId.ChestValue, Category = IdleUpgradeCategory.Reactor,
Name = "Dense Loot", Icon = "🎁",
Desc = "Every chest is worth more Cores.",
BaseCost = 30, CostGrowth = 1.175, MaxLevel = 300,
Effect = lvl => $"×{IdleBalance.ChestValueMult( lvl ):F2} value",
},
new IdleUpgrade {
Id = IdleUpgradeId.SpawnRate, Category = IdleUpgradeCategory.Reactor,
Name = "Feed Line", Icon = "⚙️",
Desc = "Ambient chest drip between detonations.",
BaseCost = 120, CostGrowth = 1.60, MaxLevel = 40,
Effect = lvl => $"every {IdleBalance.SpawnInterval( lvl ):F2}s",
},
new IdleUpgrade {
Id = IdleUpgradeId.SpawnCount, Category = IdleUpgradeCategory.Reactor,
Name = "Bulk Hopper", Icon = "📦",
Desc = "Chests dumped onto the board right before every detonation. Chain length lives here.",
BaseCost = 1000, CostGrowth = 4.00, MaxLevel = 12,
Effect = lvl => $"{IdleBalance.SpawnCount( lvl )} per cycle",
RevealAt = 500,
},
new IdleUpgrade {
Id = IdleUpgradeId.GemChance, Category = IdleUpgradeCategory.Reactor,
Name = "Gem Seeding", Icon = "💎",
Desc = "Gem chests are worth 4× a normal chest.",
BaseCost = 2400, CostGrowth = 1.70, MaxLevel = 30,
Effect = lvl => $"{IdleFormat.Pct( IdleBalance.GemChance( lvl ) )} gems",
RevealAt = 500,
},
new IdleUpgrade {
Id = IdleUpgradeId.BombChestChance, Category = IdleUpgradeCategory.Reactor,
Name = "Volatile Cargo", Icon = "💣",
Desc = "Bomb chests detonate on death and extend chains.",
BaseCost = 6000, CostGrowth = 1.75, MaxLevel = 30,
Effect = lvl => $"{IdleFormat.Pct( IdleBalance.BombChestChance( lvl ) )} bomb chests",
RevealAt = 1600,
},
new IdleUpgrade {
Id = IdleUpgradeId.GridSize, Category = IdleUpgradeCategory.Reactor,
Name = "Reactor Frame", Icon = "🔲",
Desc = "Expands the reactor board. Everything you own scales with it.",
BaseCost = 500000, CostGrowth = 120, MaxLevel = 4,
Effect = lvl => $"{IdleBalance.GridSize( lvl )}×{IdleBalance.GridSize( lvl )} board",
RevealAt = 40000,
},
// ───────────── Chains ─────────────
new IdleUpgrade {
Id = IdleUpgradeId.CycleSpeed, Category = IdleUpgradeCategory.Chains,
Name = "Fuse Timing", Icon = "⏱️",
Desc = "Drones detonate more often.",
BaseCost = 240, CostGrowth = 1.60, MaxLevel = 40,
Effect = lvl => $"every {IdleBalance.CycleInterval( lvl ):F2}s",
},
new IdleUpgrade {
Id = IdleUpgradeId.DroneCount, Category = IdleUpgradeCategory.Chains,
Name = "Drone Bay", Icon = "🛰️",
Desc = "More bombs placed per detonation cycle — widens how much of the board you cover.",
BaseCost = 3000, CostGrowth = 8.0, MaxLevel = 9,
Effect = lvl => $"{IdleBalance.DroneCount( lvl )} drones",
RevealAt = 600,
},
new IdleUpgrade {
Id = IdleUpgradeId.ChainPower, Category = IdleUpgradeCategory.Chains,
Name = "Chain Theory", Icon = "⛓️",
Desc = "Steepens the chain multiplier. This is where the real numbers come from.",
BaseCost = 10000, CostGrowth = 3.20, MaxLevel = 25,
Effect = lvl => $"exponent {IdleBalance.ChainExponent( lvl, 0 ):F2}",
RevealAt = 3000,
},
new IdleUpgrade {
Id = IdleUpgradeId.BlastSize, Category = IdleUpgradeCategory.Chains,
Name = "Warhead Class", Icon = "💥",
Desc = "Upgrades the drone payload pattern.",
BaseCost = 50000, CostGrowth = 40, MaxLevel = 4,
Effect = lvl => IdleBalance.BlastName( lvl ),
RevealAt = 8000,
},
new IdleUpgrade {
Id = IdleUpgradeId.CritChance, Category = IdleUpgradeCategory.Chains,
Name = "Overpressure", Icon = "🎲",
Desc = "Chance for a detonation to crit.",
BaseCost = 5000, CostGrowth = 1.75, MaxLevel = 30,
Effect = lvl => $"{IdleFormat.Pct( IdleBalance.CritChance( lvl ) )} crit",
RevealAt = 1200,
},
new IdleUpgrade {
Id = IdleUpgradeId.CritPower, Category = IdleUpgradeCategory.Chains,
Name = "Critical Mass", Icon = "☢️",
Desc = "Crits pay out far harder.",
BaseCost = 10000, CostGrowth = 1.85, MaxLevel = 30,
Effect = lvl => $"×{IdleBalance.CritPower( lvl ):F0} on crit",
RevealAt = 2400,
},
// ───────────── Systems ─────────────
new IdleUpgrade {
Id = IdleUpgradeId.OverloadCooldown, Category = IdleUpgradeCategory.Systems,
Name = "Manual Override", Icon = "🔴",
Desc = "Shorter cooldown on the OVERLOAD button.",
BaseCost = 1000, CostGrowth = 1.70, MaxLevel = 20,
Effect = lvl => $"{IdleBalance.OverloadCooldown( lvl ):F1}s cooldown",
},
new IdleUpgrade {
Id = IdleUpgradeId.ChargeGain, Category = IdleUpgradeCategory.Systems,
Name = "Capacitor", Icon = "🔋",
Desc = "Overloads fill the Surge meter faster.",
BaseCost = 4000, CostGrowth = 1.90, MaxLevel = 20,
Effect = lvl => $"+{IdleFormat.Pct( IdleBalance.ChargeGain( lvl, 0 ) )} per overload",
RevealAt = 1200,
},
new IdleUpgrade {
Id = IdleUpgradeId.OfflineEfficiency, Category = IdleUpgradeCategory.Systems,
Name = "Night Crew", Icon = "🌙",
Desc = "Share of your output the reactor keeps producing while you are on another screen.",
BaseCost = 10000, CostGrowth = 2.60, MaxLevel = 14,
Effect = lvl => $"{IdleFormat.Pct( IdleBalance.OfflineEfficiency( lvl, 0 ) )} while unattended",
RevealAt = 3000,
},
new IdleUpgrade {
Id = IdleUpgradeId.OfflineCap, Category = IdleUpgradeCategory.Systems,
Name = "Core Storage", Icon = "🛢️",
Desc = "How many hours of unattended production the tanks can hold.",
BaseCost = 16000, CostGrowth = 3.00, MaxLevel = 10,
Effect = lvl => $"{IdleBalance.OfflineCapHours( lvl, 0 ):F0}h stored",
RevealAt = 4000,
},
new IdleUpgrade {
Id = IdleUpgradeId.GoldenChestRate, Category = IdleUpgradeCategory.Systems,
Name = "Lucky Manifest", Icon = "🌟",
Desc = "Golden Chests appear more often. Click them before they leave.",
BaseCost = 12000, CostGrowth = 2.30, MaxLevel = 15,
Effect = lvl => $"every ~{IdleBalance.GoldenInterval( lvl ):F0}s",
RevealAt = 3000,
},
new IdleUpgrade {
Id = IdleUpgradeId.FragmentYield, Category = IdleUpgradeCategory.Systems,
Name = "Condenser Coil", Icon = "💠",
Desc = "Cheapens every Fragment condensed today — worth several extra Fragments per level.",
BaseCost = 50000, CostGrowth = 3.20, MaxLevel = 12,
Effect = lvl => $"first 💠 at {IdleFormat.Short( IdleBalance.FragmentCost( lvl, 0, 0 ) )}",
RevealAt = 8000,
},
};
public static IdleUpgrade Get( IdleUpgradeId id ) => All.FirstOrDefault( u => u.Id == id );
public static IEnumerable<IdleUpgrade> InCategory( IdleUpgradeCategory cat )
=> All.Where( u => u.Category == cat );
// ── Flux perks — permanent, survive every Meltdown ─────────────────────
public static readonly List<IdlePerk> Perks = new()
{
new IdlePerk {
Id = IdlePerkId.CoreEfficiency, Name = "Core Efficiency", Icon = "⚛️",
Desc = "All Core income multiplied.",
BaseCost = 3, CostStep = 3, MaxLevel = 12,
Effect = lvl => $"×{1f + 0.25f * lvl:F2} income",
},
new IdlePerk {
Id = IdlePerkId.ChainTheory, Name = "Resonance", Icon = "⛓️",
Desc = "Permanently steepens the chain multiplier curve.",
BaseCost = 10, CostStep = 8, MaxLevel = 10,
Effect = lvl => $"+{0.02f * lvl:F2} chain exponent",
},
new IdlePerk {
Id = IdlePerkId.Kickstart, Name = "Kickstart", Icon = "🚀",
Desc = "Start every Meltdown with upgrade levels already bought.",
BaseCost = 8, CostStep = 7, MaxLevel = 6,
Effect = lvl => $"{lvl * 6} Dense Loot · {lvl * 4} Feed Line",
},
new IdlePerk {
Id = IdlePerkId.NightShift, Name = "Night Shift", Icon = "🌙",
Desc = "Extra unattended efficiency on top of Night Crew.",
BaseCost = 6, CostStep = 5, MaxLevel = 8,
Effect = lvl => $"+{IdleFormat.Pct( 0.08f * lvl )} unattended",
},
new IdlePerk {
Id = IdlePerkId.DeepStorage, Name = "Deep Storage", Icon = "🛢️",
Desc = "Extra hours of unattended storage.",
BaseCost = 5, CostStep = 4, MaxLevel = 8,
Effect = lvl => $"+{lvl * 2}h cap",
},
new IdlePerk {
Id = IdlePerkId.Momentum, Name = "Momentum", Icon = "🔥",
Desc = "Surge lasts longer and the meter fills faster.",
BaseCost = 12, CostStep = 9, MaxLevel = 6,
Effect = lvl => $"+{lvl * 4}s surge · +{IdleFormat.Pct( 0.15f * lvl )} charge",
},
new IdlePerk {
Id = IdlePerkId.FragmentCondenser, Name = "Condenser Array", Icon = "💠",
Desc = "Raises the daily Fragment ceiling and cheapens condensing.",
BaseCost = 10, CostStep = 10, MaxLevel = 6,
Effect = lvl => $"+{lvl * 60} 💠/day",
},
new IdlePerk {
Id = IdlePerkId.AutoOverload, Name = "Autopilot", Icon = "🤖",
Desc = "The reactor fires OVERLOAD by itself. True idle.",
BaseCost = 25, CostStep = 25, MaxLevel = 3,
Effect = lvl => lvl <= 0 ? "off" : $"auto every {IdleBalance.AutoOverloadInterval( lvl ):F0}s",
},
new IdlePerk {
Id = IdlePerkId.PetLink, Name = "Pet Link", Icon = "🐾",
Desc = "Your pet works the night shift too — doubles its reactor bonus.",
BaseCost = 40, CostStep = 0, MaxLevel = 1,
Effect = lvl => lvl > 0 ? "pet bonus ×2" : "—",
},
// The sink. Every other perk has a ceiling, so without this one a reactor
// that has bought the tree out has nowhere to put Flux and every further
// Meltdown stops meaning anything. Deliberately poor value per Flux early
// — it is what you buy when there is nothing better left, not instead of
// the tree.
new IdlePerk {
Id = IdlePerkId.Containment, Name = "Containment Field", Icon = "♾️",
Desc = "No ceiling. Every level is more income, forever — the sink for Flux you have nothing else to spend on.",
BaseCost = 15, CostStep = 6, MaxLevel = 9999, Uncapped = true,
RevealAtFluxEarned = 120,
Effect = lvl => $"×{1f + 0.02f * lvl:F2} income",
},
};
public static IdlePerk GetPerk( IdlePerkId id ) => Perks.FirstOrDefault( p => p.Id == id );
// ── Singularity powers — bought with Shards, survive everything ─────────
public static readonly List<IdlePower> Powers = new()
{
new IdlePower {
Id = IdlePowerId.Overclock, Name = "Overclock", Icon = "⚡",
Desc = "Meltdown triggers at a far lower cycle. The whole prestige loop shortens.",
BaseCost = 1, CostStep = 1, MaxLevel = 8,
Effect = lvl => $"Meltdown at {IdleFormat.Short( IdleBalance.MeltdownRequirementFor( lvl ) )} Cores",
},
new IdlePower {
Id = IdlePowerId.Cascade, Name = "Resonance Cascade", Icon = "🌀",
Desc = "A resolved volley can immediately fire again on the wreckage it just made.",
BaseCost = 2, CostStep = 2, MaxLevel = 10,
Effect = lvl => $"{IdleFormat.Pct( IdleBalance.CascadeChance( lvl ) )} re-detonation",
},
new IdlePower {
Id = IdlePowerId.TierSkip, Name = "Tier Memory", Icon = "🔓",
Desc = "The reactor remembers exotic chests across a Meltdown — they are on the board from the first second of a cycle.",
BaseCost = 3, CostStep = 3, MaxLevel = 4,
Effect = lvl => lvl <= 0 ? "—" : $"{lvl} tier{(lvl == 1 ? "" : "s")} pre-unlocked",
},
new IdlePower {
Id = IdlePowerId.TwinReactor, Name = "Twin Reactor", Icon = "🛰️",
Desc = "Extra drone volleys per detonation cycle, each one landing on what the last left standing.",
BaseCost = 4, CostStep = 5, MaxLevel = 3,
Effect = lvl => lvl <= 0 ? "1 volley" : $"{1 + lvl} volleys per cycle",
},
new IdlePower {
Id = IdlePowerId.ChainMemory, Name = "Chain Memory", Icon = "♾️",
Desc = "Permanently steepens the chain curve — on top of Chain Theory and Resonance.",
BaseCost = 3, CostStep = 2, MaxLevel = 8,
Effect = lvl => $"+{0.01f * lvl:F2} chain exponent",
},
new IdlePower {
Id = IdlePowerId.FluxCatalyst, Name = "Flux Catalyst", Icon = "🔷",
Desc = "Every Meltdown pays out more Flux, so rebuilding the perk tree after a Singularity is quick.",
BaseCost = 2, CostStep = 2, MaxLevel = 6,
Effect = lvl => $"×{1f + 0.25f * lvl:F2} Meltdown Flux",
},
};
public static IdlePower GetPower( IdlePowerId id ) => Powers.FirstOrDefault( p => p.Id == id );
}