fix(v2): character window now updates live from WebSocket
The CharacterWindow only fetched once from API on mount and never updated. Now: - character_stats WS messages are tracked in useLiveData via ref - Passed through WindowRenderer to CharacterWindow as liveStats prop - Window uses live WS data when available, falls back to API fetch - Attributes, skills, vitals base values, properties (augmentations, ratings, etc.), allegiance all update in real-time Also: vitals bars in the character window use live WS vitals data (health_percentage etc.) for real-time HP/Stamina/Mana display. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a5bd659876
commit
d2c30b610b
30 changed files with 115 additions and 54 deletions
|
|
@ -66,7 +66,8 @@ export const MapLayout: React.FC<Props> = ({ data }) => {
|
|||
/>
|
||||
<WindowRenderer characters={data.characters} chatMessages={data.chatMessages}
|
||||
nearbyObjects={data.nearbyObjects} inventoryVersion={data.inventoryVersion}
|
||||
equipmentCantrips={data.equipmentCantrips} socket={data.socketRef.current} />
|
||||
equipmentCantrips={data.equipmentCantrips} characterStats={data.characterStats}
|
||||
socket={data.socketRef.current} />
|
||||
<RareNotification recentRares={data.recentRares} />
|
||||
</div>
|
||||
</WindowManagerProvider>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
|||
import { DraggableWindow } from './DraggableWindow';
|
||||
import { apiFetch } from '../../api/client';
|
||||
|
||||
interface Props { id: string; charName: string; zIndex: number; vitals?: any; }
|
||||
interface Props { id: string; charName: string; zIndex: number; vitals?: any; liveStats?: any; }
|
||||
|
||||
// Property ID maps — verbatim from v1 script.js lines 1843-1876
|
||||
const TS_AUGMENTATIONS: Record<number, string> = {
|
||||
|
|
@ -49,15 +49,19 @@ function societyRank(v: number): string {
|
|||
const gold = '#af7a30';
|
||||
const navy = '#000022';
|
||||
|
||||
export const CharacterWindow: React.FC<Props> = ({ id, charName, zIndex, vitals }) => {
|
||||
const [data, setData] = useState<any>(null);
|
||||
export const CharacterWindow: React.FC<Props> = ({ id, charName, zIndex, vitals, liveStats }) => {
|
||||
const [fetchedData, setFetchedData] = useState<any>(null);
|
||||
const [leftTab, setLeftTab] = useState(0);
|
||||
const [rightTab, setRightTab] = useState(0);
|
||||
|
||||
// Initial fetch from API
|
||||
useEffect(() => {
|
||||
apiFetch<any>(`/character-stats/${encodeURIComponent(charName)}`).then(setData).catch(() => {});
|
||||
apiFetch<any>(`/character-stats/${encodeURIComponent(charName)}`).then(setFetchedData).catch(() => {});
|
||||
}, [charName]);
|
||||
|
||||
// Use live WS data if available (more current), fall back to API fetch
|
||||
const data = liveStats || fetchedData;
|
||||
|
||||
const fmt = (n: any) => n != null ? Number(n).toLocaleString() : '\u2014';
|
||||
const sd = data?.stats_data || data || {};
|
||||
const attrs = sd.attributes || {};
|
||||
|
|
|
|||
|
|
@ -19,10 +19,11 @@ interface Props {
|
|||
nearbyObjects: Map<string, any>;
|
||||
inventoryVersion: number;
|
||||
equipmentCantrips: Map<string, any>;
|
||||
characterStats: Map<string, any>;
|
||||
socket: WebSocket | null;
|
||||
}
|
||||
|
||||
export const WindowRenderer: React.FC<Props> = React.memo(({ characters, chatMessages, nearbyObjects, inventoryVersion, equipmentCantrips, socket }) => {
|
||||
export const WindowRenderer: React.FC<Props> = React.memo(({ characters, chatMessages, nearbyObjects, inventoryVersion, equipmentCantrips, characterStats, socket }) => {
|
||||
const { windows } = useWindowManager();
|
||||
|
||||
return (
|
||||
|
|
@ -39,7 +40,8 @@ export const WindowRenderer: React.FC<Props> = React.memo(({ characters, chatMes
|
|||
return <StatsWindow key={w.id} id={w.id} charName={charName} zIndex={w.zIndex} />;
|
||||
case 'char':
|
||||
return <CharacterWindow key={w.id} id={w.id} charName={charName} zIndex={w.zIndex}
|
||||
vitals={characters.get(charName)?.vitals ?? undefined} />;
|
||||
vitals={characters.get(charName)?.vitals ?? undefined}
|
||||
liveStats={characterStats.get(charName)} />;
|
||||
case 'inv':
|
||||
return <InventoryWindow key={w.id} id={w.id} charName={charName} zIndex={w.zIndex}
|
||||
inventoryVersion={inventoryVersion} equipmentCantrips={equipmentCantrips.get(charName)} />;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export interface DashboardState {
|
|||
nearbyObjects: Map<string, any>;
|
||||
inventoryVersion: number;
|
||||
equipmentCantrips: Map<string, any>;
|
||||
characterStats: Map<string, any>;
|
||||
socketRef: React.RefObject<WebSocket | null>;
|
||||
}
|
||||
|
||||
|
|
@ -30,6 +31,8 @@ export function useLiveData(): DashboardState {
|
|||
const [inventoryVersion, setInventoryVersion] = useState(0);
|
||||
const equipmentCantripRef = useRef(new Map<string, any>());
|
||||
const [equipCantripVersion, setEquipCantripVersion] = useState(0);
|
||||
const characterStatsRef = useRef(new Map<string, any>());
|
||||
const [charStatsVersion, setCharStatsVersion] = useState(0);
|
||||
const [nearbyObjects, setNearbyObjects] = useState<Map<string, any>>(new Map());
|
||||
const charsRef = useRef(characters);
|
||||
charsRef.current = characters;
|
||||
|
|
@ -64,6 +67,11 @@ export function useLiveData(): DashboardState {
|
|||
const d = msg as unknown as { character_name: string };
|
||||
// Bump inventory version so open inventory windows can re-fetch
|
||||
setInventoryVersion(v => v + 1);
|
||||
} else if (msg.type === 'character_stats') {
|
||||
// Store full character stats for CharacterWindow live updates
|
||||
const cs = msg as unknown as { character_name: string };
|
||||
characterStatsRef.current.set(cs.character_name, msg);
|
||||
setCharStatsVersion(v => v + 1);
|
||||
} else if (msg.type === 'equipment_cantrip_state') {
|
||||
const ecs = msg as unknown as { character_name: string; items: any[]; timestamp: string };
|
||||
equipmentCantripRef.current.set(ecs.character_name, ecs);
|
||||
|
|
@ -182,6 +190,8 @@ export function useLiveData(): DashboardState {
|
|||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const equipmentCantrips = useMemo(() => equipmentCantripRef.current, [equipCantripVersion]);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const characterStats = useMemo(() => characterStatsRef.current, [charStatsVersion]);
|
||||
|
||||
return { characters, serverHealth, totalRares, totalKills, recentRares, chatMessages, nearbyObjects, inventoryVersion, equipmentCantrips, socketRef };
|
||||
return { characters, serverHealth, totalRares, totalKills, recentRares, chatMessages, nearbyObjects, inventoryVersion, equipmentCantrips, characterStats, socketRef };
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue