feat: add equipment, achievements, and visual polish

- Equipment system: 12 items across weapon/armour/trinket slots with
  common/rare/epic/legendary rarities; starter commons auto-equipped,
  higher tiers drop from boss victories
- Achievement system: 15 milestones with typed conditions; checked
  each tick and crystal rewards applied automatically
- Achievement toast: slide-in notification, auto-dismisses after 4s
- Floating click text: +X gold floats on each manual click
- Expanded quests (9 total) and upgrades (12 total)
- Upgrade panel now shows locked upgrades so players can see their
  progression path
- formatNumber utility (K/M/B/T) used consistently across all panels
- Backfill logic for existing saves to add new content gracefully
- types package now emits .d.ts declarations
This commit is contained in:
2026-03-06 13:27:48 -08:00
committed by Naomi Carrigan
parent a3daed1683
commit e9e0df31fd
33 changed files with 2066 additions and 133 deletions
+14 -2
View File
@@ -1,9 +1,15 @@
export type {
Achievement,
AchievementCondition,
AchievementConditionType,
AchievementReward,
} from "./interfaces/Achievement.js";
export type { Adventurer, AdventurerClass } from "./interfaces/Adventurer.js";
export type {
ApiError,
AuthResponse,
BossDamageRequest,
BossDamageResponse,
BossChallengeRequest,
BossChallengeResponse,
LoadResponse,
PrestigeRequest,
PrestigeResponse,
@@ -12,6 +18,12 @@ export type {
SaveResponse,
} from "./interfaces/Api.js";
export type { Boss, BossStatus } from "./interfaces/Boss.js";
export type {
Equipment,
EquipmentBonus,
EquipmentRarity,
EquipmentType,
} from "./interfaces/Equipment.js";
export type { GameState } from "./interfaces/GameState.js";
export type { Player } from "./interfaces/Player.js";
export type { PrestigeData } from "./interfaces/Prestige.js";
@@ -0,0 +1,28 @@
export type AchievementConditionType =
| "totalGoldEarned"
| "totalClicks"
| "bossesDefeated"
| "questsCompleted"
| "adventurerTotal"
| "prestigeCount"
| "equipmentOwned";
export interface AchievementCondition {
type: AchievementConditionType;
amount: number;
}
export interface AchievementReward {
crystals?: number;
}
export interface Achievement {
id: string;
name: string;
description: string;
icon: string;
condition: AchievementCondition;
reward?: AchievementReward;
/** Unix timestamp when unlocked, null if not yet unlocked */
unlockedAt: number | null;
}
@@ -15,6 +15,8 @@ export interface Adventurer {
goldPerSecond: number;
/** Base essence generated per second */
essencePerSecond: number;
/** Combat power per unit — used in boss battle simulation */
combatPower: number;
count: number;
unlocked: boolean;
}
+22 -5
View File
@@ -23,20 +23,37 @@ export interface LoadResponse {
offlineSeconds: number;
}
export interface BossDamageRequest {
export interface BossChallengeRequest {
bossId: string;
damage: number;
}
export interface BossDamageResponse {
currentHp: number;
defeated: boolean;
export interface BossChallengeResponse {
won: boolean;
partyDPS: number;
bossDPS: number;
/** Boss HP immediately before the battle */
bossHpBefore: number;
/** Boss maximum HP */
bossMaxHp: number;
/** Boss HP at end of battle before any state reset (0 on win) */
bossHpAtBattleEnd: number;
/** Boss HP stored in game after the result (0 on win, maxHp on loss) */
bossNewHp: number;
/** Total party HP at start of battle */
partyMaxHp: number;
/** Party HP remaining after battle (0 on loss) */
partyHpRemaining: number;
rewards?: {
gold: number;
essence: number;
crystals: number;
upgradeIds: string[];
equipmentIds: string[];
};
casualties?: Array<{
adventurerId: string;
killed: number;
}>;
}
export interface PrestigeRequest {
+2
View File
@@ -17,6 +17,8 @@ export interface Boss {
crystalReward: number;
/** IDs of upgrades unlocked on defeat */
upgradeRewards: string[];
/** IDs of equipment items granted on defeat */
equipmentRewards: string[];
/** Minimum prestige level required to access this boss */
prestigeRequirement: number;
}
@@ -0,0 +1,25 @@
export type EquipmentType = "weapon" | "armour" | "trinket";
export type EquipmentRarity = "common" | "rare" | "epic" | "legendary";
export interface EquipmentBonus {
/** Multiplier applied to all gold/s income (e.g. 1.1 = +10%) */
goldMultiplier?: number;
/** Multiplier applied to all combat power (e.g. 1.25 = +25%) */
combatMultiplier?: number;
/** Multiplier applied to click power (e.g. 1.5 = +50%) */
clickMultiplier?: number;
}
export interface Equipment {
id: string;
name: string;
description: string;
type: EquipmentType;
rarity: EquipmentRarity;
bonus: EquipmentBonus;
/** Whether the player has acquired this item */
owned: boolean;
/** Whether this item is currently equipped (only one per type can be equipped) */
equipped: boolean;
}
@@ -1,5 +1,7 @@
import type { Achievement } from "./Achievement.js";
import type { Adventurer } from "./Adventurer.js";
import type { Boss } from "./Boss.js";
import type { Equipment } from "./Equipment.js";
import type { Player } from "./Player.js";
import type { PrestigeData } from "./Prestige.js";
import type { Quest } from "./Quest.js";
@@ -13,6 +15,8 @@ export interface GameState {
upgrades: Upgrade[];
quests: Quest[];
bosses: Boss[];
equipment: Equipment[];
achievements: Achievement[];
prestige: PrestigeData;
/** Click power (gold per click, before upgrades) */
baseClickPower: number;
+1 -1
View File
@@ -1,6 +1,6 @@
export type QuestStatus = "locked" | "available" | "active" | "completed";
export type QuestRewardType = "gold" | "essence" | "crystals" | "upgrade" | "adventurer";
export type QuestRewardType = "gold" | "essence" | "crystals" | "upgrade" | "adventurer" | "equipment";
export interface QuestReward {
type: QuestRewardType;
+2 -1
View File
@@ -2,7 +2,8 @@
"extends": "@nhcarrigan/typescript-config",
"compilerOptions": {
"outDir": "./prod",
"rootDir": "."
"rootDir": ".",
"declaration": true
},
"exclude": ["test/**/*.ts"]
}