160 lines
4.6 KiB
TypeScript
160 lines
4.6 KiB
TypeScript
import { useCallback, useEffect, useState } from 'react'
|
|
import { AuthScreen } from './components/AuthScreen'
|
|
import IWantToHeal1App from './modes/iwt1/IWantToHeal1App'
|
|
import { IWantToHeal2App } from './modes/iwt2/IWantToHeal2App'
|
|
import { useGameAction } from './input'
|
|
import {
|
|
loadAuthSession,
|
|
type AuthSession,
|
|
} from './profile'
|
|
|
|
type GameVersion = 'iwt1' | 'iwt2'
|
|
|
|
const GAME_OPTIONS: Array<{
|
|
version: GameVersion
|
|
title: string
|
|
label: string
|
|
description: string
|
|
glyph: string
|
|
}> = [
|
|
{
|
|
version: 'iwt1',
|
|
title: 'I Want To Heal 1',
|
|
label: 'Classic Healer Runs',
|
|
description: 'Original dungeon, raid, roguelike, PvP, gear, talents, and collection progression.',
|
|
glyph: 'I',
|
|
},
|
|
{
|
|
version: 'iwt2',
|
|
title: 'I Want To Heal 2',
|
|
label: '2D Boss Arena',
|
|
description: 'Move through boss arenas with a visible party, analog movement, projectiles, and separate progression.',
|
|
glyph: 'II',
|
|
},
|
|
]
|
|
|
|
function App() {
|
|
const [selectedVersion, setSelectedVersion] = useState<GameVersion | null>(null)
|
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
const [authSession, setAuthSession] = useState<AuthSession | null>(null)
|
|
const [authChecked, setAuthChecked] = useState(false)
|
|
const [serverMessage, setServerMessage] = useState('')
|
|
|
|
useEffect(() => {
|
|
let cancelled = false
|
|
loadAuthSession()
|
|
.then((session) => {
|
|
if (cancelled) return
|
|
setAuthSession(session.account && session.profile ? session : null)
|
|
})
|
|
.catch((reason: unknown) => {
|
|
if (cancelled) return
|
|
setServerMessage(
|
|
reason instanceof Error
|
|
? `${reason.message} Offline play is still available.`
|
|
: 'Unable to reach the server. Offline play is still available.',
|
|
)
|
|
})
|
|
.finally(() => {
|
|
if (!cancelled) setAuthChecked(true)
|
|
})
|
|
return () => {
|
|
cancelled = true
|
|
}
|
|
}, [])
|
|
|
|
const acceptSession = useCallback((session: AuthSession) => {
|
|
setAuthSession(session)
|
|
setSelectedVersion(null)
|
|
setServerMessage('')
|
|
}, [])
|
|
|
|
const clearAuthSession = useCallback(() => {
|
|
setAuthSession(null)
|
|
setSelectedVersion(null)
|
|
}, [])
|
|
|
|
useGameAction((action, device) => {
|
|
if (!authSession || selectedVersion || device !== 'controller') return
|
|
if (action === 'navigateLeft' || action === 'navigateUp') {
|
|
setSelectedIndex((current) => Math.max(0, current - 1))
|
|
} else if (action === 'navigateRight' || action === 'navigateDown') {
|
|
setSelectedIndex((current) => Math.min(GAME_OPTIONS.length - 1, current + 1))
|
|
} else if (action === 'confirm') {
|
|
setSelectedVersion(GAME_OPTIONS[selectedIndex].version)
|
|
}
|
|
})
|
|
|
|
if (!authChecked) {
|
|
return (
|
|
<main className="game-shell">
|
|
<section className="message-panel">
|
|
<p className="eyebrow">Opening Chronicle</p>
|
|
<h1>Loading...</h1>
|
|
</section>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
if (!authSession) {
|
|
return (
|
|
<AuthScreen
|
|
onAuthenticated={acceptSession}
|
|
serverMessage={serverMessage}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (selectedVersion === 'iwt1') {
|
|
return (
|
|
<IWantToHeal1App
|
|
initialSession={authSession}
|
|
onAuthenticationCleared={clearAuthSession}
|
|
onBackToGameSelect={() => setSelectedVersion(null)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
if (selectedVersion === 'iwt2') {
|
|
return (
|
|
<IWantToHeal2App
|
|
onlineBackupsAvailable={authSession.account?.id !== -1}
|
|
onBackToGameSelect={() => setSelectedVersion(null)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<main className="game-shell game-version-shell">
|
|
<section className="game-version-screen" data-game-nav-active="true">
|
|
<div className="game-version-heading">
|
|
<p className="eyebrow">Select Game</p>
|
|
<h1>I Want To Heal</h1>
|
|
</div>
|
|
<div className="game-version-grid">
|
|
{GAME_OPTIONS.map((option, index) => (
|
|
<button
|
|
className={`game-version-card ${selectedIndex === index ? 'game-selected' : ''}`}
|
|
data-controller-nav="skip"
|
|
data-game-selected={selectedIndex === index ? 'true' : undefined}
|
|
key={option.version}
|
|
onClick={() => setSelectedVersion(option.version)}
|
|
onPointerDown={() => setSelectedIndex(index)}
|
|
type="button"
|
|
>
|
|
<span>{option.glyph}</span>
|
|
<div>
|
|
<strong>{option.title}</strong>
|
|
<small>{option.label}</small>
|
|
<p>{option.description}</p>
|
|
</div>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</section>
|
|
</main>
|
|
)
|
|
}
|
|
|
|
export default App
|