Release v0.1.3 2026-07-11
This commit is contained in:
+136
-9
@@ -4,6 +4,14 @@ import { SaveRepository } from "./saveRepository";
|
||||
import { AccountRepository, type AccountResult } from "./accountRepository";
|
||||
import type { AppScreen, GameModeId, GameSettings, HunterSave, SaveSlotId, SaveSlotState } from "./types";
|
||||
import type { BossId, HealerClassId, InventoryItem } from "../game/types";
|
||||
import { upgradeGearSlot, type GearOwnerId, type GearSlotId } from "../game/progression/gear";
|
||||
import {
|
||||
equipActiveInfusion,
|
||||
equipPassiveInfusion,
|
||||
infusionsForOwner,
|
||||
} from "../game/progression/infusions";
|
||||
import type { RunBuffId } from "../game/types";
|
||||
import { normalizeDifficultySlug, rollBossReward, type BossRewardAward, type DifficultySlug } from "../game/progression/loot";
|
||||
|
||||
const repository = new SaveRepository();
|
||||
const accounts = new AccountRepository();
|
||||
@@ -46,6 +54,12 @@ export interface FrontendState {
|
||||
activeSlotId: SaveSlotId | null;
|
||||
selectedMode: GameModeId;
|
||||
selectedBossId: BossId;
|
||||
selectedDifficultySlug: DifficultySlug;
|
||||
selectedGearOwnerId: GearOwnerId;
|
||||
selectedGearSlotId: GearSlotId;
|
||||
gearWorkshopMode: "upgrade" | "infusion";
|
||||
selectedInfusionId: string;
|
||||
recentRewards: BossRewardAward[];
|
||||
settings: GameSettings;
|
||||
notice: string;
|
||||
signIn: (username: string, password: string) => Promise<boolean>;
|
||||
@@ -62,11 +76,20 @@ export interface FrontendState {
|
||||
downloadSlot: (slotId: SaveSlotId) => void;
|
||||
selectMode: (mode: GameModeId) => void;
|
||||
selectBoss: (bossId: BossId) => void;
|
||||
selectDifficulty: (difficultySlug: DifficultySlug) => void;
|
||||
selectGearOwner: (ownerId: GearOwnerId) => void;
|
||||
selectGearSlot: (slotId: GearSlotId) => void;
|
||||
selectGearWorkshopMode: (mode: "upgrade" | "infusion") => void;
|
||||
selectInfusion: (infusionId: string) => void;
|
||||
upgradeSelectedGear: () => boolean;
|
||||
equipSelectedInfusion: () => boolean;
|
||||
equipPassiveInfusion: (passiveId: RunBuffId) => boolean;
|
||||
selectHealerClass: (classId: HealerClassId) => void;
|
||||
updateActiveHealerInventory: (inventory: InventoryItem[]) => void;
|
||||
updateSetting: <K extends keyof GameSettings>(key: K, value: GameSettings[K]) => void;
|
||||
touchActiveSave: () => void;
|
||||
recordBossVictory: (bossName: string) => void;
|
||||
recordBossVictory: (bossId: BossId, difficultySlug: DifficultySlug) => BossRewardAward | null;
|
||||
clearRecentRewards: () => void;
|
||||
clearNotice: () => void;
|
||||
}
|
||||
|
||||
@@ -82,6 +105,12 @@ export const useFrontendStore = create<FrontendState>((set, get) => ({
|
||||
activeSlotId: null,
|
||||
selectedMode: "roguelike-pve",
|
||||
selectedBossId: "bulldrome",
|
||||
selectedDifficultySlug: "initiate",
|
||||
selectedGearOwnerId: "priest",
|
||||
selectedGearSlotId: "weapon",
|
||||
gearWorkshopMode: "upgrade",
|
||||
selectedInfusionId: infusionsForOwner("priest")[0].id,
|
||||
recentRewards: [],
|
||||
settings: loadSettings(),
|
||||
notice: "",
|
||||
|
||||
@@ -149,12 +178,84 @@ export const useFrontendStore = create<FrontendState>((set, get) => ({
|
||||
},
|
||||
selectMode: (selectedMode) => set({ selectedMode, screen: "mode", notice: "" }),
|
||||
selectBoss: (selectedBossId) => set({ selectedBossId, notice: "" }),
|
||||
selectDifficulty: (selectedDifficultySlug) => set({ selectedDifficultySlug: normalizeDifficultySlug(selectedDifficultySlug), notice: "" }),
|
||||
selectGearOwner: (selectedGearOwnerId) => set({
|
||||
selectedGearOwnerId,
|
||||
selectedInfusionId: infusionsForOwner(selectedGearOwnerId)[0].id,
|
||||
notice: "",
|
||||
}),
|
||||
selectGearSlot: (selectedGearSlotId) => set({ selectedGearSlotId, notice: "" }),
|
||||
selectGearWorkshopMode: (gearWorkshopMode) => set({ gearWorkshopMode, notice: "" }),
|
||||
selectInfusion: (selectedInfusionId) => set({ selectedInfusionId, notice: "" }),
|
||||
upgradeSelectedGear: () => {
|
||||
const { activeSlotId, accountId, selectedGearOwnerId, selectedGearSlotId } = get();
|
||||
if (!activeSlotId) return false;
|
||||
let message = "Gear upgrade failed.";
|
||||
let upgraded = false;
|
||||
repository.updateLocal(activeSlotId, (save) => {
|
||||
try {
|
||||
const result = upgradeGearSlot(save.gearProgress, save.materials, selectedGearOwnerId, selectedGearSlotId);
|
||||
upgraded = true;
|
||||
message = `${selectedGearOwnerId} ${selectedGearSlotId} upgraded to +${result.gearProgress[selectedGearOwnerId].slots[selectedGearSlotId].level}.`;
|
||||
return { ...save, gearProgress: result.gearProgress, materials: result.inventory };
|
||||
} catch (error) {
|
||||
message = error instanceof Error ? error.message : message;
|
||||
return save;
|
||||
}
|
||||
});
|
||||
set({ slots: repository.list(accountId), notice: message });
|
||||
return upgraded;
|
||||
},
|
||||
equipSelectedInfusion: () => {
|
||||
const { activeSlotId, accountId, selectedGearOwnerId, selectedGearSlotId, selectedInfusionId } = get();
|
||||
if (!activeSlotId) return false;
|
||||
let message = "Infusion failed.";
|
||||
let equipped = false;
|
||||
repository.updateLocal(activeSlotId, (save) => {
|
||||
try {
|
||||
const wasEquipped = save.gearProgress[selectedGearOwnerId].infusionAbilityId === selectedInfusionId;
|
||||
const result = equipActiveInfusion(save.gearProgress, save.materials, selectedGearOwnerId, selectedGearSlotId, selectedInfusionId);
|
||||
equipped = true;
|
||||
message = wasEquipped ? "Infusion already equipped." : `${selectedGearOwnerId} infusion equipped.`;
|
||||
return { ...save, gearProgress: result.gearProgress, materials: result.inventory };
|
||||
} catch (error) {
|
||||
message = error instanceof Error ? error.message : message;
|
||||
return save;
|
||||
}
|
||||
});
|
||||
set({ slots: repository.list(accountId), notice: message });
|
||||
return equipped;
|
||||
},
|
||||
equipPassiveInfusion: (passiveId) => {
|
||||
const { activeSlotId, accountId, selectedGearOwnerId } = get();
|
||||
if (!activeSlotId) return false;
|
||||
let message = "Passive infusion failed.";
|
||||
let equipped = false;
|
||||
repository.updateLocal(activeSlotId, (save) => {
|
||||
try {
|
||||
const gearProgress = equipPassiveInfusion(save.gearProgress, selectedGearOwnerId, passiveId);
|
||||
equipped = true;
|
||||
message = "Passive infusion equipped. Applies next encounter.";
|
||||
return { ...save, gearProgress };
|
||||
} catch (error) {
|
||||
message = error instanceof Error ? error.message : message;
|
||||
return save;
|
||||
}
|
||||
});
|
||||
set({ slots: repository.list(accountId), notice: message });
|
||||
return equipped;
|
||||
},
|
||||
selectHealerClass: (classId) => {
|
||||
const { activeSlotId, accountId } = get();
|
||||
if (!activeSlotId) return;
|
||||
const updated = repository.updateLocal(activeSlotId, (save) => ({ ...save, activeClassId: classId }));
|
||||
if (!updated) return;
|
||||
set({ slots: repository.list(accountId), notice: `${updated.healers[classId].level > 1 ? "Level " + updated.healers[classId].level + " " : ""}${classId[0].toUpperCase() + classId.slice(1)} selected.` });
|
||||
set({
|
||||
slots: repository.list(accountId),
|
||||
selectedGearOwnerId: classId,
|
||||
selectedInfusionId: infusionsForOwner(classId)[0].id,
|
||||
notice: `${updated.healers[classId].level > 1 ? "Level " + updated.healers[classId].level + " " : ""}${classId[0].toUpperCase() + classId.slice(1)} selected.`,
|
||||
});
|
||||
},
|
||||
updateActiveHealerInventory: (inventory) => {
|
||||
const { activeSlotId, accountId } = get();
|
||||
@@ -179,21 +280,29 @@ export const useFrontendStore = create<FrontendState>((set, get) => ({
|
||||
repository.touch(activeSlotId);
|
||||
set({ slots: repository.list(accountId) });
|
||||
},
|
||||
recordBossVictory: (bossName) => {
|
||||
recordBossVictory: (bossId, difficultySlug) => {
|
||||
const { activeSlotId, accountId } = get();
|
||||
if (!activeSlotId) return;
|
||||
if (!activeSlotId) return null;
|
||||
let awarded: BossRewardAward | null = null;
|
||||
repository.updateLocal(activeSlotId, (save) => {
|
||||
const bossKills = { ...save.stats.bossKills, [bossName]: (save.stats.bossKills[bossName] ?? 0) + 1 };
|
||||
const reward = rollBossReward(bossId, difficultySlug, save.materials, save.collectionLog);
|
||||
awarded = reward.award;
|
||||
const bossKills = { ...save.stats.bossKills, [bossId]: (save.stats.bossKills[bossId] ?? 0) + 1 };
|
||||
return {
|
||||
...save,
|
||||
stats: { ...save.stats, totalBossKills: save.stats.totalBossKills + 1, flawlessClears: save.stats.flawlessClears + 1, bossKills },
|
||||
collections: save.collections.map((boss) => boss.bossName === bossName
|
||||
? { ...boss, defeated: true, drops: boss.drops.map((drop, index) => index === 0 ? { ...drop, count: drop.count + 1 } : drop) }
|
||||
: boss),
|
||||
materials: reward.inventory,
|
||||
collectionLog: reward.collectionLog,
|
||||
};
|
||||
});
|
||||
set({ slots: repository.list(accountId), notice: `${bossName} clear saved offline.` });
|
||||
set((state) => ({
|
||||
slots: repository.list(accountId),
|
||||
recentRewards: awarded ? [...state.recentRewards, awarded] : state.recentRewards,
|
||||
notice: awarded ? `${awarded.coin.name} x${awarded.quantity} saved offline.` : "Boss clear saved offline.",
|
||||
}));
|
||||
return awarded;
|
||||
},
|
||||
clearRecentRewards: () => set({ recentRewards: [] }),
|
||||
clearNotice: () => set({ notice: "" }),
|
||||
}));
|
||||
|
||||
@@ -212,11 +321,20 @@ export type FrontendSnapshot = Omit<FrontendState,
|
||||
| "downloadSlot"
|
||||
| "selectMode"
|
||||
| "selectBoss"
|
||||
| "selectDifficulty"
|
||||
| "selectGearOwner"
|
||||
| "selectGearSlot"
|
||||
| "selectGearWorkshopMode"
|
||||
| "selectInfusion"
|
||||
| "upgradeSelectedGear"
|
||||
| "equipSelectedInfusion"
|
||||
| "equipPassiveInfusion"
|
||||
| "selectHealerClass"
|
||||
| "updateActiveHealerInventory"
|
||||
| "updateSetting"
|
||||
| "touchActiveSave"
|
||||
| "recordBossVictory"
|
||||
| "clearRecentRewards"
|
||||
| "clearNotice"
|
||||
>;
|
||||
|
||||
@@ -236,11 +354,20 @@ export function getFrontendSnapshot(): FrontendSnapshot {
|
||||
downloadSlot: _downloadSlot,
|
||||
selectMode: _selectMode,
|
||||
selectBoss: _selectBoss,
|
||||
selectDifficulty: _selectDifficulty,
|
||||
selectGearOwner: _selectGearOwner,
|
||||
selectGearSlot: _selectGearSlot,
|
||||
selectGearWorkshopMode: _selectGearWorkshopMode,
|
||||
selectInfusion: _selectInfusion,
|
||||
upgradeSelectedGear: _upgradeSelectedGear,
|
||||
equipSelectedInfusion: _equipSelectedInfusion,
|
||||
equipPassiveInfusion: _equipPassiveInfusion,
|
||||
selectHealerClass: _selectHealerClass,
|
||||
updateActiveHealerInventory: _updateActiveHealerInventory,
|
||||
updateSetting: _updateSetting,
|
||||
touchActiveSave: _touchActiveSave,
|
||||
recordBossVictory: _recordBossVictory,
|
||||
clearRecentRewards: _clearRecentRewards,
|
||||
clearNotice: _clearNotice,
|
||||
...snapshot
|
||||
} = useFrontendStore.getState();
|
||||
|
||||
Reference in New Issue
Block a user