From 7a8f4f325c8441b53fb118a3a37fe81426a1d828 Mon Sep 17 00:00:00 2001 From: Tetardtek Date: Sat, 28 Mar 2026 21:19:01 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20click=20upgrades=20=E2=80=94=20buy=20cl?= =?UTF-8?q?ick=20power=20with=20tadpoles,=20tied=20to=20generators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 click upgrades, each linked to a generator type: - Nid Douillet (+1/clic, 50 base) — requires owning a Nid - Eau Fertile (+3/clic, 500 base) — requires a Mare - Spores Actives (+8/clic, 5k base) — requires a Marecage - Courant Vital (+20/clic, 50k base) — requires an Etang - Source Ancestrale (+50/clic, 500k base) — requires a Lac Cost scales x1.2 per level. Reset at prestige (like generators). Click gain = (base + upgradePower) × prestige × tree × infraBonus. ClickPanel shows upgrade shop with level badges and gen requirements. Adds tadpole sink for active play — strategic choice vs buying generators. --- Frontend/src/lib/components/ClickPanel.svelte | 111 ++++++++++-------- Frontend/src/lib/core/economy.ts | 59 +++++++++- Frontend/src/lib/core/migrateSave.ts | 31 ++++- Frontend/src/lib/stores/game.svelte.ts | 7 ++ 4 files changed, 155 insertions(+), 53 deletions(-) diff --git a/Frontend/src/lib/components/ClickPanel.svelte b/Frontend/src/lib/components/ClickPanel.svelte index 6cbfad5..ef08f3a 100644 --- a/Frontend/src/lib/components/ClickPanel.svelte +++ b/Frontend/src/lib/components/ClickPanel.svelte @@ -1,34 +1,36 @@ - +
Gain par clic - x{b.prestigeMult.toFixed(1)} prestige · x{b.treeMult.toFixed(0)} arbre · x{b.genBonus.toFixed(1)} infra - - - {b.genTypes} types (+{b.genTypes * 2}) · {b.genTotal} unites (+{(b.genTotal * 0.05).toFixed(1)}) + base {b.base} × x{b.prestigeMult.toFixed(1)} × x{b.treeMult.toFixed(0)} × x{b.genBonus.toFixed(1)}
{formatNumber(b.total)}
- +
Contribution clics @@ -40,67 +42,80 @@ {clickShare.toFixed(0)}%
- + + {#if (game.state.clickUpgrades ?? []).length > 0} + Ameliorations de ponte + {#each game.state.clickUpgrades ?? [] as upgrade} + {@const gen = game.state.generators.find((g) => g.id === upgrade.generatorId)} + {@const hasGen = gen && gen.owned > 0} + {@const cost = clickUpgradeCost(upgrade)} + {@const canAfford = hasGen && game.state.resources >= cost} +
+
+
+ {upgrade.name} + {#if upgrade.level > 0} + + nv.{upgrade.level} + + {/if} +
+ + {#if hasGen} + +{upgrade.baseClickPower}/clic par niveau ({GEN_NAMES[upgrade.generatorId]}) + {:else} + Necessite un {GEN_NAMES[upgrade.generatorId]} + {/if} + +
+ {#if hasGen} + + {:else} + + {/if} +
+ {/each} + {/if} + + + Bonus (arbre) +
Double Ponte - {#if b.doubleChance > 0} - {(b.doubleChance * 100).toFixed(0)}% chance de doubler - {:else} - Branche Ponte — "Double Ponte" (5 ADN) - {/if} + {b.doubleChance > 0 ? `${(b.doubleChance * 100).toFixed(0)}% chance de doubler` : 'Branche Ponte — 5 ADN'}
- {#if b.doubleChance > 0} - {(b.doubleChance * 100).toFixed(0)}% - {:else} - - {/if} + {b.doubleChance > 0 ? `${(b.doubleChance * 100).toFixed(0)}%` : '—'}
-
Ponte Critique - {#if b.critChance > 0} - {(b.critChance * 100).toFixed(0)}% chance de x10 - {:else} - Branche Ponte — "Ponte Critique" (20 ADN) - {/if} + {b.critChance > 0 ? `${(b.critChance * 100).toFixed(0)}% chance de x10` : 'Branche Ponte — 20 ADN'}
- {#if b.critChance > 0} - {(b.critChance * 100).toFixed(0)}% - {:else} - - {/if} + {b.critChance > 0 ? `${(b.critChance * 100).toFixed(0)}%` : '—'}
-
Auto-Ponte - {#if b.autoClicksPerSec > 0} - {b.autoClicksPerSec.toFixed(1)} clics/s auto → {formatNumber(b.effectivePerSec)}/s - {:else} - Capstone Ponte — "Ponte Automatique" (200 ADN) - {/if} + {b.autoClicksPerSec > 0 ? `${b.autoClicksPerSec.toFixed(1)} clics/s → ${formatNumber(b.effectivePerSec)}/s` : 'Capstone Ponte — 200 ADN'}
- {#if b.autoClicksPerSec > 0} - {formatNumber(b.effectivePerSec)}/s - {:else} - - {/if} + {b.autoClicksPerSec > 0 ? `${formatNumber(b.effectivePerSec)}/s` : '—'}
- - - {#if b.treeMult <= 1} -
- Depense ton ADN dans la branche Ponte pour booster tes clics -
- {/if} diff --git a/Frontend/src/lib/core/economy.ts b/Frontend/src/lib/core/economy.ts index 6c378e0..ab9a7f6 100644 --- a/Frontend/src/lib/core/economy.ts +++ b/Frontend/src/lib/core/economy.ts @@ -86,11 +86,39 @@ export interface RunStats { } | null; } +// --- Click Upgrades (achetables en têtards, liés aux générateurs) --- + +export interface ClickUpgrade { + id: string; + name: string; + generatorId: string; // lié à quel générateur + baseClickPower: number; // bonus clic par niveau + baseCost: number; // coût de base + level: number; // niveaux achetés +} + +export const DEFAULT_CLICK_UPGRADES: ClickUpgrade[] = [ + { id: "nid_douillet", name: "Nid Douillet", generatorId: "nid", baseClickPower: 1, baseCost: 50, level: 0 }, + { id: "eau_fertile", name: "Eau Fertile", generatorId: "mare", baseClickPower: 3, baseCost: 500, level: 0 }, + { id: "spores_actives", name: "Spores Actives", generatorId: "marecage", baseClickPower: 8, baseCost: 5_000, level: 0 }, + { id: "courant_vital", name: "Courant Vital", generatorId: "etang", baseClickPower: 20, baseCost: 50_000, level: 0 }, + { id: "source_ancestrale", name: "Source Ancestrale", generatorId: "lac", baseClickPower: 50, baseCost: 500_000, level: 0 }, +]; + +export function clickUpgradeCost(upgrade: ClickUpgrade): number { + return Math.floor(upgrade.baseCost * Math.pow(1.2, upgrade.level)); +} + +export function totalClickUpgradePower(clickUpgrades: ClickUpgrade[]): number { + return clickUpgrades.reduce((sum, u) => sum + u.baseClickPower * u.level, 0); +} + export interface GameState { saveVersion: number; resources: number; clickMultiplier: number; generators: Generator[]; + clickUpgrades: ClickUpgrade[]; lastTick: number; // timestamp ms — lazy calc reference lastOnline: number; // timestamp ms — dernière activité réelle (tick actif) prestigeCount: number; @@ -618,11 +646,32 @@ export function getGeneratorClickBonus(generators: Generator[]): number { return 1 + typesOwned * 2 + totalOwned * 0.05; } -// Gain par clic — scaling propre : base × prestige × arbre × generateurs +// Gain par clic — base + upgrades, le tout multiplié par prestige × arbre × infra export function getClickGain(state: GameState): number { const treeClickMult = getClickMultiplierFromTree(state.evolutionTree); const genBonus = getGeneratorClickBonus(state.generators); - return Math.floor(state.clickMultiplier * state.prestigeMultiplier * treeClickMult * genBonus); + const upgradePower = totalClickUpgradePower(state.clickUpgrades ?? []); + const base = state.clickMultiplier + upgradePower; + return Math.floor(base * state.prestigeMultiplier * treeClickMult * genBonus); +} + +// Achat d'un click upgrade (coûte des têtards) +export function buyClickUpgrade(state: GameState, upgradeId: string): GameState | null { + const idx = (state.clickUpgrades ?? []).findIndex((u) => u.id === upgradeId); + if (idx === -1) return null; + + const upgrade = state.clickUpgrades[idx]; + // Requires owning the linked generator + const gen = state.generators.find((g) => g.id === upgrade.generatorId); + if (!gen || gen.owned === 0) return null; + + const cost = clickUpgradeCost(upgrade); + if (state.resources < cost) return null; + + const updatedUpgrades = [...state.clickUpgrades]; + updatedUpgrades[idx] = { ...upgrade, level: upgrade.level + 1 }; + + return { ...state, resources: state.resources - cost, clickUpgrades: updatedUpgrades }; } // Breakdown complet du clic (pour affichage cockpit) @@ -641,7 +690,8 @@ export interface ClickBreakdown { } export function getClickBreakdown(state: GameState): ClickBreakdown { - const base = state.clickMultiplier; + const upgradePower = totalClickUpgradePower(state.clickUpgrades ?? []); + const base = state.clickMultiplier + upgradePower; const prestigeMult = state.prestigeMultiplier; const treeMult = getClickMultiplierFromTree(state.evolutionTree); const genBonus = getGeneratorClickBonus(state.generators); @@ -817,6 +867,8 @@ export function applyPrestige(state: GameState): GameState { }, freeResetAvailable: true, // 1 reset gratuit offert par prestige extraResetsUsed: 0, + // Click upgrades reset au prestige (comme les générateurs) + clickUpgrades: (state.clickUpgrades ?? DEFAULT_CLICK_UPGRADES).map((u) => ({ ...u, level: 0 })), // evolutionTree persiste — jamais reset }; } @@ -835,6 +887,7 @@ export const DEFAULT_STATE: GameState = { resources: 0, clickMultiplier: 1, generators: DEFAULT_GENERATORS, + clickUpgrades: DEFAULT_CLICK_UPGRADES, lastTick: Date.now(), lastOnline: Date.now(), prestigeCount: 0, diff --git a/Frontend/src/lib/core/migrateSave.ts b/Frontend/src/lib/core/migrateSave.ts index 5fbfad5..ab423d4 100644 --- a/Frontend/src/lib/core/migrateSave.ts +++ b/Frontend/src/lib/core/migrateSave.ts @@ -3,8 +3,8 @@ // Chaque sprint ajoute un step (v2→v3, etc.) import { CURRENT_SAVE_VERSION } from "./balance"; -import type { GameState } from "./economy"; -import { DEFAULT_EVOLUTION_TREE, DEFAULT_GENERATORS } from "./economy"; +import type { GameState, ClickUpgrade } from "./economy"; +import { DEFAULT_EVOLUTION_TREE, DEFAULT_GENERATORS, DEFAULT_CLICK_UPGRADES } from "./economy"; /** * Détecte la version d'une save et applique les migrations nécessaires. @@ -32,6 +32,11 @@ export function migrateSave(raw: Record): GameState { state.generators as Array> | undefined ); + // Click upgrades — merge with defaults (preserves levels, adds new upgrades) + state.clickUpgrades = mergeClickUpgrades( + state.clickUpgrades as Array> | undefined + ); + return state as unknown as GameState; } @@ -149,3 +154,25 @@ function mergeGenerators( return { ...defaultGen }; }); } + +/** + * Merge les click upgrades sauvegardés avec DEFAULT_CLICK_UPGRADES. + * Conserve le level, met à jour les stats de base. + */ +function mergeClickUpgrades( + saved: Array> | undefined +): ClickUpgrade[] { + if (!saved || !Array.isArray(saved)) { + return DEFAULT_CLICK_UPGRADES.map((u) => ({ ...u })); + } + + const savedById = new Map(saved.map((u) => [u.id as string, u])); + + return DEFAULT_CLICK_UPGRADES.map((def) => { + const s = savedById.get(def.id); + if (s) { + return { ...def, level: typeof s.level === "number" ? s.level : 0 }; + } + return { ...def }; + }); +} diff --git a/Frontend/src/lib/stores/game.svelte.ts b/Frontend/src/lib/stores/game.svelte.ts index 6959fd3..134f50a 100644 --- a/Frontend/src/lib/stores/game.svelte.ts +++ b/Frontend/src/lib/stores/game.svelte.ts @@ -21,6 +21,7 @@ import { totalProductionPerSecond, generatorCost as genCost, computeOfflineGains, + buyClickUpgrade as buyClickUpgradeFn, } from '$lib/core/economy'; import { migrateSave } from '$lib/core/migrateSave'; import { toast } from './toast.svelte'; @@ -133,6 +134,12 @@ class Game { if (updated) this.applyState(updated); } + buyClickUpgrade(upgradeId: string) { + if (!this.ready) return; + const updated = buyClickUpgradeFn(this.state, upgradeId); + if (updated) this.applyState(updated); + } + buyNode(nodeId: string) { if (!this.ready) return; const updated = buyEvolutionNode(this.state, nodeId);