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:
Erik 2026-04-14 16:02:49 +02:00
parent a5bd659876
commit d2c30b610b
30 changed files with 115 additions and 54 deletions

View file

@ -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 };
}