diff --git a/src/server/main.py b/src/server/main.py index 51da8de..c499a35 100644 --- a/src/server/main.py +++ b/src/server/main.py @@ -172,6 +172,29 @@ class ConnectionManager: manager = ConnectionManager() +def serialize_active_domains(world: World) -> List[dict]: + """序列化当前开启的秘境列表""" + domains_data = [] + if not world or not world.gathering_manager: + return [] + + for gathering in world.gathering_manager.gatherings: + # Check by class name to avoid circular imports + if gathering.__class__.__name__ == "HiddenDomain": + # Accessing _active_domains safely + active_domains = getattr(gathering, "_active_domains", []) + for d in active_domains: + domains_data.append({ + "id": d.id, + "name": d.name, + "desc": d.desc, + # Use str() to trigger Realm.__str__ which returns translated text + "max_realm": str(d.max_realm), + "danger_prob": d.danger_prob, + "drop_prob": d.drop_prob + }) + return domains_data + def serialize_events_for_client(events: List[Event]) -> List[dict]: """将事件转换为前端可用的结构。""" serialized: List[dict] = [] @@ -553,7 +576,8 @@ async def game_loop(): "month": world.month_stamp.get_month().value, "events": serialize_events_for_client(events), "avatars": avatar_updates, - "phenomenon": serialize_phenomenon(world.current_phenomenon) + "phenomenon": serialize_phenomenon(world.current_phenomenon), + "active_domains": serialize_active_domains(world) } await manager.broadcast(state) except Exception as e: diff --git a/web/src/components/layout/StatusBar.vue b/web/src/components/layout/StatusBar.vue index 97f1128..4b4870f 100644 --- a/web/src/components/layout/StatusBar.vue +++ b/web/src/components/layout/StatusBar.vue @@ -4,6 +4,7 @@ import { useSocketStore } from '../../stores/socket' import { ref, computed } from 'vue' import { NPopover, NModal, NList, NListItem, NTag, NEmpty, useMessage } from 'naive-ui' import { useI18n } from 'vue-i18n' +import StatusWidget from './StatusWidget.vue' const { t } = useI18n() const store = useWorldStore() @@ -17,6 +18,17 @@ const phenomenonColor = computed(() => { return getRarityColor(p.rarity); }) +const domainLabel = computed(() => { + const count = store.activeDomains.length; + return count > 0 + ? t('game.status_bar.hidden_domain.label_active', { count }) + : t('game.status_bar.hidden_domain.label'); +}); + +const domainColor = computed(() => { + return store.activeDomains.length > 0 ? '#fa8c16' : '#666'; // 有秘境时亮橙色,否则灰色 +}); + function getRarityColor(rarity: string) { switch (rarity) { case 'N': return '#ccc'; @@ -26,17 +38,8 @@ function getRarityColor(rarity: string) { default: return '#ccc'; } } - -async function openPhenomenonSelector() { - showSelector.value = true; - await store.getPhenomenaList(); -} - -async function handleSelect(id: number, name: string) { - await store.changePhenomenon(id); - showSelector.value = false; - message.success(t('game.status_bar.change_success', { name })); -} +// ... +// ... @@ -49,18 +52,14 @@ async function handleSelect(id: number, name: string) { {{ store.year }}{{ t('common.year') }} {{ store.month }}{{ t('common.month') }} - - | - - - - [{{ store.currentPhenomenon.name }}] - - + + {{ store.currentPhenomenon.name }} @@ -79,8 +78,18 @@ async function handleSelect(id: number, name: string) { {{ t('game.status_bar.click_to_change') }} - - + + + + + @@ -158,26 +167,7 @@ async function handleSelect(id: number, name: string) { gap: 10px; } -.phenomenon { - display: flex; - align-items: center; - gap: 10px; -} - -.divider { - color: #444; -} - -.phenomenon-name { - cursor: pointer; - font-weight: bold; - transition: opacity 0.2s; -} - -.phenomenon-name:hover { - opacity: 0.8; - text-decoration: underline; -} +/* .phenomenon, .divider, .phenomenon-name REMOVED (moved to StatusWidget) */ .phenomenon-card { padding: 4px 0; diff --git a/web/src/components/layout/StatusWidget.vue b/web/src/components/layout/StatusWidget.vue new file mode 100644 index 0000000..a6f93da --- /dev/null +++ b/web/src/components/layout/StatusWidget.vue @@ -0,0 +1,106 @@ + + + + + | + + + + {{ props.label }} + + + + + + + + + + + {{ title }} + + + + + + {{ item.name }} + + {{ item.max_realm }} + + + {{ item.desc }} + + 💀 {{ (item.danger_prob * 100).toFixed(0) }}% + 🎁 {{ (item.drop_prob * 100).toFixed(0) }}% + + + + + + + + + + + + \ No newline at end of file diff --git a/web/src/locales/en-US.json b/web/src/locales/en-US.json index 2020272..342f0d8 100644 --- a/web/src/locales/en-US.json +++ b/web/src/locales/en-US.json @@ -230,7 +230,15 @@ "change_success": "Phenomenon changed to: {name}", "click_to_change": "(Click to change phenomenon)", "author_bilibili": "Bilibili", - "author_github": "Github" + "author_github": "Github", + "hidden_domain": { + "label": "[Hidden Domain]", + "label_active": "[Domain Opened: {count}]", + "title": "Opened Hidden Domains", + "empty": "No hidden domains currently open", + "danger": "Danger", + "drop": "Fortune" + } }, "controls": { "resume": "Resume Game", diff --git a/web/src/locales/zh-CN.json b/web/src/locales/zh-CN.json index 0cf9dd3..d980783 100644 --- a/web/src/locales/zh-CN.json +++ b/web/src/locales/zh-CN.json @@ -230,7 +230,15 @@ "change_success": "天象已更易为:{name}", "click_to_change": "(点击可更易天象)", "author_bilibili": "B站空间", - "author_github": "Github仓库" + "author_github": "Github仓库", + "hidden_domain": { + "label": "[秘境]", + "label_active": "[秘境开启: {count}]", + "title": "当前开启秘境", + "empty": "当前暂无秘境开启", + "danger": "凶险", + "drop": "机缘" + } }, "controls": { "resume": "继续游戏", diff --git a/web/src/stores/world.ts b/web/src/stores/world.ts index 2ba200f..28f7b52 100644 --- a/web/src/stores/world.ts +++ b/web/src/stores/world.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia'; import { ref, shallowRef, computed } from 'vue'; -import type { AvatarSummary, GameEvent, MapMatrix, RegionSummary, CelestialPhenomenon } from '../types/core'; +import type { AvatarSummary, GameEvent, MapMatrix, RegionSummary, CelestialPhenomenon, HiddenDomainInfo } from '../types/core'; import type { TickPayloadDTO, InitialStateDTO } from '../types/api'; import type { FetchEventsParams } from '../types/api'; import { worldApi, eventApi } from '../api'; @@ -33,6 +33,9 @@ export const useWorldStore = defineStore('world', () => { const currentPhenomenon = ref(null); const phenomenaList = shallowRef([]); + // 秘境列表 + const activeDomains = shallowRef([]); + // 请求计数器,用于处理 loadEvents 的竞态条件。 let eventsRequestId = 0; // 请求计数器,用于处理 fetchState 的竞态条件。 @@ -110,6 +113,13 @@ export const useWorldStore = defineStore('world', () => { if (payload.phenomenon !== undefined) { currentPhenomenon.value = payload.phenomenon; } + // 处理秘境同步 + if (payload.active_domains !== undefined) { + activeDomains.value = payload.active_domains; + } else { + // 如果后端不传,说明本回合无秘境,清空 + activeDomains.value = []; + } } function applyStateSnapshot(stateRes: InitialStateDTO) { @@ -126,6 +136,7 @@ export const useWorldStore = defineStore('world', () => { eventsFilter.value = {}; currentPhenomenon.value = stateRes.phenomenon || null; isLoaded.value = true; + activeDomains.value = []; } // 提前加载地图数据(在 LLM 初始化期间可用)。 @@ -226,6 +237,7 @@ export const useWorldStore = defineStore('world', () => { eventsFilter.value = {}; isLoaded.value = false; currentPhenomenon.value = null; + activeDomains.value = []; } // --- 事件分页 --- @@ -358,6 +370,7 @@ export const useWorldStore = defineStore('world', () => { loadMoreEvents, resetEvents, getPhenomenaList, - changePhenomenon + changePhenomenon, + activeDomains }; }); diff --git a/web/src/types/api.ts b/web/src/types/api.ts index 190b65a..bd60ae8 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -3,7 +3,7 @@ * 这些类型严格对应后端接口返回的 JSON 结构。 */ -import type { MapMatrix, CelestialPhenomenon } from './core'; +import type { MapMatrix, CelestialPhenomenon, HiddenDomainInfo } from './core'; // --- 通用响应 --- @@ -39,6 +39,7 @@ export interface TickPayloadDTO { avatars?: Array>; events?: unknown[]; phenomenon?: CelestialPhenomenon | null; + active_domains?: HiddenDomainInfo[]; } export interface MapResponseDTO { diff --git a/web/src/types/core.ts b/web/src/types/core.ts index 347f81a..bc52e9b 100644 --- a/web/src/types/core.ts +++ b/web/src/types/core.ts @@ -183,6 +183,18 @@ export interface CelestialPhenomenon { effect_desc?: string; } +// web/src/types/core.ts + +// 新增秘境信息接口 +export interface HiddenDomainInfo { + id: string; + name: string; + desc: string; + max_realm: string; // 限制境界 + danger_prob: number; // 凶险度 (0.0 - 1.0) + drop_prob: number; // 机缘度 (0.0 - 1.0) +} + // --- 事件 (Events) --- export interface GameEvent {