|
|
|
@ -16,6 +16,7 @@ import {
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
ApiError,
|
|
|
|
ApiError,
|
|
|
|
initHero,
|
|
|
|
initHero,
|
|
|
|
|
|
|
|
ackChangelog,
|
|
|
|
getAdventureLog,
|
|
|
|
getAdventureLog,
|
|
|
|
getTowns,
|
|
|
|
getTowns,
|
|
|
|
getTownNPCs,
|
|
|
|
getTownNPCs,
|
|
|
|
@ -32,7 +33,7 @@ import {
|
|
|
|
defaultNpcShopCosts,
|
|
|
|
defaultNpcShopCosts,
|
|
|
|
npcShopCostsFromInit,
|
|
|
|
npcShopCostsFromInit,
|
|
|
|
} from './network/api';
|
|
|
|
} from './network/api';
|
|
|
|
import type { HeroResponse, Achievement } from './network/api';
|
|
|
|
import type { HeroResponse, Achievement, ChangelogPayload } from './network/api';
|
|
|
|
import type { AdventureLogEntry, Town, HeroQuest, NPC, TownData, EquipmentItem, BuildingData } from './game/types';
|
|
|
|
import type { AdventureLogEntry, Town, HeroQuest, NPC, TownData, EquipmentItem, BuildingData } from './game/types';
|
|
|
|
import type { OfflineReport as OfflineReportData } from './network/api';
|
|
|
|
import type { OfflineReport as OfflineReportData } from './network/api';
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
@ -60,6 +61,7 @@ import { OfflineReport } from './ui/OfflineReport';
|
|
|
|
import { HeroSheetModal, type HeroSheetTab } from './ui/HeroSheetModal';
|
|
|
|
import { HeroSheetModal, type HeroSheetTab } from './ui/HeroSheetModal';
|
|
|
|
import { NPCDialog } from './ui/NPCDialog';
|
|
|
|
import { NPCDialog } from './ui/NPCDialog';
|
|
|
|
import { NameEntryScreen } from './ui/NameEntryScreen';
|
|
|
|
import { NameEntryScreen } from './ui/NameEntryScreen';
|
|
|
|
|
|
|
|
import { ChangelogModal } from './ui/ChangelogModal';
|
|
|
|
import { AchievementsPanel } from './ui/AchievementsPanel';
|
|
|
|
import { AchievementsPanel } from './ui/AchievementsPanel';
|
|
|
|
import { Minimap } from './ui/Minimap';
|
|
|
|
import { Minimap } from './ui/Minimap';
|
|
|
|
import { NPCInteraction } from './ui/NPCInteraction';
|
|
|
|
import { NPCInteraction } from './ui/NPCInteraction';
|
|
|
|
@ -331,6 +333,9 @@ export function App() {
|
|
|
|
const [combatLogLines, setCombatLogLines] = useState<string[]>([]);
|
|
|
|
const [combatLogLines, setCombatLogLines] = useState<string[]>([]);
|
|
|
|
const [offlineReport, setOfflineReport] = useState<OfflineReportData | null>(null);
|
|
|
|
const [offlineReport, setOfflineReport] = useState<OfflineReportData | null>(null);
|
|
|
|
const [needsName, setNeedsName] = useState(false);
|
|
|
|
const [needsName, setNeedsName] = useState(false);
|
|
|
|
|
|
|
|
type ChangelogOpen = { payload: ChangelogPayload; serverVersion?: string };
|
|
|
|
|
|
|
|
const pendingChangelogRef = useRef<ChangelogOpen | null>(null);
|
|
|
|
|
|
|
|
const [changelogOpen, setChangelogOpen] = useState<ChangelogOpen | null>(null);
|
|
|
|
const logIdCounter = useRef(0);
|
|
|
|
const logIdCounter = useRef(0);
|
|
|
|
const nearbyIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
const nearbyIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
@ -445,6 +450,18 @@ export function App() {
|
|
|
|
const initRes = await initHero(telegramId);
|
|
|
|
const initRes = await initHero(telegramId);
|
|
|
|
setNpcShopCosts(npcShopCostsFromInit(initRes));
|
|
|
|
setNpcShopCosts(npcShopCostsFromInit(initRes));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (initRes.showChangelog && initRes.changelog) {
|
|
|
|
|
|
|
|
const bundle: ChangelogOpen = {
|
|
|
|
|
|
|
|
payload: initRes.changelog,
|
|
|
|
|
|
|
|
serverVersion: initRes.serverVersion,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
if (initRes.needsName) {
|
|
|
|
|
|
|
|
pendingChangelogRef.current = bundle;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
setChangelogOpen(bundle);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Gate game start behind name entry — no hero row until POST /hero/name
|
|
|
|
// Gate game start behind name entry — no hero row until POST /hero/name
|
|
|
|
if (initRes.needsName) {
|
|
|
|
if (initRes.needsName) {
|
|
|
|
setNeedsName(true);
|
|
|
|
setNeedsName(true);
|
|
|
|
@ -1055,9 +1072,20 @@ export function App() {
|
|
|
|
getAchievements(telegramId)
|
|
|
|
getAchievements(telegramId)
|
|
|
|
.then((a) => { prevAchievementsRef.current = a; setAchievements(a); })
|
|
|
|
.then((a) => { prevAchievementsRef.current = a; setAchievements(a); })
|
|
|
|
.catch(() => {});
|
|
|
|
.catch(() => {});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (pendingChangelogRef.current) {
|
|
|
|
|
|
|
|
setChangelogOpen(pendingChangelogRef.current);
|
|
|
|
|
|
|
|
pendingChangelogRef.current = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, []);
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const handleDismissChangelog = useCallback(() => {
|
|
|
|
|
|
|
|
setChangelogOpen(null);
|
|
|
|
|
|
|
|
const telegramId = getTelegramUserId() ?? 1;
|
|
|
|
|
|
|
|
ackChangelog(telegramId).catch(() => console.warn('[App] changelog ack failed'));
|
|
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
const handleUsePotion = useCallback(() => {
|
|
|
|
const handleUsePotion = useCallback(() => {
|
|
|
|
const ws = wsRef.current;
|
|
|
|
const ws = wsRef.current;
|
|
|
|
const hero = engineRef.current?.gameState.hero;
|
|
|
|
const hero = engineRef.current?.gameState.hero;
|
|
|
|
@ -1226,6 +1254,15 @@ export function App() {
|
|
|
|
{/* Name Entry Screen */}
|
|
|
|
{/* Name Entry Screen */}
|
|
|
|
{needsName && <NameEntryScreen onNameSet={handleNameSet} />}
|
|
|
|
{needsName && <NameEntryScreen onNameSet={handleNameSet} />}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{changelogOpen && (
|
|
|
|
|
|
|
|
<ChangelogModal
|
|
|
|
|
|
|
|
title={changelogOpen.payload.title}
|
|
|
|
|
|
|
|
items={changelogOpen.payload.items}
|
|
|
|
|
|
|
|
serverVersion={changelogOpen.serverVersion}
|
|
|
|
|
|
|
|
onDismiss={handleDismissChangelog}
|
|
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Death Screen */}
|
|
|
|
{/* Death Screen */}
|
|
|
|
<DeathScreen
|
|
|
|
<DeathScreen
|
|
|
|
visible={gameState.phase === GamePhase.Dead}
|
|
|
|
visible={gameState.phase === GamePhase.Dead}
|
|
|
|
|