mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
feat: 重构战报弹窗与模拟器视图,优化UI与逻辑
重构BattleReportDialog和BattleSimulatorView相关静态资源,替换旧版JS/CSS文件,提升界面一致性和交互体验。新增和优化空状态、滚动区域等通用UI组件,移除部分冗余composable,完善多语言内容。引入导弹逻辑,补充版本检测工具,提升整体代码结构和可维护性。
This commit is contained in:
1192
src/App.vue
1192
src/App.vue
@@ -13,15 +13,67 @@
|
||||
<!-- 星球信息 -->
|
||||
<SidebarGroup v-if="planet" class="border-b group-data-[collapsible=icon]:hidden">
|
||||
<div class="px-4 py-3 space-y-2 text-sm">
|
||||
<div>
|
||||
<p class="font-semibold mb-1">
|
||||
{{ planet.name }}
|
||||
<Badge v-if="planet.isMoon" variant="secondary" class="ml-1 text-xs">{{ t('planet.moon') }}</Badge>
|
||||
</p>
|
||||
<p class="text-muted-foreground text-xs">
|
||||
[{{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }}]
|
||||
</p>
|
||||
</div>
|
||||
<!-- 星球切换器 -->
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
variant="outline"
|
||||
class="w-full justify-between h-auto px-3 py-2.5 border-2 hover:bg-accent hover:border-primary transition-colors"
|
||||
>
|
||||
<div class="flex items-start gap-2.5 flex-1 min-w-0">
|
||||
<Globe class="h-5 w-5 flex-shrink-0 mt-0.5 text-primary" />
|
||||
<div class="flex-1 min-w-0 text-left">
|
||||
<div class="text-[10px] text-muted-foreground uppercase tracking-wider mb-0.5">
|
||||
{{ t('planet.currentPlanet') }}
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 mb-0.5">
|
||||
<span class="truncate font-semibold text-sm">{{ planet.name }}</span>
|
||||
<Badge v-if="planet.isMoon" variant="secondary" class="text-[10px] px-1 py-0 h-4">
|
||||
{{ t('planet.moon') }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="text-[11px] text-muted-foreground">
|
||||
[{{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }}]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ChevronsUpDown class="h-4 w-4 flex-shrink-0 text-muted-foreground ml-2" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-72 p-0" side="bottom" align="start">
|
||||
<div class="p-2">
|
||||
<div class="px-2 py-1.5 mb-1 text-xs font-semibold text-muted-foreground">
|
||||
{{ t('planet.switchPlanet') }}
|
||||
</div>
|
||||
<div class="space-y-0.5 max-h-80 overflow-y-auto">
|
||||
<Button
|
||||
v-for="p in gameStore.player.planets"
|
||||
:key="p.id"
|
||||
@click="switchToPlanet(p.id)"
|
||||
:variant="p.id === planet.id ? 'secondary' : 'ghost'"
|
||||
class="w-full justify-start h-auto py-2 px-2"
|
||||
size="sm"
|
||||
>
|
||||
<div class="flex items-start gap-2 w-full min-w-0">
|
||||
<Globe class="h-4 w-4 flex-shrink-0 mt-0.5" :class="p.id === planet.id ? 'text-primary' : ''" />
|
||||
<div class="flex-1 min-w-0 text-left">
|
||||
<div class="flex items-center gap-1.5 mb-0.5">
|
||||
<span class="truncate font-medium text-sm">{{ p.name }}</span>
|
||||
<Badge v-if="p.isMoon" variant="outline" class="text-[10px] px-1 py-0 h-4">
|
||||
{{ t('planet.moon') }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="text-[11px] text-muted-foreground">
|
||||
[{{ p.position.galaxy }}:{{ p.position.system }}:{{ p.position.position }}]
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<!-- 玩家积分显示 -->
|
||||
<div class="bg-muted/50 rounded-lg p-2">
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -50,9 +102,19 @@
|
||||
<component :is="item.icon" />
|
||||
<span>{{ item.name.value }}</span>
|
||||
<!-- 未读消息数量 -->
|
||||
<SidebarMenuBadge v-if="item.path === '/messages' && unreadMessagesCount > 0">
|
||||
<SidebarMenuBadge
|
||||
v-if="item.path === '/messages' && unreadMessagesCount > 0"
|
||||
class="bg-destructive text-destructive-foreground"
|
||||
>
|
||||
{{ unreadMessagesCount }}
|
||||
</SidebarMenuBadge>
|
||||
<!-- 正在执行的舰队任务数量 -->
|
||||
<SidebarMenuBadge
|
||||
v-if="item.path === '/fleet' && activeFleetMissionsCount > 0"
|
||||
class="bg-primary text-primary-foreground"
|
||||
>
|
||||
{{ activeFleetMissionsCount }}
|
||||
</SidebarMenuBadge>
|
||||
</RouterLink>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
@@ -72,7 +134,11 @@
|
||||
<span>{{ localeNames[gameStore.locale] }}</span>
|
||||
</SidebarMenuButton>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-48 p-2" side="right" align="end">
|
||||
<PopoverContent
|
||||
class="w-48 p-2"
|
||||
:side="sidebarOpen || innerWidth < 768 ? 'top' : 'right'"
|
||||
:align="sidebarOpen || innerWidth < 768 ? 'center' : 'end'"
|
||||
>
|
||||
<div class="space-y-1">
|
||||
<Button
|
||||
v-for="locale in locales"
|
||||
@@ -111,49 +177,139 @@
|
||||
|
||||
<!-- 主内容区 -->
|
||||
<SidebarInset>
|
||||
<div class="flex flex-col h-full overflow-hidden">
|
||||
<!-- 顶部资源栏 -->
|
||||
<header v-if="planet" class="bg-card border-b px-4 sm:px-6 py-6.5 shadow-md">
|
||||
<div class="flex items-center justify-between gap-3 sm:gap-6">
|
||||
<!-- 汉堡菜单(移动端)- 左侧占位 -->
|
||||
<div class="lg:flex-1">
|
||||
<SidebarTrigger class="lg:hidden" />
|
||||
</div>
|
||||
<div class="flex flex-col h-full overflow-hidden pt-[60px]">
|
||||
<!-- 顶部资源栏 - 固定定位 -->
|
||||
<header
|
||||
v-if="planet"
|
||||
class="fixed top-0 right-0 left-0 z-40 bg-card border-b px-4 sm:px-6 py-3 shadow-md"
|
||||
:class="sidebarOpen ? 'lg:left-[var(--sidebar-width)]' : 'lg:left-[var(--sidebar-width-icon)]'"
|
||||
>
|
||||
<div class="flex flex-col gap-3">
|
||||
<!-- 第一行:菜单、资源预览、状态 -->
|
||||
<div class="grid items-center gap-3 sm:gap-6" style="grid-template-columns: auto 1fr auto">
|
||||
<!-- 左侧:汉堡菜单(移动端)/ 占位(PC端) -->
|
||||
<div>
|
||||
<SidebarTrigger class="lg:hidden" />
|
||||
</div>
|
||||
|
||||
<!-- 资源显示 - PC端居中 -->
|
||||
<div class="flex items-center gap-3 sm:gap-6 flex-1 lg:flex-none overflow-x-auto lg:justify-center">
|
||||
<div v-for="resourceType in resourceTypes" :key="resourceType.key" class="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
|
||||
<ResourceIcon :type="resourceType.key" size="md" />
|
||||
<div class="min-w-0">
|
||||
<!-- 所有资源统一显示:当前值/容量 -->
|
||||
<p
|
||||
class="text-xs sm:text-sm font-medium truncate"
|
||||
:class="getResourceColor(planet.resources[resourceType.key], capacity?.[resourceType.key] || Infinity)"
|
||||
>
|
||||
{{ formatNumber(planet.resources[resourceType.key]) }} / {{ formatNumber(capacity?.[resourceType.key] || 0) }}
|
||||
</p>
|
||||
<p class="text-[10px] sm:text-xs text-muted-foreground truncate">
|
||||
+{{ formatNumber(Math.round((production?.[resourceType.key] || 0) / 60)) }}/{{ t('resources.perMinute') }}
|
||||
</p>
|
||||
<!-- 资源显示 - PC端居中,移动端可折叠 -->
|
||||
<div :class="['flex items-center gap-3 sm:gap-6 justify-center', resourceBarExpanded ? 'hidden' : 'overflow-x-auto']">
|
||||
<div v-for="resourceType in resourceTypes" :key="resourceType.key" class="flex items-center gap-1.5 sm:gap-2 flex-shrink-0">
|
||||
<ResourceIcon :type="resourceType.key" size="md" />
|
||||
<div class="min-w-0">
|
||||
<!-- 电力显示净产量和效率 -->
|
||||
<template v-if="resourceType.key === 'energy'">
|
||||
<p
|
||||
class="text-xs sm:text-sm font-medium truncate"
|
||||
:class="netEnergy >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'"
|
||||
>
|
||||
{{ netEnergy >= 0 ? '+' : '' }}{{ formatNumber(netEnergy) }}
|
||||
</p>
|
||||
<p class="text-[10px] sm:text-xs text-muted-foreground truncate">
|
||||
{{ formatNumber(production?.energy || 0) }} / {{ formatNumber(energyConsumption) }}
|
||||
</p>
|
||||
</template>
|
||||
<!-- 其他资源统一显示:当前值/容量 -->
|
||||
<template v-else>
|
||||
<p
|
||||
class="text-xs sm:text-sm font-medium truncate"
|
||||
:class="getResourceColor(planet.resources[resourceType.key], capacity?.[resourceType.key] || Infinity)"
|
||||
>
|
||||
{{ formatNumber(planet.resources[resourceType.key]) }} / {{ formatNumber(capacity?.[resourceType.key] || 0) }}
|
||||
</p>
|
||||
<p class="text-[10px] sm:text-xs text-muted-foreground truncate">
|
||||
+{{ formatNumber(Math.round((production?.[resourceType.key] || 0) / 60)) }}/{{ t('resources.perMinute') }}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧状态 - 右侧占位 -->
|
||||
<div class="flex items-center gap-2 sm:gap-4 flex-shrink-0 lg:flex-1 lg:justify-end">
|
||||
<!-- 建造队列状态 -->
|
||||
<div v-if="planet.buildQueue.length > 0" class="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.building') }}</span>
|
||||
</div>
|
||||
<div v-if="gameStore.player.researchQueue.length > 0" class="flex items-center gap-1.5 sm:gap-2 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.researching') }}</span>
|
||||
<!-- 右侧:展开按钮(仅移动端) + 状态 -->
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0 justify-end">
|
||||
<!-- 移动端展开按钮 -->
|
||||
<Button @click="resourceBarExpanded = !resourceBarExpanded" variant="ghost" size="sm" class="lg:hidden h-8 w-8 p-0">
|
||||
<ChevronDown v-if="!resourceBarExpanded" class="h-4 w-4" />
|
||||
<ChevronUp v-else class="h-4 w-4" />
|
||||
</Button>
|
||||
|
||||
<!-- 建造队列状态 -->
|
||||
<div v-if="planet.buildQueue.length > 0" class="flex items-center gap-1.5 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.building') }}</span>
|
||||
</div>
|
||||
<div v-if="gameStore.player.researchQueue.length > 0" class="flex items-center gap-1.5 text-xs sm:text-sm">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse" />
|
||||
<span class="text-muted-foreground hidden sm:inline">{{ t('queue.researching') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 展开的资源详情(仅移动端且展开时显示) - absolute定位覆盖在内容上,带过渡动画 -->
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-300 ease-out"
|
||||
enter-from-class="opacity-0 -translate-y-2"
|
||||
enter-to-class="opacity-100 translate-y-0"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 translate-y-0"
|
||||
leave-to-class="opacity-0 -translate-y-2"
|
||||
>
|
||||
<div
|
||||
v-if="planet && resourceBarExpanded"
|
||||
class="fixed top-[60px] right-0 left-0 z-30 bg-card border-b px-4 py-3 shadow-md lg:hidden"
|
||||
:class="sidebarOpen ? 'lg:left-[var(--sidebar-width)]' : 'lg:left-[var(--sidebar-width-icon)]'"
|
||||
>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div v-for="resourceType in resourceTypes" :key="resourceType.key" class="bg-muted/50 rounded-lg p-2.5">
|
||||
<div class="flex items-center justify-center gap-2 mb-1.5">
|
||||
<ResourceIcon :type="resourceType.key" size="md" />
|
||||
<span class="text-xs font-medium text-muted-foreground">{{ t(`resources.${resourceType.key}`) }}</span>
|
||||
</div>
|
||||
<div class="space-y-0.5 text-center">
|
||||
<!-- 电力显示净产量和效率 -->
|
||||
<template v-if="resourceType.key === 'energy'">
|
||||
<p
|
||||
class="text-sm font-semibold"
|
||||
:class="netEnergy >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'"
|
||||
>
|
||||
{{ netEnergy >= 0 ? '+' : '' }}{{ formatNumber(netEnergy) }}
|
||||
</p>
|
||||
<p class="text-[10px] text-muted-foreground">
|
||||
{{ t('resources.production') }}: {{ formatNumber(production?.energy || 0) }} / {{ formatNumber(energyConsumption) }}
|
||||
</p>
|
||||
</template>
|
||||
<!-- 其他资源统一显示:当前值/容量 -->
|
||||
<template v-else>
|
||||
<p
|
||||
class="text-sm font-semibold"
|
||||
:class="getResourceColor(planet.resources[resourceType.key], capacity?.[resourceType.key] || Infinity)"
|
||||
>
|
||||
{{ formatNumber(planet.resources[resourceType.key]) }}
|
||||
</p>
|
||||
<p class="text-[10px] text-muted-foreground">
|
||||
{{ t('resources.capacity') }}: {{ formatNumber(capacity?.[resourceType.key] || 0) }}
|
||||
</p>
|
||||
<p class="text-[10px] text-muted-foreground">
|
||||
{{ t('resources.production') }}: +{{ formatNumber(Math.round((production?.[resourceType.key] || 0) / 60)) }}/{{
|
||||
t('resources.perMinute')
|
||||
}}
|
||||
</p>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- 即将到来的敌对舰队警告 -->
|
||||
<IncomingFleetAlerts
|
||||
v-if="gameStore.player.incomingFleetAlerts && gameStore.player.incomingFleetAlerts.length > 0"
|
||||
:alerts="gameStore.player.incomingFleetAlerts"
|
||||
@mark-as-read="removeIncomingFleetAlert"
|
||||
/>
|
||||
|
||||
<!-- 建造队列 -->
|
||||
<div
|
||||
v-if="planet && (planet.buildQueue.length > 0 || gameStore.player.researchQueue.length > 0)"
|
||||
@@ -232,11 +388,13 @@
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{{ confirmDialogTitle }}</AlertDialogTitle>
|
||||
<AlertDialogDescription>{{ confirmDialogMessage }}</AlertDialogDescription>
|
||||
<AlertDialogDescription class="whitespace-pre-line">
|
||||
{{ confirmDialogMessage }}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{{ t('common.cancel') }}</AlertDialogCancel>
|
||||
<AlertDialogAction @click="handleConfirmAction">{{ t('common.confirm') }}</AlertDialogAction>
|
||||
<AlertDialogAction @click="handleConfirmDialogConfirm">{{ t('common.confirm') }}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
@@ -244,15 +402,20 @@
|
||||
<!-- 详情弹窗 -->
|
||||
<DetailDialog />
|
||||
|
||||
<!-- 更新弹窗 -->
|
||||
<UpdateDialog v-model:open="showUpdateDialog" :version-info="updateInfo" />
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<Sonner position="top-center" />
|
||||
</SidebarProvider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, computed, ref } from 'vue'
|
||||
import { onMounted, onUnmounted, computed, ref, watch } from 'vue'
|
||||
import { RouterView, RouterLink } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useUniverseStore } from '@/stores/universeStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import { useTheme } from '@/composables/useTheme'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { localeNames, detectBrowserLocale, type Locale } from '@/locales'
|
||||
@@ -260,6 +423,7 @@
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'
|
||||
import IncomingFleetAlerts from '@/components/IncomingFleetAlerts.vue'
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -275,8 +439,6 @@
|
||||
SidebarTrigger
|
||||
} from '@/components/ui/sidebar'
|
||||
import ResourceIcon from '@/components/ResourceIcon.vue'
|
||||
import DetailDialog from '@/components/DetailDialog.vue'
|
||||
import Sonner from '@/components/ui/sonner/Sonner.vue'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -287,6 +449,12 @@
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import DetailDialog from '@/components/DetailDialog.vue'
|
||||
import UpdateDialog from '@/components/UpdateDialog.vue'
|
||||
import Sonner from '@/components/ui/sonner/Sonner.vue'
|
||||
import { MissionType } from '@/types/game'
|
||||
import type { BuildQueueItem, FleetMission, NPC, IncomingFleetAlert, MissileAttack } from '@/types/game'
|
||||
import type { VersionInfo } from '@/utils/versionCheck'
|
||||
import { formatNumber, formatTime, getResourceColor } from '@/utils/format'
|
||||
import {
|
||||
Moon,
|
||||
@@ -304,62 +472,737 @@
|
||||
Languages,
|
||||
Settings,
|
||||
Wrench,
|
||||
ChevronsLeft
|
||||
ChevronsLeft,
|
||||
ChevronsUpDown,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Handshake
|
||||
} from 'lucide-vue-next'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as planetLogic from '@/logic/planetLogic'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
import * as buildingValidation from '@/logic/buildingValidation'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import { useGameLifecycle } from '@/composables/useGameLifecycle'
|
||||
import { useMissionHandler } from '@/composables/useMissionHandler'
|
||||
import { useNPCHandler } from '@/composables/useNPCHandler'
|
||||
import { useQueueHandler } from '@/composables/useQueueHandler'
|
||||
import { useGameUpdate } from '@/composables/useGameUpdate'
|
||||
import { migrateGameData } from '@/utils/migration'
|
||||
import * as researchValidation from '@/logic/researchValidation'
|
||||
import * as fleetLogic from '@/logic/fleetLogic'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import * as npcGrowthLogic from '@/logic/npcGrowthLogic'
|
||||
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
|
||||
import * as diplomaticLogic from '@/logic/diplomaticLogic'
|
||||
import pkg from '../package.json'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { migrateGameData } from '@/utils/migration'
|
||||
import { checkLatestVersion } from '@/utils/versionCheck'
|
||||
|
||||
// 执行数据迁移(在 store 初始化之前)
|
||||
migrateGameData()
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const universeStore = useUniverseStore()
|
||||
const npcStore = useNPCStore()
|
||||
const { isDark } = useTheme()
|
||||
const { t } = useI18n()
|
||||
|
||||
// ConfirmDialog 状态
|
||||
const confirmDialogOpen = ref(false)
|
||||
const confirmDialogTitle = ref('')
|
||||
const confirmDialogMessage = ref('')
|
||||
const innerWidth = computed(() => window.innerWidth)
|
||||
const confirmDialogAction = ref<(() => void) | null>(null)
|
||||
|
||||
// 更新弹窗状态
|
||||
const showUpdateDialog = ref(false)
|
||||
const updateInfo = ref<VersionInfo | null>(null)
|
||||
|
||||
const handleConfirmDialogConfirm = () => {
|
||||
if (confirmDialogAction.value) {
|
||||
confirmDialogAction.value()
|
||||
}
|
||||
confirmDialogOpen.value = false
|
||||
}
|
||||
|
||||
// 所有可用的语言选项
|
||||
const locales: Locale[] = ['zh-CN', 'zh-TW', 'en', 'de', 'ru', 'ko', 'ja']
|
||||
|
||||
// 侧边栏状态(不持久化,根据屏幕尺寸初始化)
|
||||
// PC端(≥1024px)默认打开,移动端默认关闭
|
||||
const sidebarOpen = ref(window.innerWidth >= 1024)
|
||||
|
||||
// 初始化 composables
|
||||
const { initGame } = useGameLifecycle()
|
||||
// 移动端资源栏展开状态
|
||||
const resourceBarExpanded = ref(false)
|
||||
|
||||
const { processMissionArrival, processMissionReturn } = useMissionHandler(t)
|
||||
const initGame = async () => {
|
||||
const shouldInit = gameLogic.shouldInitializeGame(gameStore.player.planets)
|
||||
if (!shouldInit) {
|
||||
const now = Date.now()
|
||||
|
||||
const { processNPCMissionArrival, processNPCMissionReturn, updateNPCGrowth, updateNPCBehavior } = useNPCHandler()
|
||||
// 计算离线收益(直接同步计算)
|
||||
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now)
|
||||
gameStore.player.planets.forEach(planet => {
|
||||
resourceLogic.updatePlanetResources(planet, now, bonuses)
|
||||
})
|
||||
|
||||
const { handleCancelBuild, handleCancelResearch, getItemName, getRemainingTime, getQueueProgress } = useQueueHandler(
|
||||
t,
|
||||
confirmDialogOpen,
|
||||
confirmDialogTitle,
|
||||
confirmDialogMessage,
|
||||
confirmDialogAction
|
||||
)
|
||||
// 只在没有NPC星球时才生成(首次加载已有玩家数据时)
|
||||
if (Object.keys(universeStore.planets).length === 0) {
|
||||
generateNPCPlanets()
|
||||
}
|
||||
return
|
||||
}
|
||||
gameStore.player = gameLogic.initializePlayer(gameStore.player.id, t('common.playerName'))
|
||||
const initialPlanet = planetLogic.createInitialPlanet(gameStore.player.id, t('planet.homePlanet'))
|
||||
gameStore.player.planets = [initialPlanet]
|
||||
gameStore.currentPlanetId = initialPlanet.id
|
||||
// 新玩家初始化时生成NPC星球
|
||||
generateNPCPlanets()
|
||||
}
|
||||
|
||||
const { updateGame } = useGameUpdate(
|
||||
processMissionArrival,
|
||||
processMissionReturn,
|
||||
processNPCMissionArrival,
|
||||
processNPCMissionReturn,
|
||||
updateNPCGrowth,
|
||||
updateNPCBehavior
|
||||
)
|
||||
const generateNPCPlanets = () => {
|
||||
const npcCount = 200
|
||||
for (let i = 0; i < npcCount; i++) {
|
||||
const position = gameLogic.generateRandomPosition()
|
||||
const key = gameLogic.generatePositionKey(position.galaxy, position.system, position.position)
|
||||
if (universeStore.planets[key]) continue
|
||||
const npcPlanet = planetLogic.createNPCPlanet(i, position, t('planet.planetPrefix'))
|
||||
universeStore.planets[key] = npcPlanet
|
||||
}
|
||||
}
|
||||
|
||||
const updateGame = async () => {
|
||||
if (gameStore.isPaused) return
|
||||
const now = Date.now()
|
||||
gameStore.gameTime = now
|
||||
// 检查军官过期
|
||||
gameLogic.checkOfficersExpiration(gameStore.player.officers, now)
|
||||
// 处理游戏更新(建造队列、研究队列等)
|
||||
const result = gameLogic.processGameUpdate(gameStore.player, now)
|
||||
gameStore.player.researchQueue = result.updatedResearchQueue
|
||||
// 处理舰队任务
|
||||
gameStore.player.fleetMissions.forEach(mission => {
|
||||
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
|
||||
processMissionArrival(mission)
|
||||
} else if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
|
||||
processMissionReturn(mission)
|
||||
}
|
||||
})
|
||||
|
||||
// 处理导弹攻击任务(使用反向循环以便安全删除)
|
||||
for (let i = gameStore.player.missileAttacks.length - 1; i >= 0; i--) {
|
||||
const missileAttack = gameStore.player.missileAttacks[i]
|
||||
if (missileAttack && missileAttack.status === 'flying' && now >= missileAttack.arrivalTime) {
|
||||
await processMissileAttackArrival(missileAttack)
|
||||
// 导弹攻击是单程的,到达后直接从数组中移除
|
||||
gameStore.player.missileAttacks.splice(i, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理NPC舰队任务
|
||||
npcStore.npcs.forEach(npc => {
|
||||
if (npc.fleetMissions) {
|
||||
npc.fleetMissions.forEach(mission => {
|
||||
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
|
||||
processNPCMissionArrival(npc, mission)
|
||||
} else if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
|
||||
processNPCMissionReturn(npc, mission)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// NPC成长系统更新
|
||||
updateNPCGrowth(1)
|
||||
|
||||
// NPC行为系统更新(侦查和攻击决策)
|
||||
updateNPCBehavior(1)
|
||||
}
|
||||
|
||||
const processMissionArrival = async (mission: FleetMission) => {
|
||||
// 从宇宙星球地图中查找目标星球
|
||||
const targetKey = gameLogic.generatePositionKey(
|
||||
mission.targetPosition.galaxy,
|
||||
mission.targetPosition.system,
|
||||
mission.targetPosition.position
|
||||
)
|
||||
// 先从玩家星球中查找,再从宇宙地图中查找
|
||||
const targetPlanet =
|
||||
gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
) || universeStore.planets[targetKey]
|
||||
|
||||
// 获取起始星球名称(用于报告)
|
||||
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
|
||||
const originPlanetName = originPlanet?.name || t('fleetView.unknownPlanet')
|
||||
|
||||
if (mission.missionType === MissionType.Transport) {
|
||||
const result = fleetLogic.processTransportArrival(mission, targetPlanet, gameStore.player, npcStore.npcs)
|
||||
// 生成运输任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Transport,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName:
|
||||
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
|
||||
success: result.success,
|
||||
message: result.success ? t('missionReports.transportSuccess') : t('missionReports.transportFailed'),
|
||||
details: {
|
||||
transportedResources: mission.cargo
|
||||
},
|
||||
read: false
|
||||
})
|
||||
} else if (mission.missionType === MissionType.Attack) {
|
||||
const attackResult = await fleetLogic.processAttackArrival(mission, targetPlanet, gameStore.player, null, gameStore.player.planets)
|
||||
if (attackResult) {
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
|
||||
// 检查是否攻击了NPC星球,更新外交关系
|
||||
if (targetPlanet) {
|
||||
const targetNpc = npcStore.npcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
|
||||
if (targetNpc) {
|
||||
diplomaticLogic.handleAttackReputation(gameStore.player, targetNpc, attackResult.battleResult, npcStore.npcs, gameStore.locale)
|
||||
}
|
||||
}
|
||||
|
||||
if (attackResult.moon) {
|
||||
gameStore.player.planets.push(attackResult.moon)
|
||||
}
|
||||
if (attackResult.debrisField) {
|
||||
// 将残骸场添加到游戏状态
|
||||
universeStore.debrisFields[attackResult.debrisField.id] = attackResult.debrisField
|
||||
}
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Colonize) {
|
||||
const newPlanet = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player, t('planet.colonyPrefix'))
|
||||
// 生成殖民任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Colonize,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: newPlanet?.id,
|
||||
targetPlanetName: newPlanet?.name,
|
||||
success: !!newPlanet,
|
||||
message: newPlanet ? t('missionReports.colonizeSuccess') : t('missionReports.colonizeFailed'),
|
||||
details: newPlanet
|
||||
? {
|
||||
newPlanetId: newPlanet.id,
|
||||
newPlanetName: newPlanet.name
|
||||
}
|
||||
: undefined,
|
||||
read: false
|
||||
})
|
||||
if (newPlanet) {
|
||||
gameStore.player.planets.push(newPlanet)
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Spy) {
|
||||
const spyReport = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player, null, npcStore.npcs)
|
||||
if (spyReport) gameStore.player.spyReports.push(spyReport)
|
||||
} else if (mission.missionType === MissionType.Deploy) {
|
||||
const deployed = fleetLogic.processDeployArrival(mission, targetPlanet, gameStore.player.id)
|
||||
// 生成部署任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Deploy,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName:
|
||||
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
|
||||
success: deployed,
|
||||
message: deployed ? t('missionReports.deploySuccess') : t('missionReports.deployFailed'),
|
||||
details: {
|
||||
deployedFleet: mission.fleet
|
||||
},
|
||||
read: false
|
||||
})
|
||||
if (deployed) {
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
return
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Recycle) {
|
||||
// 处理回收任务
|
||||
const debrisId = `debris_${mission.targetPosition.galaxy}_${mission.targetPosition.system}_${mission.targetPosition.position}`
|
||||
const debrisField = universeStore.debrisFields[debrisId]
|
||||
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
|
||||
|
||||
// 生成回收任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Recycle,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
success: !!recycleResult,
|
||||
message: recycleResult ? t('missionReports.recycleSuccess') : t('missionReports.recycleFailed'),
|
||||
details: recycleResult
|
||||
? {
|
||||
recycledResources: recycleResult.collectedResources,
|
||||
remainingDebris: recycleResult.remainingDebris || undefined
|
||||
}
|
||||
: undefined,
|
||||
read: false
|
||||
})
|
||||
|
||||
if (recycleResult && debrisField) {
|
||||
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
|
||||
// 更新残骸场
|
||||
universeStore.debrisFields[debrisId] = {
|
||||
id: debrisField.id,
|
||||
position: debrisField.position,
|
||||
resources: recycleResult.remainingDebris,
|
||||
createdAt: debrisField.createdAt,
|
||||
expiresAt: debrisField.expiresAt
|
||||
}
|
||||
} else {
|
||||
// 残骸场已被完全收集,删除
|
||||
delete universeStore.debrisFields[debrisId]
|
||||
}
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Destroy) {
|
||||
// 处理行星毁灭任务
|
||||
const destroyResult = fleetLogic.processDestroyArrival(mission, targetPlanet, gameStore.player)
|
||||
|
||||
// 生成毁灭任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Destroy,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName: targetPlanet?.name,
|
||||
success: destroyResult?.success || false,
|
||||
message: destroyResult?.success ? t('missionReports.destroySuccess') : t('missionReports.destroyFailed'),
|
||||
details: destroyResult?.success
|
||||
? {
|
||||
destroyedPlanetName:
|
||||
targetPlanet?.name ||
|
||||
`[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`
|
||||
}
|
||||
: undefined,
|
||||
read: false
|
||||
})
|
||||
|
||||
if (destroyResult && destroyResult.success && destroyResult.planetId) {
|
||||
// 星球被摧毁
|
||||
// 从玩家星球列表中移除(如果是玩家的星球)
|
||||
const planetIndex = gameStore.player.planets.findIndex(p => p.id === destroyResult.planetId)
|
||||
if (planetIndex > -1) {
|
||||
gameStore.player.planets.splice(planetIndex, 1)
|
||||
} else {
|
||||
// 不是玩家星球,从宇宙地图中移除
|
||||
delete universeStore.planets[targetKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const processMissionReturn = (mission: FleetMission) => {
|
||||
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
|
||||
if (!originPlanet) return
|
||||
shipLogic.addFleet(originPlanet.fleet, mission.fleet)
|
||||
resourceLogic.addResources(originPlanet.resources, mission.cargo)
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
}
|
||||
|
||||
// NPC任务处理
|
||||
const processNPCMissionArrival = (npc: NPC, mission: FleetMission) => {
|
||||
if (mission.missionType === MissionType.Recycle) {
|
||||
// NPC回收任务到达
|
||||
const debrisId = mission.debrisFieldId
|
||||
if (!debrisId) {
|
||||
console.warn('[NPC Mission] Recycle mission missing debrisFieldId')
|
||||
mission.status = 'returning'
|
||||
mission.returnTime = Date.now() + (mission.arrivalTime - mission.departureTime)
|
||||
return
|
||||
}
|
||||
|
||||
const debrisField = universeStore.debrisFields[debrisId]
|
||||
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
|
||||
|
||||
if (recycleResult && debrisField) {
|
||||
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
|
||||
// 更新残骸场
|
||||
universeStore.debrisFields[debrisId] = {
|
||||
id: debrisField.id,
|
||||
position: debrisField.position,
|
||||
resources: recycleResult.remainingDebris,
|
||||
createdAt: debrisField.createdAt
|
||||
}
|
||||
} else {
|
||||
// 残骸已被完全回收,从宇宙中删除
|
||||
delete universeStore.debrisFields[debrisId]
|
||||
}
|
||||
}
|
||||
|
||||
// 移除即将到来的警告(回收任务已到达)
|
||||
removeIncomingFleetAlertById(mission.id)
|
||||
|
||||
// 设置返回时间
|
||||
mission.returnTime = Date.now() + (mission.arrivalTime - mission.departureTime)
|
||||
return
|
||||
}
|
||||
|
||||
// 找到目标星球
|
||||
const targetKey = gameLogic.generatePositionKey(
|
||||
mission.targetPosition.galaxy,
|
||||
mission.targetPosition.system,
|
||||
mission.targetPosition.position
|
||||
)
|
||||
const targetPlanet =
|
||||
gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
) || universeStore.planets[targetKey]
|
||||
|
||||
if (!targetPlanet) {
|
||||
console.warn('[NPC Mission] Target planet not found')
|
||||
return
|
||||
}
|
||||
|
||||
if (mission.missionType === MissionType.Spy) {
|
||||
// NPC侦查到达
|
||||
const { spiedNotification, spyReport } = npcBehaviorLogic.processNPCSpyArrival(npc, mission, targetPlanet, gameStore.player)
|
||||
|
||||
// 保存侦查报告到NPC(用于后续攻击决策)
|
||||
if (!npc.playerSpyReports) {
|
||||
npc.playerSpyReports = {}
|
||||
}
|
||||
npc.playerSpyReports[targetPlanet.id] = spyReport
|
||||
|
||||
// 添加被侦查通知给玩家
|
||||
if (!gameStore.player.spiedNotifications) {
|
||||
gameStore.player.spiedNotifications = []
|
||||
}
|
||||
gameStore.player.spiedNotifications.push(spiedNotification)
|
||||
|
||||
// 移除即将到来的警告(侦查已到达)
|
||||
removeIncomingFleetAlertById(mission.id)
|
||||
} else if (mission.missionType === MissionType.Attack) {
|
||||
// NPC攻击到达 - 使用专门的NPC攻击处理逻辑
|
||||
fleetLogic.processNPCAttackArrival(npc, mission, targetPlanet, gameStore.player, gameStore.player.planets).then(attackResult => {
|
||||
if (attackResult) {
|
||||
// 添加战斗报告给玩家
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
|
||||
// 如果生成月球,添加到玩家星球列表
|
||||
if (attackResult.moon) {
|
||||
gameStore.player.planets.push(attackResult.moon)
|
||||
}
|
||||
|
||||
// 如果生成残骸场,添加到宇宙残骸场列表
|
||||
if (attackResult.debrisField) {
|
||||
universeStore.debrisFields[attackResult.debrisField.id] = attackResult.debrisField
|
||||
}
|
||||
}
|
||||
|
||||
// 移除即将到来的警告(攻击已到达)
|
||||
removeIncomingFleetAlertById(mission.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const processNPCMissionReturn = (npc: NPC, mission: FleetMission) => {
|
||||
// 找到NPC的起始星球
|
||||
const originPlanet = npc.planets.find(p => p.id === mission.originPlanetId)
|
||||
if (!originPlanet) return
|
||||
|
||||
// 返还舰队
|
||||
shipLogic.addFleet(originPlanet.fleet, mission.fleet)
|
||||
|
||||
// 如果携带掠夺资源,给NPC添加资源
|
||||
if (mission.cargo) {
|
||||
originPlanet.resources.metal += mission.cargo.metal
|
||||
originPlanet.resources.crystal += mission.cargo.crystal
|
||||
originPlanet.resources.deuterium += mission.cargo.deuterium
|
||||
}
|
||||
|
||||
// 从NPC任务列表中移除
|
||||
if (npc.fleetMissions) {
|
||||
const missionIndex = npc.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) {
|
||||
npc.fleetMissions.splice(missionIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理导弹攻击到达
|
||||
const processMissileAttackArrival = async (missileAttack: MissileAttack) => {
|
||||
// 动态导入导弹逻辑
|
||||
const missileLogic = await import('@/logic/missileLogic')
|
||||
|
||||
// 找到目标星球
|
||||
const targetKey = gameLogic.generatePositionKey(
|
||||
missileAttack.targetPosition.galaxy,
|
||||
missileAttack.targetPosition.system,
|
||||
missileAttack.targetPosition.position
|
||||
)
|
||||
const targetPlanet =
|
||||
gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === missileAttack.targetPosition.galaxy &&
|
||||
p.position.system === missileAttack.targetPosition.system &&
|
||||
p.position.position === missileAttack.targetPosition.position
|
||||
) || universeStore.planets[targetKey]
|
||||
|
||||
// 如果目标星球不存在,导弹失败
|
||||
if (!targetPlanet) {
|
||||
missileAttack.status = 'arrived'
|
||||
// 生成失败报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `missile-report-${missileAttack.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.MissileAttack,
|
||||
originPlanetId: missileAttack.originPlanetId,
|
||||
originPlanetName: gameStore.player.planets.find(p => p.id === missileAttack.originPlanetId)?.name || t('fleetView.unknownPlanet'),
|
||||
targetPosition: missileAttack.targetPosition,
|
||||
targetPlanetId: undefined,
|
||||
targetPlanetName: `[${missileAttack.targetPosition.galaxy}:${missileAttack.targetPosition.system}:${missileAttack.targetPosition.position}]`,
|
||||
success: false,
|
||||
message: t('missionReports.missileAttackFailed'),
|
||||
details: {
|
||||
missileCount: missileAttack.missileCount,
|
||||
missileHits: 0,
|
||||
missileIntercepted: 0,
|
||||
defenseLosses: {}
|
||||
},
|
||||
read: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 计算导弹攻击结果
|
||||
const impactResult = missileLogic.calculateMissileImpact(missileAttack.missileCount, targetPlanet)
|
||||
|
||||
// 应用损失到目标星球
|
||||
missileLogic.applyMissileAttackResult(targetPlanet, impactResult.defenseLosses)
|
||||
|
||||
// 标记导弹攻击为已到达
|
||||
missileAttack.status = 'arrived'
|
||||
|
||||
// 生成导弹攻击报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
const reportMessage =
|
||||
impactResult.missileHits > 0
|
||||
? `${t('missionReports.missileAttackSuccess')}: ${impactResult.missileHits} ${t('missionReports.hits')}`
|
||||
: t('missionReports.missileAttackIntercepted')
|
||||
|
||||
gameStore.player.missionReports.push({
|
||||
id: `missile-report-${missileAttack.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.MissileAttack,
|
||||
originPlanetId: missileAttack.originPlanetId,
|
||||
originPlanetName: gameStore.player.planets.find(p => p.id === missileAttack.originPlanetId)?.name || t('fleetView.unknownPlanet'),
|
||||
targetPosition: missileAttack.targetPosition,
|
||||
targetPlanetId: targetPlanet.id,
|
||||
targetPlanetName: targetPlanet.name,
|
||||
success: true,
|
||||
message: reportMessage,
|
||||
details: {
|
||||
missileCount: missileAttack.missileCount,
|
||||
missileHits: impactResult.missileHits,
|
||||
missileIntercepted: impactResult.missileIntercepted,
|
||||
defenseLosses: impactResult.defenseLosses
|
||||
},
|
||||
read: false
|
||||
})
|
||||
}
|
||||
|
||||
// 移除即将到来的舰队警告
|
||||
const removeIncomingFleetAlert = (alert: IncomingFleetAlert) => {
|
||||
if (!gameStore.player.incomingFleetAlerts) return
|
||||
const index = gameStore.player.incomingFleetAlerts.indexOf(alert)
|
||||
if (index > -1) {
|
||||
gameStore.player.incomingFleetAlerts.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const removeIncomingFleetAlertById = (missionId: string) => {
|
||||
if (!gameStore.player.incomingFleetAlerts) return
|
||||
const index = gameStore.player.incomingFleetAlerts.findIndex(a => a.id === missionId)
|
||||
if (index > -1) {
|
||||
gameStore.player.incomingFleetAlerts.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// NPC成长系统更新函数
|
||||
let npcUpdateCounter = 0 // 累计秒数
|
||||
const NPC_UPDATE_INTERVAL = 10 // 每10秒更新一次NPC,减少性能开销
|
||||
|
||||
const updateNPCGrowth = (deltaSeconds: number) => {
|
||||
// 累积时间
|
||||
npcUpdateCounter += deltaSeconds
|
||||
|
||||
// 只在达到更新间隔时才执行
|
||||
if (npcUpdateCounter < NPC_UPDATE_INTERVAL) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取所有星球
|
||||
const allPlanets = Object.values(universeStore.planets)
|
||||
|
||||
// 如果NPC store为空,从星球数据中初始化NPC
|
||||
if (npcStore.npcs.length === 0) {
|
||||
const npcMap = new Map<string, any>()
|
||||
|
||||
allPlanets.forEach(planet => {
|
||||
// 跳过玩家的星球
|
||||
if (planet.ownerId === gameStore.player.id || !planet.ownerId) return
|
||||
|
||||
// 这是NPC的星球
|
||||
if (!npcMap.has(planet.ownerId)) {
|
||||
npcMap.set(planet.ownerId, {
|
||||
id: planet.ownerId,
|
||||
name: `NPC-${planet.ownerId.substring(0, 8)}`,
|
||||
planets: [],
|
||||
technologies: {}, // 初始化空科技树
|
||||
difficulty: 'medium' as const, // 默认中等难度
|
||||
relations: {}, // 外交关系
|
||||
allies: [], // 盟友列表
|
||||
enemies: [] // 敌人列表
|
||||
})
|
||||
}
|
||||
|
||||
npcMap.get(planet.ownerId)!.planets.push(planet)
|
||||
})
|
||||
|
||||
// 保存到store
|
||||
npcStore.npcs = Array.from(npcMap.values())
|
||||
|
||||
// 如果有NPC,基于玩家实力初始化NPC
|
||||
if (npcStore.npcs.length > 0) {
|
||||
const gameState: npcGrowthLogic.NPCGrowthGameState = {
|
||||
planets: allPlanets,
|
||||
player: gameStore.player,
|
||||
npcs: npcStore.npcs
|
||||
}
|
||||
|
||||
const playerPower = npcGrowthLogic.calculatePlayerAveragePower(gameState)
|
||||
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcGrowthLogic.initializeNPCStartingPower(npc, playerPower)
|
||||
})
|
||||
|
||||
// 初始化NPC之间的外交关系(盟友/敌人)
|
||||
npcGrowthLogic.initializeNPCDiplomacy(npcStore.npcs)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有NPC,直接返回
|
||||
if (npcStore.npcs.length === 0) {
|
||||
npcUpdateCounter = 0
|
||||
return
|
||||
}
|
||||
|
||||
// 构建游戏状态
|
||||
const gameState: npcGrowthLogic.NPCGrowthGameState = {
|
||||
planets: allPlanets,
|
||||
player: gameStore.player,
|
||||
npcs: npcStore.npcs
|
||||
}
|
||||
|
||||
// 使用累积的时间更新每个NPC
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcGrowthLogic.updateNPCGrowth(npc, gameState, npcUpdateCounter)
|
||||
})
|
||||
|
||||
// 重置计数器
|
||||
npcUpdateCounter = 0
|
||||
}
|
||||
|
||||
// NPC行为系统更新函数(侦查和攻击决策)
|
||||
let npcBehaviorCounter = 0
|
||||
const NPC_BEHAVIOR_INTERVAL = 5 // 每5秒检查一次NPC行为
|
||||
|
||||
const updateNPCBehavior = (deltaSeconds: number) => {
|
||||
// 累积时间
|
||||
npcBehaviorCounter += deltaSeconds
|
||||
|
||||
// 只在达到更新间隔时才执行
|
||||
if (npcBehaviorCounter < NPC_BEHAVIOR_INTERVAL) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有NPC,直接返回
|
||||
if (npcStore.npcs.length === 0) {
|
||||
npcBehaviorCounter = 0
|
||||
return
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const allPlanets = Object.values(universeStore.planets)
|
||||
|
||||
// 更新每个NPC的行为
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcBehaviorLogic.updateNPCBehavior(npc, gameStore.player, allPlanets, universeStore.debrisFields, now)
|
||||
})
|
||||
|
||||
npcBehaviorCounter = 0
|
||||
}
|
||||
|
||||
// 游戏循环定时器
|
||||
let gameLoop: ReturnType<typeof setInterval> | null = null
|
||||
let konamiCleanup: (() => void) | null = null
|
||||
let versionCheckInterval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
// 清理定时器
|
||||
onUnmounted(() => {
|
||||
if (gameLoop) clearInterval(gameLoop)
|
||||
})
|
||||
// 启动游戏循环
|
||||
const startGameLoop = () => {
|
||||
// 清理旧的定时器
|
||||
if (gameLoop) {
|
||||
clearInterval(gameLoop)
|
||||
}
|
||||
// 根据游戏速度计算间隔时间
|
||||
const interval = 1000 / (gameStore.gameSpeed || 1)
|
||||
// 启动新的游戏循环
|
||||
gameLoop = setInterval(() => {
|
||||
updateGame()
|
||||
}, interval)
|
||||
}
|
||||
|
||||
// 监听游戏速度变化,重新启动游戏循环
|
||||
watch(
|
||||
() => gameStore.gameSpeed,
|
||||
() => {
|
||||
if (gameLoop) {
|
||||
startGameLoop()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 初始化游戏
|
||||
onMounted(async () => {
|
||||
@@ -368,17 +1211,91 @@
|
||||
if (isFirstVisit) {
|
||||
gameStore.locale = detectBrowserLocale()
|
||||
}
|
||||
await initGame(t('common.playerName'), t('planet.homePlanet'), t('planet.planetPrefix'))
|
||||
await initGame()
|
||||
// 启动游戏循环
|
||||
gameLoop = setInterval(() => {
|
||||
updateGame()
|
||||
}, 1000) // 每1秒更新一次
|
||||
startGameLoop()
|
||||
// 启动科乐美秘籍监听
|
||||
konamiCleanup = setupKonamiCode()
|
||||
// 首次检查版本(被动检测)
|
||||
const versionInfo = await checkLatestVersion(gameStore.player.lastVersionCheckTime || 0, (time: number) => {
|
||||
gameStore.player.lastVersionCheckTime = time
|
||||
})
|
||||
if (versionInfo) {
|
||||
updateInfo.value = versionInfo
|
||||
toast.info(t('settings.newVersionAvailable', { version: versionInfo.version }), {
|
||||
duration: Infinity,
|
||||
dismissible: true,
|
||||
action: {
|
||||
label: t('settings.viewUpdate'),
|
||||
onClick: () => {
|
||||
showUpdateDialog.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
// 启动版本检查定时器(每5分钟被动检查一次)
|
||||
versionCheckInterval = setInterval(async () => {
|
||||
const versionInfo = await checkLatestVersion(gameStore.player.lastVersionCheckTime || 0, (time: number) => {
|
||||
gameStore.player.lastVersionCheckTime = time
|
||||
})
|
||||
if (versionInfo) {
|
||||
updateInfo.value = versionInfo
|
||||
toast.info(t('settings.newVersionAvailable', { version: versionInfo.version }), {
|
||||
duration: Infinity,
|
||||
dismissible: true,
|
||||
action: {
|
||||
label: t('settings.viewUpdate'),
|
||||
onClick: () => {
|
||||
showUpdateDialog.value = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 5 * 60 * 1000)
|
||||
})
|
||||
|
||||
// 清理定时器
|
||||
onUnmounted(() => {
|
||||
if (gameLoop) clearInterval(gameLoop)
|
||||
if (konamiCleanup) konamiCleanup()
|
||||
if (versionCheckInterval) clearInterval(versionCheckInterval)
|
||||
})
|
||||
|
||||
// 科乐美秘籍:上上下下左左右右BA
|
||||
const setupKonamiCode = () => {
|
||||
const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowLeft', 'ArrowRight', 'ArrowRight', 'b', 'a']
|
||||
let konamiIndex = 0
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
// 如果已经激活GM模式,直接返回
|
||||
if (gameStore.player.isGMEnabled) return
|
||||
|
||||
const key = event.key.toLowerCase()
|
||||
// 检查是否匹配当前秘籍序列
|
||||
if (key === konamiCode[konamiIndex] || event.key === konamiCode[konamiIndex]) {
|
||||
konamiIndex++
|
||||
// 如果完成整个秘籍序列
|
||||
if (konamiIndex === konamiCode.length) {
|
||||
gameStore.player.isGMEnabled = true
|
||||
// 显示成功消息
|
||||
toast.success(t('common.gmModeActivated'))
|
||||
konamiIndex = 0
|
||||
}
|
||||
} else {
|
||||
// 如果按错了键,重置序列
|
||||
konamiIndex = 0
|
||||
}
|
||||
}
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
// 返回清理函数
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}
|
||||
|
||||
// 定义 planet computed(需要在 watch 之前定义)
|
||||
const planet = computed(() => gameStore.currentPlanet)
|
||||
|
||||
const navItems = [
|
||||
const navItems = computed(() => [
|
||||
{ name: computed(() => t('nav.overview')), path: '/', icon: Home },
|
||||
{ name: computed(() => t('nav.buildings')), path: '/buildings', icon: Building2 },
|
||||
{ name: computed(() => t('nav.research')), path: '/research', icon: FlaskConical },
|
||||
@@ -388,11 +1305,12 @@
|
||||
{ name: computed(() => t('nav.officers')), path: '/officers', icon: Users },
|
||||
{ name: computed(() => t('nav.simulator')), path: '/battle-simulator', icon: Swords },
|
||||
{ name: computed(() => t('nav.galaxy')), path: '/galaxy', icon: Globe },
|
||||
{ name: computed(() => t('nav.diplomacy')), path: '/diplomacy', icon: Handshake },
|
||||
{ name: computed(() => t('nav.messages')), path: '/messages', icon: Mail },
|
||||
{ name: computed(() => t('nav.settings')), path: '/settings', icon: Settings },
|
||||
// GM菜单仅在开发模式下显示
|
||||
...(import.meta.env.DEV ? [{ name: computed(() => t('nav.gm')), path: '/gm', icon: Wrench }] : [])
|
||||
]
|
||||
// GM菜单在启用GM模式时显示
|
||||
...(gameStore.player.isGMEnabled ? [{ name: computed(() => t('nav.gm')), path: '/gm', icon: Wrench }] : [])
|
||||
])
|
||||
|
||||
// 使用直接计算,不再缓存
|
||||
const production = computed(() => {
|
||||
@@ -413,11 +1331,35 @@
|
||||
return resourceLogic.calculateResourceCapacity(planet.value, bonuses.storageCapacityBonus)
|
||||
})
|
||||
|
||||
// 电力消耗
|
||||
const energyConsumption = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
return resourceLogic.calculateEnergyConsumption(planet.value)
|
||||
})
|
||||
|
||||
// 净电力(产量 - 消耗)
|
||||
const netEnergy = computed(() => {
|
||||
if (!planet.value || !production.value) return 0
|
||||
return production.value.energy - energyConsumption.value
|
||||
})
|
||||
|
||||
// 未读消息数量
|
||||
const unreadMessagesCount = computed(() => {
|
||||
const unreadBattles = gameStore.player.battleReports.filter(r => !r.read).length
|
||||
const unreadSpies = gameStore.player.spyReports.filter(r => !r.read).length
|
||||
return unreadBattles + unreadSpies
|
||||
const unreadSpied = gameStore.player.spiedNotifications?.filter(n => !n.read).length || 0
|
||||
const unreadMissions = gameStore.player.missionReports?.filter(r => !r.read).length || 0
|
||||
const unreadNPCActivity = gameStore.player.npcActivityNotifications?.filter(n => !n.read).length || 0
|
||||
const unreadGifts = gameStore.player.giftNotifications?.filter(n => !n.read).length || 0
|
||||
const unreadGiftRejected = gameStore.player.giftRejectedNotifications?.filter(n => !n.read).length || 0
|
||||
return unreadBattles + unreadSpies + unreadSpied + unreadMissions + unreadNPCActivity + unreadGifts + unreadGiftRejected
|
||||
})
|
||||
|
||||
// 正在执行的舰队任务数量(包括飞行中的导弹)
|
||||
const activeFleetMissionsCount = computed(() => {
|
||||
const fleetMissions = gameStore.player.fleetMissions.filter(m => m.status === 'outbound' || m.status === 'returning').length
|
||||
const flyingMissiles = gameStore.player.missileAttacks?.filter(m => m.status === 'flying').length || 0
|
||||
return fleetMissions + flyingMissiles
|
||||
})
|
||||
|
||||
// 资源类型配置
|
||||
@@ -450,17 +1392,79 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 切换到指定星球
|
||||
const switchToPlanet = (planetId: string) => {
|
||||
gameStore.currentPlanetId = planetId
|
||||
}
|
||||
|
||||
// 切换侧边栏
|
||||
const toggleSidebar = () => {
|
||||
sidebarOpen.value = !sidebarOpen.value
|
||||
}
|
||||
|
||||
// 处理确认对话框的确认操作
|
||||
const handleConfirmAction = () => {
|
||||
if (confirmDialogAction.value) {
|
||||
confirmDialogAction.value()
|
||||
// 获取队列项的名称
|
||||
const getItemName = (item: BuildQueueItem): string => {
|
||||
if (item.type === 'building' || item.type === 'demolish') {
|
||||
const buildingName = t(`buildings.${item.itemType}`)
|
||||
return item.type === 'demolish' ? `${t('buildingsView.demolish')} - ${buildingName}` : buildingName
|
||||
} else if (item.type === 'technology') {
|
||||
return t(`technologies.${item.itemType}`)
|
||||
} else if (item.type === 'ship') {
|
||||
return t(`ships.${item.itemType}`)
|
||||
} else if (item.type === 'defense') {
|
||||
return t(`defenses.${item.itemType}`)
|
||||
}
|
||||
confirmDialogOpen.value = false
|
||||
return item.itemType
|
||||
}
|
||||
|
||||
// 获取剩余时间
|
||||
const getRemainingTime = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
return Math.max(0, Math.floor((item.endTime - now) / 1000))
|
||||
}
|
||||
|
||||
// 获取队列进度
|
||||
const getQueueProgress = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
const total = item.endTime - item.startTime
|
||||
const elapsed = now - item.startTime
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100))
|
||||
}
|
||||
|
||||
// 取消建造
|
||||
const handleCancelBuild = (queueId: string) => {
|
||||
confirmDialogTitle.value = t('queue.cancelBuild')
|
||||
confirmDialogMessage.value = t('queue.confirmCancel')
|
||||
confirmDialogAction.value = () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.currentPlanet.buildQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'building') {
|
||||
const refund = buildingValidation.cancelBuildingUpgrade(gameStore.currentPlanet, item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.currentPlanet.buildQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
confirmDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 取消研究
|
||||
const handleCancelResearch = (queueId: string) => {
|
||||
confirmDialogTitle.value = t('queue.cancelResearch')
|
||||
confirmDialogMessage.value = t('queue.confirmCancel')
|
||||
confirmDialogAction.value = () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.player.researchQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'technology') {
|
||||
const refund = researchValidation.cancelTechnologyResearch(item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.player.researchQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
confirmDialogOpen.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,23 +15,37 @@
|
||||
<TableHead v-if="type === 'building' && showProductionColumn" class="text-center">{{ t('buildings.production') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showConsumptionColumn" class="text-center">{{ t('buildings.consumption') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showCapacityColumn" class="text-center">{{ t('buildings.storageCapacity') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showFleetStorageColumn" class="text-center">{{ t('buildings.fleetStorage') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showBuildQueueColumn" class="text-center">{{ t('buildings.buildQueueBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showFleetStorageColumn" class="text-center">
|
||||
{{ t('buildings.fleetStorage') }}
|
||||
</TableHead>
|
||||
<TableHead v-if="type === 'building' && showBuildQueueColumn" class="text-center">
|
||||
{{ t('buildings.buildQueueBonus') }}
|
||||
</TableHead>
|
||||
<TableHead v-if="type === 'building' && showSpaceColumn" class="text-center">{{ t('buildings.spaceBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showMissileColumn" class="text-center">{{ t('buildings.missileCapacity') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showBuildSpeedColumn" class="text-center">{{ t('buildings.buildSpeedBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showResearchSpeedColumn" class="text-center">{{ t('buildings.researchSpeedBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'building' && showBuildSpeedColumn" class="text-center">
|
||||
{{ t('buildings.buildSpeedBonus') }}
|
||||
</TableHead>
|
||||
<TableHead v-if="type === 'building' && showResearchSpeedColumn" class="text-center">
|
||||
{{ t('buildings.researchSpeedBonus') }}
|
||||
</TableHead>
|
||||
<!-- 科技相关列 -->
|
||||
<TableHead v-if="type === 'technology' && showAttackBonusColumn" class="text-center">{{ t('research.attackBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showShieldBonusColumn" class="text-center">{{ t('research.shieldBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showArmorBonusColumn" class="text-center">{{ t('research.armorBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showSpyLevelColumn" class="text-center">{{ t('research.spyLevel') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showFleetStorageColumn" class="text-center">{{ t('buildings.fleetStorage') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showResearchQueueColumn" class="text-center">{{ t('research.researchQueueBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showFleetStorageColumn" class="text-center">
|
||||
{{ t('buildings.fleetStorage') }}
|
||||
</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showResearchQueueColumn" class="text-center">
|
||||
{{ t('research.researchQueueBonus') }}
|
||||
</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showColonySlotsColumn" class="text-center">{{ t('research.colonySlots') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showSpaceColumn" class="text-center">{{ t('buildings.spaceBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showSpeedBonusColumn" class="text-center">{{ t('research.speedBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showResearchSpeedColumn" class="text-center">{{ t('buildings.researchSpeedBonus') }}</TableHead>
|
||||
<TableHead v-if="type === 'technology' && showResearchSpeedColumn" class="text-center">
|
||||
{{ t('buildings.researchSpeedBonus') }}
|
||||
</TableHead>
|
||||
<TableHead class="text-center">{{ t('player.points') }}</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -78,7 +92,8 @@
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'building' && showFleetStorageColumn" class="text-center text-sm">
|
||||
<span v-if="getLevelData(level).fleetStorage > 0" class="text-blue-600 dark:text-blue-400">
|
||||
+<NumberWithTooltip :value="getLevelData(level).fleetStorage" />
|
||||
+
|
||||
<NumberWithTooltip :value="getLevelData(level).fleetStorage" />
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</TableCell>
|
||||
@@ -87,7 +102,8 @@
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'building' && showSpaceColumn" class="text-center text-sm">
|
||||
<span v-if="getLevelData(level).spaceBonus > 0" class="text-green-600 dark:text-green-400">
|
||||
+<NumberWithTooltip :value="getLevelData(level).spaceBonus" />
|
||||
+
|
||||
<NumberWithTooltip :value="getLevelData(level).spaceBonus" />
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</TableCell>
|
||||
@@ -95,8 +111,12 @@
|
||||
<span class="text-orange-600 dark:text-orange-400">+10</span>
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'building' && showBuildSpeedColumn" class="text-center text-sm">
|
||||
<span v-if="itemType === 'roboticsFactory'" class="text-cyan-600 dark:text-cyan-400">+{{ getLevelData(level).buildSpeedBonus * 100 }}%</span>
|
||||
<span v-else-if="itemType === 'naniteFactory'" class="text-cyan-600 dark:text-cyan-400">+{{ getLevelData(level).buildSpeedBonus * 100 }}%</span>
|
||||
<span v-if="itemType === 'roboticsFactory'" class="text-cyan-600 dark:text-cyan-400">
|
||||
+{{ getLevelData(level).buildSpeedBonus * 100 }}%
|
||||
</span>
|
||||
<span v-else-if="itemType === 'naniteFactory'" class="text-cyan-600 dark:text-cyan-400">
|
||||
+{{ getLevelData(level).buildSpeedBonus * 100 }}%
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'building' && showResearchSpeedColumn" class="text-center text-sm">
|
||||
<span class="text-indigo-600 dark:text-indigo-400">+{{ (getLevelData(level).researchSpeedBonus - 1) * 100 }}%</span>
|
||||
@@ -115,7 +135,10 @@
|
||||
<span class="text-purple-600 dark:text-purple-400">+{{ level }}</span>
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'technology' && showFleetStorageColumn" class="text-center text-sm">
|
||||
<span class="text-blue-600 dark:text-blue-400">+<NumberWithTooltip :value="level * 500" /></span>
|
||||
<span class="text-blue-600 dark:text-blue-400">
|
||||
+
|
||||
<NumberWithTooltip :value="level * 500" />
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'technology' && showResearchQueueColumn" class="text-center text-sm">
|
||||
<span class="text-purple-600 dark:text-purple-400">+1</span>
|
||||
@@ -124,7 +147,7 @@
|
||||
<span class="text-green-600 dark:text-green-400">+1</span>
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'technology' && showSpaceColumn" class="text-center text-sm">
|
||||
<span class="text-green-600 dark:text-green-400">+5 {{ t('research.forAllPlanets') }}</span>
|
||||
<span class="text-green-600 dark:text-green-400">+30 {{ t('research.forAllPlanets') }}</span>
|
||||
</TableCell>
|
||||
<TableCell v-if="type === 'technology' && showSpeedBonusColumn" class="text-center text-sm">
|
||||
<span class="text-yellow-600 dark:text-yellow-400">+{{ level * 10 }}%</span>
|
||||
@@ -275,7 +298,12 @@
|
||||
<CardTitle class="text-sm">{{ t(`${typeKey}.buildCost`) }}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<div v-for="resourceType in costResourceTypes" :key="resourceType.key" v-show="unitCost[resourceType.key] > 0" class="flex items-center justify-between text-sm">
|
||||
<div
|
||||
v-for="resourceType in costResourceTypes"
|
||||
:key="resourceType.key"
|
||||
v-show="unitCost[resourceType.key] > 0"
|
||||
class="flex items-center justify-between text-sm"
|
||||
>
|
||||
<span class="text-muted-foreground">{{ t(`resources.${resourceType.key}`) }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="unitCost[resourceType.key]" /></span>
|
||||
</div>
|
||||
@@ -311,9 +339,17 @@
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm text-muted-foreground">{{ t(`${typeKey}.totalCost`) }}:</p>
|
||||
<div class="space-y-1 text-sm">
|
||||
<div v-for="resourceType in costResourceTypes" :key="resourceType.key" class="flex justify-between">
|
||||
<span>{{ t(`resources.${resourceType.key}`) }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="batchCost[resourceType.key]" /></span>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="batchCost.metal" /></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="batchCost.crystal" /></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="batchCost.deuterium" /></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -654,9 +690,9 @@
|
||||
} else if (buildingType === 'shipyard') {
|
||||
fleetStorage = 1000 * level
|
||||
} else if (buildingType === 'terraformer') {
|
||||
spaceBonus = 5
|
||||
spaceBonus = 30
|
||||
} else if (buildingType === 'lunarBase') {
|
||||
spaceBonus = 5
|
||||
spaceBonus = 30
|
||||
} else if (buildingType === 'roboticsFactory') {
|
||||
buildSpeedBonus = level
|
||||
} else if (buildingType === 'naniteFactory') {
|
||||
@@ -685,7 +721,18 @@
|
||||
}
|
||||
|
||||
const points = pointsLogic.calculateTechnologyPoints(techType, level - 1, level)
|
||||
return { cost, time, production: 0, consumption: 0, points, capacity: 0, fleetStorage: 0, spaceBonus: 0, buildSpeedBonus: 0, researchSpeedBonus }
|
||||
return {
|
||||
cost,
|
||||
time,
|
||||
production: 0,
|
||||
consumption: 0,
|
||||
points,
|
||||
capacity: 0,
|
||||
fleetStorage: 0,
|
||||
spaceBonus: 0,
|
||||
buildSpeedBonus: 0,
|
||||
researchSpeedBonus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
120
src/components/UpdateDialog.vue
Normal file
120
src/components/UpdateDialog.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<template>
|
||||
<Dialog :open="open" @update:open="$emit('update:open', $event)">
|
||||
<DialogScrollContent class="max-w-2xl max-h-[80vh] flex flex-col">
|
||||
<DialogHeader class="flex-shrink-0">
|
||||
<DialogTitle>{{ t('settings.newVersionAvailable', { version: versionInfo?.version || '' }) }}</DialogTitle>
|
||||
<DialogDescription>{{ t('settings.updateAvailable') }}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="flex-1 overflow-y-auto min-h-0 mt-4 pr-2">
|
||||
<div class="prose prose-sm dark:prose-invert max-w-none" v-html="renderedMarkdown"></div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="flex gap-2 flex-shrink-0 mt-4">
|
||||
<Button variant="outline" @click="$emit('update:open', false)">
|
||||
{{ t('common.cancel') }}
|
||||
</Button>
|
||||
<Button @click="handleDownload">
|
||||
<Download class="mr-2 h-4 w-4" />
|
||||
{{ t('settings.download') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogScrollContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { marked } from 'marked'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { Dialog, DialogScrollContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Download } from 'lucide-vue-next'
|
||||
import type { VersionInfo } from '@/utils/versionCheck'
|
||||
|
||||
const props = defineProps<{
|
||||
open: boolean
|
||||
versionInfo: VersionInfo | null
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const renderedMarkdown = computed(() => {
|
||||
if (!props.versionInfo?.releaseNotes) return ''
|
||||
return marked(props.versionInfo.releaseNotes)
|
||||
})
|
||||
|
||||
const handleDownload = () => {
|
||||
if (props.versionInfo?.downloadUrl) {
|
||||
window.open(props.versionInfo.downloadUrl, '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.prose) {
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
:deep(.prose h1) {
|
||||
font-size: 1.5em;
|
||||
font-weight: 700;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
:deep(.prose h2) {
|
||||
font-size: 1.25em;
|
||||
font-weight: 600;
|
||||
margin-top: 0.8em;
|
||||
margin-bottom: 0.4em;
|
||||
}
|
||||
|
||||
:deep(.prose h3) {
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
margin-top: 0.6em;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
:deep(.prose p) {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
:deep(.prose ul) {
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
:deep(.prose li) {
|
||||
margin-top: 0.25em;
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
:deep(.prose code) {
|
||||
background: hsl(var(--muted));
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
:deep(.prose pre) {
|
||||
background: hsl(var(--muted));
|
||||
padding: 1em;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin-top: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
:deep(.prose a) {
|
||||
color: hsl(var(--primary));
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
@@ -6,7 +6,7 @@
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-lg',
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 sm:max-w-2xl',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
v-bind="{ ...$attrs, ...forwarded }"
|
||||
:class="
|
||||
cn(
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 w-[calc(100vw-3rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border shadow-lg duration-200 sm:w-auto flex flex-col p-0',
|
||||
'bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 w-[calc(100vw-3rem)] translate-x-[-50%] translate-y-[-50%] rounded-lg border shadow-lg duration-200 sm:w-auto sm:min-w-[764px] flex flex-col p-0',
|
||||
containerClass
|
||||
)
|
||||
"
|
||||
|
||||
22
src/components/ui/empty/Empty.vue
Normal file
22
src/components/ui/empty/Empty.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div
|
||||
data-slot="empty"
|
||||
:class="
|
||||
cn(
|
||||
'flex min-w-0 flex-1 flex-col items-center justify-center gap-6 text-balance rounded-lg border-dashed p-6 text-center md:p-12',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/empty/EmptyContent.vue
Normal file
14
src/components/ui/empty/EmptyContent.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="empty-content" :class="cn('flex w-full min-w-0 max-w-sm flex-col items-center gap-4 text-balance text-sm', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
19
src/components/ui/empty/EmptyDescription.vue
Normal file
19
src/components/ui/empty/EmptyDescription.vue
Normal file
@@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<p
|
||||
data-slot="empty-description"
|
||||
:class="
|
||||
cn('text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4', $attrs.class ?? '')
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/empty/EmptyHeader.vue
Normal file
14
src/components/ui/empty/EmptyHeader.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="empty-header" :class="cn('flex max-w-sm flex-col items-center gap-2 text-center', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
17
src/components/ui/empty/EmptyMedia.vue
Normal file
17
src/components/ui/empty/EmptyMedia.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div data-slot="empty-icon" :data-variant="variant" :class="cn(emptyMediaVariants({ variant }), props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import type { EmptyMediaVariants } from '.'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { emptyMediaVariants } from '.'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
variant?: EmptyMediaVariants['variant']
|
||||
}>()
|
||||
</script>
|
||||
14
src/components/ui/empty/EmptyTitle.vue
Normal file
14
src/components/ui/empty/EmptyTitle.vue
Normal file
@@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div data-slot="empty-title" :class="cn('text-lg font-medium tracking-tight', props.class)">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes['class']
|
||||
}>()
|
||||
</script>
|
||||
23
src/components/ui/empty/index.ts
Normal file
23
src/components/ui/empty/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { VariantProps } from 'class-variance-authority'
|
||||
import { cva } from 'class-variance-authority'
|
||||
|
||||
export { default as Empty } from './Empty.vue'
|
||||
export { default as EmptyContent } from './EmptyContent.vue'
|
||||
export { default as EmptyDescription } from './EmptyDescription.vue'
|
||||
export { default as EmptyHeader } from './EmptyHeader.vue'
|
||||
export { default as EmptyMedia } from './EmptyMedia.vue'
|
||||
export { default as EmptyTitle } from './EmptyTitle.vue'
|
||||
|
||||
export const emptyMediaVariants = cva('mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-transparent',
|
||||
icon: "bg-muted text-foreground flex size-10 shrink-0 items-center justify-center rounded-lg [&_svg:not([class*='size-'])]:size-6"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default'
|
||||
}
|
||||
})
|
||||
|
||||
export type EmptyMediaVariants = VariantProps<typeof emptyMediaVariants>
|
||||
25
src/components/ui/scroll-area/ScrollArea.vue
Normal file
25
src/components/ui/scroll-area/ScrollArea.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<ScrollAreaRoot data-slot="scroll-area" v-bind="delegatedProps" :class="cn('relative', props.class)">
|
||||
<ScrollAreaViewport
|
||||
data-slot="scroll-area-viewport"
|
||||
class="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1"
|
||||
>
|
||||
<slot />
|
||||
</ScrollAreaViewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaCorner />
|
||||
</ScrollAreaRoot>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ScrollAreaRootProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ScrollAreaCorner, ScrollAreaRoot, ScrollAreaViewport } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
import ScrollBar from './ScrollBar.vue'
|
||||
|
||||
const props = defineProps<ScrollAreaRootProps & { class?: HTMLAttributes['class'] }>()
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
30
src/components/ui/scroll-area/ScrollBar.vue
Normal file
30
src/components/ui/scroll-area/ScrollBar.vue
Normal file
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<ScrollAreaScrollbar
|
||||
data-slot="scroll-area-scrollbar"
|
||||
v-bind="delegatedProps"
|
||||
:class="
|
||||
cn(
|
||||
'flex touch-none p-px transition-colors select-none',
|
||||
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent',
|
||||
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<ScrollAreaThumb data-slot="scroll-area-thumb" class="bg-border relative flex-1 rounded-full" />
|
||||
</ScrollAreaScrollbar>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ScrollAreaScrollbarProps } from 'reka-ui'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { reactiveOmit } from '@vueuse/core'
|
||||
import { ScrollAreaScrollbar, ScrollAreaThumb } from 'reka-ui'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const props = withDefaults(defineProps<ScrollAreaScrollbarProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
orientation: 'vertical'
|
||||
})
|
||||
|
||||
const delegatedProps = reactiveOmit(props, 'class')
|
||||
</script>
|
||||
2
src/components/ui/scroll-area/index.ts
Normal file
2
src/components/ui/scroll-area/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as ScrollArea } from './ScrollArea.vue'
|
||||
export { default as ScrollBar } from './ScrollBar.vue'
|
||||
@@ -37,6 +37,7 @@
|
||||
import { CircleCheckIcon, InfoIcon, Loader2Icon, OctagonXIcon, TriangleAlertIcon, XIcon } from 'lucide-vue-next'
|
||||
import { Toaster as Sonner } from 'vue-sonner'
|
||||
import { cn } from '@/lib/utils'
|
||||
import 'vue-sonner/style.css'
|
||||
|
||||
const props = defineProps<ToasterProps>()
|
||||
</script>
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useUniverseStore } from '@/stores/universeStore'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as planetLogic from '@/logic/planetLogic'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import * as officerLogic from '@/logic/officerLogic'
|
||||
|
||||
/**
|
||||
* 游戏生命周期管理
|
||||
* 处理游戏初始化、NPC星球生成等
|
||||
*/
|
||||
export const useGameLifecycle = () => {
|
||||
const gameStore = useGameStore()
|
||||
const universeStore = useUniverseStore()
|
||||
|
||||
/**
|
||||
* 生成NPC星球
|
||||
*/
|
||||
const generateNPCPlanets = (npcCount: number, planetPrefix: string) => {
|
||||
for (let i = 0; i < npcCount; i++) {
|
||||
const position = gameLogic.generateRandomPosition()
|
||||
const key = gameLogic.generatePositionKey(position.galaxy, position.system, position.position)
|
||||
if (universeStore.planets[key]) continue
|
||||
const npcPlanet = planetLogic.createNPCPlanet(i, position, planetPrefix)
|
||||
universeStore.planets[key] = npcPlanet
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化游戏
|
||||
*/
|
||||
const initGame = async (playerName: string, homePlanetName: string, planetPrefix: string) => {
|
||||
const shouldInit = gameLogic.shouldInitializeGame(gameStore.player.planets)
|
||||
|
||||
if (!shouldInit) {
|
||||
const now = Date.now()
|
||||
|
||||
// 计算离线收益(直接同步计算)
|
||||
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now)
|
||||
gameStore.player.planets.forEach(planet => {
|
||||
resourceLogic.updatePlanetResources(planet, now, bonuses)
|
||||
})
|
||||
|
||||
// 只在没有NPC星球时才生成(首次加载已有玩家数据时)
|
||||
if (Object.keys(universeStore.planets).length === 0) {
|
||||
generateNPCPlanets(200, planetPrefix)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.player = gameLogic.initializePlayer(gameStore.player.id, playerName)
|
||||
const initialPlanet = planetLogic.createInitialPlanet(gameStore.player.id, homePlanetName)
|
||||
gameStore.player.planets = [initialPlanet]
|
||||
gameStore.currentPlanetId = initialPlanet.id
|
||||
// 新玩家初始化时生成NPC星球
|
||||
generateNPCPlanets(200, planetPrefix)
|
||||
}
|
||||
|
||||
return {
|
||||
initGame,
|
||||
generateNPCPlanets
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import type { FleetMission } from '@/types/game'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
|
||||
/**
|
||||
* 游戏更新循环
|
||||
* 处理游戏状态的定期更新
|
||||
*/
|
||||
export const useGameUpdate = (
|
||||
processMissionArrival: (mission: FleetMission) => Promise<void>,
|
||||
processMissionReturn: (mission: FleetMission) => void,
|
||||
processNPCMissionArrival: (npc: any, mission: FleetMission) => void,
|
||||
processNPCMissionReturn: (npc: any, mission: FleetMission) => void,
|
||||
updateNPCGrowth: (deltaSeconds: number) => void,
|
||||
updateNPCBehavior: (deltaSeconds: number) => void
|
||||
) => {
|
||||
const gameStore = useGameStore()
|
||||
const npcStore = useNPCStore()
|
||||
|
||||
/**
|
||||
* 游戏主更新函数
|
||||
*/
|
||||
const updateGame = () => {
|
||||
if (gameStore.isPaused) return
|
||||
const now = Date.now()
|
||||
gameStore.gameTime = now
|
||||
|
||||
// 检查军官过期
|
||||
gameLogic.checkOfficersExpiration(gameStore.player.officers, now)
|
||||
|
||||
// 处理游戏更新(建造队列、研究队列等)
|
||||
const result = gameLogic.processGameUpdate(gameStore.player, now)
|
||||
gameStore.player.researchQueue = result.updatedResearchQueue
|
||||
|
||||
// 处理舰队任务
|
||||
gameStore.player.fleetMissions.forEach(mission => {
|
||||
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
|
||||
processMissionArrival(mission)
|
||||
} else if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
|
||||
processMissionReturn(mission)
|
||||
}
|
||||
})
|
||||
|
||||
// 处理NPC舰队任务
|
||||
npcStore.npcs.forEach(npc => {
|
||||
if (npc.fleetMissions) {
|
||||
npc.fleetMissions.forEach(mission => {
|
||||
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
|
||||
processNPCMissionArrival(npc, mission)
|
||||
} else if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
|
||||
processNPCMissionReturn(npc, mission)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// NPC成长系统更新
|
||||
updateNPCGrowth(1) // 传入1秒的时间间隔
|
||||
|
||||
// NPC行为系统更新(侦查和攻击决策)
|
||||
updateNPCBehavior(1)
|
||||
}
|
||||
|
||||
return {
|
||||
updateGame
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export const useI18n = () => {
|
||||
const messages = computed(() => locales[currentLocale.value])
|
||||
|
||||
// 获取翻译文本的辅助函数
|
||||
const t = (key: string): string => {
|
||||
const t = (key: string, params?: Record<string, string | number>): string => {
|
||||
const keys = key.split('.')
|
||||
let value: any = messages.value
|
||||
|
||||
@@ -22,7 +22,16 @@ export const useI18n = () => {
|
||||
}
|
||||
}
|
||||
|
||||
return typeof value === 'string' ? value : key
|
||||
let result = typeof value === 'string' ? value : key
|
||||
|
||||
// 替换参数占位符
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([paramKey, paramValue]) => {
|
||||
result = result.replace(new RegExp(`\\{${paramKey}\\}`, 'g'), String(paramValue))
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const setLocale = (locale: Locale) => {
|
||||
|
||||
@@ -1,249 +0,0 @@
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useUniverseStore } from '@/stores/universeStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import type { FleetMission } from '@/types/game'
|
||||
import { MissionType } from '@/types/game'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as fleetLogic from '@/logic/fleetLogic'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import * as diplomaticLogic from '@/logic/diplomaticLogic'
|
||||
|
||||
/**
|
||||
* 舰队任务处理
|
||||
* 处理玩家舰队任务的到达和返回
|
||||
*/
|
||||
export const useMissionHandler = (t: (key: string) => string) => {
|
||||
const gameStore = useGameStore()
|
||||
const universeStore = useUniverseStore()
|
||||
const npcStore = useNPCStore()
|
||||
|
||||
/**
|
||||
* 处理任务到达
|
||||
*/
|
||||
const processMissionArrival = async (mission: FleetMission) => {
|
||||
// 从宇宙星球地图中查找目标星球
|
||||
const targetKey = gameLogic.generatePositionKey(
|
||||
mission.targetPosition.galaxy,
|
||||
mission.targetPosition.system,
|
||||
mission.targetPosition.position
|
||||
)
|
||||
// 先从玩家星球中查找,再从宇宙地图中查找
|
||||
const targetPlanet =
|
||||
gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
) || universeStore.planets[targetKey]
|
||||
|
||||
// 获取起始星球名称(用于报告)
|
||||
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
|
||||
const originPlanetName = originPlanet?.name || t('fleetView.unknownPlanet')
|
||||
|
||||
if (mission.missionType === MissionType.Transport) {
|
||||
const result = fleetLogic.processTransportArrival(mission, targetPlanet, gameStore.player, npcStore.npcs)
|
||||
// 生成运输任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Transport,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName:
|
||||
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
|
||||
success: result.success,
|
||||
message: result.success ? t('missionReports.transportSuccess') : t('missionReports.transportFailed'),
|
||||
details: {
|
||||
transportedResources: mission.cargo
|
||||
},
|
||||
read: false
|
||||
})
|
||||
} else if (mission.missionType === MissionType.Attack) {
|
||||
const attackResult = await fleetLogic.processAttackArrival(mission, targetPlanet, gameStore.player, null, gameStore.player.planets)
|
||||
if (attackResult) {
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
|
||||
// 检查是否攻击了NPC星球,更新外交关系
|
||||
if (targetPlanet) {
|
||||
const targetNpc = npcStore.npcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
|
||||
if (targetNpc) {
|
||||
diplomaticLogic.handleAttackReputation(gameStore.player, targetNpc, attackResult.battleResult, npcStore.npcs)
|
||||
}
|
||||
}
|
||||
|
||||
if (attackResult.moon) {
|
||||
gameStore.player.planets.push(attackResult.moon)
|
||||
}
|
||||
if (attackResult.debrisField) {
|
||||
// 将残骸场添加到游戏状态
|
||||
universeStore.debrisFields[attackResult.debrisField.id] = attackResult.debrisField
|
||||
}
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Colonize) {
|
||||
const newPlanet = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player, t('planet.colonyPrefix'))
|
||||
// 生成殖民任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Colonize,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: newPlanet?.id,
|
||||
targetPlanetName: newPlanet?.name,
|
||||
success: !!newPlanet,
|
||||
message: newPlanet ? t('missionReports.colonizeSuccess') : t('missionReports.colonizeFailed'),
|
||||
details: newPlanet
|
||||
? {
|
||||
newPlanetId: newPlanet.id,
|
||||
newPlanetName: newPlanet.name
|
||||
}
|
||||
: undefined,
|
||||
read: false
|
||||
})
|
||||
if (newPlanet) {
|
||||
gameStore.player.planets.push(newPlanet)
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Spy) {
|
||||
const spyReport = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player, null, npcStore.npcs)
|
||||
if (spyReport) gameStore.player.spyReports.push(spyReport)
|
||||
} else if (mission.missionType === MissionType.Deploy) {
|
||||
const deployed = fleetLogic.processDeployArrival(mission, targetPlanet, gameStore.player.id)
|
||||
// 生成部署任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Deploy,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName:
|
||||
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
|
||||
success: deployed,
|
||||
message: deployed ? t('missionReports.deploySuccess') : t('missionReports.deployFailed'),
|
||||
details: {
|
||||
deployedFleet: mission.fleet
|
||||
},
|
||||
read: false
|
||||
})
|
||||
if (deployed) {
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
return
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Recycle) {
|
||||
// 处理回收任务
|
||||
const debrisId = `debris_${mission.targetPosition.galaxy}_${mission.targetPosition.system}_${mission.targetPosition.position}`
|
||||
const debrisField = universeStore.debrisFields[debrisId]
|
||||
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
|
||||
|
||||
// 生成回收任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Recycle,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
success: !!recycleResult,
|
||||
message: recycleResult ? t('missionReports.recycleSuccess') : t('missionReports.recycleFailed'),
|
||||
details: recycleResult
|
||||
? {
|
||||
recycledResources: recycleResult.collectedResources,
|
||||
remainingDebris: recycleResult.remainingDebris || undefined
|
||||
}
|
||||
: undefined,
|
||||
read: false
|
||||
})
|
||||
|
||||
if (recycleResult && debrisField) {
|
||||
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
|
||||
// 更新残骸场
|
||||
universeStore.debrisFields[debrisId] = {
|
||||
id: debrisField.id,
|
||||
position: debrisField.position,
|
||||
resources: recycleResult.remainingDebris,
|
||||
createdAt: debrisField.createdAt,
|
||||
expiresAt: debrisField.expiresAt
|
||||
}
|
||||
} else {
|
||||
// 残骸场已被完全收集,删除
|
||||
delete universeStore.debrisFields[debrisId]
|
||||
}
|
||||
}
|
||||
} else if (mission.missionType === MissionType.Destroy) {
|
||||
// 处理行星毁灭任务
|
||||
const destroyResult = fleetLogic.processDestroyArrival(mission, targetPlanet, gameStore.player)
|
||||
|
||||
// 生成毁灭任务报告
|
||||
if (!gameStore.player.missionReports) {
|
||||
gameStore.player.missionReports = []
|
||||
}
|
||||
gameStore.player.missionReports.push({
|
||||
id: `mission-report-${mission.id}`,
|
||||
timestamp: Date.now(),
|
||||
missionType: MissionType.Destroy,
|
||||
originPlanetId: mission.originPlanetId,
|
||||
originPlanetName,
|
||||
targetPosition: mission.targetPosition,
|
||||
targetPlanetId: targetPlanet?.id,
|
||||
targetPlanetName: targetPlanet?.name,
|
||||
success: destroyResult?.success || false,
|
||||
message: destroyResult?.success ? t('missionReports.destroySuccess') : t('missionReports.destroyFailed'),
|
||||
details: destroyResult?.success
|
||||
? {
|
||||
destroyedPlanetName:
|
||||
targetPlanet?.name ||
|
||||
`[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`
|
||||
}
|
||||
: undefined,
|
||||
read: false
|
||||
})
|
||||
|
||||
if (destroyResult && destroyResult.success && destroyResult.planetId) {
|
||||
// 星球被摧毁
|
||||
// 从玩家星球列表中移除(如果是玩家的星球)
|
||||
const planetIndex = gameStore.player.planets.findIndex(p => p.id === destroyResult.planetId)
|
||||
if (planetIndex > -1) {
|
||||
gameStore.player.planets.splice(planetIndex, 1)
|
||||
} else {
|
||||
// 不是玩家星球,从宇宙地图中移除
|
||||
delete universeStore.planets[targetKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理任务返回
|
||||
*/
|
||||
const processMissionReturn = (mission: FleetMission) => {
|
||||
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
|
||||
if (!originPlanet) return
|
||||
shipLogic.addFleet(originPlanet.fleet, mission.fleet)
|
||||
resourceLogic.addResources(originPlanet.resources, mission.cargo)
|
||||
const missionIndex = gameStore.player.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) gameStore.player.fleetMissions.splice(missionIndex, 1)
|
||||
}
|
||||
|
||||
return {
|
||||
processMissionArrival,
|
||||
processMissionReturn
|
||||
}
|
||||
}
|
||||
@@ -1,300 +0,0 @@
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useUniverseStore } from '@/stores/universeStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import type { NPC, FleetMission, IncomingFleetAlert } from '@/types/game'
|
||||
import { MissionType } from '@/types/game'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import * as fleetLogic from '@/logic/fleetLogic'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
import * as npcGrowthLogic from '@/logic/npcGrowthLogic'
|
||||
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
|
||||
|
||||
/**
|
||||
* NPC处理
|
||||
* 处理NPC舰队任务、成长系统、行为系统
|
||||
*/
|
||||
export const useNPCHandler = () => {
|
||||
const gameStore = useGameStore()
|
||||
const universeStore = useUniverseStore()
|
||||
const npcStore = useNPCStore()
|
||||
|
||||
/**
|
||||
* 移除即将到来的舰队警告
|
||||
*/
|
||||
const removeIncomingFleetAlert = (alert: IncomingFleetAlert) => {
|
||||
if (!gameStore.player.incomingFleetAlerts) return
|
||||
const index = gameStore.player.incomingFleetAlerts.indexOf(alert)
|
||||
if (index > -1) {
|
||||
gameStore.player.incomingFleetAlerts.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据任务ID移除即将到来的舰队警告
|
||||
*/
|
||||
const removeIncomingFleetAlertById = (missionId: string) => {
|
||||
if (!gameStore.player.incomingFleetAlerts) return
|
||||
const index = gameStore.player.incomingFleetAlerts.findIndex(a => a.id === missionId)
|
||||
if (index > -1) {
|
||||
gameStore.player.incomingFleetAlerts.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理NPC任务到达
|
||||
*/
|
||||
const processNPCMissionArrival = (npc: NPC, mission: FleetMission) => {
|
||||
if (mission.missionType === MissionType.Recycle) {
|
||||
// NPC回收任务到达
|
||||
const debrisId = mission.debrisFieldId
|
||||
if (!debrisId) {
|
||||
console.warn('[NPC Mission] Recycle mission missing debrisFieldId')
|
||||
mission.status = 'returning'
|
||||
mission.returnTime = Date.now() + (mission.arrivalTime - mission.departureTime)
|
||||
return
|
||||
}
|
||||
|
||||
const debrisField = universeStore.debrisFields[debrisId]
|
||||
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
|
||||
|
||||
if (recycleResult && debrisField) {
|
||||
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
|
||||
// 更新残骸场
|
||||
universeStore.debrisFields[debrisId] = {
|
||||
id: debrisField.id,
|
||||
position: debrisField.position,
|
||||
resources: recycleResult.remainingDebris,
|
||||
createdAt: debrisField.createdAt
|
||||
}
|
||||
} else {
|
||||
// 残骸已被完全回收,从宇宙中删除
|
||||
delete universeStore.debrisFields[debrisId]
|
||||
}
|
||||
}
|
||||
|
||||
// 移除即将到来的警告(回收任务已到达)
|
||||
removeIncomingFleetAlertById(mission.id)
|
||||
|
||||
// 设置返回时间
|
||||
mission.returnTime = Date.now() + (mission.arrivalTime - mission.departureTime)
|
||||
return
|
||||
}
|
||||
|
||||
// 找到目标星球
|
||||
const targetKey = gameLogic.generatePositionKey(
|
||||
mission.targetPosition.galaxy,
|
||||
mission.targetPosition.system,
|
||||
mission.targetPosition.position
|
||||
)
|
||||
const targetPlanet =
|
||||
gameStore.player.planets.find(
|
||||
p =>
|
||||
p.position.galaxy === mission.targetPosition.galaxy &&
|
||||
p.position.system === mission.targetPosition.system &&
|
||||
p.position.position === mission.targetPosition.position
|
||||
) || universeStore.planets[targetKey]
|
||||
|
||||
if (!targetPlanet) {
|
||||
console.warn('[NPC Mission] Target planet not found')
|
||||
return
|
||||
}
|
||||
|
||||
if (mission.missionType === MissionType.Spy) {
|
||||
// NPC侦查到达
|
||||
const { spiedNotification, spyReport } = npcBehaviorLogic.processNPCSpyArrival(npc, mission, targetPlanet, gameStore.player)
|
||||
|
||||
// 保存侦查报告到NPC(用于后续攻击决策)
|
||||
if (!npc.playerSpyReports) {
|
||||
npc.playerSpyReports = {}
|
||||
}
|
||||
npc.playerSpyReports[targetPlanet.id] = spyReport
|
||||
|
||||
// 添加被侦查通知给玩家
|
||||
if (!gameStore.player.spiedNotifications) {
|
||||
gameStore.player.spiedNotifications = []
|
||||
}
|
||||
gameStore.player.spiedNotifications.push(spiedNotification)
|
||||
|
||||
// 移除即将到来的警告(侦查已到达)
|
||||
removeIncomingFleetAlertById(mission.id)
|
||||
} else if (mission.missionType === MissionType.Attack) {
|
||||
// NPC攻击到达 - 使用专门的NPC攻击处理逻辑
|
||||
fleetLogic.processNPCAttackArrival(npc, mission, targetPlanet, gameStore.player, gameStore.player.planets).then(attackResult => {
|
||||
if (attackResult) {
|
||||
// 添加战斗报告给玩家
|
||||
gameStore.player.battleReports.push(attackResult.battleResult)
|
||||
|
||||
// 如果生成月球,添加到玩家星球列表
|
||||
if (attackResult.moon) {
|
||||
gameStore.player.planets.push(attackResult.moon)
|
||||
}
|
||||
|
||||
// 如果生成残骸场,添加到宇宙残骸场列表
|
||||
if (attackResult.debrisField) {
|
||||
universeStore.debrisFields[attackResult.debrisField.id] = attackResult.debrisField
|
||||
}
|
||||
}
|
||||
|
||||
// 移除即将到来的警告(攻击已到达)
|
||||
removeIncomingFleetAlertById(mission.id)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理NPC任务返回
|
||||
*/
|
||||
const processNPCMissionReturn = (npc: NPC, mission: FleetMission) => {
|
||||
// 找到NPC的起始星球
|
||||
const originPlanet = npc.planets.find(p => p.id === mission.originPlanetId)
|
||||
if (!originPlanet) return
|
||||
|
||||
// 返还舰队
|
||||
shipLogic.addFleet(originPlanet.fleet, mission.fleet)
|
||||
|
||||
// 如果携带掠夺资源,给NPC添加资源
|
||||
if (mission.cargo) {
|
||||
originPlanet.resources.metal += mission.cargo.metal
|
||||
originPlanet.resources.crystal += mission.cargo.crystal
|
||||
originPlanet.resources.deuterium += mission.cargo.deuterium
|
||||
}
|
||||
|
||||
// 从NPC任务列表中移除
|
||||
if (npc.fleetMissions) {
|
||||
const missionIndex = npc.fleetMissions.indexOf(mission)
|
||||
if (missionIndex > -1) {
|
||||
npc.fleetMissions.splice(missionIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NPC成长系统更新
|
||||
let npcUpdateCounter = 0
|
||||
const NPC_UPDATE_INTERVAL = 10
|
||||
|
||||
/**
|
||||
* 更新NPC成长系统
|
||||
*/
|
||||
const updateNPCGrowth = (deltaSeconds: number) => {
|
||||
// 累积时间
|
||||
npcUpdateCounter += deltaSeconds
|
||||
|
||||
// 只在达到更新间隔时才执行
|
||||
if (npcUpdateCounter < NPC_UPDATE_INTERVAL) {
|
||||
return
|
||||
}
|
||||
|
||||
// 获取所有星球
|
||||
const allPlanets = Object.values(universeStore.planets)
|
||||
|
||||
// 如果NPC store为空,从星球数据中初始化NPC
|
||||
if (npcStore.npcs.length === 0) {
|
||||
const npcMap = new Map<string, any>()
|
||||
|
||||
allPlanets.forEach(planet => {
|
||||
// 跳过玩家的星球
|
||||
if (planet.ownerId === gameStore.player.id || !planet.ownerId) return
|
||||
|
||||
// 这是NPC的星球
|
||||
if (!npcMap.has(planet.ownerId)) {
|
||||
npcMap.set(planet.ownerId, {
|
||||
id: planet.ownerId,
|
||||
name: `NPC-${planet.ownerId.substring(0, 8)}`,
|
||||
planets: [],
|
||||
technologies: {},
|
||||
difficulty: 'medium' as const,
|
||||
relations: {},
|
||||
allies: [],
|
||||
enemies: []
|
||||
})
|
||||
}
|
||||
|
||||
npcMap.get(planet.ownerId)!.planets.push(planet)
|
||||
})
|
||||
|
||||
// 保存到store
|
||||
npcStore.npcs = Array.from(npcMap.values())
|
||||
|
||||
// 如果有NPC,基于玩家实力初始化NPC
|
||||
if (npcStore.npcs.length > 0) {
|
||||
const gameState: npcGrowthLogic.NPCGrowthGameState = {
|
||||
planets: allPlanets,
|
||||
player: gameStore.player,
|
||||
npcs: npcStore.npcs
|
||||
}
|
||||
|
||||
const playerPower = npcGrowthLogic.calculatePlayerAveragePower(gameState)
|
||||
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcGrowthLogic.initializeNPCStartingPower(npc, playerPower)
|
||||
})
|
||||
|
||||
// 初始化NPC之间的外交关系(盟友/敌人)
|
||||
npcGrowthLogic.initializeNPCDiplomacy(npcStore.npcs)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有NPC,直接返回
|
||||
if (npcStore.npcs.length === 0) {
|
||||
npcUpdateCounter = 0
|
||||
return
|
||||
}
|
||||
|
||||
// 构建游戏状态
|
||||
const gameState: npcGrowthLogic.NPCGrowthGameState = {
|
||||
planets: allPlanets,
|
||||
player: gameStore.player,
|
||||
npcs: npcStore.npcs
|
||||
}
|
||||
|
||||
// 使用累积的时间更新每个NPC
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcGrowthLogic.updateNPCGrowth(npc, gameState, npcUpdateCounter)
|
||||
})
|
||||
|
||||
// 重置计数器
|
||||
npcUpdateCounter = 0
|
||||
}
|
||||
|
||||
// NPC行为系统更新
|
||||
let npcBehaviorCounter = 0
|
||||
const NPC_BEHAVIOR_INTERVAL = 5
|
||||
|
||||
/**
|
||||
* 更新NPC行为系统
|
||||
*/
|
||||
const updateNPCBehavior = (deltaSeconds: number) => {
|
||||
// 累积时间
|
||||
npcBehaviorCounter += deltaSeconds
|
||||
|
||||
// 只在达到更新间隔时才执行
|
||||
if (npcBehaviorCounter < NPC_BEHAVIOR_INTERVAL) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有NPC,直接返回
|
||||
if (npcStore.npcs.length === 0) {
|
||||
npcBehaviorCounter = 0
|
||||
return
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const allPlanets = Object.values(universeStore.planets)
|
||||
|
||||
// 更新每个NPC的行为
|
||||
npcStore.npcs.forEach(npc => {
|
||||
npcBehaviorLogic.updateNPCBehavior(npc, gameStore.player, allPlanets, universeStore.debrisFields, now)
|
||||
})
|
||||
|
||||
npcBehaviorCounter = 0
|
||||
}
|
||||
|
||||
return {
|
||||
processNPCMissionArrival,
|
||||
processNPCMissionReturn,
|
||||
removeIncomingFleetAlert,
|
||||
removeIncomingFleetAlertById,
|
||||
updateNPCGrowth,
|
||||
updateNPCBehavior
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
import type { Ref } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import type { BuildQueueItem } from '@/types/game'
|
||||
import * as buildingValidation from '@/logic/buildingValidation'
|
||||
import * as resourceLogic from '@/logic/resourceLogic'
|
||||
import * as researchValidation from '@/logic/researchValidation'
|
||||
|
||||
/**
|
||||
* 队列处理
|
||||
* 处理建造队列和研究队列的取消操作
|
||||
*/
|
||||
export const useQueueHandler = (
|
||||
t: (key: string) => string,
|
||||
confirmDialogOpen: Ref<boolean>,
|
||||
confirmDialogTitle: Ref<string>,
|
||||
confirmDialogMessage: Ref<string>,
|
||||
confirmDialogAction: Ref<(() => void) | null>
|
||||
) => {
|
||||
const gameStore = useGameStore()
|
||||
|
||||
/**
|
||||
* 取消建造
|
||||
*/
|
||||
const handleCancelBuild = (queueId: string) => {
|
||||
confirmDialogTitle.value = t('queue.cancelBuild')
|
||||
confirmDialogMessage.value = t('queue.confirmCancel')
|
||||
confirmDialogAction.value = () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.currentPlanet.buildQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'building') {
|
||||
const refund = buildingValidation.cancelBuildingUpgrade(gameStore.currentPlanet, item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.currentPlanet.buildQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
confirmDialogOpen.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消研究
|
||||
*/
|
||||
const handleCancelResearch = (queueId: string) => {
|
||||
confirmDialogTitle.value = t('queue.cancelResearch')
|
||||
confirmDialogMessage.value = t('queue.confirmCancel')
|
||||
confirmDialogAction.value = () => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const { item, index } = buildingValidation.findQueueItem(gameStore.player.researchQueue, queueId)
|
||||
if (!item) return false
|
||||
if (item.type === 'technology') {
|
||||
const refund = researchValidation.cancelTechnologyResearch(item)
|
||||
resourceLogic.addResources(gameStore.currentPlanet.resources, refund)
|
||||
}
|
||||
gameStore.player.researchQueue.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
confirmDialogOpen.value = true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列项名称
|
||||
*/
|
||||
const getItemName = (item: BuildQueueItem): string => {
|
||||
if (item.type === 'building' || item.type === 'demolish') {
|
||||
const buildingName = t(`buildings.${item.itemType}`)
|
||||
return item.type === 'demolish' ? `${t('buildingsView.demolish')} - ${buildingName}` : buildingName
|
||||
} else if (item.type === 'technology') {
|
||||
return t(`technologies.${item.itemType}`)
|
||||
} else if (item.type === 'ship') {
|
||||
return t(`ships.${item.itemType}`)
|
||||
} else if (item.type === 'defense') {
|
||||
return t(`defenses.${item.itemType}`)
|
||||
}
|
||||
return t('common.unknown')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取剩余时间(秒)
|
||||
*/
|
||||
const getRemainingTime = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
return Math.max(0, Math.floor((item.endTime - now) / 1000))
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列进度(百分比)
|
||||
*/
|
||||
const getQueueProgress = (item: BuildQueueItem): number => {
|
||||
const now = Date.now()
|
||||
const total = item.endTime - item.startTime
|
||||
const elapsed = now - item.startTime
|
||||
return Math.min(100, Math.max(0, (elapsed / total) * 100))
|
||||
}
|
||||
|
||||
return {
|
||||
handleCancelBuild,
|
||||
handleCancelResearch,
|
||||
getItemName,
|
||||
getRemainingTime,
|
||||
getQueueProgress
|
||||
}
|
||||
}
|
||||
@@ -123,7 +123,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
baseTime: 30, // 减少建造时间:60→30秒
|
||||
costMultiplier: 2,
|
||||
spaceUsage: 5,
|
||||
fleetStorageBonus: 1000, // 每级增加100舰队仓储
|
||||
fleetStorageBonus: 1000, // 每级增加1000舰队仓储
|
||||
requirements: { [BuildingType.RoboticsFactory]: 2 },
|
||||
levelRequirements: {
|
||||
8: { [BuildingType.RoboticsFactory]: 5, [BuildingType.ResearchLab]: 5 },
|
||||
@@ -251,7 +251,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
[BuildingType.Terraformer]: {
|
||||
id: BuildingType.Terraformer,
|
||||
name: '地形改造器',
|
||||
description: '改造行星地形,每级增加5个可用空间',
|
||||
description: '改造行星地形,每级增加30个可用空间',
|
||||
baseCost: { metal: 0, crystal: 50000, deuterium: 100000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
@@ -433,7 +433,7 @@ export const TECHNOLOGIES: Record<TechnologyType, TechnologyConfig> = {
|
||||
baseCost: { metal: 0, crystal: 400, deuterium: 600, darkMatter: 0, energy: 0 },
|
||||
baseTime: 60,
|
||||
costMultiplier: 2,
|
||||
fleetStorageBonus: 500, // 每级全局增加50舰队仓储
|
||||
fleetStorageBonus: 500, // 每级全局增加500舰队仓储
|
||||
maxLevel: 10, // 最多10级(最多11个研究队列)
|
||||
requirements: { [BuildingType.ResearchLab]: 1 },
|
||||
levelRequirements: {
|
||||
@@ -588,7 +588,7 @@ export const TECHNOLOGIES: Record<TechnologyType, TechnologyConfig> = {
|
||||
[TechnologyType.TerraformingTechnology]: {
|
||||
id: TechnologyType.TerraformingTechnology,
|
||||
name: '地形改造技术',
|
||||
description: '研究行星地形改造技术,每级为所有行星增加5个可用空间',
|
||||
description: '研究行星地形改造技术,每级为所有行星增加30个可用空间',
|
||||
baseCost: { metal: 0, crystal: 20000, deuterium: 40000, darkMatter: 0, energy: 0 },
|
||||
baseTime: 90,
|
||||
costMultiplier: 2,
|
||||
@@ -867,7 +867,7 @@ export const SHIPS: Record<ShipType, ShipConfig> = {
|
||||
fuelConsumption: 1,
|
||||
storageUsage: 100,
|
||||
requirements: {
|
||||
[BuildingType.PlanetDestroyerFactory]: 10,
|
||||
[BuildingType.PlanetDestroyerFactory]: 3,
|
||||
[TechnologyType.PlanetDestructionTech]: 7,
|
||||
[TechnologyType.HyperspaceDrive]: 7
|
||||
}
|
||||
@@ -1085,15 +1085,15 @@ export const MOON_CONFIG = {
|
||||
baseChance: 1, // 基础1%概率
|
||||
maxChance: 20, // 最大20%概率
|
||||
chancePerDebris: 100000, // 每10万资源增加1%概率
|
||||
baseSize: 60, // 月球基础空间
|
||||
lunarBaseSpaceBonus: 5 // 每级月球基地增加的空间
|
||||
baseSize: 100, // 月球基础空间
|
||||
lunarBaseSpaceBonus: 30 // 每级月球基地增加的空间
|
||||
}
|
||||
|
||||
// 行星配置
|
||||
export const PLANET_CONFIG = {
|
||||
baseSize: 200, // 行星基础空间
|
||||
terraformerSpaceBonus: 5, // 每级地形改造器增加的空间
|
||||
terraformingTechSpaceBonus: 3 // 每级地形改造技术增加的空间
|
||||
baseSize: 300, // 行星基础空间
|
||||
terraformerSpaceBonus: 30, // 每级地形改造器增加的空间
|
||||
terraformingTechSpaceBonus: 30 // 每级地形改造技术增加的空间
|
||||
}
|
||||
|
||||
// 舰队仓储配置
|
||||
|
||||
@@ -33,13 +33,19 @@ export default {
|
||||
viewRequirements: 'Anforderungen anzeigen',
|
||||
requirementsNotMet: 'Anforderungen nicht erfüllt',
|
||||
current: 'Aktuell',
|
||||
level: 'Stufe'
|
||||
level: 'Stufe',
|
||||
gmModeActivated: 'GM-Modus aktiviert! Überprüfen Sie das Navigationsmenü.'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: 'Anforderungen nicht erfüllt',
|
||||
insufficientResources: 'Unzureichende Ressourcen',
|
||||
insufficientFleetStorage: 'Unzureichender Flottenspeicher',
|
||||
shieldDomeLimit: 'Schildkuppel-Limit erreicht',
|
||||
missileSiloLimit: 'Raketensilokapazität überschritten',
|
||||
insufficientMissiles: 'Unzureichende Interkontinentalraketen',
|
||||
invalidMissileCount: 'Ungültige Raketenanzahl',
|
||||
targetOutOfRange: 'Ziel außer Reichweite',
|
||||
cannotAttackOwnPlanet: 'Eigenen Planeten kann nicht angegriffen werden',
|
||||
fleetMissionsFull: 'Flottenmissionsplätze voll',
|
||||
insufficientFleet: 'Unzureichende Flotte',
|
||||
insufficientFuel: 'Unzureichender Treibstoff',
|
||||
@@ -166,8 +172,8 @@ export default {
|
||||
darkMatterCollector: 'Sammelt seltene Dunkle-Materie-Ressourcen',
|
||||
darkMatterTank: 'Erhöht Dunkle-Materie-Speicherkapazität',
|
||||
missileSilo: 'Lagert und startet Raketen, 10 Raketen pro Stufe',
|
||||
terraformer: 'Terraformt Planetenoberfläche, erhöht verfügbaren Platz um 5 pro Stufe',
|
||||
lunarBase: 'Erhöht verfügbaren Platz auf dem Mond, +5 Platz pro Stufe',
|
||||
terraformer: 'Terraformt Planetenoberfläche, erhöht verfügbaren Platz um 30 pro Stufe',
|
||||
lunarBase: 'Erhöht verfügbaren Platz auf dem Mond, +30 Platz pro Stufe',
|
||||
sensorPhalanx: 'Erkennt Flottenaktivitäten in umliegenden Systemen',
|
||||
jumpGate: 'Überträgt Flotten sofort zu anderen Monden',
|
||||
planetDestroyerFactory: 'Konstruiert ultimative Waffen zur Zerstörung von Planeten'
|
||||
@@ -283,7 +289,7 @@ export default {
|
||||
impulseDrive: 'Mittlere Antriebstechnologie',
|
||||
hyperspaceDrive: 'Fortgeschrittene Antriebstechnologie',
|
||||
darkMatterTechnology: 'Forschung zu Eigenschaften und Anwendungen von Dunkler Materie',
|
||||
terraformingTechnology: 'Forschung zur Planeten-Terraforming-Technologie, erhöht verfügbaren Platz aller Planeten um 3 pro Stufe',
|
||||
terraformingTechnology: 'Forschung zur Planeten-Terraforming-Technologie, erhöht verfügbaren Platz aller Planeten um 30 pro Stufe',
|
||||
planetDestructionTech: 'Schreckliche Technologie zur Zerstörung ganzer Planeten'
|
||||
},
|
||||
officers: {
|
||||
@@ -316,6 +322,7 @@ export default {
|
||||
cancelResearch: 'Forschung abbrechen',
|
||||
confirmCancel: 'Möchten Sie wirklich abbrechen? 50% der Ressourcen werden zurückerstattet.',
|
||||
level: 'Stufe',
|
||||
gmModeActivated: '',
|
||||
upgradeToLevel: 'Auf Stufe aufrüsten'
|
||||
},
|
||||
overview: {
|
||||
@@ -336,6 +343,7 @@ export default {
|
||||
usedSpace: 'Verwendeter Platz',
|
||||
spaceUsage: 'Platzbedarf',
|
||||
level: 'Stufe',
|
||||
gmModeActivated: '',
|
||||
upgradeCost: 'Ausbaukosten',
|
||||
buildTime: 'Bauzeit',
|
||||
upgrade: 'Ausbauen',
|
||||
@@ -361,6 +369,7 @@ export default {
|
||||
},
|
||||
shipyard: {
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
speed: 'Geschwindigkeit',
|
||||
@@ -378,6 +387,7 @@ export default {
|
||||
title: 'Raumschiffwerft',
|
||||
fleetStorage: 'Flottenspeicher',
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
shield: 'Schild',
|
||||
speed: 'Geschwindigkeit',
|
||||
cargoCapacity: 'Ladekapazität',
|
||||
@@ -392,6 +402,7 @@ export default {
|
||||
},
|
||||
defense: {
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
buildCost: 'Baukosten',
|
||||
@@ -405,6 +416,7 @@ export default {
|
||||
defenseView: {
|
||||
title: 'Verteidigung',
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
buildTime: 'Bauzeit',
|
||||
@@ -414,6 +426,7 @@ export default {
|
||||
totalCost: 'Gesamtkosten',
|
||||
build: 'Bauen',
|
||||
shieldDomeBuilt: 'Schildkuppel bereits gebaut',
|
||||
missileCapacity: 'Raketenkapazität',
|
||||
inputError: 'Eingabefehler',
|
||||
inputErrorMessage: 'Bitte Baumenge eingeben!',
|
||||
buildFailed: 'Bau fehlgeschlagen',
|
||||
@@ -427,6 +440,7 @@ export default {
|
||||
flightMissions: 'Flugmissionen',
|
||||
currentPlanetFleet: 'Flotte auf diesem Planeten',
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
shield: 'Schild',
|
||||
armor: 'Panzerung',
|
||||
speed: 'Geschwindigkeit',
|
||||
@@ -521,27 +535,38 @@ export default {
|
||||
selectSystem: 'System auswählen',
|
||||
view: 'Anzeigen',
|
||||
myPlanet: 'Mein Planet',
|
||||
myPlanets: 'Meine Planeten',
|
||||
myPlanets: 'Meine Systeme ansehen',
|
||||
npcPlanets: 'NPC-Planeten',
|
||||
selectPlanetToView: 'Planet zum Anzeigen auswählen',
|
||||
selectPlanetToView: 'Planet auswählen, um sein System anzuzeigen',
|
||||
totalPositions: 'Insgesamt 10 Planetenpositionen',
|
||||
mine: 'Mein',
|
||||
hostile: 'Feindlich',
|
||||
emptySlot: 'Leer - Kolonisierbar',
|
||||
scout: 'Spähen',
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
colonize: 'Kolonisieren',
|
||||
switch: 'Wechseln',
|
||||
recycle: 'Recyceln',
|
||||
debrisField: 'Trümmerfeld',
|
||||
scoutPlanetTitle: 'Planet ausspionieren',
|
||||
attackPlanetTitle: 'Planet angreifen',
|
||||
missileAttackTitle: 'Raketenangriff',
|
||||
colonizePlanetTitle: 'Planet kolonisieren',
|
||||
recyclePlanetTitle: 'Trümmer recyceln',
|
||||
scoutPlanetMessage:
|
||||
'Möchten Sie wirklich Spionagesonden senden, um Planet [{coordinates}] auszuspionieren?\n\nBitte gehen Sie zur Flottenseite, um Schiffe auszuwählen und zu senden.',
|
||||
attackPlanetMessage:
|
||||
'Möchten Sie wirklich Planet [{coordinates}] angreifen?\n\nBitte gehen Sie zur Flottenseite, um Schiffe auszuwählen und zu senden.',
|
||||
missileAttackMessage: 'Interkontinentalraketen starten, um Planet [{coordinates}] anzugreifen',
|
||||
missileCount: 'Raketenanzahl',
|
||||
availableMissiles: 'Verfügbare Raketen',
|
||||
missileRange: 'Raketenreichweite',
|
||||
systems: 'Systeme',
|
||||
distance: 'Entfernung',
|
||||
flightTime: 'Flugzeit',
|
||||
launchMissile: 'Starten',
|
||||
cancel: 'Abbrechen',
|
||||
colonizePlanetMessage:
|
||||
'Möchten Sie wirklich Position [{coordinates}] kolonisieren?\n\nBitte gehen Sie zur Flottenseite, um ein Kolonieschiff zu senden.',
|
||||
recyclePlanetMessage:
|
||||
@@ -699,10 +724,24 @@ export default {
|
||||
gamePaused: 'Spiel pausiert',
|
||||
gameResumed: 'Spiel fortgesetzt',
|
||||
playerName: 'Spielername',
|
||||
gameSpeed: 'Spielgeschwindigkeit',
|
||||
gameSpeedDesc: 'Aktueller Spielgeschwindigkeitsmultiplikator',
|
||||
gameSpeed: 'Ressourcenproduktionsgeschwindigkeit',
|
||||
gameSpeedDesc: 'Aktueller Ressourcenproduktionsgeschwindigkeitsmultiplikator',
|
||||
speedChanged: 'Ressourcenproduktionsgeschwindigkeit auf {speed}x geändert',
|
||||
speedReset: 'Ressourcenproduktionsgeschwindigkeit auf 1x zurückgesetzt',
|
||||
reset: 'Zurücksetzen',
|
||||
about: 'Über',
|
||||
version: 'Version',
|
||||
latestVersion: 'Neueste Version',
|
||||
checkUpdate: 'Update prüfen',
|
||||
checking: 'Prüfen...',
|
||||
newVersionAvailable: 'Neue Version {version} verfügbar',
|
||||
upToDate: 'Bereits auf dem neuesten Stand',
|
||||
checkUpdateCooldown: 'Bitte versuchen Sie es später erneut (5 Minuten Abklingzeit)',
|
||||
checkUpdateFailed: 'Update-Prüfung fehlgeschlagen, bitte überprüfen Sie Ihre Netzwerkverbindung',
|
||||
viewUpdate: 'Update ansehen',
|
||||
updateAvailable: 'Eine neue Version ist verfügbar. Klicken Sie, um die Versionshinweise anzuzeigen.',
|
||||
download: 'Herunterladen',
|
||||
goToDownload: 'Zum Download',
|
||||
buildDate: 'Build-Datum',
|
||||
community: 'Community',
|
||||
github: 'GitHub-Repository',
|
||||
@@ -741,12 +780,22 @@ export default {
|
||||
testSpy: 'Spionage testen',
|
||||
testAttack: 'Angriff testen',
|
||||
testSpyAndAttack: 'Spionage & Angriff testen',
|
||||
testSpyMessage: 'Klicken Sie auf Bestätigen, um die Spionagemission zu beschleunigen',
|
||||
testAttackMessage: 'Klicken Sie auf Bestätigen, um die Angriffsmission zu beschleunigen',
|
||||
testSpyAndAttackMessage: 'Klicken Sie auf Bestätigen, um die Missionen zu beschleunigen',
|
||||
initializeFleet: 'NPC-Flotte initialisieren',
|
||||
accelerateMissions: 'Alle Missionen beschleunigen (5s)',
|
||||
selectNPCFirst: 'Bitte wählen Sie zuerst einen NPC',
|
||||
npcNoProbes: 'NPC hat keine Spionagesonden',
|
||||
npcNoSpyReport: 'NPC muss zuerst spionieren',
|
||||
npcMissionFailed: 'Mission konnte nicht erstellt werden',
|
||||
npcNoPlanets: 'NPC hat keine Planeten',
|
||||
npcWillSpyIn5s: '{npcName} wird in 5 Sekunden spionieren',
|
||||
npcWillAttackIn5s: '{npcName} wird in 5 Sekunden angreifen',
|
||||
npcWillSpyAndAttack: '{npcName} wird in 5s spionieren und in 10s angreifen',
|
||||
acceleratedMissions: '{count} Missionen auf 5 Sekunden beschleunigt',
|
||||
npcFleetInitialized: '{npcName} Flotte initialisiert',
|
||||
npcFleetDetails: '100 Spionagesonden\n500 Leichte Jäger\n300 Schwere Jäger\n200 Kreuzer\n100 Schlachtschiffe\n50 Bomber\n30 Zerstörer\n20 Schlachtkreuzer',
|
||||
dangerZone: 'Gefahrenzone',
|
||||
dangerZoneDesc: 'Die folgenden Vorgänge sind irreversibel',
|
||||
resetGame: 'Spiel zurücksetzen',
|
||||
@@ -798,9 +847,32 @@ export default {
|
||||
events: {
|
||||
gift: 'Geschenk gesendet',
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
allyAttacked: 'Verbündeter angegriffen',
|
||||
spy: 'Spionage',
|
||||
stealDebris: 'Trümmer gestohlen'
|
||||
},
|
||||
reports: {
|
||||
giftedResources: '{metal}M {crystal}K {deuterium}D geschenkt',
|
||||
receivedGiftFromPlayer: 'Geschenk von Spieler erhalten',
|
||||
giftedToNpc: 'Sie haben {npcName} Ressourcen geschenkt. Ansehen +{reputation}',
|
||||
rejectedPlayerGift: 'Geschenk des Spielers abgelehnt',
|
||||
npcRejectedGift: '{npcName} hat Ihr Geschenk abgelehnt. Ansehen {reputation}',
|
||||
attackedNpc: '{npcName} angegriffen',
|
||||
wasAttackedByPlayer: 'Wurde von Spieler angegriffen',
|
||||
youAttackedNpc: 'Sie haben {npcName} angegriffen',
|
||||
playerAttackedAlly: 'Spieler hat Verbündeten {allyName} angegriffen',
|
||||
allyDispleased: '{allyName} ist unzufrieden, dass Sie ihren Verbündeten {targetName} angegriffen haben',
|
||||
wasSpiedByPlayer: 'Wurde von Spieler ausspioniert (entdeckt: {detected})',
|
||||
spyDetected: 'Ihre Spionage wurde von {npcName} entdeckt',
|
||||
stoleDebrisFromTerritory: 'Trümmer aus {npcName}s Territorium gestohlen',
|
||||
playerStoleDebris: 'Spieler hat Trümmer aus Territorium gestohlen',
|
||||
recycledDebrisNearNpc: 'Sie haben Trümmer in der Nähe von {npcName}s Planeten recycelt. Sie sind unzufrieden.',
|
||||
giftedResourcesToPlayer: 'Ressourcen an Spieler geschenkt',
|
||||
receivedGiftFromNpc: 'Geschenk von {npcName} erhalten',
|
||||
acceptedGiftFromNpc: 'Sie haben ein Geschenk von {npcName} angenommen: {metal}M {crystal}K {deuterium}D',
|
||||
playerRejectedGift: 'Spieler hat Geschenk abgelehnt',
|
||||
rejectedGiftFromNpc: 'Sie haben ein Geschenk von {npcName} abgelehnt. Ansehen {reputation}'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -809,5 +881,10 @@ export default {
|
||||
first: 'Erste',
|
||||
last: 'Letzte',
|
||||
page: 'Seite {page}'
|
||||
},
|
||||
notFound: {
|
||||
title: 'Seite nicht gefunden',
|
||||
description: 'Entschuldigung, die gesuchte Seite existiert nicht',
|
||||
goHome: 'Zur Startseite'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,19 @@ export default {
|
||||
viewRequirements: 'View Requirements',
|
||||
requirementsNotMet: 'Requirements Not Met',
|
||||
current: 'Current',
|
||||
level: 'Level'
|
||||
level: 'Level',
|
||||
gmModeActivated: 'GM Mode Activated! Check the navigation menu.'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: 'Requirements not met',
|
||||
insufficientResources: 'Insufficient resources',
|
||||
insufficientFleetStorage: 'Insufficient fleet storage',
|
||||
shieldDomeLimit: 'Shield dome limit reached',
|
||||
missileSiloLimit: 'Missile silo capacity exceeded',
|
||||
insufficientMissiles: 'Insufficient interplanetary missiles',
|
||||
invalidMissileCount: 'Invalid missile count',
|
||||
targetOutOfRange: 'Target out of range',
|
||||
cannotAttackOwnPlanet: 'Cannot attack your own planet',
|
||||
fleetMissionsFull: 'Fleet mission slots full',
|
||||
insufficientFleet: 'Insufficient fleet',
|
||||
insufficientFuel: 'Insufficient fuel',
|
||||
@@ -164,8 +170,8 @@ export default {
|
||||
darkMatterCollector: 'Collects rare dark matter resources',
|
||||
darkMatterTank: 'Increases dark matter storage capacity',
|
||||
missileSilo: 'Stores and launches missiles, 10 missiles per level',
|
||||
terraformer: 'Terraforms planet surface, adds 5 available space per level',
|
||||
lunarBase: 'Increases available space on the moon, +5 space per level',
|
||||
terraformer: 'Terraforms planet surface, adds 30 available space per level',
|
||||
lunarBase: 'Increases available space on the moon, +30 space per level',
|
||||
sensorPhalanx: 'Detects fleet activities in surrounding systems',
|
||||
jumpGate: 'Instantly transfers fleets to other moons',
|
||||
planetDestroyerFactory: 'Constructs ultimate weapons capable of destroying planets'
|
||||
@@ -283,7 +289,7 @@ export default {
|
||||
impulseDrive: 'Intermediate propulsion technology',
|
||||
hyperspaceDrive: 'Advanced propulsion technology',
|
||||
darkMatterTechnology: 'Research into dark matter properties and applications',
|
||||
terraformingTechnology: 'Research planet terraforming technology, adds 3 available space to all planets per level',
|
||||
terraformingTechnology: 'Research planet terraforming technology, adds 30 available space to all planets per level',
|
||||
planetDestructionTech: 'Terrifying technology for destroying entire planets'
|
||||
},
|
||||
officers: {
|
||||
@@ -364,6 +370,7 @@ export default {
|
||||
},
|
||||
shipyard: {
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
speed: 'Speed',
|
||||
@@ -381,6 +388,7 @@ export default {
|
||||
title: 'Shipyard',
|
||||
fleetStorage: 'Fleet Storage',
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
shield: 'Shield',
|
||||
speed: 'Speed',
|
||||
cargoCapacity: 'Cargo Capacity',
|
||||
@@ -395,6 +403,7 @@ export default {
|
||||
},
|
||||
defense: {
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
buildCost: 'Build Cost',
|
||||
@@ -408,6 +417,7 @@ export default {
|
||||
defenseView: {
|
||||
title: 'Defense',
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
buildTime: 'Build Time',
|
||||
@@ -417,6 +427,7 @@ export default {
|
||||
totalCost: 'Total Cost',
|
||||
build: 'Build',
|
||||
shieldDomeBuilt: 'Shield dome already built',
|
||||
missileCapacity: 'Missile Capacity',
|
||||
inputError: 'Input Error',
|
||||
inputErrorMessage: 'Please enter build quantity!',
|
||||
buildFailed: 'Build Failed',
|
||||
@@ -429,6 +440,7 @@ export default {
|
||||
flightMissions: 'Flight Missions',
|
||||
currentPlanetFleet: 'Current Planet Fleet',
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
shield: 'Shield',
|
||||
armor: 'Armor',
|
||||
speed: 'Speed',
|
||||
@@ -525,26 +537,37 @@ export default {
|
||||
selectSystem: 'Select System',
|
||||
view: 'View',
|
||||
myPlanet: 'My Planet',
|
||||
myPlanets: 'My Planets',
|
||||
myPlanets: 'View My Systems',
|
||||
npcPlanets: 'NPC Planets',
|
||||
selectPlanetToView: 'Select planet to view',
|
||||
selectPlanetToView: 'Select planet to view its system',
|
||||
totalPositions: '10 planet positions total',
|
||||
mine: 'Mine',
|
||||
hostile: 'Hostile',
|
||||
emptySlot: 'Empty - Colonizable',
|
||||
scout: 'Scout',
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
colonize: 'Colonize',
|
||||
switch: 'Switch',
|
||||
recycle: 'Recycle',
|
||||
debrisField: 'Debris Field',
|
||||
scoutPlanetTitle: 'Scout Planet',
|
||||
attackPlanetTitle: 'Attack Planet',
|
||||
missileAttackTitle: 'Missile Attack',
|
||||
colonizePlanetTitle: 'Colonize Planet',
|
||||
recyclePlanetTitle: 'Recycle Debris',
|
||||
scoutPlanetMessage:
|
||||
'Are you sure you want to send espionage probes to scout planet [{coordinates}]?\n\nPlease go to the fleet page to select ships and send.',
|
||||
attackPlanetMessage: 'Are you sure you want to attack planet [{coordinates}]?\n\nPlease go to the fleet page to select ships and send.',
|
||||
missileAttackMessage: 'Launch interplanetary missiles to attack planet [{coordinates}]',
|
||||
missileCount: 'Missile Count',
|
||||
availableMissiles: 'Available Missiles',
|
||||
missileRange: 'Missile Range',
|
||||
systems: 'systems',
|
||||
distance: 'Distance',
|
||||
flightTime: 'Flight Time',
|
||||
launchMissile: 'Launch',
|
||||
cancel: 'Cancel',
|
||||
colonizePlanetMessage:
|
||||
'Are you sure you want to colonize position [{coordinates}]?\n\nPlease go to the fleet page to send a colony ship.',
|
||||
recyclePlanetMessage:
|
||||
@@ -697,10 +720,24 @@ export default {
|
||||
gamePaused: 'Game paused',
|
||||
gameResumed: 'Game resumed',
|
||||
playerName: 'Player Name',
|
||||
gameSpeed: 'Game Speed',
|
||||
gameSpeedDesc: 'Current game speed multiplier',
|
||||
gameSpeed: 'Resource Production Speed',
|
||||
gameSpeedDesc: 'Current resource production speed multiplier',
|
||||
speedChanged: 'Resource production speed changed to {speed}x',
|
||||
speedReset: 'Resource production speed reset to 1x',
|
||||
reset: 'Reset',
|
||||
about: 'About',
|
||||
version: 'Version',
|
||||
latestVersion: 'Latest Version',
|
||||
checkUpdate: 'Check Update',
|
||||
checking: 'Checking...',
|
||||
newVersionAvailable: 'New version {version} available',
|
||||
upToDate: 'Already up to date',
|
||||
checkUpdateCooldown: 'Please try again later (5 minute cooldown)',
|
||||
checkUpdateFailed: 'Failed to check for updates, please check your network connection',
|
||||
viewUpdate: 'View Update',
|
||||
updateAvailable: 'A new version is available. Click to view release notes.',
|
||||
download: 'Download',
|
||||
goToDownload: 'Go to Download',
|
||||
buildDate: 'Build Date',
|
||||
community: 'Community',
|
||||
github: 'GitHub Repository',
|
||||
@@ -739,12 +776,22 @@ export default {
|
||||
testSpy: 'Test Spy',
|
||||
testAttack: 'Test Attack',
|
||||
testSpyAndAttack: 'Test Spy & Attack',
|
||||
testSpyMessage: 'Click confirm to accelerate the spy mission',
|
||||
testAttackMessage: 'Click confirm to accelerate the attack mission',
|
||||
testSpyAndAttackMessage: 'Click confirm to accelerate the missions',
|
||||
initializeFleet: 'Initialize NPC Fleet',
|
||||
accelerateMissions: 'Accelerate All Missions (5s)',
|
||||
selectNPCFirst: 'Please select an NPC first',
|
||||
npcNoProbes: 'NPC has no spy probes',
|
||||
npcNoSpyReport: 'NPC needs to spy first',
|
||||
npcMissionFailed: 'Failed to create mission',
|
||||
npcNoPlanets: 'NPC has no planets',
|
||||
npcWillSpyIn5s: '{npcName} will spy in 5 seconds',
|
||||
npcWillAttackIn5s: '{npcName} will attack in 5 seconds',
|
||||
npcWillSpyAndAttack: '{npcName} will spy in 5s and attack in 10s',
|
||||
acceleratedMissions: 'Accelerated {count} missions to 5 seconds',
|
||||
npcFleetInitialized: '{npcName} fleet initialized',
|
||||
npcFleetDetails: '100 Spy Probes\n500 Light Fighters\n300 Heavy Fighters\n200 Cruisers\n100 Battleships\n50 Bombers\n30 Destroyers\n20 Battlecruisers',
|
||||
dangerZone: 'Danger Zone',
|
||||
dangerZoneDesc: 'The following operations are irreversible',
|
||||
resetGame: 'Reset Game',
|
||||
@@ -796,9 +843,32 @@ export default {
|
||||
events: {
|
||||
gift: 'Sent Gift',
|
||||
attack: 'Attack',
|
||||
missileAttack: 'Missile Attack',
|
||||
allyAttacked: 'Ally Attacked',
|
||||
spy: 'Espionage',
|
||||
stealDebris: 'Debris Stolen'
|
||||
},
|
||||
reports: {
|
||||
giftedResources: 'Gifted {metal}M {crystal}C {deuterium}D',
|
||||
receivedGiftFromPlayer: 'Received gift from player',
|
||||
giftedToNpc: 'You gifted resources to {npcName}. Reputation +{reputation}',
|
||||
rejectedPlayerGift: 'Rejected player\'s gift',
|
||||
npcRejectedGift: '{npcName} rejected your gift. Reputation {reputation}',
|
||||
attackedNpc: 'Attacked {npcName}',
|
||||
wasAttackedByPlayer: 'Was attacked by player',
|
||||
youAttackedNpc: 'You attacked {npcName}',
|
||||
playerAttackedAlly: 'Player attacked ally {allyName}',
|
||||
allyDispleased: '{allyName} is displeased that you attacked their ally {targetName}',
|
||||
wasSpiedByPlayer: 'Was spied by player (detected: {detected})',
|
||||
spyDetected: 'Your espionage was detected by {npcName}',
|
||||
stoleDebrisFromTerritory: 'Stole debris from {npcName}\'s territory',
|
||||
playerStoleDebris: 'Player stole debris from territory',
|
||||
recycledDebrisNearNpc: 'You recycled debris near {npcName}\'s planet. They are displeased.',
|
||||
giftedResourcesToPlayer: 'Gifted resources to player',
|
||||
receivedGiftFromNpc: 'Received gift from {npcName}',
|
||||
acceptedGiftFromNpc: 'You accepted a gift from {npcName}: {metal}M {crystal}C {deuterium}D',
|
||||
playerRejectedGift: 'Player rejected gift',
|
||||
rejectedGiftFromNpc: 'You rejected a gift from {npcName}. Reputation {reputation}'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -807,5 +877,10 @@ export default {
|
||||
first: 'First',
|
||||
last: 'Last',
|
||||
page: 'Page {page}'
|
||||
},
|
||||
notFound: {
|
||||
title: 'Page Not Found',
|
||||
description: 'Sorry, the page you are looking for does not exist',
|
||||
goHome: 'Go Home'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,19 @@ export default {
|
||||
viewRequirements: '必要条件を表示',
|
||||
requirementsNotMet: '必要条件が満たされていません',
|
||||
current: '現在',
|
||||
level: 'レベル'
|
||||
level: 'レベル',
|
||||
gmModeActivated: 'GMモードが有効になりました!ナビゲーションメニューをご確認ください。'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '前提条件を満たしていません',
|
||||
insufficientResources: '資源が不足しています',
|
||||
insufficientFleetStorage: '艦隊ストレージが不足しています',
|
||||
shieldDomeLimit: 'シールドドームの上限に達しました',
|
||||
missileSiloLimit: 'ミサイル格納庫の容量を超えています',
|
||||
insufficientMissiles: '惑星間ミサイルが不足しています',
|
||||
invalidMissileCount: 'ミサイル数が無効です',
|
||||
targetOutOfRange: 'ターゲットが射程外です',
|
||||
cannotAttackOwnPlanet: '自分の惑星を攻撃できません',
|
||||
fleetMissionsFull: '艦隊ミッションスロットが満杯です',
|
||||
insufficientFleet: '艦隊が不足しています',
|
||||
insufficientFuel: '燃料が不足しています',
|
||||
@@ -166,8 +172,8 @@ export default {
|
||||
darkMatterCollector: '希少なダークマター資源を収集',
|
||||
darkMatterTank: 'ダークマターの貯蔵上限を増加',
|
||||
missileSilo: 'ミサイルを保管・発射、レベル毎に10発',
|
||||
terraformer: '惑星地形を改造、レベル毎に利用可能スペース5増加',
|
||||
lunarBase: '月の利用可能スペースを増加、レベル毎に+5スペース',
|
||||
terraformer: '惑星地形を改造、レベル毎に利用可能スペース30増加',
|
||||
lunarBase: '月の利用可能スペースを増加、レベル毎に+30スペース',
|
||||
sensorPhalanx: '周辺星系の艦隊活動を探知',
|
||||
jumpGate: '他の月へ艦隊を瞬間移動',
|
||||
planetDestroyerFactory: '惑星を破壊できる究極兵器を建造'
|
||||
@@ -283,7 +289,7 @@ export default {
|
||||
impulseDrive: '中級推進技術',
|
||||
hyperspaceDrive: '高級推進技術',
|
||||
darkMatterTechnology: 'ダークマターの性質と応用を研究',
|
||||
terraformingTechnology: '惑星地形改造技術を研究、レベル毎に全惑星の利用可能スペース3増加',
|
||||
terraformingTechnology: '惑星地形改造技術を研究、レベル毎に全惑星の利用可能スペース30増加',
|
||||
planetDestructionTech: '惑星全体を破壊する恐怖の技術を研究'
|
||||
},
|
||||
officers: {
|
||||
@@ -316,10 +322,12 @@ export default {
|
||||
cancelResearch: '研究キャンセル',
|
||||
confirmCancel: 'キャンセルしますか?資源の50%が返還されます。',
|
||||
level: 'レベル',
|
||||
gmModeActivated: '',
|
||||
upgradeToLevel: 'レベルにアップグレード'
|
||||
},
|
||||
shipyard: {
|
||||
attack: '攻撃力',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
speed: '速度',
|
||||
@@ -351,6 +359,7 @@ export default {
|
||||
usedSpace: '使用済みスペース',
|
||||
spaceUsage: 'スペース使用量',
|
||||
level: 'レベル',
|
||||
gmModeActivated: '',
|
||||
upgradeCost: 'アップグレードコスト',
|
||||
buildTime: '建設時間',
|
||||
upgrade: 'アップグレード',
|
||||
@@ -362,8 +371,8 @@ export default {
|
||||
demolishRefund: '解体返還',
|
||||
demolishFailed: '解体失敗',
|
||||
demolishFailedMessage: 'この建物を解体できません。建設キューが満杯か、建物レベルが0でないか確認してください。',
|
||||
confirmDemolish: '',
|
||||
confirmDemolishMessage: ''
|
||||
confirmDemolish: '解体確認',
|
||||
confirmDemolishMessage: '以下の建物を解体しますか?'
|
||||
},
|
||||
researchView: {
|
||||
title: '研究',
|
||||
@@ -375,6 +384,7 @@ export default {
|
||||
},
|
||||
defense: {
|
||||
attack: '攻撃力',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
buildCost: '建設コスト',
|
||||
@@ -389,6 +399,7 @@ export default {
|
||||
title: '造船所',
|
||||
fleetStorage: '艦隊ストレージ',
|
||||
attack: '攻撃力',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
shield: 'シールド',
|
||||
speed: '速度',
|
||||
cargoCapacity: '積載量',
|
||||
@@ -404,6 +415,7 @@ export default {
|
||||
defenseView: {
|
||||
title: '防衛施設',
|
||||
attack: '攻撃力',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
buildTime: '建設時間',
|
||||
@@ -413,6 +425,7 @@ export default {
|
||||
totalCost: '総コスト',
|
||||
build: '建造',
|
||||
shieldDomeBuilt: 'シールドドーム建設済み',
|
||||
missileCapacity: 'ミサイル容量',
|
||||
inputError: '入力エラー',
|
||||
inputErrorMessage: '建造数を入力してください!',
|
||||
buildFailed: '建造失敗',
|
||||
@@ -425,6 +438,7 @@ export default {
|
||||
flightMissions: '飛行ミッション',
|
||||
currentPlanetFleet: '現在の惑星艦隊',
|
||||
attack: '攻撃',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
shield: 'シールド',
|
||||
armor: '装甲',
|
||||
speed: '速度',
|
||||
@@ -519,25 +533,36 @@ export default {
|
||||
selectSystem: '星系を選択',
|
||||
view: '表示',
|
||||
myPlanet: '自分の惑星',
|
||||
myPlanets: '私の惑星',
|
||||
myPlanets: '自分の星系を表示',
|
||||
npcPlanets: 'NPCの惑星',
|
||||
selectPlanetToView: '表示する惑星を選択',
|
||||
selectPlanetToView: '惑星を選択して星系を表示',
|
||||
totalPositions: '全10惑星位置',
|
||||
mine: '自分',
|
||||
hostile: '敵対',
|
||||
emptySlot: '空き - 植民可能',
|
||||
scout: '偵察',
|
||||
attack: '攻撃',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
colonize: '植民',
|
||||
switch: '切り替え',
|
||||
recycle: '回収',
|
||||
debrisField: 'デブリフィールド',
|
||||
scoutPlanetTitle: '惑星偵察',
|
||||
attackPlanetTitle: '惑星攻撃',
|
||||
missileAttackTitle: 'ミサイル攻撃',
|
||||
colonizePlanetTitle: '惑星植民',
|
||||
recyclePlanetTitle: 'デブリ回収',
|
||||
scoutPlanetMessage: '惑星[{coordinates}]にスパイプローブを送りますか?\n\n艦隊ページに移動して艦船を選択して派遣してください。',
|
||||
attackPlanetMessage: '惑星[{coordinates}]を攻撃しますか?\n\n艦隊ページに移動して艦船を選択して派遣してください。',
|
||||
missileAttackMessage: '惑星[{coordinates}]に惑星間ミサイルを発射',
|
||||
missileCount: 'ミサイル数',
|
||||
availableMissiles: '利用可能なミサイル',
|
||||
missileRange: 'ミサイル射程',
|
||||
systems: 'システム',
|
||||
distance: '距離',
|
||||
flightTime: '飛行時間',
|
||||
launchMissile: '発射',
|
||||
cancel: 'キャンセル',
|
||||
colonizePlanetMessage: '位置[{coordinates}]を植民しますか?\n\n艦隊ページに移動してコロニーシップを派遣してください。',
|
||||
recyclePlanetMessage: '位置[{coordinates}]のデブリを回収しますか?\n\n艦隊ページに移動してリサイクラーを派遣してください。',
|
||||
sendGift: 'ギフト送信',
|
||||
@@ -690,10 +715,24 @@ export default {
|
||||
gamePaused: 'ゲームを一時停止しました',
|
||||
gameResumed: 'ゲームを再開しました',
|
||||
playerName: 'プレイヤー名',
|
||||
gameSpeed: 'ゲーム速度',
|
||||
gameSpeedDesc: '現在のゲーム速度倍率',
|
||||
gameSpeed: '資源生産速度',
|
||||
gameSpeedDesc: '現在の資源生産速度倍率',
|
||||
speedChanged: '資源生産速度を{speed}xに変更しました',
|
||||
speedReset: '資源生産速度を1xにリセットしました',
|
||||
reset: 'リセット',
|
||||
about: 'について',
|
||||
version: 'バージョン',
|
||||
latestVersion: '最新バージョン',
|
||||
checkUpdate: 'アップデート確認',
|
||||
checking: '確認中...',
|
||||
newVersionAvailable: '新バージョン{version}が利用可能です',
|
||||
upToDate: '最新バージョンです',
|
||||
checkUpdateCooldown: 'しばらくしてから再度お試しください(5分間のクールダウン)',
|
||||
checkUpdateFailed: 'アップデートの確認に失敗しました。ネットワーク接続を確認してください',
|
||||
viewUpdate: '更新を表示',
|
||||
updateAvailable: '新しいバージョンが利用可能です。クリックしてリリースノートを表示します。',
|
||||
download: 'ダウンロード',
|
||||
goToDownload: 'ダウンロードへ',
|
||||
buildDate: 'ビルド日',
|
||||
community: 'コミュニティ',
|
||||
github: 'GitHubリポジトリ',
|
||||
@@ -732,12 +771,22 @@ export default {
|
||||
testSpy: '偵察テスト',
|
||||
testAttack: '攻撃テスト',
|
||||
testSpyAndAttack: '偵察&攻撃テスト',
|
||||
testSpyMessage: '確認をクリックして偵察ミッションを加速',
|
||||
testAttackMessage: '確認をクリックして攻撃ミッションを加速',
|
||||
testSpyAndAttackMessage: '確認をクリックしてミッションを加速',
|
||||
initializeFleet: 'NPC艦隊を初期化',
|
||||
accelerateMissions: 'すべてのミッションを加速(5秒)',
|
||||
selectNPCFirst: '最初にNPCを選択してください',
|
||||
npcNoProbes: 'NPCには偵察プローブがありません',
|
||||
npcNoSpyReport: 'NPCは最初に偵察する必要があります',
|
||||
npcMissionFailed: 'ミッションの作成に失敗しました',
|
||||
npcNoPlanets: 'NPCに惑星がありません',
|
||||
npcWillSpyIn5s: '{npcName}は5秒後に偵察します',
|
||||
npcWillAttackIn5s: '{npcName}は5秒後に攻撃します',
|
||||
npcWillSpyAndAttack: '{npcName}は5秒後に偵察し、10秒後に攻撃します',
|
||||
acceleratedMissions: '{count}個のミッションを5秒後に加速しました',
|
||||
npcFleetInitialized: '{npcName}艦隊が初期化されました',
|
||||
npcFleetDetails: '100 偵察プローブ\n500 軽戦闘機\n300 重戦闘機\n200 巡洋艦\n100 戦艦\n50 爆撃機\n30 駆逐艦\n20 巡洋戦艦',
|
||||
dangerZone: '危険ゾーン',
|
||||
dangerZoneDesc: '以下の操作は元に戻せません',
|
||||
resetGame: 'ゲームをリセット',
|
||||
@@ -789,9 +838,32 @@ export default {
|
||||
events: {
|
||||
gift: 'ギフト送信',
|
||||
attack: '攻撃',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
allyAttacked: '同盟が攻撃された',
|
||||
spy: '諜報活動',
|
||||
stealDebris: '残骸を略奪'
|
||||
},
|
||||
reports: {
|
||||
giftedResources: '{metal}M {crystal}C {deuterium}Dを贈呈',
|
||||
receivedGiftFromPlayer: 'プレイヤーからギフトを受け取りました',
|
||||
giftedToNpc: '{npcName}にリソースを贈呈しました。評判+{reputation}',
|
||||
rejectedPlayerGift: 'プレイヤーのギフトを拒否しました',
|
||||
npcRejectedGift: '{npcName}があなたのギフトを拒否しました。評判{reputation}',
|
||||
attackedNpc: '{npcName}を攻撃しました',
|
||||
wasAttackedByPlayer: 'プレイヤーに攻撃されました',
|
||||
youAttackedNpc: 'あなたは{npcName}を攻撃しました',
|
||||
playerAttackedAlly: 'プレイヤーが同盟{allyName}を攻撃しました',
|
||||
allyDispleased: '{allyName}はあなたが同盟{targetName}を攻撃したことに不満です',
|
||||
wasSpiedByPlayer: 'プレイヤーに偵察されました(発見:{detected})',
|
||||
spyDetected: 'あなたの偵察が{npcName}に発見されました',
|
||||
stoleDebrisFromTerritory: '{npcName}の領域から残骸を略奪しました',
|
||||
playerStoleDebris: 'プレイヤーが領域から残骸を略奪しました',
|
||||
recycledDebrisNearNpc: '{npcName}の惑星近くで残骸を回収しました。彼らは不満です。',
|
||||
giftedResourcesToPlayer: 'プレイヤーにリソースを贈呈しました',
|
||||
receivedGiftFromNpc: '{npcName}からギフトを受け取りました',
|
||||
acceptedGiftFromNpc: '{npcName}からのギフトを受け取りました:{metal}M {crystal}C {deuterium}D',
|
||||
playerRejectedGift: 'プレイヤーがギフトを拒否しました',
|
||||
rejectedGiftFromNpc: '{npcName}からのギフトを拒否しました。評判{reputation}'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -800,5 +872,10 @@ export default {
|
||||
first: '最初',
|
||||
last: '最後',
|
||||
page: '{page}ページ'
|
||||
},
|
||||
notFound: {
|
||||
title: 'ページが見つかりません',
|
||||
description: '申し訳ございません。お探しのページは存在しません',
|
||||
goHome: 'ホームに戻る'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,19 @@ export default {
|
||||
viewRequirements: '요구사항 보기',
|
||||
requirementsNotMet: '요구사항 미충족',
|
||||
current: '현재',
|
||||
level: '레벨'
|
||||
level: '레벨',
|
||||
gmModeActivated: 'GM 모드가 활성화되었습니다! 탐색 메뉴를 확인하세요.'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '전제 조건 미충족',
|
||||
insufficientResources: '자원 부족',
|
||||
insufficientFleetStorage: '함대 저장소 부족',
|
||||
shieldDomeLimit: '실드 돔 한도 도달',
|
||||
missileSiloLimit: '미사일 사일로 용량 초과',
|
||||
insufficientMissiles: '행성간 미사일 부족',
|
||||
invalidMissileCount: '잘못된 미사일 수량',
|
||||
targetOutOfRange: '목표가 사정거리 밖',
|
||||
cannotAttackOwnPlanet: '자신의 행성 공격 불가',
|
||||
fleetMissionsFull: '함대 임무 슬롯 가득 참',
|
||||
insufficientFleet: '함대 부족',
|
||||
insufficientFuel: '연료 부족',
|
||||
@@ -166,8 +172,8 @@ export default {
|
||||
darkMatterCollector: '희귀한 암흑 물질 자원 수집',
|
||||
darkMatterTank: '암흑 물질 저장 용량 증가',
|
||||
missileSilo: '미사일을 저장 및 발사, 레벨당 10발',
|
||||
terraformer: '행성 지형 개조, 레벨당 가용 공간 5 증가',
|
||||
lunarBase: '달 가용 공간 증가, 레벨당 +5 공간',
|
||||
terraformer: '행성 지형 개조, 레벨당 가용 공간 30 증가',
|
||||
lunarBase: '달 가용 공간 증가, 레벨당 +30 공간',
|
||||
sensorPhalanx: '주변 행성계의 함대 활동 감지',
|
||||
jumpGate: '다른 위성으로 함대 순간 이동',
|
||||
planetDestroyerFactory: '행성을 파괴할 수 있는 궁극 병기 건조'
|
||||
@@ -283,7 +289,7 @@ export default {
|
||||
impulseDrive: '중급 추진 기술',
|
||||
hyperspaceDrive: '고급 추진 기술',
|
||||
darkMatterTechnology: '암흑 물질의 성질과 응용 연구',
|
||||
terraformingTechnology: '행성 지형 개조 기술 연구, 레벨당 모든 행성의 가용 공간 3 증가',
|
||||
terraformingTechnology: '행성 지형 개조 기술 연구, 레벨당 모든 행성의 가용 공간 30 증가',
|
||||
planetDestructionTech: '행성 전체를 파괴하는 공포의 기술 연구'
|
||||
},
|
||||
officers: {
|
||||
@@ -316,6 +322,7 @@ export default {
|
||||
cancelResearch: '연구 취소',
|
||||
confirmCancel: '취소하시겠습니까? 자원의 50%가 환불됩니다.',
|
||||
level: '레벨',
|
||||
gmModeActivated: '',
|
||||
upgradeToLevel: '레벨로 업그레이드'
|
||||
},
|
||||
overview: {
|
||||
@@ -336,6 +343,7 @@ export default {
|
||||
usedSpace: '사용된 공간',
|
||||
spaceUsage: '공간 사용',
|
||||
level: '레벨',
|
||||
gmModeActivated: '',
|
||||
upgradeCost: '업그레이드 비용',
|
||||
buildTime: '건설 시간',
|
||||
upgrade: '업그레이드',
|
||||
@@ -347,8 +355,8 @@ export default {
|
||||
demolishRefund: '철거 환불',
|
||||
demolishFailed: '철거 실패',
|
||||
demolishFailedMessage: '이 건물을 철거할 수 없습니다. 건설 대기열이 가득 찼거나 건물 레벨이 0인지 확인하세요.',
|
||||
confirmDemolish: '',
|
||||
confirmDemolishMessage: ''
|
||||
confirmDemolish: '철거 확인',
|
||||
confirmDemolishMessage: '다음 건물을 철거하시겠습니까?'
|
||||
},
|
||||
researchView: {
|
||||
title: '연구',
|
||||
@@ -360,6 +368,7 @@ export default {
|
||||
},
|
||||
shipyard: {
|
||||
attack: '공격력',
|
||||
missileAttack: '미사일 공격',
|
||||
shield: '쉴드',
|
||||
armor: '장갑',
|
||||
speed: '속도',
|
||||
@@ -377,6 +386,7 @@ export default {
|
||||
title: '조선소',
|
||||
fleetStorage: '함대 저장소',
|
||||
attack: '공격력',
|
||||
missileAttack: '미사일 공격',
|
||||
shield: '실드',
|
||||
speed: '속도',
|
||||
cargoCapacity: '적재량',
|
||||
@@ -391,6 +401,7 @@ export default {
|
||||
},
|
||||
defense: {
|
||||
attack: '공격력',
|
||||
missileAttack: '미사일 공격',
|
||||
shield: '쉴드',
|
||||
armor: '장갑',
|
||||
buildCost: '건설 비용',
|
||||
@@ -404,6 +415,7 @@ export default {
|
||||
defenseView: {
|
||||
title: '방어 시설',
|
||||
attack: '공격력',
|
||||
missileAttack: '미사일 공격',
|
||||
shield: '실드',
|
||||
armor: '장갑',
|
||||
buildTime: '건설 시간',
|
||||
@@ -413,6 +425,7 @@ export default {
|
||||
totalCost: '총 비용',
|
||||
build: '건조',
|
||||
shieldDomeBuilt: '실드 돔이 이미 건설됨',
|
||||
missileCapacity: '미사일 용량',
|
||||
inputError: '입력 오류',
|
||||
inputErrorMessage: '건조 수량을 입력하세요!',
|
||||
buildFailed: '건조 실패',
|
||||
@@ -425,6 +438,7 @@ export default {
|
||||
flightMissions: '비행 임무',
|
||||
currentPlanetFleet: '현재 행성 함대',
|
||||
attack: '공격',
|
||||
missileAttack: '미사일 공격',
|
||||
shield: '실드',
|
||||
armor: '장갑',
|
||||
speed: '속도',
|
||||
@@ -519,26 +533,37 @@ export default {
|
||||
selectSystem: '행성계 선택',
|
||||
view: '보기',
|
||||
myPlanet: '내 행성',
|
||||
myPlanets: '내 행성들',
|
||||
myPlanets: '내 행성계 보기',
|
||||
npcPlanets: 'NPC 행성들',
|
||||
selectPlanetToView: '볼 행성 선택',
|
||||
selectPlanetToView: '행성을 선택하여 행성계 보기',
|
||||
totalPositions: '총 10개 행성 위치',
|
||||
mine: '내 것',
|
||||
hostile: '적대',
|
||||
emptySlot: '빈 자리 - 식민 가능',
|
||||
scout: '정찰',
|
||||
attack: '공격',
|
||||
missileAttack: '미사일 공격',
|
||||
colonize: '식민',
|
||||
switch: '전환',
|
||||
recycle: '회수',
|
||||
debrisField: '잔해 필드',
|
||||
scoutPlanetTitle: '행성 정찰',
|
||||
attackPlanetTitle: '행성 공격',
|
||||
missileAttackTitle: '미사일 공격',
|
||||
colonizePlanetTitle: '행성 식민',
|
||||
recyclePlanetTitle: '잔해 회수',
|
||||
scoutPlanetMessage:
|
||||
'행성 [{coordinates}]을(를) 정찰하기 위해 정찰기를 보내시겠습니까?\n\n함대 페이지로 이동하여 함선을 선택하고 파견하세요.',
|
||||
attackPlanetMessage: '행성 [{coordinates}]을(를) 공격하시겠습니까?\n\n함대 페이지로 이동하여 함선을 선택하고 파견하세요.',
|
||||
missileAttackMessage: '행성 [{coordinates}]에 행성간 미사일 발사',
|
||||
missileCount: '미사일 수량',
|
||||
availableMissiles: '사용 가능한 미사일',
|
||||
missileRange: '미사일 사정거리',
|
||||
systems: '시스템',
|
||||
distance: '거리',
|
||||
flightTime: '비행 시간',
|
||||
launchMissile: '발사',
|
||||
cancel: '취소',
|
||||
colonizePlanetMessage: '위치 [{coordinates}]을(를) 식민하시겠습니까?\n\n함대 페이지로 이동하여 식민선을 파견하세요.',
|
||||
recyclePlanetMessage: '위치 [{coordinates}]의 잔해를 회수하시겠습니까?\n\n함대 페이지로 이동하여 회수선을 파견하세요.',
|
||||
sendGift: '선물 보내기',
|
||||
@@ -691,10 +716,24 @@ export default {
|
||||
gamePaused: '게임이 일시정지되었습니다',
|
||||
gameResumed: '게임이 재개되었습니다',
|
||||
playerName: '플레이어 이름',
|
||||
gameSpeed: '게임 속도',
|
||||
gameSpeedDesc: '현재 게임 속도 배율',
|
||||
gameSpeed: '자원 생산 속도',
|
||||
gameSpeedDesc: '현재 자원 생산 속도 배율',
|
||||
speedChanged: '자원 생산 속도가 {speed}x로 변경되었습니다',
|
||||
speedReset: '자원 생산 속도가 1x로 재설정되었습니다',
|
||||
reset: '재설정',
|
||||
about: '정보',
|
||||
version: '버전',
|
||||
latestVersion: '최신 버전',
|
||||
checkUpdate: '업데이트 확인',
|
||||
checking: '확인 중...',
|
||||
newVersionAvailable: '새 버전 {version} 사용 가능',
|
||||
upToDate: '이미 최신 버전입니다',
|
||||
checkUpdateCooldown: '나중에 다시 시도해주세요 (5분 쿨다운)',
|
||||
checkUpdateFailed: '업데이트 확인 실패, 네트워크 연결을 확인하세요',
|
||||
viewUpdate: '업데이트 보기',
|
||||
updateAvailable: '새 버전이 사용 가능합니다. 릴리스 노트를 보려면 클릭하세요.',
|
||||
download: '다운로드',
|
||||
goToDownload: '다운로드로 이동',
|
||||
buildDate: '빌드 날짜',
|
||||
community: '커뮤니티',
|
||||
github: 'GitHub 저장소',
|
||||
@@ -733,12 +772,22 @@ export default {
|
||||
testSpy: '정찰 테스트',
|
||||
testAttack: '공격 테스트',
|
||||
testSpyAndAttack: '정찰 & 공격 테스트',
|
||||
initializeFleet: 'NPC 함대 초기화',
|
||||
testSpyMessage: '확인을 클릭하여 정찰 임무를 가속화',
|
||||
testAttackMessage: '확인을 클릭하여 공격 임무를 가속화',
|
||||
testSpyAndAttackMessage: '확인을 클릭하여 임무를 가속화',
|
||||
initializeFleet: 'NPC 함대 초기化',
|
||||
accelerateMissions: '모든 임무 가속(5초)',
|
||||
selectNPCFirst: '먼저 NPC를 선택하세요',
|
||||
npcNoProbes: 'NPC에 정찰 프로브가 없습니다',
|
||||
npcNoSpyReport: 'NPC가 먼저 정찰해야 합니다',
|
||||
npcMissionFailed: '임무 생성 실패',
|
||||
npcNoPlanets: 'NPC에 행성이 없습니다',
|
||||
npcWillSpyIn5s: '{npcName}이(가) 5초 후에 정찰합니다',
|
||||
npcWillAttackIn5s: '{npcName}이(가) 5초 후에 공격합니다',
|
||||
npcWillSpyAndAttack: '{npcName}이(가) 5초 후에 정찰하고 10초 후에 공격합니다',
|
||||
acceleratedMissions: '{count}개의 임무를 5초로 가속화했습니다',
|
||||
npcFleetInitialized: '{npcName} 함대가 초기화되었습니다',
|
||||
npcFleetDetails: '100 정찰 프로브\n500 경전투기\n300 중전투기\n200 순양함\n100 전함\n50 폭격기\n30 구축함\n20 순양전함',
|
||||
dangerZone: '위험 구역',
|
||||
dangerZoneDesc: '다음 작업은 되돌릴 수 없습니다',
|
||||
resetGame: '게임 초기화',
|
||||
@@ -790,9 +839,32 @@ export default {
|
||||
events: {
|
||||
gift: '선물 전송',
|
||||
attack: '공격',
|
||||
missileAttack: '미사일 공격',
|
||||
allyAttacked: '동맹 공격당함',
|
||||
spy: '정찰',
|
||||
stealDebris: '잔해 약탈'
|
||||
},
|
||||
reports: {
|
||||
giftedResources: '{metal}M {crystal}C {deuterium}D 선물함',
|
||||
receivedGiftFromPlayer: '플레이어로부터 선물을 받았습니다',
|
||||
giftedToNpc: '{npcName}에게 자원을 선물했습니다. 평판 +{reputation}',
|
||||
rejectedPlayerGift: '플레이어의 선물을 거부했습니다',
|
||||
npcRejectedGift: '{npcName}이(가) 당신의 선물을 거부했습니다. 평판 {reputation}',
|
||||
attackedNpc: '{npcName}을(를) 공격했습니다',
|
||||
wasAttackedByPlayer: '플레이어에게 공격당했습니다',
|
||||
youAttackedNpc: '당신은 {npcName}을(를) 공격했습니다',
|
||||
playerAttackedAlly: '플레이어가 동맹 {allyName}을(를) 공격했습니다',
|
||||
allyDispleased: '{allyName}은(는) 당신이 동맹 {targetName}을(를) 공격한 것에 불만입니다',
|
||||
wasSpiedByPlayer: '플레이어에게 정찰당했습니다 (발견: {detected})',
|
||||
spyDetected: '당신의 정찰이 {npcName}에게 발견되었습니다',
|
||||
stoleDebrisFromTerritory: '{npcName}의 영역에서 잔해를 약탈했습니다',
|
||||
playerStoleDebris: '플레이어가 영역에서 잔해를 약탈했습니다',
|
||||
recycledDebrisNearNpc: '{npcName}의 행성 근처에서 잔해를 수집했습니다. 그들은 불만족스러워합니다.',
|
||||
giftedResourcesToPlayer: '플레이어에게 자원을 선물했습니다',
|
||||
receivedGiftFromNpc: '{npcName}로부터 선물을 받았습니다',
|
||||
acceptedGiftFromNpc: '{npcName}의 선물을 받았습니다: {metal}M {crystal}C {deuterium}D',
|
||||
playerRejectedGift: '플레이어가 선물을 거부했습니다',
|
||||
rejectedGiftFromNpc: '{npcName}의 선물을 거부했습니다. 평판 {reputation}'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -801,5 +873,10 @@ export default {
|
||||
first: '처음',
|
||||
last: '마지막',
|
||||
page: '{page}페이지'
|
||||
},
|
||||
notFound: {
|
||||
title: '페이지를 찾을 수 없습니다',
|
||||
description: '죄송합니다. 찾으시는 페이지가 존재하지 않습니다',
|
||||
goHome: '홈으로 이동'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,19 @@ export default {
|
||||
viewRequirements: 'Просмотр требований',
|
||||
requirementsNotMet: 'Требования не выполнены',
|
||||
current: 'Текущий',
|
||||
level: 'Уровень'
|
||||
level: 'Уровень',
|
||||
gmModeActivated: 'Режим GM активирован! Проверьте навигационное меню.'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: 'Требования не выполнены',
|
||||
insufficientResources: 'Недостаточно ресурсов',
|
||||
insufficientFleetStorage: 'Недостаточно места для флота',
|
||||
shieldDomeLimit: 'Достигнут лимит щитовых куполов',
|
||||
missileSiloLimit: 'Превышена вместимость ракетной шахты',
|
||||
insufficientMissiles: 'Недостаточно межпланетных ракет',
|
||||
invalidMissileCount: 'Неверное количество ракет',
|
||||
targetOutOfRange: 'Цель вне дальности',
|
||||
cannotAttackOwnPlanet: 'Нельзя атаковать свою планету',
|
||||
fleetMissionsFull: 'Слоты миссий флота заполнены',
|
||||
insufficientFleet: 'Недостаточно флота',
|
||||
insufficientFuel: 'Недостаточно топлива',
|
||||
@@ -166,8 +172,8 @@ export default {
|
||||
darkMatterCollector: 'Собирает редкие ресурсы тёмной материи',
|
||||
darkMatterTank: 'Увеличивает ёмкость хранилища тёмной материи',
|
||||
missileSilo: 'Хранит и запускает ракеты, 10 ракет на уровень',
|
||||
terraformer: 'Терраформирует поверхность планеты, увеличивает доступное пространство на 5 за уровень',
|
||||
lunarBase: 'Увеличивает доступное пространство на луне, +5 пространства за уровень',
|
||||
terraformer: 'Терраформирует поверхность планеты, увеличивает доступное пространство на 30 за уровень',
|
||||
lunarBase: 'Увеличивает доступное пространство на луне, +30 пространства за уровень',
|
||||
sensorPhalanx: 'Обнаруживает активность флота в окружающих системах',
|
||||
jumpGate: 'Мгновенно переносит флоты на другие луны',
|
||||
planetDestroyerFactory: 'Производит абсолютное оружие, способное уничтожать планеты'
|
||||
@@ -284,7 +290,7 @@ export default {
|
||||
hyperspaceDrive: 'Продвинутая технология двигателей',
|
||||
darkMatterTechnology: 'Исследование свойств и применения тёмной материи',
|
||||
terraformingTechnology:
|
||||
'Исследование технологии терраформирования планет, увеличивает доступное пространство всех планет на 3 за уровень',
|
||||
'Исследование технологии терраформирования планет, увеличивает доступное пространство всех планет на 30 за уровень',
|
||||
planetDestructionTech: 'Исследование ужасающей технологии уничтожения целых планет'
|
||||
},
|
||||
officers: {
|
||||
@@ -317,6 +323,7 @@ export default {
|
||||
cancelResearch: 'Отменить исследование',
|
||||
confirmCancel: 'Вы уверены, что хотите отменить? 50% ресурсов будет возвращено.',
|
||||
level: 'Уровень',
|
||||
gmModeActivated: '',
|
||||
upgradeToLevel: 'Улучшить до уровня'
|
||||
},
|
||||
overview: {
|
||||
@@ -337,6 +344,7 @@ export default {
|
||||
usedSpace: 'Использовано полей',
|
||||
spaceUsage: 'Использование полей',
|
||||
level: 'Уровень',
|
||||
gmModeActivated: '',
|
||||
upgradeCost: 'Стоимость улучшения',
|
||||
buildTime: 'Время строительства',
|
||||
upgrade: 'Улучшить',
|
||||
@@ -348,8 +356,8 @@ export default {
|
||||
demolishRefund: 'Возврат от сноса',
|
||||
demolishFailed: 'Снос не удался',
|
||||
demolishFailedMessage: 'Невозможно снести это здание. Проверьте, не заполнена ли очередь строительства или уровень здания не равен 0.',
|
||||
confirmDemolish: '',
|
||||
confirmDemolishMessage: ''
|
||||
confirmDemolish: 'Подтвердить снос',
|
||||
confirmDemolishMessage: 'Вы уверены, что хотите снести следующее здание?'
|
||||
},
|
||||
researchView: {
|
||||
title: 'Исследования',
|
||||
@@ -362,6 +370,7 @@ export default {
|
||||
},
|
||||
shipyard: {
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
speed: 'Скорость',
|
||||
@@ -379,6 +388,7 @@ export default {
|
||||
title: 'Верфь',
|
||||
fleetStorage: 'Хранилище флота',
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
shield: 'Щит',
|
||||
speed: 'Скорость',
|
||||
cargoCapacity: 'Грузоподъёмность',
|
||||
@@ -393,6 +403,7 @@ export default {
|
||||
},
|
||||
defense: {
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
buildCost: 'Стоимость постройки',
|
||||
@@ -406,6 +417,7 @@ export default {
|
||||
defenseView: {
|
||||
title: 'Оборона',
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
buildTime: 'Время постройки',
|
||||
@@ -415,6 +427,7 @@ export default {
|
||||
totalCost: 'Общая стоимость',
|
||||
build: 'Построить',
|
||||
shieldDomeBuilt: 'Щитовой купол уже построен',
|
||||
missileCapacity: 'Вместимость ракет',
|
||||
inputError: 'Ошибка ввода',
|
||||
inputErrorMessage: 'Пожалуйста, введите количество для постройки!',
|
||||
buildFailed: 'Постройка не удалась',
|
||||
@@ -428,6 +441,7 @@ export default {
|
||||
flightMissions: 'Полетные миссии',
|
||||
currentPlanetFleet: 'Флот на этой планете',
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
shield: 'Щит',
|
||||
armor: 'Броня',
|
||||
speed: 'Скорость',
|
||||
@@ -522,27 +536,38 @@ export default {
|
||||
selectSystem: 'Выбрать систему',
|
||||
view: 'Показать',
|
||||
myPlanet: 'Моя планета',
|
||||
myPlanets: 'Мои планеты',
|
||||
myPlanets: 'Просмотр моих систем',
|
||||
npcPlanets: 'Планеты NPC',
|
||||
selectPlanetToView: 'Выберите планету для просмотра',
|
||||
selectPlanetToView: 'Выберите планету для просмотра её системы',
|
||||
totalPositions: 'Всего 10 позиций планет',
|
||||
mine: 'Моя',
|
||||
hostile: 'Враждебная',
|
||||
emptySlot: 'Пусто - можно колонизировать',
|
||||
scout: 'Разведка',
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
colonize: 'Колонизация',
|
||||
switch: 'Переключить',
|
||||
recycle: 'Переработка',
|
||||
debrisField: 'Поле обломков',
|
||||
scoutPlanetTitle: 'Разведать планету',
|
||||
attackPlanetTitle: 'Атаковать планету',
|
||||
missileAttackTitle: 'Ракетная атака',
|
||||
colonizePlanetTitle: 'Колонизировать планету',
|
||||
recyclePlanetTitle: 'Переработать обломки',
|
||||
scoutPlanetMessage:
|
||||
'Вы уверены, что хотите отправить шпионские зонды для разведки планеты [{coordinates}]?\n\nПерейдите на страницу флота, чтобы выбрать корабли и отправить.',
|
||||
attackPlanetMessage:
|
||||
'Вы уверены, что хотите атаковать планету [{coordinates}]?\n\nПерейдите на страницу флота, чтобы выбрать корабли и отправить.',
|
||||
missileAttackMessage: 'Запустить межпланетные ракеты по планете [{coordinates}]',
|
||||
missileCount: 'Количество ракет',
|
||||
availableMissiles: 'Доступно ракет',
|
||||
missileRange: 'Дальность ракет',
|
||||
systems: 'систем',
|
||||
distance: 'Расстояние',
|
||||
flightTime: 'Время полета',
|
||||
launchMissile: 'Запустить',
|
||||
cancel: 'Отмена',
|
||||
colonizePlanetMessage:
|
||||
'Вы уверены, что хотите колонизировать позицию [{coordinates}]?\n\nПерейдите на страницу флота, чтобы отправить колонизационный корабль.',
|
||||
recyclePlanetMessage:
|
||||
@@ -698,10 +723,24 @@ export default {
|
||||
gamePaused: 'Игра приостановлена',
|
||||
gameResumed: 'Игра возобновлена',
|
||||
playerName: 'Имя игрока',
|
||||
gameSpeed: 'Скорость игры',
|
||||
gameSpeedDesc: 'Текущий множитель скорости игры',
|
||||
gameSpeed: 'Скорость производства ресурсов',
|
||||
gameSpeedDesc: 'Текущий множитель скорости производства ресурсов',
|
||||
speedChanged: 'Скорость производства ресурсов изменена на {speed}x',
|
||||
speedReset: 'Скорость производства ресурсов сброшена на 1x',
|
||||
reset: 'Сбросить',
|
||||
about: 'О программе',
|
||||
version: 'Версия',
|
||||
latestVersion: 'Последняя версия',
|
||||
checkUpdate: 'Проверить обновление',
|
||||
checking: 'Проверка...',
|
||||
newVersionAvailable: 'Доступна новая версия {version}',
|
||||
upToDate: 'Уже актуальная версия',
|
||||
checkUpdateCooldown: 'Пожалуйста, попробуйте позже (5 минут перезарядки)',
|
||||
checkUpdateFailed: 'Не удалось проверить обновления, проверьте подключение к Интернету',
|
||||
viewUpdate: 'Просмотреть обновление',
|
||||
updateAvailable: 'Доступна новая версия. Нажмите, чтобы просмотреть примечания к выпуску.',
|
||||
download: 'Скачать',
|
||||
goToDownload: 'Перейти к загрузке',
|
||||
buildDate: 'Дата сборки',
|
||||
community: 'Сообщество',
|
||||
github: 'Репозиторий GitHub',
|
||||
@@ -740,12 +779,22 @@ export default {
|
||||
testSpy: 'Тест разведки',
|
||||
testAttack: 'Тест атаки',
|
||||
testSpyAndAttack: 'Тест разведки и атаки',
|
||||
testSpyMessage: 'Нажмите подтвердить, чтобы ускорить миссию разведки',
|
||||
testAttackMessage: 'Нажмите подтвердить, чтобы ускорить миссию атаки',
|
||||
testSpyAndAttackMessage: 'Нажмите подтвердить, чтобы ускорить миссии',
|
||||
initializeFleet: 'Инициализировать флот NPC',
|
||||
accelerateMissions: 'Ускорить все миссии (5с)',
|
||||
selectNPCFirst: 'Сначала выберите NPC',
|
||||
npcNoProbes: 'У NPC нет шпионских зондов',
|
||||
npcNoSpyReport: 'NPC нужно сначала разведать',
|
||||
npcMissionFailed: 'Не удалось создать миссию',
|
||||
npcNoPlanets: 'У NPC нет планет',
|
||||
npcWillSpyIn5s: '{npcName} проведет разведку через 5 секунд',
|
||||
npcWillAttackIn5s: '{npcName} атакует через 5 секунд',
|
||||
npcWillSpyAndAttack: '{npcName} проведет разведку через 5с и атакует через 10с',
|
||||
acceleratedMissions: 'Ускорено {count} миссий до 5 секунд',
|
||||
npcFleetInitialized: 'Флот {npcName} инициализирован',
|
||||
npcFleetDetails: '100 шпионских зондов\n500 легких истребителей\n300 тяжелых истребителей\n200 крейсеров\n100 линкоров\n50 бомбардировщиков\n30 эсминцев\n20 линейных крейсеров',
|
||||
dangerZone: 'Опасная зона',
|
||||
dangerZoneDesc: 'Следующие операции необратимы',
|
||||
resetGame: 'Сбросить игру',
|
||||
@@ -797,9 +846,32 @@ export default {
|
||||
events: {
|
||||
gift: 'Подарок отправлен',
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
allyAttacked: 'Союзник атакован',
|
||||
spy: 'Шпионаж',
|
||||
stealDebris: 'Обломки украдены'
|
||||
},
|
||||
reports: {
|
||||
giftedResources: 'Подарено {metal}M {crystal}C {deuterium}D',
|
||||
receivedGiftFromPlayer: 'Получен подарок от игрока',
|
||||
giftedToNpc: 'Вы подарили ресурсы {npcName}. Репутация +{reputation}',
|
||||
rejectedPlayerGift: 'Отклонен подарок игрока',
|
||||
npcRejectedGift: '{npcName} отклонил ваш подарок. Репутация {reputation}',
|
||||
attackedNpc: 'Атакован {npcName}',
|
||||
wasAttackedByPlayer: 'Был атакован игроком',
|
||||
youAttackedNpc: 'Вы атаковали {npcName}',
|
||||
playerAttackedAlly: 'Игрок атаковал союзника {allyName}',
|
||||
allyDispleased: '{allyName} недоволен тем, что вы атаковали их союзника {targetName}',
|
||||
wasSpiedByPlayer: 'Был разведан игроком (обнаружен: {detected})',
|
||||
spyDetected: 'Ваш шпионаж был обнаружен {npcName}',
|
||||
stoleDebrisFromTerritory: 'Украдены обломки с территории {npcName}',
|
||||
playerStoleDebris: 'Игрок украл обломки с территории',
|
||||
recycledDebrisNearNpc: 'Вы переработали обломки возле планеты {npcName}. Они недовольны.',
|
||||
giftedResourcesToPlayer: 'Подарены ресурсы игроку',
|
||||
receivedGiftFromNpc: 'Получен подарок от {npcName}',
|
||||
acceptedGiftFromNpc: 'Вы приняли подарок от {npcName}: {metal}M {crystal}C {deuterium}D',
|
||||
playerRejectedGift: 'Игрок отклонил подарок',
|
||||
rejectedGiftFromNpc: 'Вы отклонили подарок от {npcName}. Репутация {reputation}'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -808,5 +880,10 @@ export default {
|
||||
first: 'Первая',
|
||||
last: 'Последняя',
|
||||
page: 'Страница {page}'
|
||||
},
|
||||
notFound: {
|
||||
title: 'Страница не найдена',
|
||||
description: 'Извините, страница, которую вы ищете, не существует',
|
||||
goHome: 'На главную'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,20 @@ export default {
|
||||
viewRequirements: '查看前置条件',
|
||||
requirementsNotMet: '前置条件未满足',
|
||||
current: '当前',
|
||||
level: '等级'
|
||||
level: '等级',
|
||||
gmModeActivated: 'GM 模式已激活!请查看导航菜单。'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '不满足前置条件',
|
||||
insufficientResources: '资源不足',
|
||||
insufficientFleetStorage: '舰队仓储空间不足',
|
||||
shieldDomeLimit: '护盾罩数量限制',
|
||||
missileSiloLimit: '导弹发射井容量不足',
|
||||
insufficientMissiles: '星际导弹数量不足',
|
||||
invalidMissileCount: '导弹数量无效',
|
||||
targetOutOfRange: '目标超出射程',
|
||||
cannotAttackOwnPlanet: '不能攻击自己的星球',
|
||||
launchFailed: '发射失败',
|
||||
fleetMissionsFull: '舰队任务槽位已满',
|
||||
insufficientFleet: '舰队数量不足',
|
||||
insufficientFuel: '燃料不足',
|
||||
@@ -164,8 +171,8 @@ export default {
|
||||
darkMatterCollector: '收集稀有的暗物质资源',
|
||||
darkMatterTank: '增加暗物质存储上限',
|
||||
missileSilo: '存储和发射导弹,每级可存储10枚导弹',
|
||||
terraformer: '改造行星地形,每级增加5个可用空间',
|
||||
lunarBase: '增加月球可用空间,每级+5空间',
|
||||
terraformer: '改造行星地形,每级增加30个可用空间',
|
||||
lunarBase: '增加月球可用空间,每级+30空间',
|
||||
sensorPhalanx: '侦测周围星系的舰队活动',
|
||||
jumpGate: '瞬间传送舰队到其他月球',
|
||||
planetDestroyerFactory: '建造能够摧毁行星的终极武器'
|
||||
@@ -283,7 +290,7 @@ export default {
|
||||
impulseDrive: '中级推进技术',
|
||||
hyperspaceDrive: '高级推进技术',
|
||||
darkMatterTechnology: '研究暗物质的性质和应用',
|
||||
terraformingTechnology: '研究行星地形改造技术,每级为所有行星增加3个可用空间',
|
||||
terraformingTechnology: '研究行星地形改造技术,每级为所有行星增加30个可用空间',
|
||||
planetDestructionTech: '研究如何摧毁整个行星的恐怖技术'
|
||||
},
|
||||
officers: {
|
||||
@@ -415,6 +422,7 @@ export default {
|
||||
totalCost: '总成本',
|
||||
build: '建造',
|
||||
shieldDomeBuilt: '护盾罩已建造',
|
||||
missileCapacity: '导弹容量',
|
||||
inputError: '输入错误',
|
||||
inputErrorMessage: '请输入建造数量!',
|
||||
buildFailed: '建造失败',
|
||||
@@ -520,15 +528,16 @@ export default {
|
||||
selectSystem: '选择星系',
|
||||
view: '查看',
|
||||
myPlanet: '我的星球',
|
||||
myPlanets: '我的星球',
|
||||
myPlanets: '查看我的星系',
|
||||
npcPlanets: 'NPC星球',
|
||||
selectPlanetToView: '选择要查看的星球',
|
||||
selectPlanetToView: '选择星球以查看其所在星系',
|
||||
totalPositions: '共10个星球位置',
|
||||
mine: '我的',
|
||||
hostile: '敌对',
|
||||
emptySlot: '空位 - 可殖民',
|
||||
scout: '侦察',
|
||||
attack: '攻击',
|
||||
missileAttack: '导弹攻击',
|
||||
colonize: '殖民',
|
||||
switch: '切换',
|
||||
recycle: '回收',
|
||||
@@ -537,11 +546,22 @@ export default {
|
||||
debrisField: '残骸场',
|
||||
scoutPlanetTitle: '侦察星球',
|
||||
attackPlanetTitle: '攻击星球',
|
||||
missileAttackTitle: '导弹攻击',
|
||||
colonizePlanetTitle: '殖民星球',
|
||||
recyclePlanetTitle: '回收残骸',
|
||||
giftPlanetTitle: '赠送礼物',
|
||||
scoutPlanetMessage: '确定要派遣间谍探测器侦察星球 [{coordinates}] 吗?\n\n请前往舰队页面选择舰船并派遣。',
|
||||
attackPlanetMessage: '确定要攻击星球 [{coordinates}] 吗?\n\n请前往舰队页面选择舰船并派遣。',
|
||||
missileAttackMessage: '向星球 [{coordinates}] 发射导弹',
|
||||
missileCount: '导弹数量',
|
||||
availableMissiles: '可用导弹',
|
||||
missileRange: '射程',
|
||||
systems: '系统',
|
||||
distance: '距离',
|
||||
flightTime: '飞行时间',
|
||||
launchMissile: '发射',
|
||||
missileLaunched: '导弹已发射',
|
||||
cancel: '取消',
|
||||
colonizePlanetMessage: '确定要殖民位置 [{coordinates}] 吗?\n\n请前往舰队页面派遣殖民船。',
|
||||
recyclePlanetMessage: '确定要回收位置 [{coordinates}] 的残骸吗?\n\n请前往舰队页面派遣回收船。',
|
||||
giftPlanetMessage: '确定要向星球 [{coordinates}] 赠送资源吗?\n\n请前往舰队页面选择运输船并装载资源。'
|
||||
@@ -622,7 +642,11 @@ export default {
|
||||
recycleSuccess: '回收任务成功完成',
|
||||
recycleFailed: '回收任务失败,目标位置没有残骸',
|
||||
destroySuccess: '行星毁灭任务成功执行',
|
||||
destroyFailed: '行星毁灭任务失败'
|
||||
destroyFailed: '行星毁灭任务失败',
|
||||
missileAttackSuccess: '导弹攻击成功',
|
||||
missileAttackFailed: '导弹攻击失败,目标星球不存在',
|
||||
missileAttackIntercepted: '所有导弹被拦截',
|
||||
hits: '枚命中'
|
||||
},
|
||||
simulatorView: {
|
||||
title: '战斗模拟器',
|
||||
@@ -691,10 +715,24 @@ export default {
|
||||
gamePaused: '游戏已暂停',
|
||||
gameResumed: '游戏已恢复',
|
||||
playerName: '玩家名称',
|
||||
gameSpeed: '游戏速度',
|
||||
gameSpeedDesc: '当前游戏速度倍率',
|
||||
gameSpeed: '资源产出速度',
|
||||
gameSpeedDesc: '当前资源产出速度倍率',
|
||||
speedChanged: '资源产出速度已更改为 {speed}x',
|
||||
speedReset: '资源产出速度已重置为 1x',
|
||||
reset: '重置',
|
||||
about: '关于',
|
||||
version: '版本',
|
||||
latestVersion: '最新版本',
|
||||
checkUpdate: '检查更新',
|
||||
checking: '检查中...',
|
||||
newVersionAvailable: '发现新版本 {version}',
|
||||
upToDate: '已是最新版本',
|
||||
checkUpdateCooldown: '请稍后再试(5分钟冷却时间)',
|
||||
checkUpdateFailed: '检查更新失败,请检查网络连接',
|
||||
viewUpdate: '查看更新',
|
||||
updateAvailable: '有新版本可用。点击查看更新内容。',
|
||||
download: '下载',
|
||||
goToDownload: '前往下载',
|
||||
buildDate: '构建日期',
|
||||
community: '社区',
|
||||
github: 'GitHub 仓库',
|
||||
@@ -733,12 +771,22 @@ export default {
|
||||
testSpy: '测试侦查',
|
||||
testAttack: '测试攻击',
|
||||
testSpyAndAttack: '测试侦查&攻击',
|
||||
testSpyMessage: '点击确认以加速侦查任务',
|
||||
testAttackMessage: '点击确认以加速攻击任务',
|
||||
testSpyAndAttackMessage: '点击确认以加速任务执行',
|
||||
initializeFleet: '初始化NPC舰队',
|
||||
accelerateMissions: '加速所有任务(5秒)',
|
||||
selectNPCFirst: '请先选择一个NPC',
|
||||
npcNoProbes: 'NPC没有间谍探测器',
|
||||
npcNoSpyReport: 'NPC需要先侦查',
|
||||
npcMissionFailed: '创建任务失败',
|
||||
npcNoPlanets: 'NPC没有星球',
|
||||
npcWillSpyIn5s: '{npcName}将在5秒后侦查',
|
||||
npcWillAttackIn5s: '{npcName}将在5秒后攻击',
|
||||
npcWillSpyAndAttack: '{npcName}将在5秒后侦查,10秒后攻击',
|
||||
acceleratedMissions: '已加速{count}个任务至5秒后',
|
||||
npcFleetInitialized: '{npcName}舰队已初始化',
|
||||
npcFleetDetails: '100 间谍探测器\n500 轻型战机\n300 重型战机\n200 巡洋舰\n100 战列舰\n50 轰炸机\n30 毁灭者\n20 战列巡洋舰',
|
||||
dangerZone: '危险区域',
|
||||
dangerZoneDesc: '以下操作不可撤销,请谨慎操作',
|
||||
resetGame: '重置游戏',
|
||||
@@ -793,6 +841,28 @@ export default {
|
||||
allyAttacked: '攻击盟友',
|
||||
spy: '侦查',
|
||||
stealDebris: '抢夺残骸'
|
||||
},
|
||||
reports: {
|
||||
giftedResources: '赠送了 {metal}金属 {crystal}晶体 {deuterium}氘',
|
||||
receivedGiftFromPlayer: '收到玩家的礼物',
|
||||
giftedToNpc: '你向{npcName}赠送了资源。好感度+{reputation}',
|
||||
rejectedPlayerGift: '拒绝了玩家的礼物',
|
||||
npcRejectedGift: '{npcName}拒绝了你的礼物。好感度{reputation}',
|
||||
attackedNpc: '攻击了{npcName}',
|
||||
wasAttackedByPlayer: '被玩家攻击',
|
||||
youAttackedNpc: '你攻击了{npcName}',
|
||||
playerAttackedAlly: '玩家攻击了盟友{allyName}',
|
||||
allyDispleased: '{allyName}对你攻击盟友{targetName}感到不满',
|
||||
wasSpiedByPlayer: '被玩家侦查(被发现:{detected})',
|
||||
spyDetected: '你的侦查被{npcName}发现了',
|
||||
stoleDebrisFromTerritory: '从{npcName}的领地抢夺了残骸',
|
||||
playerStoleDebris: '玩家从领地抢夺了残骸',
|
||||
recycledDebrisNearNpc: '你在{npcName}的星球附近回收了残骸。他们很不高兴。',
|
||||
giftedResourcesToPlayer: '向玩家赠送了资源',
|
||||
receivedGiftFromNpc: '收到了{npcName}的礼物',
|
||||
acceptedGiftFromNpc: '你接受了{npcName}的礼物:{metal}金属 {crystal}晶体 {deuterium}氘',
|
||||
playerRejectedGift: '玩家拒绝了礼物',
|
||||
rejectedGiftFromNpc: '你拒绝了{npcName}的礼物。好感度{reputation}'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -801,5 +871,10 @@ export default {
|
||||
first: '首页',
|
||||
last: '末页',
|
||||
page: '第 {page} 页'
|
||||
},
|
||||
notFound: {
|
||||
title: '页面未找到',
|
||||
description: '抱歉,您访问的页面不存在',
|
||||
goHome: '返回首页'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,13 +33,19 @@ export default {
|
||||
viewRequirements: '查看前置條件',
|
||||
requirementsNotMet: '前置條件未滿足',
|
||||
current: '當前',
|
||||
level: '等級'
|
||||
level: '等級',
|
||||
gmModeActivated: 'GM 模式已啟用!請查看導航選單。'
|
||||
},
|
||||
errors: {
|
||||
requirementsNotMet: '不滿足前置條件',
|
||||
insufficientResources: '資源不足',
|
||||
insufficientFleetStorage: '艦隊倉儲空間不足',
|
||||
shieldDomeLimit: '護盾罩數量限制',
|
||||
missileSiloLimit: '導彈發射井容量已滿',
|
||||
insufficientMissiles: '星際導彈數量不足',
|
||||
invalidMissileCount: '導彈數量無效',
|
||||
targetOutOfRange: '目標超出射程',
|
||||
cannotAttackOwnPlanet: '不能攻擊自己的星球',
|
||||
fleetMissionsFull: '艦隊任務槽位已滿',
|
||||
insufficientFleet: '艦隊數量不足',
|
||||
insufficientFuel: '燃料不足',
|
||||
@@ -166,8 +172,8 @@ export default {
|
||||
darkMatterCollector: '收集稀有的暗物質資源',
|
||||
darkMatterTank: '增加暗物質儲存上限',
|
||||
missileSilo: '存儲和發射導彈,每級可存儲10枚導彈',
|
||||
terraformer: '改造行星地形,每級增加5個可用空間',
|
||||
lunarBase: '增加月球可用空間,每級+5空間',
|
||||
terraformer: '改造行星地形,每級增加30個可用空間',
|
||||
lunarBase: '增加月球可用空間,每級+30空間',
|
||||
sensorPhalanx: '偵測周圍星系的艦隊活動',
|
||||
jumpGate: '瞬間傳送艦隊到其他月球',
|
||||
planetDestroyerFactory: '建造能夠摧毀行星的終極武器'
|
||||
@@ -285,7 +291,7 @@ export default {
|
||||
impulseDrive: '中級推進技術',
|
||||
hyperspaceDrive: '高級推進技術',
|
||||
darkMatterTechnology: '研究暗物質的性質和應用',
|
||||
terraformingTechnology: '研究行星地形改造技術,每級為所有行星增加3個可用空間',
|
||||
terraformingTechnology: '研究行星地形改造技術,每級為所有行星增加30個可用空間',
|
||||
planetDestructionTech: '研究如何摧毀整個行星的恐怖技術'
|
||||
},
|
||||
officers: {
|
||||
@@ -318,6 +324,7 @@ export default {
|
||||
cancelResearch: '取消研究',
|
||||
confirmCancel: '確定要取消嗎?將返還50%的資源。',
|
||||
level: '等級',
|
||||
gmModeActivated: '',
|
||||
upgradeToLevel: '升級到等級'
|
||||
},
|
||||
overview: {
|
||||
@@ -338,6 +345,7 @@ export default {
|
||||
usedSpace: '已用空間',
|
||||
spaceUsage: '佔用空間',
|
||||
level: '等級',
|
||||
gmModeActivated: '',
|
||||
upgradeCost: '升級消耗',
|
||||
buildTime: '建造時間',
|
||||
upgrade: '升級',
|
||||
@@ -349,8 +357,8 @@ export default {
|
||||
demolishRefund: '拆除返還',
|
||||
demolishFailed: '拆除失敗',
|
||||
demolishFailedMessage: '無法拆除該建築,請檢查建造隊列是否已滿或建築等級是否為0。',
|
||||
confirmDemolish: '',
|
||||
confirmDemolishMessage: ''
|
||||
confirmDemolish: '確認拆除',
|
||||
confirmDemolishMessage: '確定要拆除以下建築嗎?'
|
||||
},
|
||||
researchView: {
|
||||
title: '研究',
|
||||
@@ -362,6 +370,7 @@ export default {
|
||||
},
|
||||
shipyard: {
|
||||
attack: '攻擊力',
|
||||
missileAttack: '導彈攻擊',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
speed: '速度',
|
||||
@@ -379,6 +388,7 @@ export default {
|
||||
title: '船塢',
|
||||
fleetStorage: '艦隊倉儲',
|
||||
attack: '攻擊力',
|
||||
missileAttack: '導彈攻擊',
|
||||
shield: '護盾',
|
||||
speed: '速度',
|
||||
cargoCapacity: '載貨量',
|
||||
@@ -393,6 +403,7 @@ export default {
|
||||
},
|
||||
defense: {
|
||||
attack: '攻擊力',
|
||||
missileAttack: '導彈攻擊',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
buildCost: '建造成本',
|
||||
@@ -406,6 +417,7 @@ export default {
|
||||
defenseView: {
|
||||
title: '防禦設施',
|
||||
attack: '攻擊力',
|
||||
missileAttack: '導彈攻擊',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
buildTime: '建造時間',
|
||||
@@ -415,6 +427,7 @@ export default {
|
||||
totalCost: '總成本',
|
||||
build: '建造',
|
||||
shieldDomeBuilt: '護盾罩已建造',
|
||||
missileCapacity: '導彈容量',
|
||||
inputError: '輸入錯誤',
|
||||
inputErrorMessage: '請輸入建造數量!',
|
||||
buildFailed: '建造失敗',
|
||||
@@ -427,6 +440,7 @@ export default {
|
||||
flightMissions: '飛行任務',
|
||||
currentPlanetFleet: '當前星球艦隊',
|
||||
attack: '攻擊',
|
||||
missileAttack: '導彈攻擊',
|
||||
shield: '護盾',
|
||||
armor: '裝甲',
|
||||
speed: '速度',
|
||||
@@ -521,25 +535,36 @@ export default {
|
||||
selectSystem: '選擇星系',
|
||||
view: '查看',
|
||||
myPlanet: '我的星球',
|
||||
myPlanets: '我的星球',
|
||||
myPlanets: '查看我的星系',
|
||||
npcPlanets: 'NPC星球',
|
||||
selectPlanetToView: '選擇要查看的星球',
|
||||
selectPlanetToView: '選擇星球以查看其所在星系',
|
||||
totalPositions: '共10個星球位置',
|
||||
mine: '我的',
|
||||
hostile: '敵對',
|
||||
emptySlot: '空位 - 可殖民',
|
||||
scout: '偵察',
|
||||
attack: '攻擊',
|
||||
missileAttack: '導彈攻擊',
|
||||
colonize: '殖民',
|
||||
switch: '切換',
|
||||
recycle: '回收',
|
||||
debrisField: '殘骸場',
|
||||
scoutPlanetTitle: '偵察星球',
|
||||
attackPlanetTitle: '攻擊星球',
|
||||
missileAttackTitle: '導彈攻擊',
|
||||
colonizePlanetTitle: '殖民星球',
|
||||
recyclePlanetTitle: '回收殘骸',
|
||||
scoutPlanetMessage: '確定要派遣間諜探測器偵察星球 [{coordinates}] 嗎?\n\n請前往艦隊頁面選擇艦船並派遣。',
|
||||
attackPlanetMessage: '確定要攻擊星球 [{coordinates}] 嗎?\n\n請前往艦隊頁面選擇艦船並派遣。',
|
||||
missileAttackMessage: '向星球 [{coordinates}] 發射星際導彈',
|
||||
missileCount: '導彈數量',
|
||||
availableMissiles: '可用導彈',
|
||||
missileRange: '導彈射程',
|
||||
systems: '星系',
|
||||
distance: '距離',
|
||||
flightTime: '飛行時間',
|
||||
launchMissile: '發射',
|
||||
cancel: '取消',
|
||||
colonizePlanetMessage: '確定要殖民位置 [{coordinates}] 嗎?\n\n請前往艦隊頁面派遣殖民船。',
|
||||
recyclePlanetMessage: '確定要回收位置 [{coordinates}] 的殘骸嗎?\n\n請前往艦隊頁面派遣回收船。',
|
||||
sendGift: '贈送禮物',
|
||||
@@ -692,10 +717,24 @@ export default {
|
||||
gamePaused: '遊戲已暫停',
|
||||
gameResumed: '遊戲已恢復',
|
||||
playerName: '玩家名稱',
|
||||
gameSpeed: '遊戲速度',
|
||||
gameSpeedDesc: '目前遊戲速度倍率',
|
||||
gameSpeed: '資源產出速度',
|
||||
gameSpeedDesc: '目前資源產出速度倍率',
|
||||
speedChanged: '資源產出速度已更改為 {speed}x',
|
||||
speedReset: '資源產出速度已重置為 1x',
|
||||
reset: '重置',
|
||||
about: '關於',
|
||||
version: '版本',
|
||||
latestVersion: '最新版本',
|
||||
checkUpdate: '檢查更新',
|
||||
checking: '檢查中...',
|
||||
newVersionAvailable: '發現新版本 {version}',
|
||||
upToDate: '已是最新版本',
|
||||
checkUpdateCooldown: '請稍後再試(5分鐘冷卻時間)',
|
||||
checkUpdateFailed: '檢查更新失敗,請檢查網路連線',
|
||||
viewUpdate: '查看更新',
|
||||
updateAvailable: '有新版本可用。點擊查看更新內容。',
|
||||
download: '下載',
|
||||
goToDownload: '前往下載',
|
||||
buildDate: '建置日期',
|
||||
community: '社群',
|
||||
github: 'GitHub 儲存庫',
|
||||
@@ -734,12 +773,22 @@ export default {
|
||||
testSpy: '測試偵查',
|
||||
testAttack: '測試攻擊',
|
||||
testSpyAndAttack: '測試偵查&攻擊',
|
||||
testSpyMessage: '點擊確認以加速偵查任務',
|
||||
testAttackMessage: '點擊確認以加速攻擊任務',
|
||||
testSpyAndAttackMessage: '點擊確認以加速任務執行',
|
||||
initializeFleet: '初始化NPC艦隊',
|
||||
accelerateMissions: '加速所有任務(5秒)',
|
||||
selectNPCFirst: '請先選擇一個NPC',
|
||||
npcNoProbes: 'NPC沒有間諜探測器',
|
||||
npcNoSpyReport: 'NPC需要先偵查',
|
||||
npcMissionFailed: '創建任務失敗',
|
||||
npcNoPlanets: 'NPC沒有星球',
|
||||
npcWillSpyIn5s: '{npcName}將在5秒後偵查',
|
||||
npcWillAttackIn5s: '{npcName}將在5秒後攻擊',
|
||||
npcWillSpyAndAttack: '{npcName}將在5秒後偵查,10秒後攻擊',
|
||||
acceleratedMissions: '已加速{count}個任務至5秒後',
|
||||
npcFleetInitialized: '{npcName}艦隊已初始化',
|
||||
npcFleetDetails: '100 間諜探測器\n500 輕型戰機\n300 重型戰機\n200 巡洋艦\n100 戰列艦\n50 轟炸機\n30 毀滅者\n20 戰列巡洋艦',
|
||||
dangerZone: '危險區域',
|
||||
dangerZoneDesc: '以下操作不可撤銷,請謹慎操作',
|
||||
resetGame: '重置遊戲',
|
||||
@@ -791,9 +840,32 @@ export default {
|
||||
events: {
|
||||
gift: '已贈送禮物',
|
||||
attack: '攻擊',
|
||||
missileAttack: '導彈攻擊',
|
||||
allyAttacked: '盟友被攻擊',
|
||||
spy: '間諜活動',
|
||||
stealDebris: '掠奪殘骸'
|
||||
},
|
||||
reports: {
|
||||
giftedResources: '贈送了 {metal}金屬 {crystal}晶體 {deuterium}氘',
|
||||
receivedGiftFromPlayer: '收到玩家的禮物',
|
||||
giftedToNpc: '你向{npcName}贈送了資源。好感度+{reputation}',
|
||||
rejectedPlayerGift: '拒絕了玩家的禮物',
|
||||
npcRejectedGift: '{npcName}拒絕了你的禮物。好感度{reputation}',
|
||||
attackedNpc: '攻擊了{npcName}',
|
||||
wasAttackedByPlayer: '被玩家攻擊',
|
||||
youAttackedNpc: '你攻擊了{npcName}',
|
||||
playerAttackedAlly: '玩家攻擊了盟友{allyName}',
|
||||
allyDispleased: '{allyName}對你攻擊盟友{targetName}感到不滿',
|
||||
wasSpiedByPlayer: '被玩家偵查(被發現:{detected})',
|
||||
spyDetected: '你的偵查被{npcName}發現了',
|
||||
stoleDebrisFromTerritory: '從{npcName}的領地掠奪了殘骸',
|
||||
playerStoleDebris: '玩家從領地掠奪了殘骸',
|
||||
recycledDebrisNearNpc: '你在{npcName}的星球附近回收了殘骸。他們很不高興。',
|
||||
giftedResourcesToPlayer: '向玩家贈送了資源',
|
||||
receivedGiftFromNpc: '收到了{npcName}的禮物',
|
||||
acceptedGiftFromNpc: '你接受了{npcName}的禮物:{metal}金屬 {crystal}晶體 {deuterium}氘',
|
||||
playerRejectedGift: '玩家拒絕了禮物',
|
||||
rejectedGiftFromNpc: '你拒絕了{npcName}的禮物。好感度{reputation}'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -802,5 +874,10 @@ export default {
|
||||
first: '首頁',
|
||||
last: '末頁',
|
||||
page: '第 {page} 頁'
|
||||
},
|
||||
notFound: {
|
||||
title: '找不到頁面',
|
||||
description: '抱歉,您訪問的頁面不存在',
|
||||
goHome: '返回首頁'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Fleet, Resources, BattleResult, Officer, TechnologyType } from '@/types/game'
|
||||
import { DefenseType, OfficerType } from '@/types/game'
|
||||
import { workerManager } from '@/workers/workerManager'
|
||||
import { MOON_CONFIG } from '@/config/gameConfig'
|
||||
|
||||
/**
|
||||
* 执行战斗模拟
|
||||
@@ -60,7 +61,10 @@ export const simulateBattle = async (
|
||||
|
||||
// 计算月球生成概率(根据残骸场总量)
|
||||
const totalDebris = debrisField.metal + debrisField.crystal
|
||||
const moonChance = Math.min(totalDebris / 100000, 0.2) // 最高20%概率
|
||||
const moonChance = Math.min(
|
||||
(MOON_CONFIG.baseChance + Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)),
|
||||
MOON_CONFIG.maxChance
|
||||
) / 100 // 转换为0-1的概率
|
||||
|
||||
// 生成战斗报告
|
||||
const battleResult: BattleResult = {
|
||||
|
||||
@@ -3,6 +3,9 @@ import { BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/gam
|
||||
import { BUILDINGS } from '@/config/gameConfig'
|
||||
import * as pointsLogic from './pointsLogic'
|
||||
|
||||
// 用于生成唯一ID的计数器
|
||||
let queueIdCounter = 0
|
||||
|
||||
/**
|
||||
* 计算建筑升级成本
|
||||
*/
|
||||
@@ -102,8 +105,9 @@ export const checkSpaceAvailable = (planet: Planet, buildingType: BuildingType):
|
||||
*/
|
||||
export const createBuildQueueItem = (buildingType: BuildingType, targetLevel: number, buildTime: number): BuildQueueItem => {
|
||||
const now = Date.now()
|
||||
queueIdCounter++
|
||||
return {
|
||||
id: `build_${now}`,
|
||||
id: `build_${now}_${queueIdCounter}`,
|
||||
type: 'building',
|
||||
itemType: buildingType,
|
||||
targetLevel,
|
||||
@@ -212,8 +216,9 @@ export const calculateDemolishTime = (
|
||||
*/
|
||||
export const createDemolishQueueItem = (buildingType: BuildingType, currentLevel: number, demolishTime: number): BuildQueueItem => {
|
||||
const now = Date.now()
|
||||
queueIdCounter++
|
||||
return {
|
||||
id: `demolish_${now}`,
|
||||
id: `demolish_${now}_${queueIdCounter}`,
|
||||
type: 'demolish',
|
||||
itemType: buildingType,
|
||||
targetLevel: currentLevel - 1, // 目标等级为当前等级-1
|
||||
|
||||
@@ -23,6 +23,14 @@ export const validateBuildingUpgrade = (
|
||||
const cost = buildingLogic.calculateBuildingCost(buildingType, targetLevel)
|
||||
const buildingConfig = BUILDINGS[buildingType]
|
||||
|
||||
// 检查队列中是否已存在该建筑的升级或拆除任务
|
||||
const existingQueueItem = planet.buildQueue.find(
|
||||
item => (item.type === 'building' || item.type === 'demolish') && item.itemType === buildingType
|
||||
)
|
||||
if (existingQueueItem) {
|
||||
return { valid: false, reason: 'errors.buildingAlreadyInQueue' }
|
||||
}
|
||||
|
||||
// 检查星球/月球限制
|
||||
if (buildingConfig.planetOnly && planet.isMoon) {
|
||||
return { valid: false, reason: 'errors.planetOnly' }
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import { DIPLOMATIC_CONFIG } from '@/config/gameConfig'
|
||||
import { locales, type Locale } from '@/locales'
|
||||
import type {
|
||||
DiplomaticRelation,
|
||||
RelationStatus,
|
||||
@@ -21,6 +22,37 @@ import type {
|
||||
} from '@/types/game'
|
||||
import { RelationStatus as RS, DiplomaticEventType as DET } from '@/types/game'
|
||||
|
||||
/**
|
||||
* 获取翻译文本的辅助函数
|
||||
* @param key 翻译键
|
||||
* @param locale 语言代码
|
||||
* @param params 参数
|
||||
* @returns 翻译后的文本
|
||||
*/
|
||||
const t = (key: string, locale: Locale, params?: Record<string, string | number>): string => {
|
||||
const keys = key.split('.')
|
||||
let value: any = locales[locale]
|
||||
|
||||
for (const k of keys) {
|
||||
if (value && typeof value === 'object' && k in value) {
|
||||
value = value[k]
|
||||
} else {
|
||||
return key // 如果找不到翻译,返回原始 key
|
||||
}
|
||||
}
|
||||
|
||||
let result = typeof value === 'string' ? value : key
|
||||
|
||||
// 替换参数占位符
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([paramKey, paramValue]) => {
|
||||
result = result.replace(new RegExp(`\\{${paramKey}\\}`, 'g'), String(paramValue))
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据好感度值计算关系状态
|
||||
* @param reputation 好感度值 (-100 到 +100)
|
||||
@@ -118,8 +150,11 @@ export const calculateGiftReputationGain = (resources: Resources): number => {
|
||||
const baseGain = REPUTATION_CHANGES.GIFT_BASE
|
||||
const valueGain = Math.floor(totalValue / 1000) * REPUTATION_CHANGES.GIFT_PER_1K_RESOURCES
|
||||
|
||||
// 确保达到门槛的礼物至少获得1点好感度
|
||||
const totalGain = Math.max(baseGain + valueGain, 1)
|
||||
|
||||
// 限制在最大值范围内
|
||||
return Math.min(baseGain + valueGain, REPUTATION_CHANGES.GIFT_MAX_SINGLE)
|
||||
return Math.min(totalGain, REPUTATION_CHANGES.GIFT_MAX_SINGLE)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,16 +208,22 @@ export const calculateNPCRejectionProbability = (npc: NPC, player: Player): numb
|
||||
* @param mission 舰队任务
|
||||
* @param player 玩家
|
||||
* @param targetNpc 目标NPC
|
||||
* @param locale 语言代码
|
||||
* @returns { accepted: boolean, reputationGain?: number }
|
||||
*/
|
||||
export const handleGiftArrival = (mission: FleetMission, player: Player, targetNpc: NPC): { accepted: boolean; reputationGain?: number } => {
|
||||
export const handleGiftArrival = (
|
||||
mission: FleetMission,
|
||||
player: Player,
|
||||
targetNpc: NPC,
|
||||
locale: Locale
|
||||
): { accepted: boolean; reputationGain?: number } => {
|
||||
// 计算NPC拒绝概率
|
||||
const rejectionProb = calculateNPCRejectionProbability(targetNpc, player)
|
||||
const isRejected = Math.random() < rejectionProb
|
||||
|
||||
if (isRejected) {
|
||||
// NPC拒绝礼物
|
||||
handleGiftRejection(player, targetNpc, mission.cargo)
|
||||
handleGiftRejection(player, targetNpc, mission.cargo, locale)
|
||||
return { accepted: false }
|
||||
}
|
||||
|
||||
@@ -200,7 +241,11 @@ export const handleGiftArrival = (mission: FleetMission, player: Player, targetN
|
||||
relation,
|
||||
reputationGain,
|
||||
DET.GiftResources,
|
||||
`Gifted ${mission.cargo.metal}M ${mission.cargo.crystal}C ${mission.cargo.deuterium}D`
|
||||
t('diplomacy.reports.giftedResources', locale, {
|
||||
metal: mission.cargo.metal.toString(),
|
||||
crystal: mission.cargo.crystal.toString(),
|
||||
deuterium: mission.cargo.deuterium.toString()
|
||||
})
|
||||
)
|
||||
|
||||
// 也更新NPC对玩家的关系(双向好感度)
|
||||
@@ -209,7 +254,12 @@ export const handleGiftArrival = (mission: FleetMission, player: Player, targetN
|
||||
}
|
||||
|
||||
const npcRelation = getOrCreateRelation(targetNpc.relations, targetNpc.id, player.id)
|
||||
targetNpc.relations[player.id] = updateReputation(npcRelation, reputationGain, DET.GiftResources, `Received gift from player`)
|
||||
targetNpc.relations[player.id] = updateReputation(
|
||||
npcRelation,
|
||||
reputationGain,
|
||||
DET.GiftResources,
|
||||
t('diplomacy.reports.receivedGiftFromPlayer', locale)
|
||||
)
|
||||
|
||||
// 生成外交报告
|
||||
generateDiplomaticReport(
|
||||
@@ -217,7 +267,7 @@ export const handleGiftArrival = (mission: FleetMission, player: Player, targetN
|
||||
targetNpc,
|
||||
DET.GiftResources,
|
||||
reputationGain,
|
||||
`You gifted resources to ${targetNpc.name}. Reputation +${reputationGain}`
|
||||
t('diplomacy.reports.giftedToNpc', locale, { npcName: targetNpc.name, reputation: reputationGain.toString() })
|
||||
)
|
||||
|
||||
return { accepted: true, reputationGain }
|
||||
@@ -228,8 +278,9 @@ export const handleGiftArrival = (mission: FleetMission, player: Player, targetN
|
||||
* @param player 玩家
|
||||
* @param npc NPC
|
||||
* @param rejectedResources 被拒绝的资源
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
const handleGiftRejection = (player: Player, npc: NPC, rejectedResources: Resources): void => {
|
||||
const handleGiftRejection = (player: Player, npc: NPC, rejectedResources: Resources, locale: Locale): void => {
|
||||
const { GIFT_ACCEPTANCE_CONFIG } = DIPLOMATIC_CONFIG
|
||||
|
||||
// 创建拒绝通知
|
||||
@@ -268,7 +319,7 @@ const handleGiftRejection = (player: Player, npc: NPC, rejectedResources: Resour
|
||||
npcRelation,
|
||||
GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY,
|
||||
DET.GiftResources,
|
||||
`Rejected player's gift`
|
||||
t('diplomacy.reports.rejectedPlayerGift', locale)
|
||||
)
|
||||
|
||||
// 生成外交报告
|
||||
@@ -277,7 +328,10 @@ const handleGiftRejection = (player: Player, npc: NPC, rejectedResources: Resour
|
||||
npc,
|
||||
DET.GiftResources,
|
||||
GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY,
|
||||
`${npc.name} rejected your gift. Reputation ${GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY}`
|
||||
t('diplomacy.reports.npcRejectedGift', locale, {
|
||||
npcName: npc.name,
|
||||
reputation: GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY.toString()
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -287,8 +341,15 @@ const handleGiftRejection = (player: Player, npc: NPC, rejectedResources: Resour
|
||||
* @param defender 防御者(NPC)
|
||||
* @param battleResult 战斗结果
|
||||
* @param allNpcs 所有NPC列表
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const handleAttackReputation = (attacker: Player, defender: NPC, battleResult: BattleResult, allNpcs: NPC[]): void => {
|
||||
export const handleAttackReputation = (
|
||||
attacker: Player,
|
||||
defender: NPC,
|
||||
battleResult: BattleResult,
|
||||
allNpcs: NPC[],
|
||||
locale: Locale
|
||||
): void => {
|
||||
const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG
|
||||
|
||||
// 计算好感度降低值
|
||||
@@ -304,7 +365,12 @@ export const handleAttackReputation = (attacker: Player, defender: NPC, battleRe
|
||||
}
|
||||
|
||||
const relation = getOrCreateRelation(attacker.diplomaticRelations, attacker.id, defender.id)
|
||||
attacker.diplomaticRelations[defender.id] = updateReputation(relation, -reputationLoss, DET.Attack, `Attacked ${defender.name}`)
|
||||
attacker.diplomaticRelations[defender.id] = updateReputation(
|
||||
relation,
|
||||
reputationLoss,
|
||||
DET.Attack,
|
||||
t('diplomacy.reports.attackedNpc', locale, { npcName: defender.name })
|
||||
)
|
||||
|
||||
// 更新被攻击NPC对玩家的关系
|
||||
if (!defender.relations) {
|
||||
@@ -312,15 +378,26 @@ export const handleAttackReputation = (attacker: Player, defender: NPC, battleRe
|
||||
}
|
||||
|
||||
const defenderRelation = getOrCreateRelation(defender.relations, defender.id, attacker.id)
|
||||
defender.relations[attacker.id] = updateReputation(defenderRelation, -reputationLoss, DET.Attack, `Was attacked by player`)
|
||||
defender.relations[attacker.id] = updateReputation(
|
||||
defenderRelation,
|
||||
reputationLoss,
|
||||
DET.Attack,
|
||||
t('diplomacy.reports.wasAttackedByPlayer', locale)
|
||||
)
|
||||
|
||||
// 检查盟友关系网络
|
||||
if (defender.allies && defender.allies.length > 0) {
|
||||
handleAllyAttackedReputation(attacker, defender, allNpcs)
|
||||
handleAllyAttackedReputation(attacker, defender, allNpcs, locale)
|
||||
}
|
||||
|
||||
// 生成外交报告
|
||||
generateDiplomaticReport(attacker, defender, DET.Attack, -reputationLoss, `You attacked ${defender.name}`)
|
||||
generateDiplomaticReport(
|
||||
attacker,
|
||||
defender,
|
||||
DET.Attack,
|
||||
-reputationLoss,
|
||||
t('diplomacy.reports.youAttackedNpc', locale, { npcName: defender.name })
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,8 +405,9 @@ export const handleAttackReputation = (attacker: Player, defender: NPC, battleRe
|
||||
* @param attacker 攻击者(玩家)
|
||||
* @param attackedNpc 被攻击的NPC
|
||||
* @param allNpcs 所有NPC列表
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const handleAllyAttackedReputation = (attacker: Player, attackedNpc: NPC, allNpcs: NPC[]): void => {
|
||||
export const handleAllyAttackedReputation = (attacker: Player, attackedNpc: NPC, allNpcs: NPC[], locale: Locale): void => {
|
||||
const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG
|
||||
|
||||
// 找到所有盟友
|
||||
@@ -344,9 +422,9 @@ export const handleAllyAttackedReputation = (attacker: Player, attackedNpc: NPC,
|
||||
const allyRelation = getOrCreateRelation(ally.relations, ally.id, attacker.id)
|
||||
ally.relations[attacker.id] = updateReputation(
|
||||
allyRelation,
|
||||
-REPUTATION_CHANGES.ALLY_ATTACKED,
|
||||
REPUTATION_CHANGES.ALLY_ATTACKED,
|
||||
DET.AllyAttacked,
|
||||
`Player attacked ally ${attackedNpc.name}`
|
||||
t('diplomacy.reports.playerAttackedAlly', locale, { allyName: attackedNpc.name })
|
||||
)
|
||||
|
||||
// 生成外交报告
|
||||
@@ -354,8 +432,8 @@ export const handleAllyAttackedReputation = (attacker: Player, attackedNpc: NPC,
|
||||
attacker,
|
||||
ally,
|
||||
DET.AllyAttacked,
|
||||
-REPUTATION_CHANGES.ALLY_ATTACKED,
|
||||
`${ally.name} is displeased that you attacked their ally ${attackedNpc.name}`
|
||||
REPUTATION_CHANGES.ALLY_ATTACKED,
|
||||
t('diplomacy.reports.allyDispleased', locale, { allyName: ally.name, targetName: attackedNpc.name })
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -365,8 +443,9 @@ export const handleAllyAttackedReputation = (attacker: Player, attackedNpc: NPC,
|
||||
* @param spy 侦查者(玩家)
|
||||
* @param target 侦查目标(NPC)
|
||||
* @param wasDetected 是否被发现
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const handleSpyReputation = (spy: Player, target: NPC, wasDetected: boolean): void => {
|
||||
export const handleSpyReputation = (spy: Player, target: NPC, wasDetected: boolean, locale: Locale): void => {
|
||||
const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG
|
||||
|
||||
const reputationLoss = wasDetected ? REPUTATION_CHANGES.SPY_DETECTED : REPUTATION_CHANGES.SPY_UNDETECTED
|
||||
@@ -377,11 +456,16 @@ export const handleSpyReputation = (spy: Player, target: NPC, wasDetected: boole
|
||||
}
|
||||
|
||||
const targetRelation = getOrCreateRelation(target.relations, target.id, spy.id)
|
||||
target.relations[spy.id] = updateReputation(targetRelation, -reputationLoss, DET.Spy, `Was spied by player (detected: ${wasDetected})`)
|
||||
target.relations[spy.id] = updateReputation(
|
||||
targetRelation,
|
||||
reputationLoss,
|
||||
DET.Spy,
|
||||
t('diplomacy.reports.wasSpiedByPlayer', locale, { detected: wasDetected ? 'true' : 'false' })
|
||||
)
|
||||
|
||||
// 如果被发现,生成外交报告
|
||||
if (wasDetected) {
|
||||
generateDiplomaticReport(spy, target, DET.Spy, -reputationLoss, `Your espionage was detected by ${target.name}`)
|
||||
generateDiplomaticReport(spy, target, DET.Spy, reputationLoss, t('diplomacy.reports.spyDetected', locale, { npcName: target.name }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,8 +475,9 @@ export const handleSpyReputation = (spy: Player, target: NPC, wasDetected: boole
|
||||
* @param player 玩家
|
||||
* @param debrisPosition 残骸位置
|
||||
* @param allNpcs 所有NPC列表
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const handleDebrisRecycleReputation = (player: Player, debrisPosition: Position, allNpcs: NPC[]): void => {
|
||||
export const handleDebrisRecycleReputation = (player: Player, debrisPosition: Position, allNpcs: NPC[], locale: Locale): void => {
|
||||
const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG
|
||||
|
||||
// 找到该位置的NPC星球所有者
|
||||
@@ -414,9 +499,9 @@ export const handleDebrisRecycleReputation = (player: Player, debrisPosition: Po
|
||||
const relation = getOrCreateRelation(player.diplomaticRelations, player.id, npcOwner.id)
|
||||
player.diplomaticRelations[npcOwner.id] = updateReputation(
|
||||
relation,
|
||||
-REPUTATION_CHANGES.STEAL_DEBRIS,
|
||||
REPUTATION_CHANGES.STEAL_DEBRIS,
|
||||
DET.StealDebris,
|
||||
`Stole debris from ${npcOwner.name}'s territory`
|
||||
t('diplomacy.reports.stoleDebrisFromTerritory', locale, { npcName: npcOwner.name })
|
||||
)
|
||||
|
||||
// 更新NPC对玩家的关系
|
||||
@@ -427,9 +512,9 @@ export const handleDebrisRecycleReputation = (player: Player, debrisPosition: Po
|
||||
const npcRelation = getOrCreateRelation(npcOwner.relations, npcOwner.id, player.id)
|
||||
npcOwner.relations[player.id] = updateReputation(
|
||||
npcRelation,
|
||||
-REPUTATION_CHANGES.STEAL_DEBRIS,
|
||||
REPUTATION_CHANGES.STEAL_DEBRIS,
|
||||
DET.StealDebris,
|
||||
`Player stole debris from territory`
|
||||
t('diplomacy.reports.playerStoleDebris', locale)
|
||||
)
|
||||
|
||||
// 生成外交报告
|
||||
@@ -437,8 +522,8 @@ export const handleDebrisRecycleReputation = (player: Player, debrisPosition: Po
|
||||
player,
|
||||
npcOwner,
|
||||
DET.StealDebris,
|
||||
-REPUTATION_CHANGES.STEAL_DEBRIS,
|
||||
`You recycled debris near ${npcOwner.name}'s planet. They are displeased.`
|
||||
REPUTATION_CHANGES.STEAL_DEBRIS,
|
||||
t('diplomacy.reports.recycledDebrisNearNpc', locale, { npcName: npcOwner.name })
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -539,8 +624,9 @@ export const handleNPCGiftToPlayer = (npc: NPC, player: Player, giftResources: R
|
||||
* @param player 玩家
|
||||
* @param npc NPC
|
||||
* @param giftNotification 礼物通知
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const acceptNPCGift = (player: Player, npc: NPC, giftNotification: GiftNotification): void => {
|
||||
export const acceptNPCGift = (player: Player, npc: NPC, giftNotification: GiftNotification, locale: Locale): void => {
|
||||
// 将资源添加到玩家主星球
|
||||
if (player.planets && player.planets.length > 0) {
|
||||
const mainPlanet = player.planets[0]
|
||||
@@ -558,7 +644,12 @@ export const acceptNPCGift = (player: Player, npc: NPC, giftNotification: GiftNo
|
||||
}
|
||||
|
||||
const npcRelation = getOrCreateRelation(npc.relations, npc.id, player.id)
|
||||
npc.relations[player.id] = updateReputation(npcRelation, giftNotification.expectedReputationGain, DET.GiftResources, `Gifted resources to player`)
|
||||
npc.relations[player.id] = updateReputation(
|
||||
npcRelation,
|
||||
giftNotification.expectedReputationGain,
|
||||
DET.GiftResources,
|
||||
t('diplomacy.reports.giftedResourcesToPlayer', locale)
|
||||
)
|
||||
|
||||
// 也更新玩家对NPC的关系(收到礼物会增加好感)
|
||||
if (!player.diplomaticRelations) {
|
||||
@@ -570,7 +661,7 @@ export const acceptNPCGift = (player: Player, npc: NPC, giftNotification: GiftNo
|
||||
playerRelation,
|
||||
giftNotification.expectedReputationGain,
|
||||
DET.GiftResources,
|
||||
`Received gift from ${npc.name}`
|
||||
t('diplomacy.reports.receivedGiftFromNpc', locale, { npcName: npc.name })
|
||||
)
|
||||
|
||||
// 生成外交报告
|
||||
@@ -579,7 +670,12 @@ export const acceptNPCGift = (player: Player, npc: NPC, giftNotification: GiftNo
|
||||
npc,
|
||||
DET.GiftResources,
|
||||
giftNotification.expectedReputationGain,
|
||||
`You accepted a gift from ${npc.name}: ${giftNotification.resources.metal}M ${giftNotification.resources.crystal}C ${giftNotification.resources.deuterium}D`
|
||||
t('diplomacy.reports.acceptedGiftFromNpc', locale, {
|
||||
npcName: npc.name,
|
||||
metal: giftNotification.resources.metal.toString(),
|
||||
crystal: giftNotification.resources.crystal.toString(),
|
||||
deuterium: giftNotification.resources.deuterium.toString()
|
||||
})
|
||||
)
|
||||
|
||||
// 移除礼物通知
|
||||
@@ -593,8 +689,9 @@ export const acceptNPCGift = (player: Player, npc: NPC, giftNotification: GiftNo
|
||||
* @param player 玩家
|
||||
* @param npc NPC
|
||||
* @param giftNotification 礼物通知
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const rejectNPCGift = (player: Player, npc: NPC, giftNotification: GiftNotification): void => {
|
||||
export const rejectNPCGift = (player: Player, npc: NPC, giftNotification: GiftNotification, locale: Locale): void => {
|
||||
const { GIFT_ACCEPTANCE_CONFIG } = DIPLOMATIC_CONFIG
|
||||
|
||||
// 拒绝礼物会降低好感度
|
||||
@@ -607,7 +704,7 @@ export const rejectNPCGift = (player: Player, npc: NPC, giftNotification: GiftNo
|
||||
npcRelation,
|
||||
GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY,
|
||||
DET.GiftResources,
|
||||
`Player rejected gift`
|
||||
t('diplomacy.reports.playerRejectedGift', locale)
|
||||
)
|
||||
|
||||
// 生成外交报告
|
||||
@@ -616,7 +713,10 @@ export const rejectNPCGift = (player: Player, npc: NPC, giftNotification: GiftNo
|
||||
npc,
|
||||
DET.GiftResources,
|
||||
GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY,
|
||||
`You rejected a gift from ${npc.name}. Reputation ${GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY}`
|
||||
t('diplomacy.reports.rejectedGiftFromNpc', locale, {
|
||||
npcName: npc.name,
|
||||
reputation: GIFT_ACCEPTANCE_CONFIG.REJECTION_REPUTATION_PENALTY.toString()
|
||||
})
|
||||
)
|
||||
|
||||
// 移除礼物通知
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer, DebrisField, NPC } from '@/types/game'
|
||||
import type { Locale } from '@/locales'
|
||||
import { ShipType, DefenseType, MissionType, BuildingType, OfficerType, TechnologyType } from '@/types/game'
|
||||
import { FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
|
||||
import * as battleLogic from './battleLogic'
|
||||
@@ -75,13 +76,14 @@ export const processTransportArrival = (
|
||||
mission: FleetMission,
|
||||
targetPlanet: Planet | undefined,
|
||||
player?: Player,
|
||||
allNpcs?: NPC[]
|
||||
allNpcs?: NPC[],
|
||||
locale: Locale = 'zh-CN'
|
||||
): { success: boolean; reputationGain?: number } => {
|
||||
// 检查是否是赠送任务
|
||||
if (mission.isGift && mission.giftTargetNpcId && player && allNpcs) {
|
||||
const targetNpc = allNpcs.find(n => n.id === mission.giftTargetNpcId)
|
||||
if (targetNpc) {
|
||||
const giftResult = diplomaticLogic.handleGiftArrival(mission, player, targetNpc)
|
||||
const giftResult = diplomaticLogic.handleGiftArrival(mission, player, targetNpc, locale)
|
||||
mission.status = 'returning'
|
||||
|
||||
// 如果礼物被拒绝,资源返还给玩家
|
||||
@@ -316,8 +318,8 @@ export const processNPCAttackArrival = async (
|
||||
* 基于天体物理学技术等级
|
||||
*/
|
||||
export const calculateMaxPlanets = (astrophysicsLevel: number): number => {
|
||||
// 基础1个星球 + 每2级天体物理学增加1个殖民地槽位
|
||||
return 1 + Math.floor(astrophysicsLevel / 2)
|
||||
// 基础1个星球(主星) + 每级天体物理学增加1个殖民地槽位
|
||||
return 1 + astrophysicsLevel
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -463,7 +465,8 @@ export const processSpyArrival = (
|
||||
targetPlanet: Planet | undefined,
|
||||
attacker: Player,
|
||||
defender: Player | null,
|
||||
allNpcs?: NPC[]
|
||||
allNpcs?: NPC[],
|
||||
locale: Locale = 'zh-CN'
|
||||
): SpyReport | null => {
|
||||
if (!targetPlanet) {
|
||||
mission.status = 'returning'
|
||||
@@ -504,7 +507,7 @@ export const processSpyArrival = (
|
||||
if (allNpcs && targetPlanet.ownerId) {
|
||||
const targetNpc = allNpcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
|
||||
if (targetNpc) {
|
||||
diplomaticLogic.handleSpyReputation(attacker, targetNpc, wasDetected)
|
||||
diplomaticLogic.handleSpyReputation(attacker, targetNpc, wasDetected, locale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -536,7 +539,8 @@ export const processRecycleArrival = (
|
||||
mission: FleetMission,
|
||||
debrisField: DebrisField | undefined,
|
||||
player?: Player,
|
||||
allNpcs?: NPC[]
|
||||
allNpcs?: NPC[],
|
||||
locale: Locale = 'zh-CN'
|
||||
): { collectedResources: Pick<Resources, 'metal' | 'crystal'>; remainingDebris: Pick<Resources, 'metal' | 'crystal'> | null } | null => {
|
||||
if (!debrisField) {
|
||||
mission.status = 'returning'
|
||||
@@ -558,6 +562,12 @@ export const processRecycleArrival = (
|
||||
const totalDebris = debrisField.resources.metal + debrisField.resources.crystal
|
||||
const collectedAmount = Math.min(totalDebris, availableCapacity)
|
||||
|
||||
// 防止除零:如果残骸为0,直接返回
|
||||
if (totalDebris === 0) {
|
||||
mission.status = 'returning'
|
||||
return null
|
||||
}
|
||||
|
||||
// 按比例收集金属和晶体
|
||||
const metalRatio = debrisField.resources.metal / totalDebris
|
||||
const crystalRatio = debrisField.resources.crystal / totalDebris
|
||||
@@ -577,7 +587,7 @@ export const processRecycleArrival = (
|
||||
|
||||
// 检查是否在NPC星球位置回收残骸,如果是则降低好感度
|
||||
if (player && allNpcs && collectedAmount > 0) {
|
||||
diplomaticLogic.handleDebrisRecycleReputation(player, debrisField.position, allNpcs)
|
||||
diplomaticLogic.handleDebrisRecycleReputation(player, debrisField.position, allNpcs, locale)
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -19,6 +19,7 @@ export const initializePlayer = (playerId: string, playerName: string = 'Command
|
||||
officers: {} as Record<OfficerType, Officer>,
|
||||
researchQueue: [],
|
||||
fleetMissions: [],
|
||||
missileAttacks: [],
|
||||
battleReports: [],
|
||||
spyReports: [],
|
||||
spiedNotifications: [],
|
||||
|
||||
207
src/logic/missileLogic.ts
Normal file
207
src/logic/missileLogic.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
/**
|
||||
* 导弹系统逻辑
|
||||
* 处理星际导弹攻击、射程计算、拦截等
|
||||
*/
|
||||
|
||||
import type { Planet, MissileAttack, DefenseType, TechnologyType, Position } from '@/types/game'
|
||||
import { DefenseType as DefenseTypes } from '@/types/game'
|
||||
|
||||
/**
|
||||
* 计算导弹射程(基于脉冲引擎等级)
|
||||
* 射程 = 5 * impulseDriveLevel - 1(系统距离)
|
||||
*/
|
||||
export const calculateMissileRange = (impulseDriveLevel: number): number => {
|
||||
if (impulseDriveLevel === 0) return 0
|
||||
return 5 * impulseDriveLevel - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算两个位置之间的系统距离
|
||||
*/
|
||||
export const calculateSystemDistance = (from: Position, to: Position): number => {
|
||||
// 如果在不同银河系,距离无限大
|
||||
if (from.galaxy !== to.galaxy) {
|
||||
return Infinity
|
||||
}
|
||||
|
||||
// 同一银河系内的系统距离
|
||||
return Math.abs(from.system - to.system)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查目标是否在射程内
|
||||
*/
|
||||
export const isTargetInRange = (
|
||||
originPosition: Position,
|
||||
targetPosition: Position,
|
||||
impulseDriveLevel: number
|
||||
): boolean => {
|
||||
const range = calculateMissileRange(impulseDriveLevel)
|
||||
const distance = calculateSystemDistance(originPosition, targetPosition)
|
||||
return distance <= range
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算导弹飞行时间(秒)
|
||||
* 基础飞行时间: 30秒 + 60秒/系统距离
|
||||
*/
|
||||
export const calculateMissileFlightTime = (distance: number): number => {
|
||||
return 30 + distance * 60
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建导弹攻击任务
|
||||
*/
|
||||
export const createMissileAttack = (
|
||||
playerId: string,
|
||||
originPlanet: Planet,
|
||||
targetPosition: Position,
|
||||
targetPlanetId: string | undefined,
|
||||
missileCount: number
|
||||
): MissileAttack => {
|
||||
const now = Date.now()
|
||||
const distance = calculateSystemDistance(originPlanet.position, targetPosition)
|
||||
const flightTime = calculateMissileFlightTime(distance) * 1000 // 转换为毫秒
|
||||
|
||||
return {
|
||||
id: `missile_${now}_${playerId}`,
|
||||
playerId,
|
||||
originPlanetId: originPlanet.id,
|
||||
targetPosition,
|
||||
targetPlanetId,
|
||||
missileCount,
|
||||
launchTime: now,
|
||||
arrivalTime: now + flightTime,
|
||||
status: 'flying'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证导弹发射条件
|
||||
*/
|
||||
export const validateMissileLaunch = (
|
||||
originPlanet: Planet,
|
||||
targetPosition: Position,
|
||||
missileCount: number,
|
||||
technologies: Partial<Record<TechnologyType, number>>
|
||||
): {
|
||||
valid: boolean
|
||||
reason?: string
|
||||
} => {
|
||||
// 检查是否有足够的星际导弹
|
||||
const availableMissiles = originPlanet.defense[DefenseTypes.InterplanetaryMissile] || 0
|
||||
if (availableMissiles < missileCount) {
|
||||
return { valid: false, reason: 'errors.insufficientMissiles' }
|
||||
}
|
||||
|
||||
// 检查发射数量
|
||||
if (missileCount <= 0) {
|
||||
return { valid: false, reason: 'errors.invalidMissileCount' }
|
||||
}
|
||||
|
||||
// 检查射程
|
||||
const impulseDriveLevel = technologies['impulseDrive'] || 0
|
||||
if (!isTargetInRange(originPlanet.position, targetPosition, impulseDriveLevel)) {
|
||||
return { valid: false, reason: 'errors.targetOutOfRange' }
|
||||
}
|
||||
|
||||
// 不能攻击自己的星球
|
||||
if (
|
||||
originPlanet.position.galaxy === targetPosition.galaxy &&
|
||||
originPlanet.position.system === targetPosition.system &&
|
||||
originPlanet.position.position === targetPosition.position
|
||||
) {
|
||||
return { valid: false, reason: 'errors.cannotAttackOwnPlanet' }
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行导弹发射(扣除导弹)
|
||||
*/
|
||||
export const executeMissileLaunch = (planet: Planet, missileCount: number): void => {
|
||||
const currentMissiles = planet.defense[DefenseTypes.InterplanetaryMissile] || 0
|
||||
planet.defense[DefenseTypes.InterplanetaryMissile] = currentMissiles - missileCount
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算导弹攻击结果(考虑拦截)
|
||||
* @returns 实际命中的导弹数量
|
||||
*/
|
||||
export const calculateMissileImpact = (
|
||||
attackingMissiles: number,
|
||||
defenderPlanet: Planet
|
||||
): {
|
||||
missileHits: number
|
||||
missileIntercepted: number
|
||||
defenseLosses: Partial<Record<DefenseType, number>>
|
||||
} => {
|
||||
const antiBallisticMissiles = defenderPlanet.defense[DefenseTypes.AntiBallisticMissile] || 0
|
||||
|
||||
// 反弹道导弹拦截(1:1)
|
||||
const intercepted = Math.min(attackingMissiles, antiBallisticMissiles)
|
||||
const missileHits = attackingMissiles - intercepted
|
||||
|
||||
// 计算防御损失
|
||||
const defenseLosses: Partial<Record<DefenseType, number>> = {}
|
||||
|
||||
// 消耗的反弹道导弹
|
||||
if (intercepted > 0) {
|
||||
defenseLosses[DefenseTypes.AntiBallisticMissile] = intercepted
|
||||
}
|
||||
|
||||
// 如果有导弹命中,随机摧毁防御设施
|
||||
if (missileHits > 0) {
|
||||
const defenseTypes = Object.keys(defenderPlanet.defense) as DefenseType[]
|
||||
const availableDefenses = defenseTypes.filter(type => {
|
||||
// 不能摧毁护盾罩和行星护盾
|
||||
if (
|
||||
type === DefenseTypes.SmallShieldDome ||
|
||||
type === DefenseTypes.LargeShieldDome ||
|
||||
type === DefenseTypes.PlanetaryShield
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return (defenderPlanet.defense[type] || 0) > 0
|
||||
})
|
||||
|
||||
// 每枚导弹可以摧毁一个防御设施
|
||||
for (let i = 0; i < missileHits && availableDefenses.length > 0; i++) {
|
||||
const randomIndex = Math.floor(Math.random() * availableDefenses.length)
|
||||
const targetDefense = availableDefenses[randomIndex]
|
||||
|
||||
if (targetDefense) {
|
||||
if (!defenseLosses[targetDefense]) {
|
||||
defenseLosses[targetDefense] = 0
|
||||
}
|
||||
defenseLosses[targetDefense]!++
|
||||
|
||||
// 如果该类型防御全部摧毁,从可用列表中移除
|
||||
const remaining = (defenderPlanet.defense[targetDefense] || 0) - (defenseLosses[targetDefense] || 0)
|
||||
if (remaining <= 0) {
|
||||
availableDefenses.splice(randomIndex, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
missileHits,
|
||||
missileIntercepted: intercepted,
|
||||
defenseLosses
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用导弹攻击结果到星球
|
||||
*/
|
||||
export const applyMissileAttackResult = (
|
||||
planet: Planet,
|
||||
defenseLosses: Partial<Record<DefenseType, number>>
|
||||
): void => {
|
||||
for (const [defenseType, lossCount] of Object.entries(defenseLosses)) {
|
||||
const currentCount = planet.defense[defenseType as DefenseType] || 0
|
||||
planet.defense[defenseType as DefenseType] = Math.max(0, currentCount - lossCount)
|
||||
}
|
||||
}
|
||||
@@ -97,8 +97,8 @@ export const shouldNPCSpyPlayer = (npc: NPC, player: Player, currentTime: number
|
||||
return Math.random() < 0.5
|
||||
}
|
||||
if (relation.status === RelationStatus.Hostile) {
|
||||
// 敌对NPC侦查频率提高50%
|
||||
return Math.random() < 1.5
|
||||
// 敌对NPC必定侦查
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +695,7 @@ export const forceNPCSpyAndAttack = (
|
||||
return { spyMission, attackMission: null }
|
||||
}
|
||||
|
||||
const { spyReport } = processNPCSpyArrival(npc, spyMission, targetPlanet, player)
|
||||
const { spyReport, spiedNotification } = processNPCSpyArrival(npc, spyMission, targetPlanet, player)
|
||||
|
||||
// 保存侦查报告到NPC
|
||||
if (!npc.playerSpyReports) {
|
||||
@@ -703,6 +703,12 @@ export const forceNPCSpyAndAttack = (
|
||||
}
|
||||
npc.playerSpyReports[targetPlanet.id] = spyReport
|
||||
|
||||
// 添加被侦查通知给玩家
|
||||
if (!player.spiedNotifications) {
|
||||
player.spiedNotifications = []
|
||||
}
|
||||
player.spiedNotifications.push(spiedNotification)
|
||||
|
||||
// 3. 立即发起攻击
|
||||
const attackMission = forceNPCAttackPlayer(npc, player, allPlanets, targetPlanetIndex)
|
||||
|
||||
@@ -713,7 +719,7 @@ export const forceNPCSpyAndAttack = (
|
||||
* 测试函数:加速舰队任务到达时间
|
||||
* 将任务的到达时间设置为现在+指定秒数
|
||||
*/
|
||||
export const accelerateNPCMission = (npc: NPC, missionId: string, arriveInSeconds = 5): boolean => {
|
||||
export const accelerateNPCMission = (npc: NPC, missionId: string, arriveInSeconds = 5, player?: Player): boolean => {
|
||||
if (!npc.fleetMissions) {
|
||||
console.error('[Test] NPC has no fleet missions')
|
||||
return false
|
||||
@@ -726,7 +732,19 @@ export const accelerateNPCMission = (npc: NPC, missionId: string, arriveInSecond
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
mission.arrivalTime = now + arriveInSeconds * 1000
|
||||
const flightTime = arriveInSeconds * 1000 // 飞行时间(毫秒)
|
||||
|
||||
// 同时修改 departureTime 和 arrivalTime,保持飞行时间为指定秒数
|
||||
mission.departureTime = now
|
||||
mission.arrivalTime = now + flightTime
|
||||
|
||||
// 同时更新对应的 IncomingFleetAlert
|
||||
if (player && player.incomingFleetAlerts) {
|
||||
const alert = player.incomingFleetAlerts.find(a => a.id === missionId)
|
||||
if (alert) {
|
||||
alert.arrivalTime = mission.arrivalTime
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -734,21 +752,34 @@ export const accelerateNPCMission = (npc: NPC, missionId: string, arriveInSecond
|
||||
/**
|
||||
* 测试函数:加速所有NPC舰队任务
|
||||
*/
|
||||
export const accelerateAllNPCMissions = (npc: NPC, arriveInSeconds = 5): number => {
|
||||
export const accelerateAllNPCMissions = (npc: NPC, arriveInSeconds = 5, player?: Player): number => {
|
||||
if (!npc.fleetMissions) {
|
||||
console.error('[Test] NPC has no fleet missions')
|
||||
return 0
|
||||
}
|
||||
|
||||
const now = Date.now()
|
||||
const flightTime = arriveInSeconds * 1000
|
||||
let count = 0
|
||||
|
||||
npc.fleetMissions.forEach(mission => {
|
||||
if (mission.status === 'outbound') {
|
||||
mission.arrivalTime = now + arriveInSeconds * 1000
|
||||
// 同时修改 departureTime 和 arrivalTime
|
||||
mission.departureTime = now
|
||||
mission.arrivalTime = now + flightTime
|
||||
|
||||
// 同时更新对应的 IncomingFleetAlert
|
||||
if (player && player.incomingFleetAlerts) {
|
||||
const alert = player.incomingFleetAlerts.find(a => a.id === mission.id)
|
||||
if (alert) {
|
||||
alert.arrivalTime = mission.arrivalTime
|
||||
}
|
||||
}
|
||||
|
||||
count++
|
||||
} else if (mission.status === 'returning' && mission.returnTime) {
|
||||
mission.returnTime = now + arriveInSeconds * 1000
|
||||
// 对于返回任务,保持原来的逻辑
|
||||
mission.returnTime = now + flightTime
|
||||
count++
|
||||
}
|
||||
})
|
||||
|
||||
@@ -3,6 +3,9 @@ import { TechnologyType, BuildingType } from '@/types/game'
|
||||
import { TECHNOLOGIES } from '@/config/gameConfig'
|
||||
import * as pointsLogic from './pointsLogic'
|
||||
|
||||
// 用于生成唯一ID的计数器
|
||||
let researchQueueIdCounter = 0
|
||||
|
||||
/**
|
||||
* 计算科技研究成本
|
||||
*/
|
||||
@@ -77,8 +80,9 @@ export const checkTechnologyRequirements = (
|
||||
*/
|
||||
export const createResearchQueueItem = (techType: TechnologyType, targetLevel: number, researchTime: number): BuildQueueItem => {
|
||||
const now = Date.now()
|
||||
researchQueueIdCounter++
|
||||
return {
|
||||
id: `research_${now}`,
|
||||
id: `research_${now}_${researchQueueIdCounter}`,
|
||||
type: 'technology',
|
||||
itemType: techType,
|
||||
targetLevel,
|
||||
|
||||
@@ -21,6 +21,14 @@ export const validateTechnologyResearch = (
|
||||
const targetLevel = currentLevel + 1
|
||||
const cost = researchLogic.calculateTechnologyCost(techType, targetLevel)
|
||||
|
||||
// 检查队列中是否已存在该科技的研究任务
|
||||
const existingQueueItem = researchQueue.find(
|
||||
item => item.type === 'technology' && item.itemType === techType
|
||||
)
|
||||
if (existingQueueItem) {
|
||||
return { valid: false, reason: 'errors.technologyAlreadyInQueue' }
|
||||
}
|
||||
|
||||
// 检查研究队列是否已满
|
||||
const maxQueue = publicLogic.getMaxResearchQueue(technologies)
|
||||
if (researchQueue.length >= maxQueue) {
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { Resources, BuildQueueItem, Fleet } from '@/types/game'
|
||||
import { ShipType, DefenseType, BuildingType, TechnologyType } from '@/types/game'
|
||||
import { SHIPS, DEFENSES } from '@/config/gameConfig'
|
||||
|
||||
// 用于生成唯一ID的计数器
|
||||
let shipQueueIdCounter = 0
|
||||
|
||||
/**
|
||||
* 计算舰船建造成本
|
||||
*/
|
||||
@@ -153,13 +156,52 @@ export const checkShieldDomeLimit = (
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算导弹发射井容量
|
||||
*/
|
||||
export const calculateMissileSiloCapacity = (buildings: Partial<Record<BuildingType, number>>): number => {
|
||||
const siloLevel = buildings[BuildingType.MissileSilo] || 0
|
||||
return siloLevel * 10 // 每级存储10枚导弹
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算当前导弹总数
|
||||
*/
|
||||
export const calculateCurrentMissileCount = (defense: Partial<Record<DefenseType, number>>): number => {
|
||||
const interplanetaryMissiles = defense[DefenseType.InterplanetaryMissile] || 0
|
||||
const antiBallisticMissiles = defense[DefenseType.AntiBallisticMissile] || 0
|
||||
return interplanetaryMissiles + antiBallisticMissiles
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查导弹容量限制
|
||||
*/
|
||||
export const checkMissileSiloLimit = (
|
||||
defenseType: DefenseType,
|
||||
currentDefense: Partial<Record<DefenseType, number>>,
|
||||
buildings: Partial<Record<BuildingType, number>>,
|
||||
quantity: number
|
||||
): boolean => {
|
||||
// 只对导弹类型进行检查
|
||||
if (defenseType !== DefenseType.InterplanetaryMissile && defenseType !== DefenseType.AntiBallisticMissile) {
|
||||
return true
|
||||
}
|
||||
|
||||
const maxCapacity = calculateMissileSiloCapacity(buildings)
|
||||
const currentCount = calculateCurrentMissileCount(currentDefense)
|
||||
const newCount = currentCount + quantity
|
||||
|
||||
return newCount <= maxCapacity
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建舰船建造队列项
|
||||
*/
|
||||
export const createShipQueueItem = (shipType: ShipType, quantity: number, buildTime: number): BuildQueueItem => {
|
||||
const now = Date.now()
|
||||
shipQueueIdCounter++
|
||||
return {
|
||||
id: `ship_${now}`,
|
||||
id: `ship_${now}_${shipQueueIdCounter}`,
|
||||
type: 'ship',
|
||||
itemType: shipType,
|
||||
quantity,
|
||||
@@ -173,8 +215,9 @@ export const createShipQueueItem = (shipType: ShipType, quantity: number, buildT
|
||||
*/
|
||||
export const createDefenseQueueItem = (defenseType: DefenseType, quantity: number, buildTime: number): BuildQueueItem => {
|
||||
const now = Date.now()
|
||||
shipQueueIdCounter++
|
||||
return {
|
||||
id: `defense_${now}`,
|
||||
id: `defense_${now}_${shipQueueIdCounter}`,
|
||||
type: 'defense',
|
||||
itemType: defenseType,
|
||||
quantity,
|
||||
|
||||
@@ -100,6 +100,11 @@ export const validateDefenseBuild = (
|
||||
return { valid: false, reason: 'errors.shieldDomeLimit' }
|
||||
}
|
||||
|
||||
// 导弹发射井容量限制
|
||||
if (!shipLogic.checkMissileSiloLimit(defenseType, planet.defense, planet.buildings, quantity)) {
|
||||
return { valid: false, reason: 'errors.missileSiloLimit' }
|
||||
}
|
||||
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
|
||||
@@ -15,19 +15,8 @@ const router = createRouter({
|
||||
{ path: '/galaxy', name: 'galaxy', component: () => import('@/views/GalaxyView.vue') },
|
||||
{ path: '/diplomacy', name: 'diplomacy', component: () => import('@/views/DiplomacyView.vue') },
|
||||
{ path: '/settings', name: 'settings', component: () => import('@/views/SettingsView.vue') },
|
||||
{
|
||||
path: '/gm',
|
||||
name: 'gm',
|
||||
component: () => import('@/views/GMView.vue'),
|
||||
beforeEnter: (_to, _from, next) => {
|
||||
// GM页面仅在开发模式下可访问
|
||||
if (import.meta.env.DEV) {
|
||||
next()
|
||||
} else {
|
||||
next('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
{ path: '/gm', name: 'gm', component: () => import('@/views/GMView.vue') },
|
||||
{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFoundView.vue') }
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { Planet, Player, BuildQueueItem, FleetMission, BattleResult, SpyReport, Officer, SpiedNotification, NPCActivityNotification, IncomingFleetAlert } from '@/types/game'
|
||||
import type { Planet, Player, BuildQueueItem, FleetMission, BattleResult, SpyReport, Officer, SpiedNotification, NPCActivityNotification, IncomingFleetAlert, MissileAttack } from '@/types/game'
|
||||
import { TechnologyType, OfficerType } from '@/types/game'
|
||||
import type { Locale } from '@/locales'
|
||||
import pkg from '../../package.json'
|
||||
@@ -9,6 +9,7 @@ export const useGameStore = defineStore('game', {
|
||||
state: () => ({
|
||||
gameTime: Date.now(),
|
||||
isPaused: false,
|
||||
gameSpeed: 1,
|
||||
player: {
|
||||
id: 'player1',
|
||||
name: '',
|
||||
@@ -17,6 +18,7 @@ export const useGameStore = defineStore('game', {
|
||||
officers: {} as Record<OfficerType, Officer>,
|
||||
researchQueue: [] as BuildQueueItem[],
|
||||
fleetMissions: [] as FleetMission[],
|
||||
missileAttacks: [] as MissileAttack[],
|
||||
battleReports: [] as BattleResult[],
|
||||
spyReports: [] as SpyReport[],
|
||||
spiedNotifications: [] as SpiedNotification[],
|
||||
@@ -25,7 +27,9 @@ export const useGameStore = defineStore('game', {
|
||||
incomingFleetAlerts: [] as IncomingFleetAlert[],
|
||||
giftNotifications: [],
|
||||
giftRejectedNotifications: [],
|
||||
points: 0
|
||||
points: 0,
|
||||
isGMEnabled: false, // 明确设置 GM 模式默认为 false
|
||||
lastVersionCheckTime: 0 // 最后一次检查版本的时间戳,默认为0
|
||||
} as Player,
|
||||
currentPlanetId: '',
|
||||
isDark: '',
|
||||
|
||||
@@ -6,37 +6,38 @@
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
|
||||
/* Light mode colors */
|
||||
--background: oklch(0.99 0 0);
|
||||
--foreground: oklch(0.129 0.042 264.695);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.129 0.042 264.695);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.129 0.042 264.695);
|
||||
--primary: oklch(0.208 0.042 265.755);
|
||||
--primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--secondary: oklch(0.968 0.007 247.896);
|
||||
--secondary-foreground: oklch(0.208 0.042 265.755);
|
||||
--muted: oklch(0.968 0.007 247.896);
|
||||
--muted-foreground: oklch(0.554 0.046 257.417);
|
||||
--accent: oklch(0.968 0.007 247.896);
|
||||
--accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.929 0.013 255.508);
|
||||
--input: oklch(0.929 0.013 255.508);
|
||||
/* Light mode colors - 更护眼的暖色调配色 */
|
||||
--background: oklch(0.95 0.008 85);
|
||||
--foreground: oklch(0.3 0.02 85);
|
||||
--card: oklch(0.97 0.006 85);
|
||||
--card-foreground: oklch(0.3 0.02 85);
|
||||
--popover: oklch(0.97 0.006 85);
|
||||
--popover-foreground: oklch(0.3 0.02 85);
|
||||
--primary: oklch(0.35 0.03 240);
|
||||
--primary-foreground: oklch(0.98 0.005 85);
|
||||
--secondary: oklch(0.92 0.01 85);
|
||||
--secondary-foreground: oklch(0.3 0.02 85);
|
||||
--muted: oklch(0.92 0.01 85);
|
||||
--muted-foreground: oklch(0.5 0.02 85);
|
||||
--accent: oklch(0.92 0.01 85);
|
||||
--accent-foreground: oklch(0.3 0.02 85);
|
||||
--destructive: oklch(0.5 0.15 25);
|
||||
--destructive-foreground: oklch(0.98 0.005 85);
|
||||
--border: oklch(0.86 0.012 85);
|
||||
--input: oklch(0.86 0.012 85);
|
||||
--ring: oklch(0.704 0.04 256.788);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.984 0.003 247.858);
|
||||
--sidebar-foreground: oklch(0.129 0.042 264.695);
|
||||
--sidebar-primary: oklch(0.208 0.042 265.755);
|
||||
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
|
||||
--sidebar-accent: oklch(0.968 0.007 247.896);
|
||||
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
|
||||
--sidebar-border: oklch(0.929 0.013 255.508);
|
||||
--sidebar: oklch(0.96 0.006 85);
|
||||
--sidebar-foreground: oklch(0.3 0.02 85);
|
||||
--sidebar-primary: oklch(0.35 0.03 240);
|
||||
--sidebar-primary-foreground: oklch(0.98 0.005 85);
|
||||
--sidebar-accent: oklch(0.92 0.01 85);
|
||||
--sidebar-accent-foreground: oklch(0.3 0.02 85);
|
||||
--sidebar-border: oklch(0.86 0.012 85);
|
||||
--sidebar-ring: oklch(0.704 0.04 256.788);
|
||||
}
|
||||
|
||||
@@ -94,6 +95,7 @@
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-destructive-foreground: var(--destructive-foreground);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
|
||||
@@ -206,7 +206,8 @@ export const MissionType = {
|
||||
Expedition: 'expedition',
|
||||
HarvestDarkMatter: 'harvestDarkMatter', // 暗物质采集
|
||||
Recycle: 'recycle', // 回收残骸
|
||||
Destroy: 'destroy' // 行星毁灭
|
||||
Destroy: 'destroy', // 行星毁灭
|
||||
MissileAttack: 'missileAttack' // 导弹攻击
|
||||
} as const
|
||||
|
||||
export type MissionType = (typeof MissionType)[keyof typeof MissionType]
|
||||
@@ -284,6 +285,19 @@ export interface FleetMission {
|
||||
giftTargetNpcId?: string // 赠送目标NPC ID
|
||||
}
|
||||
|
||||
// 导弹攻击任务(不使用舰队系统)
|
||||
export interface MissileAttack {
|
||||
id: string
|
||||
playerId: string
|
||||
originPlanetId: string
|
||||
targetPosition: { galaxy: number; system: number; position: number }
|
||||
targetPlanetId?: string
|
||||
missileCount: number // 发射的星际导弹数量
|
||||
launchTime: number
|
||||
arrivalTime: number
|
||||
status: 'flying' | 'arrived' | 'intercepted'
|
||||
}
|
||||
|
||||
// 战斗结果
|
||||
export interface BattleResult {
|
||||
id: string
|
||||
@@ -407,6 +421,11 @@ export interface MissionReport {
|
||||
destroyedPlanetName?: string
|
||||
// 部署任务:部署的舰队
|
||||
deployedFleet?: Partial<Fleet>
|
||||
// 导弹攻击任务:导弹信息
|
||||
missileCount?: number
|
||||
missileHits?: number
|
||||
missileIntercepted?: number
|
||||
defenseLosses?: Partial<Record<DefenseType, number>>
|
||||
}
|
||||
read?: boolean
|
||||
}
|
||||
@@ -533,6 +552,7 @@ export interface Player {
|
||||
officers: Record<OfficerType, Officer>
|
||||
researchQueue: BuildQueueItem[]
|
||||
fleetMissions: FleetMission[]
|
||||
missileAttacks: MissileAttack[] // 导弹攻击任务
|
||||
battleReports: BattleResult[]
|
||||
spyReports: SpyReport[]
|
||||
spiedNotifications: SpiedNotification[] // 被侦查通知
|
||||
@@ -542,6 +562,9 @@ export interface Player {
|
||||
giftNotifications: GiftNotification[] // 礼物通知(等待接受/拒绝)
|
||||
giftRejectedNotifications: GiftRejectedNotification[] // 礼物被拒绝通知
|
||||
points: number // 总积分(每1000资源=1分)
|
||||
isGMEnabled?: boolean // GM模式开关(默认false,通过秘籍激活)
|
||||
lastVersionCheckTime?: number // 最后一次自动检查版本的时间戳(被动检测)
|
||||
lastManualUpdateCheck?: number // 最后一次手动检查更新的时间戳(主动检测)
|
||||
// 外交系统字段
|
||||
diplomaticRelations?: Record<string, DiplomaticRelation> // 玩家对NPC的关系(key: npcId)
|
||||
diplomaticReports?: DiplomaticReport[] // 外交变化报告
|
||||
|
||||
@@ -342,6 +342,7 @@ export const simulateBattle = (
|
||||
} else if (defenderUnits.length === 0) {
|
||||
winner = 'attacker'
|
||||
} else {
|
||||
// OGame原版规则:6回合后双方都有剩余单位时判定为平局
|
||||
winner = 'draw'
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
/**
|
||||
* 格式化数字为英文单位(K, M, B, T, Q)
|
||||
* @param num 数字
|
||||
|
||||
57
src/utils/versionCheck.ts
Normal file
57
src/utils/versionCheck.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import pkg from '../../package.json'
|
||||
|
||||
export interface VersionInfo {
|
||||
version: string
|
||||
releaseNotes: string
|
||||
downloadUrl: string
|
||||
}
|
||||
|
||||
// 检查GitHub最新版本
|
||||
export const checkLatestVersion = async (lastCheckTime: number, updateCheckTime: (time: number) => void): Promise<VersionInfo | null> => {
|
||||
const now = Date.now()
|
||||
const fiveMinutes = 5 * 60 * 1000 // 5分钟
|
||||
|
||||
// 如果距离上次检查不到5分钟,跳过
|
||||
if (now - lastCheckTime < fiveMinutes) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`https://api.github.com/repos/${pkg.author.name}/${pkg.name}/releases/latest`)
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Failed to fetch latest version:', response.status)
|
||||
// 更新检查时间,避免频繁请求失败的API
|
||||
updateCheckTime(now)
|
||||
throw new Error(`Failed to fetch version: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const githubVersion = data.tag_name?.replace(/^v/, '') // 移除开头的 'v' (如 v1.2.0 -> 1.2.0)
|
||||
|
||||
// 更新最后检查时间
|
||||
updateCheckTime(now)
|
||||
|
||||
// 比较版本号
|
||||
if (githubVersion && githubVersion !== pkg.version) {
|
||||
return {
|
||||
version: githubVersion,
|
||||
releaseNotes: data.body || '',
|
||||
downloadUrl: `https://github.com/${pkg.author.name}/${pkg.name}/releases/latest`
|
||||
}
|
||||
}
|
||||
return null
|
||||
} catch (error) {
|
||||
console.error('Error checking version:', error)
|
||||
// 更新检查时间,避免频繁请求失败的API
|
||||
updateCheckTime(now)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否可以进行版本检查(距离上次检查是否超过5分钟)
|
||||
export const canCheckVersion = (lastCheckTime: number): boolean => {
|
||||
const now = Date.now()
|
||||
const fiveMinutes = 5 * 60 * 1000 // 5分钟
|
||||
return now - lastCheckTime >= fiveMinutes
|
||||
}
|
||||
@@ -415,7 +415,9 @@
|
||||
${t('buildingsView.demolishRefund')}:
|
||||
${t('resources.metal')}: ${formatNumber(refund.metal)}
|
||||
${t('resources.crystal')}: ${formatNumber(refund.crystal)}
|
||||
${t('resources.deuterium')}: ${formatNumber(refund.deuterium)}${refund.darkMatter > 0 ? `\n${t('resources.darkMatter')}: ${formatNumber(refund.darkMatter)}` : ''}`
|
||||
${t('resources.deuterium')}: ${formatNumber(refund.deuterium)}${
|
||||
refund.darkMatter > 0 ? `\n${t('resources.darkMatter')}: ${formatNumber(refund.darkMatter)}` : ''
|
||||
}`
|
||||
|
||||
pendingDemolishBuilding.value = buildingType
|
||||
demolishConfirmOpen.value = true
|
||||
|
||||
@@ -5,6 +5,29 @@
|
||||
|
||||
<h1 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6">{{ t('defenseView.title') }}</h1>
|
||||
|
||||
<!-- 导弹容量显示 -->
|
||||
<div v-if="missileSiloCapacity > 0" class="mb-4 sm:mb-6 p-3 sm:p-4 bg-muted/50 rounded-lg border">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm sm:text-base font-medium">{{ t('defenseView.missileCapacity') }}:</div>
|
||||
<div class="text-sm sm:text-base font-bold">
|
||||
<span :class="currentMissileCount > missileSiloCapacity ? 'text-destructive' : 'text-primary'">
|
||||
{{ formatNumber(currentMissileCount) }}
|
||||
</span>
|
||||
<span class="text-muted-foreground mx-1">/</span>
|
||||
<span>{{ formatNumber(missileSiloCapacity) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<div class="w-full bg-background rounded-full h-2.5 sm:h-3 overflow-hidden">
|
||||
<div
|
||||
class="h-full transition-all duration-300"
|
||||
:class="currentMissileCount > missileSiloCapacity ? 'bg-destructive' : 'bg-primary'"
|
||||
:style="{ width: `${Math.min((currentMissileCount / missileSiloCapacity) * 100, 100)}%` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4">
|
||||
<Card v-for="defenseType in Object.values(DefenseType)" :key="defenseType" class="relative">
|
||||
<CardUnlockOverlay :requirements="DEFENSES[defenseType].requirements" />
|
||||
@@ -156,6 +179,7 @@
|
||||
import { formatNumber, getResourceCostColor } from '@/utils/format'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
import * as shipValidation from '@/logic/shipValidation'
|
||||
import * as shipLogic from '@/logic/shipLogic'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const detailDialog = useDetailDialogStore()
|
||||
@@ -163,6 +187,17 @@
|
||||
const { DEFENSES } = useGameConfig()
|
||||
const planet = computed(() => gameStore.currentPlanet)
|
||||
|
||||
// 导弹容量相关计算
|
||||
const missileSiloCapacity = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
return shipLogic.calculateMissileSiloCapacity(planet.value.buildings)
|
||||
})
|
||||
|
||||
const currentMissileCount = computed(() => {
|
||||
if (!planet.value) return 0
|
||||
return shipLogic.calculateCurrentMissileCount(planet.value.defense)
|
||||
})
|
||||
|
||||
// AlertDialog 状态
|
||||
const alertDialogOpen = ref(false)
|
||||
const alertDialogTitle = ref('')
|
||||
@@ -197,11 +232,12 @@
|
||||
}
|
||||
|
||||
const buildDefense = (defenseType: DefenseType, quantity: number): boolean => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const validation = shipValidation.validateDefenseBuild(gameStore.currentPlanet, defenseType, quantity, gameStore.player.technologies)
|
||||
const currentPlanet = gameStore.currentPlanet
|
||||
if (!currentPlanet) return false
|
||||
const validation = shipValidation.validateDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.technologies)
|
||||
if (!validation.valid) return false
|
||||
const queueItem = shipValidation.executeDefenseBuild(gameStore.currentPlanet, defenseType, quantity, gameStore.player.officers)
|
||||
gameStore.currentPlanet.buildQueue.push(queueItem)
|
||||
const queueItem = shipValidation.executeDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.officers)
|
||||
currentPlanet.buildQueue.push(queueItem)
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -12,19 +12,34 @@
|
||||
<TabsList class="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="all">
|
||||
{{ t('diplomacy.tabs.all') }}
|
||||
<Badge variant="secondary" class="ml-2">{{ allNpcs.length }}</Badge>
|
||||
<Badge variant="outline" class="ml-2 bg-blue-100 dark:bg-blue-950 text-blue-700 dark:text-blue-300 border-blue-300 dark:border-blue-700">{{ allNpcs.length }}</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="friendly">
|
||||
{{ t('diplomacy.tabs.friendly') }}
|
||||
<Badge variant="secondary" class="ml-2">{{ friendlyNpcs.length }}</Badge>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="ml-2 bg-green-100 dark:bg-green-950 text-green-700 dark:text-green-300 border-green-300 dark:border-green-700"
|
||||
>
|
||||
{{ friendlyNpcs.length }}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="neutral">
|
||||
{{ t('diplomacy.tabs.neutral') }}
|
||||
<Badge variant="secondary" class="ml-2">{{ neutralNpcs.length }}</Badge>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="ml-2 bg-gray-100 dark:bg-gray-800 text-gray-700 dark:text-gray-300 border-gray-300 dark:border-gray-600"
|
||||
>
|
||||
{{ neutralNpcs.length }}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="hostile">
|
||||
{{ t('diplomacy.tabs.hostile') }}
|
||||
<Badge variant="secondary" class="ml-2">{{ hostileNpcs.length }}</Badge>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="ml-2 bg-red-100 dark:bg-red-950 text-red-700 dark:text-red-300 border-red-300 dark:border-red-700"
|
||||
>
|
||||
{{ hostileNpcs.length }}
|
||||
</Badge>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
@@ -202,7 +217,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
@@ -222,6 +237,52 @@
|
||||
|
||||
const activeTab = ref('all')
|
||||
|
||||
// 检测并生成NPC盟友
|
||||
const initializeNPCAllies = () => {
|
||||
const npcs = npcStore.npcs
|
||||
if (npcs.length < 2) return // 至少需要2个NPC才能生成盟友关系
|
||||
|
||||
npcs.forEach(npc => {
|
||||
// 如果NPC没有盟友列表,初始化为空数组
|
||||
if (!npc.allies) {
|
||||
npc.allies = []
|
||||
}
|
||||
|
||||
// 如果NPC没有盟友,随机生成1-2个盟友
|
||||
if (npc.allies.length === 0) {
|
||||
const otherNpcs = npcs.filter(n => n.id !== npc.id)
|
||||
if (otherNpcs.length === 0) return
|
||||
|
||||
// 随机选择1-2个盟友
|
||||
const allyCount = Math.min(Math.floor(Math.random() * 2) + 1, otherNpcs.length)
|
||||
const shuffled = [...otherNpcs].sort(() => Math.random() - 0.5)
|
||||
const selectedAllies = shuffled.slice(0, allyCount)
|
||||
|
||||
selectedAllies.forEach(ally => {
|
||||
// 添加双向盟友关系
|
||||
if (!npc.allies!.includes(ally.id)) {
|
||||
npc.allies!.push(ally.id)
|
||||
}
|
||||
|
||||
// 确保盟友也有盟友列表
|
||||
if (!ally.allies) {
|
||||
ally.allies = []
|
||||
}
|
||||
|
||||
// 确保双向关系
|
||||
if (!ally.allies.includes(npc.id)) {
|
||||
ally.allies.push(npc.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 组件挂载时初始化NPC盟友
|
||||
onMounted(() => {
|
||||
initializeNPCAllies()
|
||||
})
|
||||
|
||||
// 分页状态
|
||||
const ITEMS_PER_PAGE = 20
|
||||
const currentPage = ref<Record<string, number>>({
|
||||
|
||||
@@ -332,7 +332,7 @@
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||
import { computed, ref, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ShipType, MissionType, BuildingType } from '@/types/game'
|
||||
import type { Fleet, Resources } from '@/types/game'
|
||||
@@ -473,6 +473,13 @@
|
||||
// 是否为赠送模式
|
||||
const isGiftMode = ref(false)
|
||||
|
||||
// 监听目标NPC变化,当目标不再是NPC时自动禁用赠送模式
|
||||
watch(targetNpc, newValue => {
|
||||
if (!newValue && isGiftMode.value) {
|
||||
isGiftMode.value = false
|
||||
}
|
||||
})
|
||||
|
||||
// 计算赠送的预估好感度增加值
|
||||
const calculateGiftReputation = (): number => {
|
||||
return diplomaticLogic.calculateGiftReputationGain(cargo.value)
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
<template>
|
||||
<div class="container mx-auto p-4 sm:p-6 space-y-4 sm:space-y-6">
|
||||
<!-- 无权限时显示404 -->
|
||||
<div v-if="!gameStore.player.isGMEnabled" class="container mx-auto p-4 sm:p-6 flex items-center justify-center min-h-[60vh]">
|
||||
<Empty class="border-0">
|
||||
<EmptyMedia>
|
||||
<div class="text-8xl sm:text-9xl font-bold text-muted-foreground/20">404</div>
|
||||
</EmptyMedia>
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>{{ t('notFound.title') }}</EmptyTitle>
|
||||
<EmptyDescription>{{ t('notFound.description') }}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button @click="goHome" size="lg">
|
||||
<Home class="mr-2 h-4 w-4" />
|
||||
{{ t('notFound.goHome') }}
|
||||
</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
|
||||
<!-- 有权限时显示GM页面 -->
|
||||
<div v-else class="container mx-auto p-4 sm:p-6 space-y-4 sm:space-y-6">
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl sm:text-3xl font-bold">{{ t('gmView.title') }}</h1>
|
||||
<Badge variant="destructive">{{ t('gmView.adminOnly') }}</Badge>
|
||||
@@ -12,7 +32,7 @@
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Select v-model="selectedPlanetId">
|
||||
<SelectTrigger>
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue :placeholder="t('gmView.choosePlanet')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -25,148 +45,144 @@
|
||||
</Card>
|
||||
|
||||
<!-- 标签切换 -->
|
||||
<div v-if="selectedPlanet" class="flex flex-wrap gap-2 border-b">
|
||||
<Button
|
||||
v-for="tab in tabs"
|
||||
:key="tab.value"
|
||||
@click="activeTab = tab.value"
|
||||
:variant="activeTab === tab.value ? 'default' : 'ghost'"
|
||||
class="rounded-b-none"
|
||||
>
|
||||
{{ t(tab.label) }}
|
||||
</Button>
|
||||
</div>
|
||||
<Tabs v-if="selectedPlanet" default-value="resources" class="w-full">
|
||||
<TabsList class="grid w-full" :style="{ gridTemplateColumns: `repeat(${tabs.length}, minmax(0, 1fr))` }">
|
||||
<TabsTrigger v-for="tab in tabs" :key="tab.value" :value="tab.value">
|
||||
{{ t(tab.label) }}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<!-- 资源 -->
|
||||
<div v-if="selectedPlanet && activeTab === 'resources'" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyResources') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.resourcesDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div v-for="resource in resourceTypes" :key="resource" class="space-y-2">
|
||||
<Label>{{ t(`resources.${resource}`) }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="selectedPlanet.resources[resource]" type="number" min="0" class="flex-1" />
|
||||
<Button @click="setResourceAmount(resource, 1000000)" variant="outline" size="sm">+1M</Button>
|
||||
<Button @click="setResourceAmount(resource, 10000000)" variant="outline" size="sm">+10M</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- 建筑 -->
|
||||
<div v-if="selectedPlanet && activeTab === 'buildings'" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyBuildings') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.buildingsDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="building in buildingTypes" :key="building" class="space-y-2">
|
||||
<Label>{{ BUILDINGS[building].name }}</Label>
|
||||
<!-- 资源 -->
|
||||
<TabsContent value="resources" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyResources') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.resourcesDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<div v-for="resource in resourceTypes" :key="resource" class="space-y-2">
|
||||
<Label>{{ t(`resources.${resource}`) }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="selectedPlanet.buildings[building]" type="number" min="0" max="100" class="flex-1" />
|
||||
<Button @click="setBuildingLevel(building, 10)" variant="outline" size="sm">Lv 10</Button>
|
||||
<Button @click="setBuildingLevel(building, 30)" variant="outline" size="sm">Lv 30</Button>
|
||||
<Input v-model.number="selectedPlanet.resources[resource]" type="number" min="0" class="flex-1" />
|
||||
<Button @click="setResourceAmount(resource, 1000000)" variant="outline" size="sm">+1M</Button>
|
||||
<Button @click="setResourceAmount(resource, 10000000)" variant="outline" size="sm">+10M</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 科技 -->
|
||||
<div v-if="activeTab === 'research'" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyResearch') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.researchDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="tech in technologyTypes" :key="tech" class="space-y-2">
|
||||
<Label>{{ TECHNOLOGIES[tech].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="gameStore.player.technologies[tech]" type="number" min="0" max="50" class="flex-1" />
|
||||
<Button @click="setTechnologyLevel(tech, 10)" variant="outline" size="sm">Lv 10</Button>
|
||||
<Button @click="setTechnologyLevel(tech, 20)" variant="outline" size="sm">Lv 20</Button>
|
||||
<!-- 建筑 -->
|
||||
<TabsContent value="buildings" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyBuildings') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.buildingsDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="building in buildingTypes" :key="building" class="space-y-2">
|
||||
<Label>{{ BUILDINGS[building].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="selectedPlanet.buildings[building]" type="number" min="0" max="100" class="flex-1" />
|
||||
<Button @click="setBuildingLevel(building, 10)" variant="outline" size="sm">Lv 10</Button>
|
||||
<Button @click="setBuildingLevel(building, 30)" variant="outline" size="sm">Lv 30</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 舰船 -->
|
||||
<div v-if="selectedPlanet && activeTab === 'ships'" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyShips') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.shipsDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="ship in shipTypes" :key="ship" class="space-y-2">
|
||||
<Label>{{ SHIPS[ship].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="selectedPlanet.fleet[ship]" type="number" min="0" class="flex-1" />
|
||||
<Button @click="setShipCount(ship, 100)" variant="outline" size="sm">+100</Button>
|
||||
<Button @click="setShipCount(ship, 1000)" variant="outline" size="sm">+1K</Button>
|
||||
<!-- 科技 -->
|
||||
<TabsContent value="research" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyResearch') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.researchDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="tech in technologyTypes" :key="tech" class="space-y-2">
|
||||
<Label>{{ TECHNOLOGIES[tech].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="gameStore.player.technologies[tech]" type="number" min="0" max="50" class="flex-1" />
|
||||
<Button @click="setTechnologyLevel(tech, 10)" variant="outline" size="sm">Lv 10</Button>
|
||||
<Button @click="setTechnologyLevel(tech, 20)" variant="outline" size="sm">Lv 20</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 防御 -->
|
||||
<div v-if="selectedPlanet && activeTab === 'defense'" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyDefense') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.defenseDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="defense in defenseTypes" :key="defense" class="space-y-2">
|
||||
<Label>{{ DEFENSES[defense].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="selectedPlanet.defense[defense]" type="number" min="0" class="flex-1" />
|
||||
<Button @click="setDefenseCount(defense, 100)" variant="outline" size="sm">+100</Button>
|
||||
<Button @click="setDefenseCount(defense, 1000)" variant="outline" size="sm">+1K</Button>
|
||||
<!-- 舰船 -->
|
||||
<TabsContent value="ships" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyShips') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.shipsDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="ship in shipTypes" :key="ship" class="space-y-2">
|
||||
<Label>{{ SHIPS[ship].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="selectedPlanet.fleet[ship]" type="number" min="0" class="flex-1" />
|
||||
<Button @click="setShipCount(ship, 100)" variant="outline" size="sm">+100</Button>
|
||||
<Button @click="setShipCount(ship, 1000)" variant="outline" size="sm">+1K</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 军官 -->
|
||||
<div v-if="activeTab === 'officers'" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyOfficers') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.officersDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="officer in officerTypes" :key="officer" class="space-y-2">
|
||||
<Label>{{ OFFICERS[officer].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="officerDays[officer]" type="number" min="0" :placeholder="t('gmView.days')" class="flex-1" />
|
||||
<Button @click="setOfficerDays(officer, 7)" variant="outline" size="sm">7{{ t('gmView.days') }}</Button>
|
||||
<Button @click="setOfficerDays(officer, 30)" variant="outline" size="sm">30{{ t('gmView.days') }}</Button>
|
||||
<Button @click="setOfficerDays(officer, 365)" variant="outline" size="sm">365{{ t('gmView.days') }}</Button>
|
||||
<!-- 防御 -->
|
||||
<TabsContent value="defense" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyDefense') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.defenseDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="defense in defenseTypes" :key="defense" class="space-y-2">
|
||||
<Label>{{ DEFENSES[defense].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="selectedPlanet.defense[defense]" type="number" min="0" class="flex-1" />
|
||||
<Button @click="setDefenseCount(defense, 100)" variant="outline" size="sm">+100</Button>
|
||||
<Button @click="setDefenseCount(defense, 1000)" variant="outline" size="sm">+1K</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
|
||||
<!-- 军官 -->
|
||||
<TabsContent value="officers" class="space-y-4">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('gmView.modifyOfficers') }}</CardTitle>
|
||||
<CardDescription>{{ t('gmView.officersDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div v-for="officer in officerTypes" :key="officer" class="space-y-2">
|
||||
<Label>{{ OFFICERS[officer].name }}</Label>
|
||||
<div class="flex gap-2">
|
||||
<Input v-model.number="officerDays[officer]" type="number" min="0" :placeholder="t('gmView.days')" class="flex-1" />
|
||||
<Button @click="setOfficerDays(officer, 7)" variant="outline" size="sm">7{{ t('gmView.days') }}</Button>
|
||||
<Button @click="setOfficerDays(officer, 30)" variant="outline" size="sm">30{{ t('gmView.days') }}</Button>
|
||||
<Button @click="setOfficerDays(officer, 365)" variant="outline" size="sm">365{{ t('gmView.days') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<!-- NPC测试 -->
|
||||
<Card class="border-primary">
|
||||
@@ -175,30 +191,32 @@
|
||||
<CardDescription>{{ t('gmView.npcTestingDesc') || 'Test NPC spy and attack behavior' }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-3">
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('gmView.selectNPC') || 'Select NPC' }}</Label>
|
||||
<Select v-model="selectedNPCId">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('gmView.chooseNPC') || 'Choose NPC'" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="npc in npcStore.npcs" :key="npc.id" :value="npc.id">{{ npc.name }} ({{ npc.difficulty }})</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('gmView.selectNPC') || 'Select NPC' }}</Label>
|
||||
<Select v-model="selectedNPCId">
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue :placeholder="t('gmView.chooseNPC') || 'Choose NPC'" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="npc in npcStore.npcs" :key="npc.id" :value="npc.id">{{ npc.name }} ({{ npc.difficulty }})</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('gmView.targetPlanet') || 'Target Planet' }}</Label>
|
||||
<Select v-model="targetPlanetIndex">
|
||||
<SelectTrigger>
|
||||
<SelectValue :placeholder="t('gmView.chooseTarget') || 'Choose Target Planet'" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="(planet, index) in gameStore.player.planets" :key="planet.id" :value="index.toString()">
|
||||
{{ planet.name }} ({{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }})
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('gmView.targetPlanet') || 'Target Planet' }}</Label>
|
||||
<Select v-model="targetPlanetIndex">
|
||||
<SelectTrigger class="w-full">
|
||||
<SelectValue :placeholder="t('gmView.chooseTarget') || 'Choose Target Planet'" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="(planet, index) in gameStore.player.planets" :key="planet.id" :value="index.toString()">
|
||||
{{ planet.name }} ({{ planet.position.galaxy }}:{{ planet.position.system }}:{{ planet.position.position }})
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
@@ -231,14 +249,46 @@
|
||||
<CardDescription>{{ t('gmView.dangerZoneDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-2">
|
||||
<Button @click="resetGame" variant="destructive" class="w-full">{{ t('gmView.resetGame') }}</Button>
|
||||
<Button @click="showResetConfirmDialog" variant="destructive" class="w-full">{{ t('gmView.resetGame') }}</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- Reset Game 确认对话框 -->
|
||||
<AlertDialog :open="resetDialogOpen" @update:open="handleResetDialogClose">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{{ t('gmView.resetGame') }}</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{{ t('gmView.resetGameConfirm') }}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel @click="handleResetCancel">{{ t('common.cancel') }}</AlertDialogCancel>
|
||||
<AlertDialogAction @click="confirmResetGame">{{ t('common.confirm') }}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- AlertDialog 提示对话框 -->
|
||||
<AlertDialog :open="alertDialogOpen" @update:open="alertDialogOpen = $event">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{{ alertDialogTitle }}</AlertDialogTitle>
|
||||
<AlertDialogDescription v-if="alertDialogMessage" class="whitespace-pre-line">
|
||||
{{ alertDialogMessage }}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogAction @click="handleAlertConfirm">{{ t('common.confirm') }}</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import { useUniverseStore } from '@/stores/universeStore'
|
||||
@@ -250,21 +300,47 @@
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { BuildingType, TechnologyType, ShipType, DefenseType, OfficerType } from '@/types/game'
|
||||
import * as npcBehaviorLogic from '@/logic/npcBehaviorLogic'
|
||||
import { Home } from 'lucide-vue-next'
|
||||
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const npcStore = useNPCStore()
|
||||
const universeStore = useUniverseStore()
|
||||
const { t } = useI18n()
|
||||
const { BUILDINGS, TECHNOLOGIES, SHIPS, DEFENSES, OFFICERS } = useGameConfig()
|
||||
|
||||
const goHome = () => {
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
const selectedPlanetId = ref<string>(gameStore.player.planets[0]?.id || '')
|
||||
const activeTab = ref<'resources' | 'buildings' | 'research' | 'ships' | 'defense' | 'officers'>('resources')
|
||||
const officerDays = ref<Record<OfficerType, number>>({} as Record<OfficerType, number>)
|
||||
const selectedNPCId = ref<string>(npcStore.npcs[0]?.id || '')
|
||||
const targetPlanetIndex = ref<string>('0')
|
||||
|
||||
// AlertDialog 状态
|
||||
const alertDialogOpen = ref(false)
|
||||
const alertDialogTitle = ref('')
|
||||
const alertDialogMessage = ref('')
|
||||
const alertDialogCallback = ref<(() => void) | null>(null)
|
||||
|
||||
// Reset Dialog 状态
|
||||
const resetDialogOpen = ref(false)
|
||||
|
||||
// 初始化军官天数显示
|
||||
Object.values(OfficerType).forEach(officer => {
|
||||
const officerData = gameStore.player.officers[officer]
|
||||
@@ -354,17 +430,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
const resetGame = () => {
|
||||
if (confirm(t('gmView.resetGameConfirm'))) {
|
||||
// 显示重置游戏确认对话框
|
||||
const showResetConfirmDialog = () => {
|
||||
// 暂停游戏
|
||||
gameStore.isPaused = true
|
||||
resetDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 处理重置对话框关闭
|
||||
const handleResetDialogClose = (open: boolean) => {
|
||||
if (!open) {
|
||||
// 如果对话框关闭,恢复游戏
|
||||
gameStore.isPaused = false
|
||||
}
|
||||
resetDialogOpen.value = open
|
||||
}
|
||||
|
||||
// 取消重置
|
||||
const handleResetCancel = () => {
|
||||
resetDialogOpen.value = false
|
||||
gameStore.isPaused = false
|
||||
}
|
||||
|
||||
// 确认重置游戏
|
||||
const confirmResetGame = () => {
|
||||
gameStore.isPaused = true
|
||||
resetDialogOpen.value = false
|
||||
try {
|
||||
gameStore.player.isGMEnabled = false
|
||||
localStorage.clear()
|
||||
location.reload()
|
||||
} catch (error) {
|
||||
console.error('Failed to reset game:', error)
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
// 显示AlertDialog的辅助函数
|
||||
const showAlert = (title: string, message: string, callback?: () => void) => {
|
||||
alertDialogTitle.value = title
|
||||
alertDialogMessage.value = message
|
||||
alertDialogCallback.value = callback || null
|
||||
alertDialogOpen.value = true
|
||||
}
|
||||
|
||||
// AlertDialog确认处理
|
||||
const handleAlertConfirm = () => {
|
||||
alertDialogOpen.value = false
|
||||
if (alertDialogCallback.value) {
|
||||
alertDialogCallback.value()
|
||||
alertDialogCallback.value = null
|
||||
}
|
||||
}
|
||||
|
||||
// NPC测试函数
|
||||
const testNPCSpy = () => {
|
||||
if (!selectedNPC.value) {
|
||||
alert(t('gmView.selectNPCFirst') || 'Please select an NPC first')
|
||||
showAlert(t('gmView.selectNPCFirst') || 'Please select an NPC first', '')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -376,17 +498,18 @@
|
||||
)
|
||||
|
||||
if (mission) {
|
||||
// 加速任务到5秒后到达
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value, mission.id, 5)
|
||||
alert(`${selectedNPC.value.name} will spy in 5 seconds`)
|
||||
showAlert(t('gmView.npcWillSpyIn5s', { npcName: selectedNPC.value.name }), t('gmView.testSpyMessage'), () => {
|
||||
// 加速任务到5秒后到达(在确认后执行,这样时间更准确)
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value!, mission.id, 5, gameStore.player)
|
||||
})
|
||||
} else {
|
||||
alert(t('gmView.npcNoProbes') || 'NPC does not have spy probes')
|
||||
showAlert(t('gmView.npcNoProbes') || 'NPC does not have spy probes', '')
|
||||
}
|
||||
}
|
||||
|
||||
const testNPCAttack = () => {
|
||||
if (!selectedNPC.value) {
|
||||
alert(t('gmView.selectNPCFirst') || 'Please select an NPC first')
|
||||
showAlert(t('gmView.selectNPCFirst') || 'Please select an NPC first', '')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -398,17 +521,18 @@
|
||||
)
|
||||
|
||||
if (mission) {
|
||||
// 加速任务到5秒后到达
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value, mission.id, 5)
|
||||
alert(`${selectedNPC.value.name} will attack in 5 seconds`)
|
||||
showAlert(t('gmView.npcWillAttackIn5s', { npcName: selectedNPC.value.name }), t('gmView.testAttackMessage'), () => {
|
||||
// 加速任务到5秒后到达(在确认后执行,这样时间更准确)
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value!, mission.id, 5, gameStore.player)
|
||||
})
|
||||
} else {
|
||||
alert(t('gmView.npcNoSpyReport') || 'NPC needs to spy first')
|
||||
showAlert(t('gmView.npcNoSpyReport') || 'NPC needs to spy first', '')
|
||||
}
|
||||
}
|
||||
|
||||
const testNPCSpyAndAttack = () => {
|
||||
if (!selectedNPC.value) {
|
||||
alert(t('gmView.selectNPCFirst') || 'Please select an NPC first')
|
||||
showAlert(t('gmView.selectNPCFirst') || 'Please select an NPC first', '')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -420,36 +544,37 @@
|
||||
)
|
||||
|
||||
if (spyMission && attackMission) {
|
||||
// 加速任务:侦查5秒后到达,攻击10秒后到达
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value, spyMission.id, 5)
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value, attackMission.id, 10)
|
||||
alert(`${selectedNPC.value.name} will spy in 5s and attack in 10s`)
|
||||
showAlert(t('gmView.npcWillSpyAndAttack', { npcName: selectedNPC.value.name }), t('gmView.testSpyAndAttackMessage'), () => {
|
||||
// 加速任务:侦查5秒后到达,攻击10秒后到达(在确认后执行,这样时间更准确)
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value!, spyMission.id, 5, gameStore.player)
|
||||
npcBehaviorLogic.accelerateNPCMission(selectedNPC.value!, attackMission.id, 10, gameStore.player)
|
||||
})
|
||||
} else {
|
||||
alert(t('gmView.npcMissionFailed') || 'Failed to create missions')
|
||||
showAlert(t('gmView.npcMissionFailed') || 'Failed to create missions', '')
|
||||
}
|
||||
}
|
||||
|
||||
const accelerateAllMissions = () => {
|
||||
if (!selectedNPC.value) {
|
||||
alert(t('gmView.selectNPCFirst') || 'Please select an NPC first')
|
||||
showAlert(t('gmView.selectNPCFirst') || 'Please select an NPC first', '')
|
||||
return
|
||||
}
|
||||
|
||||
const count = npcBehaviorLogic.accelerateAllNPCMissions(selectedNPC.value, 5)
|
||||
alert(`Accelerated ${count} missions to 5 seconds`)
|
||||
const count = npcBehaviorLogic.accelerateAllNPCMissions(selectedNPC.value, 5, gameStore.player)
|
||||
showAlert(t('gmView.acceleratedMissions', { count }), '')
|
||||
}
|
||||
|
||||
// 初始化NPC舰队
|
||||
const initializeNPCFleet = () => {
|
||||
if (!selectedNPC.value) {
|
||||
alert(t('gmView.selectNPCFirst') || 'Please select an NPC first')
|
||||
showAlert(t('gmView.selectNPCFirst') || 'Please select an NPC first', '')
|
||||
return
|
||||
}
|
||||
|
||||
// 给NPC的第一个星球添加基础舰队
|
||||
const npcPlanet = selectedNPC.value.planets[0]
|
||||
if (!npcPlanet) {
|
||||
alert('NPC has no planets')
|
||||
showAlert(t('gmView.npcNoPlanets'), '')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -465,8 +590,6 @@
|
||||
npcPlanet.fleet[ShipType.Destroyer] = (npcPlanet.fleet[ShipType.Destroyer] || 0) + 30
|
||||
npcPlanet.fleet[ShipType.Battlecruiser] = (npcPlanet.fleet[ShipType.Battlecruiser] || 0) + 20
|
||||
|
||||
alert(
|
||||
`${selectedNPC.value.name} fleet initialized:\n- 100 Spy Probes\n- 500 Light Fighters\n- 300 Heavy Fighters\n- 200 Cruisers\n- 100 Battleships\n- 50 Bombers\n- 30 Destroyers\n- 20 Battlecruisers`
|
||||
)
|
||||
showAlert(t('gmView.npcFleetInitialized', { npcName: selectedNPC.value.name }), t('gmView.npcFleetDetails'))
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -162,7 +162,7 @@
|
||||
<div
|
||||
v-for="slot in systemSlots"
|
||||
:key="slot.position"
|
||||
class="flex items-center gap-2 sm:gap-4 p-2 sm:p-3 border rounded-lg hover:bg-muted/50 transition-colors"
|
||||
class="flex flex-col sm:flex-row sm:items-center gap-2 sm:gap-4 p-2 sm:p-3 border rounded-lg hover:bg-muted/50 transition-colors"
|
||||
:class="{
|
||||
// 空位置
|
||||
'bg-muted/30': !slot.planet,
|
||||
@@ -192,38 +192,81 @@
|
||||
(!getRelation(slot.planet) || getRelation(slot.planet)?.status === RelationStatus.Neutral)
|
||||
}"
|
||||
>
|
||||
<!-- 位置编号 -->
|
||||
<div class="w-8 sm:w-12 text-center">
|
||||
<Badge variant="outline" class="text-xs sm:text-sm">{{ slot.position }}</Badge>
|
||||
</div>
|
||||
|
||||
<!-- 星球信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div v-if="slot.planet" class="space-y-1">
|
||||
<!-- 移动端:垂直布局 / PC端:水平布局 -->
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2">
|
||||
<!-- 星球名称和坐标 -->
|
||||
<div class="flex items-baseline gap-1.5 min-w-0">
|
||||
<h3 class="font-semibold text-sm sm:text-base truncate">{{ slot.planet.name }}</h3>
|
||||
<span class="text-xs text-muted-foreground whitespace-nowrap flex-shrink-0 sm:hidden">
|
||||
[{{ slot.planet.position.galaxy }}:{{ slot.planet.position.system }}:{{ slot.planet.position.position }}]
|
||||
</span>
|
||||
<!-- 移动端布局 -->
|
||||
<div class="sm:hidden w-full space-y-2">
|
||||
<!-- 第一行:位置编号 + 星球信息(名称、坐标、状态、残骸) -->
|
||||
<div class="flex items-start gap-2 w-full">
|
||||
<!-- 位置编号 -->
|
||||
<div class="w-8 text-center flex-shrink-0">
|
||||
<Badge variant="outline" class="text-xs">{{ slot.position }}</Badge>
|
||||
</div>
|
||||
<!-- 星球信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div v-if="slot.planet" class="space-y-1">
|
||||
<!-- 第一行:名称、坐标、状态、残骸 -->
|
||||
<div class="flex items-center gap-1.5 min-w-0 flex-wrap">
|
||||
<h3 class="font-semibold text-sm truncate">{{ slot.planet.name }}</h3>
|
||||
<span class="text-xs text-muted-foreground whitespace-nowrap flex-shrink-0">
|
||||
[{{ slot.planet.position.galaxy }}:{{ slot.planet.position.system }}:{{ slot.planet.position.position }}]
|
||||
</span>
|
||||
<Badge v-if="isMyPlanet(slot.planet)" variant="default" class="text-xs flex-shrink-0">
|
||||
{{ t('galaxyView.mine') }}
|
||||
</Badge>
|
||||
<Badge v-else :variant="getRelationBadgeVariant(slot.planet)" class="text-xs flex-shrink-0">
|
||||
{{ getRelationStatusText(slot.planet) }}
|
||||
</Badge>
|
||||
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<PopoverTrigger as-child>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-amber-50 dark:hover:bg-amber-950/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 gap-1"
|
||||
>
|
||||
<Recycle class="h-3 w-3" />
|
||||
</Badge>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-3" side="top" align="start">
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-semibold text-amber-700 dark:text-amber-400">{{ t('galaxyView.debrisField') }}</p>
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.metal) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.crystal) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<!-- 第二行:好感度 -->
|
||||
<div v-if="!isMyPlanet(slot.planet) && getReputationValue(slot.planet) !== null" class="text-xs">
|
||||
<span class="text-muted-foreground">{{ t('diplomacy.reputation') }}:</span>
|
||||
<span class="ml-1 font-semibold" :class="getReputationColor(getReputationValue(slot.planet))">
|
||||
{{ getReputationValue(slot.planet)! > 0 ? '+' : '' }}{{ getReputationValue(slot.planet) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 徽章组 -->
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<Badge v-if="isMyPlanet(slot.planet)" variant="default" class="text-xs">{{ t('galaxyView.mine') }}</Badge>
|
||||
<Badge v-else :variant="getRelationBadgeVariant(slot.planet)" class="text-xs">
|
||||
{{ getRelationStatusText(slot.planet) }}
|
||||
</Badge>
|
||||
<!-- 残骸场徽章 - 紧凑显示 -->
|
||||
<!-- 空位置 -->
|
||||
<div v-else class="space-y-1">
|
||||
<div class="text-sm text-muted-foreground">{{ t('galaxyView.emptySlot') }}</div>
|
||||
<!-- 残骸场徽章 - 空位置时也显示 -->
|
||||
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<PopoverTrigger as-child>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-amber-50 dark:hover:bg-amber-950/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 gap-1"
|
||||
class="text-xs cursor-pointer hover:bg-amber-50 dark:hover:bg-amber-950/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 gap-1 inline-flex"
|
||||
>
|
||||
<Recycle class="h-3 w-3" />
|
||||
<span class="hidden sm:inline">{{ t('galaxyView.debris') }}</span>
|
||||
<span>{{ t('galaxyView.debris') }}</span>
|
||||
</Badge>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-3" side="top" align="start">
|
||||
@@ -250,59 +293,195 @@
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PC端:显示坐标 -->
|
||||
<p class="text-xs text-muted-foreground hidden sm:block">
|
||||
[{{ slot.planet.position.galaxy }}:{{ slot.planet.position.system }}:{{ slot.planet.position.position }}]
|
||||
</p>
|
||||
<!-- 好感度显示(仅NPC星球) -->
|
||||
<div v-if="!isMyPlanet(slot.planet) && getReputationValue(slot.planet) !== null" class="text-xs">
|
||||
<span class="text-muted-foreground">{{ t('diplomacy.reputation') }}:</span>
|
||||
<span class="ml-1 font-semibold" :class="getReputationColor(getReputationValue(slot.planet))">
|
||||
{{ getReputationValue(slot.planet)! > 0 ? '+' : '' }}{{ getReputationValue(slot.planet) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 空位置 -->
|
||||
<div v-else class="space-y-1">
|
||||
<div class="text-sm text-muted-foreground">{{ t('galaxyView.emptySlot') }}</div>
|
||||
<!-- 残骸场徽章 - 空位置时也显示 -->
|
||||
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<PopoverTrigger as-child>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-amber-50 dark:hover:bg-amber-950/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 gap-1 inline-flex"
|
||||
>
|
||||
<Recycle class="h-3 w-3" />
|
||||
<span>{{ t('galaxyView.debris') }}</span>
|
||||
</Badge>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-3" side="top" align="start">
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-semibold text-amber-700 dark:text-amber-400">{{ t('galaxyView.debrisField') }}</p>
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.metal) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.crystal) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<!-- 第三行:操作按钮 -->
|
||||
<div class="flex gap-1 pl-10">
|
||||
<TooltipProvider :delay-duration="300">
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPlanetActions(slot.planet, 'spy')" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Eye class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.scout') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPlanetActions(slot.planet, 'attack')" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Sword class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.attack') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && hasInterplanetaryMissiles">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showMissileAttackDialog(slot.planet)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Bomb class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.missileAttack') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && getPlanetNPC(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPlanetActions(slot.planet, 'gift')" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Gift class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.sendGift') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="!slot.planet">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPlanetActions(null, 'colonize', slot.position)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Rocket class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.colonize') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && isMyPlanet(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="switchToPlanet(slot.planet.id)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Home class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.switch') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button
|
||||
@click="showPlanetActions(slot.planet, 'recycle', slot.position)"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
class="h-8 w-8 p-0"
|
||||
>
|
||||
<Recycle class="h-3 w-3" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.recycle') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex gap-1 sm:gap-2 flex-shrink-0">
|
||||
<!-- PC端布局:位置编号 + 星球信息(水平) -->
|
||||
<div class="hidden sm:flex items-center gap-4 flex-1 min-w-0">
|
||||
<!-- 位置编号 -->
|
||||
<div class="w-12 text-center flex-shrink-0">
|
||||
<Badge variant="outline" class="text-sm">{{ slot.position }}</Badge>
|
||||
</div>
|
||||
|
||||
<!-- 星球信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div v-if="slot.planet" class="space-y-1">
|
||||
<!-- PC端:标题和徽章 -->
|
||||
<div class="flex items-center gap-2 flex-wrap">
|
||||
<h3 class="font-semibold text-base">{{ slot.planet.name }}</h3>
|
||||
<Badge v-if="isMyPlanet(slot.planet)" variant="default" class="text-xs">{{ t('galaxyView.mine') }}</Badge>
|
||||
<Badge v-else :variant="getRelationBadgeVariant(slot.planet)" class="text-xs">
|
||||
{{ getRelationStatusText(slot.planet) }}
|
||||
</Badge>
|
||||
<!-- 残骸场徽章 -->
|
||||
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<PopoverTrigger as-child>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-amber-50 dark:hover:bg-amber-950/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 gap-1"
|
||||
>
|
||||
<Recycle class="h-3 w-3" />
|
||||
<span>{{ t('galaxyView.debris') }}</span>
|
||||
</Badge>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-3" side="top" align="start">
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-semibold text-amber-700 dark:text-amber-400">{{ t('galaxyView.debrisField') }}</p>
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.metal) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.crystal) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
<!-- PC端:坐标 -->
|
||||
<p class="text-xs text-muted-foreground">
|
||||
[{{ slot.planet.position.galaxy }}:{{ slot.planet.position.system }}:{{ slot.planet.position.position }}]
|
||||
</p>
|
||||
<!-- PC端:好感度显示(仅NPC星球) -->
|
||||
<div v-if="!isMyPlanet(slot.planet) && getReputationValue(slot.planet) !== null" class="text-xs">
|
||||
<span class="text-muted-foreground">{{ t('diplomacy.reputation') }}:</span>
|
||||
<span class="ml-1 font-semibold" :class="getReputationColor(getReputationValue(slot.planet))">
|
||||
{{ getReputationValue(slot.planet)! > 0 ? '+' : '' }}{{ getReputationValue(slot.planet) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 空位置 -->
|
||||
<div v-else class="space-y-1">
|
||||
<div class="text-sm text-muted-foreground">{{ t('galaxyView.emptySlot') }}</div>
|
||||
<!-- 残骸场徽章 - 空位置时也显示 -->
|
||||
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
|
||||
<PopoverTrigger as-child>
|
||||
<Badge
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-amber-50 dark:hover:bg-amber-950/30 border-amber-300 dark:border-amber-700 text-amber-700 dark:text-amber-400 gap-1 inline-flex"
|
||||
>
|
||||
<Recycle class="h-3 w-3" />
|
||||
<span>{{ t('galaxyView.debris') }}</span>
|
||||
</Badge>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-auto p-3" side="top" align="start">
|
||||
<div class="space-y-2">
|
||||
<p class="text-xs font-semibold text-amber-700 dark:text-amber-400">{{ t('galaxyView.debrisField') }}</p>
|
||||
<div class="space-y-1 text-xs">
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="metal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.metal) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<ResourceIcon type="crystal" size="sm" />
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium">
|
||||
{{ formatNumber(getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)!.resources.crystal) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 (PC端) -->
|
||||
<div class="hidden sm:flex gap-1 sm:gap-2 flex-shrink-0">
|
||||
<TooltipProvider :delay-duration="300">
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
@@ -324,6 +503,16 @@
|
||||
<p>{{ t('galaxyView.attack') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && hasInterplanetaryMissiles">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showMissileAttackDialog(slot.planet)" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
<Bomb class="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{{ t('galaxyView.missileAttack') }}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && getPlanetNPC(slot.planet)">
|
||||
<TooltipTrigger as-child>
|
||||
<Button @click="showPlanetActions(slot.planet, 'gift')" variant="outline" size="sm" class="h-8 w-8 p-0">
|
||||
@@ -376,6 +565,60 @@
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<!-- 导弹攻击对话框 -->
|
||||
<Dialog :open="missileDialogOpen" @update:open="missileDialogOpen = $event">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t('galaxyView.missileAttackTitle') }}</DialogTitle>
|
||||
<DialogDescription v-if="missileTargetPlanet">
|
||||
{{
|
||||
t('galaxyView.missileAttackMessage').replace(
|
||||
'{coordinates}',
|
||||
`${missileTargetPlanet.position.galaxy}:${missileTargetPlanet.position.system}:${missileTargetPlanet.position.position}`
|
||||
)
|
||||
}}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="gameStore.currentPlanet && missileTargetPlanet" class="space-y-4">
|
||||
<!-- 导弹数量输入 -->
|
||||
<div class="space-y-2">
|
||||
<Label>{{ t('galaxyView.missileCount') }}</Label>
|
||||
<Input
|
||||
v-model.number="missileCount"
|
||||
type="number"
|
||||
min="1"
|
||||
:max="gameStore.currentPlanet.defense['interplanetaryMissile'] || 0"
|
||||
/>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ t('galaxyView.availableMissiles') }}: {{ gameStore.currentPlanet.defense['interplanetaryMissile'] || 0 }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 射程和距离信息 -->
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.missileRange') }}:</span>
|
||||
<span>{{ calculateMissileRange() }} {{ t('galaxyView.systems') }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.distance') }}:</span>
|
||||
<span>{{ calculateDistance(missileTargetPlanet) }} {{ t('galaxyView.systems') }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-muted-foreground">{{ t('galaxyView.flightTime') }}:</span>
|
||||
<span>{{ formatFlightTime(calculateDistance(missileTargetPlanet)) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="missileDialogOpen = false">{{ t('galaxyView.cancel') }}</Button>
|
||||
<Button @click="launchMissileAttack">{{ t('galaxyView.launchMissile') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- 快速派遣对话框 -->
|
||||
<AlertDialog :open="alertDialogOpen" @update:open="alertDialogOpen = $event">
|
||||
<AlertDialogContent>
|
||||
@@ -405,10 +648,12 @@
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '@/components/ui/select'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -420,7 +665,7 @@
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import ResourceIcon from '@/components/ResourceIcon.vue'
|
||||
import { Home, Eye, Sword, Rocket, Recycle, Gift, Globe } from 'lucide-vue-next'
|
||||
import { Home, Eye, Sword, Rocket, Recycle, Gift, Globe, Bomb } from 'lucide-vue-next'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import * as gameLogic from '@/logic/gameLogic'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
@@ -438,6 +683,11 @@
|
||||
const alertDialogMessage = ref('')
|
||||
const alertDialogConfirmAction = ref<(() => void) | null>(null)
|
||||
|
||||
// 导弹攻击对话框状态
|
||||
const missileDialogOpen = ref(false)
|
||||
const missileTargetPlanet = ref<Planet | null>(null)
|
||||
const missileCount = ref(1)
|
||||
|
||||
const selectedGalaxy = ref(1)
|
||||
const selectedSystem = ref(1)
|
||||
const currentGalaxy = ref(1)
|
||||
@@ -465,6 +715,12 @@
|
||||
return gameStore.player.planets.filter(p => !p.isMoon)
|
||||
})
|
||||
|
||||
// 检查当前星球是否有星际导弹
|
||||
const hasInterplanetaryMissiles = computed(() => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
return (gameStore.currentPlanet.defense['interplanetaryMissile'] || 0) > 0
|
||||
})
|
||||
|
||||
// 判断当前是否在母星所在星系
|
||||
const isInHomePlanetSystem = computed(() => {
|
||||
if (!homePlanet.value) return false
|
||||
@@ -680,4 +936,80 @@
|
||||
}
|
||||
alertDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 显示导弹攻击对话框
|
||||
const showMissileAttackDialog = (planet: Planet) => {
|
||||
missileTargetPlanet.value = planet
|
||||
missileCount.value = 1
|
||||
missileDialogOpen.value = true
|
||||
}
|
||||
|
||||
// 执行导弹攻击
|
||||
const launchMissileAttack = () => {
|
||||
if (!missileTargetPlanet.value || !gameStore.currentPlanet) return
|
||||
|
||||
// 导入missileLogic进行验证和执行
|
||||
import('@/logic/missileLogic').then(missileLogic => {
|
||||
const validation = missileLogic.validateMissileLaunch(
|
||||
gameStore.currentPlanet!,
|
||||
missileTargetPlanet.value!.position,
|
||||
missileCount.value,
|
||||
gameStore.player.technologies
|
||||
)
|
||||
|
||||
if (!validation.valid) {
|
||||
alertDialogTitle.value = t('errors.launchFailed')
|
||||
alertDialogMessage.value = t(validation.reason || 'errors.unknown')
|
||||
alertDialogOpen.value = true
|
||||
return
|
||||
}
|
||||
|
||||
// 创建导弹攻击任务
|
||||
const missileAttack = missileLogic.createMissileAttack(
|
||||
gameStore.player.id,
|
||||
gameStore.currentPlanet!,
|
||||
missileTargetPlanet.value!.position,
|
||||
missileTargetPlanet.value!.id,
|
||||
missileCount.value
|
||||
)
|
||||
|
||||
// 扣除导弹
|
||||
missileLogic.executeMissileLaunch(gameStore.currentPlanet!, missileCount.value)
|
||||
|
||||
// 添加到玩家的导弹攻击列表
|
||||
gameStore.player.missileAttacks.push(missileAttack)
|
||||
|
||||
// 关闭对话框
|
||||
missileDialogOpen.value = false
|
||||
|
||||
// 显示成功提示
|
||||
alertDialogTitle.value = t('common.success')
|
||||
alertDialogMessage.value = t('galaxyView.missileLaunched')
|
||||
alertDialogOpen.value = true
|
||||
})
|
||||
}
|
||||
|
||||
// 计算导弹射程
|
||||
const calculateMissileRange = () => {
|
||||
const impulseDriveLevel = gameStore.player.technologies['impulseDrive'] || 0
|
||||
if (impulseDriveLevel === 0) return 0
|
||||
return 5 * impulseDriveLevel - 1
|
||||
}
|
||||
|
||||
// 计算到目标的距离
|
||||
const calculateDistance = (target: Planet) => {
|
||||
if (!gameStore.currentPlanet) return 0
|
||||
const from = gameStore.currentPlanet.position
|
||||
const to = target.position
|
||||
if (from.galaxy !== to.galaxy) return Infinity
|
||||
return Math.abs(from.system - to.system)
|
||||
}
|
||||
|
||||
// 格式化飞行时间
|
||||
const formatFlightTime = (distance: number) => {
|
||||
const seconds = 30 + distance * 60
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const secs = seconds % 60
|
||||
return `${minutes}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -580,7 +580,8 @@
|
||||
[MissionType.Colonize]: t('fleetView.colonize'),
|
||||
[MissionType.Deploy]: t('fleetView.deploy'),
|
||||
[MissionType.Recycle]: t('fleetView.recycle'),
|
||||
[MissionType.Destroy]: t('fleetView.destroy')
|
||||
[MissionType.Destroy]: t('fleetView.destroy'),
|
||||
[MissionType.MissileAttack]: t('galaxyView.missileAttack')
|
||||
}
|
||||
return typeMap[missionType] || missionType
|
||||
}
|
||||
@@ -615,7 +616,7 @@
|
||||
const acceptGift = (gift: GiftNotification) => {
|
||||
const npc = npcStore.npcs.find(n => n.id === gift.fromNpcId)
|
||||
if (npc) {
|
||||
diplomaticLogic.acceptNPCGift(gameStore.player, npc, gift)
|
||||
diplomaticLogic.acceptNPCGift(gameStore.player, npc, gift, gameStore.locale)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,7 +624,7 @@
|
||||
const rejectGift = (gift: GiftNotification) => {
|
||||
const npc = npcStore.npcs.find(n => n.id === gift.fromNpcId)
|
||||
if (npc) {
|
||||
diplomaticLogic.rejectNPCGift(gameStore.player, npc, gift)
|
||||
diplomaticLogic.rejectNPCGift(gameStore.player, npc, gift, gameStore.locale)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
34
src/views/NotFoundView.vue
Normal file
34
src/views/NotFoundView.vue
Normal file
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<div class="container mx-auto p-4 sm:p-6 flex items-center justify-center min-h-[60vh]">
|
||||
<Empty class="border-0">
|
||||
<EmptyMedia>
|
||||
<div class="text-8xl sm:text-9xl font-bold text-muted-foreground/20">404</div>
|
||||
</EmptyMedia>
|
||||
<EmptyHeader>
|
||||
<EmptyTitle>{{ t('notFound.title') }}</EmptyTitle>
|
||||
<EmptyDescription>{{ t('notFound.description') }}</EmptyDescription>
|
||||
</EmptyHeader>
|
||||
<EmptyContent>
|
||||
<Button @click="goHome" size="lg">
|
||||
<Home class="mr-2 h-4 w-4" />
|
||||
{{ t('notFound.goHome') }}
|
||||
</Button>
|
||||
</EmptyContent>
|
||||
</Empty>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from '@/components/ui/empty'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Home } from 'lucide-vue-next'
|
||||
|
||||
const router = useRouter()
|
||||
const { t } = useI18n()
|
||||
|
||||
const goHome = () => {
|
||||
router.push('/')
|
||||
}
|
||||
</script>
|
||||
@@ -59,6 +59,22 @@
|
||||
<CardDescription>{{ t('settings.gameSettingsDesc') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent class="space-y-4">
|
||||
<!-- 游戏倍率 -->
|
||||
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between p-4 border rounded-lg gap-3">
|
||||
<div class="space-y-1 flex-1">
|
||||
<h3 class="font-medium">{{ t('settings.gameSpeed') }}</h3>
|
||||
<p class="text-sm text-muted-foreground">{{ t('settings.gameSpeedDesc') }}</p>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-4 w-full sm:w-auto">
|
||||
<div class="flex items-center gap-2 flex-1 sm:flex-initial">
|
||||
<Button @click="decreaseSpeed" variant="outline" size="sm" :disabled="gameStore.gameSpeed <= 0.5">-</Button>
|
||||
<span class="min-w-[60px] text-center font-medium">{{ gameStore.gameSpeed || 1 }}x</span>
|
||||
<Button @click="increaseSpeed" variant="outline" size="sm" :disabled="gameStore.gameSpeed >= 10">+</Button>
|
||||
</div>
|
||||
<Button @click="resetSpeed" variant="ghost" size="sm">{{ t('settings.reset') }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 游戏暂停 -->
|
||||
<div class="flex items-center justify-between p-4 border rounded-lg">
|
||||
<div class="space-y-1">
|
||||
@@ -88,6 +104,15 @@
|
||||
<span class="text-muted-foreground">{{ t('settings.buildDate') }}:</span>
|
||||
<span class="font-medium">{{ pkg.buildDate }}</span>
|
||||
</div>
|
||||
<!-- 检查更新按钮 -->
|
||||
<div class="pt-2">
|
||||
<Button @click="handleCheckVersion" variant="outline" size="sm" :disabled="isCheckingVersion || !canCheck" class="w-full">
|
||||
<RefreshCw class="mr-2 h-4 w-4" :class="{ 'animate-spin': isCheckingVersion }" />
|
||||
<template v-if="isCheckingVersion">{{ t('settings.checking') }}</template>
|
||||
<template v-else-if="!canCheck && cooldownTime">{{ cooldownTime }}</template>
|
||||
<template v-else>{{ t('settings.checkUpdate') }}</template>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 社区链接 -->
|
||||
@@ -127,11 +152,14 @@
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- 更新对话框 -->
|
||||
<UpdateDialog v-model:open="showUpdateDialog" :version-info="updateInfo" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'
|
||||
@@ -146,23 +174,64 @@
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import { Download, Upload, Trash2, ExternalLink, MessagesSquare, Play, Pause } from 'lucide-vue-next'
|
||||
import { Download, Upload, Trash2, ExternalLink, MessagesSquare, Play, Pause, RefreshCw } from 'lucide-vue-next'
|
||||
import { saveAs } from 'file-saver'
|
||||
import { toast } from 'vue-sonner'
|
||||
import pkg from '../../package.json'
|
||||
import 'vue-sonner/style.css'
|
||||
import { checkLatestVersion, canCheckVersion } from '@/utils/versionCheck'
|
||||
import type { VersionInfo } from '@/utils/versionCheck'
|
||||
import UpdateDialog from '@/components/UpdateDialog.vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
const gameStore = useGameStore()
|
||||
|
||||
const fileInputRef = ref<HTMLInputElement>()
|
||||
const isExporting = ref(false)
|
||||
const isCheckingVersion = ref(false)
|
||||
const cooldownTime = ref('')
|
||||
|
||||
const showConfirmDialog = ref(false)
|
||||
const confirmTitle = ref('')
|
||||
const confirmMessage = ref('')
|
||||
let confirmCallback: (() => void) | null = null
|
||||
|
||||
// 计算是否可以检查版本(主动检测:5分钟内不能重复检查)
|
||||
const canCheck = computed(() => canCheckVersion(gameStore.player.lastManualUpdateCheck || 0))
|
||||
|
||||
// 计算剩余冷却时间
|
||||
const updateCooldownTime = () => {
|
||||
if (canCheck.value) {
|
||||
cooldownTime.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const lastCheck = gameStore.player.lastManualUpdateCheck || 0
|
||||
const now = Date.now()
|
||||
const fiveMinutes = 5 * 60 * 1000
|
||||
const timePassed = now - lastCheck
|
||||
const timeRemaining = fiveMinutes - timePassed
|
||||
|
||||
if (timeRemaining <= 0) {
|
||||
cooldownTime.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
const minutes = Math.floor(timeRemaining / 60000)
|
||||
const seconds = Math.floor((timeRemaining % 60000) / 1000)
|
||||
cooldownTime.value = `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
|
||||
}
|
||||
|
||||
// 每秒更新倒计时
|
||||
let cooldownInterval: ReturnType<typeof setInterval> | null = null
|
||||
onMounted(() => {
|
||||
updateCooldownTime()
|
||||
cooldownInterval = setInterval(updateCooldownTime, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (cooldownInterval) clearInterval(cooldownInterval)
|
||||
})
|
||||
|
||||
const openGithub = () => {
|
||||
window.open(`https://github.com/${pkg.author.name}/${pkg.name}`, '_blank')
|
||||
}
|
||||
@@ -171,6 +240,32 @@
|
||||
window.open(`https://qm.qq.com/q/${pkg.id}`, '_blank')
|
||||
}
|
||||
|
||||
// 手动检查版本
|
||||
const showUpdateDialog = ref(false)
|
||||
const updateInfo = ref<VersionInfo | null>(null)
|
||||
|
||||
const handleCheckVersion = async () => {
|
||||
if (isCheckingVersion.value || !canCheck.value) return
|
||||
|
||||
isCheckingVersion.value = true
|
||||
try {
|
||||
const versionInfo = await checkLatestVersion(gameStore.player.lastManualUpdateCheck || 0, (time: number) => {
|
||||
gameStore.player.lastManualUpdateCheck = time
|
||||
})
|
||||
if (versionInfo) {
|
||||
updateInfo.value = versionInfo
|
||||
showUpdateDialog.value = true
|
||||
} else {
|
||||
toast.success(t('settings.upToDate'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check for updates:', error)
|
||||
toast.error(t('settings.checkUpdateFailed'))
|
||||
} finally {
|
||||
isCheckingVersion.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 导出数据(包含游戏数据和地图数据)
|
||||
const handleExport = async () => {
|
||||
try {
|
||||
@@ -281,10 +376,37 @@
|
||||
}
|
||||
|
||||
const clearData = () => {
|
||||
// 清除localStorage
|
||||
localStorage.clear()
|
||||
// 重新加载页面
|
||||
window.location.reload()
|
||||
gameStore.isPaused = true
|
||||
try {
|
||||
localStorage.clear()
|
||||
window.location.reload()
|
||||
} catch (error) {
|
||||
console.error('Failed to clear data:', error)
|
||||
// 即使出错也尝试重新加载
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
// 增加游戏倍率
|
||||
const increaseSpeed = () => {
|
||||
if (gameStore.gameSpeed < 10) {
|
||||
gameStore.gameSpeed = Math.min(10, gameStore.gameSpeed + 0.5)
|
||||
toast.success(t('settings.speedChanged', { speed: gameStore.gameSpeed }))
|
||||
}
|
||||
}
|
||||
|
||||
// 减少游戏倍率
|
||||
const decreaseSpeed = () => {
|
||||
if (gameStore.gameSpeed > 0.5) {
|
||||
gameStore.gameSpeed = Math.max(0.5, gameStore.gameSpeed - 0.5)
|
||||
toast.success(t('settings.speedChanged', { speed: gameStore.gameSpeed }))
|
||||
}
|
||||
}
|
||||
|
||||
// 重置游戏倍率
|
||||
const resetSpeed = () => {
|
||||
gameStore.gameSpeed = 1
|
||||
toast.success(t('settings.speedReset'))
|
||||
}
|
||||
|
||||
// 切换游戏暂停状态
|
||||
|
||||
@@ -304,6 +304,7 @@ const simulateBattle = (attacker: BattleSideData, defender: BattleSideData, maxR
|
||||
} else if (defenderUnits.length === 0) {
|
||||
winner = 'attacker'
|
||||
} else {
|
||||
// OGame原版规则:6回合后双方都有剩余单位时判定为平局
|
||||
winner = 'draw'
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user