Android build v1.1.39

This commit is contained in:
Warren H
2026-07-09 22:07:35 -04:00
parent 4766a34cd3
commit 90930b71aa
9 changed files with 445 additions and 167 deletions
+7 -5
View File
@@ -22,7 +22,7 @@ import {
Iwt2RoguelikeUpgradeScreen,
Iwt2SettingsScreen,
} from './screens/Iwt2ShellScreens'
import { writeIwt2Save, type Iwt2Save } from './save/iwt2Repository'
import { writeIwt2Save, type Iwt2LocalSaveSlot, type Iwt2Save } from './save/iwt2Repository'
import { IWT2_BOSS_METADATA, type Iwt2BossId } from './content/bosses'
import { iwt2BossCoinRewardFor } from './content/bossRewards'
import {
@@ -136,8 +136,8 @@ const MENU_ITEMS: Array<{
},
{
screen: 'cloud-save',
title: 'Backup Slot',
description: 'Choose local, online, or fresh IWT2 progress.',
title: 'Cloud Backup',
description: 'Compare the active local slot with your online backup.',
glyph: 'C',
},
{
@@ -150,10 +150,12 @@ const MENU_ITEMS: Array<{
export function IWantToHeal2App({
initialSave,
localSlot,
onlineBackupsAvailable,
onExitToSaveSelect,
}: {
initialSave: Iwt2Save
localSlot: Iwt2LocalSaveSlot
onlineBackupsAvailable: boolean
onExitToSaveSelect: () => void
}) {
@@ -174,8 +176,8 @@ export function IWantToHeal2App({
const cancelPvpQueueRef = useRef<(() => void) | null>(null)
useEffect(() => {
writeIwt2Save(save)
}, [save])
writeIwt2Save(save, localSlot)
}, [localSlot, save])
useEffect(() => () => {
cancelPvpQueueRef.current?.()
+51 -11
View File
@@ -101,14 +101,20 @@ export type Iwt2OnlineSaveResult = {
save: Iwt2Save | null
}
const IWT2_SAVE_KEY = 'i-want-to-heal-2:save:v1'
export const IWT2_LOCAL_SAVE_SLOTS = [1, 2, 3] as const
export type Iwt2LocalSaveSlot = (typeof IWT2_LOCAL_SAVE_SLOTS)[number]
export type Iwt2LocalSaveSlots = Record<Iwt2LocalSaveSlot, Iwt2Save | null>
export function createDefaultIwt2Save(): Iwt2Save {
const IWT2_LEGACY_SAVE_KEY = 'i-want-to-heal-2:save:v1'
const IWT2_SAVE_MIGRATION_KEY = 'i-want-to-heal-2:save-slots:migrated:v1'
const IWT2_ACTIVE_SAVE_SLOT_KEY = 'i-want-to-heal-2:active-save-slot:v1'
export function createDefaultIwt2Save(characterName = 'Healer'): Iwt2Save {
return {
version: 2,
updatedAt: Date.now(),
character: {
name: 'Healer',
name: normalizeCharacterName(characterName),
level: 1,
experience: 0,
healerStyle: 'dawnweaver',
@@ -153,12 +159,38 @@ function normalizeSave(value: unknown): Iwt2Save {
}
}
export function loadIwt2Save(): Iwt2Save {
return loadExistingIwt2Save() ?? createDefaultIwt2Save()
function iwt2SaveSlotKey(slot: Iwt2LocalSaveSlot) {
return `i-want-to-heal-2:save:slot-${slot}:v1`
}
export function loadExistingIwt2Save(): Iwt2Save | null {
const serialized = window.localStorage.getItem(IWT2_SAVE_KEY)
function migrateLegacyIwt2Save() {
if (window.localStorage.getItem(IWT2_SAVE_MIGRATION_KEY)) return
const legacySave = window.localStorage.getItem(IWT2_LEGACY_SAVE_KEY)
const firstSlotKey = iwt2SaveSlotKey(1)
if (legacySave && !window.localStorage.getItem(firstSlotKey)) {
window.localStorage.setItem(firstSlotKey, legacySave)
}
window.localStorage.setItem(IWT2_SAVE_MIGRATION_KEY, 'true')
}
export function activeIwt2SaveSlot(): Iwt2LocalSaveSlot {
const stored = Number(window.localStorage.getItem(IWT2_ACTIVE_SAVE_SLOT_KEY))
return IWT2_LOCAL_SAVE_SLOTS.includes(stored as Iwt2LocalSaveSlot)
? stored as Iwt2LocalSaveSlot
: 1
}
export function selectActiveIwt2SaveSlot(slot: Iwt2LocalSaveSlot) {
window.localStorage.setItem(IWT2_ACTIVE_SAVE_SLOT_KEY, String(slot))
}
export function loadIwt2Save(slot = activeIwt2SaveSlot()): Iwt2Save {
return loadExistingIwt2Save(slot) ?? createDefaultIwt2Save()
}
export function loadExistingIwt2Save(slot = activeIwt2SaveSlot()): Iwt2Save | null {
migrateLegacyIwt2Save()
const serialized = window.localStorage.getItem(iwt2SaveSlotKey(slot))
if (!serialized) return null
try {
return normalizeSave(JSON.parse(serialized))
@@ -167,6 +199,14 @@ export function loadExistingIwt2Save(): Iwt2Save | null {
}
}
export function loadIwt2SaveSlots(): Iwt2LocalSaveSlots {
return {
1: loadExistingIwt2Save(1),
2: loadExistingIwt2Save(2),
3: loadExistingIwt2Save(3),
}
}
export async function loadIwt2OnlineSave(): Promise<Iwt2OnlineSaveResult> {
const result = await requestGameApiJson<{ save: unknown | null }>('/api/iwt2/sync-save')
return {
@@ -185,12 +225,12 @@ export async function writeIwt2OnlineSave(save: Iwt2Save): Promise<Iwt2OnlineSav
}
}
export function writeIwt2Save(save: Iwt2Save) {
replaceIwt2Save(save)
export function writeIwt2Save(save: Iwt2Save, slot = activeIwt2SaveSlot()) {
replaceIwt2Save(save, slot)
}
export function replaceIwt2Save(save: Iwt2Save) {
window.localStorage.setItem(IWT2_SAVE_KEY, JSON.stringify(normalizeSave(save)))
export function replaceIwt2Save(save: Iwt2Save, slot = activeIwt2SaveSlot()) {
window.localStorage.setItem(iwt2SaveSlotKey(slot), JSON.stringify(normalizeSave(save)))
}
export function recordIwt2BossKill(
+1 -15
View File
@@ -9,7 +9,6 @@ import {
import { useDualScreen, useDualScreenWorkshopPublisher, type DualScreenWorkshopState } from '../../../dualScreen'
import { getGameMode } from '../../../gameRepository'
import {
createDefaultIwt2Save,
loadIwt2OnlineSave,
setIwt2InfusionAbility,
setIwt2PassiveInfusion,
@@ -1946,11 +1945,6 @@ export function Iwt2CloudSaveScreen({
setMessage('Local save now uses online progress.')
}, [onlineBackupsAvailable, onlineSave, onSaveUpdated])
const handleUseNewSave = useCallback(() => {
onSaveUpdated(createDefaultIwt2Save())
setMessage('Started a new local IWT2 save.')
}, [onSaveUpdated])
const actions = useMemo<Iwt2NavAction[]>(() => withBackAction([
{
key: 'use-local',
@@ -1970,16 +1964,8 @@ export function Iwt2CloudSaveScreen({
disabled: syncingOnlineSave || !onlineBackupsAvailable || !onlineSave,
onConfirm: handleUseOnlineSave,
},
{
key: 'new-save',
label: 'Use New Save File',
detail: 'Start over with a fresh IWT2 character. Online save is not overwritten until you choose local save.',
value: 'New',
onConfirm: handleUseNewSave,
},
], onBack), [
handleUseLocalSave,
handleUseNewSave,
handleUseOnlineSave,
onlineBackupsAvailable,
onlineSave,
@@ -1989,7 +1975,7 @@ export function Iwt2CloudSaveScreen({
])
return (
<Iwt2ScreenShell title="Backup Slot" onBack={onBack}>
<Iwt2ScreenShell title="Cloud Backup" onBack={onBack}>
{statusMessage && <p className="iwt2-screen-note">{statusMessage}</p>}
<Iwt2ActionList actions={actions} />
</Iwt2ScreenShell>