import { defineStore } from 'pinia'; import { ref, shallowRef, computed } from 'vue'; import type { AvatarSummary, GameEvent, MapMatrix, RegionSummary } from '../types/core'; import type { TickPayloadDTO, InitialStateDTO, MapResponseDTO } from '../types/api'; import { gameApi } from '../api/game'; export const useWorldStore = defineStore('world', () => { // --- State --- const year = ref(0); const month = ref(0); // 使用 shallowRef 存储大量数据以优化性能 // Key: Avatar ID const avatars = shallowRef>(new Map()); const events = shallowRef([]); const mapData = shallowRef([]); const regions = shallowRef>(new Map()); const isLoaded = ref(false); // --- Getters --- const avatarList = computed(() => Array.from(avatars.value.values())); // --- Actions --- function setTime(y: number, m: number) { year.value = y; month.value = m; } function updateAvatars(list: Partial[]) { const next = new Map(avatars.value); let changed = false; for (const av of list) { if (!av.id) continue; const existing = next.get(av.id); if (existing) { // Merge next.set(av.id, { ...existing, ...av } as AvatarSummary); changed = true; } else { // New Avatar? Only insert if it has enough info (at least name) // This handles newly born avatars sent by backend if (av.name) { next.set(av.id, av as AvatarSummary); changed = true; } } // Else: ignore. Do NOT insert new avatars from tick updates unless they have full info. } if (changed) { avatars.value = next; } } function addEvents(rawEvents: any[]) { if (!rawEvents || rawEvents.length === 0) return; // 转换 DTO -> Domain const newEvents: GameEvent[] = rawEvents.map(e => ({ id: e.id, text: e.text, content: e.content, year: e.year ?? year.value, month: e.month ?? month.value, timestamp: (e.year ?? year.value) * 12 + (e.month ?? month.value), relatedAvatarIds: e.related_avatar_ids || [], isMajor: e.is_major, isStory: e.is_story })); // 排序并保留最新的 N 条 const MAX_EVENTS = 300; const combined = [...newEvents, ...events.value]; combined.sort((a, b) => b.timestamp - a.timestamp); // 降序 events.value = combined.slice(0, MAX_EVENTS); } function handleTick(payload: TickPayloadDTO) { if (!isLoaded.value) return; setTime(payload.year, payload.month); // 检查并处理死亡事件,移除已死亡的角色 if (payload.events && Array.isArray(payload.events)) { const deathEvents = payload.events.filter((e: any) => { const c = e.content || ''; return c.includes('身亡') || c.includes('老死'); }); if (deathEvents.length > 0) { const next = new Map(avatars.value); let changed = false; for (const de of deathEvents) { if (de.related_avatar_ids && Array.isArray(de.related_avatar_ids)) { for (const id of de.related_avatar_ids) { if (next.has(id)) { next.delete(id); changed = true; } } } } if (changed) { avatars.value = next; } } } if (payload.avatars) updateAvatars(payload.avatars); if (payload.events) addEvents(payload.events); } async function initialize() { try { const [stateRes, mapRes] = await Promise.all([ gameApi.fetchInitialState(), gameApi.fetchMap() ]); // 1. Set Map mapData.value = mapRes.data; const regionMap = new Map(); mapRes.regions.forEach(r => regionMap.set(r.id, r)); regions.value = regionMap; // 2. Set State setTime(stateRes.year, stateRes.month); const avatarMap = new Map(); if (stateRes.avatars) { stateRes.avatars.forEach(av => avatarMap.set(av.id, av)); } avatars.value = avatarMap; // 3. Set Events (Initial state might have history?) events.value = []; if (stateRes.events) addEvents(stateRes.events); isLoaded.value = true; } catch (e) { console.error('Failed to initialize world', e); } } function reset() { year.value = 0; month.value = 0; avatars.value = new Map(); events.value = []; isLoaded.value = false; } return { year, month, avatars, avatarList, events, mapData, regions, isLoaded, initialize, handleTick, reset }; });