mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
Compare commits
6 Commits
revert-7-m
...
v1.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cfcde0b024 | ||
|
|
053bd24855 | ||
|
|
7d1f36046d | ||
|
|
22ae07de90 | ||
|
|
a76909a2c7 | ||
|
|
8144f305e2 |
2
.github/workflows/github-pages.yml
vendored
2
.github/workflows/github-pages.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Deploy Vue Project
|
||||
name: 构建 Github Pages
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -8,12 +8,8 @@
|
||||
"email": "1962257451@qq.com"
|
||||
},
|
||||
"private": true,
|
||||
"version": "1.2.5",
|
||||
<<<<<<< Updated upstream
|
||||
"buildDate": "2025/12/15 21:21:23",
|
||||
=======
|
||||
"buildDate": "2025/12/15 21:59:38",
|
||||
>>>>>>> Stashed changes
|
||||
"version": "1.3.0",
|
||||
"buildDate": "2025/12/17 21:05:49",
|
||||
"main": "dist-electron/main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
351
src/App.vue
351
src/App.vue
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<SidebarProvider :open="sidebarOpen" @update:open="sidebarOpen = $event">
|
||||
<SidebarProvider :open="sidebarOpen" @update:open="handleSidebarOpenChange">
|
||||
<Sidebar collapsible="icon">
|
||||
<!-- Logo -->
|
||||
<SidebarHeader class="border-b">
|
||||
@@ -17,6 +17,7 @@
|
||||
<Popover>
|
||||
<PopoverTrigger as-child>
|
||||
<Button
|
||||
data-tutorial="planet-selector"
|
||||
variant="outline"
|
||||
class="w-full justify-between h-auto px-3 py-2.5 border-2 hover:bg-accent hover:border-primary transition-colors"
|
||||
>
|
||||
@@ -94,28 +95,35 @@
|
||||
</SidebarGroup>
|
||||
|
||||
<!-- 导航菜单 -->
|
||||
<SidebarGroup>
|
||||
<SidebarGroup data-tutorial="navigation">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem v-for="item in navItems" :key="item.path">
|
||||
<SidebarMenuButton as-child :is-active="$route.path === item.path" :tooltip="item.name.value">
|
||||
<RouterLink :to="item.path">
|
||||
<component :is="item.icon" />
|
||||
<span>{{ item.name.value }}</span>
|
||||
<!-- 未读消息数量 -->
|
||||
<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
|
||||
:data-nav-path="item.path"
|
||||
:is-active="$route.path === item.path"
|
||||
:tooltip="item.name.value"
|
||||
@click="handleNavClick(item.path, $event)"
|
||||
>
|
||||
<component :is="item.icon" />
|
||||
<span>{{ item.name.value }}</span>
|
||||
<!-- 未读消息数量 -->
|
||||
<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>
|
||||
<!-- 未读外交报告数量 -->
|
||||
<SidebarMenuBadge
|
||||
v-if="item.path === '/diplomacy' && unreadDiplomaticReportsCount > 0"
|
||||
class="bg-destructive text-destructive-foreground"
|
||||
>
|
||||
{{ unreadDiplomaticReportsCount }}
|
||||
</SidebarMenuBadge>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
@@ -189,11 +197,14 @@
|
||||
<div class="grid items-center gap-3 sm:gap-6" style="grid-template-columns: auto 1fr auto">
|
||||
<!-- 左侧:汉堡菜单(移动端)/ 占位(PC端) -->
|
||||
<div>
|
||||
<SidebarTrigger class="lg:hidden" />
|
||||
<SidebarTrigger class="lg:hidden" data-tutorial="mobile-menu" />
|
||||
</div>
|
||||
|
||||
<!-- 资源显示 - PC端居中,移动端可折叠 -->
|
||||
<div :class="['flex items-center gap-3 sm:gap-6 justify-center', resourceBarExpanded ? 'hidden' : 'overflow-x-auto']">
|
||||
<div
|
||||
class="resource-bar flex items-center gap-3 sm:gap-6 justify-center"
|
||||
:class="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">
|
||||
@@ -233,15 +244,11 @@
|
||||
<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>
|
||||
<!-- 外交通知 -->
|
||||
<DiplomaticNotifications />
|
||||
|
||||
<!-- 队列通知 -->
|
||||
<QueueNotifications />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -310,70 +317,6 @@
|
||||
@mark-as-read="removeIncomingFleetAlert"
|
||||
/>
|
||||
|
||||
<!-- 建造队列 -->
|
||||
<div
|
||||
v-if="planet && (planet.buildQueue.length > 0 || gameStore.player.researchQueue.length > 0)"
|
||||
class="bg-card border-b px-4 sm:px-6 py-4.5"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<!-- 建造队列 -->
|
||||
<div v-for="item in planet.buildQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div class="h-2 w-2 rounded-full bg-green-500 animate-pulse flex-shrink-0" />
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground hidden sm:inline flex-shrink-0 text-[10px] sm:text-xs">
|
||||
<template v-if="item.type === 'ship' || item.type === 'defense'">
|
||||
→ {{ t('queue.quantity') }} {{ item.quantity }}
|
||||
</template>
|
||||
<template v-else>→ {{ t('queue.level') }} {{ item.targetLevel }}</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">
|
||||
{{ formatTime(getRemainingTime(item)) }}
|
||||
</span>
|
||||
<Button
|
||||
@click="handleCancelBuild(item.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs"
|
||||
>
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
<!-- 研究队列 -->
|
||||
<div v-for="item in gameStore.player.researchQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse flex-shrink-0" />
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground hidden sm:inline flex-shrink-0 text-[10px] sm:text-xs">
|
||||
→ {{ t('queue.level') }} {{ item.targetLevel }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">
|
||||
{{ formatTime(getRemainingTime(item)) }}
|
||||
</span>
|
||||
<Button
|
||||
@click="handleCancelResearch(item.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs"
|
||||
>
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<div class="animate-fade-in">
|
||||
@@ -405,6 +348,9 @@
|
||||
<!-- 更新弹窗 -->
|
||||
<UpdateDialog v-model:open="showUpdateDialog" :version-info="updateInfo" />
|
||||
|
||||
<!-- 新手引导 -->
|
||||
<TutorialOverlay />
|
||||
|
||||
<!-- Toast 通知 -->
|
||||
<Sonner position="top-center" />
|
||||
</SidebarProvider>
|
||||
@@ -412,18 +358,21 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted, computed, ref, watch } from 'vue'
|
||||
import { RouterView, RouterLink } from 'vue-router'
|
||||
import { RouterView, useRouter } 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 { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { useTutorial } from '@/composables/useTutorial'
|
||||
import { localeNames, detectBrowserLocale, type Locale } from '@/locales'
|
||||
import { Button } from '@/components/ui/button'
|
||||
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 DiplomaticNotifications from '@/components/DiplomaticNotifications.vue'
|
||||
import QueueNotifications from '@/components/QueueNotifications.vue'
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -451,11 +400,13 @@
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import DetailDialog from '@/components/DetailDialog.vue'
|
||||
import UpdateDialog from '@/components/UpdateDialog.vue'
|
||||
import TutorialOverlay from '@/components/TutorialOverlay.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 { MissionType, BuildingType, DiplomaticEventType } from '@/types/game'
|
||||
import type { FleetMission, NPC, IncomingFleetAlert, MissileAttack } from '@/types/game'
|
||||
import { DIPLOMATIC_CONFIG } from '@/config/gameConfig'
|
||||
import type { VersionInfo } from '@/utils/versionCheck'
|
||||
import { formatNumber, formatTime, getResourceColor } from '@/utils/format'
|
||||
import { formatNumber, getResourceColor } from '@/utils/format'
|
||||
import {
|
||||
Moon,
|
||||
Sun,
|
||||
@@ -497,11 +448,14 @@
|
||||
// 执行数据迁移(在 store 初始化之前)
|
||||
migrateGameData()
|
||||
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const universeStore = useUniverseStore()
|
||||
const npcStore = useNPCStore()
|
||||
const { isDark } = useTheme()
|
||||
const { t } = useI18n()
|
||||
const { BUILDINGS } = useGameConfig()
|
||||
const { startTutorial, tutorialState, currentStep } = useTutorial()
|
||||
|
||||
// ConfirmDialog 状态
|
||||
const confirmDialogOpen = ref(false)
|
||||
@@ -536,10 +490,10 @@
|
||||
if (!shouldInit) {
|
||||
const now = Date.now()
|
||||
|
||||
// 计算离线收益(直接同步计算)
|
||||
// 计算离线收益(直接同步计算,应用游戏速度)
|
||||
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now)
|
||||
gameStore.player.planets.forEach(planet => {
|
||||
resourceLogic.updatePlanetResources(planet, now, bonuses)
|
||||
resourceLogic.updatePlanetResources(planet, now, bonuses, gameStore.gameSpeed)
|
||||
})
|
||||
|
||||
// 只在没有NPC星球时才生成(首次加载已有玩家数据时)
|
||||
@@ -568,13 +522,13 @@
|
||||
}
|
||||
|
||||
const updateGame = async () => {
|
||||
if (gameStore.isPaused) return
|
||||
const now = Date.now()
|
||||
gameStore.gameTime = now
|
||||
if (gameStore.isPaused) return
|
||||
// 检查军官过期
|
||||
gameLogic.checkOfficersExpiration(gameStore.player.officers, now)
|
||||
// 处理游戏更新(建造队列、研究队列等)
|
||||
const result = gameLogic.processGameUpdate(gameStore.player, now)
|
||||
const result = gameLogic.processGameUpdate(gameStore.player, now, gameStore.gameSpeed)
|
||||
gameStore.player.researchQueue = result.updatedResearchQueue
|
||||
// 处理舰队任务
|
||||
gameStore.player.fleetMissions.forEach(mission => {
|
||||
@@ -613,6 +567,27 @@
|
||||
|
||||
// NPC行为系统更新(侦查和攻击决策)
|
||||
updateNPCBehavior(1)
|
||||
|
||||
// 检查并处理被消灭的NPC(所有星球都被摧毁的NPC)
|
||||
const eliminatedNpcIds = diplomaticLogic.checkAndHandleEliminatedNPCs(npcStore.npcs, gameStore.player, gameStore.locale)
|
||||
if (eliminatedNpcIds.length > 0) {
|
||||
// 从universeStore中移除被消灭NPC的星球数据
|
||||
eliminatedNpcIds.forEach(npcId => {
|
||||
const npc = npcStore.npcs.find(n => n.id === npcId)
|
||||
if (npc && npc.planets) {
|
||||
// 遍历NPC的所有星球,从universeStore中删除
|
||||
npc.planets.forEach(planet => {
|
||||
const planetKey = gameLogic.generatePositionKey(planet.position.galaxy, planet.position.system, planet.position.position)
|
||||
if (universeStore.planets[planetKey]) {
|
||||
delete universeStore.planets[planetKey]
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 从NPC列表中移除被消灭的NPC
|
||||
npcStore.npcs = npcStore.npcs.filter(npc => !eliminatedNpcIds.includes(npc.id))
|
||||
}
|
||||
}
|
||||
|
||||
const processMissionArrival = async (mission: FleetMission) => {
|
||||
@@ -812,6 +787,15 @@
|
||||
|
||||
if (destroyResult && destroyResult.success && destroyResult.planetId) {
|
||||
// 星球被摧毁
|
||||
|
||||
// 处理外交关系(如果目标是NPC星球)
|
||||
if (targetPlanet && targetPlanet.ownerId) {
|
||||
const planetOwner = npcStore.npcs.find(npc => npc.id === targetPlanet.ownerId)
|
||||
if (planetOwner) {
|
||||
diplomaticLogic.handlePlanetDestructionReputation(gameStore.player, targetPlanet, planetOwner, npcStore.npcs, gameStore.locale)
|
||||
}
|
||||
}
|
||||
|
||||
// 从玩家星球列表中移除(如果是玩家的星球)
|
||||
const planetIndex = gameStore.player.planets.findIndex(p => p.id === destroyResult.planetId)
|
||||
if (planetIndex > -1) {
|
||||
@@ -1010,6 +994,44 @@
|
||||
// 应用损失到目标星球
|
||||
missileLogic.applyMissileAttackResult(targetPlanet, impactResult.defenseLosses)
|
||||
|
||||
// 如果目标是NPC的星球,扣除外交好感度
|
||||
if (targetPlanet.ownerId && targetPlanet.ownerId !== gameStore.player.id) {
|
||||
const targetNpc = npcStore.npcs.find(npc => npc.id === targetPlanet.ownerId)
|
||||
if (targetNpc) {
|
||||
// 导弹攻击扣除好感度
|
||||
const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG
|
||||
const reputationLoss = REPUTATION_CHANGES.ATTACK / 2 // 导弹攻击的好感度惩罚是普通攻击的一半
|
||||
|
||||
// 更新玩家对NPC的关系
|
||||
if (!gameStore.player.diplomaticRelations) {
|
||||
gameStore.player.diplomaticRelations = {}
|
||||
}
|
||||
const relation = diplomaticLogic.getOrCreateRelation(
|
||||
gameStore.player.diplomaticRelations,
|
||||
gameStore.player.id,
|
||||
targetNpc.id
|
||||
)
|
||||
gameStore.player.diplomaticRelations[targetNpc.id] = diplomaticLogic.updateReputation(
|
||||
relation,
|
||||
reputationLoss,
|
||||
DiplomaticEventType.Attack,
|
||||
t('diplomacy.reports.missileAttackNpc', { npcName: targetNpc.name })
|
||||
)
|
||||
|
||||
// 更新NPC对玩家的关系
|
||||
if (!targetNpc.relations) {
|
||||
targetNpc.relations = {}
|
||||
}
|
||||
const npcRelation = diplomaticLogic.getOrCreateRelation(targetNpc.relations, targetNpc.id, gameStore.player.id)
|
||||
targetNpc.relations[gameStore.player.id] = diplomaticLogic.updateReputation(
|
||||
npcRelation,
|
||||
reputationLoss,
|
||||
DiplomaticEventType.Attack,
|
||||
t('diplomacy.reports.wasAttackedByMissile')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 标记导弹攻击为已到达
|
||||
missileAttack.status = 'arrived'
|
||||
|
||||
@@ -1062,7 +1084,7 @@
|
||||
|
||||
// NPC成长系统更新函数
|
||||
let npcUpdateCounter = 0 // 累计秒数
|
||||
const NPC_UPDATE_INTERVAL = 10 // 每10秒更新一次NPC,减少性能开销
|
||||
const NPC_UPDATE_INTERVAL = 1 // 每1秒更新一次NPC,确保发育速度与玩家相当
|
||||
|
||||
const updateNPCGrowth = (deltaSeconds: number) => {
|
||||
// 累积时间
|
||||
@@ -1216,6 +1238,14 @@
|
||||
startGameLoop()
|
||||
// 启动科乐美秘籍监听
|
||||
konamiCleanup = setupKonamiCode()
|
||||
|
||||
// 启动新手引导(如果尚未完成)
|
||||
startTutorial()
|
||||
|
||||
// 添加队列取消事件监听
|
||||
window.addEventListener('cancel-build', handleCancelBuildEvent as EventListener)
|
||||
window.addEventListener('cancel-research', handleCancelResearchEvent as EventListener)
|
||||
|
||||
// 首次检查版本(被动检测)
|
||||
const versionInfo = await checkLatestVersion(gameStore.player.lastVersionCheckTime || 0, (time: number) => {
|
||||
gameStore.player.lastVersionCheckTime = time
|
||||
@@ -1259,8 +1289,21 @@
|
||||
if (gameLoop) clearInterval(gameLoop)
|
||||
if (konamiCleanup) konamiCleanup()
|
||||
if (versionCheckInterval) clearInterval(versionCheckInterval)
|
||||
// 移除队列取消事件监听
|
||||
window.removeEventListener('cancel-build', handleCancelBuildEvent as EventListener)
|
||||
window.removeEventListener('cancel-research', handleCancelResearchEvent as EventListener)
|
||||
})
|
||||
|
||||
// 处理取消建造事件
|
||||
const handleCancelBuildEvent = (event: CustomEvent) => {
|
||||
handleCancelBuild(event.detail)
|
||||
}
|
||||
|
||||
// 处理取消研究事件
|
||||
const handleCancelResearchEvent = (event: CustomEvent) => {
|
||||
handleCancelResearch(event.detail)
|
||||
}
|
||||
|
||||
// 科乐美秘籍:上上下下左左右右BA
|
||||
const setupKonamiCode = () => {
|
||||
const konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowLeft', 'ArrowRight', 'ArrowRight', 'b', 'a']
|
||||
@@ -1312,6 +1355,56 @@
|
||||
...(gameStore.player.isGMEnabled ? [{ name: computed(() => t('nav.gm')), path: '/gm', icon: Wrench }] : [])
|
||||
])
|
||||
|
||||
// 功能解锁要求配置
|
||||
const featureRequirements: Record<string, { building: BuildingType; level: number }> = {
|
||||
'/research': { building: BuildingType.ResearchLab, level: 1 },
|
||||
'/shipyard': { building: BuildingType.Shipyard, level: 1 },
|
||||
'/defense': { building: BuildingType.Shipyard, level: 1 },
|
||||
'/fleet': { building: BuildingType.Shipyard, level: 1 }
|
||||
}
|
||||
|
||||
// 检查功能是否解锁
|
||||
const checkFeatureUnlocked = (path: string): { unlocked: boolean; requirement?: { building: BuildingType; level: number } } => {
|
||||
const requirement = featureRequirements[path]
|
||||
if (!requirement) {
|
||||
return { unlocked: true }
|
||||
}
|
||||
|
||||
const currentLevel = planet.value?.buildings[requirement.building] || 0
|
||||
return {
|
||||
unlocked: currentLevel >= requirement.level,
|
||||
requirement
|
||||
}
|
||||
}
|
||||
|
||||
// 处理导航点击
|
||||
const handleNavClick = (path: string, event: Event) => {
|
||||
const { unlocked, requirement } = checkFeatureUnlocked(path)
|
||||
|
||||
if (!unlocked && requirement) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
const buildingName = BUILDINGS.value[requirement.building]?.name || requirement.building
|
||||
const currentLevel = planet.value?.buildings[requirement.building] || 0
|
||||
|
||||
toast.warning(t('common.featureLocked'), {
|
||||
description: `${t('common.requiredBuilding')}: ${buildingName} Lv ${requirement.level} (${t(
|
||||
'common.currentLevel'
|
||||
)}: Lv ${currentLevel})`,
|
||||
action: {
|
||||
label: t('common.goToBuildings'),
|
||||
onClick: () => router.push('/buildings')
|
||||
},
|
||||
duration: 3000
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 功能已解锁,正常导航
|
||||
router.push(path)
|
||||
}
|
||||
|
||||
// 使用直接计算,不再缓存
|
||||
const production = computed(() => {
|
||||
if (!planet.value) return null
|
||||
@@ -1362,6 +1455,11 @@
|
||||
return fleetMissions + flyingMissiles
|
||||
})
|
||||
|
||||
// 未读外交报告数量
|
||||
const unreadDiplomaticReportsCount = computed(() => {
|
||||
return (gameStore.player.diplomaticReports || []).filter(r => !r.read).length
|
||||
})
|
||||
|
||||
// 资源类型配置
|
||||
const resourceTypes = [
|
||||
{ key: 'metal' as const },
|
||||
@@ -1402,33 +1500,22 @@
|
||||
sidebarOpen.value = !sidebarOpen.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}`)
|
||||
// 处理侧边栏打开/关闭状态变化
|
||||
const handleSidebarOpenChange = (open: boolean) => {
|
||||
// 如果是移动端且在教程的菜单相关步骤,阻止关闭侧边栏
|
||||
if (window.innerWidth < 768 && tutorialState.value.isActive && currentStep.value) {
|
||||
// 只在第3步期间阻止关闭侧边栏,让玩家必须手动打开
|
||||
if (currentStep.value.id === 'menu_intro_mobile') {
|
||||
// 只允许打开,不允许关闭
|
||||
if (open) {
|
||||
sidebarOpen.value = true
|
||||
}
|
||||
// 如果试图关闭,忽略该操作,保持打开状态
|
||||
return
|
||||
}
|
||||
}
|
||||
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))
|
||||
// 其他情况正常更新
|
||||
sidebarOpen.value = open
|
||||
}
|
||||
|
||||
// 取消建造
|
||||
|
||||
300
src/components/DiplomaticNotifications.vue
Normal file
300
src/components/DiplomaticNotifications.vue
Normal file
@@ -0,0 +1,300 @@
|
||||
<template>
|
||||
<Popover v-model:open="isOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button variant="outline" size="icon" class="relative">
|
||||
<ScrollText class="h-4 w-4" />
|
||||
<Badge
|
||||
v-if="unreadCount > 0"
|
||||
variant="destructive"
|
||||
class="absolute -top-1 -right-1 h-5 w-5 p-0 flex items-center justify-center text-xs"
|
||||
>
|
||||
{{ unreadCount > 9 ? '9+' : unreadCount }}
|
||||
</Badge>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-96 p-0" align="end">
|
||||
<div class="flex items-center justify-between p-4 border-b">
|
||||
<h3 class="font-semibold">{{ t('diplomacy.notifications') }}</h3>
|
||||
<Button v-if="unreadCount > 0" variant="ghost" size="sm" @click="markAllAsRead">
|
||||
{{ t('diplomacy.markAllRead') }}
|
||||
</Button>
|
||||
</div>
|
||||
<ScrollArea class="h-96">
|
||||
<div v-if="reports.length === 0" class="p-8 text-center text-muted-foreground">
|
||||
{{ t('diplomacy.noReports') }}
|
||||
</div>
|
||||
<div v-else class="divide-y">
|
||||
<div
|
||||
v-for="report in reports"
|
||||
:key="report.id"
|
||||
class="p-4 hover:bg-muted/50 cursor-pointer transition-colors"
|
||||
:class="{ 'bg-primary/5': !report.read }"
|
||||
@click="handleReportClick(report)"
|
||||
>
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="flex-shrink-0 mt-1">
|
||||
<component :is="getEventIcon(report.eventType)" class="h-4 w-4" :class="getEventIconColor(report.eventType)" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-medium text-sm">{{ report.npcName }}</span>
|
||||
<Badge :variant="getStatusBadgeVariant(report.newStatus)" class="text-xs">
|
||||
{{ getStatusText(report.newStatus) }}
|
||||
</Badge>
|
||||
<span v-if="!report.read" class="ml-auto">
|
||||
<Badge variant="destructive" class="h-2 w-2 p-0 rounded-full" />
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground line-clamp-2">
|
||||
{{ report.messageKey && report.messageParams ? t(report.messageKey, report.messageParams) : report.message }}
|
||||
</p>
|
||||
<p class="text-xs text-muted-foreground mt-1">
|
||||
{{ formatRelativeTime((Date.now() - report.timestamp) / 1000, t) }}{{ t('diplomacy.ago') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
<div v-if="reports.length > 0" class="p-2 border-t">
|
||||
<Button variant="ghost" size="sm" class="w-full" @click="goToDiplomacy">
|
||||
{{ t('diplomacy.viewAll') }}
|
||||
</Button>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
<!-- 外交报告详情对话框 -->
|
||||
<Dialog :open="detailDialogOpen" @update:open="detailDialogOpen = $event">
|
||||
<DialogContent class="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<component
|
||||
v-if="selectedReport"
|
||||
:is="getEventIcon(selectedReport.eventType)"
|
||||
class="h-5 w-5"
|
||||
:class="getEventIconColor(selectedReport.eventType)"
|
||||
/>
|
||||
{{ t('diplomacy.reportDetails') }}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="selectedReport" class="space-y-4">
|
||||
<!-- NPC信息 -->
|
||||
<div class="flex items-center gap-3 p-4 bg-muted/50 rounded-lg">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<h3 class="font-semibold text-lg">{{ selectedReport.npcName }}</h3>
|
||||
<Badge :variant="getStatusBadgeVariant(selectedReport.newStatus)">
|
||||
{{ getStatusText(selectedReport.newStatus) }}
|
||||
</Badge>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ formatRelativeTime((Date.now() - selectedReport.timestamp) / 1000, t) }}{{ t('diplomacy.ago') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 事件描述 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('diplomacy.eventDescription') }}</h4>
|
||||
<p class="text-sm p-3 bg-muted/30 rounded-md">
|
||||
{{
|
||||
selectedReport.messageKey && selectedReport.messageParams
|
||||
? t(selectedReport.messageKey, selectedReport.messageParams)
|
||||
: selectedReport.message
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 关系变化 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- 好感度变化 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('diplomacy.reputationChange') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<div class="flex items-center justify-between text-sm mb-2">
|
||||
<span class="text-muted-foreground">{{ t('diplomacy.before') }}</span>
|
||||
<span class="font-semibold" :class="getReputationColor(selectedReport.newReputation - selectedReport.reputationChange)">
|
||||
{{ selectedReport.newReputation - selectedReport.reputationChange > 0 ? '+' : ''
|
||||
}}{{ selectedReport.newReputation - selectedReport.reputationChange }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center justify-center text-lg font-bold my-1"
|
||||
:class="selectedReport.reputationChange >= 0 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'"
|
||||
>
|
||||
{{ selectedReport.reputationChange >= 0 ? '+' : '' }}{{ selectedReport.reputationChange }}
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm mt-2">
|
||||
<span class="text-muted-foreground">{{ t('diplomacy.after') }}</span>
|
||||
<span class="font-semibold" :class="getReputationColor(selectedReport.newReputation)">
|
||||
{{ selectedReport.newReputation > 0 ? '+' : '' }}{{ selectedReport.newReputation }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关系状态变化 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('diplomacy.statusChange') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<div class="flex items-center justify-between text-sm mb-2">
|
||||
<span class="text-muted-foreground">{{ t('diplomacy.before') }}</span>
|
||||
<Badge :variant="getStatusBadgeVariant(selectedReport.oldStatus)" class="text-xs">
|
||||
{{ getStatusText(selectedReport.oldStatus) }}
|
||||
</Badge>
|
||||
</div>
|
||||
<div class="flex items-center justify-center my-3">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="h-6 w-6 text-muted-foreground"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 14l-7 7m0 0l-7-7m7 7V3" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm mt-2">
|
||||
<span class="text-muted-foreground">{{ t('diplomacy.after') }}</span>
|
||||
<Badge :variant="getStatusBadgeVariant(selectedReport.newStatus)" class="text-xs">
|
||||
{{ getStatusText(selectedReport.newStatus) }}
|
||||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="detailDialogOpen = false">{{ t('common.close') }}</Button>
|
||||
<Button @click="goToDiplomacyFromDialog">{{ t('diplomacy.viewDiplomacy') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { ScrollText, Gift, Sword, Eye, Trash2, Skull } from 'lucide-vue-next'
|
||||
import { RelationStatus, DiplomaticEventType } from '@/types/game'
|
||||
import type { DiplomaticReport } from '@/types/game'
|
||||
import { formatRelativeTime } from '@/utils/format'
|
||||
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const { t } = useI18n()
|
||||
const isOpen = ref(false)
|
||||
const detailDialogOpen = ref(false)
|
||||
const selectedReport = ref<DiplomaticReport | null>(null)
|
||||
|
||||
const reports = computed(() => {
|
||||
return (gameStore.player.diplomaticReports || []).slice().reverse().slice(0, 20) // 最近20条
|
||||
})
|
||||
|
||||
const unreadCount = computed(() => {
|
||||
return (gameStore.player.diplomaticReports || []).filter(r => !r.read).length
|
||||
})
|
||||
|
||||
const getEventIcon = (eventType: DiplomaticReport['eventType']) => {
|
||||
switch (eventType) {
|
||||
case DiplomaticEventType.GiftResources:
|
||||
return Gift
|
||||
case DiplomaticEventType.Attack:
|
||||
case DiplomaticEventType.AllyAttacked:
|
||||
return Sword
|
||||
case DiplomaticEventType.Spy:
|
||||
return Eye
|
||||
case DiplomaticEventType.StealDebris:
|
||||
return Trash2
|
||||
case DiplomaticEventType.DestroyPlanet:
|
||||
return Skull
|
||||
default:
|
||||
return ScrollText
|
||||
}
|
||||
}
|
||||
|
||||
const getEventIconColor = (eventType: DiplomaticReport['eventType']) => {
|
||||
switch (eventType) {
|
||||
case DiplomaticEventType.GiftResources:
|
||||
return 'text-green-500'
|
||||
case DiplomaticEventType.Attack:
|
||||
case DiplomaticEventType.DestroyPlanet:
|
||||
return 'text-red-500'
|
||||
case DiplomaticEventType.AllyAttacked:
|
||||
return 'text-orange-500'
|
||||
case DiplomaticEventType.Spy:
|
||||
return 'text-purple-500'
|
||||
case DiplomaticEventType.StealDebris:
|
||||
return 'text-yellow-500'
|
||||
default:
|
||||
return 'text-muted-foreground'
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusBadgeVariant = (status: RelationStatus) => {
|
||||
switch (status) {
|
||||
case RelationStatus.Hostile:
|
||||
return 'destructive'
|
||||
case RelationStatus.Friendly:
|
||||
return 'default'
|
||||
case RelationStatus.Neutral:
|
||||
default:
|
||||
return 'secondary'
|
||||
}
|
||||
}
|
||||
|
||||
const getStatusText = (status: RelationStatus) => {
|
||||
switch (status) {
|
||||
case RelationStatus.Hostile:
|
||||
return t('diplomacy.status.hostile')
|
||||
case RelationStatus.Friendly:
|
||||
return t('diplomacy.status.friendly')
|
||||
case RelationStatus.Neutral:
|
||||
default:
|
||||
return t('diplomacy.status.neutral')
|
||||
}
|
||||
}
|
||||
|
||||
const getReputationColor = (reputation: number | null) => {
|
||||
if (reputation === null) return 'text-muted-foreground'
|
||||
if (reputation >= 20) return 'text-green-600 dark:text-green-400'
|
||||
if (reputation <= -20) return 'text-red-600 dark:text-red-400'
|
||||
return 'text-muted-foreground'
|
||||
}
|
||||
|
||||
const handleReportClick = (report: DiplomaticReport) => {
|
||||
// 标记为已读
|
||||
report.read = true
|
||||
// 设置选中的报告并打开详情对话框
|
||||
selectedReport.value = report
|
||||
detailDialogOpen.value = true
|
||||
// 关闭通知面板
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const markAllAsRead = () => {
|
||||
gameStore.player.diplomaticReports?.forEach(report => {
|
||||
report.read = true
|
||||
})
|
||||
}
|
||||
|
||||
const goToDiplomacy = () => {
|
||||
isOpen.value = false
|
||||
router.push('/diplomacy')
|
||||
}
|
||||
|
||||
const goToDiplomacyFromDialog = () => {
|
||||
detailDialogOpen.value = false
|
||||
router.push('/diplomacy')
|
||||
}
|
||||
</script>
|
||||
@@ -175,15 +175,21 @@
|
||||
<CardContent class="space-y-2">
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="totalStats.metal" /></span>
|
||||
<span class="font-medium">
|
||||
<NumberWithTooltip :value="totalStats.metal" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.crystal') }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="totalStats.crystal" /></span>
|
||||
<span class="font-medium">
|
||||
<NumberWithTooltip :value="totalStats.crystal" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-muted-foreground">{{ t('resources.deuterium') }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="totalStats.deuterium" /></span>
|
||||
<span class="font-medium">
|
||||
<NumberWithTooltip :value="totalStats.deuterium" />
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -305,11 +311,15 @@
|
||||
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>
|
||||
<span class="font-medium">
|
||||
<NumberWithTooltip :value="unitCost[resourceType.key]" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm pt-2 border-t">
|
||||
<span class="text-muted-foreground">{{ t('player.points') }}:</span>
|
||||
<span class="font-bold text-primary"><NumberWithTooltip :value="pointsPerUnit" /></span>
|
||||
<span class="font-bold text-primary">
|
||||
<NumberWithTooltip :value="pointsPerUnit" />
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
@@ -341,15 +351,21 @@
|
||||
<div class="space-y-1 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span>{{ t('resources.metal') }}:</span>
|
||||
<span class="font-medium"><NumberWithTooltip :value="batchCost.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>
|
||||
<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>
|
||||
<span class="font-medium">
|
||||
<NumberWithTooltip :value="batchCost.deuterium" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -470,7 +486,7 @@
|
||||
const showFleetStorageColumn = computed(() => {
|
||||
if (props.type === 'building') {
|
||||
const buildingType = props.itemType as BuildingType
|
||||
return buildingType === 'shipyard'
|
||||
return buildingType === 'shipyard' || buildingType === 'hangar'
|
||||
} else if (props.type === 'technology') {
|
||||
const techType = props.itemType as TechnologyType
|
||||
return techType === 'computerTechnology'
|
||||
@@ -662,43 +678,84 @@
|
||||
const storageBonus = 1 + (activeBonuses.value.storageCapacityBonus || 0) / 100
|
||||
const baseCapacity = 10000
|
||||
|
||||
if (buildingType === 'metalMine') {
|
||||
production = Math.floor(1500 * level * Math.pow(1.5, level) * resourceBonus)
|
||||
consumption = Math.floor(10 * level * Math.pow(1.1, level))
|
||||
} else if (buildingType === 'crystalMine') {
|
||||
production = Math.floor(1000 * level * Math.pow(1.5, level) * resourceBonus)
|
||||
consumption = Math.floor(10 * level * Math.pow(1.1, level))
|
||||
} else if (buildingType === 'deuteriumSynthesizer') {
|
||||
production = Math.floor(500 * level * Math.pow(1.5, level) * resourceBonus)
|
||||
consumption = Math.floor(10 * level * Math.pow(1.1, level))
|
||||
} else if (buildingType === 'solarPlant') {
|
||||
production = Math.floor(50 * level * Math.pow(1.1, level) * energyBonus)
|
||||
} else if (buildingType === 'metalStorage') {
|
||||
capacity = Math.floor(baseCapacity * Math.pow(2, level) * storageBonus)
|
||||
} else if (buildingType === 'crystalStorage') {
|
||||
capacity = Math.floor(baseCapacity * Math.pow(2, level) * storageBonus)
|
||||
} else if (buildingType === 'deuteriumTank') {
|
||||
capacity = Math.floor(baseCapacity * Math.pow(2, level) * storageBonus)
|
||||
} else if (buildingType === 'darkMatterCollector') {
|
||||
capacity = 1000 + level * 100
|
||||
production = Math.floor(25 * level * Math.pow(1.5, level))
|
||||
} else if (buildingType === 'darkMatterTank') {
|
||||
const darkMatterBaseCapacity = 1000
|
||||
capacity = Math.floor(darkMatterBaseCapacity * Math.pow(2, level) * storageBonus)
|
||||
} else if (buildingType === 'fusionReactor') {
|
||||
production = Math.floor(150 * level * Math.pow(1.15, level))
|
||||
} else if (buildingType === 'shipyard') {
|
||||
fleetStorage = 1000 * level
|
||||
} else if (buildingType === 'terraformer') {
|
||||
spaceBonus = 30
|
||||
} else if (buildingType === 'lunarBase') {
|
||||
spaceBonus = 30
|
||||
} else if (buildingType === 'roboticsFactory') {
|
||||
buildSpeedBonus = level
|
||||
} else if (buildingType === 'naniteFactory') {
|
||||
buildSpeedBonus = level * 2
|
||||
} else if (buildingType === 'researchLab') {
|
||||
researchSpeedBonus = level
|
||||
// Building calculation configuration
|
||||
const buildingCalculations: Record<string, (level: number) => Partial<{
|
||||
production: number
|
||||
consumption: number
|
||||
capacity: number
|
||||
fleetStorage: number
|
||||
spaceBonus: number
|
||||
buildSpeedBonus: number
|
||||
researchSpeedBonus: number
|
||||
}>> = {
|
||||
metalMine: (lvl) => ({
|
||||
production: Math.floor(1500 * lvl * Math.pow(1.5, lvl) * resourceBonus),
|
||||
consumption: Math.floor(10 * lvl * Math.pow(1.1, lvl))
|
||||
}),
|
||||
crystalMine: (lvl) => ({
|
||||
production: Math.floor(1000 * lvl * Math.pow(1.5, lvl) * resourceBonus),
|
||||
consumption: Math.floor(10 * lvl * Math.pow(1.1, lvl))
|
||||
}),
|
||||
deuteriumSynthesizer: (lvl) => ({
|
||||
production: Math.floor(500 * lvl * Math.pow(1.5, lvl) * resourceBonus),
|
||||
consumption: Math.floor(10 * lvl * Math.pow(1.1, lvl))
|
||||
}),
|
||||
solarPlant: (lvl) => ({
|
||||
production: Math.floor(50 * lvl * Math.pow(1.1, lvl) * energyBonus)
|
||||
}),
|
||||
metalStorage: (lvl) => ({
|
||||
capacity: Math.floor(baseCapacity * Math.pow(2, lvl) * storageBonus)
|
||||
}),
|
||||
crystalStorage: (lvl) => ({
|
||||
capacity: Math.floor(baseCapacity * Math.pow(2, lvl) * storageBonus)
|
||||
}),
|
||||
deuteriumTank: (lvl) => ({
|
||||
capacity: Math.floor(baseCapacity * Math.pow(2, lvl) * storageBonus)
|
||||
}),
|
||||
darkMatterCollector: (lvl) => ({
|
||||
capacity: 1000 + lvl * 100,
|
||||
production: Math.floor(25 * lvl * Math.pow(1.5, lvl))
|
||||
}),
|
||||
darkMatterTank: (lvl) => ({
|
||||
capacity: Math.floor(1000 * Math.pow(2, lvl) * storageBonus)
|
||||
}),
|
||||
fusionReactor: (lvl) => ({
|
||||
production: Math.floor(150 * lvl * Math.pow(1.15, lvl))
|
||||
}),
|
||||
shipyard: (lvl) => ({
|
||||
fleetStorage: 1000 * lvl
|
||||
}),
|
||||
hangar: (lvl) => ({
|
||||
fleetStorage: 500 * lvl
|
||||
}),
|
||||
terraformer: () => ({
|
||||
spaceBonus: 30
|
||||
}),
|
||||
lunarBase: () => ({
|
||||
spaceBonus: 30
|
||||
}),
|
||||
roboticsFactory: (lvl) => ({
|
||||
buildSpeedBonus: lvl
|
||||
}),
|
||||
naniteFactory: (lvl) => ({
|
||||
buildSpeedBonus: lvl * 2
|
||||
}),
|
||||
researchLab: (lvl) => ({
|
||||
researchSpeedBonus: lvl
|
||||
})
|
||||
}
|
||||
|
||||
// Apply calculations if configuration exists
|
||||
const calc = buildingCalculations[buildingType]
|
||||
if (calc) {
|
||||
const result = calc(level)
|
||||
production = result.production ?? production
|
||||
consumption = result.consumption ?? consumption
|
||||
capacity = result.capacity ?? capacity
|
||||
fleetStorage = result.fleetStorage ?? fleetStorage
|
||||
spaceBonus = result.spaceBonus ?? spaceBonus
|
||||
buildSpeedBonus = result.buildSpeedBonus ?? buildSpeedBonus
|
||||
researchSpeedBonus = result.researchSpeedBonus ?? researchSpeedBonus
|
||||
}
|
||||
|
||||
const points = pointsLogic.calculateBuildingPoints(buildingType, level - 1, level)
|
||||
|
||||
@@ -53,7 +53,13 @@
|
||||
<div v-if="npc.allies && npc.allies.length > 0" class="pt-2 border-t">
|
||||
<p class="text-sm text-muted-foreground mb-2">{{ t('diplomacy.alliedWith') }}:</p>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<Badge v-for="allyId in npc.allies.slice(0, 3)" :key="allyId" variant="outline" class="text-xs">
|
||||
<Badge
|
||||
v-for="allyId in npc.allies.slice(0, 3)"
|
||||
:key="allyId"
|
||||
variant="outline"
|
||||
class="text-xs cursor-pointer hover:bg-accent transition-colors"
|
||||
@click="scrollToAlly(allyId)"
|
||||
>
|
||||
{{ getAllyName(allyId) }}
|
||||
</Badge>
|
||||
<Badge v-if="npc.allies.length > 3" variant="outline" class="text-xs">
|
||||
@@ -80,7 +86,9 @@
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<component :is="getEventIcon(recentEvent.reason)" class="h-3 w-3" />
|
||||
<span>{{ getEventText(recentEvent.reason) }}</span>
|
||||
<span class="text-muted-foreground">{{ formatTime(Date.now() - recentEvent.timestamp) }} {{ t('diplomacy.ago') }}</span>
|
||||
<span class="text-muted-foreground">
|
||||
{{ formatRelativeTime((Date.now() - recentEvent.timestamp) / 1000, t) }}{{ t('diplomacy.ago') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
@@ -98,7 +106,7 @@
|
||||
import { Gift, Globe, Sword, Eye, Trash2 } from 'lucide-vue-next'
|
||||
import { RelationStatus, DiplomaticEventType } from '@/types/game'
|
||||
import type { DiplomaticRelation, NPC } from '@/types/game'
|
||||
import { formatTime } from '@/utils/format'
|
||||
import { formatRelativeTime } from '@/utils/format'
|
||||
|
||||
const props = defineProps<{
|
||||
npc: NPC
|
||||
@@ -229,4 +237,12 @@
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 滚动到盟友卡片
|
||||
const scrollToAlly = (allyId: string) => {
|
||||
// 触发父组件的滚动事件
|
||||
// 通过emit通知父组件滚动到指定的NPC卡片
|
||||
const event = new CustomEvent('scrollToNpc', { detail: { npcId: allyId }, bubbles: true })
|
||||
document.dispatchEvent(event)
|
||||
}
|
||||
</script>
|
||||
|
||||
166
src/components/QueueNotifications.vue
Normal file
166
src/components/QueueNotifications.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<Popover v-model:open="isOpen">
|
||||
<PopoverTrigger as-child>
|
||||
<Button data-tutorial="queue-button" variant="outline" size="icon" class="relative">
|
||||
<ListOrdered class="h-4 w-4" />
|
||||
<Badge
|
||||
v-if="totalQueueCount > 0"
|
||||
variant="default"
|
||||
class="absolute -top-1 -right-1 h-5 w-5 p-0 flex items-center justify-center text-xs"
|
||||
>
|
||||
{{ totalQueueCount > 9 ? '9+' : totalQueueCount }}
|
||||
</Badge>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="w-96 p-0" align="end">
|
||||
<div class="flex items-center justify-between p-4 border-b">
|
||||
<h3 class="font-semibold">{{ t('queue.title') }}</h3>
|
||||
</div>
|
||||
<ScrollArea class="max-h-96">
|
||||
<div v-if="totalQueueCount === 0" class="p-8 text-center text-muted-foreground">
|
||||
{{ t('queue.empty') }}
|
||||
</div>
|
||||
<div v-else class="divide-y p-4 space-y-3">
|
||||
<!-- 建造队列 -->
|
||||
<div v-for="item in buildQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div
|
||||
class="h-2 w-2 rounded-full animate-pulse flex-shrink-0"
|
||||
:class="item.type === 'demolish' ? 'bg-destructive' : 'bg-green-500'"
|
||||
/>
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs">
|
||||
<template v-if="item.type === 'ship' || item.type === 'defense'">
|
||||
→ {{ t('queue.quantity') }} {{ item.quantity }}
|
||||
</template>
|
||||
<template v-else-if="item.type === 'demolish'">→ {{ t('queue.demolishing') }}</template>
|
||||
<template v-else>→ {{ t('queue.level') }} {{ item.targetLevel }}</template>
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">
|
||||
{{ formatTime(getRemainingTime(item)) }}
|
||||
</span>
|
||||
<Button @click="handleCancel(item)" variant="ghost" size="sm" class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs">
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
|
||||
<!-- 研究队列 -->
|
||||
<div v-for="item in researchQueue" :key="item.id" class="space-y-1.5">
|
||||
<div class="flex items-center justify-between text-xs sm:text-sm gap-2">
|
||||
<div class="flex items-center gap-1.5 sm:gap-2 min-w-0 flex-1">
|
||||
<div class="h-2 w-2 rounded-full bg-blue-500 animate-pulse flex-shrink-0" />
|
||||
<span class="font-medium truncate">{{ getItemName(item) }}</span>
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs">→ {{ t('queue.level') }} {{ item.targetLevel }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||
<span class="text-muted-foreground text-[10px] sm:text-xs whitespace-nowrap">
|
||||
{{ formatTime(getRemainingTime(item)) }}
|
||||
</span>
|
||||
<Button
|
||||
@click="handleCancelResearch(item.id)"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
class="h-5 sm:h-6 px-1.5 sm:px-2 text-[10px] sm:text-xs"
|
||||
>
|
||||
{{ t('queue.cancel') }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Progress :model-value="getQueueProgress(item)" class="h-1.5" />
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { ListOrdered } from 'lucide-vue-next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Progress } from '@/components/ui/progress'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useGameConfig } from '@/composables/useGameConfig'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { formatTime } from '@/utils/format'
|
||||
import type { BuildQueueItem, BuildingType, ShipType, DefenseType, TechnologyType } from '@/types/game'
|
||||
|
||||
const { t } = useI18n()
|
||||
const gameStore = useGameStore()
|
||||
const { BUILDINGS, SHIPS, DEFENSES, TECHNOLOGIES } = useGameConfig()
|
||||
|
||||
const isOpen = ref(false)
|
||||
|
||||
// 获取当前星球的建造队列
|
||||
const buildQueue = computed(() => {
|
||||
return gameStore.currentPlanet?.buildQueue || []
|
||||
})
|
||||
|
||||
// 获取研究队列
|
||||
const researchQueue = computed(() => {
|
||||
return gameStore.player.researchQueue || []
|
||||
})
|
||||
|
||||
// 总队列数量
|
||||
const totalQueueCount = computed(() => {
|
||||
return buildQueue.value.length + researchQueue.value.length
|
||||
})
|
||||
|
||||
// 获取队列项名称
|
||||
const getItemName = (item: BuildQueueItem): string => {
|
||||
if (item.type === 'building' || item.type === 'demolish') {
|
||||
return BUILDINGS.value[item.itemType as BuildingType].name
|
||||
} else if (item.type === 'ship') {
|
||||
return SHIPS.value[item.itemType as ShipType].name
|
||||
} else if (item.type === 'defense') {
|
||||
return DEFENSES.value[item.itemType as DefenseType].name
|
||||
} else if (item.type === 'technology') {
|
||||
return TECHNOLOGIES.value[item.itemType as TechnologyType].name
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
// 获取剩余时间
|
||||
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 elapsed = now - item.startTime
|
||||
const total = item.endTime - item.startTime
|
||||
return Math.min(100, (elapsed / total) * 100)
|
||||
}
|
||||
|
||||
// 统一的取消处理
|
||||
const handleCancel = (item: BuildQueueItem) => {
|
||||
let eventName: string
|
||||
if (item.type === 'building' || item.type === 'ship' || item.type === 'defense' || item.type === 'demolish') {
|
||||
eventName = 'cancel-build'
|
||||
} else if (item.type === 'technology') {
|
||||
eventName = 'cancel-research'
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
const event = new CustomEvent(eventName, { detail: item.id })
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
|
||||
// 取消研究
|
||||
const handleCancelResearch = (queueId: string) => {
|
||||
const event = new CustomEvent('cancel-research', { detail: queueId })
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
</script>
|
||||
438
src/components/TutorialOverlay.vue
Normal file
438
src/components/TutorialOverlay.vue
Normal file
@@ -0,0 +1,438 @@
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="tutorialState.isActive && currentStep" class="tutorial-overlay">
|
||||
<!-- Dark overlay parts (4 rectangles around the highlight) -->
|
||||
<template v-if="highlightRect && currentStep.target">
|
||||
<!-- Top overlay -->
|
||||
<div
|
||||
class="tutorial-backdrop-part"
|
||||
:style="{
|
||||
top: '0',
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: `${highlightRect.top}px`
|
||||
}"
|
||||
/>
|
||||
<!-- Bottom overlay -->
|
||||
<div
|
||||
class="tutorial-backdrop-part"
|
||||
:style="{
|
||||
top: `${highlightRect.bottom}px`,
|
||||
left: '0',
|
||||
width: '100%',
|
||||
height: `calc(100% - ${highlightRect.bottom}px)`
|
||||
}"
|
||||
/>
|
||||
<!-- Left overlay -->
|
||||
<div
|
||||
class="tutorial-backdrop-part"
|
||||
:style="{
|
||||
top: `${highlightRect.top}px`,
|
||||
left: '0',
|
||||
width: `${highlightRect.left}px`,
|
||||
height: `${highlightRect.height}px`
|
||||
}"
|
||||
/>
|
||||
<!-- Right overlay -->
|
||||
<div
|
||||
class="tutorial-backdrop-part"
|
||||
:style="{
|
||||
top: `${highlightRect.top}px`,
|
||||
left: `${highlightRect.right}px`,
|
||||
width: `calc(100% - ${highlightRect.right}px)`,
|
||||
height: `${highlightRect.height}px`
|
||||
}"
|
||||
/>
|
||||
<!-- Highlight border -->
|
||||
<div
|
||||
class="tutorial-highlight-border"
|
||||
:style="{
|
||||
top: `${highlightRect.top}px`,
|
||||
left: `${highlightRect.left}px`,
|
||||
width: `${highlightRect.width}px`,
|
||||
height: `${highlightRect.height}px`
|
||||
}"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- Full overlay for center placement (no target) -->
|
||||
<div v-else class="tutorial-backdrop-full" />
|
||||
|
||||
<!-- Tutorial tooltip -->
|
||||
<div
|
||||
v-if="tooltipPosition"
|
||||
class="tutorial-tooltip"
|
||||
:class="`tutorial-tooltip-${currentStep.placement || 'center'}`"
|
||||
:style="{
|
||||
top: tooltipPosition.top,
|
||||
left: tooltipPosition.left,
|
||||
transform: tooltipPosition.transform
|
||||
}"
|
||||
>
|
||||
<Card class="tutorial-card">
|
||||
<CardHeader class="pb-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<CardTitle class="text-lg">{{ t(currentStep.title) }}</CardTitle>
|
||||
<Button v-if="currentStep.canSkip" variant="ghost" size="icon" class="h-6 w-6" @click="skipTutorial">
|
||||
<XIcon :size="16" />
|
||||
</Button>
|
||||
</div>
|
||||
<CardDescription class="text-sm mt-2">
|
||||
{{ t(currentStep.content) }}
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent class="pt-0 space-y-3">
|
||||
<!-- Progress bar -->
|
||||
<div class="space-y-1">
|
||||
<div class="flex justify-between text-xs text-muted-foreground">
|
||||
<span>{{ t('tutorial.progress') }}</span>
|
||||
<span>{{ tutorialState.currentStepIndex + 1 }} / {{ totalSteps }}</span>
|
||||
</div>
|
||||
<div class="w-full bg-secondary rounded-full h-1.5">
|
||||
<div class="bg-primary h-1.5 rounded-full transition-all duration-300" :style="{ width: `${progress}%` }" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation buttons -->
|
||||
<div class="flex gap-2">
|
||||
<Button v-if="tutorialState.currentStepIndex > 0" variant="outline" size="sm" @click="previousStep">
|
||||
<ChevronLeftIcon :size="16" class="mr-1" />
|
||||
{{ t('tutorial.previous') }}
|
||||
</Button>
|
||||
|
||||
<Button v-if="!isLastStep" class="ml-auto" size="sm" @click="handleNext" :disabled="!canProceed">
|
||||
{{ t('tutorial.next') }}
|
||||
<ChevronRightIcon :size="16" class="ml-1" />
|
||||
</Button>
|
||||
|
||||
<Button v-else class="ml-auto" size="sm" @click="completeTutorial">
|
||||
{{ t('tutorial.completeButton') }}
|
||||
<CheckIcon :size="16" class="ml-1" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { useTutorial, getTutorialSteps } from '@/composables/useTutorial'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { XIcon, ChevronLeftIcon, ChevronRightIcon, CheckIcon } from 'lucide-vue-next'
|
||||
|
||||
const { t } = useI18n()
|
||||
const { tutorialState, currentStep, progress, isLastStep, nextStep, previousStep, skipTutorial, completeTutorial } = useTutorial()
|
||||
|
||||
const highlightRect = ref<DOMRect | null>(null)
|
||||
const tooltipPosition = ref<{ top: string; left: string; transform: string } | null>(null)
|
||||
const totalSteps = computed(() => getTutorialSteps().length)
|
||||
const isMobile = ref(false)
|
||||
|
||||
// Check if current step can proceed
|
||||
const canProceed = computed(() => {
|
||||
if (!currentStep.value) return false
|
||||
|
||||
// 所有步骤都允许手动点击下一步
|
||||
return true
|
||||
})
|
||||
|
||||
// 检测是否为移动端
|
||||
const checkMobile = () => {
|
||||
isMobile.value = window.innerWidth < 768
|
||||
}
|
||||
|
||||
// Calculate highlight and tooltip positions
|
||||
const updatePositions = () => {
|
||||
if (!currentStep.value) {
|
||||
highlightRect.value = null
|
||||
tooltipPosition.value = null
|
||||
return
|
||||
}
|
||||
|
||||
// 检测移动端
|
||||
checkMobile()
|
||||
|
||||
// For center placement, no target element needed
|
||||
if (!currentStep.value.target || currentStep.value.placement === 'center') {
|
||||
highlightRect.value = null
|
||||
tooltipPosition.value = {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Find target element
|
||||
const targetElement = document.querySelector(currentStep.value.target)
|
||||
if (!targetElement) {
|
||||
// Fallback to center if target not found
|
||||
highlightRect.value = null
|
||||
tooltipPosition.value = {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Auto-scroll target element into view
|
||||
targetElement.scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
block: 'center',
|
||||
inline: 'center'
|
||||
})
|
||||
|
||||
// Get target element rect
|
||||
const rect = targetElement.getBoundingClientRect()
|
||||
const padding = currentStep.value.highlightPadding || 8
|
||||
|
||||
// Set highlight rect with padding
|
||||
highlightRect.value = new DOMRect(rect.left - padding, rect.top - padding, rect.width + padding * 2, rect.height + padding * 2)
|
||||
|
||||
// 获取视口尺寸
|
||||
const viewportWidth = window.innerWidth
|
||||
const viewportHeight = window.innerHeight
|
||||
|
||||
// 气泡的预估尺寸(根据视口大小响应式调整)
|
||||
const tooltipWidth = isMobile.value ? Math.min(viewportWidth - 32, 360) : 480
|
||||
const tooltipHeight = isMobile.value ? 280 : 300 // 预估高度
|
||||
|
||||
// 计算各个方向的可用空间
|
||||
const spaceTop = rect.top
|
||||
const spaceBottom = viewportHeight - rect.bottom
|
||||
const spaceLeft = rect.left
|
||||
const spaceRight = viewportWidth - rect.right
|
||||
|
||||
const tooltipOffset = isMobile.value ? 8 : 16 // 移动端使用更小的间距
|
||||
const edgeMargin = isMobile.value ? 8 : 16 // 距离边缘的最小距离
|
||||
|
||||
// 根据优先级和可用空间自动选择最佳位置
|
||||
let placement = currentStep.value.placement || 'bottom'
|
||||
let finalPosition: { top: string; left: string; transform: string }
|
||||
|
||||
// 移动端优先使用 bottom 或 top 位置
|
||||
if (isMobile.value) {
|
||||
// 移动端强制使用 top/bottom,忽略 left/right
|
||||
if (placement === 'left' || placement === 'right') {
|
||||
placement = spaceBottom > spaceTop ? 'bottom' : 'top'
|
||||
}
|
||||
}
|
||||
|
||||
// 智能位置选择:如果指定位置空间不足,自动调整
|
||||
const canFitTop = spaceTop >= tooltipHeight + tooltipOffset + edgeMargin
|
||||
const canFitBottom = spaceBottom >= tooltipHeight + tooltipOffset + edgeMargin
|
||||
const canFitLeft = spaceLeft >= tooltipWidth + tooltipOffset + edgeMargin
|
||||
const canFitRight = spaceRight >= tooltipWidth + tooltipOffset + edgeMargin
|
||||
|
||||
// 自动调整位置
|
||||
if (placement === 'top' && !canFitTop && canFitBottom) {
|
||||
placement = 'bottom'
|
||||
} else if (placement === 'bottom' && !canFitBottom && canFitTop) {
|
||||
placement = 'top'
|
||||
} else if (placement === 'left' && !canFitLeft && canFitRight) {
|
||||
placement = 'right'
|
||||
} else if (placement === 'right' && !canFitRight && canFitLeft) {
|
||||
placement = 'left'
|
||||
}
|
||||
|
||||
// 计算位置
|
||||
switch (placement) {
|
||||
case 'top': {
|
||||
let left = rect.left + rect.width / 2
|
||||
// 确保不超出左右边界
|
||||
left = Math.max(tooltipWidth / 2 + edgeMargin, Math.min(left, viewportWidth - tooltipWidth / 2 - edgeMargin))
|
||||
finalPosition = {
|
||||
top: `${Math.max(edgeMargin, rect.top - tooltipOffset)}px`,
|
||||
left: `${left}px`,
|
||||
transform: 'translate(-50%, -100%)'
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'bottom': {
|
||||
let left = rect.left + rect.width / 2
|
||||
// 确保不超出左右边界
|
||||
left = Math.max(tooltipWidth / 2 + edgeMargin, Math.min(left, viewportWidth - tooltipWidth / 2 - edgeMargin))
|
||||
finalPosition = {
|
||||
top: `${Math.min(viewportHeight - tooltipHeight - edgeMargin, rect.bottom + tooltipOffset)}px`,
|
||||
left: `${left}px`,
|
||||
transform: 'translate(-50%, 0)'
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'left': {
|
||||
let top = rect.top + rect.height / 2
|
||||
// 确保不超出上下边界
|
||||
top = Math.max(tooltipHeight / 2 + edgeMargin, Math.min(top, viewportHeight - tooltipHeight / 2 - edgeMargin))
|
||||
finalPosition = {
|
||||
top: `${top}px`,
|
||||
left: `${Math.max(edgeMargin, rect.left - tooltipOffset)}px`,
|
||||
transform: 'translate(-100%, -50%)'
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'right': {
|
||||
let top = rect.top + rect.height / 2
|
||||
// 确保不超出上下边界
|
||||
top = Math.max(tooltipHeight / 2 + edgeMargin, Math.min(top, viewportHeight - tooltipHeight / 2 - edgeMargin))
|
||||
finalPosition = {
|
||||
top: `${top}px`,
|
||||
left: `${Math.min(viewportWidth - tooltipWidth - edgeMargin, rect.right + tooltipOffset)}px`,
|
||||
transform: 'translate(0, -50%)'
|
||||
}
|
||||
break
|
||||
}
|
||||
default:
|
||||
finalPosition = {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}
|
||||
}
|
||||
|
||||
tooltipPosition.value = finalPosition
|
||||
}
|
||||
|
||||
// Handle next step
|
||||
const handleNext = () => {
|
||||
if (canProceed.value) {
|
||||
nextStep()
|
||||
}
|
||||
}
|
||||
|
||||
// Update positions when step changes
|
||||
watch(
|
||||
() => currentStep.value,
|
||||
() => {
|
||||
// Wait for DOM update and route change
|
||||
setTimeout(() => {
|
||||
updatePositions()
|
||||
}, 100)
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// Update positions on window resize or scroll
|
||||
const handleResize = () => {
|
||||
checkMobile()
|
||||
updatePositions()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkMobile()
|
||||
window.addEventListener('resize', handleResize)
|
||||
window.addEventListener('scroll', handleResize, true)
|
||||
updatePositions()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
window.removeEventListener('scroll', handleResize, true)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tutorial-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tutorial-backdrop-part {
|
||||
position: fixed;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
pointer-events: auto;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tutorial-backdrop-full {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.tutorial-highlight-border {
|
||||
position: fixed;
|
||||
background: transparent;
|
||||
border: 4px solid rgba(59, 130, 246, 0.5);
|
||||
border-radius: 8px;
|
||||
pointer-events: none;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.tutorial-tooltip {
|
||||
position: fixed;
|
||||
z-index: 10001;
|
||||
pointer-events: auto;
|
||||
max-width: 480px;
|
||||
min-width: 320px;
|
||||
}
|
||||
|
||||
/* 移动端样式调整 */
|
||||
@media (max-width: 767px) {
|
||||
.tutorial-tooltip {
|
||||
max-width: calc(100vw - 32px);
|
||||
min-width: calc(100vw - 32px);
|
||||
width: calc(100vw - 32px);
|
||||
}
|
||||
|
||||
.tutorial-tooltip-center {
|
||||
max-width: calc(100vw - 32px);
|
||||
}
|
||||
|
||||
.tutorial-card {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.tutorial-highlight-border {
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-tooltip-center {
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.tutorial-tooltip-center {
|
||||
max-width: calc(100vw - 32px);
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-card {
|
||||
animation: tutorial-fade-in 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes tutorial-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Dark mode adjustments */
|
||||
.dark .tutorial-backdrop-part {
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.dark .tutorial-backdrop-full {
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
|
||||
.dark .tutorial-highlight-border {
|
||||
border-color: rgba(59, 130, 246, 0.6);
|
||||
}
|
||||
</style>
|
||||
@@ -7,7 +7,7 @@
|
||||
</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 class="prose prose-sm dark:prose-invert max-w-none" v-html="renderedMarkdown" />
|
||||
</div>
|
||||
|
||||
<DialogFooter class="flex gap-2 flex-shrink-0 mt-4">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="setOpenMobile">
|
||||
<Sheet v-else-if="isMobile" :open="openMobile" v-bind="$attrs" @update:open="handleOpenMobileChange">
|
||||
<SheetContent
|
||||
data-sidebar="sidebar"
|
||||
data-slot="sidebar"
|
||||
@@ -79,12 +79,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { SidebarProps } from '.'
|
||||
import { watch } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Sheet, SheetContent } from '@/components/ui/sheet'
|
||||
import SheetDescription from '@/components/ui/sheet/SheetDescription.vue'
|
||||
import SheetHeader from '@/components/ui/sheet/SheetHeader.vue'
|
||||
import SheetTitle from '@/components/ui/sheet/SheetTitle.vue'
|
||||
import { SIDEBAR_WIDTH_MOBILE, useSidebar } from './utils'
|
||||
import { useTutorial } from '@/composables/useTutorial'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false
|
||||
@@ -96,5 +99,51 @@
|
||||
collapsible: 'offcanvas'
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
|
||||
const { tutorialState, currentStep, nextStep } = useTutorial()
|
||||
|
||||
// 包装setOpenMobile以拦截教程期间的关闭操作
|
||||
const handleOpenMobileChange = (open: boolean) => {
|
||||
// 如果是移动端且在教程的菜单相关步骤,阻止关闭侧边栏
|
||||
if (tutorialState.value.isActive && currentStep.value) {
|
||||
// 只在第3步期间阻止关闭侧边栏,让玩家必须手动打开
|
||||
if (currentStep.value.id === 'menu_intro_mobile') {
|
||||
// 只允许打开,不允许关闭
|
||||
if (open) {
|
||||
setOpenMobile(true)
|
||||
}
|
||||
// 如果试图关闭,忽略该操作,保持打开状态
|
||||
return
|
||||
}
|
||||
}
|
||||
// 其他情况正常更新
|
||||
setOpenMobile(open)
|
||||
}
|
||||
|
||||
// 监听openMobile变化,在移动端教程第3步时,侧边栏打开后自动推进到第4步
|
||||
watch(
|
||||
() => openMobile.value,
|
||||
(isOpen) => {
|
||||
if (isMobile.value && tutorialState.value.isActive && currentStep.value) {
|
||||
// 如果在第3步且侧边栏刚打开,自动推进到第4步
|
||||
if (currentStep.value.id === 'menu_intro_mobile' && isOpen) {
|
||||
setTimeout(() => {
|
||||
nextStep()
|
||||
}, 300) // 延迟300ms让侧边栏动画完成
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 监听路由变化,在移动端关闭侧边栏
|
||||
watch(
|
||||
() => router.currentRoute.value.path,
|
||||
() => {
|
||||
if (isMobile.value && openMobile.value) {
|
||||
// 路由变化时关闭移动端侧边栏
|
||||
setOpenMobile(false)
|
||||
}
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -41,3 +41,9 @@
|
||||
|
||||
const props = defineProps<ToasterProps>()
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dark [data-sonner-toast][data-styled='true'] [data-description] {
|
||||
color: oklch(0.91 0 0 / 1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,6 +29,7 @@ export const useGameConfig = () => {
|
||||
[BuildingType.RoboticsFactory]: 'roboticsFactory',
|
||||
[BuildingType.NaniteFactory]: 'naniteFactory',
|
||||
[BuildingType.Shipyard]: 'shipyard',
|
||||
[BuildingType.Hangar]: 'hangar',
|
||||
[BuildingType.ResearchLab]: 'researchLab',
|
||||
[BuildingType.MetalStorage]: 'metalStorage',
|
||||
[BuildingType.CrystalStorage]: 'crystalStorage',
|
||||
|
||||
562
src/composables/useTutorial.ts
Normal file
562
src/composables/useTutorial.ts
Normal file
@@ -0,0 +1,562 @@
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import type { TutorialStep, TutorialState } from '@/types/game'
|
||||
import { BuildingType, TechnologyType } from '@/types/game'
|
||||
|
||||
// 桌面端引导步骤定义
|
||||
export const desktopTutorialSteps: TutorialStep[] = [
|
||||
// 第1步:欢迎和基础介绍
|
||||
{
|
||||
id: 'welcome',
|
||||
title: 'tutorial.welcome.title',
|
||||
content: 'tutorial.welcome.content',
|
||||
placement: 'center',
|
||||
route: '/',
|
||||
action: 'none',
|
||||
canSkip: true
|
||||
},
|
||||
// 第2步:资源栏介绍
|
||||
{
|
||||
id: 'resources_intro',
|
||||
title: 'tutorial.resources.title',
|
||||
content: 'tutorial.resources.content',
|
||||
target: '.resource-bar',
|
||||
placement: 'bottom',
|
||||
route: '/',
|
||||
action: 'none',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第3步:星球选择器介绍
|
||||
{
|
||||
id: 'planet_info',
|
||||
title: 'tutorial.planet.title',
|
||||
content: 'tutorial.planet.content',
|
||||
target: '[data-tutorial="planet-selector"]',
|
||||
placement: 'bottom',
|
||||
route: '/',
|
||||
action: 'none',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第4步:导航菜单介绍
|
||||
{
|
||||
id: 'navigation',
|
||||
title: 'tutorial.navigation.title',
|
||||
content: 'tutorial.navigation.content',
|
||||
target: '[data-tutorial="navigation"]',
|
||||
placement: 'right',
|
||||
route: '/',
|
||||
action: 'none',
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第5步:前往建筑页面
|
||||
{
|
||||
id: 'goto_buildings',
|
||||
title: 'tutorial.gotoBuildings.title',
|
||||
content: 'tutorial.gotoBuildings.content',
|
||||
target: '[data-nav-path="/buildings"]',
|
||||
placement: 'right',
|
||||
route: '/',
|
||||
action: 'click',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第6步:建造太阳能电站(提供能源,是所有资源建筑的前置条件)
|
||||
{
|
||||
id: 'build_solar_plant',
|
||||
title: 'tutorial.buildSolarPlant.title',
|
||||
content: 'tutorial.buildSolarPlant.content',
|
||||
target: `[data-building="${BuildingType.SolarPlant}"]`,
|
||||
placement: 'top',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.SolarPlant,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第7步:了解建造队列
|
||||
{
|
||||
id: 'wait_for_build',
|
||||
title: 'tutorial.waitBuild.title',
|
||||
content: 'tutorial.waitBuild.content',
|
||||
target: '[data-tutorial="queue-button"]',
|
||||
placement: 'bottom',
|
||||
route: '/buildings',
|
||||
action: 'none',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第8步:建造金属矿(有了太阳能电站后才能建造)
|
||||
{
|
||||
id: 'build_metal_mine',
|
||||
title: 'tutorial.buildMetalMine.title',
|
||||
content: 'tutorial.buildMetalMine.content',
|
||||
target: `[data-building="${BuildingType.MetalMine}"]`,
|
||||
placement: 'top',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.MetalMine,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第9步:建造晶体矿
|
||||
{
|
||||
id: 'build_crystal_mine',
|
||||
title: 'tutorial.buildCrystalMine.title',
|
||||
content: 'tutorial.buildCrystalMine.content',
|
||||
target: `[data-building="${BuildingType.CrystalMine}"]`,
|
||||
placement: 'top',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.CrystalMine,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第10步:建造重氢合成器
|
||||
{
|
||||
id: 'build_deuterium',
|
||||
title: 'tutorial.buildDeuterium.title',
|
||||
content: 'tutorial.buildDeuterium.content',
|
||||
target: `[data-building="${BuildingType.DeuteriumSynthesizer}"]`,
|
||||
placement: 'top',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.DeuteriumSynthesizer,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第11步:升级资源矿到2级(为机器人工厂做准备)
|
||||
{
|
||||
id: 'upgrade_mines_intro',
|
||||
title: 'tutorial.upgradeMines.title',
|
||||
content: 'tutorial.upgradeMines.content',
|
||||
placement: 'center',
|
||||
route: '/buildings',
|
||||
action: 'none'
|
||||
},
|
||||
// 第12步:建造机器人工厂(需要三种资源矿各2级)
|
||||
{
|
||||
id: 'build_robotics',
|
||||
title: 'tutorial.buildRobotics.title',
|
||||
content: 'tutorial.buildRobotics.content',
|
||||
target: `[data-building="${BuildingType.RoboticsFactory}"]`,
|
||||
placement: 'top',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.RoboticsFactory,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第13步:继续升级资源矿到3级(为研究实验室做准备)
|
||||
{
|
||||
id: 'upgrade_mines_for_lab',
|
||||
title: 'tutorial.upgradeMinesForLab.title',
|
||||
content: 'tutorial.upgradeMinesForLab.content',
|
||||
placement: 'center',
|
||||
route: '/buildings',
|
||||
action: 'none'
|
||||
},
|
||||
// 第14步:建造研究实验室(需要三种资源矿各3级)
|
||||
{
|
||||
id: 'build_research_lab',
|
||||
title: 'tutorial.buildResearchLab.title',
|
||||
content: 'tutorial.buildResearchLab.content',
|
||||
target: `[data-building="${BuildingType.ResearchLab}"]`,
|
||||
placement: 'top',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.ResearchLab,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第15步:前往研究页面
|
||||
{
|
||||
id: 'goto_research',
|
||||
title: 'tutorial.gotoResearch.title',
|
||||
content: 'tutorial.gotoResearch.content',
|
||||
target: '[data-nav-path="/research"]',
|
||||
placement: 'right',
|
||||
route: '/buildings',
|
||||
action: 'click',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第16步:研究能量科技
|
||||
{
|
||||
id: 'research_energy',
|
||||
title: 'tutorial.researchEnergy.title',
|
||||
content: 'tutorial.researchEnergy.content',
|
||||
target: `[data-tech="${TechnologyType.EnergyTechnology}"]`,
|
||||
placement: 'top',
|
||||
route: '/research',
|
||||
action: 'research',
|
||||
actionTarget: TechnologyType.EnergyTechnology,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第17步:介绍船坞(需要机器人工厂2级)
|
||||
{
|
||||
id: 'shipyard_intro',
|
||||
title: 'tutorial.shipyardIntro.title',
|
||||
content: 'tutorial.shipyardIntro.content',
|
||||
placement: 'center',
|
||||
route: '/research',
|
||||
action: 'none'
|
||||
},
|
||||
// 第18步:返回建筑页面建造船坞
|
||||
{
|
||||
id: 'goto_buildings_for_shipyard',
|
||||
title: 'tutorial.gotoBuildingsForShipyard.title',
|
||||
content: 'tutorial.gotoBuildingsForShipyard.content',
|
||||
target: '[data-nav-path="/buildings"]',
|
||||
placement: 'right',
|
||||
route: '/research',
|
||||
action: 'click',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第19步:建造船坞
|
||||
{
|
||||
id: 'build_shipyard',
|
||||
title: 'tutorial.buildShipyard.title',
|
||||
content: 'tutorial.buildShipyard.content',
|
||||
target: `[data-building="${BuildingType.Shipyard}"]`,
|
||||
placement: 'top',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.Shipyard,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第20步:舰队和探索介绍
|
||||
{
|
||||
id: 'fleet_intro',
|
||||
title: 'tutorial.fleetIntro.title',
|
||||
content: 'tutorial.fleetIntro.content',
|
||||
placement: 'center',
|
||||
route: '/buildings',
|
||||
action: 'none'
|
||||
},
|
||||
// 第21步:银河系探索介绍
|
||||
{
|
||||
id: 'galaxy_intro',
|
||||
title: 'tutorial.galaxyIntro.title',
|
||||
content: 'tutorial.galaxyIntro.content',
|
||||
target: '[data-nav-path="/galaxy"]',
|
||||
placement: 'right',
|
||||
route: '/buildings',
|
||||
action: 'none',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第22步:引导完成
|
||||
{
|
||||
id: 'tutorial_complete',
|
||||
title: 'tutorial.complete.title',
|
||||
content: 'tutorial.complete.content',
|
||||
placement: 'center',
|
||||
route: '/buildings',
|
||||
action: 'none'
|
||||
}
|
||||
]
|
||||
|
||||
// 移动端引导步骤定义
|
||||
export const mobileTutorialSteps: TutorialStep[] = [
|
||||
// 第1步:欢迎(移动端)
|
||||
{
|
||||
id: 'welcome_mobile',
|
||||
title: 'tutorial.mobile.welcome.title',
|
||||
content: 'tutorial.mobile.welcome.content',
|
||||
placement: 'center',
|
||||
route: '/',
|
||||
action: 'none',
|
||||
canSkip: true
|
||||
},
|
||||
// 第2步:顶部资源栏介绍
|
||||
{
|
||||
id: 'resources_intro_mobile',
|
||||
title: 'tutorial.mobile.resources.title',
|
||||
content: 'tutorial.mobile.resources.content',
|
||||
target: '.resource-bar',
|
||||
placement: 'bottom',
|
||||
route: '/',
|
||||
action: 'none',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第3步:汉堡菜单介绍 - 引导玩家手动点击打开菜单
|
||||
{
|
||||
id: 'menu_intro_mobile',
|
||||
title: 'tutorial.mobile.menu.title',
|
||||
content: 'tutorial.mobile.menu.content',
|
||||
target: '[data-tutorial="mobile-menu"]',
|
||||
placement: 'bottom',
|
||||
route: '/',
|
||||
action: 'none', // 让玩家手动点击汉堡菜单打开侧边栏
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第4步:前往建筑页面 - 此时侧边栏已打开,让玩家手动点击
|
||||
{
|
||||
id: 'goto_buildings_mobile',
|
||||
title: 'tutorial.mobile.gotoBuildings.title',
|
||||
content: 'tutorial.mobile.gotoBuildings.content',
|
||||
target: '[data-nav-path="/buildings"]',
|
||||
placement: 'right', // 改为right,因为菜单在左侧展开
|
||||
route: '/',
|
||||
action: 'click', // 使用click,但不会自动触发,只是用来标识这是一个点击操作
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第5步:建造太阳能电站
|
||||
{
|
||||
id: 'build_solar_plant_mobile',
|
||||
title: 'tutorial.mobile.buildSolarPlant.title',
|
||||
content: 'tutorial.mobile.buildSolarPlant.content',
|
||||
target: `[data-building="${BuildingType.SolarPlant}"]`,
|
||||
placement: 'bottom',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.SolarPlant,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第6步:了解建造队列
|
||||
{
|
||||
id: 'wait_for_build_mobile',
|
||||
title: 'tutorial.mobile.waitBuild.title',
|
||||
content: 'tutorial.mobile.waitBuild.content',
|
||||
target: '[data-tutorial="queue-button"]',
|
||||
placement: 'bottom',
|
||||
route: '/buildings',
|
||||
action: 'none',
|
||||
highlightPadding: 8
|
||||
},
|
||||
// 第7步:建造金属矿
|
||||
{
|
||||
id: 'build_metal_mine_mobile',
|
||||
title: 'tutorial.mobile.buildMetalMine.title',
|
||||
content: 'tutorial.mobile.buildMetalMine.content',
|
||||
target: `[data-building="${BuildingType.MetalMine}"]`,
|
||||
placement: 'bottom',
|
||||
route: '/buildings',
|
||||
action: 'build',
|
||||
actionTarget: BuildingType.MetalMine,
|
||||
highlightPadding: 12
|
||||
},
|
||||
// 第8步:完成教程
|
||||
{
|
||||
id: 'tutorial_complete_mobile',
|
||||
title: 'tutorial.mobile.complete.title',
|
||||
content: 'tutorial.mobile.complete.content',
|
||||
placement: 'center',
|
||||
route: '/buildings',
|
||||
action: 'none'
|
||||
}
|
||||
]
|
||||
|
||||
// 检测是否为移动端
|
||||
const isMobileDevice = () => {
|
||||
return window.innerWidth < 768
|
||||
}
|
||||
|
||||
// 根据设备类型获取教程步骤
|
||||
export const getTutorialSteps = (): TutorialStep[] => {
|
||||
return isMobileDevice() ? mobileTutorialSteps : desktopTutorialSteps
|
||||
}
|
||||
|
||||
// 导出统一的 tutorialSteps(为了兼容性)
|
||||
export const tutorialSteps = getTutorialSteps()
|
||||
|
||||
const tutorialState = ref<TutorialState>({
|
||||
isActive: false,
|
||||
currentStepIndex: 0,
|
||||
completedSteps: [],
|
||||
skipped: false
|
||||
})
|
||||
|
||||
export function useTutorial() {
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
|
||||
// 动态获取教程步骤
|
||||
const currentTutorialSteps = computed(() => getTutorialSteps())
|
||||
|
||||
const currentStep = computed(() => {
|
||||
if (!tutorialState.value.isActive || tutorialState.value.currentStepIndex >= currentTutorialSteps.value.length) {
|
||||
return null
|
||||
}
|
||||
return currentTutorialSteps.value[tutorialState.value.currentStepIndex]
|
||||
})
|
||||
|
||||
const progress = computed(() => {
|
||||
return Math.round((tutorialState.value.currentStepIndex / currentTutorialSteps.value.length) * 100)
|
||||
})
|
||||
|
||||
const isLastStep = computed(() => {
|
||||
return tutorialState.value.currentStepIndex === currentTutorialSteps.value.length - 1
|
||||
})
|
||||
|
||||
// 初始化引导
|
||||
const startTutorial = () => {
|
||||
const player = gameStore.player
|
||||
if (!player.tutorialProgress || !player.tutorialProgress.tutorialCompleted) {
|
||||
const now = Date.now()
|
||||
tutorialState.value = {
|
||||
isActive: true,
|
||||
currentStepIndex: 0,
|
||||
completedSteps: player.tutorialProgress?.completedStepIds || [],
|
||||
skipped: false,
|
||||
lastActiveTime: now
|
||||
}
|
||||
|
||||
// 如果有进度,恢复到上次的步骤
|
||||
if (player.tutorialProgress?.currentStep) {
|
||||
const stepIndex = currentTutorialSteps.value.findIndex((s: TutorialStep) => s.id === player.tutorialProgress?.currentStep)
|
||||
if (stepIndex !== -1) {
|
||||
tutorialState.value.currentStepIndex = stepIndex
|
||||
}
|
||||
}
|
||||
|
||||
// 跳转到当前步骤的路由
|
||||
if (currentStep.value?.route) {
|
||||
router.push(currentStep.value.route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下一步
|
||||
const nextStep = () => {
|
||||
if (!currentStep.value) return
|
||||
|
||||
// 标记当前步骤为已完成
|
||||
if (!tutorialState.value.completedSteps.includes(currentStep.value.id)) {
|
||||
tutorialState.value.completedSteps.push(currentStep.value.id)
|
||||
}
|
||||
|
||||
// 保存进度到store
|
||||
saveProgress()
|
||||
|
||||
// 移动到下一步
|
||||
if (tutorialState.value.currentStepIndex < currentTutorialSteps.value.length - 1) {
|
||||
tutorialState.value.currentStepIndex++
|
||||
|
||||
// 跳转到新步骤的路由
|
||||
if (currentStep.value?.route) {
|
||||
router.push(currentStep.value.route)
|
||||
}
|
||||
} else {
|
||||
// 引导完成
|
||||
completeTutorial()
|
||||
}
|
||||
}
|
||||
|
||||
// 上一步
|
||||
const previousStep = () => {
|
||||
if (tutorialState.value.currentStepIndex > 0) {
|
||||
tutorialState.value.currentStepIndex--
|
||||
|
||||
// 跳转到新步骤的路由
|
||||
if (currentStep.value?.route) {
|
||||
router.push(currentStep.value.route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 跳过引导
|
||||
const skipTutorial = () => {
|
||||
tutorialState.value.isActive = false
|
||||
tutorialState.value.skipped = true
|
||||
|
||||
// 保存跳过状态(跳过也视为已完成,避免刷新后重新弹出)
|
||||
if (!gameStore.player.tutorialProgress) {
|
||||
gameStore.player.tutorialProgress = {
|
||||
tutorialCompleted: true,
|
||||
completedStepIds: tutorialState.value.completedSteps,
|
||||
currentStep: null,
|
||||
skippedAt: Date.now()
|
||||
}
|
||||
} else {
|
||||
gameStore.player.tutorialProgress.tutorialCompleted = true
|
||||
gameStore.player.tutorialProgress.skippedAt = Date.now()
|
||||
gameStore.player.tutorialProgress.currentStep = null
|
||||
}
|
||||
}
|
||||
|
||||
// 完成引导
|
||||
const completeTutorial = () => {
|
||||
tutorialState.value.isActive = false
|
||||
|
||||
// 保存完成状态
|
||||
gameStore.player.tutorialProgress = {
|
||||
tutorialCompleted: true,
|
||||
completedStepIds: tutorialState.value.completedSteps,
|
||||
currentStep: null
|
||||
}
|
||||
}
|
||||
|
||||
// 保存进度
|
||||
const saveProgress = () => {
|
||||
if (!gameStore.player.tutorialProgress) {
|
||||
gameStore.player.tutorialProgress = {
|
||||
tutorialCompleted: false,
|
||||
completedStepIds: [],
|
||||
currentStep: null
|
||||
}
|
||||
}
|
||||
|
||||
gameStore.player.tutorialProgress.completedStepIds = [...tutorialState.value.completedSteps]
|
||||
gameStore.player.tutorialProgress.currentStep = currentStep.value?.id || null
|
||||
}
|
||||
|
||||
// 检查步骤完成条件
|
||||
const checkStepCompletion = (stepId: string): boolean => {
|
||||
const step = currentTutorialSteps.value.find((s: TutorialStep) => s.id === stepId)
|
||||
if (!step) return false
|
||||
|
||||
const planet = gameStore.currentPlanet
|
||||
if (!planet) return false
|
||||
|
||||
switch (step.action) {
|
||||
case 'build':
|
||||
if (step.actionTarget) {
|
||||
// 简单检查队列中是否有该建筑
|
||||
const inQueue = planet.buildQueue.some(
|
||||
item => item.itemType === step.actionTarget && (item.type === 'building' || item.type === 'demolish')
|
||||
)
|
||||
return inQueue
|
||||
}
|
||||
return false
|
||||
|
||||
case 'research':
|
||||
if (step.actionTarget) {
|
||||
// 简单检查队列中是否有该科技
|
||||
const inQueue = planet.buildQueue.some(item => item.itemType === step.actionTarget && item.type === 'technology')
|
||||
return inQueue
|
||||
}
|
||||
return false
|
||||
|
||||
case 'click':
|
||||
case 'none':
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 不再自动推进,完全由玩家手动点击"下一步"按钮控制
|
||||
|
||||
// 监听路由变化,自动推进需要导航的教程步骤
|
||||
watch(
|
||||
() => router.currentRoute.value.path,
|
||||
newPath => {
|
||||
if (tutorialState.value.isActive && currentStep.value) {
|
||||
// 如果当前步骤需要导航到特定页面,且已经到达该页面,自动推进
|
||||
if (currentStep.value.action === 'none' && currentStep.value.target && currentStep.value.target.includes('data-nav-path')) {
|
||||
// 提取目标路径
|
||||
const match = currentStep.value.target.match(/data-nav-path="([^"]+)"/)
|
||||
if (match && match[1] === newPath) {
|
||||
setTimeout(() => {
|
||||
nextStep()
|
||||
}, 500)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
tutorialState,
|
||||
currentStep,
|
||||
progress,
|
||||
isLastStep,
|
||||
startTutorial,
|
||||
nextStep,
|
||||
previousStep,
|
||||
skipTutorial,
|
||||
completeTutorial,
|
||||
checkStepCompletion
|
||||
}
|
||||
}
|
||||
@@ -130,6 +130,21 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
|
||||
12: { [BuildingType.RoboticsFactory]: 8, [BuildingType.ResearchLab]: 8, [BuildingType.NaniteFactory]: 2 }
|
||||
}
|
||||
},
|
||||
[BuildingType.Hangar]: {
|
||||
id: BuildingType.Hangar,
|
||||
name: '机库',
|
||||
description: '专门用于扩展舰队存储容量,支持星球专业化发展',
|
||||
baseCost: { metal: 200, crystal: 100, deuterium: 50, darkMatter: 0, energy: 0 },
|
||||
baseTime: 20,
|
||||
costMultiplier: 1.8,
|
||||
spaceUsage: 3,
|
||||
fleetStorageBonus: 1500, // 每级增加1500舰队仓储,比船坞更高
|
||||
requirements: { [BuildingType.RoboticsFactory]: 1 }, // 只需要1级机器人工厂
|
||||
levelRequirements: {
|
||||
10: { [BuildingType.RoboticsFactory]: 3 },
|
||||
20: { [BuildingType.RoboticsFactory]: 5 }
|
||||
}
|
||||
},
|
||||
[BuildingType.ResearchLab]: {
|
||||
id: BuildingType.ResearchLab,
|
||||
name: '研究实验室',
|
||||
@@ -674,13 +689,13 @@ export const SHIPS: Record<ShipType, ShipConfig> = {
|
||||
[ShipType.Battleship]: {
|
||||
id: ShipType.Battleship,
|
||||
name: '战列舰',
|
||||
description: '重型战舰',
|
||||
description: '重型战舰,主力作战单位',
|
||||
cost: { metal: 45000, crystal: 15000, deuterium: 0, darkMatter: 0, energy: 0 },
|
||||
buildTime: 90,
|
||||
cargoCapacity: 1500,
|
||||
attack: 1000,
|
||||
shield: 200,
|
||||
armor: 6000,
|
||||
attack: 1200,
|
||||
shield: 300,
|
||||
armor: 10000,
|
||||
speed: 10000,
|
||||
fuelConsumption: 500,
|
||||
storageUsage: 25,
|
||||
@@ -728,13 +743,13 @@ export const SHIPS: Record<ShipType, ShipConfig> = {
|
||||
[ShipType.Destroyer]: {
|
||||
id: ShipType.Destroyer,
|
||||
name: '驱逐舰',
|
||||
description: '擅长摧毁大型舰船的猎杀者',
|
||||
description: '专业反大型舰船战舰,高火力低防护',
|
||||
cost: { metal: 60000, crystal: 50000, deuterium: 15000, darkMatter: 0, energy: 0 },
|
||||
buildTime: 120,
|
||||
cargoCapacity: 2000,
|
||||
attack: 2000,
|
||||
shield: 500,
|
||||
armor: 11000,
|
||||
attack: 2500,
|
||||
shield: 250,
|
||||
armor: 8000,
|
||||
speed: 5000,
|
||||
fuelConsumption: 1000,
|
||||
storageUsage: 40,
|
||||
|
||||
@@ -8,6 +8,7 @@ export default {
|
||||
close: 'Schließen',
|
||||
back: 'Zurück',
|
||||
next: 'Weiter',
|
||||
gotIt: '',
|
||||
previous: 'Vorherige',
|
||||
submit: 'Absenden',
|
||||
reset: 'Zurücksetzen',
|
||||
@@ -125,6 +126,7 @@ export default {
|
||||
roboticsFactory: 'Roboterfabrik',
|
||||
naniteFactory: 'Nanitenfabrik',
|
||||
shipyard: 'Raumschiffwerft',
|
||||
hangar: 'Hangar',
|
||||
researchLab: 'Forschungslabor',
|
||||
metalStorage: 'Metallspeicher',
|
||||
crystalStorage: 'Kristallspeicher',
|
||||
@@ -165,6 +167,7 @@ export default {
|
||||
roboticsFactory: 'Beschleunigt Baugeschwindigkeit',
|
||||
naniteFactory: 'Erhöht Bauauftragskapazität, +1 pro Stufe (max 10 Stufen)',
|
||||
shipyard: 'Baut Schiffe',
|
||||
hangar: 'Spezialisierte Einrichtung zur Erweiterung der Flottenspeicherkapazität, unterstützt Planetenspezialisierung',
|
||||
researchLab: 'Erforscht Technologien',
|
||||
metalStorage: 'Erhöht Metallspeicherkapazität',
|
||||
crystalStorage: 'Erhöht Kristallspeicherkapazität',
|
||||
@@ -199,10 +202,10 @@ export default {
|
||||
lightFighter: 'Grundlegende Kampfeinheit',
|
||||
heavyFighter: 'Schwer gepanzerter Jäger',
|
||||
cruiser: 'Mittleres Kriegsschiff, ausgewogene Offensive und Defensive',
|
||||
battleship: 'Mächtiges Kriegsschiff',
|
||||
battleship: 'Schweres Hauptkriegsschiff mit starker Feuerkraft und hoher Verteidigung',
|
||||
battlecruiser: 'Schnelles mächtiges Kriegsschiff, hervorragend gegen Schlachtschiffe',
|
||||
bomber: 'Spezialisiertes Schiff zum Angriff auf Verteidigungsanlagen',
|
||||
destroyer: 'Jäger spezialisiert auf Zerstörung großer Schiffe',
|
||||
destroyer: 'Spezialisiertes Anti-Großschiff mit hoher Feuerkraft aber geringer Verteidigung',
|
||||
smallCargo: 'Transportiert kleine Mengen Ressourcen',
|
||||
largeCargo: 'Transportiert große Mengen Ressourcen',
|
||||
colonyShip: 'Zur Kolonisierung neuer Planeten',
|
||||
@@ -312,10 +315,13 @@ export default {
|
||||
darkMatterSpecialist: 'Verbessert Dunkle-Materie-Sammlungseffizienz'
|
||||
},
|
||||
queue: {
|
||||
title: 'Bauauftrag',
|
||||
empty: 'Keine aktiven Aufgaben',
|
||||
buildQueue: 'Bauauftrag',
|
||||
researchQueue: 'Forschungsauftrag',
|
||||
building: 'Im Bau',
|
||||
researching: 'In Forschung',
|
||||
demolishing: 'Wird abgerissen',
|
||||
remaining: 'Verbleibend',
|
||||
cancel: 'Abbrechen',
|
||||
cancelBuild: 'Bau abbrechen',
|
||||
@@ -587,11 +593,13 @@ export default {
|
||||
battles: 'Kämpfe',
|
||||
spy: 'Spionage',
|
||||
npc: 'NPC',
|
||||
diplomacy: '',
|
||||
spied: 'Ausspioniert',
|
||||
battleReports: 'Kampfberichte',
|
||||
spyReports: 'Spionageberichte',
|
||||
noBattleReports: 'Keine Kampfberichte',
|
||||
noSpyReports: 'Keine Spionageberichte',
|
||||
noDiplomaticReports: '',
|
||||
noSpiedNotifications: 'Keine Ausspionierungs-Benachrichtigungen',
|
||||
battleReport: 'Kampfbericht',
|
||||
spyReport: 'Spionagebericht',
|
||||
@@ -646,7 +654,38 @@ export default {
|
||||
hostile: 'Sie sind feindlich und nehmen keine Geschenke an',
|
||||
neutral_distrust: 'Sie vertrauen Ihnen nicht',
|
||||
polite_decline: 'Sie lehnten höflich ab'
|
||||
}
|
||||
},
|
||||
// Spied notification dialog
|
||||
spiedNotificationDetails: '',
|
||||
spyDetected: '',
|
||||
detectionResult: '',
|
||||
detectionSuccess: '',
|
||||
spiedNotificationMessage: '',
|
||||
spiedNotificationTip: '',
|
||||
viewInGalaxy: '',
|
||||
// Mission report dialog
|
||||
missionReportDetails: '',
|
||||
missionSuccess: '',
|
||||
missionFailed: '',
|
||||
origin: '',
|
||||
destination: '',
|
||||
missionDetails: '',
|
||||
transportedResources: '',
|
||||
recycledResources: '',
|
||||
remainingDebris: '',
|
||||
newPlanet: '',
|
||||
// NPC activity dialog
|
||||
npcActivityDetails: '',
|
||||
activityType: {
|
||||
recycle: ''
|
||||
},
|
||||
activityLocation: '',
|
||||
position: '',
|
||||
nearPlanet: '',
|
||||
activityDescription: '',
|
||||
npcActivityMessage: '',
|
||||
arrivalTime: '',
|
||||
npcActivityTip: ''
|
||||
},
|
||||
missionReports: {
|
||||
transportSuccess: 'Transportmission erfolgreich abgeschlossen',
|
||||
@@ -802,7 +841,8 @@ export default {
|
||||
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',
|
||||
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',
|
||||
@@ -840,6 +880,10 @@ export default {
|
||||
recentEvents: 'Aktuelle Ereignisse',
|
||||
recentEventsDescription: 'Protokoll der jüngsten diplomatischen Aktivitäten',
|
||||
ago: 'vor',
|
||||
notifications: '',
|
||||
markAllRead: '',
|
||||
noReports: '',
|
||||
viewAll: '',
|
||||
status: {
|
||||
friendly: 'Freundlich',
|
||||
neutral: 'Neutral',
|
||||
@@ -855,10 +899,17 @@ export default {
|
||||
viewPlanets: 'Planeten ansehen'
|
||||
},
|
||||
lastEvent: 'Letztes Ereignis',
|
||||
reportDetails: '',
|
||||
eventDescription: '',
|
||||
reputationChange: '',
|
||||
before: '',
|
||||
after: '',
|
||||
statusChange: '',
|
||||
viewDiplomacy: '',
|
||||
events: {
|
||||
gift: 'Geschenk gesendet',
|
||||
attack: 'Angriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
missileAttack: 'Raketenangriff',
|
||||
allyAttacked: 'Verbündeter angegriffen',
|
||||
spy: 'Spionage',
|
||||
stealDebris: 'Trümmer gestohlen'
|
||||
@@ -883,12 +934,20 @@ export default {
|
||||
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}'
|
||||
rejectedGiftFromNpc: 'Sie haben ein Geschenk von {npcName} abgelehnt. Ansehen {reputation}',
|
||||
destroyedNpcPlanet: '{npcName}s {planetName} zerstört',
|
||||
playerDestroyedPlanet: 'Spieler hat {planetName} zerstört',
|
||||
youDestroyedNpcPlanet: 'Sie haben {npcName}s {planetName} zerstört. Ansehen {reputation}',
|
||||
playerDestroyedAllyPlanet: 'Spieler hat Verbündeten {allyName}s {planetName} zerstört',
|
||||
allyOutraged: '{allyName} ist empört, dass Sie den Planeten {planetName} ihres Verbündeten {targetName} zerstört haben',
|
||||
npcEliminated: 'NPC {npcName} wurde vollständig eliminiert',
|
||||
npcEliminatedMessage: 'Sie haben alle Planeten von {npcName} zerstört! Diese Fraktion wurde vollständig ausgelöscht.'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
previous: 'Vorherige',
|
||||
next: 'Nächste',
|
||||
gotIt: '',
|
||||
first: 'Erste',
|
||||
last: 'Letzte',
|
||||
page: 'Seite {page}'
|
||||
@@ -897,5 +956,39 @@ export default {
|
||||
title: 'Seite nicht gefunden',
|
||||
description: 'Entschuldigung, die gesuchte Seite existiert nicht',
|
||||
goHome: 'Zur Startseite'
|
||||
},
|
||||
time: {
|
||||
days: 'Tage',
|
||||
hours: 'Stunden',
|
||||
minutes: 'Minuten',
|
||||
seconds: 'Sekunden'
|
||||
},
|
||||
tutorial: {
|
||||
welcome: {
|
||||
title: 'Willkommen bei OGame',
|
||||
content: 'Willkommen, Kommandant! Beginnen wir mit den Grundlagen und bauen Sie Ihr Weltraum-Imperium auf.'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: 'Solarkraftwerk bauen',
|
||||
content:
|
||||
'Bauen Sie zuerst ein Solarkraftwerk! Es liefert Energie für Ihren Planeten. Ohne Energie können andere Ressourcengebäude nicht funktionieren. Dies ist der wichtigste erste Schritt.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: 'Bauauftrag',
|
||||
content:
|
||||
'Ihr Gebäude befindet sich jetzt in der Bauauftragsliste. Klicken Sie auf das Warteschlangensymbol oben rechts, um alle laufenden Bau- und Forschungsaufträge anzuzeigen. Gebäude brauchen Zeit zum Fertigstellen, aber Sie können während des Wartens weitermachen.'
|
||||
},
|
||||
mobile: {
|
||||
welcome: {
|
||||
title: 'Willkommen bei OGame (Mobil)',
|
||||
content:
|
||||
'Willkommen, Kommandant! Dies ist ein optimiertes Tutorial für Touchscreens. Wir werden schnell die Kernfunktionen durchgehen, damit Sie mit dem Aufbau Ihres Imperiums beginnen können.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: 'Bauauftrag',
|
||||
content:
|
||||
'Klicken Sie auf das Warteschlangensymbol oben rechts, um den Baufortschritt anzuzeigen. Sie können weiter andere Seiten durchsuchen - der Bau läuft im Hintergrund.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ export default {
|
||||
roboticsFactory: 'Robotics Factory',
|
||||
naniteFactory: 'Nanite Factory',
|
||||
shipyard: 'Shipyard',
|
||||
hangar: 'Hangar',
|
||||
researchLab: 'Research Lab',
|
||||
metalStorage: 'Metal Storage',
|
||||
crystalStorage: 'Crystal Storage',
|
||||
@@ -163,6 +164,7 @@ export default {
|
||||
roboticsFactory: 'Accelerates construction speed',
|
||||
naniteFactory: 'Increases build queue capacity, +1 per level (max 10 levels)',
|
||||
shipyard: 'Constructs ships',
|
||||
hangar: 'Specialized facility for expanding fleet storage capacity, supports planetary specialization',
|
||||
researchLab: 'Researches technologies',
|
||||
metalStorage: 'Increases metal storage capacity',
|
||||
crystalStorage: 'Increases crystal storage capacity',
|
||||
@@ -197,10 +199,10 @@ export default {
|
||||
lightFighter: 'Basic combat unit',
|
||||
heavyFighter: 'Heavily armored fighter',
|
||||
cruiser: 'Medium warship, balanced offense and defense',
|
||||
battleship: 'Powerful warship',
|
||||
battleship: 'Main heavy warship with powerful firepower and strong defense',
|
||||
battlecruiser: 'Fast powerful warship, excels at attacking battleships',
|
||||
bomber: 'Specialized ship for attacking defense structures',
|
||||
destroyer: 'Hunter specialized in destroying large ships',
|
||||
destroyer: 'Specialized anti-capital ship with high firepower but low defense',
|
||||
smallCargo: 'Transports small amounts of resources',
|
||||
largeCargo: 'Transports large amounts of resources',
|
||||
colonyShip: 'Used to colonize new planets',
|
||||
@@ -312,6 +314,8 @@ export default {
|
||||
darkMatterSpecialist: 'Improves dark matter collection efficiency'
|
||||
},
|
||||
queue: {
|
||||
title: 'Build Queue',
|
||||
empty: 'No active tasks',
|
||||
buildQueueBonus: 'Build Queue',
|
||||
spaceBonus: 'Space Bonus',
|
||||
buildSpeedBonus: 'Build Speed Bonus',
|
||||
@@ -319,6 +323,7 @@ export default {
|
||||
researchQueueBonus: 'Research Queue',
|
||||
building: 'Building',
|
||||
researching: 'Researching',
|
||||
demolishing: 'Demolishing',
|
||||
remaining: 'Remaining',
|
||||
cancel: 'Cancel',
|
||||
cancelBuild: 'Cancel Build',
|
||||
@@ -477,7 +482,8 @@ export default {
|
||||
recallFleet: 'Recall Fleet',
|
||||
abortMission: 'Abort Mission',
|
||||
abortMissionTitle: 'Confirm Abort Mission',
|
||||
abortMissionWarning: 'WARNING: Aborting this mission will permanently lose {ships} ships and {resources} resources!\n\nThis action is irreversible and the fleet and resources will not return.',
|
||||
abortMissionWarning:
|
||||
'WARNING: Aborting this mission will permanently lose {ships} ships and {resources} resources!\n\nThis action is irreversible and the fleet and resources will not return.',
|
||||
abortMissionSuccess: 'Mission Aborted',
|
||||
abortMissionSuccessMessage: 'Mission has been aborted, fleet and resources are lost.',
|
||||
sendFailed: 'Send Failed',
|
||||
@@ -588,10 +594,12 @@ export default {
|
||||
battles: 'Battles',
|
||||
spy: 'Spy',
|
||||
npc: 'NPC',
|
||||
diplomacy: 'Diplomacy',
|
||||
battleReports: 'Battle Reports',
|
||||
spyReports: 'Spy Reports',
|
||||
noBattleReports: 'No battle reports',
|
||||
noSpyReports: 'No spy reports',
|
||||
noDiplomaticReports: 'No diplomatic reports',
|
||||
battleReport: 'Battle Report',
|
||||
spyReport: 'Spy Report',
|
||||
victory: 'Victory',
|
||||
@@ -639,7 +647,38 @@ export default {
|
||||
hostile: 'They are hostile towards you and do not accept gifts',
|
||||
neutral_distrust: 'They lack trust in you',
|
||||
polite_decline: 'They politely declined'
|
||||
}
|
||||
},
|
||||
// Spied notification dialog
|
||||
spiedNotificationDetails: 'Spied Notification Details',
|
||||
spyDetected: 'Spy Detected',
|
||||
detectionResult: 'Detection Result',
|
||||
detectionSuccess: 'Your spy probe was detected!',
|
||||
spiedNotificationMessage: '{npc} attempted to spy on your planet {planet}',
|
||||
spiedNotificationTip: 'Consider increasing your defense or counter-attacking if this NPC is hostile',
|
||||
viewInGalaxy: 'View in Galaxy',
|
||||
// Mission report dialog
|
||||
missionReportDetails: 'Mission Report Details',
|
||||
missionSuccess: 'Success',
|
||||
missionFailed: 'Failed',
|
||||
origin: 'Origin',
|
||||
destination: 'Destination',
|
||||
missionDetails: 'Mission Details',
|
||||
transportedResources: 'Transported Resources',
|
||||
recycledResources: 'Recycled Resources',
|
||||
remainingDebris: 'Remaining Debris',
|
||||
newPlanet: 'New Planet',
|
||||
// NPC activity dialog
|
||||
npcActivityDetails: 'NPC Activity Details',
|
||||
activityType: {
|
||||
recycle: 'Recycling Debris'
|
||||
},
|
||||
activityLocation: 'Activity Location',
|
||||
position: 'Position',
|
||||
nearPlanet: 'Near Planet',
|
||||
activityDescription: 'Activity Description',
|
||||
npcActivityMessage: '{npc} is {activity} at {position}',
|
||||
arrivalTime: 'Arrival Time',
|
||||
npcActivityTip: 'NPCs may collect debris from battles. You can try to reach the location first if you want to compete for resources'
|
||||
},
|
||||
missionReports: {
|
||||
transportSuccess: 'Transport mission completed successfully',
|
||||
@@ -798,7 +837,8 @@ export default {
|
||||
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',
|
||||
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',
|
||||
@@ -806,7 +846,8 @@ export default {
|
||||
completeAllQueues: 'Complete All Queues',
|
||||
completeAllQueuesDesc: 'Instantly complete all building, research, ship, defense queues and fleet missions',
|
||||
completeQueues: 'Complete Queues',
|
||||
completeQueuesSuccess: 'Completed {buildingCount} building queues, {researchCount} research queues, {missionCount} fleet missions, {missileCount} missile attacks'
|
||||
completeQueuesSuccess:
|
||||
'Completed {buildingCount} building queues, {researchCount} research queues, {missionCount} fleet missions, {missileCount} missile attacks'
|
||||
},
|
||||
alerts: {
|
||||
npcSpyIncoming: 'NPC Spy Probe Incoming',
|
||||
@@ -836,6 +877,10 @@ export default {
|
||||
recentEvents: 'Recent Events',
|
||||
recentEventsDescription: 'Recent diplomatic activity log',
|
||||
ago: 'ago',
|
||||
notifications: 'Diplomatic Notifications',
|
||||
markAllRead: 'Mark All Read',
|
||||
noReports: 'No diplomatic events',
|
||||
viewAll: 'View All',
|
||||
status: {
|
||||
friendly: 'Friendly',
|
||||
neutral: 'Neutral',
|
||||
@@ -851,6 +896,13 @@ export default {
|
||||
viewPlanets: 'View Planets'
|
||||
},
|
||||
lastEvent: 'Last Event',
|
||||
reportDetails: 'Diplomatic Report Details',
|
||||
eventDescription: 'Event Description',
|
||||
reputationChange: 'Reputation Change',
|
||||
before: 'Before',
|
||||
after: 'After',
|
||||
statusChange: 'Status Change',
|
||||
viewDiplomacy: 'View Diplomacy Page',
|
||||
events: {
|
||||
gift: 'Sent Gift',
|
||||
attack: 'Attack',
|
||||
@@ -863,7 +915,7 @@ export default {
|
||||
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',
|
||||
rejectedPlayerGift: "Rejected player's gift",
|
||||
npcRejectedGift: '{npcName} rejected your gift. Reputation {reputation}',
|
||||
attackedNpc: 'Attacked {npcName}',
|
||||
wasAttackedByPlayer: 'Was attacked by player',
|
||||
@@ -872,14 +924,21 @@ export default {
|
||||
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',
|
||||
stoleDebrisFromTerritory: "Stole debris from {npcName}'s territory",
|
||||
playerStoleDebris: 'Player stole debris from territory',
|
||||
recycledDebrisNearNpc: 'You recycled debris near {npcName}\'s planet. They are displeased.',
|
||||
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}'
|
||||
rejectedGiftFromNpc: 'You rejected a gift from {npcName}. Reputation {reputation}',
|
||||
destroyedNpcPlanet: "Destroyed {npcName}'s {planetName}",
|
||||
playerDestroyedPlanet: 'Player destroyed {planetName}',
|
||||
youDestroyedNpcPlanet: "You destroyed {npcName}'s {planetName}. Reputation {reputation}",
|
||||
playerDestroyedAllyPlanet: "Player destroyed ally {allyName}'s {planetName}",
|
||||
allyOutraged: "{allyName} is outraged that you destroyed their ally {targetName}'s {planetName}",
|
||||
npcEliminated: 'NPC {npcName} has been completely eliminated',
|
||||
npcEliminatedMessage: "You destroyed all of {npcName}'s planets! This faction has been completely wiped out."
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -893,5 +952,162 @@ export default {
|
||||
title: 'Page Not Found',
|
||||
description: 'Sorry, the page you are looking for does not exist',
|
||||
goHome: 'Go Home'
|
||||
},
|
||||
time: {
|
||||
days: 'days',
|
||||
hours: 'hours',
|
||||
minutes: 'minutes',
|
||||
seconds: 'seconds'
|
||||
},
|
||||
tutorial: {
|
||||
progress: 'Progress',
|
||||
previous: 'Previous',
|
||||
next: 'Next',
|
||||
gotIt: 'Got It',
|
||||
completeButton: 'Complete',
|
||||
skip: 'Skip Tutorial',
|
||||
welcome: {
|
||||
title: 'Welcome to OGame',
|
||||
content:
|
||||
'Welcome, Commander! This tutorial will guide you through the basics of building your empire. Click "Next" to begin your journey.'
|
||||
},
|
||||
resources: {
|
||||
title: 'Resource Overview',
|
||||
content:
|
||||
'These are your resources: Metal, Crystal, and Deuterium. They are essential for building structures and researching technologies. Energy is also important to power your infrastructure.'
|
||||
},
|
||||
planet: {
|
||||
title: 'Your Planet',
|
||||
content: 'This is your home planet. You can view its name, coordinates, and switch between planets here as you expand your empire.'
|
||||
},
|
||||
navigation: {
|
||||
title: 'Navigation Menu',
|
||||
content:
|
||||
'Use this menu to navigate between different sections: Buildings, Research, Fleet, Galaxy, and more. Each section offers unique gameplay features.'
|
||||
},
|
||||
gotoBuildings: {
|
||||
title: 'Go to Buildings',
|
||||
content: 'Let\'s start by building some structures. Click on the "Buildings" menu item to view available structures.'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: 'Build Solar Plant',
|
||||
content:
|
||||
'First, build a Solar Plant! It generates energy for your planet. Without energy, other resource buildings cannot function. This is the most important first step.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: 'Build Queue',
|
||||
content:
|
||||
'Your building is now in the construction queue. Click the queue icon in the top-right corner to view all ongoing construction and research tasks. Buildings take time to complete, but you can continue working while waiting.'
|
||||
},
|
||||
buildMetalMine: {
|
||||
title: 'Build Metal Mine',
|
||||
content:
|
||||
"Now that you have energy, you can build the Metal Mine. It's your primary source of metal, which is used in almost every structure and ship."
|
||||
},
|
||||
buildCrystalMine: {
|
||||
title: 'Build Crystal Mine',
|
||||
content: 'Crystal is rarer but essential for advanced technologies. Build a Crystal Mine to start collecting this valuable resource.'
|
||||
},
|
||||
buildDeuterium: {
|
||||
title: 'Build Deuterium Synthesizer',
|
||||
content:
|
||||
'Deuterium is essential for ship fuel and advanced research. Build a Deuterium Synthesizer to start producing this critical resource.'
|
||||
},
|
||||
upgradeMines: {
|
||||
title: 'Upgrade Resource Mines',
|
||||
content:
|
||||
'Next, you need to upgrade all three resource mines (Metal, Crystal, Deuterium) to level 2 to meet the requirements for building a Robotics Factory. Upgrade them when you have enough resources.'
|
||||
},
|
||||
buildRobotics: {
|
||||
title: 'Build Robotics Factory',
|
||||
content:
|
||||
'The Robotics Factory significantly speeds up construction. It requires Metal Mine, Crystal Mine, and Deuterium Synthesizer at level 2 each. Build it to improve construction efficiency!'
|
||||
},
|
||||
upgradeMinesForLab: {
|
||||
title: 'Continue Upgrading Mines',
|
||||
content:
|
||||
'Now you need to upgrade all three resource mines to level 3 to meet the requirements for the Research Lab. Keep developing your resource production.'
|
||||
},
|
||||
buildResearchLab: {
|
||||
title: 'Build Research Lab',
|
||||
content:
|
||||
'The Research Lab is the foundation of technological advancement. It requires all three resource mines at level 3. Build it to unlock technology research!'
|
||||
},
|
||||
gotoResearch: {
|
||||
title: 'Go to Research',
|
||||
content: 'Now that you have a Research Lab, click on the "Research" menu to view available technologies.'
|
||||
},
|
||||
researchEnergy: {
|
||||
title: 'Research Energy Technology',
|
||||
content:
|
||||
"Energy Technology improves your energy production and unlocks advanced structures. It's one of the most fundamental and important technologies."
|
||||
},
|
||||
shipyardIntro: {
|
||||
title: 'Fleet and Shipyard',
|
||||
content:
|
||||
'Ships allow you to explore the galaxy, transport resources, and defend your empire. To build ships, you need a Shipyard (requires Robotics Factory level 2).'
|
||||
},
|
||||
gotoBuildingsForShipyard: {
|
||||
title: 'Return to Buildings',
|
||||
content: 'Go back to the Buildings page to construct your Shipyard.'
|
||||
},
|
||||
buildShipyard: {
|
||||
title: 'Build Shipyard',
|
||||
content: 'The Shipyard allows you to construct ships and defense systems. This is crucial for fleet operations.'
|
||||
},
|
||||
fleetIntro: {
|
||||
title: 'Fleet Operations',
|
||||
content:
|
||||
'Once you have ships, you can send them on missions: transport resources, colonize planets, attack enemies, or explore debris fields.'
|
||||
},
|
||||
galaxyIntro: {
|
||||
title: 'Explore the Galaxy',
|
||||
content:
|
||||
'The Galaxy view shows other planets, debris fields, and opportunities for expansion. Use it to scout targets and plan your strategy.'
|
||||
},
|
||||
complete: {
|
||||
title: 'Tutorial Complete!',
|
||||
content:
|
||||
'Congratulations, Commander! You now know the basics. Continue building your empire, research technologies, and explore the galaxy. Remember: develop energy first, then resources, then factories and research! Good luck!'
|
||||
},
|
||||
// Mobile tutorial
|
||||
mobile: {
|
||||
welcome: {
|
||||
title: 'Welcome to OGame (Mobile)',
|
||||
content:
|
||||
"Welcome, Commander! This is a streamlined tutorial designed for touchscreens. We'll quickly cover the core features to get you started building your empire."
|
||||
},
|
||||
resources: {
|
||||
title: 'Top Resource Bar',
|
||||
content: 'At the top, you see your resources: Metal, Crystal, and Deuterium. Tap to view detailed production info.'
|
||||
},
|
||||
menu: {
|
||||
title: 'Open Navigation Menu',
|
||||
content:
|
||||
'Tap this menu icon to open the navigation bar. You can access Buildings, Research, Fleet, and all other features from here.'
|
||||
},
|
||||
gotoBuildings: {
|
||||
title: 'Go to Buildings Page',
|
||||
content: 'The menu is now open! Now tap "Buildings" to start constructing your infrastructure.'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: 'Build Solar Plant',
|
||||
content: 'First, build a Solar Plant! Scroll down to find it and tap the card to build. Energy is the foundation of everything.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: 'Build Queue',
|
||||
content:
|
||||
'Click the queue icon in the top-right corner to view build progress. You can continue browsing other pages - construction happens in the background.'
|
||||
},
|
||||
buildMetalMine: {
|
||||
title: 'Build Metal Mine',
|
||||
content: 'Now that you have energy, build a Metal Mine. Scroll down to find it and tap to build.'
|
||||
},
|
||||
complete: {
|
||||
title: 'Quick Tutorial Complete!',
|
||||
content:
|
||||
"Great! You've mastered the basics. Continue building Crystal and Deuterium facilities, then explore other features. Remember: energy first, then resources!"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default {
|
||||
close: '閉じる',
|
||||
back: '戻る',
|
||||
next: '次へ',
|
||||
gotIt: '',
|
||||
previous: '前へ',
|
||||
submit: '送信',
|
||||
reset: 'リセット',
|
||||
@@ -125,6 +126,7 @@ export default {
|
||||
roboticsFactory: 'ロボット工場',
|
||||
naniteFactory: 'ナノマシン工場',
|
||||
shipyard: '造船所',
|
||||
hangar: '格納庫',
|
||||
researchLab: '研究所',
|
||||
metalStorage: '金属倉庫',
|
||||
crystalStorage: 'クリスタル倉庫',
|
||||
@@ -165,6 +167,7 @@ export default {
|
||||
roboticsFactory: '建設速度を向上',
|
||||
naniteFactory: '建設キュー数を増加、レベル毎に+1(最大10レベル)',
|
||||
shipyard: '艦船を建造',
|
||||
hangar: '艦隊収容能力を拡張する専用施設、惑星の専門化をサポート',
|
||||
researchLab: '技術を研究',
|
||||
metalStorage: '金属の貯蔵上限を増加',
|
||||
crystalStorage: 'クリスタルの貯蔵上限を増加',
|
||||
@@ -199,10 +202,10 @@ export default {
|
||||
lightFighter: '基本戦闘ユニット',
|
||||
heavyFighter: '重装甲戦闘機',
|
||||
cruiser: '中型戦艦、攻守バランス型',
|
||||
battleship: '強力な戦艦',
|
||||
battleship: '主力重戦艦、強力な火力と高い防御力を持つ',
|
||||
battlecruiser: '高速強力な戦闘艦、戦艦への攻撃に優れる',
|
||||
bomber: '防御施設への攻撃に特化した艦船',
|
||||
destroyer: '大型艦の破壊に特化したハンター',
|
||||
destroyer: '対大型艦専用艦、高火力だが防御力が低い',
|
||||
smallCargo: '少量の資源を輸送',
|
||||
largeCargo: '大量の資源を輸送',
|
||||
colonyShip: '新惑星の植民に使用',
|
||||
@@ -312,10 +315,13 @@ export default {
|
||||
darkMatterSpecialist: 'ダークマター採取効率を向上'
|
||||
},
|
||||
queue: {
|
||||
title: '建設キュー',
|
||||
empty: '進行中のタスクはありません',
|
||||
buildQueue: '建設キュー',
|
||||
researchQueue: '研究キュー',
|
||||
building: '建設中',
|
||||
researching: '研究中',
|
||||
demolishing: '解体中',
|
||||
remaining: '残り時間',
|
||||
cancel: 'キャンセル',
|
||||
cancelBuild: '建設キャンセル',
|
||||
@@ -580,10 +586,12 @@ export default {
|
||||
battles: '戦闘',
|
||||
spy: 'スパイ',
|
||||
npc: 'NPC',
|
||||
diplomacy: '',
|
||||
battleReports: '戦闘レポート',
|
||||
spyReports: 'スパイレポート',
|
||||
noBattleReports: '戦闘レポートなし',
|
||||
noSpyReports: 'スパイレポートなし',
|
||||
noDiplomaticReports: '',
|
||||
battleReport: '戦闘レポート',
|
||||
spyReport: 'スパイレポート',
|
||||
victory: '勝利',
|
||||
@@ -639,7 +647,38 @@ export default {
|
||||
hostile: '相手は敵対的でギフトを受け取りません',
|
||||
neutral_distrust: '相手はあなたを信頼していません',
|
||||
polite_decline: '丁重に断りました'
|
||||
}
|
||||
},
|
||||
// Spied notification dialog
|
||||
spiedNotificationDetails: '',
|
||||
spyDetected: '',
|
||||
detectionResult: '',
|
||||
detectionSuccess: '',
|
||||
spiedNotificationMessage: '',
|
||||
spiedNotificationTip: '',
|
||||
viewInGalaxy: '',
|
||||
// Mission report dialog
|
||||
missionReportDetails: '',
|
||||
missionSuccess: '',
|
||||
missionFailed: '',
|
||||
origin: '',
|
||||
destination: '',
|
||||
missionDetails: '',
|
||||
transportedResources: '',
|
||||
recycledResources: '',
|
||||
remainingDebris: '',
|
||||
newPlanet: '',
|
||||
// NPC activity dialog
|
||||
npcActivityDetails: '',
|
||||
activityType: {
|
||||
recycle: ''
|
||||
},
|
||||
activityLocation: '',
|
||||
position: '',
|
||||
nearPlanet: '',
|
||||
activityDescription: '',
|
||||
npcActivityMessage: '',
|
||||
arrivalTime: '',
|
||||
npcActivityTip: ''
|
||||
},
|
||||
missionReports: {
|
||||
transportSuccess: '輸送ミッションが正常に完了しました',
|
||||
@@ -831,6 +870,10 @@ export default {
|
||||
recentEvents: '最近のイベント',
|
||||
recentEventsDescription: '最近の外交活動ログ',
|
||||
ago: '前',
|
||||
notifications: '',
|
||||
markAllRead: '',
|
||||
noReports: '',
|
||||
viewAll: '',
|
||||
status: {
|
||||
friendly: '友好的',
|
||||
neutral: '中立',
|
||||
@@ -846,10 +889,17 @@ export default {
|
||||
viewPlanets: '惑星を表示'
|
||||
},
|
||||
lastEvent: '最後のイベント',
|
||||
reportDetails: '',
|
||||
eventDescription: '',
|
||||
reputationChange: '',
|
||||
before: '',
|
||||
after: '',
|
||||
statusChange: '',
|
||||
viewDiplomacy: '',
|
||||
events: {
|
||||
gift: 'ギフト送信',
|
||||
attack: '攻撃',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
missileAttack: 'ミサイル攻撃',
|
||||
allyAttacked: '同盟が攻撃された',
|
||||
spy: '諜報活動',
|
||||
stealDebris: '残骸を略奪'
|
||||
@@ -874,12 +924,20 @@ export default {
|
||||
receivedGiftFromNpc: '{npcName}からギフトを受け取りました',
|
||||
acceptedGiftFromNpc: '{npcName}からのギフトを受け取りました:{metal}M {crystal}C {deuterium}D',
|
||||
playerRejectedGift: 'プレイヤーがギフトを拒否しました',
|
||||
rejectedGiftFromNpc: '{npcName}からのギフトを拒否しました。評判{reputation}'
|
||||
rejectedGiftFromNpc: '{npcName}からのギフトを拒否しました。評判{reputation}',
|
||||
destroyedNpcPlanet: '{npcName}の{planetName}を破壊しました',
|
||||
playerDestroyedPlanet: 'プレイヤーが{planetName}を破壊しました',
|
||||
youDestroyedNpcPlanet: 'あなたは{npcName}の{planetName}を破壊しました。評判{reputation}',
|
||||
playerDestroyedAllyPlanet: 'プレイヤーが同盟{allyName}の{planetName}を破壊しました',
|
||||
allyOutraged: '{allyName}はあなたが同盟{targetName}の{planetName}を破壊したことに激怒しています',
|
||||
npcEliminated: 'NPC {npcName}は完全に排除されました',
|
||||
npcEliminatedMessage: 'あなたは{npcName}のすべての惑星を破壊しました!この勢力は完全に壊滅しました。'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
previous: '前へ',
|
||||
next: '次へ',
|
||||
gotIt: '',
|
||||
first: '最初',
|
||||
last: '最後',
|
||||
page: '{page}ページ'
|
||||
@@ -888,5 +946,39 @@ export default {
|
||||
title: 'ページが見つかりません',
|
||||
description: '申し訳ございません。お探しのページは存在しません',
|
||||
goHome: 'ホームに戻る'
|
||||
},
|
||||
time: {
|
||||
days: '日',
|
||||
hours: '時間',
|
||||
minutes: '分',
|
||||
seconds: '秒'
|
||||
},
|
||||
tutorial: {
|
||||
welcome: {
|
||||
title: 'OGame-Vue-Ts へようこそ',
|
||||
content: 'ようこそ、司令官!基礎から始めて、宇宙帝国を築きましょう。'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: '太陽光発電所を建設',
|
||||
content:
|
||||
'まず太陽光発電所を建設しましょう!惑星にエネルギーを供給します。エネルギーがないと、他の資源施設が機能しません。これが最も重要な第一歩です。'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '建設キュー',
|
||||
content:
|
||||
'建物は建設キューに追加されました。右上のキューアイコンをクリックすると、進行中のすべての建設と研究タスクを確認できます。建設には時間がかかりますが、待機中も作業を続けられます。'
|
||||
},
|
||||
mobile: {
|
||||
welcome: {
|
||||
title: 'OGame-Vue-Ts へようこそ(モバイル版)',
|
||||
content:
|
||||
'ようこそ、司令官!これはタッチスクリーン向けに最適化された簡易チュートリアルです。帝国建設を始めるために、コア機能を素早くご紹介します。'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '建設キュー',
|
||||
content:
|
||||
'右上のキューアイコンをクリックして建設進度を確認できます。他のページを閲覧し続けることができます。建設はバックグラウンドで進行します。'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default {
|
||||
close: '닫기',
|
||||
back: '돌아가기',
|
||||
next: '다음',
|
||||
gotIt: '',
|
||||
previous: '이전',
|
||||
submit: '제출',
|
||||
reset: '초기화',
|
||||
@@ -125,6 +126,7 @@ export default {
|
||||
roboticsFactory: '로봇 공장',
|
||||
naniteFactory: '나노 공장',
|
||||
shipyard: '조선소',
|
||||
hangar: '격납고',
|
||||
researchLab: '연구소',
|
||||
metalStorage: '금속 창고',
|
||||
crystalStorage: '크리스탈 창고',
|
||||
@@ -165,6 +167,7 @@ export default {
|
||||
roboticsFactory: '건설 속도 향상',
|
||||
naniteFactory: '건설 대기열 수 증가, 레벨당 +1 (최대 10레벨)',
|
||||
shipyard: '함선 건조',
|
||||
hangar: '함대 저장 용량 확장 전용 시설, 행성 전문화 지원',
|
||||
researchLab: '기술 연구',
|
||||
metalStorage: '금속 저장 용량 증가',
|
||||
crystalStorage: '크리스탈 저장 용량 증가',
|
||||
@@ -199,10 +202,10 @@ export default {
|
||||
lightFighter: '기본 전투 유닛',
|
||||
heavyFighter: '중장갑 전투기',
|
||||
cruiser: '중형 전함, 공격과 방어 균형',
|
||||
battleship: '강력한 전함',
|
||||
battleship: '주력 중전함, 강력한 화력과 높은 방어력 보유',
|
||||
battlecruiser: '빠르고 강력한 전투함, 전함 공격에 탁월',
|
||||
bomber: '방어 시설 공격 전문 함선',
|
||||
destroyer: '대형 함선 파괴 전문 헌터',
|
||||
destroyer: '대형 함선 전문 격파함, 높은 화력이지만 방어력 낮음',
|
||||
smallCargo: '소량의 자원 운송',
|
||||
largeCargo: '대량의 자원 운송',
|
||||
colonyShip: '새로운 행성 식민에 사용',
|
||||
@@ -312,10 +315,13 @@ export default {
|
||||
darkMatterSpecialist: '암흑 물질 수집 효율 향상'
|
||||
},
|
||||
queue: {
|
||||
title: '건설 대기열',
|
||||
empty: '활성 작업 없음',
|
||||
buildQueue: '건설 대기열',
|
||||
researchQueue: '연구 대기열',
|
||||
building: '건설 중',
|
||||
researching: '연구 중',
|
||||
demolishing: '철거 중',
|
||||
remaining: '남은 시간',
|
||||
cancel: '취소',
|
||||
cancelBuild: '건설 취소',
|
||||
@@ -581,10 +587,12 @@ export default {
|
||||
battles: '전투',
|
||||
spy: '정찰',
|
||||
npc: 'NPC',
|
||||
diplomacy: '',
|
||||
battleReports: '전투 보고서',
|
||||
spyReports: '정찰 보고서',
|
||||
noBattleReports: '전투 보고서 없음',
|
||||
noSpyReports: '정찰 보고서 없음',
|
||||
noDiplomaticReports: '',
|
||||
battleReport: '전투 보고서',
|
||||
spyReport: '정찰 보고서',
|
||||
victory: '승리',
|
||||
@@ -640,7 +648,38 @@ export default {
|
||||
hostile: '상대방이 적대적이어서 선물을 받지 않습니다',
|
||||
neutral_distrust: '상대방이 당신을 신뢰하지 않습니다',
|
||||
polite_decline: '정중하게 거절했습니다'
|
||||
}
|
||||
},
|
||||
// Spied notification dialog
|
||||
spiedNotificationDetails: '',
|
||||
spyDetected: '',
|
||||
detectionResult: '',
|
||||
detectionSuccess: '',
|
||||
spiedNotificationMessage: '',
|
||||
spiedNotificationTip: '',
|
||||
viewInGalaxy: '',
|
||||
// Mission report dialog
|
||||
missionReportDetails: '',
|
||||
missionSuccess: '',
|
||||
missionFailed: '',
|
||||
origin: '',
|
||||
destination: '',
|
||||
missionDetails: '',
|
||||
transportedResources: '',
|
||||
recycledResources: '',
|
||||
remainingDebris: '',
|
||||
newPlanet: '',
|
||||
// NPC activity dialog
|
||||
npcActivityDetails: '',
|
||||
activityType: {
|
||||
recycle: ''
|
||||
},
|
||||
activityLocation: '',
|
||||
position: '',
|
||||
nearPlanet: '',
|
||||
activityDescription: '',
|
||||
npcActivityMessage: '',
|
||||
arrivalTime: '',
|
||||
npcActivityTip: ''
|
||||
},
|
||||
missionReports: {
|
||||
transportSuccess: '수송 임무가 성공적으로 완료되었습니다',
|
||||
@@ -832,6 +871,10 @@ export default {
|
||||
recentEvents: '최근 이벤트',
|
||||
recentEventsDescription: '최근 외교 활동 로그',
|
||||
ago: '전',
|
||||
notifications: '',
|
||||
markAllRead: '',
|
||||
noReports: '',
|
||||
viewAll: '',
|
||||
status: {
|
||||
friendly: '우호적',
|
||||
neutral: '중립',
|
||||
@@ -847,10 +890,17 @@ export default {
|
||||
viewPlanets: '행성 보기'
|
||||
},
|
||||
lastEvent: '최근 이벤트',
|
||||
reportDetails: '',
|
||||
eventDescription: '',
|
||||
reputationChange: '',
|
||||
before: '',
|
||||
after: '',
|
||||
statusChange: '',
|
||||
viewDiplomacy: '',
|
||||
events: {
|
||||
gift: '선물 전송',
|
||||
attack: '공격',
|
||||
missileAttack: '미사일 공격',
|
||||
missileAttack: '미사일 공격',
|
||||
allyAttacked: '동맹 공격당함',
|
||||
spy: '정찰',
|
||||
stealDebris: '잔해 약탈'
|
||||
@@ -875,12 +925,20 @@ export default {
|
||||
receivedGiftFromNpc: '{npcName}로부터 선물을 받았습니다',
|
||||
acceptedGiftFromNpc: '{npcName}의 선물을 받았습니다: {metal}M {crystal}C {deuterium}D',
|
||||
playerRejectedGift: '플레이어가 선물을 거부했습니다',
|
||||
rejectedGiftFromNpc: '{npcName}의 선물을 거부했습니다. 평판 {reputation}'
|
||||
rejectedGiftFromNpc: '{npcName}의 선물을 거부했습니다. 평판 {reputation}',
|
||||
destroyedNpcPlanet: '{npcName}의 {planetName}을(를) 파괴했습니다',
|
||||
playerDestroyedPlanet: '플레이어가 {planetName}을(를) 파괴했습니다',
|
||||
youDestroyedNpcPlanet: '당신은 {npcName}의 {planetName}을(를) 파괴했습니다. 평판 {reputation}',
|
||||
playerDestroyedAllyPlanet: '플레이어가 동맹 {allyName}의 {planetName}을(를) 파괴했습니다',
|
||||
allyOutraged: '{allyName}은(는) 당신이 동맹 {targetName}의 {planetName}을(를) 파괴한 것에 분노하고 있습니다',
|
||||
npcEliminated: 'NPC {npcName}이(가) 완전히 제거되었습니다',
|
||||
npcEliminatedMessage: '당신은 {npcName}의 모든 행성을 파괴했습니다! 이 세력은 완전히 소멸되었습니다.'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
previous: '이전',
|
||||
next: '다음',
|
||||
gotIt: '',
|
||||
first: '처음',
|
||||
last: '마지막',
|
||||
page: '{page}페이지'
|
||||
@@ -889,5 +947,39 @@ export default {
|
||||
title: '페이지를 찾을 수 없습니다',
|
||||
description: '죄송합니다. 찾으시는 페이지가 존재하지 않습니다',
|
||||
goHome: '홈으로 이동'
|
||||
},
|
||||
time: {
|
||||
days: '일',
|
||||
hours: '시간',
|
||||
minutes: '분',
|
||||
seconds: '초'
|
||||
},
|
||||
tutorial: {
|
||||
welcome: {
|
||||
title: 'OGame에 오신 것을 환영합니다',
|
||||
content: '환영합니다, 사령관! 기초부터 시작하여 우주 제국을 건설해 봅시다.'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: '태양광 발전소 건설',
|
||||
content:
|
||||
'먼저 태양광 발전소를 건설하세요! 행성에 에너지를 공급합니다. 에너지가 없으면 다른 자원 건물이 작동할 수 없습니다. 가장 중요한 첫 단계입니다.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '건설 대기열',
|
||||
content:
|
||||
'건물이 건설 대기열에 추가되었습니다. 오른쪽 상단의 대기열 아이콘을 클릭하면 진행 중인 모든 건설 및 연구 작업을 확인할 수 있습니다. 건설에는 시간이 걸리지만 대기하는 동안 계속 작업할 수 있습니다.'
|
||||
},
|
||||
mobile: {
|
||||
welcome: {
|
||||
title: 'OGame에 오신 것을 환영합니다 (모바일)',
|
||||
content:
|
||||
'환영합니다, 사령관! 터치스크린용으로 설계된 간소화된 튜토리얼입니다. 제국 건설을 시작할 수 있도록 핵심 기능을 빠르게 소개하겠습니다.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '건설 대기열',
|
||||
content:
|
||||
'오른쪽 상단의 대기열 아이콘을 클릭하여 건설 진행 상황을 확인하세요. 다른 페이지를 계속 탐색할 수 있으며, 건설은 백그라운드에서 진행됩니다.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default {
|
||||
close: 'Закрыть',
|
||||
back: 'Назад',
|
||||
next: 'Далее',
|
||||
gotIt: '',
|
||||
previous: 'Предыдущий',
|
||||
submit: 'Отправить',
|
||||
reset: 'Сбросить',
|
||||
@@ -125,6 +126,7 @@ export default {
|
||||
roboticsFactory: 'Фабрика роботов',
|
||||
naniteFactory: 'Нанитная фабрика',
|
||||
shipyard: 'Верфь',
|
||||
hangar: 'Ангар',
|
||||
researchLab: 'Исследовательская лаборатория',
|
||||
metalStorage: 'Хранилище металла',
|
||||
crystalStorage: 'Хранилище кристалла',
|
||||
@@ -165,6 +167,7 @@ export default {
|
||||
roboticsFactory: 'Ускоряет скорость строительства',
|
||||
naniteFactory: 'Увеличивает вместимость очереди строительства, +1 за уровень (макс 10 уровней)',
|
||||
shipyard: 'Строит корабли',
|
||||
hangar: 'Специализированное сооружение для расширения вместимости флота, поддерживает специализацию планет',
|
||||
researchLab: 'Исследует технологии',
|
||||
metalStorage: 'Увеличивает ёмкость хранилища металла',
|
||||
crystalStorage: 'Увеличивает ёмкость хранилища кристалла',
|
||||
@@ -199,10 +202,10 @@ export default {
|
||||
lightFighter: 'Базовая боевая единица',
|
||||
heavyFighter: 'Тяжелобронированный истребитель',
|
||||
cruiser: 'Средний боевой корабль, сбалансированная атака и защита',
|
||||
battleship: 'Мощный боевой корабль',
|
||||
battleship: 'Основной тяжёлый боевой корабль с мощной огневой мощью и высокой защитой',
|
||||
battlecruiser: 'Быстрый мощный боевой корабль, отлично атакует линкоры',
|
||||
bomber: 'Специализированный корабль для атаки оборонительных сооружений',
|
||||
destroyer: 'Охотник, специализирующийся на уничтожении крупных кораблей',
|
||||
destroyer: 'Специализированный противокапитальный корабль с высокой огневой мощью, но низкой защитой',
|
||||
smallCargo: 'Транспортирует небольшое количество ресурсов',
|
||||
largeCargo: 'Транспортирует большое количество ресурсов',
|
||||
colonyShip: 'Используется для колонизации новых планет',
|
||||
@@ -313,10 +316,13 @@ export default {
|
||||
darkMatterSpecialist: 'Улучшает эффективность сбора тёмной материи'
|
||||
},
|
||||
queue: {
|
||||
title: 'Очередь строительства',
|
||||
empty: 'Нет активных задач',
|
||||
buildQueue: 'Очередь строительства',
|
||||
researchQueue: 'Очередь исследований',
|
||||
building: 'Строится',
|
||||
researching: 'Исследуется',
|
||||
demolishing: 'Сносится',
|
||||
remaining: 'Осталось',
|
||||
cancel: 'Отменить',
|
||||
cancelBuild: 'Отменить строительство',
|
||||
@@ -588,10 +594,12 @@ export default {
|
||||
battles: 'Битвы',
|
||||
spy: 'Разведка',
|
||||
npc: 'NPC',
|
||||
diplomacy: '',
|
||||
battleReports: 'Отчёты о боях',
|
||||
spyReports: 'Отчёты разведки',
|
||||
noBattleReports: 'Нет отчётов о боях',
|
||||
noSpyReports: 'Нет отчётов разведки',
|
||||
noDiplomaticReports: '',
|
||||
battleReport: 'Отчёт о бое',
|
||||
spyReport: 'Отчёт разведки',
|
||||
victory: 'Победа',
|
||||
@@ -647,7 +655,38 @@ export default {
|
||||
hostile: 'Они враждебны и не принимают подарки',
|
||||
neutral_distrust: 'Они вам не доверяют',
|
||||
polite_decline: 'Вежливо отказались'
|
||||
}
|
||||
},
|
||||
// Spied notification dialog
|
||||
spiedNotificationDetails: '',
|
||||
spyDetected: '',
|
||||
detectionResult: '',
|
||||
detectionSuccess: '',
|
||||
spiedNotificationMessage: '',
|
||||
spiedNotificationTip: '',
|
||||
viewInGalaxy: '',
|
||||
// Mission report dialog
|
||||
missionReportDetails: '',
|
||||
missionSuccess: '',
|
||||
missionFailed: '',
|
||||
origin: '',
|
||||
destination: '',
|
||||
missionDetails: '',
|
||||
transportedResources: '',
|
||||
recycledResources: '',
|
||||
remainingDebris: '',
|
||||
newPlanet: '',
|
||||
// NPC activity dialog
|
||||
npcActivityDetails: '',
|
||||
activityType: {
|
||||
recycle: ''
|
||||
},
|
||||
activityLocation: '',
|
||||
position: '',
|
||||
nearPlanet: '',
|
||||
activityDescription: '',
|
||||
npcActivityMessage: '',
|
||||
arrivalTime: '',
|
||||
npcActivityTip: ''
|
||||
},
|
||||
missionReports: {
|
||||
transportSuccess: 'Миссия транспортировки успешно завершена',
|
||||
@@ -801,7 +840,8 @@ export default {
|
||||
npcWillSpyAndAttack: '{npcName} проведет разведку через 5с и атакует через 10с',
|
||||
acceleratedMissions: 'Ускорено {count} миссий до 5 секунд',
|
||||
npcFleetInitialized: 'Флот {npcName} инициализирован',
|
||||
npcFleetDetails: '100 шпионских зондов\n500 легких истребителей\n300 тяжелых истребителей\n200 крейсеров\n100 линкоров\n50 бомбардировщиков\n30 эсминцев\n20 линейных крейсеров',
|
||||
npcFleetDetails:
|
||||
'100 шпионских зондов\n500 легких истребителей\n300 тяжелых истребителей\n200 крейсеров\n100 линкоров\n50 бомбардировщиков\n30 эсминцев\n20 линейных крейсеров',
|
||||
dangerZone: 'Опасная зона',
|
||||
dangerZoneDesc: 'Следующие операции необратимы',
|
||||
resetGame: 'Сбросить игру',
|
||||
@@ -839,6 +879,10 @@ export default {
|
||||
recentEvents: 'Недавние события',
|
||||
recentEventsDescription: 'Журнал последних дипломатических действий',
|
||||
ago: 'назад',
|
||||
notifications: '',
|
||||
markAllRead: '',
|
||||
noReports: '',
|
||||
viewAll: '',
|
||||
status: {
|
||||
friendly: 'Дружественный',
|
||||
neutral: 'Нейтральный',
|
||||
@@ -854,10 +898,17 @@ export default {
|
||||
viewPlanets: 'Посмотреть планеты'
|
||||
},
|
||||
lastEvent: 'Последнее событие',
|
||||
reportDetails: '',
|
||||
eventDescription: '',
|
||||
reputationChange: '',
|
||||
before: '',
|
||||
after: '',
|
||||
statusChange: '',
|
||||
viewDiplomacy: '',
|
||||
events: {
|
||||
gift: 'Подарок отправлен',
|
||||
attack: 'Атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
missileAttack: 'Ракетная атака',
|
||||
allyAttacked: 'Союзник атакован',
|
||||
spy: 'Шпионаж',
|
||||
stealDebris: 'Обломки украдены'
|
||||
@@ -882,12 +933,20 @@ export default {
|
||||
receivedGiftFromNpc: 'Получен подарок от {npcName}',
|
||||
acceptedGiftFromNpc: 'Вы приняли подарок от {npcName}: {metal}M {crystal}C {deuterium}D',
|
||||
playerRejectedGift: 'Игрок отклонил подарок',
|
||||
rejectedGiftFromNpc: 'Вы отклонили подарок от {npcName}. Репутация {reputation}'
|
||||
rejectedGiftFromNpc: 'Вы отклонили подарок от {npcName}. Репутация {reputation}',
|
||||
destroyedNpcPlanet: 'Уничтожена {planetName} игрока {npcName}',
|
||||
playerDestroyedPlanet: 'Игрок уничтожил {planetName}',
|
||||
youDestroyedNpcPlanet: 'Вы уничтожили {planetName} игрока {npcName}. Репутация {reputation}',
|
||||
playerDestroyedAllyPlanet: 'Игрок уничтожил {planetName} союзника {allyName}',
|
||||
allyOutraged: '{allyName} возмущен тем, что вы уничтожили {planetName} их союзника {targetName}',
|
||||
npcEliminated: 'NPC {npcName} полностью уничтожен',
|
||||
npcEliminatedMessage: 'Вы уничтожили все планеты {npcName}! Эта фракция полностью уничтожена.'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
previous: 'Предыдущая',
|
||||
next: 'Следующая',
|
||||
gotIt: '',
|
||||
first: 'Первая',
|
||||
last: 'Последняя',
|
||||
page: 'Страница {page}'
|
||||
@@ -896,5 +955,39 @@ export default {
|
||||
title: 'Страница не найдена',
|
||||
description: 'Извините, страница, которую вы ищете, не существует',
|
||||
goHome: 'На главную'
|
||||
},
|
||||
time: {
|
||||
days: 'дней',
|
||||
hours: 'часов',
|
||||
minutes: 'минут',
|
||||
seconds: 'секунд'
|
||||
},
|
||||
tutorial: {
|
||||
welcome: {
|
||||
title: 'Добро пожаловать в OGame',
|
||||
content: 'Добро пожаловать, Командир! Давайте начнём с основ и построим вашу космическую империю.'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: 'Постройте солнечную электростанцию',
|
||||
content:
|
||||
'Сначала постройте солнечную электростанцию! Она обеспечивает энергией вашу планету. Без энергии другие ресурсные здания не могут функционировать. Это самый важный первый шаг.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: 'Очередь строительства',
|
||||
content:
|
||||
'Ваше здание теперь в очереди строительства. Нажмите на значок очереди в правом верхнем углу, чтобы увидеть все текущие задачи строительства и исследований. Строительство занимает время, но вы можете продолжать работать во время ожидания.'
|
||||
},
|
||||
mobile: {
|
||||
welcome: {
|
||||
title: 'Добро пожаловать в OGame (Мобильная версия)',
|
||||
content:
|
||||
'Добро пожаловать, Командир! Это упрощённое руководство, разработанное для сенсорных экранов. Мы быстро рассмотрим основные функции, чтобы вы могли начать строить свою империю.'
|
||||
},
|
||||
waitBuild: {
|
||||
title: 'Очередь строительства',
|
||||
content:
|
||||
'Нажмите на значок очереди в правом верхнем углу, чтобы увидеть прогресс строительства. Вы можете продолжать просматривать другие страницы - строительство происходит в фоновом режиме.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +126,7 @@ export default {
|
||||
roboticsFactory: '机器人工厂',
|
||||
naniteFactory: '纳米工厂',
|
||||
shipyard: '船坞',
|
||||
hangar: '机库',
|
||||
researchLab: '研究实验室',
|
||||
metalStorage: '金属仓库',
|
||||
crystalStorage: '晶体仓库',
|
||||
@@ -164,6 +165,7 @@ export default {
|
||||
roboticsFactory: '加快建造速度',
|
||||
naniteFactory: '增加建造队列数量,每级+1队列(最多10级)',
|
||||
shipyard: '建造舰船',
|
||||
hangar: '专门用于扩展舰队存储容量,支持星球专业化发展',
|
||||
researchLab: '研究科技',
|
||||
metalStorage: '增加金属存储上限',
|
||||
crystalStorage: '增加晶体存储上限',
|
||||
@@ -198,10 +200,10 @@ export default {
|
||||
lightFighter: '基础战斗单位',
|
||||
heavyFighter: '重装战斗机',
|
||||
cruiser: '中型战舰,攻守平衡',
|
||||
battleship: '强力战舰',
|
||||
battleship: '主力重型战舰,拥有强大的火力和防护',
|
||||
battlecruiser: '快速强大的战斗舰船,擅长攻击战列舰',
|
||||
bomber: '专门对付防御设施的轰炸舰',
|
||||
destroyer: '擅长摧毁大型舰船的猎杀者',
|
||||
destroyer: '专业反大型舰船战舰,高火力低防护',
|
||||
smallCargo: '运输少量资源',
|
||||
largeCargo: '运输大量资源',
|
||||
colonyShip: '用于殖民新星球',
|
||||
@@ -313,11 +315,14 @@ export default {
|
||||
darkMatterSpecialist: '提升暗物质采集效率'
|
||||
},
|
||||
queue: {
|
||||
title: '建造队列',
|
||||
empty: '当前没有进行中的任务',
|
||||
buildQueueBonus: '建造队列',
|
||||
spaceBonus: '空间加成',
|
||||
researchQueueBonus: '研究队列',
|
||||
building: '建造中',
|
||||
researching: '研究中',
|
||||
demolishing: '拆除中',
|
||||
remaining: '剩余时间',
|
||||
cancel: '取消',
|
||||
cancelBuild: '取消建造',
|
||||
@@ -576,10 +581,12 @@ export default {
|
||||
battles: '战斗',
|
||||
spy: '侦查',
|
||||
npc: 'NPC',
|
||||
diplomacy: '',
|
||||
battleReports: '战斗报告',
|
||||
spyReports: '间谍报告',
|
||||
noBattleReports: '暂无战斗报告',
|
||||
noSpyReports: '暂无间谍报告',
|
||||
noDiplomaticReports: '',
|
||||
battleReport: '战斗报告',
|
||||
spyReport: '间谍报告',
|
||||
victory: '胜利',
|
||||
@@ -635,7 +642,38 @@ export default {
|
||||
hostile: '对方对你有敌意,不接受礼物',
|
||||
neutral_distrust: '对方对你缺乏信任',
|
||||
polite_decline: '对方礼貌地拒绝了'
|
||||
}
|
||||
},
|
||||
// 被侦查通知对话框
|
||||
spiedNotificationDetails: '被侦查通知详情',
|
||||
spyDetected: '侦查被发现',
|
||||
detectionResult: '检测结果',
|
||||
detectionSuccess: '你的侦查探测被发现了!',
|
||||
spiedNotificationMessage: '{npc}试图侦查你的星球{planet}',
|
||||
spiedNotificationTip: '考虑增强防御或反击,如果这个NPC对你有敌意',
|
||||
viewInGalaxy: '在星系中查看',
|
||||
// 任务报告对话框
|
||||
missionReportDetails: '任务报告详情',
|
||||
missionSuccess: '成功',
|
||||
missionFailed: '失败',
|
||||
origin: '起点',
|
||||
destination: '终点',
|
||||
missionDetails: '任务详情',
|
||||
transportedResources: '运输资源',
|
||||
recycledResources: '回收资源',
|
||||
remainingDebris: '剩余残骸',
|
||||
newPlanet: '新星球',
|
||||
// NPC活动对话框
|
||||
npcActivityDetails: 'NPC活动详情',
|
||||
activityType: {
|
||||
recycle: '回收残骸'
|
||||
},
|
||||
activityLocation: '活动位置',
|
||||
position: '位置',
|
||||
nearPlanet: '附近星球',
|
||||
activityDescription: '活动描述',
|
||||
npcActivityMessage: '{npc}正在{position}{activity}',
|
||||
arrivalTime: '到达时间',
|
||||
npcActivityTip: 'NPC可能会收集战斗产生的残骸。如果你想竞争资源,可以尝试先到达该位置'
|
||||
},
|
||||
missionReports: {
|
||||
transportSuccess: '运输任务成功完成',
|
||||
@@ -801,7 +839,8 @@ export default {
|
||||
completeAllQueues: '一键完成所有队列',
|
||||
completeAllQueuesDesc: '立即完成所有建筑、科技、舰船、防御队列和飞行任务',
|
||||
completeQueues: '完成队列',
|
||||
completeQueuesSuccess: '已完成 {buildingCount} 个建筑队列、{researchCount} 个科技队列、{missionCount} 个飞行任务、{missileCount} 个导弹任务'
|
||||
completeQueuesSuccess:
|
||||
'已完成 {buildingCount} 个建筑队列、{researchCount} 个科技队列、{missionCount} 个飞行任务、{missileCount} 个导弹任务'
|
||||
},
|
||||
alerts: {
|
||||
npcSpyIncoming: 'NPC侦查即将到达',
|
||||
@@ -831,6 +870,10 @@ export default {
|
||||
recentEvents: '最近事件',
|
||||
recentEventsDescription: '最近的外交活动记录',
|
||||
ago: '前',
|
||||
notifications: '外交通知',
|
||||
markAllRead: '全部已读',
|
||||
noReports: '暂无外交事件',
|
||||
viewAll: '查看全部',
|
||||
status: {
|
||||
friendly: '友好',
|
||||
neutral: '中立',
|
||||
@@ -846,6 +889,13 @@ export default {
|
||||
viewPlanets: '查看星球'
|
||||
},
|
||||
lastEvent: '最近活动',
|
||||
reportDetails: '外交报告详情',
|
||||
eventDescription: '事件描述',
|
||||
reputationChange: '好感度变化',
|
||||
before: '之前',
|
||||
after: '之后',
|
||||
statusChange: '关系状态变化',
|
||||
viewDiplomacy: '查看外交页面',
|
||||
events: {
|
||||
gift: '赠送资源',
|
||||
attack: '攻击',
|
||||
@@ -873,7 +923,14 @@ export default {
|
||||
receivedGiftFromNpc: '收到了{npcName}的礼物',
|
||||
acceptedGiftFromNpc: '你接受了{npcName}的礼物:{metal}金属 {crystal}晶体 {deuterium}氘',
|
||||
playerRejectedGift: '玩家拒绝了礼物',
|
||||
rejectedGiftFromNpc: '你拒绝了{npcName}的礼物。好感度{reputation}'
|
||||
rejectedGiftFromNpc: '你拒绝了{npcName}的礼物。好感度{reputation}',
|
||||
destroyedNpcPlanet: '摧毁了{npcName}的{planetName}',
|
||||
playerDestroyedPlanet: '玩家摧毁了{planetName}',
|
||||
youDestroyedNpcPlanet: '你摧毁了{npcName}的{planetName}。好感度{reputation}',
|
||||
playerDestroyedAllyPlanet: '玩家摧毁了盟友{allyName}的{planetName}',
|
||||
allyOutraged: '{allyName}对你摧毁盟友{targetName}的{planetName}感到愤怒',
|
||||
npcEliminated: 'NPC {npcName}已被彻底消灭',
|
||||
npcEliminatedMessage: '你消灭了{npcName}的所有星球!该势力已被彻底摧毁。'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
@@ -887,5 +944,144 @@ export default {
|
||||
title: '页面未找到',
|
||||
description: '抱歉,您访问的页面不存在',
|
||||
goHome: '返回首页'
|
||||
},
|
||||
time: {
|
||||
days: '天',
|
||||
hours: '小时',
|
||||
minutes: '分钟',
|
||||
seconds: '秒'
|
||||
},
|
||||
tutorial: {
|
||||
progress: '进度',
|
||||
previous: '上一步',
|
||||
next: '下一步',
|
||||
gotIt: '我知道了',
|
||||
completeButton: '完成',
|
||||
skip: '跳过引导',
|
||||
welcome: {
|
||||
title: '欢迎来到 OGame',
|
||||
content: '欢迎,指挥官!本教程将引导您了解建立帝国的基础知识。点击"下一步"开始您的征程。'
|
||||
},
|
||||
resources: {
|
||||
title: '资源概览',
|
||||
content: '这些是您的资源:金属、晶体和重氢。它们是建造建筑和研究科技的必需品。能量也很重要,用于为您的基础设施供电。'
|
||||
},
|
||||
planet: {
|
||||
title: '您的星球',
|
||||
content: '这是您的母星。您可以在这里查看星球名称、坐标,并在扩张帝国时切换星球。'
|
||||
},
|
||||
navigation: {
|
||||
title: '导航菜单',
|
||||
content: '使用此菜单在不同部分之间导航:建筑、研究、舰队、星系等。每个部分都提供独特的游戏功能。'
|
||||
},
|
||||
gotoBuildings: {
|
||||
title: '前往建筑页面',
|
||||
content: '让我们从建造一些建筑开始。点击"建筑"菜单项查看可用建筑。'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: '建造太阳能电站',
|
||||
content: '首先建造太阳能电站!它为您的星球提供能量。没有能量,其他资源建筑无法运作。这是最重要的第一步。'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '建造队列',
|
||||
content:
|
||||
'您的建筑现在在建造队列中。点击右上角的队列图标可以查看所有正在进行的建造和研究任务。建筑需要时间完成,但您可以在等待时继续操作。'
|
||||
},
|
||||
buildMetalMine: {
|
||||
title: '建造金属矿',
|
||||
content: '现在有了能量,可以建造金属矿了。金属矿是您的主要金属来源,金属几乎用于每个建筑和舰船。'
|
||||
},
|
||||
buildCrystalMine: {
|
||||
title: '建造晶体矿',
|
||||
content: '晶体更稀有但对高级科技至关重要。建造晶体矿开始收集这种宝贵的资源。'
|
||||
},
|
||||
buildDeuterium: {
|
||||
title: '建造重氢合成器',
|
||||
content: '重氢是舰船燃料和高级研究的必需品。建造重氢合成器开始生产这种关键资源。'
|
||||
},
|
||||
upgradeMines: {
|
||||
title: '升级资源矿',
|
||||
content: '接下来,您需要升级三种资源矿(金属、晶体、重氢)到2级,以满足建造机器人工厂的要求。资源充足后,继续升级它们。'
|
||||
},
|
||||
buildRobotics: {
|
||||
title: '建造机器人工厂',
|
||||
content: '机器人工厂可以大幅加快建造速度。它需要金属矿、晶体矿和重氢合成器各达到2级。建造它来提升建造效率!'
|
||||
},
|
||||
upgradeMinesForLab: {
|
||||
title: '继续升级资源矿',
|
||||
content: '现在需要将三种资源矿升级到3级,以满足研究实验室的建造要求。继续发展您的资源产能。'
|
||||
},
|
||||
buildResearchLab: {
|
||||
title: '建造研究实验室',
|
||||
content: '研究实验室是技术进步的基础。它需要三种资源矿各达到3级。建造它以解锁科技研究!'
|
||||
},
|
||||
gotoResearch: {
|
||||
title: '前往研究页面',
|
||||
content: '既然您有了研究实验室,点击"研究"菜单查看可用的科技。'
|
||||
},
|
||||
researchEnergy: {
|
||||
title: '研究能量科技',
|
||||
content: '能量科技可以提高您的能量产出并解锁高级建筑。这是最基础也是最重要的科技之一。'
|
||||
},
|
||||
shipyardIntro: {
|
||||
title: '舰队与船坞',
|
||||
content: '舰船让您能够探索星系、运输资源并保卫您的帝国。要建造舰船,您需要船坞(需要机器人工厂2级)。'
|
||||
},
|
||||
gotoBuildingsForShipyard: {
|
||||
title: '返回建筑页面',
|
||||
content: '返回建筑页面来建造您的船坞。'
|
||||
},
|
||||
buildShipyard: {
|
||||
title: '建造船坞',
|
||||
content: '船坞允许您建造舰船和防御系统。这对舰队行动至关重要。'
|
||||
},
|
||||
fleetIntro: {
|
||||
title: '舰队行动',
|
||||
content: '一旦您拥有舰船,就可以派遣它们执行任务:运输资源、殖民星球、攻击敌人或探索废墟场。'
|
||||
},
|
||||
galaxyIntro: {
|
||||
title: '探索星系',
|
||||
content: '星系视图显示其他星球、废墟场和扩张机会。使用它来侦察目标并规划您的战略。'
|
||||
},
|
||||
complete: {
|
||||
title: '教程完成!',
|
||||
content:
|
||||
'恭喜,指挥官!您现在了解了基础知识。继续建设您的帝国,研究科技,探索星系。记住:先发展能量,再建资源,然后是工厂和研究!祝您好运!'
|
||||
},
|
||||
// 移动端教程
|
||||
mobile: {
|
||||
welcome: {
|
||||
title: '欢迎来到 OGame(移动版)',
|
||||
content: '欢迎,指挥官!这是专为触摸屏设计的简化教程。我们将快速介绍核心功能,让您开始建设帝国。'
|
||||
},
|
||||
resources: {
|
||||
title: '顶部资源栏',
|
||||
content: '顶部显示您的资源:金属、晶体和重氢。点击可查看详细生产信息。'
|
||||
},
|
||||
menu: {
|
||||
title: '打开导航菜单',
|
||||
content: '点击这个菜单图标打开导航栏,您可以访问建筑、研究、舰队等所有功能。'
|
||||
},
|
||||
gotoBuildings: {
|
||||
title: '前往建筑页面',
|
||||
content: '菜单已打开!现在点击"建筑"选项,开始建造基础设施。'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: '建造太阳能电站',
|
||||
content: '首先建造太阳能电站!向下滚动找到它,点击卡片进行建造。能量是一切的基础。'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '建造队列',
|
||||
content: '点击右上角的队列图标可以查看建造进度。您可以继续浏览其他页面,建造会在后台进行。'
|
||||
},
|
||||
buildMetalMine: {
|
||||
title: '建造金属矿',
|
||||
content: '有了能量后,建造金属矿。向下滚动找到金属矿,点击建造。'
|
||||
},
|
||||
complete: {
|
||||
title: '快速教程完成!',
|
||||
content: '很好!您已经掌握了基础操作。继续建造晶体矿和重氢合成器,然后探索其他功能。记住:先能量,再资源!'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ export default {
|
||||
close: '關閉',
|
||||
back: '返回',
|
||||
next: '下一步',
|
||||
gotIt: '',
|
||||
previous: '上一步',
|
||||
submit: '提交',
|
||||
reset: '重置',
|
||||
@@ -125,6 +126,7 @@ export default {
|
||||
roboticsFactory: '機器人工廠',
|
||||
naniteFactory: '納米工廠',
|
||||
shipyard: '船塢',
|
||||
hangar: '機庫',
|
||||
researchLab: '研究實驗室',
|
||||
metalStorage: '金屬倉庫',
|
||||
crystalStorage: '晶體倉庫',
|
||||
@@ -165,6 +167,7 @@ export default {
|
||||
roboticsFactory: '加快建造速度',
|
||||
naniteFactory: '增加建造佇列數量,每級+1佇列(最多10級)',
|
||||
shipyard: '建造艦船',
|
||||
hangar: '專門用於擴展艦隊儲存容量,支援行星專業化發展',
|
||||
researchLab: '研究科技',
|
||||
metalStorage: '增加金屬儲存上限',
|
||||
crystalStorage: '增加晶體儲存上限',
|
||||
@@ -199,10 +202,10 @@ export default {
|
||||
lightFighter: '基礎戰鬥單位',
|
||||
heavyFighter: '重裝戰鬥機',
|
||||
cruiser: '中型戰艦,攻守平衡',
|
||||
battleship: '強力戰艦',
|
||||
battleship: '主力重型戰艦,擁有強大的火力和防護',
|
||||
battlecruiser: '快速強大的戰鬥艦船,擅長攻擊戰列艦',
|
||||
bomber: '專門對付防禦設施的轟炸艦',
|
||||
destroyer: '擅長摧毀大型艦船的獵殺者',
|
||||
destroyer: '專業反大型艦船戰艦,高火力低防護',
|
||||
smallCargo: '運輸少量資源',
|
||||
largeCargo: '運輸大量資源',
|
||||
colonyShip: '用於殖民新星球',
|
||||
@@ -314,10 +317,13 @@ export default {
|
||||
darkMatterSpecialist: '提升暗物質採集效率'
|
||||
},
|
||||
queue: {
|
||||
title: '建造佇列',
|
||||
empty: '當前沒有進行中的任務',
|
||||
buildQueue: '建造佇列',
|
||||
researchQueue: '研究佇列',
|
||||
building: '建造中',
|
||||
researching: '研究中',
|
||||
demolishing: '拆除中',
|
||||
remaining: '剩餘時間',
|
||||
cancel: '取消',
|
||||
cancelBuild: '取消建造',
|
||||
@@ -582,10 +588,12 @@ export default {
|
||||
battles: '戰鬥',
|
||||
spy: '偵查',
|
||||
npc: 'NPC',
|
||||
diplomacy: '',
|
||||
battleReports: '戰鬥報告',
|
||||
spyReports: '間諜報告',
|
||||
noBattleReports: '暫無戰鬥報告',
|
||||
noSpyReports: '暫無間諜報告',
|
||||
noDiplomaticReports: '',
|
||||
battleReport: '戰鬥報告',
|
||||
spyReport: '間諜報告',
|
||||
victory: '勝利',
|
||||
@@ -641,7 +649,38 @@ export default {
|
||||
hostile: '對方對你有敵意,不接受禮物',
|
||||
neutral_distrust: '對方對你缺乏信任',
|
||||
polite_decline: '對方禮貌地拒絕了'
|
||||
}
|
||||
},
|
||||
// Spied notification dialog
|
||||
spiedNotificationDetails: '',
|
||||
spyDetected: '',
|
||||
detectionResult: '',
|
||||
detectionSuccess: '',
|
||||
spiedNotificationMessage: '',
|
||||
spiedNotificationTip: '',
|
||||
viewInGalaxy: '',
|
||||
// Mission report dialog
|
||||
missionReportDetails: '',
|
||||
missionSuccess: '',
|
||||
missionFailed: '',
|
||||
origin: '',
|
||||
destination: '',
|
||||
missionDetails: '',
|
||||
transportedResources: '',
|
||||
recycledResources: '',
|
||||
remainingDebris: '',
|
||||
newPlanet: '',
|
||||
// NPC activity dialog
|
||||
npcActivityDetails: '',
|
||||
activityType: {
|
||||
recycle: ''
|
||||
},
|
||||
activityLocation: '',
|
||||
position: '',
|
||||
nearPlanet: '',
|
||||
activityDescription: '',
|
||||
npcActivityMessage: '',
|
||||
arrivalTime: '',
|
||||
npcActivityTip: ''
|
||||
},
|
||||
missionReports: {
|
||||
transportSuccess: '運輸任務成功完成',
|
||||
@@ -833,6 +872,10 @@ export default {
|
||||
recentEvents: '最近事件',
|
||||
recentEventsDescription: '最近的外交活動記錄',
|
||||
ago: '前',
|
||||
notifications: '',
|
||||
markAllRead: '',
|
||||
noReports: '',
|
||||
viewAll: '',
|
||||
status: {
|
||||
friendly: '友好',
|
||||
neutral: '中立',
|
||||
@@ -848,10 +891,17 @@ export default {
|
||||
viewPlanets: '查看星球'
|
||||
},
|
||||
lastEvent: '最近事件',
|
||||
reportDetails: '外交報告詳情',
|
||||
eventDescription: '事件描述',
|
||||
reputationChange: '好感度變化',
|
||||
before: '之前',
|
||||
after: '之後',
|
||||
statusChange: '關係狀態變化',
|
||||
viewDiplomacy: '查看外交頁面',
|
||||
events: {
|
||||
gift: '已贈送禮物',
|
||||
attack: '攻擊',
|
||||
missileAttack: '導彈攻擊',
|
||||
missileAttack: '導彈攻擊',
|
||||
allyAttacked: '盟友被攻擊',
|
||||
spy: '間諜活動',
|
||||
stealDebris: '掠奪殘骸'
|
||||
@@ -876,12 +926,20 @@ export default {
|
||||
receivedGiftFromNpc: '收到了{npcName}的禮物',
|
||||
acceptedGiftFromNpc: '你接受了{npcName}的禮物:{metal}金屬 {crystal}晶體 {deuterium}氘',
|
||||
playerRejectedGift: '玩家拒絕了禮物',
|
||||
rejectedGiftFromNpc: '你拒絕了{npcName}的禮物。好感度{reputation}'
|
||||
rejectedGiftFromNpc: '你拒絕了{npcName}的禮物。好感度{reputation}',
|
||||
destroyedNpcPlanet: '摧毀了{npcName}的{planetName}',
|
||||
playerDestroyedPlanet: '玩家摧毀了{planetName}',
|
||||
youDestroyedNpcPlanet: '你摧毀了{npcName}的{planetName}。好感度{reputation}',
|
||||
playerDestroyedAllyPlanet: '玩家摧毀了盟友{allyName}的{planetName}',
|
||||
allyOutraged: '{allyName}對你摧毀盟友{targetName}的{planetName}感到憤怒',
|
||||
npcEliminated: 'NPC {npcName}已被徹底消滅',
|
||||
npcEliminatedMessage: '你消滅了{npcName}的所有星球!該勢力已被徹底摧毀。'
|
||||
}
|
||||
},
|
||||
pagination: {
|
||||
previous: '上一頁',
|
||||
next: '下一頁',
|
||||
gotIt: '',
|
||||
first: '首頁',
|
||||
last: '末頁',
|
||||
page: '第 {page} 頁'
|
||||
@@ -890,5 +948,36 @@ export default {
|
||||
title: '找不到頁面',
|
||||
description: '抱歉,您訪問的頁面不存在',
|
||||
goHome: '返回首頁'
|
||||
},
|
||||
time: {
|
||||
days: '天',
|
||||
hours: '小時',
|
||||
minutes: '分鐘',
|
||||
seconds: '秒'
|
||||
},
|
||||
tutorial: {
|
||||
welcome: {
|
||||
title: '歡迎來到 OGame',
|
||||
content: '歡迎,指揮官!讓我們從基礎開始,建立您的宇宙帝國。'
|
||||
},
|
||||
buildSolarPlant: {
|
||||
title: '建造太陽能電站',
|
||||
content: '首先建造太陽能電站!它為您的星球提供能量。沒有能量,其他資源建築無法運作。這是最重要的第一步。'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '建造佇列',
|
||||
content:
|
||||
'您的建築現在在建造佇列中。點擊右上角的佇列圖示可以查看所有正在進行的建造和研究任務。建築需要時間完成,但您可以在等待時繼續操作。'
|
||||
},
|
||||
mobile: {
|
||||
welcome: {
|
||||
title: '歡迎來到 OGame(移動版)',
|
||||
content: '歡迎,指揮官!這是專為觸控螢幕設計的簡化教程。我們將快速介紹核心功能,讓您開始建設帝國。'
|
||||
},
|
||||
waitBuild: {
|
||||
title: '建造佇列',
|
||||
content: '點擊右上角的佇列圖示可以查看建造進度。您可以繼續瀏覽其他頁面,建造會在背景進行。'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,7 @@ export const simulateBattle = async (
|
||||
|
||||
// 计算月球生成概率(根据残骸场总量)
|
||||
const totalDebris = debrisField.metal + debrisField.crystal
|
||||
const moonChance = Math.min(
|
||||
(MOON_CONFIG.baseChance + Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)),
|
||||
MOON_CONFIG.maxChance
|
||||
) / 100 // 转换为0-1的概率
|
||||
const moonChance = Math.min(MOON_CONFIG.baseChance + Math.floor(totalDebris / MOON_CONFIG.chancePerDebris), MOON_CONFIG.maxChance) / 100 // 转换为0-1的概率
|
||||
|
||||
// 生成战斗报告
|
||||
const battleResult: BattleResult = {
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
Resources,
|
||||
Player,
|
||||
NPC,
|
||||
Planet,
|
||||
FleetMission,
|
||||
BattleResult,
|
||||
Position,
|
||||
@@ -528,20 +529,173 @@ export const handleDebrisRecycleReputation = (player: Player, debrisPosition: Po
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理星球摧毁事件的好感度变化
|
||||
* 摧毁星球是最严重的行为,直接导致敌对关系
|
||||
* @param attacker 攻击者(玩家)
|
||||
* @param destroyedPlanet 被摧毁的星球
|
||||
* @param planetOwner 星球所有者(NPC)
|
||||
* @param allNpcs 所有NPC列表
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const handlePlanetDestructionReputation = (
|
||||
attacker: Player,
|
||||
destroyedPlanet: Planet,
|
||||
planetOwner: NPC,
|
||||
allNpcs: NPC[],
|
||||
locale: Locale
|
||||
): void => {
|
||||
const { HOSTILE_THRESHOLD } = DIPLOMATIC_CONFIG
|
||||
const now = Date.now()
|
||||
|
||||
// 更新玩家对被摧毁星球所有者的关系 - 直接设为敌对
|
||||
if (!attacker.diplomaticRelations) {
|
||||
attacker.diplomaticRelations = {}
|
||||
}
|
||||
|
||||
const relation = getOrCreateRelation(attacker.diplomaticRelations, attacker.id, planetOwner.id)
|
||||
const eventDescription = t('diplomacy.reports.destroyedNpcPlanet', locale, {
|
||||
npcName: planetOwner.name,
|
||||
planetName: destroyedPlanet.name
|
||||
})
|
||||
|
||||
attacker.diplomaticRelations[planetOwner.id] = {
|
||||
...relation,
|
||||
reputation: HOSTILE_THRESHOLD, // 直接设为敌对阈值
|
||||
status: RS.Hostile,
|
||||
lastUpdated: now,
|
||||
history: [
|
||||
...(relation.history || []),
|
||||
{
|
||||
timestamp: now,
|
||||
change: HOSTILE_THRESHOLD - relation.reputation,
|
||||
reason: DET.DestroyPlanet,
|
||||
details: eventDescription
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 更新星球所有者对玩家的关系 - 直接设为敌对
|
||||
if (!planetOwner.relations) {
|
||||
planetOwner.relations = {}
|
||||
}
|
||||
|
||||
const ownerRelation = getOrCreateRelation(planetOwner.relations, planetOwner.id, attacker.id)
|
||||
const ownerEventDescription = t('diplomacy.reports.playerDestroyedPlanet', locale, {
|
||||
planetName: destroyedPlanet.name
|
||||
})
|
||||
|
||||
planetOwner.relations[attacker.id] = {
|
||||
...ownerRelation,
|
||||
reputation: HOSTILE_THRESHOLD, // 直接设为敌对阈值
|
||||
status: RS.Hostile,
|
||||
lastUpdated: now,
|
||||
history: [
|
||||
...(ownerRelation.history || []),
|
||||
{
|
||||
timestamp: now,
|
||||
change: HOSTILE_THRESHOLD - ownerRelation.reputation,
|
||||
reason: DET.DestroyPlanet,
|
||||
details: ownerEventDescription
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 生成外交报告
|
||||
generateDiplomaticReport(
|
||||
attacker,
|
||||
planetOwner,
|
||||
DET.DestroyPlanet,
|
||||
HOSTILE_THRESHOLD,
|
||||
t('diplomacy.reports.youDestroyedNpcPlanet', locale, {
|
||||
npcName: planetOwner.name,
|
||||
planetName: destroyedPlanet.name,
|
||||
reputation: HOSTILE_THRESHOLD
|
||||
}),
|
||||
'diplomacy.reports.youDestroyedNpcPlanet',
|
||||
{ npcName: planetOwner.name, planetName: destroyedPlanet.name, reputation: HOSTILE_THRESHOLD }
|
||||
)
|
||||
|
||||
// 检查盟友关系网络 - 摧毁星球对盟友的影响更严重
|
||||
if (planetOwner.allies && planetOwner.allies.length > 0) {
|
||||
handleAllyPlanetDestroyedReputation(attacker, planetOwner, destroyedPlanet, allNpcs, locale)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理盟友星球被摧毁的好感度变化
|
||||
* @param attacker 攻击者(玩家)
|
||||
* @param attackedNpc 星球被摧毁的NPC
|
||||
* @param destroyedPlanet 被摧毁的星球
|
||||
* @param allNpcs 所有NPC列表
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const handleAllyPlanetDestroyedReputation = (
|
||||
attacker: Player,
|
||||
attackedNpc: NPC,
|
||||
destroyedPlanet: Planet,
|
||||
allNpcs: NPC[],
|
||||
locale: Locale
|
||||
): void => {
|
||||
const { REPUTATION_CHANGES } = DIPLOMATIC_CONFIG
|
||||
|
||||
// 找到所有盟友
|
||||
const allies = allNpcs.filter(npc => attackedNpc.allies?.includes(npc.id))
|
||||
|
||||
allies.forEach(ally => {
|
||||
// 更新盟友对玩家的关系 - 摧毁盟友星球的惩罚是攻击的两倍
|
||||
if (!ally.relations) {
|
||||
ally.relations = {}
|
||||
}
|
||||
|
||||
const allyRelation = getOrCreateRelation(ally.relations, ally.id, attacker.id)
|
||||
const reputationLoss = REPUTATION_CHANGES.ALLY_ATTACKED * 2 // 双倍惩罚
|
||||
ally.relations[attacker.id] = updateReputation(
|
||||
allyRelation,
|
||||
reputationLoss,
|
||||
DET.DestroyPlanet,
|
||||
t('diplomacy.reports.playerDestroyedAllyPlanet', locale, { allyName: attackedNpc.name, planetName: destroyedPlanet.name })
|
||||
)
|
||||
|
||||
// 生成外交报告
|
||||
generateDiplomaticReport(
|
||||
attacker,
|
||||
ally,
|
||||
DET.DestroyPlanet,
|
||||
reputationLoss,
|
||||
t('diplomacy.reports.allyOutraged', locale, {
|
||||
allyName: ally.name,
|
||||
targetName: attackedNpc.name,
|
||||
planetName: destroyedPlanet.name
|
||||
}),
|
||||
'diplomacy.reports.allyOutraged',
|
||||
{
|
||||
allyName: ally.name,
|
||||
targetName: attackedNpc.name,
|
||||
planetName: destroyedPlanet.name
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成外交报告
|
||||
* @param player 玩家
|
||||
* @param npc NPC
|
||||
* @param eventType 事件类型
|
||||
* @param reputationChange 好感度变化值
|
||||
* @param message 消息内容
|
||||
* @param message 消息内容(已弃用,用于向后兼容)
|
||||
* @param messageKey 翻译键(可选)
|
||||
* @param messageParams 翻译参数(可选)
|
||||
*/
|
||||
const generateDiplomaticReport = (
|
||||
player: Player,
|
||||
npc: NPC,
|
||||
eventType: DiplomaticEventType,
|
||||
reputationChange: number,
|
||||
message: string
|
||||
message: string,
|
||||
messageKey?: string,
|
||||
messageParams?: Record<string, string | number>
|
||||
): void => {
|
||||
if (!player.diplomaticReports) {
|
||||
player.diplomaticReports = []
|
||||
@@ -570,6 +724,8 @@ const generateDiplomaticReport = (
|
||||
oldStatus,
|
||||
newStatus,
|
||||
message,
|
||||
messageKey,
|
||||
messageParams,
|
||||
read: false
|
||||
}
|
||||
|
||||
@@ -724,3 +880,78 @@ export const rejectNPCGift = (player: Player, npc: NPC, giftNotification: GiftNo
|
||||
player.giftNotifications = player.giftNotifications.filter(n => n.id !== giftNotification.id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理NPC被彻底消灭(所有星球被摧毁)
|
||||
* @param eliminatedNpc 被消灭的NPC
|
||||
* @param player 玩家
|
||||
* @param allNpcs 所有NPC列表
|
||||
* @param locale 语言代码
|
||||
*/
|
||||
export const handleNPCElimination = (eliminatedNpc: NPC, player: Player, allNpcs: NPC[], locale: Locale): void => {
|
||||
const { HOSTILE_THRESHOLD } = DIPLOMATIC_CONFIG
|
||||
|
||||
// 1. 将玩家对该NPC的关系设为最低(敌对状态)
|
||||
if (!player.diplomaticRelations) {
|
||||
player.diplomaticRelations = {}
|
||||
}
|
||||
|
||||
const relation = getOrCreateRelation(player.diplomaticRelations, player.id, eliminatedNpc.id)
|
||||
const now = Date.now()
|
||||
|
||||
player.diplomaticRelations[eliminatedNpc.id] = {
|
||||
...relation,
|
||||
reputation: HOSTILE_THRESHOLD, // 设为敌对阈值
|
||||
status: RS.Hostile,
|
||||
lastUpdated: now,
|
||||
history: [
|
||||
...(relation.history || []),
|
||||
{
|
||||
timestamp: now,
|
||||
change: HOSTILE_THRESHOLD - relation.reputation,
|
||||
reason: DET.DestroyPlanet,
|
||||
details: t('diplomacy.reports.npcEliminated', locale, { npcName: eliminatedNpc.name })
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 2. 生成外交报告
|
||||
generateDiplomaticReport(
|
||||
player,
|
||||
eliminatedNpc,
|
||||
DET.DestroyPlanet,
|
||||
HOSTILE_THRESHOLD,
|
||||
t('diplomacy.reports.npcEliminatedMessage', locale, { npcName: eliminatedNpc.name }),
|
||||
'diplomacy.reports.npcEliminatedMessage',
|
||||
{ npcName: eliminatedNpc.name }
|
||||
)
|
||||
|
||||
// 3. 从所有其他NPC的盟友列表中移除被消灭的NPC
|
||||
allNpcs.forEach(npc => {
|
||||
if (npc.id !== eliminatedNpc.id && npc.allies && npc.allies.includes(eliminatedNpc.id)) {
|
||||
npc.allies = npc.allies.filter(allyId => allyId !== eliminatedNpc.id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并处理被消灭的NPC(所有星球都被摧毁的NPC)
|
||||
* @param allNpcs 所有NPC列表
|
||||
* @param player 玩家
|
||||
* @param locale 语言代码
|
||||
* @returns 被消灭的NPC ID列表
|
||||
*/
|
||||
export const checkAndHandleEliminatedNPCs = (allNpcs: NPC[], player: Player, locale: Locale): string[] => {
|
||||
const eliminatedNpcIds: string[] = []
|
||||
|
||||
allNpcs.forEach(npc => {
|
||||
// 检查NPC是否还有星球
|
||||
if (!npc.planets || npc.planets.length === 0) {
|
||||
// NPC被彻底消灭
|
||||
handleNPCElimination(npc, player, allNpcs, locale)
|
||||
eliminatedNpcIds.push(npc.id)
|
||||
}
|
||||
})
|
||||
|
||||
return eliminatedNpcIds
|
||||
}
|
||||
|
||||
@@ -36,9 +36,10 @@ export const calculateDistance = (
|
||||
|
||||
/**
|
||||
* 计算飞行时间
|
||||
* 平衡后的时间倍率,确保游戏节奏合理
|
||||
*/
|
||||
export const calculateFlightTime = (distance: number, minSpeed: number): number => {
|
||||
return Math.max(10, Math.floor((distance * 10000) / minSpeed)) // 至少10秒
|
||||
return Math.max(10, Math.floor((distance * 50) / minSpeed)) // 至少10秒
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -717,7 +718,8 @@ export const updateFleetMissions = async (
|
||||
attacker: Player,
|
||||
defender: Player | null,
|
||||
now: number,
|
||||
allNpcs?: NPC[]
|
||||
allNpcs?: NPC[],
|
||||
locale?: Locale
|
||||
): Promise<{
|
||||
completedMissions: string[]
|
||||
battleReports: BattleResult[]
|
||||
@@ -767,7 +769,24 @@ export const updateFleetMissions = async (
|
||||
planets.set(moonKey, attackResult.moon)
|
||||
}
|
||||
if (attackResult.debrisField) {
|
||||
newDebrisFields.push(attackResult.debrisField)
|
||||
// 检查该位置是否已存在残骸场
|
||||
const existingDebris = debrisFields.get(attackResult.debrisField.id)
|
||||
if (existingDebris) {
|
||||
// 累加残骸资源
|
||||
const updatedDebris: DebrisField = {
|
||||
...existingDebris,
|
||||
resources: {
|
||||
metal: existingDebris.resources.metal + attackResult.debrisField.resources.metal,
|
||||
crystal: existingDebris.resources.crystal + attackResult.debrisField.resources.crystal
|
||||
}
|
||||
}
|
||||
debrisFields.set(attackResult.debrisField.id, updatedDebris)
|
||||
updatedDebrisFields.push(updatedDebris)
|
||||
} else {
|
||||
// 新建残骸场
|
||||
debrisFields.set(attackResult.debrisField.id, attackResult.debrisField)
|
||||
newDebrisFields.push(attackResult.debrisField)
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
@@ -821,6 +840,15 @@ export const updateFleetMissions = async (
|
||||
if (destroyResult && destroyResult.success && destroyResult.planetId) {
|
||||
// 星球被摧毁
|
||||
destroyedPlanetIds.push(destroyResult.planetId)
|
||||
|
||||
// 处理外交关系(如果目标是NPC星球)
|
||||
if (targetPlanet && targetPlanet.ownerId && allNpcs && locale) {
|
||||
const planetOwner = allNpcs.find(npc => npc.id === targetPlanet.ownerId)
|
||||
if (planetOwner) {
|
||||
diplomaticLogic.handlePlanetDestructionReputation(attacker, targetPlanet, planetOwner, allNpcs, locale)
|
||||
}
|
||||
}
|
||||
|
||||
planets.delete(targetKey)
|
||||
}
|
||||
break
|
||||
|
||||
@@ -39,7 +39,12 @@ export const calculateMaxFleetStorage = (planet: Planet, technologies: Record<Te
|
||||
const shipyardBonus = BUILDINGS[BuildingType.Shipyard].fleetStorageBonus || 0
|
||||
maxStorage += shipyardLevel * shipyardBonus
|
||||
|
||||
// 3. 计算机技术全局加成
|
||||
// 3. 机库建筑加成(每个星球独立)
|
||||
const hangarLevel = planet.buildings[BuildingType.Hangar] || 0
|
||||
const hangarBonus = BUILDINGS[BuildingType.Hangar].fleetStorageBonus || 0
|
||||
maxStorage += hangarLevel * hangarBonus
|
||||
|
||||
// 4. 计算机技术全局加成
|
||||
const computerTechLevel = technologies[TechnologyType.ComputerTechnology] || 0
|
||||
const computerTechBonus = TECHNOLOGIES[TechnologyType.ComputerTechnology].fleetStorageBonus || 0
|
||||
maxStorage += computerTechLevel * computerTechBonus
|
||||
@@ -82,5 +87,9 @@ export const getMaxBuildableShips = (planet: Planet, shipType: ShipType, technol
|
||||
const shipStorageUsage = SHIPS[shipType].storageUsage
|
||||
|
||||
if (shipStorageUsage === 0) return Number.MAX_SAFE_INTEGER
|
||||
|
||||
// 如果当前已经超限(舰队返回等情况),则不允许建造新舰船
|
||||
if (availableStorage <= 0) return 0
|
||||
|
||||
return Math.floor(availableStorage / shipStorageUsage)
|
||||
}
|
||||
|
||||
@@ -100,7 +100,8 @@ export const generatePositionKey = (galaxy: number, system: number, position: nu
|
||||
*/
|
||||
export const processGameUpdate = (
|
||||
player: Player,
|
||||
now: number
|
||||
now: number,
|
||||
gameSpeed: number = 1
|
||||
): {
|
||||
updatedResearchQueue: BuildQueueItem[]
|
||||
} => {
|
||||
@@ -114,7 +115,7 @@ export const processGameUpdate = (
|
||||
|
||||
// 更新所有星球资源(直接同步计算,避免 Worker 通信开销)
|
||||
player.planets.forEach(planet => {
|
||||
resourceLogic.updatePlanetResources(planet, now, bonuses)
|
||||
resourceLogic.updatePlanetResources(planet, now, bonuses, gameSpeed)
|
||||
})
|
||||
|
||||
// 更新所有星球其他状态
|
||||
|
||||
@@ -31,11 +31,7 @@ export const calculateSystemDistance = (from: Position, to: Position): number =>
|
||||
/**
|
||||
* 检查目标是否在射程内
|
||||
*/
|
||||
export const isTargetInRange = (
|
||||
originPosition: Position,
|
||||
targetPosition: Position,
|
||||
impulseDriveLevel: number
|
||||
): boolean => {
|
||||
export const isTargetInRange = (originPosition: Position, targetPosition: Position, impulseDriveLevel: number): boolean => {
|
||||
const range = calculateMissileRange(impulseDriveLevel)
|
||||
const distance = calculateSystemDistance(originPosition, targetPosition)
|
||||
return distance <= range
|
||||
@@ -156,11 +152,7 @@ export const calculateMissileImpact = (
|
||||
const defenseTypes = Object.keys(defenderPlanet.defense) as DefenseType[]
|
||||
const availableDefenses = defenseTypes.filter(type => {
|
||||
// 不能摧毁护盾罩和行星护盾
|
||||
if (
|
||||
type === DefenseTypes.SmallShieldDome ||
|
||||
type === DefenseTypes.LargeShieldDome ||
|
||||
type === DefenseTypes.PlanetaryShield
|
||||
) {
|
||||
if (type === DefenseTypes.SmallShieldDome || type === DefenseTypes.LargeShieldDome || type === DefenseTypes.PlanetaryShield) {
|
||||
return false
|
||||
}
|
||||
return (defenderPlanet.defense[type] || 0) > 0
|
||||
@@ -196,10 +188,7 @@ export const calculateMissileImpact = (
|
||||
/**
|
||||
* 应用导弹攻击结果到星球
|
||||
*/
|
||||
export const applyMissileAttackResult = (
|
||||
planet: Planet,
|
||||
defenseLosses: Partial<Record<DefenseType, number>>
|
||||
): void => {
|
||||
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)
|
||||
|
||||
@@ -61,14 +61,14 @@ export const calculateDynamicDifficulty = (playerPoints: number): DynamicDifficu
|
||||
// 积分区间和对应的难度参数
|
||||
if (playerPoints < 1000) {
|
||||
// 新手期:0-1,000分
|
||||
// NPC保持30-50%实力,给予充分发展空间
|
||||
// NPC保持30-50%实力,给予充分发展空间,但资源增长速度加快
|
||||
const ratio = 0.3 + (playerPoints / 1000) * 0.2
|
||||
return {
|
||||
powerRatio: ratio,
|
||||
checkInterval: 300, // 5分钟
|
||||
resourceGrowthRate: 0.4,
|
||||
buildingGrowthSpeed: 0.4,
|
||||
techGrowthSpeed: 0.4
|
||||
resourceGrowthRate: 0.8, // 从0.4提升到0.8,确保NPC有足够资源发育
|
||||
buildingGrowthSpeed: 0.6, // 从0.4提升到0.6
|
||||
techGrowthSpeed: 0.6 // 从0.4提升到0.6
|
||||
}
|
||||
} else if (playerPoints < 5000) {
|
||||
// 初级期:1,000-5,000分
|
||||
@@ -77,9 +77,9 @@ export const calculateDynamicDifficulty = (playerPoints: number): DynamicDifficu
|
||||
return {
|
||||
powerRatio: ratio,
|
||||
checkInterval: 240, // 4分钟
|
||||
resourceGrowthRate: 0.6,
|
||||
buildingGrowthSpeed: 0.6,
|
||||
techGrowthSpeed: 0.6
|
||||
resourceGrowthRate: 1.0, // 从0.6提升到1.0,与玩家资源产出相当
|
||||
buildingGrowthSpeed: 0.8, // 从0.6提升到0.8
|
||||
techGrowthSpeed: 0.8 // 从0.6提升到0.8
|
||||
}
|
||||
} else if (playerPoints < 20000) {
|
||||
// 中级期:5,000-20,000分
|
||||
@@ -559,9 +559,7 @@ export const initializeNPCDiplomacy = (npcs: NPC[]): void => {
|
||||
// 为每个NPC随机分配1-2个盟友
|
||||
npcs.forEach(npc => {
|
||||
// 获取还未建立关系的潜在盟友
|
||||
const potentialAllies = npcs.filter(
|
||||
n => n.id !== npc.id && !npc.allies!.includes(n.id) && !n.allies!.includes(npc.id)
|
||||
)
|
||||
const potentialAllies = npcs.filter(n => n.id !== npc.id && !npc.allies!.includes(n.id) && !n.allies!.includes(npc.id))
|
||||
|
||||
if (potentialAllies.length === 0) return
|
||||
|
||||
|
||||
@@ -22,9 +22,7 @@ export const validateTechnologyResearch = (
|
||||
const cost = researchLogic.calculateTechnologyCost(techType, targetLevel)
|
||||
|
||||
// 检查队列中是否已存在该科技的研究任务
|
||||
const existingQueueItem = researchQueue.find(
|
||||
item => item.type === 'technology' && item.itemType === techType
|
||||
)
|
||||
const existingQueueItem = researchQueue.find(item => item.type === 'technology' && item.itemType === techType)
|
||||
if (existingQueueItem) {
|
||||
return { valid: false, reason: 'errors.technologyAlreadyInQueue' }
|
||||
}
|
||||
|
||||
@@ -115,23 +115,27 @@ export const updatePlanetResources = (
|
||||
darkMatterProductionBonus: number
|
||||
energyProductionBonus: number
|
||||
storageCapacityBonus: number
|
||||
}
|
||||
},
|
||||
gameSpeed: number = 1
|
||||
): void => {
|
||||
const timeDiff = (now - planet.lastUpdate) / 1000 // 转换为秒
|
||||
|
||||
// 应用游戏速度到时间差(游戏速度影响资源产出速率)
|
||||
const effectiveTimeDiff = timeDiff * gameSpeed
|
||||
|
||||
// 计算能量消耗(每小时)
|
||||
const energyConsumption = calculateEnergyConsumption(planet)
|
||||
|
||||
// 先增加能量产出
|
||||
const energyProduction = calculateEnergyProduction(planet, { energyProductionBonus: bonuses.energyProductionBonus })
|
||||
planet.resources.energy += (energyProduction * timeDiff) / 3600
|
||||
planet.resources.energy += (energyProduction * effectiveTimeDiff) / 3600
|
||||
|
||||
// 限制能量上限
|
||||
const capacity = calculateResourceCapacity(planet, bonuses.storageCapacityBonus)
|
||||
planet.resources.energy = Math.min(planet.resources.energy, capacity.energy)
|
||||
|
||||
// 扣除能量消耗
|
||||
planet.resources.energy -= (energyConsumption * timeDiff) / 3600
|
||||
planet.resources.energy -= (energyConsumption * effectiveTimeDiff) / 3600
|
||||
|
||||
// 能量不能为负数,最低为0
|
||||
planet.resources.energy = Math.max(0, planet.resources.energy)
|
||||
@@ -143,11 +147,11 @@ export const updatePlanetResources = (
|
||||
energyProductionBonus: bonuses.energyProductionBonus
|
||||
})
|
||||
|
||||
// 更新资源(转换为每秒产量)
|
||||
planet.resources.metal += (production.metal * timeDiff) / 3600
|
||||
planet.resources.crystal += (production.crystal * timeDiff) / 3600
|
||||
planet.resources.deuterium += (production.deuterium * timeDiff) / 3600
|
||||
planet.resources.darkMatter += (production.darkMatter * timeDiff) / 3600
|
||||
// 更新资源(转换为每秒产量,应用游戏速度)
|
||||
planet.resources.metal += (production.metal * effectiveTimeDiff) / 3600
|
||||
planet.resources.crystal += (production.crystal * effectiveTimeDiff) / 3600
|
||||
planet.resources.deuterium += (production.deuterium * effectiveTimeDiff) / 3600
|
||||
planet.resources.darkMatter += (production.darkMatter * effectiveTimeDiff) / 3600
|
||||
|
||||
// 限制资源上限
|
||||
planet.resources.metal = Math.min(planet.resources.metal, capacity.metal)
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import type { Planet, Player, BuildQueueItem, FleetMission, BattleResult, SpyReport, Officer, SpiedNotification, NPCActivityNotification, IncomingFleetAlert, MissileAttack } 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'
|
||||
|
||||
@@ -24,6 +24,7 @@ export const BuildingType = {
|
||||
RoboticsFactory: 'roboticsFactory',
|
||||
NaniteFactory: 'naniteFactory', // 纳米工厂
|
||||
Shipyard: 'shipyard',
|
||||
Hangar: 'hangar', // 机库
|
||||
ResearchLab: 'researchLab',
|
||||
MetalStorage: 'metalStorage',
|
||||
CrystalStorage: 'crystalStorage',
|
||||
@@ -227,7 +228,8 @@ export const DiplomaticEventType = {
|
||||
Attack: 'attack', // 攻击
|
||||
Spy: 'spy', // 侦查
|
||||
StealDebris: 'stealDebris', // 抢夺残骸
|
||||
AllyAttacked: 'allyAttacked' // 盟友被攻击
|
||||
AllyAttacked: 'allyAttacked', // 盟友被攻击
|
||||
DestroyPlanet: 'destroyPlanet' // 摧毁星球
|
||||
} as const
|
||||
|
||||
export type DiplomaticEventType = (typeof DiplomaticEventType)[keyof typeof DiplomaticEventType]
|
||||
@@ -259,7 +261,9 @@ export interface DiplomaticReport {
|
||||
newReputation: number // 新的好感度值
|
||||
oldStatus: RelationStatus // 旧的关系状态
|
||||
newStatus: RelationStatus // 新的关系状态
|
||||
message: string // 消息内容
|
||||
message: string // 消息内容(已弃用,保留用于兼容性)
|
||||
messageKey?: string // 翻译键(如 'diplomacy.reports.youDestroyedNpcPlanet')
|
||||
messageParams?: Record<string, string | number> // 翻译参数(如 { npcName: 'NPC-1', planetName: '星球 1:1:8', reputation: -80 })
|
||||
read?: boolean // 已读状态
|
||||
}
|
||||
|
||||
@@ -568,6 +572,8 @@ export interface Player {
|
||||
// 外交系统字段
|
||||
diplomaticRelations?: Record<string, DiplomaticRelation> // 玩家对NPC的关系(key: npcId)
|
||||
diplomaticReports?: DiplomaticReport[] // 外交变化报告
|
||||
// 新手引导字段
|
||||
tutorialProgress?: TutorialProgress // 新手引导进度
|
||||
}
|
||||
|
||||
// 游戏状态
|
||||
@@ -616,3 +622,33 @@ export interface NPC {
|
||||
allies?: string[] // 盟友列表(NPC ID)
|
||||
enemies?: string[] // 敌人列表(NPC ID)
|
||||
}
|
||||
|
||||
// 新手引导系统
|
||||
export interface TutorialStep {
|
||||
id: string
|
||||
title: string // 标题
|
||||
content: string // 内容描述
|
||||
target?: string // 目标元素的选择器或ID
|
||||
placement?: 'top' | 'bottom' | 'left' | 'right' | 'center' // 提示框位置
|
||||
route?: string // 需要跳转的路由
|
||||
action?: 'click' | 'build' | 'research' | 'none' // 需要完成的操作类型
|
||||
actionTarget?: string // 操作目标(建筑ID、科技ID等)
|
||||
completionCheck?: () => boolean // 完成条件检查函数(运行时注入)
|
||||
canSkip?: boolean // 是否可跳过此步骤
|
||||
highlightPadding?: number // 高亮区域的padding
|
||||
}
|
||||
|
||||
export interface TutorialState {
|
||||
isActive: boolean // 引导是否激活
|
||||
currentStepIndex: number // 当前步骤索引
|
||||
completedSteps: string[] // 已完成的步骤ID列表
|
||||
skipped: boolean // 是否已跳过整个引导
|
||||
lastActiveTime?: number // 最后活跃时间
|
||||
}
|
||||
|
||||
export interface TutorialProgress {
|
||||
tutorialCompleted: boolean // 是否完成了整个引导
|
||||
completedStepIds: string[] // 已完成的步骤ID
|
||||
currentStep: string | null // 当前步骤ID
|
||||
skippedAt?: number // 跳过的时间戳
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/**
|
||||
* 格式化数字为英文单位(K, M, B, T, Q)
|
||||
* @param num 数字
|
||||
@@ -32,6 +31,30 @@ export const getResourceColor = (current: number, max: number): string => {
|
||||
if (ratio >= 0.7) return 'text-yellow-600 dark:text-yellow-400'
|
||||
return ''
|
||||
}
|
||||
/**
|
||||
* 格式化相对时间(用于显示"多久之前")
|
||||
* @param seconds 秒数
|
||||
* @param t 翻译函数
|
||||
* @returns 格式化后的相对时间字符串
|
||||
*/
|
||||
export const formatRelativeTime = (seconds: number, t: (key: string) => string): string => {
|
||||
const days = Math.floor(seconds / 86400)
|
||||
const hours = Math.floor((seconds % 86400) / 3600)
|
||||
const minutes = Math.floor((seconds % 3600) / 60)
|
||||
const secs = Math.floor(seconds % 60)
|
||||
|
||||
if (days > 0) {
|
||||
return `${days}${t('time.days')}${hours}${t('time.hours')}`
|
||||
}
|
||||
if (hours > 0) {
|
||||
return `${hours}${t('time.hours')}${minutes}${t('time.minutes')}`
|
||||
}
|
||||
if (minutes > 0) {
|
||||
return `${minutes}${t('time.minutes')}`
|
||||
}
|
||||
return `${secs}${t('time.seconds')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化时间(秒转为 年:天:时:分:秒)
|
||||
* @param seconds 秒数
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
||||
<Card v-for="buildingType in availableBuildings" :key="buildingType" class="relative">
|
||||
<Card v-for="buildingType in availableBuildings" :key="buildingType" :data-building="buildingType" class="relative">
|
||||
<!-- 前置条件遮罩 -->
|
||||
<CardUnlockOverlay :requirements="BUILDINGS[buildingType].requirements" :currentLevel="getBuildingLevel(buildingType)" />
|
||||
|
||||
@@ -225,18 +225,18 @@
|
||||
})
|
||||
})
|
||||
|
||||
const upgradeBuilding = (buildingType: BuildingType): boolean => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const upgradeBuilding = (buildingType: BuildingType): { success: boolean; reason?: string } => {
|
||||
if (!gameStore.currentPlanet) return { success: false }
|
||||
const validation = buildingValidation.validateBuildingUpgrade(
|
||||
gameStore.currentPlanet,
|
||||
buildingType,
|
||||
gameStore.player.technologies,
|
||||
gameStore.player.officers
|
||||
)
|
||||
if (!validation.valid) return false
|
||||
if (!validation.valid) return { success: false, reason: validation.reason }
|
||||
const queueItem = buildingValidation.executeBuildingUpgrade(gameStore.currentPlanet, buildingType, gameStore.player.officers)
|
||||
gameStore.currentPlanet.buildQueue.push(queueItem)
|
||||
return true
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
const getUsedSpace = (planet: Planet): number => {
|
||||
@@ -253,10 +253,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
const success = upgradeBuilding(buildingType)
|
||||
if (!success) {
|
||||
const result = upgradeBuilding(buildingType)
|
||||
if (!result.success) {
|
||||
alertDialogTitle.value = t('buildingsView.upgradeFailed')
|
||||
alertDialogMessage.value = t('buildingsView.upgradeFailedMessage')
|
||||
alertDialogMessage.value = result.reason ? t(result.reason) : t('buildingsView.upgradeFailedMessage')
|
||||
alertDialogOpen.value = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,14 +231,14 @@
|
||||
return defenseType === DefenseType.SmallShieldDome || defenseType === DefenseType.LargeShieldDome
|
||||
}
|
||||
|
||||
const buildDefense = (defenseType: DefenseType, quantity: number): boolean => {
|
||||
const buildDefense = (defenseType: DefenseType, quantity: number): { success: boolean; reason?: string } => {
|
||||
const currentPlanet = gameStore.currentPlanet
|
||||
if (!currentPlanet) return false
|
||||
if (!currentPlanet) return { success: false }
|
||||
const validation = shipValidation.validateDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.technologies)
|
||||
if (!validation.valid) return false
|
||||
if (!validation.valid) return { success: false, reason: validation.reason }
|
||||
const queueItem = shipValidation.executeDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.officers)
|
||||
currentPlanet.buildQueue.push(queueItem)
|
||||
return true
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
// 建造防御设施
|
||||
@@ -251,10 +251,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
const success = buildDefense(defenseType, quantity)
|
||||
if (!success) {
|
||||
const result = buildDefense(defenseType, quantity)
|
||||
if (!result.success) {
|
||||
alertDialogTitle.value = t('defenseView.buildFailed')
|
||||
alertDialogMessage.value = t('defenseView.buildFailedMessage')
|
||||
alertDialogMessage.value = result.reason ? t(result.reason) : t('defenseView.buildFailedMessage')
|
||||
alertDialogOpen.value = true
|
||||
} else {
|
||||
quantities.value[defenseType] = 0
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
<TabsList class="grid w-full grid-cols-4">
|
||||
<TabsTrigger value="all">
|
||||
{{ t('diplomacy.tabs.all') }}
|
||||
<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>
|
||||
<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') }}
|
||||
@@ -50,7 +55,13 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<NpcRelationCard v-for="npc in paginatedAllNpcs" :key="npc.id" :npc="npc" :relation="getRelation(npc.id)" />
|
||||
<NpcRelationCard
|
||||
v-for="npc in paginatedAllNpcs"
|
||||
:key="npc.id"
|
||||
:npc="npc"
|
||||
:relation="getRelation(npc.id)"
|
||||
:data-npc-id="npc.id"
|
||||
/>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="totalPagesAll > 1"
|
||||
@@ -84,7 +95,13 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<NpcRelationCard v-for="npc in paginatedFriendlyNpcs" :key="npc.id" :npc="npc" :relation="getRelation(npc.id)" />
|
||||
<NpcRelationCard
|
||||
v-for="npc in paginatedFriendlyNpcs"
|
||||
:key="npc.id"
|
||||
:npc="npc"
|
||||
:relation="getRelation(npc.id)"
|
||||
:data-npc-id="npc.id"
|
||||
/>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="totalPagesFriendly > 1"
|
||||
@@ -118,7 +135,13 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<NpcRelationCard v-for="npc in paginatedNeutralNpcs" :key="npc.id" :npc="npc" :relation="getRelation(npc.id)" />
|
||||
<NpcRelationCard
|
||||
v-for="npc in paginatedNeutralNpcs"
|
||||
:key="npc.id"
|
||||
:npc="npc"
|
||||
:relation="getRelation(npc.id)"
|
||||
:data-npc-id="npc.id"
|
||||
/>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="totalPagesNeutral > 1"
|
||||
@@ -152,7 +175,13 @@
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<NpcRelationCard v-for="npc in paginatedHostileNpcs" :key="npc.id" :npc="npc" :relation="getRelation(npc.id)" />
|
||||
<NpcRelationCard
|
||||
v-for="npc in paginatedHostileNpcs"
|
||||
:key="npc.id"
|
||||
:npc="npc"
|
||||
:relation="getRelation(npc.id)"
|
||||
:data-npc-id="npc.id"
|
||||
/>
|
||||
</div>
|
||||
<Pagination
|
||||
v-if="totalPagesHostile > 1"
|
||||
@@ -179,57 +208,20 @@
|
||||
</template>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<!-- 外交报告历史 -->
|
||||
<Card v-if="diplomaticReports.length > 0">
|
||||
<CardHeader>
|
||||
<CardTitle>{{ t('diplomacy.recentEvents') }}</CardTitle>
|
||||
<CardDescription>{{ t('diplomacy.recentEventsDescription') }}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="space-y-2 max-h-96 overflow-y-auto">
|
||||
<div
|
||||
v-for="report in diplomaticReports"
|
||||
:key="report.id"
|
||||
class="flex items-start gap-3 p-3 rounded-lg border bg-card hover:bg-accent/50 transition-colors"
|
||||
>
|
||||
<div class="flex-shrink-0 mt-0.5">
|
||||
<component :is="getEventIcon(report.eventType)" class="h-5 w-5" :class="getEventIconColor(report.reputationChange)" />
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-medium">{{ report.npcName }}</span>
|
||||
<Badge :variant="getReputationBadgeVariant(report.reputationChange)" class="text-xs">
|
||||
{{ report.reputationChange > 0 ? '+' : '' }}{{ report.reputationChange }}
|
||||
</Badge>
|
||||
<Badge :variant="getStatusBadgeVariant(report.newStatus)" class="text-xs">
|
||||
{{ getStatusText(report.newStatus) }}
|
||||
</Badge>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">{{ report.message }}</p>
|
||||
<p class="text-xs text-muted-foreground mt-1">{{ formatTime(Date.now() - report.timestamp) }} {{ t('diplomacy.ago') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { computed, ref, onMounted, onUnmounted, nextTick } from 'vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Pagination, PaginationContent, PaginationItem, PaginationNext, PaginationPrevious } from '@/components/ui/pagination'
|
||||
import NpcRelationCard from '@/components/NpcRelationCard.vue'
|
||||
import { Gift, Sword, Eye, Trash2 } from 'lucide-vue-next'
|
||||
import { RelationStatus, DiplomaticEventType } from '@/types/game'
|
||||
import type { DiplomaticRelation, DiplomaticReport } from '@/types/game'
|
||||
import { formatTime } from '@/utils/format'
|
||||
import { RelationStatus } from '@/types/game'
|
||||
import type { DiplomaticRelation } from '@/types/game'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const npcStore = useNPCStore()
|
||||
@@ -281,6 +273,49 @@
|
||||
// 组件挂载时初始化NPC盟友
|
||||
onMounted(() => {
|
||||
initializeNPCAllies()
|
||||
|
||||
// 监听滚动到NPC卡片的事件
|
||||
const handleScrollToNpc = (event: Event) => {
|
||||
const customEvent = event as CustomEvent<{ npcId: string }>
|
||||
const npcId = customEvent.detail.npcId
|
||||
|
||||
// 切换到"全部"标签
|
||||
activeTab.value = 'all'
|
||||
|
||||
// 等待DOM更新后再滚动
|
||||
nextTick(() => {
|
||||
// 找到目标NPC在列表中的索引
|
||||
const npcIndex = allNpcs.value.findIndex(npc => npc.id === npcId)
|
||||
if (npcIndex === -1) return
|
||||
|
||||
// 计算目标NPC所在的页面
|
||||
const targetPage = Math.floor(npcIndex / ITEMS_PER_PAGE) + 1
|
||||
currentPage.value.all = targetPage
|
||||
|
||||
// 再次等待分页更新后滚动到卡片
|
||||
nextTick(() => {
|
||||
// 使用data属性来标识卡片
|
||||
const cards = document.querySelectorAll('[data-npc-id]')
|
||||
const targetCard = Array.from(cards).find(card => card.getAttribute('data-npc-id') === npcId)
|
||||
|
||||
if (targetCard) {
|
||||
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||||
// 添加高亮效果
|
||||
targetCard.classList.add('ring-2', 'ring-primary', 'ring-offset-2')
|
||||
setTimeout(() => {
|
||||
targetCard.classList.remove('ring-2', 'ring-primary', 'ring-offset-2')
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('scrollToNpc', handleScrollToNpc)
|
||||
|
||||
// 清理事件监听器
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('scrollToNpc', handleScrollToNpc)
|
||||
})
|
||||
})
|
||||
|
||||
// 分页状态
|
||||
@@ -387,65 +422,4 @@
|
||||
const pageNumbersFriendly = computed(() => getPageNumbers(currentPage.value.friendly || 1, totalPagesFriendly.value))
|
||||
const pageNumbersNeutral = computed(() => getPageNumbers(currentPage.value.neutral || 1, totalPagesNeutral.value))
|
||||
const pageNumbersHostile = computed(() => getPageNumbers(currentPage.value.hostile || 1, totalPagesHostile.value))
|
||||
|
||||
// 外交报告(最近20条,按时间倒序)
|
||||
const diplomaticReports = computed(() => {
|
||||
const reports = gameStore.player.diplomaticReports || []
|
||||
return [...reports].sort((a, b) => b.timestamp - a.timestamp).slice(0, 20)
|
||||
})
|
||||
|
||||
// 获取事件图标
|
||||
const getEventIcon = (eventType: DiplomaticReport['eventType']) => {
|
||||
switch (eventType) {
|
||||
case DiplomaticEventType.GiftResources:
|
||||
return Gift
|
||||
case DiplomaticEventType.Attack:
|
||||
case DiplomaticEventType.AllyAttacked:
|
||||
return Sword
|
||||
case DiplomaticEventType.Spy:
|
||||
return Eye
|
||||
case DiplomaticEventType.StealDebris:
|
||||
return Trash2
|
||||
default:
|
||||
return Gift
|
||||
}
|
||||
}
|
||||
|
||||
// 获取事件图标颜色
|
||||
const getEventIconColor = (reputationChange: number) => {
|
||||
if (reputationChange > 0) return 'text-green-600 dark:text-green-400'
|
||||
if (reputationChange < 0) return 'text-red-600 dark:text-red-400'
|
||||
return 'text-muted-foreground'
|
||||
}
|
||||
|
||||
// 获取好感度Badge样式
|
||||
const getReputationBadgeVariant = (change: number) => {
|
||||
if (change > 0) return 'default'
|
||||
if (change < 0) return 'destructive'
|
||||
return 'secondary'
|
||||
}
|
||||
|
||||
// 获取关系状态Badge样式
|
||||
const getStatusBadgeVariant = (status: RelationStatus) => {
|
||||
switch (status) {
|
||||
case RelationStatus.Friendly:
|
||||
return 'default'
|
||||
case RelationStatus.Hostile:
|
||||
return 'destructive'
|
||||
default:
|
||||
return 'secondary'
|
||||
}
|
||||
}
|
||||
|
||||
// 获取关系状态文本
|
||||
const getStatusText = (status: RelationStatus) => {
|
||||
switch (status) {
|
||||
case RelationStatus.Friendly:
|
||||
return t('diplomacy.status.friendly')
|
||||
case RelationStatus.Hostile:
|
||||
return t('diplomacy.status.hostile')
|
||||
default:
|
||||
return t('diplomacy.status.neutral')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<!-- 标签切换 -->
|
||||
<Tabs v-model="activeTab" class="w-full">
|
||||
<TabsList class="grid w-full grid-cols-2 sm:grid-cols-4" :tab-count="4">
|
||||
<TabsList class="grid w-full grid-cols-2 sm:grid-cols-4">
|
||||
<TabsTrigger v-for="tab in tabs" :key="tab.value" :value="tab.value" class="flex items-center justify-center gap-1 px-2">
|
||||
<component :is="tab.icon" class="h-3 w-3 sm:h-4 sm:w-4" />
|
||||
<span class="text-xs sm:text-sm truncate">{{ tab.label }}</span>
|
||||
@@ -296,6 +296,249 @@
|
||||
|
||||
<!-- 间谍报告对话框 -->
|
||||
<SpyReportDialog v-model:open="showSpyDialog" :report="selectedSpyReport" />
|
||||
|
||||
<!-- 被侦查通知详情对话框 -->
|
||||
<Dialog :open="showSpiedDialog" @update:open="showSpiedDialog = $event">
|
||||
<DialogContent class="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<Eye class="h-5 w-5 text-purple-500" />
|
||||
{{ t('messagesView.spiedNotificationDetails') }}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="selectedSpiedNotification" class="space-y-4">
|
||||
<!-- 侦查者信息 -->
|
||||
<div class="p-4 bg-muted/50 rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h3 class="font-semibold text-lg">{{ selectedSpiedNotification.npcName }}</h3>
|
||||
<Badge variant="destructive">{{ t('messagesView.spyDetected') }}</Badge>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ formatDate(selectedSpiedNotification.timestamp) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 被侦查星球 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.targetPlanet') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md flex items-center gap-2">
|
||||
<Globe class="h-4 w-4 text-blue-500" />
|
||||
<span class="font-medium">{{ selectedSpiedNotification.targetPlanetName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 检测结果 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.detectionResult') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<div v-if="selectedSpiedNotification.detectionSuccess" class="flex items-center gap-2 text-yellow-600 dark:text-yellow-400">
|
||||
<AlertTriangle class="h-5 w-5" />
|
||||
<span class="font-medium">{{ t('messagesView.detectionSuccess') }}</span>
|
||||
</div>
|
||||
<p class="text-sm mt-2">
|
||||
{{
|
||||
t('messagesView.spiedNotificationMessage', {
|
||||
npc: selectedSpiedNotification.npcName,
|
||||
planet: selectedSpiedNotification.targetPlanetName
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 建议 -->
|
||||
<div class="p-3 bg-blue-50 dark:bg-blue-950/30 rounded-md border border-blue-200 dark:border-blue-800">
|
||||
<p class="text-sm text-blue-800 dark:text-blue-200">
|
||||
{{ t('messagesView.spiedNotificationTip') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showSpiedDialog = false">{{ t('common.close') }}</Button>
|
||||
<Button @click="viewNPCInGalaxy(selectedSpiedNotification?.npcId)">{{ t('messagesView.viewInGalaxy') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- 任务报告详情对话框 -->
|
||||
<Dialog :open="showMissionDialog" @update:open="showMissionDialog = $event">
|
||||
<DialogContent class="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<component :is="getMissionIcon(selectedMissionReport?.missionType)" class="h-5 w-5" />
|
||||
{{ t('messagesView.missionReportDetails') }}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="selectedMissionReport" class="space-y-4">
|
||||
<!-- 任务状态 -->
|
||||
<div class="p-4 bg-muted/50 rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h3 class="font-semibold text-lg">{{ getMissionTypeName(selectedMissionReport.missionType) }}</h3>
|
||||
<Badge :variant="selectedMissionReport.success ? 'default' : 'destructive'">
|
||||
{{ selectedMissionReport.success ? t('messagesView.missionSuccess') : t('messagesView.missionFailed') }}
|
||||
</Badge>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ formatDate(selectedMissionReport.timestamp) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 起点和终点 -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.origin') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<p class="font-medium">{{ selectedMissionReport.originPlanetName }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.destination') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<p class="font-medium" v-if="selectedMissionReport.targetPlanetName">{{ selectedMissionReport.targetPlanetName }}</p>
|
||||
<p class="text-sm text-muted-foreground" v-else>
|
||||
[{{ selectedMissionReport.targetPosition.galaxy }}:{{ selectedMissionReport.targetPosition.system }}:{{
|
||||
selectedMissionReport.targetPosition.position
|
||||
}}]
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 任务详情 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.missionDetails') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<p class="text-sm mb-2">{{ selectedMissionReport.message }}</p>
|
||||
|
||||
<!-- 运输任务详情 -->
|
||||
<div v-if="selectedMissionReport.details?.transportedResources" class="mt-3 space-y-1">
|
||||
<p class="text-xs font-semibold text-muted-foreground">{{ t('messagesView.transportedResources') }}:</p>
|
||||
<div class="grid grid-cols-3 gap-2 text-sm">
|
||||
<div>{{ t('resources.metal') }}: {{ selectedMissionReport.details.transportedResources.metal.toLocaleString() }}</div>
|
||||
<div>{{ t('resources.crystal') }}: {{ selectedMissionReport.details.transportedResources.crystal.toLocaleString() }}</div>
|
||||
<div>
|
||||
{{ t('resources.deuterium') }}: {{ selectedMissionReport.details.transportedResources.deuterium.toLocaleString() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 回收任务详情 -->
|
||||
<div v-if="selectedMissionReport.details?.recycledResources" class="mt-3 space-y-1">
|
||||
<p class="text-xs font-semibold text-muted-foreground">{{ t('messagesView.recycledResources') }}:</p>
|
||||
<div class="grid grid-cols-2 gap-2 text-sm">
|
||||
<div>{{ t('resources.metal') }}: {{ selectedMissionReport.details.recycledResources.metal.toLocaleString() }}</div>
|
||||
<div>{{ t('resources.crystal') }}: {{ selectedMissionReport.details.recycledResources.crystal.toLocaleString() }}</div>
|
||||
</div>
|
||||
<div v-if="selectedMissionReport.details.remainingDebris" class="mt-2">
|
||||
<p class="text-xs font-semibold text-muted-foreground">{{ t('messagesView.remainingDebris') }}:</p>
|
||||
<div class="grid grid-cols-2 gap-2 text-sm text-yellow-600 dark:text-yellow-400">
|
||||
<div>{{ t('resources.metal') }}: {{ selectedMissionReport.details.remainingDebris.metal.toLocaleString() }}</div>
|
||||
<div>{{ t('resources.crystal') }}: {{ selectedMissionReport.details.remainingDebris.crystal.toLocaleString() }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 殖民任务详情 -->
|
||||
<div v-if="selectedMissionReport.details?.newPlanetName" class="mt-3">
|
||||
<p class="text-xs font-semibold text-muted-foreground">{{ t('messagesView.newPlanet') }}:</p>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<Globe class="h-4 w-4 text-green-500" />
|
||||
<span class="font-medium">{{ selectedMissionReport.details.newPlanetName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showMissionDialog = false">{{ t('common.close') }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<!-- NPC活动通知详情对话框 -->
|
||||
<Dialog :open="showNPCActivityDialog" @update:open="showNPCActivityDialog = $event">
|
||||
<DialogContent class="max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<Recycle class="h-5 w-5 text-yellow-500" />
|
||||
{{ t('messagesView.npcActivityDetails') }}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="selectedNPCActivityNotification" class="space-y-4">
|
||||
<!-- NPC信息 -->
|
||||
<div class="p-4 bg-muted/50 rounded-lg">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<h3 class="font-semibold text-lg">{{ selectedNPCActivityNotification.npcName }}</h3>
|
||||
<Badge variant="secondary">{{ t('messagesView.activityType.' + selectedNPCActivityNotification.activityType) }}</Badge>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
{{ formatDate(selectedNPCActivityNotification.timestamp) }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 活动位置 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.activityLocation') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Globe class="h-4 w-4 text-blue-500" />
|
||||
<span class="font-medium">
|
||||
{{ t('messagesView.position') }}: [{{ selectedNPCActivityNotification.targetPosition.galaxy }}:{{
|
||||
selectedNPCActivityNotification.targetPosition.system
|
||||
}}:{{ selectedNPCActivityNotification.targetPosition.position }}]
|
||||
</span>
|
||||
</div>
|
||||
<p v-if="selectedNPCActivityNotification.targetPlanetName" class="text-sm text-muted-foreground">
|
||||
{{ t('messagesView.nearPlanet') }}: {{ selectedNPCActivityNotification.targetPlanetName }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 活动描述 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.activityDescription') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<p class="text-sm">
|
||||
{{
|
||||
t('messagesView.npcActivityMessage', {
|
||||
npc: selectedNPCActivityNotification.npcName,
|
||||
activity: t('messagesView.activityType.' + selectedNPCActivityNotification.activityType),
|
||||
position: `[${selectedNPCActivityNotification.targetPosition.galaxy}:${selectedNPCActivityNotification.targetPosition.system}:${selectedNPCActivityNotification.targetPosition.position}]`
|
||||
})
|
||||
}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 到达时间 -->
|
||||
<div class="space-y-2">
|
||||
<h4 class="font-semibold text-sm">{{ t('messagesView.arrivalTime') }}</h4>
|
||||
<div class="p-3 bg-muted/30 rounded-md">
|
||||
<p class="font-medium">{{ formatDate(selectedNPCActivityNotification.arrivalTime) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<div class="p-3 bg-yellow-50 dark:bg-yellow-950/30 rounded-md border border-yellow-200 dark:border-yellow-800">
|
||||
<p class="text-sm text-yellow-800 dark:text-yellow-200">
|
||||
{{ t('messagesView.npcActivityTip') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="showNPCActivityDialog = false">{{ t('common.close') }}</Button>
|
||||
<Button @click="viewLocationInGalaxy(selectedNPCActivityNotification?.targetPosition)">
|
||||
{{ t('messagesView.viewInGalaxy') }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -303,14 +546,16 @@
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useI18n } from '@/composables/useI18n'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
||||
import BattleReportDialog from '@/components/BattleReportDialog.vue'
|
||||
import SpyReportDialog from '@/components/SpyReportDialog.vue'
|
||||
import { formatDate } from '@/utils/format'
|
||||
import { X, Sword, Eye, AlertTriangle, Package, Recycle, Gift, Ban, Check, Users } from 'lucide-vue-next'
|
||||
import { X, Sword, Eye, AlertTriangle, Package, Recycle, Gift, Ban, Check, Users, Skull, Globe } from 'lucide-vue-next'
|
||||
import type {
|
||||
BattleResult,
|
||||
SpyReport,
|
||||
@@ -324,6 +569,7 @@
|
||||
import { useNPCStore } from '@/stores/npcStore'
|
||||
import * as diplomaticLogic from '@/logic/diplomaticLogic'
|
||||
|
||||
const router = useRouter()
|
||||
const gameStore = useGameStore()
|
||||
const npcStore = useNPCStore()
|
||||
const { t } = useI18n()
|
||||
@@ -332,8 +578,14 @@
|
||||
// 对话框状态
|
||||
const showBattleDialog = ref(false)
|
||||
const showSpyDialog = ref(false)
|
||||
const showSpiedDialog = ref(false)
|
||||
const showMissionDialog = ref(false)
|
||||
const showNPCActivityDialog = ref(false)
|
||||
const selectedBattleReport = ref<BattleResult | null>(null)
|
||||
const selectedSpyReport = ref<SpyReport | null>(null)
|
||||
const selectedSpiedNotification = ref<SpiedNotification | null>(null)
|
||||
const selectedMissionReport = ref<MissionReport | null>(null)
|
||||
const selectedNPCActivityNotification = ref<NPCActivityNotification | null>(null)
|
||||
|
||||
// 排序后的战斗报告(最新的在前)
|
||||
const sortedBattleReports = computed(() => {
|
||||
@@ -525,6 +777,9 @@
|
||||
if (!notification.read) {
|
||||
notification.read = true
|
||||
}
|
||||
// 设置选中的通知并打开详情对话框
|
||||
selectedSpiedNotification.value = notification
|
||||
showSpiedDialog.value = true
|
||||
}
|
||||
|
||||
// 删除战斗报告
|
||||
@@ -560,6 +815,9 @@
|
||||
if (!notification.read) {
|
||||
notification.read = true
|
||||
}
|
||||
// 设置选中的通知并打开详情对话框
|
||||
selectedNPCActivityNotification.value = notification
|
||||
showNPCActivityDialog.value = true
|
||||
}
|
||||
|
||||
// 删除NPC活动通知
|
||||
@@ -592,6 +850,9 @@
|
||||
if (!report.read) {
|
||||
report.read = true
|
||||
}
|
||||
// 设置选中的报告并打开详情对话框
|
||||
selectedMissionReport.value = report
|
||||
showMissionDialog.value = true
|
||||
}
|
||||
|
||||
// 删除任务报告
|
||||
@@ -656,4 +917,56 @@
|
||||
gameStore.player.giftRejectedNotifications.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// 查看NPC在星系中的位置
|
||||
const viewNPCInGalaxy = (npcId?: string) => {
|
||||
if (!npcId) return
|
||||
const npc = npcStore.npcs.find(n => n.id === npcId)
|
||||
if (!npc || npc.planets.length === 0) return
|
||||
|
||||
const targetPlanet = npc.planets[0]
|
||||
if (!targetPlanet) return
|
||||
|
||||
showSpiedDialog.value = false
|
||||
router.push({
|
||||
path: '/galaxy',
|
||||
query: {
|
||||
galaxy: targetPlanet.position.galaxy,
|
||||
system: targetPlanet.position.system,
|
||||
highlightNpc: npcId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查看位置在星系中
|
||||
const viewLocationInGalaxy = (position?: { galaxy: number; system: number; position: number }) => {
|
||||
if (!position) return
|
||||
|
||||
showNPCActivityDialog.value = false
|
||||
router.push({
|
||||
path: '/galaxy',
|
||||
query: {
|
||||
galaxy: position.galaxy,
|
||||
system: position.system
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取任务类型图标
|
||||
const getMissionIcon = (missionType?: MissionType) => {
|
||||
if (!missionType) return Package
|
||||
|
||||
switch (missionType) {
|
||||
case MissionType.Transport:
|
||||
return Package
|
||||
case MissionType.Recycle:
|
||||
return Recycle
|
||||
case MissionType.Colonize:
|
||||
return Globe
|
||||
case MissionType.Destroy:
|
||||
return Skull
|
||||
default:
|
||||
return Package
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div v-if="planet" class="container mx-auto p-4 sm:p-6">
|
||||
<!-- 未解锁遮罩 -->
|
||||
<UnlockRequirement :required-building="BuildingType.ResearchLab" :required-level="1" />
|
||||
<!-- <UnlockRequirement :required-building="BuildingType.ResearchLab" :required-level="1" /> -->
|
||||
|
||||
<h1 class="text-2xl sm:text-3xl font-bold mb-4 sm:mb-6">{{ t('researchView.title') }}</h1>
|
||||
|
||||
<div class="grid grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
|
||||
<Card v-for="techType in Object.values(TechnologyType)" :key="techType" class="relative">
|
||||
<Card v-for="techType in Object.values(TechnologyType)" :key="techType" :data-tech="techType" class="relative">
|
||||
<CardUnlockOverlay :requirements="TECHNOLOGIES[techType].requirements" :currentLevel="getTechLevel(techType)" />
|
||||
<CardHeader>
|
||||
<div class="mb-2">
|
||||
@@ -98,7 +98,6 @@
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle
|
||||
} from '@/components/ui/alert-dialog'
|
||||
import UnlockRequirement from '@/components/UnlockRequirement.vue'
|
||||
import CardUnlockOverlay from '@/components/CardUnlockOverlay.vue'
|
||||
import { formatNumber, getResourceCostColor } from '@/utils/format'
|
||||
import * as publicLogic from '@/logic/publicLogic'
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div
|
||||
class="h-full transition-all duration-300"
|
||||
:class="fleetStorageUsage > maxFleetStorage ? 'bg-destructive' : 'bg-primary'"
|
||||
:style="{ width: `${Math.min((fleetStorageUsage / maxFleetStorage) * 100, 100)}%` }"
|
||||
:style="{ width: `${maxFleetStorage > 0 ? Math.min((fleetStorageUsage / maxFleetStorage) * 100, 100) : 0}%` }"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -216,13 +216,13 @@
|
||||
[ShipType.Deathstar]: 0
|
||||
})
|
||||
|
||||
const buildShip = (shipType: ShipType, quantity: number): boolean => {
|
||||
if (!gameStore.currentPlanet) return false
|
||||
const buildShip = (shipType: ShipType, quantity: number): { success: boolean; reason?: string } => {
|
||||
if (!gameStore.currentPlanet) return { success: false }
|
||||
const validation = shipValidation.validateShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.technologies)
|
||||
if (!validation.valid) return false
|
||||
if (!validation.valid) return { success: false, reason: validation.reason }
|
||||
const queueItem = shipValidation.executeShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.officers)
|
||||
gameStore.currentPlanet.buildQueue.push(queueItem)
|
||||
return true
|
||||
return { success: true }
|
||||
}
|
||||
|
||||
// 建造舰船
|
||||
@@ -235,10 +235,10 @@
|
||||
return
|
||||
}
|
||||
|
||||
const success = buildShip(shipType, quantity)
|
||||
if (!success) {
|
||||
const result = buildShip(shipType, quantity)
|
||||
if (!result.success) {
|
||||
alertDialogTitle.value = t('shipyardView.buildFailed')
|
||||
alertDialogMessage.value = t('shipyardView.buildFailedMessage')
|
||||
alertDialogMessage.value = result.reason ? t(result.reason) : t('shipyardView.buildFailedMessage')
|
||||
alertDialogOpen.value = true
|
||||
} else {
|
||||
quantities.value[shipType] = 0
|
||||
@@ -260,6 +260,11 @@
|
||||
darkMatter: config.cost.darkMatter * quantity
|
||||
}
|
||||
|
||||
// 检查舰队仓储空间是否足够
|
||||
if (!fleetStorageLogic.hasEnoughFleetStorage(planet.value, shipType, quantity, gameStore.player.technologies)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return (
|
||||
publicLogic.checkRequirements(planet.value, gameStore.player.technologies, config.requirements) &&
|
||||
planet.value.resources.metal >= totalCost.metal &&
|
||||
|
||||
Reference in New Issue
Block a user