import { useEffect } from 'react'
import { useGameStore } from './ui/store'
import Boot from './ui/screens/Boot'
import NewGame from './ui/screens/NewGame'
import GameScreen from './ui/screens/GameScreen'
import { EventModal } from './ui/components/EventModal'
import { BattleModal } from './ui/components/BattleModal'
import { PaperModal } from './ui/components/PaperModal'
import { sClick } from './ui/sound'
import { ensureFx, fxVisibilityAutopause, applySeason } from './ui/fx'
import { startFxLayer, spawnBurst, stopFxLayer } from './ui/fx-canvas'
import { DriftLayer } from './ui/components/DriftLayer'
import { seasonOf } from './game/data/season'
import { Component, type ReactNode, type ErrorInfo } from 'react'
interface EBState { err: Error | null }
export class AppErrorBoundary extends Component<{ children: ReactNode }, EBState> {
state: EBState = { err: null }
static getDerivedStateFromError(err: Error): EBState { return { err } }
componentDidCatch(err: Error, info: ErrorInfo): void {
console.error('[ui-crash]', err, info.componentStack)
}
render(): ReactNode {
if (this.state.err) {
return (
界面出错了(游戏数据未受影响)
{String(this.state.err?.message ?? this.state.err)}
)
}
return this.props.children
}
}
export default function App() {
const screen = useGameStore((s) => s.screen)
const pendingEventId = useGameStore((s) => s.pendingEventId)
const battleView = useGameStore((s) => s.battleView)
useEffect(() => {
useGameStore.getState().init()
const clicker = () => sClick()
document.addEventListener('click', clicker, true)
const fx = ensureFx()
const stopVis = fxVisibilityAutopause()
const burst = (e: Event) => {
const kind = e.type.replace('fx:', '')
if (kind === 'spark' || kind === 'blade') spawnBurst('spark', 'center')
if (kind === 'ripple') spawnBurst('ripple')
if (kind === 'pulse') spawnBurst('ripple')
}
for (const k of ['fx:spark', 'fx:blade', 'fx:ripple', 'fx:pulse']) window.addEventListener(k, burst)
const seasonInit = () => applySeason(seasonOf(useGameStore.getState().world?.state.month ?? 1))
seasonInit()
startFxLayer(fx.mode === 'soft' ? 18 : fx.mode === 'full' ? 70 : 36)
return () => {
document.removeEventListener('click', clicker, true)
stopVis()
for (const k of ['fx:spark', 'fx:blade', 'fx:ripple', 'fx:pulse']) window.removeEventListener(k, burst)
stopFxLayer()
}
}, [])
return (
{screen === 'boot' &&
}
{screen === 'newgame' &&
}
{screen === 'game' &&
}
{pendingEventId &&
}
{battleView &&
}
)
}