feat: 新增Android平台支持及构建流程

集成Android平台相关目录与配置文件,包含Gradle构建脚本、资源文件、启动图标、Java入口、Proguard规则等,完善.gitignore以排除Android构建产物。更新CI流程,支持自动构建并发布Android APK。移除README中项目结构说明,简化文档。
This commit is contained in:
谦君
2025-12-20 00:48:36 +08:00
parent 20fb2bb6a4
commit 1368bb4445
97 changed files with 7859 additions and 335 deletions

View File

@@ -117,7 +117,8 @@
:data-nav-path="item.path"
:is-active="$route.path === item.path"
:tooltip="item.name.value"
@click="handleNavClick(item.path, $event)"
:disabled="!isFeatureUnlocked(item.path)"
@click="router.push(item.path)"
>
<component :is="item.icon" />
<span>{{ item.name.value }}</span>
@@ -259,7 +260,7 @@
</div>
</div>
<!-- 右侧展开按钮仅移动端 -->
<!-- 右侧队列通知 + 展开按钮 -->
<div class="flex items-center gap-2 sm:gap-3 flex-shrink-0 justify-end">
<!-- 移动端展开按钮 -->
<Button @click="resourceBarExpanded = !resourceBarExpanded" variant="ghost" size="sm" class="lg:hidden h-8 w-8 p-0">
@@ -330,6 +331,9 @@
<!-- 即将到来的敌对舰队警告 -->
<IncomingFleetAlerts @open-panel="openEnemyAlertPanel" />
<!-- 低电量警告 -->
<LowEnergyWarning />
<!-- 内容区域 -->
<main class="flex-1">
<Transition name="page" mode="out-in">
@@ -365,7 +369,6 @@
<div class="fixed bottom-4 right-4 z-50 flex flex-col gap-2">
<!-- 返回顶部 -->
<BackToTop />
<!-- 队列通知 -->
<QueueNotifications />
@@ -443,6 +446,7 @@
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
import { Input } from '@/components/ui/input'
import IncomingFleetAlerts from '@/components/IncomingFleetAlerts.vue'
import LowEnergyWarning from '@/components/LowEnergyWarning.vue'
import DiplomaticNotifications from '@/components/DiplomaticNotifications.vue'
import EnemyAlertNotifications from '@/components/EnemyAlertNotifications.vue'
import QueueNotifications from '@/components/QueueNotifications.vue'
@@ -503,7 +507,8 @@
ChevronDown,
ChevronUp,
Handshake,
Pencil
Pencil,
Trophy
} from 'lucide-vue-next'
import * as gameLogic from '@/logic/gameLogic'
import * as planetLogic from '@/logic/planetLogic'
@@ -570,7 +575,8 @@
'/research': { building: BuildingType.ResearchLab, level: 1 },
'/shipyard': { building: BuildingType.Shipyard, level: 1 },
'/defense': { building: BuildingType.Shipyard, level: 1 },
'/fleet': { building: BuildingType.Shipyard, level: 1 }
'/fleet': { building: BuildingType.Shipyard, level: 1 },
'/officers': { building: BuildingType.Shipyard, level: 1 }
}
// 判断是否为首页
@@ -599,6 +605,7 @@
{ name: computed(() => t('nav.simulator')), path: '/battle-simulator', icon: Swords },
{ name: computed(() => t('nav.galaxy')), path: '/galaxy', icon: Globe },
{ name: computed(() => t('nav.diplomacy')), path: '/diplomacy', icon: Handshake },
{ name: computed(() => t('nav.achievements')), path: '/achievements', icon: Trophy },
{ name: computed(() => t('nav.messages')), path: '/messages', icon: Mail },
{ name: computed(() => t('nav.settings')), path: '/settings', icon: Settings },
// GM菜单在启用GM模式时显示
@@ -809,6 +816,9 @@
// NPC行为系统更新侦查和攻击决策
updateNPCBehavior(1)
// 检查成就解锁
checkAchievementUnlocks()
// 检查并处理被消灭的NPC所有星球都被摧毁的NPC
const eliminatedNpcIds = diplomaticLogic.checkAndHandleEliminatedNPCs(npcStore.npcs, gameStore.player, gameStore.locale)
if (eliminatedNpcIds.length > 0) {
@@ -839,13 +849,28 @@
mission.targetPosition.position
)
// 先从玩家星球中查找,再从宇宙地图中查找
// 如果任务指定了targetIsMoon需要精确匹配行星或月球
const targetPlanet =
gameStore.player.planets.find(p => {
const positionMatch =
p.position.galaxy === mission.targetPosition.galaxy &&
p.position.system === mission.targetPosition.system &&
p.position.position === mission.targetPosition.position
// 如果任务明确指定目标类型,按类型匹配
if (mission.targetIsMoon !== undefined) {
return positionMatch && p.isMoon === mission.targetIsMoon
}
// 兼容旧任务:默认优先匹配行星(非月球)
return positionMatch && !p.isMoon
}) ||
// 如果没有匹配到指定类型,尝试匹配同位置的任何星球
gameStore.player.planets.find(
p =>
p.position.galaxy === mission.targetPosition.galaxy &&
p.position.system === mission.targetPosition.system &&
p.position.position === mission.targetPosition.position
) || universeStore.planets[targetKey]
) ||
universeStore.planets[targetKey]
// 获取起始星球名称(用于报告)
const originPlanet = gameStore.player.planets.find(p => p.id === mission.originPlanetId)
@@ -854,7 +879,32 @@
if (mission.missionType === MissionType.Transport) {
// 在处理任务之前保存货物信息因为processTransportArrival会清空cargo
const transportedResources = { ...mission.cargo }
const isGiftMission = mission.isGift && mission.giftTargetNpcId
const result = fleetLogic.processTransportArrival(mission, targetPlanet, gameStore.player, npcStore.npcs)
// 更新成就统计(仅在成功时追踪)
if (result.success) {
const totalTransported =
transportedResources.metal + transportedResources.crystal + transportedResources.deuterium + transportedResources.darkMatter
if (isGiftMission) {
// 送礼成功
gameLogic.trackDiplomacyStats(gameStore.player, 'gift', { resourcesAmount: totalTransported })
} else {
// 普通运输任务成功
gameLogic.trackMissionStats(gameStore.player, 'transport', { resourcesAmount: totalTransported })
}
}
// 生成失败原因消息
let transportFailMessage = t('missionReports.transportFailed')
if (!result.success && result.failReason) {
if (result.failReason === 'targetNotFound') {
transportFailMessage = t('missionReports.transportFailedTargetNotFound')
} else if (result.failReason === 'giftRejected') {
transportFailMessage = t('missionReports.transportFailedGiftRejected')
}
}
// 生成运输任务报告
if (!gameStore.player.missionReports) {
gameStore.player.missionReports = []
@@ -870,9 +920,10 @@
targetPlanetName:
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
success: result.success,
message: result.success ? t('missionReports.transportSuccess') : t('missionReports.transportFailed'),
message: result.success ? t('missionReports.transportSuccess') : transportFailMessage,
details: {
transportedResources
transportedResources,
failReason: result.failReason
},
read: false
})
@@ -881,6 +932,13 @@
if (attackResult) {
gameStore.player.battleReports.push(attackResult.battleResult)
// 更新成就统计 - 攻击
const debrisValue = attackResult.debrisField
? attackResult.debrisField.resources.metal + attackResult.debrisField.resources.crystal
: 0
const won = attackResult.battleResult.winner === 'attacker'
gameLogic.trackAttackStats(gameStore.player, attackResult.battleResult, won, debrisValue)
// 检查是否攻击了NPC星球更新外交关系
if (targetPlanet) {
const targetNpc = npcStore.npcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
@@ -898,7 +956,24 @@
}
}
} else if (mission.missionType === MissionType.Colonize) {
const newPlanet = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player, t('planet.colonyPrefix'))
const colonizeResult = fleetLogic.processColonizeArrival(mission, targetPlanet, gameStore.player, t('planet.colonyPrefix'))
const newPlanet = colonizeResult.planet
// 更新成就统计 - 殖民
if (colonizeResult.success && newPlanet) {
gameLogic.trackMissionStats(gameStore.player, 'colonize')
}
// 生成失败原因消息
let failMessage = t('missionReports.colonizeFailed')
if (!colonizeResult.success && colonizeResult.failReason) {
if (colonizeResult.failReason === 'positionOccupied') {
failMessage = t('missionReports.colonizeFailedOccupied')
} else if (colonizeResult.failReason === 'maxColoniesReached') {
failMessage = t('missionReports.colonizeFailedMaxColonies')
}
}
// 生成殖民任务报告
if (!gameStore.player.missionReports) {
gameStore.player.missionReports = []
@@ -912,24 +987,72 @@
targetPosition: mission.targetPosition,
targetPlanetId: newPlanet?.id,
targetPlanetName: newPlanet?.name,
success: !!newPlanet,
message: newPlanet ? t('missionReports.colonizeSuccess') : t('missionReports.colonizeFailed'),
success: colonizeResult.success,
message: colonizeResult.success ? t('missionReports.colonizeSuccess') : failMessage,
details: newPlanet
? {
newPlanetId: newPlanet.id,
newPlanetName: newPlanet.name
}
: undefined,
: { failReason: colonizeResult.failReason },
read: false
})
if (newPlanet) {
gameStore.player.planets.push(newPlanet)
}
} else if (mission.missionType === MissionType.Spy) {
const spyReport = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player, null, npcStore.npcs)
if (spyReport) gameStore.player.spyReports.push(spyReport)
const spyResult = fleetLogic.processSpyArrival(mission, targetPlanet, gameStore.player, null, npcStore.npcs)
if (spyResult.success && spyResult.report) {
gameStore.player.spyReports.push(spyResult.report)
// 更新成就统计 - 侦查
gameLogic.trackMissionStats(gameStore.player, 'spy')
}
// 生成侦查任务报告(即使失败也生成)
if (!gameStore.player.missionReports) {
gameStore.player.missionReports = []
}
let spyFailMessage = t('missionReports.spyFailed')
if (!spyResult.success && spyResult.failReason) {
if (spyResult.failReason === 'targetNotFound') {
spyFailMessage = t('missionReports.spyFailedTargetNotFound')
}
}
gameStore.player.missionReports.push({
id: `mission-report-${mission.id}`,
timestamp: Date.now(),
missionType: MissionType.Spy,
originPlanetId: mission.originPlanetId,
originPlanetName,
targetPosition: mission.targetPosition,
targetPlanetId: targetPlanet?.id,
targetPlanetName:
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
success: spyResult.success,
message: spyResult.success ? t('missionReports.spySuccess') : spyFailMessage,
details: spyResult.success ? { spyReportId: spyResult.report?.id } : { failReason: spyResult.failReason },
read: false
})
} else if (mission.missionType === MissionType.Deploy) {
const deployed = fleetLogic.processDeployArrival(mission, targetPlanet, gameStore.player.id, gameStore.player.technologies)
// 更新成就统计 - 部署
if (deployed.success) {
gameLogic.trackMissionStats(gameStore.player, 'deploy')
}
// 生成失败原因消息
let deployFailMessage = t('missionReports.deployFailed')
if (!deployed.success && deployed.failReason) {
if (deployed.failReason === 'targetNotFound') {
deployFailMessage = t('missionReports.deployFailedTargetNotFound')
} else if (deployed.failReason === 'notOwnPlanet') {
deployFailMessage = t('missionReports.deployFailedNotOwnPlanet')
}
}
// 生成部署任务报告
if (!gameStore.player.missionReports) {
gameStore.player.missionReports = []
@@ -945,9 +1068,10 @@
targetPlanetName:
targetPlanet?.name || `[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`,
success: deployed.success,
message: deployed.success ? t('missionReports.deploySuccess') : t('missionReports.deployFailed'),
message: deployed.success ? t('missionReports.deploySuccess') : deployFailMessage,
details: {
deployedFleet: mission.fleet
deployedFleet: mission.fleet,
failReason: deployed.failReason
},
read: false
})
@@ -962,6 +1086,23 @@
const debrisField = universeStore.debrisFields[debrisId]
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
// 更新成就统计 - 回收(无论是否有残骸都算飞行任务,但只有成功回收才计入回收资源量)
const totalRecycled =
recycleResult.success && recycleResult.collectedResources
? recycleResult.collectedResources.metal + recycleResult.collectedResources.crystal
: 0
gameLogic.trackMissionStats(gameStore.player, 'recycle', { resourcesAmount: totalRecycled })
// 生成失败原因消息
let recycleFailMessage = t('missionReports.recycleFailed')
if (!recycleResult.success && recycleResult.failReason) {
if (recycleResult.failReason === 'noDebrisField') {
recycleFailMessage = t('missionReports.recycleFailedNoDebris')
} else if (recycleResult.failReason === 'debrisEmpty') {
recycleFailMessage = t('missionReports.recycleFailedDebrisEmpty')
}
}
// 生成回收任务报告
if (!gameStore.player.missionReports) {
gameStore.player.missionReports = []
@@ -973,18 +1114,18 @@
originPlanetId: mission.originPlanetId,
originPlanetName,
targetPosition: mission.targetPosition,
success: !!recycleResult,
message: recycleResult ? t('missionReports.recycleSuccess') : t('missionReports.recycleFailed'),
details: recycleResult
success: recycleResult.success,
message: recycleResult.success ? t('missionReports.recycleSuccess') : recycleFailMessage,
details: recycleResult.success
? {
recycledResources: recycleResult.collectedResources,
remainingDebris: recycleResult.remainingDebris || undefined
}
: undefined,
: { failReason: recycleResult.failReason },
read: false
})
if (recycleResult && debrisField) {
if (recycleResult.success && recycleResult.collectedResources && debrisField) {
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
// 更新残骸场
universeStore.debrisFields[debrisId] = {
@@ -1003,6 +1144,25 @@
// 处理行星毁灭任务
const destroyResult = fleetLogic.processDestroyArrival(mission, targetPlanet, gameStore.player)
// 更新成就统计 - 行星毁灭
if (destroyResult.success) {
gameLogic.trackMissionStats(gameStore.player, 'destroy')
}
// 生成失败原因消息
let destroyFailMessage = t('missionReports.destroyFailed')
if (!destroyResult.success && destroyResult.failReason) {
if (destroyResult.failReason === 'targetNotFound') {
destroyFailMessage = t('missionReports.destroyFailedTargetNotFound')
} else if (destroyResult.failReason === 'ownPlanet') {
destroyFailMessage = t('missionReports.destroyFailedOwnPlanet')
} else if (destroyResult.failReason === 'noDeathstar') {
destroyFailMessage = t('missionReports.destroyFailedNoDeathstar')
} else if (destroyResult.failReason === 'chanceFailed') {
destroyFailMessage = t('missionReports.destroyFailedChance', { chance: destroyResult.destructionChance.toFixed(1) })
}
}
// 生成毁灭任务报告
if (!gameStore.player.missionReports) {
gameStore.player.missionReports = []
@@ -1016,19 +1176,23 @@
targetPosition: mission.targetPosition,
targetPlanetId: targetPlanet?.id,
targetPlanetName: targetPlanet?.name,
success: destroyResult?.success || false,
message: destroyResult?.success ? t('missionReports.destroySuccess') : t('missionReports.destroyFailed'),
details: destroyResult?.success
success: destroyResult.success,
message: destroyResult.success ? t('missionReports.destroySuccess') : destroyFailMessage,
details: destroyResult.success
? {
destroyedPlanetName:
targetPlanet?.name ||
`[${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}]`
}
: undefined,
: {
failReason: destroyResult.failReason,
destructionChance: destroyResult.destructionChance,
deathstarsLost: destroyResult.deathstarsLost
},
read: false
})
if (destroyResult && destroyResult.success && destroyResult.planetId) {
if (destroyResult.success && destroyResult.planetId) {
// 星球被摧毁
// 处理外交关系如果目标是NPC星球
@@ -1036,6 +1200,20 @@
const planetOwner = npcStore.npcs.find(npc => npc.id === targetPlanet.ownerId)
if (planetOwner) {
diplomaticLogic.handlePlanetDestructionReputation(gameStore.player, targetPlanet, planetOwner, npcStore.npcs, gameStore.locale)
// 从NPC的星球列表中移除被摧毁的星球
const npcPlanetIndex = planetOwner.planets.findIndex(p => p.id === destroyResult.planetId)
if (npcPlanetIndex > -1) {
planetOwner.planets.splice(npcPlanetIndex, 1)
}
// 检查并处理被消灭的NPC所有星球都被摧毁的NPC
const eliminatedNpcIds = diplomaticLogic.checkAndHandleEliminatedNPCs(npcStore.npcs, gameStore.player, gameStore.locale)
// 从npcStore中移除被消灭的NPC
if (eliminatedNpcIds.length > 0) {
npcStore.npcs = npcStore.npcs.filter(npc => !eliminatedNpcIds.includes(npc.id))
}
}
}
@@ -1052,6 +1230,11 @@
// 处理远征任务
const expeditionResult = fleetLogic.processExpeditionArrival(mission)
// 更新成就统计 - 远征
const isSuccessful =
expeditionResult.eventType === 'resources' || expeditionResult.eventType === 'darkMatter' || expeditionResult.eventType === 'fleet'
gameLogic.trackMissionStats(gameStore.player, 'expedition', { successful: isSuccessful })
// 生成远征任务报告
if (!gameStore.player.missionReports) {
gameStore.player.missionReports = []
@@ -1129,7 +1312,13 @@
const debrisField = universeStore.debrisFields[debrisId]
const recycleResult = fleetLogic.processRecycleArrival(mission, debrisField)
if (recycleResult && debrisField) {
if (recycleResult && debrisField && recycleResult.collectedResources) {
// 更新成就统计 - 被NPC回收残骸如果残骸是玩家战斗产生的
const totalRecycled = recycleResult.collectedResources.metal + recycleResult.collectedResources.crystal
if (totalRecycled > 0) {
gameLogic.trackDiplomacyStats(gameStore.player, 'debrisRecycledByNPC', { resourcesAmount: totalRecycled })
}
if (recycleResult.remainingDebris && (recycleResult.remainingDebris.metal > 0 || recycleResult.remainingDebris.crystal > 0)) {
// 更新残骸场
universeStore.debrisFields[debrisId] = {
@@ -1175,6 +1364,9 @@
// NPC侦查到达
const { spiedNotification, spyReport } = npcBehaviorLogic.processNPCSpyArrival(npc, mission, targetPlanet, gameStore.player)
// 更新成就统计 - 被NPC侦查
gameLogic.trackDiplomacyStats(gameStore.player, 'spiedByNPC')
// 保存侦查报告到NPC用于后续攻击决策
if (!npc.playerSpyReports) {
npc.playerSpyReports = {}
@@ -1193,6 +1385,14 @@
// NPC攻击到达 - 使用专门的NPC攻击处理逻辑
fleetLogic.processNPCAttackArrival(npc, mission, targetPlanet, gameStore.player, gameStore.player.planets).then(attackResult => {
if (attackResult) {
// 更新成就统计 - 被NPC攻击 + 防御统计
gameLogic.trackDiplomacyStats(gameStore.player, 'attackedByNPC')
const debrisValue = attackResult.debrisField
? attackResult.debrisField.resources.metal + attackResult.debrisField.resources.crystal
: 0
const won = attackResult.battleResult.winner === 'defender'
gameLogic.trackDefenseStats(gameStore.player, attackResult.battleResult, won, debrisValue)
// 添加战斗报告给玩家
gameStore.player.battleReports.push(attackResult.battleResult)
@@ -1372,6 +1572,24 @@
}
}
/**
* 同步NPC星球数据到universeStore
* 解决npcStore和universeStore数据不同步的问题
*/
const syncNPCPlanetToUniverse = (npc: any) => {
npc.planets.forEach((npcPlanet: any) => {
const planetKey = gameLogic.generatePositionKey(npcPlanet.position.galaxy, npcPlanet.position.system, npcPlanet.position.position)
const universePlanet = universeStore.planets[planetKey]
if (universePlanet) {
// 同步所有关键数据
universePlanet.resources = { ...npcPlanet.resources }
universePlanet.buildings = { ...npcPlanet.buildings }
universePlanet.fleet = { ...npcPlanet.fleet }
universePlanet.defense = { ...npcPlanet.defense }
}
})
}
const updateNPCGrowth = (deltaSeconds: number) => {
// 累积时间
npcUpdateCounter.value += deltaSeconds
@@ -1432,20 +1650,20 @@
// 保存到store
npcStore.npcs = Array.from(npcMap.values())
// 如果有NPC基于玩家实力初始化NPC
// 如果有NPC基于距离初始化NPC实力
if (npcStore.npcs.length > 0) {
const gameState: npcGrowthLogic.NPCGrowthGameState = {
planets: allPlanets,
player: gameStore.player,
npcs: npcStore.npcs
// 获取玩家母星(第一个非月球星球)
const homeworld = gameStore.player.planets.find(p => !p.isMoon)
if (homeworld) {
npcStore.npcs.forEach(npc => {
// 基于距离初始化NPC实力
npcGrowthLogic.initializeNPCByDistance(npc, homeworld.position)
// 同步NPC星球数据到universeStore
syncNPCPlanetToUniverse(npc)
})
}
const playerPower = npcGrowthLogic.calculatePlayerAveragePower(gameState)
npcStore.npcs.forEach(npc => {
npcGrowthLogic.initializeNPCStartingPower(npc, playerPower)
})
// 初始化NPC之间的外交关系盟友/敌人)
npcGrowthLogic.initializeNPCDiplomacy(npcStore.npcs)
}
@@ -1459,6 +1677,9 @@
// 确保所有NPC都与玩家建立了关系修复旧版本保存的数据
if (npcStore.npcs.length > 0) {
const now = Date.now()
// 获取玩家母星(用于计算距离)
const homeworld = gameStore.player.planets.find(p => !p.isMoon)
npcStore.npcs.forEach(npc => {
if (!npc.relations) {
npc.relations = {}
@@ -1474,6 +1695,19 @@
history: []
}
}
// 迁移旧存档如果NPC没有距离数据计算并设置
if (homeworld && npc.distanceToHomeworld === undefined) {
const npcPlanet = npc.planets[0]
if (npcPlanet) {
npc.distanceToHomeworld = npcGrowthLogic.calculateDistanceToHomeworld(npcPlanet.position, homeworld.position)
npc.difficultyLevel = npcGrowthLogic.calculateDifficultyLevel(npc.distanceToHomeworld)
// 重新初始化NPC实力以匹配新的距离难度系统
npcGrowthLogic.initializeNPCByDistance(npc, homeworld.position)
// 同步NPC星球数据到universeStore
syncNPCPlanetToUniverse(npc)
}
}
})
}
@@ -1483,16 +1717,16 @@
return
}
// 构建游戏状态
const gameState: npcGrowthLogic.NPCGrowthGameState = {
planets: allPlanets,
player: gameStore.player,
npcs: npcStore.npcs
}
// 获取玩家母星用于距离计算
const homeworldForGrowth = gameStore.player.planets.find(p => !p.isMoon)
// 使用累积的时间更新每个NPC应用游戏速度倍率
// 使用累积的时间更新每个NPC基于距离的成长系统
npcStore.npcs.forEach(npc => {
npcGrowthLogic.updateNPCGrowth(npc, gameState, npcUpdateCounter.value, gameStore.gameSpeed)
if (homeworldForGrowth) {
npcGrowthLogic.updateNPCGrowthByDistance(npc, homeworldForGrowth.position, npcUpdateCounter.value, gameStore.gameSpeed)
// 同步NPC星球数据到universeStore确保侦查报告显示正确数据
syncNPCPlanetToUniverse(npc)
}
})
// 重置计数器
@@ -1566,6 +1800,56 @@
npcBehaviorCounter.value = 0
}
// 更新NPC关系统计友好/敌对数量)
const updateNPCRelationStats = () => {
let friendlyCount = 0
let hostileCount = 0
const playerId = gameStore.player.id
npcStore.npcs.forEach(npc => {
const relation = npc.relations?.[playerId]
if (relation) {
const status = diplomaticLogic.calculateRelationStatus(relation.reputation)
if (status === 'friendly') {
friendlyCount++
} else if (status === 'hostile') {
hostileCount++
}
}
})
gameLogic.trackDiplomacyStats(gameStore.player, 'updateRelations', { friendlyCount, hostileCount })
}
// 检查成就解锁
const achievementCheckCounter = ref(0)
const ACHIEVEMENT_CHECK_INTERVAL = 5 // 每5秒检查一次成就
const checkAchievementUnlocks = () => {
achievementCheckCounter.value += 1
// 只在达到更新间隔时才执行
if (achievementCheckCounter.value < ACHIEVEMENT_CHECK_INTERVAL) {
return
}
// 更新NPC关系统计
updateNPCRelationStats()
// 检查并解锁成就
const unlocks = gameLogic.checkAndUnlockAchievements(gameStore.player)
// 显示成就解锁通知(奖励已在 checkAndUnlockAchievements 中应用)
unlocks.forEach(unlock => {
// 显示 toast 通知
const tierName = t(`achievements.tiers.${unlock.tier}`)
const achievementName = t(`achievements.names.${unlock.id}`)
toast.success(t('achievements.unlocked'), {
description: `${achievementName} (${tierName})`
})
})
achievementCheckCounter.value = 0
}
// 启动游戏循环
const startGameLoop = () => {
if (gameStore.isPaused) return
@@ -1665,45 +1949,13 @@
}
// 检查功能是否解锁
const checkFeatureUnlocked = (path: string): { unlocked: boolean; requirement?: { building: BuildingType; level: number } } => {
const isFeatureUnlocked = (path: string): boolean => {
const requirement = featureRequirements[path]
if (!requirement) {
return { unlocked: true }
return 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)
return currentLevel >= requirement.level
}
// 切换到月球

View File

@@ -157,7 +157,7 @@
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog'
import { ScrollArea } from '@/components/ui/scroll-area'
import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty'
import { Siren, Eye, Sword, Shield, Globe } from 'lucide-vue-next'
import { Siren, Eye, Sword, Shield, Globe, Recycle } from 'lucide-vue-next'
import { MissionType } from '@/types/game'
import type { IncomingFleetAlert } from '@/types/game'
import { formatDate, formatTime } from '@/utils/format'
@@ -200,6 +200,8 @@
return Eye
case MissionType.Attack:
return Sword
case MissionType.Recycle:
return Recycle
default:
return Siren
}
@@ -212,6 +214,8 @@
return 'text-purple-500'
case MissionType.Attack:
return 'text-red-500'
case MissionType.Recycle:
return 'text-amber-500'
default:
return 'text-yellow-500'
}
@@ -229,6 +233,8 @@
return t('enemyAlert.missionType.spy')
case MissionType.Attack:
return t('enemyAlert.missionType.attack')
case MissionType.Recycle:
return t('enemyAlert.missionType.recycle')
default:
return t('enemyAlert.missionType.unknown')
}
@@ -241,6 +247,8 @@
return t('enemyAlert.warning.spy')
case MissionType.Attack:
return t('enemyAlert.warning.attack')
case MissionType.Recycle:
return t('enemyAlert.warning.recycle')
default:
return t('enemyAlert.warning.unknown')
}

View File

@@ -0,0 +1,72 @@
<template>
<div v-if="showWarning" class="bg-destructive/10 border-b border-destructive/20">
<div class="px-4 sm:px-6 py-2 flex items-center justify-between gap-3">
<!-- 警告图标和信息 -->
<div class="flex items-center gap-2 flex-1 min-w-0">
<Zap class="h-5 w-5 text-destructive flex-shrink-0 animate-pulse" />
<div class="flex-1 min-w-0">
<p class="text-sm font-semibold text-destructive">
{{ t('energy.lowWarning') }}
</p>
<p class="text-xs text-muted-foreground">
{{ detailMessage }}
</p>
</div>
</div>
<!-- 建造电站按钮 -->
<Button @click="goToBuildSolarPlant" variant="outline" size="sm" class="flex-shrink-0">
{{ t('energy.buildSolarPlant') }}
</Button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useGameStore } from '@/stores/gameStore'
import { Button } from '@/components/ui/button'
import { Zap } from 'lucide-vue-next'
import { useI18n } from '@/composables/useI18n'
import * as resourceLogic from '@/logic/resourceLogic'
import * as officerLogic from '@/logic/officerLogic'
const gameStore = useGameStore()
const router = useRouter()
const { t } = useI18n()
// 获取当前星球
const planet = computed(() => gameStore.currentPlanet)
// 计算能量产量
const energyProduction = computed(() => {
if (!planet.value) return 0
const now = Date.now()
const bonuses = officerLogic.calculateActiveBonuses(gameStore.player.officers, now)
return resourceLogic.calculateEnergyProduction(planet.value, { energyProductionBonus: bonuses.energyProductionBonus })
})
// 计算能量消耗
const energyConsumption = computed(() => {
if (!planet.value) return 0
return resourceLogic.calculateEnergyConsumption(planet.value)
})
// 是否显示警告(电力产量 < 消耗)
const showWarning = computed(() => {
if (!planet.value) return false
return energyProduction.value < energyConsumption.value
})
// 详细消息
const detailMessage = computed(() => {
const deficit = Math.ceil(energyConsumption.value - energyProduction.value)
return t('energy.deficitDetail', { deficit: deficit.toString() })
})
// 跳转到建筑页面建造太阳能电站
const goToBuildSolarPlant = () => {
router.push('/buildings')
}
</script>

View File

@@ -4,12 +4,20 @@
<CardHeader>
<div class="flex items-start justify-between">
<div class="flex-1">
<CardTitle class="flex items-center gap-2">
<CardTitle class="flex items-center gap-2 flex-wrap">
{{ npc.name }}
<span v-if="npc.note" class="text-muted-foreground font-normal">({{ npc.note }})</span>
<Badge :variant="statusBadgeVariant">
{{ statusText }}
</Badge>
<!-- NPC难度等级徽章 -->
<Badge
v-if="npc.difficultyLevel"
:variant="difficultyBadgeVariant"
:class="difficultyLevelColor"
>
Lv.{{ npc.difficultyLevel }}
</Badge>
</CardTitle>
<CardDescription class="mt-1">
{{ npc.planets.length }} {{ t('diplomacy.planets') }}
@@ -208,6 +216,28 @@
return 'text-muted-foreground'
})
// NPC难度等级颜色
const difficultyLevelColor = computed(() => {
const level = props.npc.difficultyLevel
if (!level) return 'text-muted-foreground'
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
})
// NPC难度等级Badge样式
const difficultyBadgeVariant = computed((): 'default' | 'secondary' | 'destructive' | 'outline' => {
const level = props.npc.difficultyLevel
if (!level) return 'outline'
if (level <= 2) return 'secondary'
if (level <= 4) return 'default'
return 'destructive'
})
// 最近的外交事件
const recentEvent = computed(() => {
if (!props.relation?.history || props.relation.history.length === 0) return null

View File

@@ -18,6 +18,15 @@
<div class="flex items-center gap-2">
<span class="font-medium truncate">{{ npc.name }}</span>
<span v-if="npc.note" class="text-muted-foreground text-sm truncate">({{ npc.note }})</span>
<!-- NPC难度等级徽章 -->
<Badge
v-if="npc.difficultyLevel"
:variant="difficultyBadgeVariant"
class="text-xs"
:class="difficultyLevelColor"
>
Lv.{{ npc.difficultyLevel }}
</Badge>
</div>
<div class="text-xs text-muted-foreground">
{{ npc.planets.length }} {{ t('diplomacy.planets') }}
@@ -68,9 +77,18 @@
'bg-gray-400': status === RelationStatus.Neutral
}"
/>
<div class="flex-1 min-w-0">
<div class="flex-1 min-w-0 flex items-center gap-1 flex-wrap">
<span class="font-medium truncate">{{ npc.name }}</span>
<span v-if="npc.note" class="text-muted-foreground text-sm ml-1">({{ npc.note }})</span>
<span v-if="npc.note" class="text-muted-foreground text-sm">({{ npc.note }})</span>
<!-- NPC难度等级徽章 (移动端) -->
<Badge
v-if="npc.difficultyLevel"
:variant="difficultyBadgeVariant"
class="text-xs"
:class="difficultyLevelColor"
>
Lv.{{ npc.difficultyLevel }}
</Badge>
</div>
<ChevronDown class="h-4 w-4 text-muted-foreground transition-transform flex-shrink-0" :class="{ 'rotate-180': isExpanded }" />
</div>
@@ -214,6 +232,28 @@
return 'text-muted-foreground'
})
// NPC难度等级颜色
const difficultyLevelColor = computed(() => {
const level = props.npc.difficultyLevel
if (!level) return 'text-muted-foreground'
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
})
// NPC难度等级Badge样式
const difficultyBadgeVariant = computed((): 'default' | 'secondary' | 'destructive' | 'outline' => {
const level = props.npc.difficultyLevel
if (!level) return 'outline'
if (level <= 2) return 'secondary'
if (level <= 4) return 'default'
return 'destructive'
})
// 最近的外交事件
const recentEvent = computed(() => {
if (!props.relation?.history || props.relation.history.length === 0) return null

View File

@@ -173,7 +173,8 @@
const getQueueProgress = (item: BuildQueueItem): number => {
const elapsed = currentTime.value - item.startTime
const total = item.endTime - item.startTime
return Math.min(100, (elapsed / total) * 100)
if (total <= 0) return 100
return Math.max(0, Math.min(100, (elapsed / total) * 100))
}
// 统一的取消处理

View File

@@ -106,6 +106,14 @@ const hints: Hint[] = [
icon: 'swords',
delay: 500
},
{
id: 'achievements_intro',
route: '/achievements',
titleKey: 'hints.achievements.title',
messageKey: 'hints.achievements.message',
icon: 'trophy',
delay: 500
},
{
id: 'settings_intro',
route: '/settings',

View File

@@ -0,0 +1,555 @@
import { AchievementCategory, AchievementTier, type AchievementConfig } from '@/types/game'
// 成就配置
// 每个成就有5个等级青铜、白银、黄金、铂金、钻石
// 每个等级有对应的目标值和奖励(暗物质 + 积分)
export const ACHIEVEMENTS: AchievementConfig[] = [
// ==================== 资源类成就 ====================
{
id: 'metalCollector',
category: AchievementCategory.Resource,
icon: 'Gem',
statKey: 'totalMetalProduced',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'crystalCollector',
category: AchievementCategory.Resource,
icon: 'Diamond',
statKey: 'totalCrystalProduced',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5000, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 50000, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 500000, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 5000000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 50000000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'deuteriumCollector',
category: AchievementCategory.Resource,
icon: 'Droplet',
statKey: 'totalDeuteriumProduced',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 2500, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 25000, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 250000, reward: { darkMatter: 300, points: 3000 } },
{ tier: AchievementTier.Platinum, target: 2500000, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 25000000, reward: { darkMatter: 7500, points: 75000 } }
]
},
{
id: 'darkMatterCollector',
category: AchievementCategory.Resource,
icon: 'Sparkles',
statKey: 'totalDarkMatterProduced',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 100, reward: { darkMatter: 20, points: 200 } },
{ tier: AchievementTier.Silver, target: 1000, reward: { darkMatter: 100, points: 1000 } },
{ tier: AchievementTier.Gold, target: 10000, reward: { darkMatter: 500, points: 5000 } },
{ tier: AchievementTier.Platinum, target: 100000, reward: { darkMatter: 2500, points: 25000 } },
{ tier: AchievementTier.Diamond, target: 1000000, reward: { darkMatter: 10000, points: 100000 } }
]
},
{
id: 'resourceConsumer',
category: AchievementCategory.Resource,
icon: 'Flame',
statKey: 'totalResourcesConsumed',
checkType: 'sum',
tiers: [
{ tier: AchievementTier.Bronze, target: 50000, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 500000, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 5000000, reward: { darkMatter: 350, points: 3500 } },
{ tier: AchievementTier.Platinum, target: 50000000, reward: { darkMatter: 1750, points: 17500 } },
{ tier: AchievementTier.Diamond, target: 500000000, reward: { darkMatter: 8500, points: 85000 } }
]
},
// ==================== 建造类成就 ====================
{
id: 'masterBuilder',
category: AchievementCategory.Building,
icon: 'Building2',
statKey: 'buildingsUpgraded',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 50, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 200, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'researcher',
category: AchievementCategory.Building,
icon: 'FlaskConical',
statKey: 'researchCompleted',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 300, points: 3000 } },
{ tier: AchievementTier.Platinum, target: 250, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 500, reward: { darkMatter: 7500, points: 75000 } }
]
},
{
id: 'shipwright',
category: AchievementCategory.Building,
icon: 'Rocket',
statKey: 'totalShipsProduced',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'fortifier',
category: AchievementCategory.Building,
icon: 'Shield',
statKey: 'totalDefensesBuilt',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
]
},
// ==================== 战斗类成就 ====================
{
id: 'warmonger',
category: AchievementCategory.Combat,
icon: 'Swords',
statKey: 'attacksLaunched',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 20, points: 200 } },
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 100, points: 1000 } },
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 400, points: 4000 } },
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 2000, points: 20000 } },
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 10000, points: 100000 } }
]
},
{
id: 'conqueror',
category: AchievementCategory.Combat,
icon: 'Crown',
statKey: 'attacksWon',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 25, points: 250 } },
{ tier: AchievementTier.Silver, target: 15, reward: { darkMatter: 125, points: 1250 } },
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 500, points: 5000 } },
{ tier: AchievementTier.Platinum, target: 200, reward: { darkMatter: 2500, points: 25000 } },
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 12500, points: 125000 } }
]
},
{
id: 'defender',
category: AchievementCategory.Combat,
icon: 'ShieldCheck',
statKey: 'defensesSuccessful',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 25, points: 250 } },
{ tier: AchievementTier.Silver, target: 15, reward: { darkMatter: 125, points: 1250 } },
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 500, points: 5000 } },
{ tier: AchievementTier.Platinum, target: 200, reward: { darkMatter: 2500, points: 25000 } },
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 12500, points: 125000 } }
]
},
{
id: 'fleetDestroyer',
category: AchievementCategory.Combat,
icon: 'Bomb',
statKey: 'totalEnemyFleetDestroyedInDefense',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 300, points: 3000 } },
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 7500, points: 75000 } }
]
},
{
id: 'debrisCreator',
category: AchievementCategory.Combat,
icon: 'Trash2',
statKey: 'totalDebrisCreated',
checkType: 'sum',
tiers: [
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'fleetSacrifice',
category: AchievementCategory.Combat,
icon: 'Skull',
statKey: 'totalFleetLost',
checkType: 'sum',
tiers: [
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'defenseSacrifice',
category: AchievementCategory.Combat,
icon: 'ShieldOff',
statKey: 'totalDefenseLostInDefense',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 500, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 2000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 10000, reward: { darkMatter: 5000, points: 50000 } }
]
},
// ==================== 任务类成就 ====================
{
id: 'pilot',
category: AchievementCategory.Mission,
icon: 'Plane',
statKey: 'totalFlightMissions',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 50, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 200, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 1000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 5000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'transporter',
category: AchievementCategory.Mission,
icon: 'Truck',
statKey: 'transportMissions',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'cargoMaster',
category: AchievementCategory.Mission,
icon: 'Package',
statKey: 'transportedResources',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'colonizer',
category: AchievementCategory.Mission,
icon: 'Flag',
statKey: 'colonizations',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 150, points: 1500 } },
{ tier: AchievementTier.Gold, target: 5, reward: { darkMatter: 500, points: 5000 } },
{ tier: AchievementTier.Platinum, target: 8, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 12, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'spy',
category: AchievementCategory.Mission,
icon: 'Eye',
statKey: 'spyMissions',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'deployer',
category: AchievementCategory.Mission,
icon: 'ArrowDownToLine',
statKey: 'deployments',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'explorer',
category: AchievementCategory.Mission,
icon: 'Compass',
statKey: 'expeditionsTotal',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 300, points: 3000 } },
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 7500, points: 75000 } }
]
},
{
id: 'luckyExplorer',
category: AchievementCategory.Mission,
icon: 'Sparkle',
statKey: 'expeditionsSuccessful',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 20, points: 200 } },
{ tier: AchievementTier.Silver, target: 15, reward: { darkMatter: 100, points: 1000 } },
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 400, points: 4000 } },
{ tier: AchievementTier.Platinum, target: 200, reward: { darkMatter: 2000, points: 20000 } },
{ tier: AchievementTier.Diamond, target: 1000, reward: { darkMatter: 10000, points: 100000 } }
]
},
{
id: 'recycler',
category: AchievementCategory.Mission,
icon: 'Recycle',
statKey: 'recyclingMissions',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 25, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 100, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 500, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 2000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'scavenger',
category: AchievementCategory.Mission,
icon: 'Pickaxe',
statKey: 'recycledResources',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'destroyer',
category: AchievementCategory.Mission,
icon: 'Zap',
statKey: 'planetDestructions',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 100, points: 1000 } },
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 500, points: 5000 } },
{ tier: AchievementTier.Gold, target: 10, reward: { darkMatter: 2000, points: 20000 } },
{ tier: AchievementTier.Platinum, target: 25, reward: { darkMatter: 10000, points: 100000 } },
{ tier: AchievementTier.Diamond, target: 50, reward: { darkMatter: 50000, points: 500000 } }
]
},
{
id: 'fuelBurner',
category: AchievementCategory.Mission,
icon: 'Fuel',
statKey: 'fuelConsumed',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 1000, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 10000, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 100000, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 1000000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 10000000, reward: { darkMatter: 5000, points: 50000 } }
]
},
// ==================== 外交类成就 ====================
{
id: 'diplomat',
category: AchievementCategory.Diplomacy,
icon: 'HandshakeIcon',
statKey: 'friendlyNPCCount',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 20, points: 200 } },
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 100, points: 1000 } },
{ tier: AchievementTier.Gold, target: 5, reward: { darkMatter: 400, points: 4000 } },
{ tier: AchievementTier.Platinum, target: 10, reward: { darkMatter: 2000, points: 20000 } },
{ tier: AchievementTier.Diamond, target: 20, reward: { darkMatter: 10000, points: 100000 } }
]
},
{
id: 'nemesis',
category: AchievementCategory.Diplomacy,
icon: 'Angry',
statKey: 'hostileNPCCount',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 1, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 3, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 5, reward: { darkMatter: 300, points: 3000 } },
{ tier: AchievementTier.Platinum, target: 10, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 20, reward: { darkMatter: 7500, points: 75000 } }
]
},
{
id: 'generous',
category: AchievementCategory.Diplomacy,
icon: 'Gift',
statKey: 'giftsSent',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 10, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 30, reward: { darkMatter: 300, points: 3000 } },
{ tier: AchievementTier.Platinum, target: 100, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 300, reward: { darkMatter: 7500, points: 75000 } }
]
},
{
id: 'philanthropist',
category: AchievementCategory.Diplomacy,
icon: 'HeartHandshake',
statKey: 'giftResourcesTotal',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 15, points: 150 } },
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 75, points: 750 } },
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 300, points: 3000 } },
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1500, points: 15000 } },
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 7500, points: 75000 } }
]
},
{
id: 'target',
category: AchievementCategory.Diplomacy,
icon: 'Target',
statKey: 'attackedByNPC',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 10, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 30, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 100, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 300, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'watched',
category: AchievementCategory.Diplomacy,
icon: 'ScanEye',
statKey: 'spiedByNPC',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 5, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 20, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 50, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 150, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 500, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'robbed',
category: AchievementCategory.Diplomacy,
icon: 'Banknote',
statKey: 'debrisRecycledByNPC',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 3, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 10, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 30, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 100, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 300, reward: { darkMatter: 5000, points: 50000 } }
]
},
{
id: 'lostToNPC',
category: AchievementCategory.Diplomacy,
icon: 'BadgeDollarSign',
statKey: 'debrisResourcesLostToNPC',
checkType: 'gte',
tiers: [
{ tier: AchievementTier.Bronze, target: 10000, reward: { darkMatter: 10, points: 100 } },
{ tier: AchievementTier.Silver, target: 100000, reward: { darkMatter: 50, points: 500 } },
{ tier: AchievementTier.Gold, target: 1000000, reward: { darkMatter: 200, points: 2000 } },
{ tier: AchievementTier.Platinum, target: 10000000, reward: { darkMatter: 1000, points: 10000 } },
{ tier: AchievementTier.Diamond, target: 100000000, reward: { darkMatter: 5000, points: 50000 } }
]
}
]
// 成就ID映射方便快速查找
export const ACHIEVEMENT_MAP: Record<string, AchievementConfig> = Object.fromEntries(ACHIEVEMENTS.map(a => [a.id, a]))
// 按类别分组的成就
export const ACHIEVEMENTS_BY_CATEGORY: Record<AchievementCategory, AchievementConfig[]> = {
[AchievementCategory.Resource]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Resource),
[AchievementCategory.Building]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Building),
[AchievementCategory.Combat]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Combat),
[AchievementCategory.Mission]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Mission),
[AchievementCategory.Diplomacy]: ACHIEVEMENTS.filter(a => a.category === AchievementCategory.Diplomacy)
}
// 等级顺序(用于比较)
export const TIER_ORDER: AchievementTier[] = [
AchievementTier.Bronze,
AchievementTier.Silver,
AchievementTier.Gold,
AchievementTier.Platinum,
AchievementTier.Diamond
]
// 获取等级索引
export function getTierIndex(tier: AchievementTier): number {
return TIER_ORDER.indexOf(tier)
}
// 获取下一个等级
export function getNextTier(tier: AchievementTier | null): AchievementTier | null {
if (tier === null) return AchievementTier.Bronze
const index = getTierIndex(tier)
if (index >= TIER_ORDER.length - 1) return null
return TIER_ORDER[index + 1] ?? null
}

View File

@@ -59,6 +59,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
baseTime: 15, // 减少建造时间30→15秒
costMultiplier: 1.5,
spaceUsage: 2,
planetOnly: true, // OGame规则月球不能建造太阳能电站
levelRequirements: {
15: { [BuildingType.RoboticsFactory]: 3 },
25: { [BuildingType.RoboticsFactory]: 6, [BuildingType.ResearchLab]: 5 },
@@ -73,6 +74,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
baseTime: 30,
costMultiplier: 1.8,
spaceUsage: 4,
planetOnly: true, // OGame规则月球不能建造核聚变反应堆
requirements: {
[TechnologyType.EnergyTechnology]: 3,
[BuildingType.DeuteriumSynthesizer]: 5
@@ -109,6 +111,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
costMultiplier: 2,
spaceUsage: 8,
maxLevel: 10, // 最多10级最多11个建造队列
planetOnly: true, // OGame规则月球不能建造纳米工厂
requirements: { [BuildingType.RoboticsFactory]: 10 },
levelRequirements: {
3: { [BuildingType.ResearchLab]: 10, [BuildingType.Shipyard]: 8, [TechnologyType.ComputerTechnology]: 8 },
@@ -139,6 +142,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
costMultiplier: 1.8,
spaceUsage: 3,
fleetStorageBonus: 1500, // 每级增加1500舰队仓储比船坞更高
planetOnly: true, // OGame规则月球不能建造机库
requirements: { [BuildingType.RoboticsFactory]: 1 }, // 只需要1级机器人工厂
levelRequirements: {
10: { [BuildingType.RoboticsFactory]: 3 },
@@ -153,6 +157,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
baseTime: 30, // 减少建造时间60→30秒
costMultiplier: 2,
spaceUsage: 3,
planetOnly: true, // OGame规则月球不能建造研究实验室
requirements: {
[BuildingType.MetalMine]: 3,
[BuildingType.CrystalMine]: 3,
@@ -176,11 +181,8 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
baseTime: 15, // 减少建造时间30→15秒
costMultiplier: 2,
spaceUsage: 1,
requirements: { [BuildingType.MetalMine]: 2 },
levelRequirements: {
8: { [BuildingType.MetalMine]: 15, [BuildingType.RoboticsFactory]: 3 },
12: { [BuildingType.MetalMine]: 25, [BuildingType.RoboticsFactory]: 6 }
}
planetOnly: true, // OGame规则月球不能建造仓储月球没有矿场
requirements: { [BuildingType.MetalMine]: 1 }
},
[BuildingType.CrystalStorage]: {
id: BuildingType.CrystalStorage,
@@ -190,11 +192,8 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
baseTime: 15, // 减少建造时间30→15秒
costMultiplier: 2,
spaceUsage: 1,
requirements: { [BuildingType.CrystalMine]: 2 },
levelRequirements: {
8: { [BuildingType.CrystalMine]: 15, [BuildingType.RoboticsFactory]: 3 },
12: { [BuildingType.CrystalMine]: 25, [BuildingType.RoboticsFactory]: 6 }
}
planetOnly: true, // OGame规则月球不能建造仓储月球没有矿场
requirements: { [BuildingType.CrystalMine]: 1 }
},
[BuildingType.DeuteriumTank]: {
id: BuildingType.DeuteriumTank,
@@ -204,11 +203,8 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
baseTime: 15, // 减少建造时间30→15秒
costMultiplier: 2,
spaceUsage: 1,
requirements: { [BuildingType.DeuteriumSynthesizer]: 2 },
levelRequirements: {
8: { [BuildingType.DeuteriumSynthesizer]: 15, [BuildingType.RoboticsFactory]: 3 },
12: { [BuildingType.DeuteriumSynthesizer]: 25, [BuildingType.RoboticsFactory]: 6 }
}
planetOnly: true, // OGame规则月球不能建造仓储月球没有矿场
requirements: { [BuildingType.DeuteriumSynthesizer]: 1 }
},
[BuildingType.DarkMatterCollector]: {
id: BuildingType.DarkMatterCollector,
@@ -255,6 +251,7 @@ export const BUILDINGS: Record<BuildingType, BuildingConfig> = {
costMultiplier: 2,
spaceUsage: 5,
maxLevel: 10,
planetOnly: true, // OGame规则月球不能建造导弹发射井
requirements: {
[BuildingType.Shipyard]: 1
},
@@ -1097,12 +1094,18 @@ export const OFFICERS: Record<OfficerType, OfficerConfig> = {
// 月球配置
export const MOON_CONFIG = {
minDebrisField: 100000, // 最小残骸场 (金属+晶体)
baseChance: 1, // 基础1%概率
maxChance: 20, // 最大20%概率
minDebrisField: 100000, // 最小残骸场 (金属+晶体) 100k = 1%概率
baseChance: 0, // 基础0%概率每100k残骸增加1%
maxChance: 20, // 最大20%概率需要2M残骸
chancePerDebris: 100000, // 每10万资源增加1%概率
baseSize: 100, // 月球基础空间
lunarBaseSpaceBonus: 30 // 每级月球基地增加的空间
baseFields: 1, // 月球初始空间OGame规则初始只有1格
lunarBaseFieldsBonus: 3, // 每级月球基地增加的空间(每级+3格占用1格净增2格
minDiameter: 3476, // 最小月球直径(km)1%概率时
maxDiameter: 8944, // 最大月球直径(km)20%概率时保证>8000km
baseDiameter: 3000, // 基础直径(km)
diameterPerChance: 273, // 每1%概率增加的直径(km)(8944-3476)/20≈273
jumpGateCooldown: 3600000 // 跳跃门冷却时间(ms) = 1小时 (OGame规则)
// 注月球资源容量与行星相同基础10000OGame规则允许资源超过容量存储
}
// 行星配置

View File

@@ -81,7 +81,8 @@ export default {
officers: 'Offiziere',
simulator: 'Simulator',
galaxy: 'Galaxie',
diplomacy: 'Diplomacy',
diplomacy: 'Diplomatie',
achievements: 'Erfolge',
messages: 'Nachrichten',
settings: 'Einstellungen',
gm: 'GM'
@@ -109,6 +110,14 @@ export default {
hour: 'Stunde',
noEnergy: 'Keine Energie'
},
energy: {
lowWarning: 'Energiedefizit! Ressourcenproduktion gestoppt!',
severeWarning: 'Energiedefizit! Ressourcenproduktion gestoppt!',
criticalWarning: 'Energiedefizit! Ressourcenproduktion gestoppt!',
noProduction: 'Energiedefizit! Ressourcenproduktion gestoppt!',
deficitDetail: 'Energiedefizit: {deficit}, bauen Sie mehr Kraftwerke',
buildSolarPlant: 'Kraftwerk bauen'
},
planet: {
planet: 'Planet',
moon: 'Mond',
@@ -421,6 +430,7 @@ export default {
shipyardView: {
title: 'Raumschiffwerft',
fleetStorage: 'Flottenspeicher',
owned: 'Besitz',
attack: 'Angriff',
missileAttack: 'Raketenangriff',
shield: 'Schild',
@@ -486,8 +496,10 @@ export default {
available: 'Verfügbar',
all: 'Alle',
targetCoordinates: 'Zielkoordinaten',
targetType: 'Zieltyp',
planet: 'Planet',
moon: 'Mond',
galaxy: 'Galaxie',
diplomacy: 'Diplomacy',
system: 'System',
position: 'Position',
missionType: 'Missionstyp',
@@ -531,7 +543,30 @@ export default {
noDeathstar: 'Todesstern für Zerstörungsmission erforderlich',
giftMode: 'Geschenkmodus',
giftModeDescription: 'Ressourcen als Geschenk senden an',
estimatedReputationGain: 'Geschätzter Reputationsgewinn'
estimatedReputationGain: 'Geschätzter Reputationsgewinn',
// Flotten-Vorlagen
fleetPresets: 'Flotten-Vorlagen',
fleetPresetsDescription: 'Speichere häufige Flottenkonfigurationen für schnellen Versand (max. 3)',
savePreset: 'Vorlage speichern',
noPresets: 'Noch keine Vorlagen, wähle eine Flotte und klicke "Vorlage speichern"',
shipTypes: 'Schiffstypen',
editPreset: 'Vorlageninhalt bearbeiten',
renamePreset: 'Umbenennen',
deletePreset: 'Vorlage löschen',
editingPresetHint: 'Vorlage bearbeiten, Flottenkonfiguration ändern und "Speichern" klicken',
presetLimitReached: 'Vorlagenlimit erreicht',
presetLimitReachedMessage: 'Maximal {max} Vorlagen erlaubt',
presetError: 'Speichern fehlgeschlagen',
presetNoShips: 'Bitte wähle zuerst mindestens ein Schiff',
presetDefaultName: 'Vorlage {number}',
savePresetTitle: 'Flottenvorlage speichern',
savePresetDescription: 'Benenne diese Flottenkonfiguration',
renamePresetTitle: 'Vorlage umbenennen',
renamePresetDescription: 'Neuen Vorlagennamen eingeben',
presetName: 'Vorlagenname',
presetNamePlaceholder: 'Vorlagennamen eingeben',
deletePresetTitle: 'Vorlage löschen',
deletePresetMessage: 'Vorlage "{name}" wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.'
},
officersView: {
title: 'Offiziere',
@@ -571,7 +606,6 @@ export default {
title: 'Galaxie',
selectCoordinates: 'Koordinaten auswählen',
galaxy: 'Galaxie',
diplomacy: 'Diplomacy',
selectGalaxy: 'Galaxie auswählen',
system: 'System',
selectSystem: 'System auswählen',
@@ -733,14 +767,29 @@ export default {
missionReports: {
transportSuccess: 'Transportmission erfolgreich abgeschlossen',
transportFailed: 'Transportmission fehlgeschlagen',
transportFailedTargetNotFound: 'Transport fehlgeschlagen: Zielplanet existiert nicht',
transportFailedGiftRejected: 'Transport fehlgeschlagen: Geschenk wurde abgelehnt',
colonizeSuccess: 'Kolonisierungsmission erfolgreich, neuer Planet gegründet',
colonizeFailed: 'Kolonisierungsmission fehlgeschlagen',
colonizeFailedOccupied: 'Kolonisierung fehlgeschlagen: Zielposition ist bereits von einem anderen Planeten besetzt',
colonizeFailedMaxColonies: 'Kolonisierung fehlgeschlagen: Maximale Anzahl an Kolonien erreicht. Forsche Astrophysik, um das Limit zu erhöhen.',
spySuccess: 'Spionagemission erfolgreich abgeschlossen',
spyFailed: 'Spionagemission fehlgeschlagen',
spyFailedTargetNotFound: 'Spionage fehlgeschlagen: Zielplanet existiert nicht',
deploySuccess: 'Einsatzmission erfolgreich abgeschlossen',
deployFailed: 'Einsatzmission fehlgeschlagen',
deployFailedTargetNotFound: 'Einsatz fehlgeschlagen: Zielplanet existiert nicht',
deployFailedNotOwnPlanet: 'Einsatz fehlgeschlagen: Zielplanet gehört nicht dir',
recycleSuccess: 'Recyclingmission erfolgreich abgeschlossen',
recycleFailed: 'Recyclingmission fehlgeschlagen, keine Trümmer am Zielort',
recycleFailedNoDebris: 'Recycling fehlgeschlagen: Kein Trümmerfeld am Zielort',
recycleFailedDebrisEmpty: 'Recycling fehlgeschlagen: Trümmerfeld ist bereits leer',
destroySuccess: 'Planetenzerstörungsmission erfolgreich ausgeführt',
destroyFailed: 'Planetenzerstörungsmission fehlgeschlagen',
destroyFailedTargetNotFound: 'Zerstörung fehlgeschlagen: Zielplanet existiert nicht',
destroyFailedOwnPlanet: 'Zerstörung fehlgeschlagen: Eigener Planet kann nicht zerstört werden',
destroyFailedNoDeathstar: 'Zerstörung fehlgeschlagen: Kein Todesstern für die Mission',
destroyFailedChance: 'Zerstörung fehlgeschlagen: Wahrscheinlichkeitsprüfung gescheitert (Erfolgsrate: {chance}%)',
missileAttackSuccess: 'Raketenangriff erfolgreich',
missileAttackFailed: 'Raketenangriff fehlgeschlagen, Zielplanet existiert nicht',
missileAttackIntercepted: 'Alle Raketen abgefangen',
@@ -1259,6 +1308,10 @@ export default {
message:
'Simulieren Sie Kampfergebnisse vor dem Angriff. Geben Sie Flotten und Technologiestufen ein, um Sieg, Verluste und Beute vorherzusagen.'
},
achievements: {
title: 'Erfolgssystem',
message: 'Schließen Sie Spielziele ab, um Erfolge freizuschalten und Dunkle Materie-Belohnungen zu erhalten! Erfolge haben mehrere Stufen - streben Sie höhere Herausforderungen an, um bessere Belohnungen zu erhalten.'
},
settings: {
title: 'Einstellungen',
message: 'Verwalten Sie hier Spieldaten, Benachrichtigungen und Import/Export. Sichern Sie regelmäßig Ihren Fortschritt!'
@@ -1268,5 +1321,102 @@ export default {
message:
'Der GM-Modus ermöglicht schnelle Änderung von Ressourcen, Gebäuden und Technologiestufen. Nutzen Sie ihn zum Testen oder für vollständige Spielinhalte.'
}
},
achievements: {
title: 'Erfolge',
unlocked: 'Erfolg freigeschaltet',
progress: 'Fortschritt',
nextTier: 'Nächste Stufe',
maxTierReached: 'Höchste Stufe erreicht',
tiers: {
bronze: 'Bronze',
silver: 'Silber',
gold: 'Gold',
platinum: 'Platin',
diamond: 'Diamant'
},
categories: {
resource: 'Ressourcen',
building: 'Gebäude',
combat: 'Kampf',
mission: 'Missionen',
diplomacy: 'Diplomatie'
},
names: {
metalCollector: 'Metallsammler',
crystalCollector: 'Kristallsammler',
deuteriumCollector: 'Deuteriumsammler',
darkMatterCollector: 'Dunkle-Materie-Sammler',
resourceConsumer: 'Ressourcenverbraucher',
masterBuilder: 'Meisterbauer',
researcher: 'Forscher',
shipwright: 'Schiffsbauer',
fortifier: 'Befestiger',
warmonger: 'Kriegstreiber',
conqueror: 'Eroberer',
defender: 'Verteidiger',
fleetDestroyer: 'Flottenzerstörer',
debrisCreator: 'Trümmererzeuger',
fleetSacrifice: 'Flottenopfer',
defenseSacrifice: 'Verteidigungsopfer',
pilot: 'Pilot',
transporter: 'Transporter',
cargoMaster: 'Frachtmeister',
colonizer: 'Kolonisator',
spy: 'Spionagemeister',
deployer: 'Stationierer',
explorer: 'Entdecker',
luckyExplorer: 'Glücklicher Entdecker',
recycler: 'Recycler',
scavenger: 'Sammler',
destroyer: 'Vernichter',
fuelBurner: 'Treibstoffverbrenner',
diplomat: 'Diplomat',
nemesis: 'Erzfeind',
generous: 'Großzügig',
philanthropist: 'Philanthrop',
target: 'Zielscheibe',
watched: 'Überwacht',
robbed: 'Beraubt',
lostToNPC: 'An NPC verloren'
},
descriptions: {
metalCollector: 'Gesamtes produziertes Metall',
crystalCollector: 'Gesamtes produziertes Kristall',
deuteriumCollector: 'Gesamtes produziertes Deuterium',
darkMatterCollector: 'Gesamte produzierte Dunkle Materie',
resourceConsumer: 'Gesamte verbrauchte Ressourcen',
masterBuilder: 'Gesamte Gebäudeupgrades',
researcher: 'Gesamte abgeschlossene Forschungen',
shipwright: 'Gesamte produzierte Schiffe',
fortifier: 'Gesamte gebaute Verteidigungen',
warmonger: 'Gestartete Angriffe',
conqueror: 'Gewonnene Angriffe',
defender: 'Erfolgreiche Verteidigungen',
fleetDestroyer: 'In der Verteidigung zerstörte feindliche Flotte',
debrisCreator: 'In Kämpfen erzeugte Trümmer',
fleetSacrifice: 'Gesamte verlorene Flotte',
defenseSacrifice: 'In der Verteidigung verlorene Verteidigungen',
pilot: 'Gesamte Flugmissionen',
transporter: 'Gesamte Transportmissionen',
cargoMaster: 'Gesamte transportierte Ressourcen',
colonizer: 'Kolonisierte Planeten',
spy: 'Abgeschlossene Spionagemissionen',
deployer: 'Abgeschlossene Stationierungen',
explorer: 'Gesamte Expeditionen',
luckyExplorer: 'Erfolgreiche Expeditionen',
recycler: 'Gesamte Recycling-Missionen',
scavenger: 'Gesamte recycelte Ressourcen',
destroyer: 'Zerstörte Planeten',
fuelBurner: 'Gesamter verbrauchter Treibstoff',
diplomat: 'Anzahl freundlicher NPCs',
nemesis: 'Anzahl feindlicher NPCs',
generous: 'Gesendete Geschenke',
philanthropist: 'Gesamte geschenkte Ressourcen',
target: 'Von NPC angegriffen',
watched: 'Von NPC ausspioniert',
robbed: 'Von NPC gesammelte Trümmer',
lostToNPC: 'An NPC verlorene Trümmerressourcen'
}
}
}

View File

@@ -81,6 +81,7 @@ export default {
simulator: 'Simulator',
galaxy: 'Galaxy',
diplomacy: 'Diplomacy',
achievements: 'Achievements',
messages: 'Messages',
settings: 'Settings',
gm: 'GM'
@@ -108,6 +109,14 @@ export default {
hour: 'hour',
noEnergy: 'No Energy'
},
energy: {
lowWarning: 'Energy deficit! Resource production stopped!',
severeWarning: 'Energy deficit! Resource production stopped!',
criticalWarning: 'Energy deficit! Resource production stopped!',
noProduction: 'Energy deficit! Resource production stopped!',
deficitDetail: 'Energy deficit: {deficit}, build more power plants',
buildSolarPlant: 'Build Power Plant'
},
planet: {
planet: 'Planet',
moon: 'Moon',
@@ -419,6 +428,7 @@ export default {
shipyardView: {
title: 'Shipyard',
fleetStorage: 'Fleet Storage',
owned: 'Owned',
attack: 'Attack',
missileAttack: 'Missile Attack',
shield: 'Shield',
@@ -482,6 +492,9 @@ export default {
available: 'Available',
all: 'All',
targetCoordinates: 'Target Coordinates',
targetType: 'Target Type',
planet: 'Planet',
moon: 'Moon',
galaxy: 'Galaxy',
system: 'System',
position: 'Position',
@@ -528,7 +541,47 @@ export default {
noDeathstar: 'Deathstar required for destruction mission',
giftMode: 'Gift Mode',
giftModeDescription: 'Send resources as a gift to',
estimatedReputationGain: 'Estimated reputation gain'
estimatedReputationGain: 'Estimated reputation gain',
// Fleet presets
fleetPresets: 'Fleet Presets',
fleetPresetsDescription: 'Save common fleet configurations for quick dispatch (max 3)',
savePreset: 'Save Preset',
noPresets: 'No presets yet, select fleet and click "Save Preset" to create',
shipTypes: 'ship types',
editPreset: 'Edit preset content',
renamePreset: 'Rename',
deletePreset: 'Delete preset',
editingPresetHint: 'Editing preset, modify fleet configuration and click "Save" to update',
presetLimitReached: 'Preset limit reached',
presetLimitReachedMessage: 'Maximum of {max} presets allowed',
presetError: 'Save failed',
presetNoShips: 'Please select at least one ship first',
presetDefaultName: 'Preset {number}',
savePresetTitle: 'Save Fleet Preset',
savePresetDescription: 'Name this fleet configuration',
renamePresetTitle: 'Rename Preset',
renamePresetDescription: 'Enter a new preset name',
presetName: 'Preset Name',
presetNamePlaceholder: 'Enter preset name',
deletePresetTitle: 'Delete Preset',
deletePresetMessage: 'Are you sure you want to delete preset "{name}"? This action cannot be undone.',
// Jump Gate
jumpGate: 'Jump Gate',
jumpGateDescription: 'Use the Jump Gate to instantly transfer fleet to another moon with Jump Gate',
jumpGateNotAvailable: 'Jump Gate Not Available',
jumpGateRequiresMoon: 'Jump Gate can only be used on moons',
jumpGateNotBuilt: 'Current moon does not have a Jump Gate',
jumpGateCooldown: 'Jump Gate Cooling Down',
jumpGateCooldownRemaining: 'Cooldown Remaining',
jumpGateReady: 'Jump Gate Ready',
jumpGateSelectTarget: 'Select Target Moon',
jumpGateNoTargetMoons: 'No available target moons (requires Jump Gate and cooldown complete)',
jumpGateSelectFleet: 'Select Fleet to Transfer',
jumpGateTransfer: 'Transfer Fleet',
jumpGateSuccess: 'Jump Gate Transfer Successful',
jumpGateSuccessMessage: 'Fleet has been instantly transferred to {target}',
jumpGateFailed: 'Jump Gate Transfer Failed',
jumpGateFailedMessage: 'Please check Jump Gate status and fleet configuration'
},
officersView: {
title: 'Officers',
@@ -616,7 +669,26 @@ export default {
giftPlanetTitle: 'Send Gift',
giftPlanetMessage:
'Are you sure you want to send resources as a gift to planet [{coordinates}]?\n\nPlease go to the fleet page to select transport ships and load resources.',
npcPlanetName: "{name}'s Planet"
npcPlanetName: "{name}'s Planet",
// Sensor Phalanx Scan
phalanxScan: 'Phalanx Scan',
phalanxScanTitle: 'Sensor Phalanx Scan',
phalanxScanDescription: 'Scanning fleet activity at planet [{coordinates}]',
phalanxNoMoon: 'Requires a moon with Sensor Phalanx to scan',
phalanxOutOfRange: 'Target is out of scan range',
phalanxRange: 'Scan Range',
phalanxCost: 'Scan Cost',
phalanxNoFleets: 'No fleet activity detected',
phalanxFleetDetected: '{count} fleet(s) detected',
phalanxMission: 'Mission',
phalanxOrigin: 'Origin',
phalanxDestination: 'Destination',
phalanxArrival: 'Arrival',
phalanxReturn: 'Return',
phalanxStatus: 'Status',
phalanxStatusOutbound: 'Outbound',
phalanxStatusReturning: 'Returning',
phalanxInsufficientDeuterium: 'Insufficient Deuterium'
},
messagesView: {
title: 'Messages',
@@ -726,14 +798,29 @@ export default {
missionReports: {
transportSuccess: 'Transport mission completed successfully',
transportFailed: 'Transport mission failed',
transportFailedTargetNotFound: 'Transport failed: Target planet does not exist',
transportFailedGiftRejected: 'Transport failed: Gift was rejected',
colonizeSuccess: 'Colonization mission successful, new planet established',
colonizeFailed: 'Colonization mission failed',
colonizeFailedOccupied: 'Colonization failed: Target position is already occupied by another planet',
colonizeFailedMaxColonies: 'Colonization failed: Maximum number of colonies reached. Research Astrophysics to increase the limit.',
spySuccess: 'Espionage mission completed successfully',
spyFailed: 'Espionage mission failed',
spyFailedTargetNotFound: 'Espionage failed: Target planet does not exist',
deploySuccess: 'Deployment mission completed successfully',
deployFailed: 'Deployment mission failed',
deployFailedTargetNotFound: 'Deployment failed: Target planet does not exist',
deployFailedNotOwnPlanet: 'Deployment failed: Target planet does not belong to you',
recycleSuccess: 'Recycling mission completed successfully',
recycleFailed: 'Recycling mission failed, no debris at target location',
recycleFailedNoDebris: 'Recycling failed: No debris field at target location',
recycleFailedDebrisEmpty: 'Recycling failed: Debris field has been cleared',
destroySuccess: 'Planet destruction mission executed successfully',
destroyFailed: 'Planet destruction mission failed',
destroyFailedTargetNotFound: 'Destruction failed: Target planet does not exist',
destroyFailedOwnPlanet: 'Destruction failed: Cannot destroy your own planet',
destroyFailedNoDeathstar: 'Destruction failed: No Death Star to execute the mission',
destroyFailedChance: 'Destruction failed: Probability check failed (Success rate: {chance}%)',
missileAttackSuccess: 'Missile attack successful',
missileAttackFailed: 'Missile attack failed, target planet does not exist',
missileAttackIntercepted: 'All missiles intercepted',
@@ -957,11 +1044,13 @@ export default {
missionType: {
spy: 'Spy',
attack: 'Attack',
recycle: 'Recycle',
unknown: 'Unknown'
},
warning: {
spy: 'Enemy spy incoming!',
attack: 'Enemy attack incoming!',
recycle: 'Enemy is recycling debris near you!',
unknown: 'Enemy fleet incoming!'
}
},
@@ -1376,6 +1465,10 @@ export default {
title: 'Battle Simulator',
message: 'Simulate battle outcomes before attacking. Enter both fleets and tech levels to predict victory, losses, and loot.'
},
achievements: {
title: 'Achievement System',
message: 'Complete game objectives to unlock achievements and earn Dark Matter rewards! Achievements have multiple tiers - aim for higher challenges to get better rewards.'
},
settings: {
title: 'Settings',
message: 'Manage game data, adjust notifications, export/import saves here. Remember to backup your progress regularly!'
@@ -1385,5 +1478,102 @@ export default {
message:
'GM mode allows quick modification of resources, buildings, and tech levels. Use it for testing or experiencing full game content.'
}
},
achievements: {
title: 'Achievements',
unlocked: 'Achievement Unlocked',
progress: 'Progress',
nextTier: 'Next Tier',
maxTierReached: 'Max Tier Reached',
tiers: {
bronze: 'Bronze',
silver: 'Silver',
gold: 'Gold',
platinum: 'Platinum',
diamond: 'Diamond'
},
categories: {
resource: 'Resource',
building: 'Building',
combat: 'Combat',
mission: 'Mission',
diplomacy: 'Diplomacy'
},
names: {
metalCollector: 'Metal Collector',
crystalCollector: 'Crystal Collector',
deuteriumCollector: 'Deuterium Collector',
darkMatterCollector: 'Dark Matter Collector',
resourceConsumer: 'Resource Consumer',
masterBuilder: 'Master Builder',
researcher: 'Researcher',
shipwright: 'Shipwright',
fortifier: 'Fortifier',
warmonger: 'Warmonger',
conqueror: 'Conqueror',
defender: 'Defender',
fleetDestroyer: 'Fleet Destroyer',
debrisCreator: 'Debris Creator',
fleetSacrifice: 'Fleet Sacrifice',
defenseSacrifice: 'Defense Sacrifice',
pilot: 'Pilot',
transporter: 'Transporter',
cargoMaster: 'Cargo Master',
colonizer: 'Colonizer',
spy: 'Spy Master',
deployer: 'Deployer',
explorer: 'Explorer',
luckyExplorer: 'Lucky Explorer',
recycler: 'Recycler',
scavenger: 'Scavenger',
destroyer: 'Destroyer',
fuelBurner: 'Fuel Burner',
diplomat: 'Diplomat',
nemesis: 'Nemesis',
generous: 'Generous',
philanthropist: 'Philanthropist',
target: 'Target',
watched: 'Watched',
robbed: 'Robbed',
lostToNPC: 'Lost to NPC'
},
descriptions: {
metalCollector: 'Total metal produced',
crystalCollector: 'Total crystal produced',
deuteriumCollector: 'Total deuterium produced',
darkMatterCollector: 'Total dark matter produced',
resourceConsumer: 'Total resources consumed',
masterBuilder: 'Total buildings upgraded',
researcher: 'Total researches completed',
shipwright: 'Total ships produced',
fortifier: 'Total defenses built',
warmonger: 'Total attacks launched',
conqueror: 'Total attacks won',
defender: 'Total defenses successful',
fleetDestroyer: 'Enemy fleet destroyed in defense',
debrisCreator: 'Total debris created from battles',
fleetSacrifice: 'Total fleet lost',
defenseSacrifice: 'Total defenses lost in defense',
pilot: 'Total flight missions',
transporter: 'Total transport missions',
cargoMaster: 'Total resources transported',
colonizer: 'Planets colonized',
spy: 'Spy missions completed',
deployer: 'Deploy missions completed',
explorer: 'Total expeditions',
luckyExplorer: 'Successful expeditions',
recycler: 'Total recycling missions',
scavenger: 'Total resources recycled',
destroyer: 'Planets destroyed',
fuelBurner: 'Total fuel consumed',
diplomat: 'Number of friendly NPCs',
nemesis: 'Number of hostile NPCs',
generous: 'Total gifts sent',
philanthropist: 'Total resources gifted',
target: 'Times attacked by NPC',
watched: 'Times spied by NPC',
robbed: 'Times debris recycled by NPC',
lostToNPC: 'Total debris resources lost to NPC'
}
}
}

View File

@@ -81,9 +81,11 @@ export default {
officers: '士官',
simulator: 'シミュレーター',
galaxy: '銀河',
diplomacy: 'Diplomacy',
diplomacy: '外交',
achievements: '実績',
messages: 'メッセージ',
settings: '設定',
guide: 'ゲームガイド',
gm: 'GM'
},
sidebar: {
@@ -109,6 +111,14 @@ export default {
hour: '時間',
noEnergy: 'エネルギー不足'
},
energy: {
lowWarning: 'エネルギー不足!資源生産が停止しています!',
severeWarning: 'エネルギー不足!資源生産が停止しています!',
criticalWarning: 'エネルギー不足!資源生産が停止しています!',
noProduction: 'エネルギー不足!資源生産が停止しています!',
deficitDetail: 'エネルギー不足: {deficit}、発電所を建設してください',
buildSolarPlant: '発電所を建設'
},
planet: {
planet: '惑星',
moon: '月',
@@ -434,6 +444,7 @@ export default {
shipyardView: {
title: '造船所',
fleetStorage: '艦隊ストレージ',
owned: '所有',
attack: '攻撃力',
missileAttack: 'ミサイル攻撃',
shield: 'シールド',
@@ -484,8 +495,10 @@ export default {
available: '利用可能',
all: '全て',
targetCoordinates: '目標座標',
targetType: '目標タイプ',
planet: '惑星',
moon: '月',
galaxy: '銀河',
diplomacy: 'Diplomacy',
system: '星系',
position: '位置',
missionType: 'ミッションタイプ',
@@ -529,7 +542,30 @@ export default {
noDeathstar: '破壊ミッションにはデススターが必要です',
giftMode: 'ギフトモード',
giftModeDescription: '資源を贈り物として送る',
estimatedReputationGain: '推定評判獲得'
estimatedReputationGain: '推定評判獲得',
// 艦隊プリセット
fleetPresets: '艦隊プリセット',
fleetPresetsDescription: 'よく使う艦隊構成を保存して素早く派遣最大3つ',
savePreset: 'プリセット保存',
noPresets: 'プリセットなし、艦隊を選択して「プリセット保存」をクリック',
shipTypes: '種の艦船',
editPreset: 'プリセット内容を編集',
renamePreset: '名前変更',
deletePreset: 'プリセット削除',
editingPresetHint: 'プリセット編集中、艦隊構成を変更して「保存」をクリック',
presetLimitReached: 'プリセット上限到達',
presetLimitReachedMessage: '最大{max}個のプリセットまで',
presetError: '保存失敗',
presetNoShips: '艦船を1隻以上選択してください',
presetDefaultName: 'プリセット {number}',
savePresetTitle: '艦隊プリセットを保存',
savePresetDescription: 'この艦隊構成に名前を付ける',
renamePresetTitle: 'プリセット名変更',
renamePresetDescription: '新しいプリセット名を入力',
presetName: 'プリセット名',
presetNamePlaceholder: 'プリセット名を入力',
deletePresetTitle: 'プリセット削除',
deletePresetMessage: 'プリセット「{name}」を削除しますか?この操作は取り消せません。'
},
officersView: {
title: '士官',
@@ -569,7 +605,6 @@ export default {
title: '銀河',
selectCoordinates: '座標選択',
galaxy: '銀河',
diplomacy: 'Diplomacy',
selectGalaxy: '銀河を選択',
system: '星系',
selectSystem: '星系を選択',
@@ -726,14 +761,29 @@ export default {
missionReports: {
transportSuccess: '輸送ミッションが正常に完了しました',
transportFailed: '輸送ミッションが失敗しました',
transportFailedTargetNotFound: '輸送失敗:目標惑星が存在しません',
transportFailedGiftRejected: '輸送失敗:贈り物が拒否されました',
colonizeSuccess: '植民ミッション成功、新しい惑星が確立されました',
colonizeFailed: '植民ミッションが失敗しました',
colonizeFailedOccupied: '植民失敗:目標位置は既に他の惑星に占有されています',
colonizeFailedMaxColonies: '植民失敗:コロニー数の上限に達しました。天体物理学を研究して上限を増やしてください。',
spySuccess: 'スパイミッションが正常に完了しました',
spyFailed: 'スパイミッションが失敗しました',
spyFailedTargetNotFound: 'スパイ失敗:目標惑星が存在しません',
deploySuccess: '配備ミッションが正常に完了しました',
deployFailed: '配備ミッションが失敗しました',
deployFailedTargetNotFound: '配備失敗:目標惑星が存在しません',
deployFailedNotOwnPlanet: '配備失敗:目標惑星はあなたのものではありません',
recycleSuccess: '回収ミッションが正常に完了しました',
recycleFailed: '回収ミッションが失敗しました。目標位置にデブリがありません',
recycleFailedNoDebris: '回収失敗:目標位置にデブリフィールドがありません',
recycleFailedDebrisEmpty: '回収失敗:デブリフィールドは既に空です',
destroySuccess: '惑星破壊ミッションが正常に実行されました',
destroyFailed: '惑星破壊ミッションが失敗しました',
destroyFailedTargetNotFound: '破壊失敗:目標惑星が存在しません',
destroyFailedOwnPlanet: '破壊失敗:自分の惑星を破壊することはできません',
destroyFailedNoDeathstar: '破壊失敗:ミッションを実行するデススターがありません',
destroyFailedChance: '破壊失敗:確率判定に失敗しました(成功率:{chance}%',
missileAttackSuccess: 'ミサイル攻撃成功',
missileAttackFailed: 'ミサイル攻撃失敗、目標惑星が存在しません',
missileAttackIntercepted: '全てのミサイルが迎撃されました',
@@ -946,11 +996,13 @@ export default {
missionType: {
spy: '偵察',
attack: '攻撃',
recycle: '回収',
unknown: '不明'
},
warning: {
spy: '敵の偵察が接近中!',
attack: '敵の攻撃が接近中!',
recycle: '敵があなたの近くでデブリを回収中!',
unknown: '敵艦隊が接近中!'
}
},
@@ -1238,6 +1290,10 @@ export default {
title: '戦闘シミュレーター',
message: '攻撃前に戦闘結果をシミュレート。双方の艦隊と技術レベルを入力して、勝敗と損失を予測。'
},
achievements: {
title: '実績システム',
message: 'ゲーム目標を達成して実績をアンロックし、ダークマター報酬を獲得!実績には複数のティアがあり、高い目標に挑戦してより良い報酬を手に入れましょう。'
},
settings: {
title: '設定',
message: 'ここでゲームデータの管理、通知設定、セーブのエクスポート/インポートができます。定期的にバックアップを!'
@@ -1246,5 +1302,102 @@ export default {
title: 'GM管理パネル',
message: 'GMモードでは資源、建物、技術レベルを素早く変更できます。テストや完全なゲームコンテンツの体験に使用。'
}
},
achievements: {
title: '実績',
unlocked: '実績解除',
progress: '進捗',
nextTier: '次のティア',
maxTierReached: '最高ティア達成',
tiers: {
bronze: 'ブロンズ',
silver: 'シルバー',
gold: 'ゴールド',
platinum: 'プラチナ',
diamond: 'ダイヤモンド'
},
categories: {
resource: '資源',
building: '建設',
combat: '戦闘',
mission: 'ミッション',
diplomacy: '外交'
},
names: {
metalCollector: 'メタルコレクター',
crystalCollector: 'クリスタルコレクター',
deuteriumCollector: 'デューテリウムコレクター',
darkMatterCollector: 'ダークマターコレクター',
resourceConsumer: '資源消費者',
masterBuilder: 'マスタービルダー',
researcher: '研究者',
shipwright: '造船士',
fortifier: '要塞化者',
warmonger: '戦争屋',
conqueror: '征服者',
defender: '防衛者',
fleetDestroyer: '艦隊破壊者',
debrisCreator: '残骸生成者',
fleetSacrifice: '艦隊犠牲者',
defenseSacrifice: '防衛犠牲者',
pilot: 'パイロット',
transporter: '輸送者',
cargoMaster: '貨物マスター',
colonizer: '植民者',
spy: 'スパイマスター',
deployer: '配備者',
explorer: '探検家',
luckyExplorer: '幸運な探検家',
recycler: 'リサイクラー',
scavenger: 'スカベンジャー',
destroyer: '破壊者',
fuelBurner: '燃料消費者',
diplomat: '外交官',
nemesis: '宿敵',
generous: '寛大な者',
philanthropist: '博愛者',
target: '標的',
watched: '監視下',
robbed: '略奪される',
lostToNPC: 'NPCに奪われた'
},
descriptions: {
metalCollector: '総メタル生産量',
crystalCollector: '総クリスタル生産量',
deuteriumCollector: '総デューテリウム生産量',
darkMatterCollector: '総ダークマター生産量',
resourceConsumer: '総資源消費量',
masterBuilder: '建物アップグレード総数',
researcher: '研究完了総数',
shipwright: '艦船生産総数',
fortifier: '防衛施設建設総数',
warmonger: '攻撃開始回数',
conqueror: '攻撃勝利回数',
defender: '防衛成功回数',
fleetDestroyer: '防衛で破壊した敵艦隊数',
debrisCreator: '戦闘で生成した残骸総量',
fleetSacrifice: '艦隊損失総数',
defenseSacrifice: '防衛で損失した防衛施設数',
pilot: '飛行ミッション総数',
transporter: '輸送ミッション総数',
cargoMaster: '輸送資源総量',
colonizer: '植民した惑星数',
spy: 'スパイミッション完了数',
deployer: '配備ミッション完了数',
explorer: '遠征総数',
luckyExplorer: '遠征成功回数',
recycler: 'リサイクルミッション総数',
scavenger: 'リサイクル資源総量',
destroyer: '破壊した惑星数',
fuelBurner: '燃料消費総量',
diplomat: '友好的なNPCの数',
nemesis: '敵対的なNPCの数',
generous: '贈り物送信回数',
philanthropist: '贈与資源総量',
target: 'NPCに攻撃された回数',
watched: 'NPCにスパイされた回数',
robbed: 'NPCに残骸を回収された回数',
lostToNPC: 'NPCに奪われた残骸資源総量'
}
}
}

View File

@@ -81,7 +81,8 @@ export default {
officers: '장교',
simulator: '시뮬레이터',
galaxy: '은하계',
diplomacy: 'Diplomacy',
diplomacy: '외교',
achievements: '업적',
messages: '메시지',
settings: '설정',
gm: 'GM'
@@ -109,6 +110,14 @@ export default {
hour: '시간',
noEnergy: '에너지 부족'
},
energy: {
lowWarning: '에너지 부족! 자원 생산 중단!',
severeWarning: '에너지 부족! 자원 생산 중단!',
criticalWarning: '에너지 부족! 자원 생산 중단!',
noProduction: '에너지 부족! 자원 생산 중단!',
deficitDetail: '에너지 부족: {deficit}, 발전소를 더 건설하세요',
buildSolarPlant: '발전소 건설'
},
planet: {
planet: '행성',
moon: '위성',
@@ -420,6 +429,7 @@ export default {
shipyardView: {
title: '조선소',
fleetStorage: '함대 저장소',
owned: '보유',
attack: '공격력',
missileAttack: '미사일 공격',
shield: '실드',
@@ -484,8 +494,10 @@ export default {
available: '사용 가능',
all: '전체',
targetCoordinates: '목표 좌표',
targetType: '목표 유형',
planet: '행성',
moon: '달',
galaxy: '은하계',
diplomacy: 'Diplomacy',
system: '행성계',
position: '위치',
missionType: '임무 유형',
@@ -529,7 +541,30 @@ export default {
noDeathstar: '파괴 임무를 위해 데스스타가 필요합니다',
giftMode: '선물 모드',
giftModeDescription: '자원을 선물로 보내기',
estimatedReputationGain: '예상 평판 획득'
estimatedReputationGain: '예상 평판 획득',
// 함대 프리셋
fleetPresets: '함대 프리셋',
fleetPresetsDescription: '자주 사용하는 함대 구성을 저장하여 빠르게 파견 (최대 3개)',
savePreset: '프리셋 저장',
noPresets: '프리셋 없음, 함대를 선택하고 "프리셋 저장"을 클릭',
shipTypes: '종류의 함선',
editPreset: '프리셋 내용 편집',
renamePreset: '이름 변경',
deletePreset: '프리셋 삭제',
editingPresetHint: '프리셋 편집 중, 함대 구성을 변경하고 "저장"을 클릭',
presetLimitReached: '프리셋 한도 도달',
presetLimitReachedMessage: '최대 {max}개의 프리셋만 허용',
presetError: '저장 실패',
presetNoShips: '최소 하나의 함선을 선택하세요',
presetDefaultName: '프리셋 {number}',
savePresetTitle: '함대 프리셋 저장',
savePresetDescription: '이 함대 구성에 이름 지정',
renamePresetTitle: '프리셋 이름 변경',
renamePresetDescription: '새 프리셋 이름 입력',
presetName: '프리셋 이름',
presetNamePlaceholder: '프리셋 이름 입력',
deletePresetTitle: '프리셋 삭제',
deletePresetMessage: '프리셋 "{name}"을(를) 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.'
},
officersView: {
title: '장교',
@@ -569,7 +604,6 @@ export default {
title: '은하계',
selectCoordinates: '좌표 선택',
galaxy: '은하계',
diplomacy: 'Diplomacy',
selectGalaxy: '은하계 선택',
system: '행성계',
selectSystem: '행성계 선택',
@@ -728,14 +762,29 @@ export default {
missionReports: {
transportSuccess: '수송 임무가 성공적으로 완료되었습니다',
transportFailed: '수송 임무 실패',
transportFailedTargetNotFound: '수송 실패: 목표 행성이 존재하지 않습니다',
transportFailedGiftRejected: '수송 실패: 선물이 거절되었습니다',
colonizeSuccess: '식민 임무 성공, 새로운 행성이 건설되었습니다',
colonizeFailed: '식민 임무 실패',
colonizeFailedOccupied: '식민 실패: 목표 위치가 이미 다른 행성에 의해 점유되어 있습니다',
colonizeFailedMaxColonies: '식민 실패: 최대 식민지 수에 도달했습니다. 천체물리학을 연구하여 한도를 늘리세요.',
spySuccess: '정찰 임무가 성공적으로 완료되었습니다',
spyFailed: '정찰 임무 실패',
spyFailedTargetNotFound: '정찰 실패: 목표 행성이 존재하지 않습니다',
deploySuccess: '배치 임무가 성공적으로 완료되었습니다',
deployFailed: '배치 임무 실패',
deployFailedTargetNotFound: '배치 실패: 목표 행성이 존재하지 않습니다',
deployFailedNotOwnPlanet: '배치 실패: 목표 행성이 당신의 것이 아닙니다',
recycleSuccess: '회수 임무가 성공적으로 완료되었습니다',
recycleFailed: '회수 임무 실패, 목표 위치에 잔해가 없습니다',
recycleFailedNoDebris: '회수 실패: 목표 위치에 잔해장이 없습니다',
recycleFailedDebrisEmpty: '회수 실패: 잔해장이 이미 비어 있습니다',
destroySuccess: '행성 파괴 임무가 성공적으로 실행되었습니다',
destroyFailed: '행성 파괴 임무 실패',
destroyFailedTargetNotFound: '파괴 실패: 목표 행성이 존재하지 않습니다',
destroyFailedOwnPlanet: '파괴 실패: 자신의 행성을 파괴할 수 없습니다',
destroyFailedNoDeathstar: '파괴 실패: 임무를 수행할 데스스타가 없습니다',
destroyFailedChance: '파괴 실패: 확률 판정 실패 (성공률: {chance}%)',
missileAttackSuccess: '미사일 공격 성공',
missileAttackFailed: '미사일 공격 실패, 목표 행성이 존재하지 않습니다',
missileAttackIntercepted: '모든 미사일이 요격되었습니다',
@@ -948,11 +997,13 @@ export default {
missionType: {
spy: '정찰',
attack: '공격',
recycle: '회수',
unknown: '알 수 없음'
},
warning: {
spy: '적 정찰 접근 중!',
attack: '적 공격 접근 중!',
recycle: '적이 근처에서 잔해를 회수 중!',
unknown: '적 함대 접근 중!'
}
},
@@ -1240,6 +1291,10 @@ export default {
title: '전투 시뮬레이터',
message: '공격 전에 전투 결과를 시뮬레이션하세요. 양측 함대와 기술 레벨을 입력하여 승패와 손실을 예측.'
},
achievements: {
title: '업적 시스템',
message: '게임 목표를 완료하여 업적을 해제하고 암흑 물질 보상을 획득하세요! 업적은 여러 등급이 있으며, 더 높은 도전으로 더 좋은 보상을 받으세요.'
},
settings: {
title: '설정',
message: '여기서 게임 데이터 관리, 알림 설정, 저장 내보내기/가져오기가 가능합니다. 정기적으로 백업하세요!'
@@ -1248,5 +1303,102 @@ export default {
title: 'GM 관리 패널',
message: 'GM 모드에서는 자원, 건물, 기술 레벨을 빠르게 수정할 수 있습니다. 테스트나 전체 게임 콘텐츠 체험에 사용.'
}
},
achievements: {
title: '업적',
unlocked: '업적 해제',
progress: '진행도',
nextTier: '다음 등급',
maxTierReached: '최고 등급 달성',
tiers: {
bronze: '브론즈',
silver: '실버',
gold: '골드',
platinum: '플래티넘',
diamond: '다이아몬드'
},
categories: {
resource: '자원',
building: '건설',
combat: '전투',
mission: '임무',
diplomacy: '외교'
},
names: {
metalCollector: '금속 수집가',
crystalCollector: '크리스탈 수집가',
deuteriumCollector: '중수소 수집가',
darkMatterCollector: '암흑 물질 수집가',
resourceConsumer: '자원 소비자',
masterBuilder: '마스터 빌더',
researcher: '연구원',
shipwright: '조선공',
fortifier: '요새화자',
warmonger: '전쟁광',
conqueror: '정복자',
defender: '방어자',
fleetDestroyer: '함대 파괴자',
debrisCreator: '잔해 생성자',
fleetSacrifice: '함대 희생자',
defenseSacrifice: '방어 희생자',
pilot: '파일럿',
transporter: '운송가',
cargoMaster: '화물 마스터',
colonizer: '식민자',
spy: '스파이 마스터',
deployer: '배치자',
explorer: '탐험가',
luckyExplorer: '행운의 탐험가',
recycler: '재활용자',
scavenger: '스캐빈저',
destroyer: '파괴자',
fuelBurner: '연료 소비자',
diplomat: '외교관',
nemesis: '숙적',
generous: '관대한 자',
philanthropist: '자선가',
target: '표적',
watched: '감시 중',
robbed: '약탈당함',
lostToNPC: 'NPC에게 손실'
},
descriptions: {
metalCollector: '총 금속 생산량',
crystalCollector: '총 크리스탈 생산량',
deuteriumCollector: '총 중수소 생산량',
darkMatterCollector: '총 암흑 물질 생산량',
resourceConsumer: '총 자원 소비량',
masterBuilder: '건물 업그레이드 총 횟수',
researcher: '연구 완료 총 횟수',
shipwright: '함선 생산 총 수량',
fortifier: '방어 시설 건설 총 수량',
warmonger: '공격 발동 횟수',
conqueror: '공격 승리 횟수',
defender: '방어 성공 횟수',
fleetDestroyer: '방어에서 파괴한 적 함대 수',
debrisCreator: '전투에서 생성된 잔해 총량',
fleetSacrifice: '손실된 함대 총 수',
defenseSacrifice: '방어에서 손실된 방어 시설 수',
pilot: '비행 임무 총 횟수',
transporter: '운송 임무 총 횟수',
cargoMaster: '운송 자원 총량',
colonizer: '식민화한 행성 수',
spy: '스파이 임무 완료 횟수',
deployer: '배치 임무 완료 횟수',
explorer: '탐험 총 횟수',
luckyExplorer: '탐험 성공 횟수',
recycler: '재활용 임무 총 횟수',
scavenger: '재활용 자원 총량',
destroyer: '파괴한 행성 수',
fuelBurner: '연료 소비 총량',
diplomat: '우호적인 NPC 수',
nemesis: '적대적인 NPC 수',
generous: '선물 보낸 횟수',
philanthropist: '선물한 자원 총량',
target: 'NPC에게 공격당한 횟수',
watched: 'NPC에게 정찰당한 횟수',
robbed: 'NPC에게 잔해 회수당한 횟수',
lostToNPC: 'NPC에게 빼앗긴 잔해 자원 총량'
}
}
}

View File

@@ -81,7 +81,8 @@ export default {
officers: 'Офицеры',
simulator: 'Симулятор',
galaxy: 'Галактика',
diplomacy: 'Diplomacy',
diplomacy: 'Дипломатия',
achievements: 'Достижения',
messages: 'Сообщения',
settings: 'Настройки',
gm: 'GM'
@@ -109,6 +110,14 @@ export default {
hour: 'час',
noEnergy: 'Нет энергии'
},
energy: {
lowWarning: 'Дефицит энергии! Производство ресурсов остановлено!',
severeWarning: 'Дефицит энергии! Производство ресурсов остановлено!',
criticalWarning: 'Дефицит энергии! Производство ресурсов остановлено!',
noProduction: 'Дефицит энергии! Производство ресурсов остановлено!',
deficitDetail: 'Дефицит энергии: {deficit}, постройте больше электростанций',
buildSolarPlant: 'Построить электростанцию'
},
planet: {
planet: 'Планета',
moon: 'Луна',
@@ -422,6 +431,7 @@ export default {
shipyardView: {
title: 'Верфь',
fleetStorage: 'Хранилище флота',
owned: 'В наличии',
attack: 'Атака',
missileAttack: 'Ракетная атака',
shield: 'Щит',
@@ -487,8 +497,10 @@ export default {
available: 'Доступно',
all: 'Все',
targetCoordinates: 'Целевые координаты',
targetType: 'Тип цели',
planet: 'Планета',
moon: 'Луна',
galaxy: 'Галактика',
diplomacy: 'Diplomacy',
system: 'Система',
position: 'Позиция',
missionType: 'Тип миссии',
@@ -532,7 +544,30 @@ export default {
noDeathstar: 'Для миссии разрушения требуется Звезда Смерти',
giftMode: 'Режим подарка',
giftModeDescription: 'Отправить ресурсы в подарок',
estimatedReputationGain: 'Ожидаемый прирост репутации'
estimatedReputationGain: 'Ожидаемый прирост репутации',
// Шаблоны флота
fleetPresets: 'Шаблоны флота',
fleetPresetsDescription: 'Сохраняйте часто используемые конфигурации флота (макс. 3)',
savePreset: 'Сохранить шаблон',
noPresets: 'Нет шаблонов, выберите флот и нажмите "Сохранить шаблон"',
shipTypes: 'типов кораблей',
editPreset: 'Редактировать содержимое шаблона',
renamePreset: 'Переименовать',
deletePreset: 'Удалить шаблон',
editingPresetHint: 'Редактирование шаблона, измените конфигурацию и нажмите "Сохранить"',
presetLimitReached: 'Достигнут лимит шаблонов',
presetLimitReachedMessage: 'Максимум {max} шаблонов',
presetError: 'Ошибка сохранения',
presetNoShips: 'Выберите хотя бы один корабль',
presetDefaultName: 'Шаблон {number}',
savePresetTitle: 'Сохранить шаблон флота',
savePresetDescription: 'Назовите эту конфигурацию флота',
renamePresetTitle: 'Переименовать шаблон',
renamePresetDescription: 'Введите новое имя шаблона',
presetName: 'Имя шаблона',
presetNamePlaceholder: 'Введите имя шаблона',
deletePresetTitle: 'Удалить шаблон',
deletePresetMessage: 'Вы уверены, что хотите удалить шаблон "{name}"? Это действие нельзя отменить.'
},
officersView: {
title: 'Офицеры',
@@ -572,7 +607,6 @@ export default {
title: 'Галактика',
selectCoordinates: 'Выбрать координаты',
galaxy: 'Галактика',
diplomacy: 'Diplomacy',
selectGalaxy: 'Выбрать галактику',
system: 'Система',
selectSystem: 'Выбрать систему',
@@ -734,14 +768,29 @@ export default {
missionReports: {
transportSuccess: 'Миссия транспортировки успешно завершена',
transportFailed: 'Миссия транспортировки провалена',
transportFailedTargetNotFound: 'Транспортировка провалена: Целевая планета не существует',
transportFailedGiftRejected: 'Транспортировка провалена: Подарок был отклонён',
colonizeSuccess: 'Миссия колонизации успешна, новая планета создана',
colonizeFailed: 'Миссия колонизации провалена',
colonizeFailedOccupied: 'Колонизация провалена: Целевая позиция уже занята другой планетой',
colonizeFailedMaxColonies: 'Колонизация провалена: Достигнуто максимальное количество колоний. Исследуйте Астрофизику для увеличения лимита.',
spySuccess: 'Миссия шпионажа успешно завершена',
spyFailed: 'Миссия шпионажа провалена',
spyFailedTargetNotFound: 'Шпионаж провален: Целевая планета не существует',
deploySuccess: 'Миссия размещения успешно завершена',
deployFailed: 'Миссия размещения провалена',
deployFailedTargetNotFound: 'Размещение провалено: Целевая планета не существует',
deployFailedNotOwnPlanet: 'Размещение провалено: Целевая планета не принадлежит вам',
recycleSuccess: 'Миссия переработки успешно завершена',
recycleFailed: 'Миссия переработки провалена, нет обломков в целевой позиции',
recycleFailedNoDebris: 'Переработка провалена: Нет поля обломков в целевой позиции',
recycleFailedDebrisEmpty: 'Переработка провалена: Поле обломков уже пусто',
destroySuccess: 'Миссия уничтожения планеты успешно выполнена',
destroyFailed: 'Миссия уничтожения планеты провалена',
destroyFailedTargetNotFound: 'Уничтожение провалено: Целевая планета не существует',
destroyFailedOwnPlanet: 'Уничтожение провалено: Нельзя уничтожить собственную планету',
destroyFailedNoDeathstar: 'Уничтожение провалено: Нет Звезды Смерти для выполнения миссии',
destroyFailedChance: 'Уничтожение провалено: Проверка вероятности не удалась (Шанс успеха: {chance}%)',
expeditionResources: 'Экспедиция обнаружила ресурсы!',
expeditionDarkMatter: 'Экспедиция обнаружила тёмную материю!',
expeditionFleet: 'Экспедиция обнаружила заброшенные корабли!',
@@ -1256,6 +1305,10 @@ export default {
title: 'Симулятор боя',
message: 'Симулируйте результаты боя перед атакой. Введите флоты и уровни технологий для прогноза победы, потерь и добычи.'
},
achievements: {
title: 'Система достижений',
message: 'Выполняйте игровые цели для разблокировки достижений и получения наград в виде тёмной материи! Достижения имеют несколько уровней - стремитесь к более высоким целям для лучших наград.'
},
settings: {
title: 'Настройки',
message: 'Управляйте игровыми данными, уведомлениями, импортом/экспортом сохранений. Регулярно создавайте резервные копии!'
@@ -1265,5 +1318,102 @@ export default {
message:
'Режим ГМ позволяет быстро изменять ресурсы, здания и уровни технологий. Используйте для тестирования или полного доступа к контенту.'
}
},
achievements: {
title: 'Достижения',
unlocked: 'Достижение разблокировано',
progress: 'Прогресс',
nextTier: 'Следующий уровень',
maxTierReached: 'Достигнут максимальный уровень',
tiers: {
bronze: 'Бронза',
silver: 'Серебро',
gold: 'Золото',
platinum: 'Платина',
diamond: 'Алмаз'
},
categories: {
resource: 'Ресурсы',
building: 'Строительство',
combat: 'Бой',
mission: 'Миссии',
diplomacy: 'Дипломатия'
},
names: {
metalCollector: 'Коллекционер металла',
crystalCollector: 'Коллекционер кристаллов',
deuteriumCollector: 'Коллекционер дейтерия',
darkMatterCollector: 'Коллекционер тёмной материи',
resourceConsumer: 'Потребитель ресурсов',
masterBuilder: 'Мастер-строитель',
researcher: 'Исследователь',
shipwright: 'Кораблестроитель',
fortifier: 'Укрепитель',
warmonger: 'Поджигатель войны',
conqueror: 'Завоеватель',
defender: 'Защитник',
fleetDestroyer: 'Уничтожитель флота',
debrisCreator: 'Создатель обломков',
fleetSacrifice: 'Жертва флота',
defenseSacrifice: 'Жертва обороны',
pilot: 'Пилот',
transporter: 'Транспортёр',
cargoMaster: 'Мастер груза',
colonizer: 'Колонизатор',
spy: 'Мастер шпионажа',
deployer: 'Размещатель',
explorer: 'Исследователь',
luckyExplorer: 'Счастливый исследователь',
recycler: 'Переработчик',
scavenger: 'Мусорщик',
destroyer: 'Уничтожитель',
fuelBurner: 'Сжигатель топлива',
diplomat: 'Дипломат',
nemesis: 'Заклятый враг',
generous: 'Щедрый',
philanthropist: 'Филантроп',
target: 'Мишень',
watched: 'Под наблюдением',
robbed: 'Ограблен',
lostToNPC: 'Потеряно НПС'
},
descriptions: {
metalCollector: 'Всего добыто металла',
crystalCollector: 'Всего добыто кристаллов',
deuteriumCollector: 'Всего добыто дейтерия',
darkMatterCollector: 'Всего добыто тёмной материи',
resourceConsumer: 'Всего потреблено ресурсов',
masterBuilder: 'Всего улучшений зданий',
researcher: 'Всего завершённых исследований',
shipwright: 'Всего произведённых кораблей',
fortifier: 'Всего построенной обороны',
warmonger: 'Всего начатых атак',
conqueror: 'Всего выигранных атак',
defender: 'Всего успешных защит',
fleetDestroyer: 'Вражеский флот уничтожен в обороне',
debrisCreator: 'Всего обломков созданных в боях',
fleetSacrifice: 'Всего потерянного флота',
defenseSacrifice: 'Всего потерянной обороны',
pilot: 'Всего лётных миссий',
transporter: 'Всего транспортных миссий',
cargoMaster: 'Всего перевезённых ресурсов',
colonizer: 'Колонизировано планет',
spy: 'Завершённых шпионских миссий',
deployer: 'Завершённых миссий размещения',
explorer: 'Всего экспедиций',
luckyExplorer: 'Успешных экспедиций',
recycler: 'Всего миссий переработки',
scavenger: 'Всего переработанных ресурсов',
destroyer: 'Уничтожено планет',
fuelBurner: 'Всего израсходовано топлива',
diplomat: 'Количество дружественных НПС',
nemesis: 'Количество враждебных НПС',
generous: 'Всего отправленных подарков',
philanthropist: 'Всего подаренных ресурсов',
target: 'Раз атакован НПС',
watched: 'Раз шпионил НПС',
robbed: 'Раз НПС собрал обломки',
lostToNPC: 'Всего обломков потеряно НПС'
}
}
}

View File

@@ -81,6 +81,7 @@ export default {
simulator: '模拟',
galaxy: '星系',
diplomacy: '外交',
achievements: '成就',
messages: '消息',
settings: '设置',
gm: 'GM'
@@ -108,6 +109,14 @@ export default {
hour: '小时',
noEnergy: '电力不足'
},
energy: {
lowWarning: '电力不足,资源生产已停止!',
severeWarning: '电力不足,资源生产已停止!',
criticalWarning: '电力不足,资源生产已停止!',
noProduction: '电力不足,资源生产已停止!',
deficitDetail: '电力缺口: {deficit},请建造更多电站',
buildSolarPlant: '建造电站'
},
planet: {
planet: '星球',
moon: '月球',
@@ -416,6 +425,7 @@ export default {
shipyardView: {
title: '船坞',
fleetStorage: '舰队仓储',
owned: '拥有',
attack: '攻击力',
shield: '护盾',
speed: '速度',
@@ -475,6 +485,9 @@ export default {
available: '可用',
all: '全部',
targetCoordinates: '目标坐标',
targetType: '目标类型',
planet: '行星',
moon: '月球',
galaxy: '银河系',
system: '星系',
position: '位置',
@@ -520,7 +533,47 @@ export default {
noDeathstar: '需要死星才能执行毁灭任务',
giftMode: '赠送模式',
giftModeDescription: '将资源作为礼物赠送给',
estimatedReputationGain: '预计好感度增加'
estimatedReputationGain: '预计好感度增加',
// 舰队预设
fleetPresets: '舰队预设',
fleetPresetsDescription: '保存常用的舰队配置快速派遣最多3个',
savePreset: '保存预设',
noPresets: '暂无预设,选择舰队后点击"保存预设"创建',
shipTypes: '种舰船',
editPreset: '编辑预设内容',
renamePreset: '重命名',
deletePreset: '删除预设',
editingPresetHint: '正在编辑预设,修改舰队配置后点击"保存"更新预设内容',
presetLimitReached: '预设数量已满',
presetLimitReachedMessage: '最多只能保存 {max} 个预设',
presetError: '保存失败',
presetNoShips: '请先选择至少一艘舰船',
presetDefaultName: '预设 {number}',
savePresetTitle: '保存舰队预设',
savePresetDescription: '为这个舰队配置命名',
renamePresetTitle: '重命名预设',
renamePresetDescription: '输入新的预设名称',
presetName: '预设名称',
presetNamePlaceholder: '输入预设名称',
deletePresetTitle: '删除预设',
deletePresetMessage: '确定要删除预设"{name}"吗?此操作不可撤销。',
// 跳跃门
jumpGate: '跳跃门',
jumpGateDescription: '使用跳跃门瞬间传送舰队到其他有跳跃门的月球',
jumpGateNotAvailable: '跳跃门不可用',
jumpGateRequiresMoon: '跳跃门只能在月球上使用',
jumpGateNotBuilt: '当前月球没有建造跳跃门',
jumpGateCooldown: '跳跃门冷却中',
jumpGateCooldownRemaining: '剩余冷却时间',
jumpGateReady: '跳跃门就绪',
jumpGateSelectTarget: '选择目标月球',
jumpGateNoTargetMoons: '没有可用的目标月球(需要有跳跃门且冷却完成)',
jumpGateSelectFleet: '选择传送舰队',
jumpGateTransfer: '传送舰队',
jumpGateSuccess: '跳跃门传送成功',
jumpGateSuccessMessage: '舰队已瞬间传送到 {target}',
jumpGateFailed: '跳跃门传送失败',
jumpGateFailedMessage: '请检查跳跃门状态和舰队配置'
},
officersView: {
title: '军官',
@@ -602,7 +655,26 @@ export default {
colonizePlanetMessage: '确定要殖民位置 [{coordinates}] 吗?\n\n请前往舰队页面派遣殖民船。',
recyclePlanetMessage: '确定要回收位置 [{coordinates}] 的残骸吗?\n\n请前往舰队页面派遣回收船。',
giftPlanetMessage: '确定要向星球 [{coordinates}] 赠送资源吗?\n\n请前往舰队页面选择运输船并装载资源。',
npcPlanetName: '{name}的星球'
npcPlanetName: '{name}的星球',
// 传感器阵列扫描
phalanxScan: '传感器扫描',
phalanxScanTitle: '传感器阵列扫描',
phalanxScanDescription: '扫描星球 [{coordinates}] 的舰队活动',
phalanxNoMoon: '需要有传感器阵列的月球才能扫描',
phalanxOutOfRange: '目标超出扫描范围',
phalanxRange: '扫描范围',
phalanxCost: '扫描消耗',
phalanxNoFleets: '未检测到舰队活动',
phalanxFleetDetected: '检测到 {count} 支舰队',
phalanxMission: '任务',
phalanxOrigin: '出发地',
phalanxDestination: '目的地',
phalanxArrival: '到达时间',
phalanxReturn: '返回时间',
phalanxStatus: '状态',
phalanxStatusOutbound: '前往中',
phalanxStatusReturning: '返回中',
phalanxInsufficientDeuterium: '氘不足'
},
messagesView: {
title: '消息中心',
@@ -718,14 +790,29 @@ export default {
missionReports: {
transportSuccess: '运输任务成功完成',
transportFailed: '运输任务失败',
transportFailedTargetNotFound: '运输失败:目标星球不存在',
transportFailedGiftRejected: '运输失败:礼物被拒绝',
colonizeSuccess: '殖民任务成功,新星球已建立',
colonizeFailed: '殖民任务失败',
colonizeFailedOccupied: '殖民失败:目标位置已被其他星球占用',
colonizeFailedMaxColonies: '殖民失败:已达到殖民地数量上限。研究天体物理学可增加上限。',
spySuccess: '侦查任务成功完成',
spyFailed: '侦查任务失败',
spyFailedTargetNotFound: '侦查失败:目标星球不存在',
deploySuccess: '部署任务成功完成',
deployFailed: '部署任务失败',
deployFailedTargetNotFound: '部署失败:目标星球不存在',
deployFailedNotOwnPlanet: '部署失败:目标星球不属于你',
recycleSuccess: '回收任务成功完成',
recycleFailed: '回收任务失败,目标位置没有残骸',
recycleFailedNoDebris: '回收失败:目标位置没有残骸场',
recycleFailedDebrisEmpty: '回收失败:残骸场已被清空',
destroySuccess: '行星毁灭任务成功执行',
destroyFailed: '行星毁灭任务失败',
destroyFailedTargetNotFound: '毁灭失败:目标星球不存在',
destroyFailedOwnPlanet: '毁灭失败:无法摧毁自己的星球',
destroyFailedNoDeathstar: '毁灭失败:没有死星执行任务',
destroyFailedChance: '毁灭失败:概率判定失败(成功率:{chance}%',
missileAttackSuccess: '导弹攻击成功',
missileAttackFailed: '导弹攻击失败,目标星球不存在',
missileAttackIntercepted: '所有导弹被拦截',
@@ -943,11 +1030,13 @@ export default {
missionType: {
spy: '侦查',
attack: '攻击',
recycle: '回收',
unknown: '未知'
},
warning: {
spy: '敌方侦查即将到达!',
attack: '敌方攻击即将到达!',
recycle: '敌方正在回收你附近的残骸!',
unknown: '敌方舰队即将到达!'
}
},
@@ -1336,6 +1425,10 @@ export default {
title: '战斗模拟器',
message: '在发动攻击前模拟战斗结果。输入双方舰队和科技等级,预测胜负和损失。'
},
achievements: {
title: '成就系统',
message: '完成各类游戏目标解锁成就,获得暗物质奖励!成就分为多个等级,挑战更高难度获得更丰厚的奖励。'
},
settings: {
title: '设置',
message: '在这里管理游戏数据、调整通知设置、导出/导入存档。记得定期备份你的进度!'
@@ -1344,5 +1437,102 @@ export default {
title: 'GM管理面板',
message: 'GM模式可以快速修改资源、建筑、科技等级。用于测试或体验完整游戏内容。'
}
},
achievements: {
title: '成就',
unlocked: '成就解锁',
progress: '进度',
nextTier: '下一等级',
maxTierReached: '已达最高等级',
tiers: {
bronze: '青铜',
silver: '白银',
gold: '黄金',
platinum: '铂金',
diamond: '钻石'
},
categories: {
resource: '资源',
building: '建造',
combat: '战斗',
mission: '任务',
diplomacy: '外交'
},
names: {
metalCollector: '金属收藏家',
crystalCollector: '晶体收藏家',
deuteriumCollector: '重氢收藏家',
darkMatterCollector: '暗物质收藏家',
resourceConsumer: '资源消耗者',
masterBuilder: '建造大师',
researcher: '科学家',
shipwright: '造船匠',
fortifier: '防御专家',
warmonger: '好战者',
conqueror: '征服者',
defender: '防御者',
fleetDestroyer: '舰队毁灭者',
debrisCreator: '残骸制造者',
fleetSacrifice: '舰队牺牲者',
defenseSacrifice: '防御牺牲者',
pilot: '飞行员',
transporter: '运输专家',
cargoMaster: '货运大师',
colonizer: '殖民者',
spy: '间谍大师',
deployer: '部署专家',
explorer: '探险家',
luckyExplorer: '幸运探险家',
recycler: '回收专家',
scavenger: '拾荒者',
destroyer: '毁灭者',
fuelBurner: '燃料消耗者',
diplomat: '外交官',
nemesis: '宿敌',
generous: '慷慨者',
philanthropist: '慈善家',
target: '目标',
watched: '被监视',
robbed: '被抢夺',
lostToNPC: '资源损失者'
},
descriptions: {
metalCollector: '累计生产金属',
crystalCollector: '累计生产晶体',
deuteriumCollector: '累计生产重氢',
darkMatterCollector: '累计生产暗物质',
resourceConsumer: '累计消耗资源',
masterBuilder: '累计升级建筑次数',
researcher: '累计完成研究次数',
shipwright: '累计生产舰船数量',
fortifier: '累计建造防御设施',
warmonger: '发起攻击次数',
conqueror: '攻击胜利次数',
defender: '防御成功次数',
fleetDestroyer: '防御中消灭的敌方舰队数量',
debrisCreator: '战斗中产生的残骸资源总量',
fleetSacrifice: '损失的舰队总数',
defenseSacrifice: '防御中损失的防御设施数量',
pilot: '完成飞行任务次数',
transporter: '运输任务次数',
cargoMaster: '运输资源总量',
colonizer: '成功殖民星球次数',
spy: '执行侦查任务次数',
deployer: '执行部署任务次数',
explorer: '远征总次数',
luckyExplorer: '远征成功次数',
recycler: '回收任务次数',
scavenger: '回收资源总量',
destroyer: '摧毁星球次数',
fuelBurner: '消耗燃料总量',
diplomat: '友好NPC数量',
nemesis: '敌对NPC数量',
generous: '送礼次数',
philanthropist: '赠送资源总量',
target: '被NPC攻击次数',
watched: '被NPC侦查次数',
robbed: '被NPC回收残骸次数',
lostToNPC: '被NPC回收的残骸资源总量'
}
}
}

View File

@@ -81,9 +81,11 @@ export default {
officers: '軍官',
simulator: '模擬',
galaxy: '星系',
diplomacy: 'Diplomacy',
diplomacy: '外交',
achievements: '成就',
messages: '訊息',
settings: '設定',
guide: '遊戲指南',
gm: 'GM'
},
sidebar: {
@@ -109,6 +111,14 @@ export default {
hour: '小時',
noEnergy: '電力不足'
},
energy: {
lowWarning: '電力不足,資源生產已停止!',
severeWarning: '電力不足,資源生產已停止!',
criticalWarning: '電力不足,資源生產已停止!',
noProduction: '電力不足,資源生產已停止!',
deficitDetail: '電力缺口: {deficit},請建造更多電站',
buildSolarPlant: '建造電站'
},
planet: {
planet: '星球',
moon: '月球',
@@ -422,6 +432,7 @@ export default {
shipyardView: {
title: '船塢',
fleetStorage: '艦隊倉儲',
owned: '擁有',
attack: '攻擊力',
missileAttack: '導彈攻擊',
shield: '護盾',
@@ -486,8 +497,10 @@ export default {
available: '可用',
all: '全部',
targetCoordinates: '目標座標',
targetType: '目標類型',
planet: '行星',
moon: '月球',
galaxy: '銀河系',
diplomacy: 'Diplomacy',
system: '星系',
position: '位置',
missionType: '任務類型',
@@ -531,7 +544,30 @@ export default {
noDeathstar: '需要死星才能執行毀滅任務',
giftMode: '贈送模式',
giftModeDescription: '將資源作為禮物贈送給',
estimatedReputationGain: '預計好感度增加'
estimatedReputationGain: '預計好感度增加',
// 艦隊預設
fleetPresets: '艦隊預設',
fleetPresetsDescription: '儲存常用的艦隊配置快速派遣最多3個',
savePreset: '儲存預設',
noPresets: '暫無預設,選擇艦隊後點擊「儲存預設」建立',
shipTypes: '種艦船',
editPreset: '編輯預設內容',
renamePreset: '重新命名',
deletePreset: '刪除預設',
editingPresetHint: '正在編輯預設,修改艦隊配置後點擊「儲存」更新預設內容',
presetLimitReached: '預設數量已滿',
presetLimitReachedMessage: '最多只能儲存 {max} 個預設',
presetError: '儲存失敗',
presetNoShips: '請先選擇至少一艘艦船',
presetDefaultName: '預設 {number}',
savePresetTitle: '儲存艦隊預設',
savePresetDescription: '為這個艦隊配置命名',
renamePresetTitle: '重新命名預設',
renamePresetDescription: '輸入新的預設名稱',
presetName: '預設名稱',
presetNamePlaceholder: '輸入預設名稱',
deletePresetTitle: '刪除預設',
deletePresetMessage: '確定要刪除預設「{name}」嗎?此操作不可撤銷。'
},
officersView: {
title: '軍官',
@@ -571,7 +607,6 @@ export default {
title: '星系',
selectCoordinates: '選擇座標',
galaxy: '銀河系',
diplomacy: 'Diplomacy',
selectGalaxy: '選擇銀河系',
system: '星系',
selectSystem: '選擇星系',
@@ -728,14 +763,29 @@ export default {
missionReports: {
transportSuccess: '運輸任務成功完成',
transportFailed: '運輸任務失敗',
transportFailedTargetNotFound: '運輸失敗:目標星球不存在',
transportFailedGiftRejected: '運輸失敗:禮物被拒絕',
colonizeSuccess: '殖民任務成功,新星球已建立',
colonizeFailed: '殖民任務失敗',
colonizeFailedOccupied: '殖民失敗:目標位置已被其他星球佔用',
colonizeFailedMaxColonies: '殖民失敗:已達到殖民地數量上限。研究天體物理學可增加上限。',
spySuccess: '偵查任務成功完成',
spyFailed: '偵查任務失敗',
spyFailedTargetNotFound: '偵查失敗:目標星球不存在',
deploySuccess: '部署任務成功完成',
deployFailed: '部署任務失敗',
deployFailedTargetNotFound: '部署失敗:目標星球不存在',
deployFailedNotOwnPlanet: '部署失敗:目標星球不屬於你',
recycleSuccess: '回收任務成功完成',
recycleFailed: '回收任務失敗,目標位置沒有殘骸',
recycleFailedNoDebris: '回收失敗:目標位置沒有殘骸場',
recycleFailedDebrisEmpty: '回收失敗:殘骸場已被清空',
destroySuccess: '行星毀滅任務成功執行',
destroyFailed: '行星毀滅任務失敗',
destroyFailedTargetNotFound: '毀滅失敗:目標星球不存在',
destroyFailedOwnPlanet: '毀滅失敗:無法摧毀自己的星球',
destroyFailedNoDeathstar: '毀滅失敗:沒有死星執行任務',
destroyFailedChance: '毀滅失敗:概率判定失敗(成功率:{chance}%',
expeditionResources: '探險隊發現了資源!',
expeditionDarkMatter: '探險隊發現了暗物質!',
expeditionFleet: '探險隊發現了廢棄的艦船!',
@@ -948,11 +998,13 @@ export default {
missionType: {
spy: '偵查',
attack: '攻擊',
recycle: '回收',
unknown: '未知'
},
warning: {
spy: '敵方偵查即將到達!',
attack: '敵方攻擊即將到達!',
recycle: '敵方正在回收你附近的殘骸!',
unknown: '敵方艦隊即將到達!'
}
},
@@ -1235,6 +1287,10 @@ export default {
title: '戰鬥模擬器',
message: '在發動攻擊前模擬戰鬥結果。輸入雙方艦隊和科技等級,預測勝負和損失。'
},
achievements: {
title: '成就系統',
message: '完成各類遊戲目標解鎖成就,獲得暗物質獎勵!成就分為多個等級,挑戰更高難度獲得更豐厚的獎勵。'
},
settings: {
title: '設置',
message: '在這裡管理遊戲數據、調整通知設置、導出/導入存檔。記得定期備份你的進度!'
@@ -1243,5 +1299,239 @@ export default {
title: 'GM管理面板',
message: 'GM模式可以快速修改資源、建築、科技等級。用於測試或體驗完整遊戲內容。'
}
},
achievements: {
title: '成就',
unlocked: '成就解鎖',
progress: '進度',
nextTier: '下一等級',
maxTierReached: '已達最高等級',
tiers: {
bronze: '青銅',
silver: '白銀',
gold: '黃金',
platinum: '白金',
diamond: '鑽石'
},
categories: {
resource: '資源',
building: '建造',
combat: '戰鬥',
mission: '任務',
diplomacy: '外交'
},
names: {
metalCollector: '金屬收藏家',
crystalCollector: '晶體收藏家',
deuteriumCollector: '重氫收藏家',
darkMatterCollector: '暗物質收藏家',
resourceConsumer: '資源消耗者',
masterBuilder: '建造大師',
researcher: '科學家',
shipwright: '造船匠',
fortifier: '防禦專家',
warmonger: '好戰者',
conqueror: '征服者',
defender: '防禦者',
fleetDestroyer: '艦隊毀滅者',
debrisCreator: '殘骸製造者',
fleetSacrifice: '艦隊犧牲者',
defenseSacrifice: '防禦犧牲者',
pilot: '飛行員',
transporter: '運輸專家',
cargoMaster: '貨運大師',
colonizer: '殖民者',
spy: '間諜大師',
deployer: '部署專家',
explorer: '探險家',
luckyExplorer: '幸運探險家',
recycler: '回收專家',
scavenger: '拾荒者',
destroyer: '毀滅者',
fuelBurner: '燃料消耗者',
diplomat: '外交官',
nemesis: '宿敵',
generous: '慷慨者',
philanthropist: '慈善家',
target: '目標',
watched: '被監視',
robbed: '被搶奪',
lostToNPC: '資源損失者'
},
descriptions: {
metalCollector: '累計生產金屬',
crystalCollector: '累計生產晶體',
deuteriumCollector: '累計生產重氫',
darkMatterCollector: '累計生產暗物質',
resourceConsumer: '累計消耗資源',
masterBuilder: '累計升級建築次數',
researcher: '累計完成研究次數',
shipwright: '累計生產艦船數量',
fortifier: '累計建造防禦設施',
warmonger: '發起攻擊次數',
conqueror: '攻擊勝利次數',
defender: '防禦成功次數',
fleetDestroyer: '防禦中消滅的敵方艦隊數量',
debrisCreator: '戰鬥中產生的殘骸資源總量',
fleetSacrifice: '損失的艦隊總數',
defenseSacrifice: '防禦中損失的防禦設施數量',
pilot: '完成飛行任務次數',
transporter: '運輸任務次數',
cargoMaster: '運輸資源總量',
colonizer: '成功殖民星球次數',
spy: '執行偵查任務次數',
deployer: '執行部署任務次數',
explorer: '遠征總次數',
luckyExplorer: '遠征成功次數',
recycler: '回收任務次數',
scavenger: '回收資源總量',
destroyer: '摧毀星球次數',
fuelBurner: '消耗燃料總量',
diplomat: '友好NPC數量',
nemesis: '敵對NPC數量',
generous: '送禮次數',
philanthropist: '贈送資源總量',
target: '被NPC攻擊次數',
watched: '被NPC偵查次數',
robbed: '被NPC回收殘骸次數',
lostToNPC: '被NPC回收的殘骸資源總量'
}
},
guide: {
title: '遊戲指南',
tableOfContents: '目錄',
quickstart: {
title: '新手入門',
description: '快速了解遊戲核心機制,開始你的星際征程',
step1: {
title: '建造資源礦場',
content1: '資源是發展的基礎。首先建造金屬礦、晶體礦和重氫合成器。',
item1: '金屬礦 - 產出金屬,是最基礎的資源',
item2: '晶體礦 - 產出晶體,用於高級建築和艦船',
item3: '重氫合成器 - 產出重氫,用於燃料和高級科技',
item4: '建議優先發展金屬礦,其次晶體礦'
},
step2: {
title: '確保能源供應',
content1: '礦場需要電力才能運作。建造太陽能電站提供電力。',
item1: '太陽能電站 - 提供基礎電力,無需消耗資源',
item2: '核聚變反應堆 - 後期可用,消耗重氫但產電量更高'
},
step3: {
title: '建設基礎設施',
content1: '設施是發展的重要支撐。',
item1: '機器人工廠 - 加速所有建築的建造速度',
item2: '船塢 - 用於建造艦船和防禦設施',
item3: '研究實驗室 - 用於研發科技'
},
step4: {
title: '研發科技',
content1: '科技可以解鎖更多內容並提供加成。',
item1: '能量技術 - 解鎖更多科技的前置',
item2: '燃燒引擎 - 提高艦船速度',
item3: '電腦技術 - 增加可同時執行的艦隊任務數'
},
step5: {
title: '建造艦隊',
content1: '艦隊是探索和戰鬥的關鍵。',
item1: '小型貨船 - 用於運輸資源',
item2: '輕型戰鬥機 - 基礎戰鬥單位',
item3: '間諜衛星 - 偵查敵人情報'
},
step6: {
title: '探索宇宙',
content1: '使用艦隊探索宇宙,獲取資源和發展。',
item1: '偵查 - 獲取敵人情報',
item2: '攻擊 - 掠奪資源',
item3: '運輸 - 在星球間轉移資源',
item4: '殖民 - 建立新的殖民地'
}
},
buildings: {
title: '建築',
description: '了解遊戲中所有建築的功能和作用',
resource: { title: '資源建築' },
energy: { title: '能源建築' },
facility: { title: '設施建築' },
storage: { title: '倉儲建築' },
moon: { title: '月球建築' }
},
research: {
title: '研究',
description: '了解所有科技的功能和解鎖條件',
basic: { title: '基礎科技' },
drive: { title: '驅動科技' },
combat: { title: '戰鬥科技' },
advanced: { title: '高級科技' }
},
ships: {
title: '艦船',
description: '了解各類艦船的屬性和用途',
combat: { title: '戰鬥艦船' },
civil: { title: '民用艦船' },
special: { title: '特殊艦船' },
stats: '攻擊: {attack} | 護盾: {shield} | 速度: {speed}',
cargo: '載貨量: {cargo}'
},
defense: {
title: '防禦',
description: '了解各類防禦設施的屬性和作用',
basic: { title: '基礎防禦' },
advanced: { title: '高級防禦' },
shields: { title: '護盾與導彈' },
stats: '攻擊: {attack} | 護盾: {shield}'
},
systems: {
title: '遊戲系統',
description: '了解遊戲的核心系統機制',
resource: {
title: '資源系統',
content1: '資源是遊戲的核心,用於建造建築、研發科技和生產艦船。',
item1: '金屬 - 最基礎的資源,產量最高',
item2: '晶體 - 用於高級建築和艦船',
item3: '重氫 - 用於燃料和高級科技',
item4: '暗物質 - 稀有資源,用於購買軍官和特殊功能'
},
combat: {
title: '戰鬥系統',
content1: '戰鬥採用回合制,每輪艦船和防禦設施都會互相攻擊。',
item1: '攻擊力 - 決定對敵方造成的傷害',
item2: '護盾值 - 吸收部分傷害',
item3: '結構值 - 單位的生命值',
item4: '速射 - 某些艦船可以在一輪內多次攻擊特定目標'
},
missions: {
title: '艦隊任務',
content1: '艦隊可以執行多種類型的任務。',
item1: '攻擊 - 攻擊敵方星球,掠奪資源',
item2: '運輸 - 在自己的星球間運送資源',
item3: '部署 - 將艦隊永久轉移到另一個星球',
item4: '殖民 - 使用殖民船建立新殖民地',
item5: '偵查 - 使用間諜衛星獲取情報',
item6: '回收 - 回收戰場殘骸中的資源'
},
diplomacy: {
title: '外交系統',
content1: 'NPC勢力具有好感度系統影響他們對你的態度。',
item1: '友好 - NPC不會主動攻擊你',
item2: '中立 - NPC可能會偵查你',
item3: '敵對 - NPC會主動攻擊你的星球'
},
achievements: {
title: '成就系統',
content1: '完成各類遊戲目標解鎖成就,獲得暗物質獎勵。',
item1: '成就分為多個等級,難度遞增',
item2: '完成成就可獲得暗物質獎勵',
item3: '成就進度會自動追蹤'
},
officers: {
title: '軍官系統',
content1: '雇傭軍官可以獲得各種加成效果。',
item1: '指揮官 - 減少建造時間',
item2: '提督 - 增加艦隊任務槽位',
item3: '工程師 - 減少防禦修復時間',
item4: '地質學家 - 增加資源產量'
}
}
}
}

View File

@@ -0,0 +1,463 @@
import {
AchievementTier,
BuildingType,
TechnologyType,
ShipType,
DefenseType,
type AchievementStats,
type AchievementProgress,
type AchievementReward,
type Player,
type Resources,
type BattleResult
} from '@/types/game'
import { ACHIEVEMENTS, ACHIEVEMENT_MAP, getNextTier, getTierIndex } from '@/config/achievementConfig'
// 初始化空的成就统计数据
export function initializeAchievementStats(): AchievementStats {
const emptyShipRecord = Object.values(ShipType).reduce((acc, type) => {
acc[type] = 0
return acc
}, {} as Record<ShipType, number>)
const emptyDefenseRecord = Object.values(DefenseType).reduce((acc, type) => {
acc[type] = 0
return acc
}, {} as Record<DefenseType, number>)
const emptyBuildingRecord = Object.values(BuildingType).reduce((acc, type) => {
acc[type] = 0
return acc
}, {} as Record<BuildingType, number>)
const emptyTechRecord = Object.values(TechnologyType).reduce((acc, type) => {
acc[type] = 0
return acc
}, {} as Record<TechnologyType, number>)
return {
// 资源统计
totalMetalProduced: 0,
totalCrystalProduced: 0,
totalDeuteriumProduced: 0,
totalDarkMatterProduced: 0,
totalMetalConsumed: 0,
totalCrystalConsumed: 0,
totalDeuteriumConsumed: 0,
totalDarkMatterConsumed: 0,
// 建造统计
buildingsUpgraded: 0,
maxBuildingLevel: { ...emptyBuildingRecord },
researchCompleted: 0,
maxTechnologyLevel: { ...emptyTechRecord },
shipsProduced: { ...emptyShipRecord },
totalShipsProduced: 0,
defensesBuilt: { ...emptyDefenseRecord },
totalDefensesBuilt: 0,
// 战斗统计
attacksLaunched: 0,
attacksWon: 0,
attacksLost: 0,
fleetLostInAttack: { ...emptyShipRecord },
totalFleetLostInAttack: 0,
debrisCreatedFromAttacks: 0,
defensesSuccessful: 0,
defensesFailed: 0,
fleetLostInDefense: { ...emptyShipRecord },
totalFleetLostInDefense: 0,
defenseLostInDefense: { ...emptyDefenseRecord },
totalDefenseLostInDefense: 0,
enemyFleetDestroyedInDefense: { ...emptyShipRecord },
totalEnemyFleetDestroyedInDefense: 0,
debrisCreatedFromDefenses: 0,
// 任务统计
totalFlightMissions: 0,
transportMissions: 0,
transportedResources: 0,
colonizations: 0,
spyMissions: 0,
deployments: 0,
expeditionsTotal: 0,
expeditionsSuccessful: 0,
recyclingMissions: 0,
recycledResources: 0,
planetDestructions: 0,
fuelConsumed: 0,
// 外交统计
friendlyNPCCount: 0,
hostileNPCCount: 0,
giftsSent: 0,
giftResourcesTotal: 0,
attackedByNPC: 0,
spiedByNPC: 0,
debrisRecycledByNPC: 0,
debrisResourcesLostToNPC: 0
}
}
// 初始化所有成就进度
export function initializeAchievements(): Record<string, AchievementProgress> {
const achievements: Record<string, AchievementProgress> = {}
for (const config of ACHIEVEMENTS) {
achievements[config.id] = {
id: config.id,
currentTier: null,
currentValue: 0,
tierUnlocks: {
[AchievementTier.Bronze]: null,
[AchievementTier.Silver]: null,
[AchievementTier.Gold]: null,
[AchievementTier.Platinum]: null,
[AchievementTier.Diamond]: null
}
}
}
return achievements
}
// 获取成就的当前值
function getAchievementValue(stats: AchievementStats, statKey: string, checkType: string): number {
// 处理特殊的组合统计键
if (statKey === 'totalResourcesConsumed') {
return stats.totalMetalConsumed + stats.totalCrystalConsumed + stats.totalDeuteriumConsumed + stats.totalDarkMatterConsumed
}
if (statKey === 'totalDebrisCreated') {
return stats.debrisCreatedFromAttacks + stats.debrisCreatedFromDefenses
}
if (statKey === 'totalFleetLost') {
return stats.totalFleetLostInAttack + stats.totalFleetLostInDefense
}
// 处理普通统计键
const value = stats[statKey as keyof AchievementStats]
if (typeof value === 'number') {
return value
}
// 处理 Record 类型的值(如果需要求和)
if (checkType === 'sum' && typeof value === 'object') {
return Object.values(value as Record<string, number>).reduce((a, b) => a + b, 0)
}
return 0
}
// 检查并更新成就进度,返回新解锁的成就
export interface AchievementUnlock {
id: string
tier: AchievementTier
reward: AchievementReward
}
export function checkAchievements(player: Player): AchievementUnlock[] {
if (!player.achievementStats || !player.achievements) {
return []
}
const unlocks: AchievementUnlock[] = []
const stats = player.achievementStats
const achievements = player.achievements
for (const config of ACHIEVEMENTS) {
const progress = achievements[config.id]
if (!progress) continue
// 获取当前值
const currentValue = getAchievementValue(stats, config.statKey, config.checkType)
progress.currentValue = currentValue
// 检查每个等级
for (const tierConfig of config.tiers) {
// 如果已经解锁这个等级,跳过
if (progress.tierUnlocks[tierConfig.tier] !== null) continue
// 检查是否达到目标
let achieved = false
if (config.checkType === 'gte' || config.checkType === 'sum') {
achieved = currentValue >= tierConfig.target
} else if (config.checkType === 'eq') {
achieved = currentValue === tierConfig.target
} else if (config.checkType === 'max') {
achieved = currentValue >= tierConfig.target
}
if (achieved) {
// 解锁成就
const now = Date.now()
progress.tierUnlocks[tierConfig.tier] = now
// 更新当前最高等级
if (progress.currentTier === null || getTierIndex(tierConfig.tier) > getTierIndex(progress.currentTier)) {
progress.currentTier = tierConfig.tier
progress.unlockedAt = now
}
unlocks.push({
id: config.id,
tier: tierConfig.tier,
reward: tierConfig.reward
})
}
}
}
return unlocks
}
// 应用成就奖励
export function applyAchievementReward(player: Player, reward: AchievementReward): void {
const firstPlanet = player.planets[0]
if (reward.darkMatter && firstPlanet) {
// 奖励添加到第一个星球的资源中
firstPlanet.resources.darkMatter += reward.darkMatter
}
if (reward.points) {
player.points += reward.points
}
}
// ==================== 统计更新函数 ====================
// 更新资源生产统计
export function updateResourceProductionStats(
stats: AchievementStats,
produced: { metal?: number; crystal?: number; deuterium?: number; darkMatter?: number }
): void {
if (produced.metal) stats.totalMetalProduced += produced.metal
if (produced.crystal) stats.totalCrystalProduced += produced.crystal
if (produced.deuterium) stats.totalDeuteriumProduced += produced.deuterium
if (produced.darkMatter) stats.totalDarkMatterProduced += produced.darkMatter
}
// 更新资源消耗统计
export function updateResourceConsumptionStats(stats: AchievementStats, consumed: Partial<Resources>): void {
if (consumed.metal) stats.totalMetalConsumed += consumed.metal
if (consumed.crystal) stats.totalCrystalConsumed += consumed.crystal
if (consumed.deuterium) stats.totalDeuteriumConsumed += consumed.deuterium
if (consumed.darkMatter) stats.totalDarkMatterConsumed += consumed.darkMatter
}
// 更新建筑升级统计
export function updateBuildingStats(stats: AchievementStats, buildingType: BuildingType, level: number): void {
stats.buildingsUpgraded += 1
// 更新最高等级记录
if (level > (stats.maxBuildingLevel[buildingType] || 0)) {
stats.maxBuildingLevel[buildingType] = level
}
}
// 更新科技研究统计
export function updateResearchStats(stats: AchievementStats, techType: TechnologyType, level: number): void {
stats.researchCompleted += 1
// 更新最高等级记录
if (level > (stats.maxTechnologyLevel[techType] || 0)) {
stats.maxTechnologyLevel[techType] = level
}
}
// 更新舰船生产统计
export function updateShipProductionStats(stats: AchievementStats, shipType: ShipType, quantity: number): void {
stats.shipsProduced[shipType] = (stats.shipsProduced[shipType] || 0) + quantity
stats.totalShipsProduced += quantity
}
// 更新防御建造统计
export function updateDefenseProductionStats(stats: AchievementStats, defenseType: DefenseType, quantity: number): void {
stats.defensesBuilt[defenseType] = (stats.defensesBuilt[defenseType] || 0) + quantity
stats.totalDefensesBuilt += quantity
}
// 更新攻击统计(玩家作为攻击者)
export function updateAttackStats(stats: AchievementStats, battleResult: BattleResult, won: boolean, debrisValue: number): void {
// 攻击也算飞行任务
stats.totalFlightMissions += 1
stats.attacksLaunched += 1
if (won) {
stats.attacksWon += 1
} else if (battleResult.winner === 'defender') {
stats.attacksLost += 1
}
// 记录攻击中损失的舰队
if (battleResult.attackerLosses) {
for (const [shipType, count] of Object.entries(battleResult.attackerLosses)) {
if (count && count > 0) {
stats.fleetLostInAttack[shipType as ShipType] = (stats.fleetLostInAttack[shipType as ShipType] || 0) + count
stats.totalFleetLostInAttack += count
}
}
}
// 记录产生的残骸
stats.debrisCreatedFromAttacks += debrisValue
}
// 更新防御统计(玩家作为防御者)
export function updateDefenseStats(stats: AchievementStats, battleResult: BattleResult, won: boolean, debrisValue: number): void {
if (won) {
stats.defensesSuccessful += 1
} else {
stats.defensesFailed += 1
}
// 记录防御中损失的舰队
if (battleResult.defenderLosses?.fleet) {
for (const [shipType, count] of Object.entries(battleResult.defenderLosses.fleet)) {
if (count && count > 0) {
stats.fleetLostInDefense[shipType as ShipType] = (stats.fleetLostInDefense[shipType as ShipType] || 0) + count
stats.totalFleetLostInDefense += count
}
}
}
// 记录防御中损失的防御设施
if (battleResult.defenderLosses?.defense) {
for (const [defenseType, count] of Object.entries(battleResult.defenderLosses.defense)) {
if (count && count > 0) {
stats.defenseLostInDefense[defenseType as DefenseType] = (stats.defenseLostInDefense[defenseType as DefenseType] || 0) + count
stats.totalDefenseLostInDefense += count
}
}
}
// 记录防御中消灭的敌方舰队
if (battleResult.attackerLosses) {
for (const [shipType, count] of Object.entries(battleResult.attackerLosses)) {
if (count && count > 0) {
stats.enemyFleetDestroyedInDefense[shipType as ShipType] = (stats.enemyFleetDestroyedInDefense[shipType as ShipType] || 0) + count
stats.totalEnemyFleetDestroyedInDefense += count
}
}
}
// 记录产生的残骸
stats.debrisCreatedFromDefenses += debrisValue
}
// 更新飞行任务统计
export function updateFlightMissionStats(stats: AchievementStats): void {
stats.totalFlightMissions += 1
}
// 更新运输任务统计
export function updateTransportStats(stats: AchievementStats, resourcesAmount: number): void {
stats.transportMissions += 1
stats.transportedResources += resourcesAmount
}
// 更新殖民统计
export function updateColonizationStats(stats: AchievementStats): void {
stats.colonizations += 1
}
// 更新侦查统计
export function updateSpyStats(stats: AchievementStats): void {
stats.spyMissions += 1
}
// 更新部署统计
export function updateDeploymentStats(stats: AchievementStats): void {
stats.deployments += 1
}
// 更新探险统计
export function updateExpeditionStats(stats: AchievementStats, successful: boolean): void {
stats.expeditionsTotal += 1
if (successful) {
stats.expeditionsSuccessful += 1
}
}
// 更新回收统计
export function updateRecyclingStats(stats: AchievementStats, resourcesAmount: number): void {
stats.recyclingMissions += 1
stats.recycledResources += resourcesAmount
}
// 更新行星毁灭统计
export function updatePlanetDestructionStats(stats: AchievementStats): void {
stats.planetDestructions += 1
}
// 更新燃料消耗统计
export function updateFuelConsumptionStats(stats: AchievementStats, fuelAmount: number): void {
stats.fuelConsumed += fuelAmount
}
// 更新友好NPC数量
export function updateFriendlyNPCStats(stats: AchievementStats, count: number): void {
stats.friendlyNPCCount = count
}
// 更新敌对NPC数量
export function updateHostileNPCStats(stats: AchievementStats, count: number): void {
stats.hostileNPCCount = count
}
// 更新送礼统计
export function updateGiftStats(stats: AchievementStats, resourcesAmount: number): void {
stats.giftsSent += 1
stats.giftResourcesTotal += resourcesAmount
}
// 更新被NPC攻击统计
export function updateAttackedByNPCStats(stats: AchievementStats): void {
stats.attackedByNPC += 1
}
// 更新被NPC侦查统计
export function updateSpiedByNPCStats(stats: AchievementStats): void {
stats.spiedByNPC += 1
}
// 更新被NPC回收残骸统计
export function updateDebrisRecycledByNPCStats(stats: AchievementStats, resourcesAmount: number): void {
stats.debrisRecycledByNPC += 1
stats.debrisResourcesLostToNPC += resourcesAmount
}
// 获取成就的下一个目标
export function getNextTarget(achievementId: string, currentTier: AchievementTier | null): number | null {
const config = ACHIEVEMENT_MAP[achievementId]
if (!config) return null
const nextTier = getNextTier(currentTier)
if (!nextTier) return null
const tierConfig = config.tiers.find(t => t.tier === nextTier)
return tierConfig?.target ?? null
}
// 获取成就进度百分比
export function getAchievementProgress(achievementId: string, currentValue: number, currentTier: AchievementTier | null): number {
const config = ACHIEVEMENT_MAP[achievementId]
if (!config) return 0
const nextTier = getNextTier(currentTier)
if (!nextTier) return 100 // 已达到最高等级
const tierConfig = config.tiers.find(t => t.tier === nextTier)
if (!tierConfig) return 0
// 计算当前等级的起始值
let startValue = 0
if (currentTier) {
const currentTierConfig = config.tiers.find(t => t.tier === currentTier)
startValue = currentTierConfig?.target ?? 0
}
const progress = ((currentValue - startValue) / (tierConfig.target - startValue)) * 100
return Math.min(100, Math.max(0, progress))
}

View File

@@ -73,6 +73,21 @@ export const createFleetMission = (
}
}
/**
* 运输任务失败原因
*/
export type TransportFailReason = 'targetNotFound' | 'giftRejected'
/**
* 运输任务结果
*/
export interface TransportResult {
success: boolean
reputationGain?: number
overflow?: Resources
failReason?: TransportFailReason
}
/**
* 处理运输任务到达
*/
@@ -83,7 +98,7 @@ export const processTransportArrival = (
allNpcs?: NPC[],
locale: Locale = 'zh-CN',
storageCapacityBonus: number = 0
): { success: boolean; reputationGain?: number; overflow?: Resources } => {
): TransportResult => {
// 检查是否是赠送任务
if (mission.isGift && mission.giftTargetNpcId && player && allNpcs) {
const targetNpc = allNpcs.find(n => n.id === mission.giftTargetNpcId)
@@ -94,7 +109,7 @@ export const processTransportArrival = (
// 如果礼物被拒绝,资源返还给玩家
if (!giftResult.accepted) {
// 资源保留在cargo中返回时会退还给玩家
return { success: false, reputationGain: undefined }
return { success: false, reputationGain: undefined, failReason: 'giftRejected' }
}
// 礼物被接受,清空货物
@@ -114,10 +129,13 @@ export const processTransportArrival = (
mission.cargo = result.overflow
return { success: true, overflow: result.overflow }
}
// 运输成功,清空货物
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
return { success: true }
}
// 目标星球不存在
mission.status = 'returning'
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
return { success: false }
return { success: false, failReason: 'targetNotFound' }
}
/**
@@ -356,6 +374,15 @@ export const canColonize = (player: Player): boolean => {
return currentPlanets < maxPlanets
}
/**
* 殖民任务结果
*/
export interface ColonizeResult {
success: boolean
planet: Planet | null
failReason?: 'positionOccupied' | 'maxColoniesReached'
}
/**
* 处理殖民任务到达
*/
@@ -364,18 +391,18 @@ export const processColonizeArrival = (
targetPlanet: Planet | undefined,
player: Player,
colonyNameTemplate: string = 'Colony'
): Planet | null => {
): ColonizeResult => {
if (targetPlanet) {
// 位置已被占用
mission.status = 'returning'
return null
return { success: false, planet: null, failReason: 'positionOccupied' }
}
// 检查殖民地槽位限制
if (!canColonize(player)) {
// 超出殖民地数量限制,殖民船返回
mission.status = 'returning'
return null
return { success: false, planet: null, failReason: 'maxColoniesReached' }
}
// 创建新殖民地
@@ -431,7 +458,7 @@ export const processColonizeArrival = (
mission.fleet[ShipType.ColonyShip] = (mission.fleet[ShipType.ColonyShip] || 1) - 1
mission.status = 'returning'
return newPlanet
return { success: true, planet: newPlanet }
}
/**
@@ -481,6 +508,20 @@ export const calculateSpyDetectionChance = (attackerSpyLevel: number, defenderSp
return Math.max(0.01, Math.min(0.99, baseChance))
}
/**
* 侦查任务失败原因
*/
export type SpyFailReason = 'targetNotFound'
/**
* 侦查任务结果
*/
export interface SpyResult {
success: boolean
report?: SpyReport
failReason?: SpyFailReason
}
/**
* 处理间谍任务到达
*/
@@ -491,10 +532,10 @@ export const processSpyArrival = (
defender: Player | null,
allNpcs?: NPC[],
locale: Locale = 'zh-CN'
): SpyReport | null => {
): SpyResult => {
if (!targetPlanet) {
mission.status = 'returning'
return null
return { success: false, failReason: 'targetNotFound' }
}
// 获取间谍技术等级
@@ -536,7 +577,21 @@ export const processSpyArrival = (
}
mission.status = 'returning'
return spyReport
return { success: true, report: spyReport }
}
/**
* 部署任务失败原因
*/
export type DeployFailReason = 'targetNotFound' | 'notOwnPlanet'
/**
* 部署任务结果
*/
export interface DeployResult {
success: boolean
overflow?: Partial<Record<ShipType, number>>
failReason?: DeployFailReason
}
/**
@@ -547,10 +602,14 @@ export const processDeployArrival = (
targetPlanet: Planet | undefined,
playerId: string,
technologies: Record<TechnologyType, number>
): { success: boolean; overflow?: Partial<Record<ShipType, number>> } => {
if (!targetPlanet || targetPlanet.ownerId !== playerId) {
): DeployResult => {
if (!targetPlanet) {
mission.status = 'returning'
return { success: false }
return { success: false, failReason: 'targetNotFound' }
}
if (targetPlanet.ownerId !== playerId) {
mission.status = 'returning'
return { success: false, failReason: 'notOwnPlanet' }
}
// 使用安全添加函数,防止舰队仓储溢出
@@ -566,6 +625,21 @@ export const processDeployArrival = (
return { success: true }
}
/**
* 回收任务失败原因
*/
export type RecycleFailReason = 'noDebrisField' | 'debrisEmpty'
/**
* 回收任务结果
*/
export interface RecycleResult {
success: boolean
collectedResources?: Pick<Resources, 'metal' | 'crystal'>
remainingDebris?: Pick<Resources, 'metal' | 'crystal'> | null
failReason?: RecycleFailReason
}
/**
* 处理回收任务到达
*/
@@ -575,10 +649,10 @@ export const processRecycleArrival = (
player?: Player,
allNpcs?: NPC[],
locale: Locale = 'zh-CN'
): { collectedResources: Pick<Resources, 'metal' | 'crystal'>; remainingDebris: Pick<Resources, 'metal' | 'crystal'> | null } | null => {
): RecycleResult => {
if (!debrisField) {
mission.status = 'returning'
return null
return { success: false, failReason: 'noDebrisField' }
}
// 计算回收船的货舱容量
@@ -599,7 +673,7 @@ export const processRecycleArrival = (
// 防止除零如果残骸为0直接返回
if (totalDebris === 0) {
mission.status = 'returning'
return null
return { success: false, failReason: 'debrisEmpty' }
}
// 按比例收集金属和晶体
@@ -625,6 +699,7 @@ export const processRecycleArrival = (
}
return {
success: true,
collectedResources: {
metal: collectedMetal,
crystal: collectedCrystal
@@ -840,42 +915,114 @@ export const calculatePlanetDefensePower = (fleet: Partial<Fleet>, defense: Part
}
/**
* 处理行星毁灭任务到达
* 处理行星/月球毁灭任务到达
* OGame规则
* - 月球销毁概率 = (100 - √diameter) × √deathstars
* - 死星反向销毁概率 = √diameter / 2
* - 两个概率独立判定
*/
/**
* 毁灭任务失败原因
*/
export type DestroyFailReason = 'targetNotFound' | 'ownPlanet' | 'noDeathstar' | 'chanceFailed'
/**
* 销毁任务结果
*/
export interface DestroyResult {
success: boolean // 目标是否被销毁
destructionChance: number // 销毁概率
planetId?: string // 被销毁的星球/月球ID
deathstarsLost: boolean // 死星是否被反向销毁
deathstarDestructionChance: number // 死星反向销毁概率
isMoon: boolean // 目标是否为月球
failReason?: DestroyFailReason // 失败原因
}
export const processDestroyArrival = (
mission: FleetMission,
targetPlanet: Planet | undefined,
attacker: Player
): { success: boolean; destructionChance: number; planetId?: string } | null => {
if (!targetPlanet || targetPlanet.ownerId === attacker.id) {
): DestroyResult => {
if (!targetPlanet) {
mission.status = 'returning'
return null
return {
success: false,
destructionChance: 0,
deathstarsLost: false,
deathstarDestructionChance: 0,
isMoon: false,
failReason: 'targetNotFound'
}
}
if (targetPlanet.ownerId === attacker.id) {
mission.status = 'returning'
return {
success: false,
destructionChance: 0,
deathstarsLost: false,
deathstarDestructionChance: 0,
isMoon: targetPlanet.isMoon || false,
failReason: 'ownPlanet'
}
}
// 检查是否有死星
const deathstarCount = mission.fleet[ShipType.Deathstar] || 0
if (deathstarCount === 0) {
mission.status = 'returning'
return null
return {
success: false,
destructionChance: 0,
deathstarsLost: false,
deathstarDestructionChance: 0,
isMoon: targetPlanet.isMoon || false,
failReason: 'noDeathstar'
}
}
// 计算目标星球的防御力量
const planetaryShieldCount = targetPlanet.defense[DefenseType.PlanetaryShield] || 0
const defensePower = calculatePlanetDefensePower(targetPlanet.fleet, targetPlanet.defense)
// 根据目标类型使用不同的销毁逻辑
if (targetPlanet.isMoon) {
// 月球销毁使用 OGame 公式
const result = moonLogic.tryDestroyMoon(targetPlanet, deathstarCount)
// 计算摧毁概率
const destructionChance = calculateDestructionChance(deathstarCount, planetaryShieldCount, defensePower)
// 如果死星被反向销毁,从任务舰队中移除
if (result.deathstarsDestroyed) {
mission.fleet[ShipType.Deathstar] = 0
}
// 随机判断是否成功
const randomValue = Math.random() * 100
const success = randomValue < destructionChance
mission.status = 'returning'
mission.status = 'returning'
return {
success: result.moonDestroyed,
destructionChance: result.moonDestructionChance,
planetId: result.moonDestroyed ? targetPlanet.id : undefined,
deathstarsLost: result.deathstarsDestroyed,
deathstarDestructionChance: result.deathstarDestructionChance,
isMoon: true,
failReason: result.moonDestroyed ? undefined : 'chanceFailed'
}
} else {
// 行星销毁使用原有逻辑
const planetaryShieldCount = targetPlanet.defense[DefenseType.PlanetaryShield] || 0
const defensePower = calculatePlanetDefensePower(targetPlanet.fleet, targetPlanet.defense)
const destructionChance = calculateDestructionChance(deathstarCount, planetaryShieldCount, defensePower)
return {
success,
destructionChance,
planetId: success ? targetPlanet.id : undefined
const randomValue = Math.random() * 100
const success = randomValue < destructionChance
mission.status = 'returning'
return {
success,
destructionChance,
planetId: success ? targetPlanet.id : undefined,
deathstarsLost: false,
deathstarDestructionChance: 0,
isMoon: false,
failReason: success ? undefined : 'chanceFailed'
}
}
}
@@ -985,17 +1132,17 @@ export const updateFleetMissions = async (
}
case MissionType.Colonize:
const newColony = processColonizeArrival(mission, targetPlanet, attacker)
if (newColony) {
newColonies.push(newColony)
planets.set(targetKey, newColony)
const colonizeResult = processColonizeArrival(mission, targetPlanet, attacker)
if (colonizeResult.success && colonizeResult.planet) {
newColonies.push(colonizeResult.planet)
planets.set(targetKey, colonizeResult.planet)
}
break
case MissionType.Spy:
const spyReport = processSpyArrival(mission, targetPlanet, attacker, defender)
if (spyReport) {
spyReports.push(spyReport)
const spyResult = processSpyArrival(mission, targetPlanet, attacker, defender)
if (spyResult.success && spyResult.report) {
spyReports.push(spyResult.report)
}
break
@@ -1010,7 +1157,7 @@ export const updateFleetMissions = async (
const debrisId = `debris_${mission.targetPosition.galaxy}_${mission.targetPosition.system}_${mission.targetPosition.position}`
const debrisField = debrisFields.get(debrisId)
const recycleResult = processRecycleArrival(mission, debrisField, attacker, allNpcs)
if (recycleResult) {
if (recycleResult.success && recycleResult.collectedResources) {
if (recycleResult.remainingDebris) {
// 更新残骸场
const updatedDebris: DebrisField = {
@@ -1029,7 +1176,7 @@ export const updateFleetMissions = async (
case MissionType.Destroy:
const destroyResult = processDestroyArrival(mission, targetPlanet, attacker)
if (destroyResult && destroyResult.success && destroyResult.planetId) {
if (destroyResult.success && destroyResult.planetId) {
// 星球被摧毁
destroyedPlanetIds.push(destroyResult.planetId)

View File

@@ -6,6 +6,7 @@ import * as researchLogic from './researchLogic'
import * as pointsLogic from './pointsLogic'
import * as planetLogic from './planetLogic'
import * as resourceLogic from './resourceLogic'
import * as achievementLogic from './achievementLogic'
/**
* 初始化玩家数据
@@ -105,6 +106,14 @@ export const processGameUpdate = (
): {
updatedResearchQueue: BuildQueueItem[]
} => {
// 确保成就统计数据存在
if (!player.achievementStats) {
player.achievementStats = achievementLogic.initializeAchievementStats()
}
if (!player.achievements) {
player.achievements = achievementLogic.initializeAchievements()
}
// 获取军官加成
const bonuses = officerLogic.calculateActiveBonuses(player.officers, now)
@@ -113,16 +122,48 @@ export const processGameUpdate = (
pointsLogic.addPoints(player, points)
}
// 通知回调
const onCompleted = (type: string, itemType: string, level?: number, _quantity?: number) => {
// 通知回调 + 成就统计更新
const onCompleted = (type: string, itemType: string, level?: number, quantity?: number) => {
if (onNotification) {
onNotification(type, itemType, level)
}
// 更新成就统计
if (player.achievementStats) {
if (type === 'building' && level !== undefined) {
achievementLogic.updateBuildingStats(player.achievementStats, itemType as any, level)
} else if (type === 'technology' && level !== undefined) {
achievementLogic.updateResearchStats(player.achievementStats, itemType as any, level)
} else if (type === 'ship' && quantity !== undefined) {
achievementLogic.updateShipProductionStats(player.achievementStats, itemType as any, quantity)
} else if (type === 'defense' && quantity !== undefined) {
achievementLogic.updateDefenseProductionStats(player.achievementStats, itemType as any, quantity)
}
}
}
// 更新所有星球资源(直接同步计算,避免 Worker 通信开销)
player.planets.forEach(planet => {
// 计算更新前的资源(用于计算生产量)
const resourcesBefore = { ...planet.resources }
resourceLogic.updatePlanetResources(planet, now, bonuses, gameSpeed)
// 追踪资源生产统计
if (player.achievementStats) {
const metalProduced = Math.max(0, planet.resources.metal - resourcesBefore.metal)
const crystalProduced = Math.max(0, planet.resources.crystal - resourcesBefore.crystal)
const deuteriumProduced = Math.max(0, planet.resources.deuterium - resourcesBefore.deuterium)
const darkMatterProduced = Math.max(0, planet.resources.darkMatter - resourcesBefore.darkMatter)
if (metalProduced > 0 || crystalProduced > 0 || deuteriumProduced > 0 || darkMatterProduced > 0) {
achievementLogic.updateResourceProductionStats(player.achievementStats, {
metal: metalProduced,
crystal: crystalProduced,
deuterium: deuteriumProduced,
darkMatter: darkMatterProduced
})
}
}
})
// 更新所有星球其他状态
@@ -159,3 +200,161 @@ export const processGameUpdate = (
export const checkOfficersExpiration = (officers: Record<OfficerType, Officer>, now: number): void => {
officerLogic.checkAndDeactivateExpiredOfficers(officers, now)
}
/**
* 检查成就进度并解锁新成就
*/
export const checkAndUnlockAchievements = (player: Player): achievementLogic.AchievementUnlock[] => {
if (!player.achievementStats || !player.achievements) {
return []
}
const unlocks = achievementLogic.checkAchievements(player)
// 应用奖励
unlocks.forEach(unlock => {
achievementLogic.applyAchievementReward(player, unlock.reward)
})
return unlocks
}
/**
* 更新资源消耗统计
*/
export const trackResourceConsumption = (
player: Player,
consumed: { metal?: number; crystal?: number; deuterium?: number; darkMatter?: number }
): void => {
if (!player.achievementStats) {
player.achievementStats = achievementLogic.initializeAchievementStats()
}
achievementLogic.updateResourceConsumptionStats(player.achievementStats, consumed)
}
/**
* 更新攻击统计(玩家作为攻击者)
*/
export const trackAttackStats = (player: Player, battleResult: any, won: boolean, debrisValue: number): void => {
if (!player.achievementStats) {
player.achievementStats = achievementLogic.initializeAchievementStats()
}
achievementLogic.updateAttackStats(player.achievementStats, battleResult, won, debrisValue)
}
/**
* 更新防御统计(玩家作为防御者)
*/
export const trackDefenseStats = (player: Player, battleResult: any, won: boolean, debrisValue: number): void => {
if (!player.achievementStats) {
player.achievementStats = achievementLogic.initializeAchievementStats()
}
achievementLogic.updateDefenseStats(player.achievementStats, battleResult, won, debrisValue)
}
/**
* 更新任务统计
*/
export const trackMissionStats = (
player: Player,
missionType: string,
details?: {
resourcesAmount?: number
successful?: boolean
fuelAmount?: number
}
): void => {
if (!player.achievementStats) {
player.achievementStats = achievementLogic.initializeAchievementStats()
}
const stats = player.achievementStats
achievementLogic.updateFlightMissionStats(stats)
switch (missionType) {
case 'transport':
if (details?.resourcesAmount) {
achievementLogic.updateTransportStats(stats, details.resourcesAmount)
}
break
case 'colonize':
achievementLogic.updateColonizationStats(stats)
break
case 'spy':
achievementLogic.updateSpyStats(stats)
break
case 'deploy':
achievementLogic.updateDeploymentStats(stats)
break
case 'expedition':
achievementLogic.updateExpeditionStats(stats, details?.successful ?? false)
break
case 'recycle':
// 回收任务总是计入任务次数,但只有有资源时才增加回收资源量
achievementLogic.updateRecyclingStats(stats, details?.resourcesAmount ?? 0)
break
case 'destroy':
achievementLogic.updatePlanetDestructionStats(stats)
break
}
if (details?.fuelAmount) {
achievementLogic.updateFuelConsumptionStats(stats, details.fuelAmount)
}
}
/**
* 更新外交统计
*/
export const trackDiplomacyStats = (
player: Player,
eventType: string,
details?: {
resourcesAmount?: number
friendlyCount?: number
hostileCount?: number
}
): void => {
if (!player.achievementStats) {
player.achievementStats = achievementLogic.initializeAchievementStats()
}
const stats = player.achievementStats
switch (eventType) {
case 'gift':
if (details?.resourcesAmount) {
achievementLogic.updateGiftStats(stats, details.resourcesAmount)
}
break
case 'attackedByNPC':
achievementLogic.updateAttackedByNPCStats(stats)
break
case 'spiedByNPC':
achievementLogic.updateSpiedByNPCStats(stats)
break
case 'debrisRecycledByNPC':
if (details?.resourcesAmount) {
achievementLogic.updateDebrisRecycledByNPCStats(stats, details.resourcesAmount)
}
break
case 'updateRelations':
if (details?.friendlyCount !== undefined) {
achievementLogic.updateFriendlyNPCStats(stats, details.friendlyCount)
}
if (details?.hostileCount !== undefined) {
achievementLogic.updateHostileNPCStats(stats, details.hostileCount)
}
break
}
}
/**
* 追踪燃料消耗(在舰队出发时调用)
*/
export const trackFuelConsumption = (player: Player, fuelAmount: number): void => {
if (!player.achievementStats) {
player.achievementStats = achievementLogic.initializeAchievementStats()
}
achievementLogic.updateFuelConsumptionStats(player.achievementStats, fuelAmount)
}

View File

@@ -4,8 +4,9 @@ import { MOON_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
/**
* 计算月球生成概率
* OGame规则每100,000残骸(金属+晶体) = 1%概率最高20%需要2,000,000残骸
* @param debrisField 战斗产生的残骸场
* @returns 生成概率0-100
* @returns 生成概率0-20
*/
export const calculateMoonGenerationChance = (debrisField: Resources): number => {
const totalDebris = debrisField.metal + debrisField.crystal
@@ -15,18 +16,42 @@ export const calculateMoonGenerationChance = (debrisField: Resources): number =>
return 0
}
// 计算概率:基础概率 + (残骸量 / 每单位增加量) * 1%
const additionalChance = Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)
const chance = MOON_CONFIG.baseChance + additionalChance
// 计算概率:每100k残骸 = 1%
const chance = Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)
// 限制在最大概率内
// 限制在最大概率内20%
return Math.min(chance, MOON_CONFIG.maxChance)
}
/**
* 计算月球直径
* OGame规则直径基于生成时的moonchance20%概率保证直径>8000km
* @param moonChance 月球生成概率1-20
* @returns 月球直径(km)
*/
export const calculateMoonDiameter = (moonChance: number): number => {
// 基础直径 + 每%概率增加的直径 + 随机波动
const baseDiameter = MOON_CONFIG.baseDiameter
const bonusDiameter = moonChance * MOON_CONFIG.diameterPerChance
// 添加±10%的随机波动
const randomFactor = 0.9 + Math.random() * 0.2
const diameter = Math.floor((baseDiameter + bonusDiameter) * randomFactor)
// 确保在合理范围内
return Math.max(MOON_CONFIG.minDiameter, Math.min(MOON_CONFIG.maxDiameter, diameter))
}
/**
* 尝试生成月球
* OGame规则
* - 每100k残骸 = 1%概率
* - 最高20%概率需要2M残骸
* - 月球初始只有1格空间
* - 直径基于moonchance计算
*
* @param debrisField 战斗产生的残骸场
* @param planetPosition 星球坐标
* @param planetId 母星ID
* @param playerId 玩家ID
* @returns 生成的月球对象如果未生成则返回null
*/
@@ -49,6 +74,9 @@ export const tryGenerateMoon = (
return null
}
// 计算月球直径
const diameter = calculateMoonDiameter(chance)
// 生成月球
const moon: Planet = {
id: `moon_${Date.now()}`,
@@ -89,10 +117,11 @@ export const tryGenerateMoon = (
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: MOON_CONFIG.baseSize,
maxSpace: MOON_CONFIG.baseFields, // OGame规则月球初始只有1格空间
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
isMoon: true,
parentPlanetId: planetId
parentPlanetId: planetId,
diameter: diameter // 月球直径(km)
}
// 初始化所有建筑为0级
@@ -118,3 +147,182 @@ export const hasMoonAtPosition = (planets: Planet[], position: { galaxy: number;
p.position.position === position.position
)
}
/**
* 月球销毁结果类型
*/
export interface MoonDestructionResult {
moonDestroyed: boolean // 月球是否被销毁
deathstarsDestroyed: boolean // 死星是否被反向销毁
moonDestructionChance: number // 月球销毁概率
deathstarDestructionChance: number // 死星被销毁概率
}
/**
* 计算月球销毁概率
* OGame规则销毁概率 = (100 - √diameter) × √deathstars
* @param moonDiameter 月球直径(km)
* @param deathstarCount 死星数量
* @returns 销毁概率(0-100)
*/
export const calculateMoonDestructionChance = (moonDiameter: number, deathstarCount: number): number => {
if (deathstarCount <= 0) return 0
// 公式:(100 - √diameter) × √deathstars
const chance = (100 - Math.sqrt(moonDiameter)) * Math.sqrt(deathstarCount)
// 限制在0-100之间
return Math.max(0, Math.min(100, chance))
}
/**
* 计算死星被反向销毁的概率
* OGame规则反向销毁概率 = √diameter / 2
* @param moonDiameter 月球直径(km)
* @returns 死星被销毁概率(0-100)
*/
export const calculateDeathstarDestructionChance = (moonDiameter: number): number => {
// 公式√diameter / 2
const chance = Math.sqrt(moonDiameter) / 2
// 限制在0-100之间
return Math.max(0, Math.min(100, chance))
}
/**
* 尝试销毁月球(死星攻击月球时调用)
* OGame规则
* - 死星攻击月球时,先进行正常战斗
* - 战斗后,如果死星存活,则判定月球销毁
* - 月球销毁概率 = (100 - √diameter) × √deathstars
* - 死星反向销毁概率 = √diameter / 2
* - 两个概率独立判定
*
* @param moon 目标月球
* @param deathstarCount 攻击的死星数量
* @returns 销毁结果
*/
export const tryDestroyMoon = (moon: Planet, deathstarCount: number): MoonDestructionResult => {
if (!moon.isMoon || deathstarCount <= 0) {
return {
moonDestroyed: false,
deathstarsDestroyed: false,
moonDestructionChance: 0,
deathstarDestructionChance: 0
}
}
const diameter = moon.diameter || MOON_CONFIG.minDiameter
// 计算概率
const moonDestructionChance = calculateMoonDestructionChance(diameter, deathstarCount)
const deathstarDestructionChance = calculateDeathstarDestructionChance(diameter)
// 独立判定
const moonRoll = Math.random() * 100
const deathstarRoll = Math.random() * 100
const moonDestroyed = moonRoll < moonDestructionChance
const deathstarsDestroyed = deathstarRoll < deathstarDestructionChance
return {
moonDestroyed,
deathstarsDestroyed,
moonDestructionChance,
deathstarDestructionChance
}
}
/**
* 计算传感器阵列扫描范围
* OGame规则范围 = level² - 1 个星系
* @param sensorPhalanxLevel 传感器阵列等级
* @returns 可扫描的星系范围(单向)
*/
export const calculateSensorPhalanxRange = (sensorPhalanxLevel: number): number => {
if (sensorPhalanxLevel <= 0) return 0
return sensorPhalanxLevel * sensorPhalanxLevel - 1
}
/**
* 检查目标坐标是否在传感器阵列扫描范围内
* @param moonPosition 月球坐标
* @param targetPosition 目标坐标
* @param sensorPhalanxLevel 传感器阵列等级
* @returns 是否在扫描范围内
*/
export const isInSensorPhalanxRange = (
moonPosition: { galaxy: number; system: number; position: number },
targetPosition: { galaxy: number; system: number; position: number },
sensorPhalanxLevel: number
): boolean => {
// 必须在同一银河系
if (moonPosition.galaxy !== targetPosition.galaxy) return false
const range = calculateSensorPhalanxRange(sensorPhalanxLevel)
const systemDistance = Math.abs(moonPosition.system - targetPosition.system)
return systemDistance <= range
}
/**
* 检查跳跃门是否可用(冷却是否结束)
* OGame规则跳跃门使用后有1小时冷却时间
* @param moon 月球对象
* @returns 是否可以使用跳跃门
*/
export const isJumpGateReady = (moon: Planet): boolean => {
if (!moon.isMoon) return false
if (!moon.buildings[BuildingType.JumpGate] || moon.buildings[BuildingType.JumpGate] <= 0) return false
const lastUsed = moon.jumpGateLastUsed || 0
const cooldownEnd = lastUsed + MOON_CONFIG.jumpGateCooldown
return Date.now() >= cooldownEnd
}
/**
* 获取跳跃门剩余冷却时间(毫秒)
* @param moon 月球对象
* @returns 剩余冷却时间毫秒0表示已冷却完成
*/
export const getJumpGateCooldownRemaining = (moon: Planet): number => {
if (!moon.isMoon) return 0
if (!moon.buildings[BuildingType.JumpGate] || moon.buildings[BuildingType.JumpGate] <= 0) return 0
const lastUsed = moon.jumpGateLastUsed || 0
const cooldownEnd = lastUsed + MOON_CONFIG.jumpGateCooldown
const remaining = cooldownEnd - Date.now()
return Math.max(0, remaining)
}
/**
* 使用跳跃门(记录使用时间)
* @param moon 月球对象
*/
export const useJumpGate = (moon: Planet): void => {
if (moon.isMoon) {
moon.jumpGateLastUsed = Date.now()
}
}
/**
* 检查两个月球之间是否可以使用跳跃门传送
* OGame规则两个月球都必须有跳跃门且都必须冷却完成
* @param sourceMoon 源月球
* @param targetMoon 目标月球
* @returns 是否可以传送
*/
export const canUseJumpGate = (sourceMoon: Planet, targetMoon: Planet): boolean => {
// 两个都必须是月球
if (!sourceMoon.isMoon || !targetMoon.isMoon) return false
// 两个都必须有跳跃门
const sourceHasGate = sourceMoon.buildings[BuildingType.JumpGate] > 0
const targetHasGate = targetMoon.buildings[BuildingType.JumpGate] > 0
if (!sourceHasGate || !targetHasGate) return false
// 两个跳跃门都必须冷却完成
return isJumpGateReady(sourceMoon) && isJumpGateReady(targetMoon)
}

View File

@@ -1,5 +1,5 @@
import type { NPC, Planet, Player } from '@/types/game'
import { TechnologyType, BuildingType, ShipType } from '@/types/game'
import { TechnologyType, BuildingType, ShipType, DefenseType } from '@/types/game'
import { BUILDINGS, SHIPS, TECHNOLOGIES } from '@/config/gameConfig'
import * as buildingLogic from './buildingLogic'
import * as researchLogic from './researchLogic'
@@ -632,3 +632,347 @@ export const initializeNPCDiplomacy = (npcs: NPC[]): void => {
}
})
}
// ==================== 距离难度系统 ====================
/**
* 计算NPC星球到玩家母星的距离
* 银河系距离权重最大,星系次之,位置最小
*/
export const calculateDistanceToHomeworld = (
npcPosition: { galaxy: number; system: number; position: number },
homeworldPosition: { galaxy: number; system: number; position: number }
): number => {
const galaxyDistance = Math.abs(npcPosition.galaxy - homeworldPosition.galaxy)
const systemDistance = Math.abs(npcPosition.system - homeworldPosition.system)
const positionDistance = Math.abs(npcPosition.position - homeworldPosition.position)
// 银河系跨度权重最大
return galaxyDistance * 100 + systemDistance * 10 + positionDistance
}
/**
* 基于距离计算难度等级1-无限)
*/
export const calculateDifficultyLevel = (distance: number): number => {
if (distance <= 10) return 1 // 新手
if (distance <= 30) return 2 // 简单
if (distance <= 60) return 3 // 普通
if (distance <= 100) return 4 // 困难
if (distance <= 200) return 5 // 专家
if (distance <= 400) return 6 // 大师
// 超过400继续增长
return 6 + Math.floor((distance - 400) / 200)
}
/**
* 距离难度倍率配置
*/
export interface DistanceDifficultyMultipliers {
buildingMultiplier: number
techMultiplier: number
fleetMultiplier: number
resourceMultiplier: number
defenseMultiplier: number
}
/**
* 基于距离计算实力倍率
* 使用对数增长,确保数值合理
*
* 建筑等级上限30
* 科技等级上限20
*
* 建筑等级示例baseLevel=5
* - 距离 0: 5级
* - 距离 10: 8级
* - 距离 50: 14级
* - 距离 100: 18级
* - 距离 200: 22级
* - 距离 400: 26级
* - 距离 800+: 30级上限
*/
export const calculateDistanceDifficultyMultiplier = (distance: number): DistanceDifficultyMultipliers => {
// 使用对数增长,确保建筑等级在合理范围内
// 公式: 1 + ln(1 + distance) * 0.8
// 这样可以确保最大建筑倍率约为6倍5*6=30
const logMultiplier = 1 + Math.log(1 + distance) * 0.8
// 限制建筑倍率最大为6因为基础等级是55*6=30
const buildingMultiplier = Math.min(6, Math.max(1, Math.round(logMultiplier)))
// 科技倍率稍低上限4倍5*4=20
const techMultiplier = Math.min(4, Math.max(1, Math.round(logMultiplier * 0.7)))
// 舰队倍率:使用更平缓的增长
// 基础舰队10艘最大约500艘左右
const fleetMultiplier = Math.min(50, Math.max(1, Math.round(logMultiplier * 8)))
// 资源倍率限制最大20倍避免Infinity
const resourceMultiplier = Math.min(20, Math.max(1, logMultiplier * 3))
// 防御倍率
const defenseMultiplier = Math.min(30, Math.max(1, Math.round(logMultiplier * 5)))
return {
buildingMultiplier,
techMultiplier,
fleetMultiplier,
resourceMultiplier,
defenseMultiplier
}
}
/**
* 基于距离难度初始化NPC星球
* 替代旧的 initializeNPCStartingPower
*
* 建筑等级上限30
* 科技等级上限20
* 资源上限:基于仓储建筑等级计算 (10000 * 2^level)
* 舰队数量:基于船坞等级和难度等级合理计算
*/
export const initializeNPCByDistance = (
npc: NPC,
homeworldPosition: { galaxy: number; system: number; position: number }
): void => {
const planet = npc.planets[0]
if (!planet) return
const distance = calculateDistanceToHomeworld(planet.position, homeworldPosition)
const multipliers = calculateDistanceDifficultyMultiplier(distance)
// 保存距离和难度等级到NPC
npc.distanceToHomeworld = distance
npc.difficultyLevel = calculateDifficultyLevel(distance)
// 基础等级 * 倍率,并限制上限
const baseLevel = 5
const MAX_BUILDING_LEVEL = 30
const MAX_TECH_LEVEL = 20
const targetBuildingLevel = Math.min(MAX_BUILDING_LEVEL, Math.max(1, Math.floor(baseLevel * multipliers.buildingMultiplier)))
const targetTechLevel = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(baseLevel * multipliers.techMultiplier)))
// 设置资源建筑上限30
planet.buildings[BuildingType.MetalMine] = Math.min(MAX_BUILDING_LEVEL, targetBuildingLevel)
planet.buildings[BuildingType.CrystalMine] = Math.min(MAX_BUILDING_LEVEL, Math.max(1, Math.floor(targetBuildingLevel * 0.8)))
planet.buildings[BuildingType.DeuteriumSynthesizer] = Math.min(MAX_BUILDING_LEVEL, Math.max(1, Math.floor(targetBuildingLevel * 0.6)))
planet.buildings[BuildingType.SolarPlant] = Math.min(MAX_BUILDING_LEVEL, targetBuildingLevel + 2)
// 设置设施建筑
planet.buildings[BuildingType.RoboticsFactory] = Math.min(15, Math.max(1, Math.floor(targetBuildingLevel * 0.5)))
planet.buildings[BuildingType.Shipyard] = Math.min(12, Math.max(1, Math.floor(targetBuildingLevel * 0.4)))
planet.buildings[BuildingType.ResearchLab] = Math.min(12, Math.max(1, Math.floor(targetBuildingLevel * 0.4)))
// 设置仓储上限10级对应10000*2^10=10,240,000容量
const storageLevel = Math.min(10, Math.max(1, Math.floor(targetBuildingLevel * 0.3)))
planet.buildings[BuildingType.MetalStorage] = storageLevel
planet.buildings[BuildingType.CrystalStorage] = storageLevel
planet.buildings[BuildingType.DeuteriumTank] = storageLevel
// 设置科技上限20
npc.technologies[TechnologyType.EnergyTechnology] = Math.min(MAX_TECH_LEVEL, targetTechLevel)
npc.technologies[TechnologyType.ComputerTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.8)))
npc.technologies[TechnologyType.WeaponsTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.7)))
npc.technologies[TechnologyType.ShieldingTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.7)))
npc.technologies[TechnologyType.ArmourTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.7)))
npc.technologies[TechnologyType.CombustionDrive] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.6)))
npc.technologies[TechnologyType.EspionageTechnology] = Math.min(MAX_TECH_LEVEL, Math.max(1, Math.floor(targetTechLevel * 0.5)))
// 计算舰队仓储容量(船坞每级+1000
const shipyardLevel = planet.buildings[BuildingType.Shipyard] || 1
const fleetStorageCapacity = shipyardLevel * 1000
// 基于难度等级和船坞容量计算舰队数量
// 难度等级1-7对应不同的舰队规模
const difficultyLevel = npc.difficultyLevel || 1
const fleetScale = Math.min(1, difficultyLevel / 7) // 0.14 ~ 1.0
// 设置舰队(基于船坞容量和难度等级)
// 总舰队数量不超过船坞容量的80%
const maxTotalFleet = Math.floor(fleetStorageCapacity * 0.8)
const baseFleetCount = Math.floor(maxTotalFleet * fleetScale)
// 分配舰队比例
planet.fleet[ShipType.EspionageProbe] = Math.max(5, Math.floor(baseFleetCount * 0.05))
planet.fleet[ShipType.LightFighter] = Math.floor(baseFleetCount * 0.35)
planet.fleet[ShipType.HeavyFighter] = Math.floor(baseFleetCount * 0.20)
planet.fleet[ShipType.Cruiser] = Math.floor(baseFleetCount * 0.15)
planet.fleet[ShipType.Battleship] = Math.floor(baseFleetCount * 0.05)
planet.fleet[ShipType.SmallCargo] = Math.floor(baseFleetCount * 0.10)
planet.fleet[ShipType.Recycler] = Math.floor(baseFleetCount * 0.10)
// 设置防御设施(基于难度等级,合理范围)
const defenseScale = difficultyLevel * 5
planet.defense[DefenseType.RocketLauncher] = Math.floor(defenseScale * 2)
planet.defense[DefenseType.LightLaser] = Math.floor(defenseScale * 1.5)
planet.defense[DefenseType.HeavyLaser] = Math.floor(defenseScale * 0.8)
planet.defense[DefenseType.GaussCannon] = Math.floor(defenseScale * 0.3)
planet.defense[DefenseType.IonCannon] = Math.floor(defenseScale * 0.3)
// 高级防御设施只有高难度NPC才有
if (difficultyLevel >= 4) {
planet.defense[DefenseType.PlasmaTurret] = Math.floor(defenseScale * 0.1)
planet.defense[DefenseType.SmallShieldDome] = 1
}
if (difficultyLevel >= 6) {
planet.defense[DefenseType.LargeShieldDome] = 1
}
// 设置资源(基于仓储建筑等级计算容量上限)
// 容量公式10000 * 2^level
const metalCapacity = 10000 * Math.pow(2, planet.buildings[BuildingType.MetalStorage] || 0)
const crystalCapacity = 10000 * Math.pow(2, planet.buildings[BuildingType.CrystalStorage] || 0)
const deuteriumCapacity = 10000 * Math.pow(2, planet.buildings[BuildingType.DeuteriumTank] || 0)
const darkMatterCapacity = 1000 * Math.pow(2, planet.buildings[BuildingType.DarkMatterTank] || 0)
// 资源设置为容量的50%-80%(基于难度等级)
const resourceFillRate = 0.5 + (difficultyLevel / 7) * 0.3
planet.resources.metal = Math.floor(metalCapacity * resourceFillRate)
planet.resources.crystal = Math.floor(crystalCapacity * resourceFillRate)
planet.resources.deuterium = Math.floor(deuteriumCapacity * resourceFillRate)
planet.resources.darkMatter = Math.floor(darkMatterCapacity * resourceFillRate)
}
/**
* 基于距离的NPC成长配置
*/
export interface DistanceBasedGrowthConfig {
resourceGrowthRate: number
buildingGrowthSpeed: number
techGrowthSpeed: number
checkInterval: number
}
/**
* 根据距离计算NPC成长配置
*/
export const calculateDistanceBasedGrowthConfig = (distance: number): DistanceBasedGrowthConfig => {
const multipliers = calculateDistanceDifficultyMultiplier(distance)
return {
resourceGrowthRate: multipliers.resourceMultiplier,
buildingGrowthSpeed: multipliers.buildingMultiplier,
techGrowthSpeed: multipliers.techMultiplier,
checkInterval: 180 // 3分钟检查一次
}
}
/**
* 基于距离更新NPC成长
* 替代旧的 updateNPCGrowth 中基于玩家积分的逻辑
*/
export const updateNPCGrowthByDistance = (
npc: NPC,
homeworldPosition: { galaxy: number; system: number; position: number },
deltaSeconds: number,
gameSpeed: number = 1
): void => {
const planet = npc.planets[0]
if (!planet) return
// 如果没有距离信息,先计算
if (npc.distanceToHomeworld === undefined) {
npc.distanceToHomeworld = calculateDistanceToHomeworld(planet.position, homeworldPosition)
npc.difficultyLevel = calculateDifficultyLevel(npc.distanceToHomeworld)
}
const config = calculateDistanceBasedGrowthConfig(npc.distanceToHomeworld)
// 1. 持续生成资源(应用游戏速度倍率)
generateNPCResourcesByDistance(npc, deltaSeconds, config, gameSpeed)
// 2. 定期评估并调整实力
const now = Date.now()
const lastGrowthCheck = (npc as any).lastGrowthCheck || 0
if (now - lastGrowthCheck >= config.checkInterval * 1000) {
;(npc as any).lastGrowthCheck = now
// 计算目标实力(基于距离倍率)
const multipliers = calculateDistanceDifficultyMultiplier(npc.distanceToHomeworld)
const baseLevel = 5
const targetBuildingLevel = Math.floor(baseLevel * multipliers.buildingMultiplier)
const targetTechLevel = Math.floor(baseLevel * multipliers.techMultiplier)
// 获取当前平均建筑等级
let totalBuildingLevels = 0
let buildingCount = 0
Object.values(planet.buildings).forEach(level => {
totalBuildingLevels += level
buildingCount++
})
const avgBuildingLevel = buildingCount > 0 ? totalBuildingLevels / buildingCount : 0
// 获取当前平均科技等级
const techLevels = Object.values(npc.technologies)
const avgTechLevel = techLevels.length > 0 ? techLevels.reduce((sum, level) => sum + level, 0) / techLevels.length : 0
// 如果实力不足,进行升级
if (avgBuildingLevel < targetBuildingLevel) {
autoUpgradeNPCBuildings(npc)
}
if (avgTechLevel < targetTechLevel) {
autoResearchNPCTechnologies(npc)
}
// 计算目标舰队战力
const targetFleetPower = 1000 * multipliers.fleetMultiplier
let currentFleetPower = 0
Object.entries(planet.fleet).forEach(([shipType, count]) => {
const shipConfig = SHIPS[shipType as ShipType]
const power = shipConfig.attack + shipConfig.shield + shipConfig.armor / 10
currentFleetPower += power * (count as number)
})
if (currentFleetPower < targetFleetPower) {
autoBuildNPCFleet(npc)
}
}
}
/**
* 基于距离生成NPC资源
*/
export const generateNPCResourcesByDistance = (
npc: NPC,
deltaSeconds: number,
config: DistanceBasedGrowthConfig,
gameSpeed: number = 1
): void => {
const planet = npc.planets[0]
if (!planet) return
// 基于建筑等级计算资源产量
const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0
const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0
const deuteriumLevel = planet.buildings[BuildingType.DeuteriumSynthesizer] || 0
const darkMatterLevel = planet.buildings[BuildingType.DarkMatterCollector] || 0
// 简化的资源产量计算(每秒产量)
const metalProduction = 30 * metalMineLevel * Math.pow(1.1, metalMineLevel) * config.resourceGrowthRate
const crystalProduction = 20 * crystalMineLevel * Math.pow(1.1, crystalMineLevel) * config.resourceGrowthRate
const deuteriumProduction = 10 * deuteriumLevel * Math.pow(1.1, deuteriumLevel) * config.resourceGrowthRate
const darkMatterProduction = ((25 * darkMatterLevel * Math.pow(1.5, darkMatterLevel)) / 3600) * config.resourceGrowthRate
// 应用游戏速度倍率到时间
const effectiveDeltaSeconds = deltaSeconds * gameSpeed
// 增加资源
planet.resources.metal += metalProduction * effectiveDeltaSeconds
planet.resources.crystal += crystalProduction * effectiveDeltaSeconds
planet.resources.deuterium += deuteriumProduction * effectiveDeltaSeconds
planet.resources.darkMatter += darkMatterProduction * effectiveDeltaSeconds
// 确保不超过存储上限
const metalStorage = planet.buildings[BuildingType.MetalStorage] || 0
const crystalStorage = planet.buildings[BuildingType.CrystalStorage] || 0
const deuteriumStorage = planet.buildings[BuildingType.DeuteriumTank] || 0
const darkMatterStorage = planet.buildings[BuildingType.DarkMatterTank] || 0
planet.resources.metal = Math.min(planet.resources.metal, 10000 * Math.pow(2, metalStorage))
planet.resources.crystal = Math.min(planet.resources.crystal, 10000 * Math.pow(2, crystalStorage))
planet.resources.deuterium = Math.min(planet.resources.deuterium, 10000 * Math.pow(2, deuteriumStorage))
planet.resources.darkMatter = Math.min(planet.resources.darkMatter, 1000 * Math.pow(2, darkMatterStorage))
}

View File

@@ -143,12 +143,18 @@ export const calculateMoonChance = (debrisField: Resources): number => {
/**
* 创建月球
* @param parentPlanet 母星球
* @param position 坐标
* @param playerId 玩家ID
* @param moonSuffix 月球名称后缀
* @param diameter 月球直径(km),用于计算销毁概率
*/
export const createMoon = (
parentPlanet: Planet,
position: { galaxy: number; system: number; position: number },
playerId: string,
moonSuffix: string = "'s Moon"
moonSuffix: string = "'s Moon",
diameter?: number
): Planet => {
const moonId = `moon_${Date.now()}`
const moon: Planet = {
@@ -196,10 +202,11 @@ export const createMoon = (
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: MOON_CONFIG.baseSize,
maxSpace: MOON_CONFIG.baseFields, // OGame规则月球初始只有1格空间
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
isMoon: true,
parentPlanetId: parentPlanet.id
parentPlanetId: parentPlanet.id,
diameter: diameter || MOON_CONFIG.minDiameter // 月球直径(km)
}
// 初始化建筑等级
@@ -212,11 +219,12 @@ export const createMoon = (
/**
* 计算月球空间上限
* OGame规则月球初始1格月球基地每级+3格但月球基地本身占用1格净增2格
*/
export const calculateMoonMaxSpace = (moon: Planet): number => {
if (!moon.isMoon) return 0
const lunarBaseLevel = moon.buildings[BuildingType.LunarBase] || 0
return MOON_CONFIG.baseSize + lunarBaseLevel * MOON_CONFIG.lunarBaseSpaceBonus
return MOON_CONFIG.baseFields + lunarBaseLevel * MOON_CONFIG.lunarBaseFieldsBonus
}
/**

View File

@@ -120,31 +120,31 @@ export const getResourceCapacity = (planet: Planet, officers: Record<OfficerType
* 计算最大建造队列数量
* @param planet 星球对象
* @param additionalBuildQueue 军官提供的额外队列数量
* @returns 最大建造队列数量(基础1个 + 纳米工厂等级 + 军官加成最多10个
* @returns 最大建造队列数量(基础3个 + 纳米工厂等级 + 军官加成最多10个
*/
export const getMaxBuildQueue = (planet: Planet, additionalBuildQueue: number = 0): number => {
const naniteFactoryLevel = planet.buildings[BuildingType.NaniteFactory] || 0
return Math.min(1 + naniteFactoryLevel + additionalBuildQueue, 10)
return Math.min(3 + naniteFactoryLevel + additionalBuildQueue, 10)
}
/**
* 计算最大研究队列数量
* @param technologies 已研究的科技等级
* @returns 最大研究队列数量(基础1个 + 计算机技术等级最多10个
* @returns 最大研究队列数量(基础3个 + 计算机技术等级最多10个
*/
export const getMaxResearchQueue = (technologies: Partial<Record<TechnologyType, number>>): number => {
const computerTechLevel = technologies[TechnologyType.ComputerTechnology] || 0
return Math.min(1 + computerTechLevel, 10)
return Math.min(3 + computerTechLevel, 10)
}
/**
* 计算最大舰队任务数量
* @param additionalFleetSlots 军官提供的额外槽位数量
* @param computerTechnologyLevel 计算机技术等级
* @returns 最大舰队任务数量(基础1个 + 计算机技术等级 + 军官加成最多20个
* @returns 最大舰队任务数量(基础3个 + 计算机技术等级 + 军官加成最多20个
*/
export const getMaxFleetMissions = (additionalFleetSlots: number = 0, computerTechnologyLevel: number = 0): number => {
return Math.min(1 + computerTechnologyLevel + additionalFleetSlots, 20)
return Math.min(3 + computerTechnologyLevel + additionalFleetSlots, 20)
}
/**

View File

@@ -97,6 +97,8 @@ export const calculateResourceCapacity = (planet: Planet, storageCapacityBonus:
const bonus = 1 + (storageCapacityBonus || 0) / 100
// OGame规则基础容量10000资源可以超过容量只影响生产不会丢失
// 月球没有矿场,所以超过容量没有影响,玩家可以从行星运输资源到月球
const baseCapacity = 10000
const darkMatterBaseCapacity = 1000 // 暗物质基础容量较小
return {

View File

@@ -16,6 +16,7 @@ const router = createRouter({
{ path: '/messages', name: 'messages', component: () => import('@/views/MessagesView.vue') },
{ path: '/galaxy', name: 'galaxy', component: () => import('@/views/GalaxyView.vue') },
{ path: '/diplomacy', name: 'diplomacy', component: () => import('@/views/DiplomacyView.vue') },
{ path: '/achievements', name: 'achievements', component: () => import('@/views/AchievementsView.vue') },
{ path: '/settings', name: 'settings', component: () => import('@/views/SettingsView.vue') },
{ path: '/gm', name: 'gm', component: () => import('@/views/GMView.vue') },
{ path: '/:pathMatch(.*)*', name: 'not-found', component: () => import('@/views/NotFoundView.vue') }

View File

@@ -10,9 +10,12 @@ import type {
SpiedNotification,
NPCActivityNotification,
IncomingFleetAlert,
MissileAttack
MissileAttack,
AchievementStats,
AchievementProgress
} from '@/types/game'
import { TechnologyType, OfficerType } from '@/types/game'
import { initializeAchievementStats, initializeAchievements } from '@/logic/achievementLogic'
import type { Locale } from '@/locales'
import pkg from '../../package.json'
import { encryptData, decryptData } from '@/utils/crypto'
@@ -41,7 +44,9 @@ export const useGameStore = defineStore('game', {
giftRejectedNotifications: [],
points: 0,
isGMEnabled: false, // 明确设置 GM 模式默认为 false
lastVersionCheckTime: 0 // 最后一次检查版本的时间戳默认为0
lastVersionCheckTime: 0, // 最后一次检查版本的时间戳默认为0
achievementStats: initializeAchievementStats() as AchievementStats,
achievements: initializeAchievements() as Record<string, AchievementProgress>
} as Player,
currentPlanetId: '',
isDark: '',

View File

@@ -267,6 +267,16 @@ export interface DiplomaticReport {
read?: boolean // 已读状态
}
// 舰队预设
export interface FleetPreset {
id: string
name: string // 自定义预设名称
fleet: Partial<Fleet> // 预设舰队数量
targetPosition?: { galaxy: number; system: number; position: number } // 预设目标坐标
missionType?: MissionType // 预设任务类型
cargo?: Partial<Resources> // 预设运输资源
}
// 舰队任务
export interface FleetMission {
id: string
@@ -275,6 +285,7 @@ export interface FleetMission {
isHostile?: boolean // 是否是敌对任务(用于警告显示)
originPlanetId: string
targetPosition: { galaxy: number; system: number; position: number }
targetIsMoon?: boolean // 目标是否为月球(用于区分同坐标的行星和月球)
targetPlanetId?: string
debrisFieldId?: string // 残骸场ID用于回收任务
missionType: MissionType
@@ -413,6 +424,8 @@ export interface MissionReport {
message: string // 任务结果描述
// 任务特定的详细信息
details?: {
// 通用:失败原因
failReason?: string
// 运输任务:运输的资源
transportedResources?: Resources
// 殖民任务:新星球信息
@@ -423,6 +436,9 @@ export interface MissionReport {
remainingDebris?: Pick<Resources, 'metal' | 'crystal'>
// 毁灭任务:摧毁的星球
destroyedPlanetName?: string
// 毁灭任务:概率和死星损失
destructionChance?: number
deathstarsLost?: boolean
// 部署任务:部署的舰队
deployedFleet?: Partial<Fleet>
// 导弹攻击任务:导弹信息
@@ -436,6 +452,8 @@ export interface MissionReport {
foundFleet?: Partial<Fleet>
// 探险任务:损失的舰船
fleetLost?: Partial<Fleet>
// 侦查任务报告ID
spyReportId?: string
}
read?: boolean
}
@@ -501,6 +519,8 @@ export interface Planet {
maxFleetStorage: number // 舰队仓储上限
isMoon: boolean // 是否为月球
parentPlanetId?: string // 如果是月球,指向母星的ID
diameter?: number // 月球直径(km),用于销毁概率计算
jumpGateLastUsed?: number // 跳跃门上次使用时间戳(ms),用于冷却计算
}
// 月球特殊配置
@@ -586,6 +606,11 @@ export interface Player {
hintsEnabled?: boolean // 是否启用弱引导提示默认true
// 显示设置
backgroundEnabled?: boolean // 是否启用背景动画默认false
// 舰队预设
fleetPresets?: FleetPreset[] // 舰队预设列表最多3个
// 成就系统
achievementStats?: AchievementStats // 成就统计数据
achievements?: Record<string, AchievementProgress> // 成就进度
}
export interface NotificationSettings {
@@ -624,7 +649,10 @@ export interface NPC {
note?: string // 玩家添加的备注
planets: Planet[]
technologies: Record<TechnologyType, number>
difficulty: 'easy' | 'medium' | 'hard'
difficulty: 'easy' | 'medium' | 'hard' // 保留兼容,不再使用
// 距离难度系统
difficultyLevel?: number // 基于距离的难度等级1-无限)
distanceToHomeworld?: number // 到玩家母星的距离
// 行为跟踪字段
lastSpyTime?: number // 上次侦查玩家的时间
lastAttackTime?: number // 上次攻击玩家的时间
@@ -676,3 +704,121 @@ export interface TutorialProgress {
currentStep: string | null // 当前步骤ID
skippedAt?: number // 跳过的时间戳
}
// ==================== 成就系统类型 ====================
// 成就类别枚举
export const AchievementCategory = {
Resource: 'resource',
Building: 'building',
Combat: 'combat',
Mission: 'mission',
Diplomacy: 'diplomacy'
} as const
export type AchievementCategory = (typeof AchievementCategory)[keyof typeof AchievementCategory]
// 成就等级枚举
export const AchievementTier = {
Bronze: 'bronze',
Silver: 'silver',
Gold: 'gold',
Platinum: 'platinum',
Diamond: 'diamond'
} as const
export type AchievementTier = (typeof AchievementTier)[keyof typeof AchievementTier]
// 成就统计数据接口
export interface AchievementStats {
// 资源统计
totalMetalProduced: number
totalCrystalProduced: number
totalDeuteriumProduced: number
totalDarkMatterProduced: number
totalMetalConsumed: number
totalCrystalConsumed: number
totalDeuteriumConsumed: number
totalDarkMatterConsumed: number
// 建造统计
buildingsUpgraded: number
maxBuildingLevel: Record<BuildingType, number>
researchCompleted: number
maxTechnologyLevel: Record<TechnologyType, number>
shipsProduced: Record<ShipType, number>
totalShipsProduced: number
defensesBuilt: Record<DefenseType, number>
totalDefensesBuilt: number
// 战斗统计
attacksLaunched: number
attacksWon: number
attacksLost: number
fleetLostInAttack: Record<ShipType, number>
totalFleetLostInAttack: number
debrisCreatedFromAttacks: number
defensesSuccessful: number
defensesFailed: number
fleetLostInDefense: Record<ShipType, number>
totalFleetLostInDefense: number
defenseLostInDefense: Record<DefenseType, number>
totalDefenseLostInDefense: number
enemyFleetDestroyedInDefense: Record<ShipType, number>
totalEnemyFleetDestroyedInDefense: number
debrisCreatedFromDefenses: number
// 任务统计
totalFlightMissions: number
transportMissions: number
transportedResources: number
colonizations: number
spyMissions: number
deployments: number
expeditionsTotal: number
expeditionsSuccessful: number
recyclingMissions: number
recycledResources: number
planetDestructions: number
fuelConsumed: number
// 外交统计
friendlyNPCCount: number
hostileNPCCount: number
giftsSent: number
giftResourcesTotal: number
attackedByNPC: number
spiedByNPC: number
debrisRecycledByNPC: number
debrisResourcesLostToNPC: number
}
// 成就等级配置
export interface AchievementTierConfig {
tier: AchievementTier
target: number
reward: AchievementReward
}
// 成就奖励
export interface AchievementReward {
darkMatter?: number
points?: number
}
// 成就配置接口
export interface AchievementConfig {
id: string
category: AchievementCategory
icon: string
tiers: AchievementTierConfig[]
statKey: keyof AchievementStats | string
checkType: 'gte' | 'eq' | 'sum' | 'max'
}
// 玩家成就进度
export interface AchievementProgress {
id: string
currentTier: AchievementTier | null
currentValue: number
unlockedAt?: number
tierUnlocks: Record<AchievementTier, number | null>
}

View File

@@ -0,0 +1,343 @@
<template>
<div class="container mx-auto p-4 sm:p-6 space-y-4 sm:space-y-6">
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h1 class="text-2xl sm:text-3xl font-bold">{{ t('achievements.title') }}</h1>
<div class="flex items-center gap-2">{{ unlockedCount }} / {{ totalCount }} {{ t('achievements.unlocked') }}</div>
</div>
<!-- 分类标签 -->
<Tabs v-model="activeCategory" class="w-full">
<TabsList class="w-full grid grid-cols-5 h-10">
<TabsTrigger v-for="category in categories" :key="category.value" :value="category.value" class="text-xs sm:text-sm">
{{ t(`achievements.categories.${category.value}`) }}
<Badge v-if="getCategoryUnlockedCount(category.value) > 0" class="ml-1 h-5 px-1.5 text-[10px] bg-primary text-primary-foreground">
{{ getCategoryUnlockedCount(category.value) }}
</Badge>
</TabsTrigger>
</TabsList>
<!-- 成就卡片网格 -->
<TabsContent v-for="category in categories" :key="category.value" :value="category.value" class="mt-4">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
<Card v-for="achievement in getAchievementsByCategory(category.value)" :key="achievement.id" class="relative overflow-hidden">
<!-- 等级指示条 -->
<div class="absolute top-0 left-0 right-0 h-1 flex">
<div v-for="tier in tierOrder" :key="tier" class="flex-1" :class="getTierBarClass(achievement.id, tier)" />
</div>
<CardHeader class="pt-4">
<div class="flex items-start gap-3">
<div class="p-2 rounded-lg" :class="getIconBgClass(achievement.id)">
<component :is="getIcon(achievement.icon)" class="h-6 w-6" :class="getIconClass(achievement.id)" />
</div>
<div class="flex-1 min-w-0">
<CardTitle class="text-sm sm:text-base flex items-center gap-2">
{{ t(`achievements.names.${achievement.id}`) }}
<Badge v-if="getCurrentTier(achievement.id)" :class="getTierBadgeClass(getCurrentTier(achievement.id)!)">
{{ t(`achievements.tiers.${getCurrentTier(achievement.id)}`) }}
</Badge>
</CardTitle>
<CardDescription class="text-xs mt-1">
{{ t(`achievements.descriptions.${achievement.id}`) }}
</CardDescription>
</div>
</div>
</CardHeader>
<CardContent class="space-y-3">
<!-- 进度条 -->
<div class="space-y-1">
<div class="flex justify-between text-xs">
<span class="text-muted-foreground">{{ t('achievements.progress') }}</span>
<span class="font-medium">
{{ formatNumber(getCurrentValue(achievement.id)) }} /
{{ formatNumber(getNextTarget(achievement.id) || getCurrentValue(achievement.id)) }}
</span>
</div>
<Progress :model-value="getProgressPercentage(achievement.id)" class="h-2" />
</div>
<!-- 下一等级奖励 -->
<div v-if="getNextTierConfig(achievement.id)" class="p-2 bg-muted/50 rounded-lg">
<p class="text-xs text-muted-foreground mb-1">
{{ t('achievements.nextTier') }}: {{ t(`achievements.tiers.${getNextTierConfig(achievement.id)!.tier}`) }}
</p>
<div class="flex items-center gap-3 text-xs">
<div v-if="getNextTierConfig(achievement.id)!.reward.darkMatter" class="flex items-center gap-1">
<Sparkles class="h-3 w-3 text-purple-500" />
<span>+{{ formatNumber(getNextTierConfig(achievement.id)!.reward.darkMatter!) }}</span>
</div>
<div v-if="getNextTierConfig(achievement.id)!.reward.points" class="flex items-center gap-1">
<Star class="h-3 w-3 text-yellow-500" />
<span>+{{ formatNumber(getNextTierConfig(achievement.id)!.reward.points!) }}</span>
</div>
</div>
</div>
<!-- 已达最高等级 -->
<div
v-else-if="getCurrentTier(achievement.id) === 'diamond'"
class="p-2 bg-gradient-to-r from-purple-500/10 to-blue-500/10 rounded-lg"
>
<p class="text-xs text-center font-medium text-purple-600 dark:text-purple-400">
{{ t('achievements.maxTierReached') }}
</p>
</div>
</CardContent>
</Card>
</div>
</TabsContent>
</Tabs>
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { useGameStore } from '@/stores/gameStore'
import { useI18n } from '@/composables/useI18n'
import { formatNumber } from '@/utils/format'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Progress } from '@/components/ui/progress'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { AchievementCategory, AchievementTier, type AchievementTierConfig } from '@/types/game'
import { ACHIEVEMENTS, ACHIEVEMENT_MAP, TIER_ORDER, getNextTier } from '@/config/achievementConfig'
import { getAchievementProgress } from '@/logic/achievementLogic'
import {
Sparkles,
Star,
Gem,
Diamond,
Droplet,
Flame,
Building2,
FlaskConical,
Rocket,
Shield,
Swords,
Crown,
ShieldCheck,
Bomb,
Trash2,
Skull,
ShieldOff,
Plane,
Truck,
Package,
Flag,
Eye,
ArrowDownToLine,
Compass,
Sparkle,
Recycle,
Pickaxe,
Zap,
Fuel,
Handshake as HandshakeIcon,
Angry,
Gift,
HeartHandshake,
Target,
ScanEye,
Banknote,
BadgeDollarSign
} from 'lucide-vue-next'
const { t } = useI18n()
const gameStore = useGameStore()
const activeCategory = ref<AchievementCategory>(AchievementCategory.Resource)
const categories = [
{ value: AchievementCategory.Resource },
{ value: AchievementCategory.Building },
{ value: AchievementCategory.Combat },
{ value: AchievementCategory.Mission },
{ value: AchievementCategory.Diplomacy }
]
const tierOrder = TIER_ORDER
// 图标映射
const iconMap: Record<string, any> = {
Gem,
Diamond,
Droplet,
Sparkles,
Flame,
Building2,
FlaskConical,
Rocket,
Shield,
Swords,
Crown,
ShieldCheck,
Bomb,
Trash2,
Skull,
ShieldOff,
Plane,
Truck,
Package,
Flag,
Eye,
ArrowDownToLine,
Compass,
Sparkle,
Recycle,
Pickaxe,
Zap,
Fuel,
HandshakeIcon,
Angry,
Gift,
HeartHandshake,
Target,
ScanEye,
Banknote,
BadgeDollarSign
}
const getIcon = (iconName: string) => {
return iconMap[iconName] || Sparkles
}
// 获取成就进度
const getProgress = (achievementId: string) => {
return gameStore.player.achievements?.[achievementId]
}
const getCurrentTier = (achievementId: string) => {
return getProgress(achievementId)?.currentTier || null
}
const getCurrentValue = (achievementId: string) => {
return getProgress(achievementId)?.currentValue || 0
}
const getNextTarget = (achievementId: string) => {
const config = ACHIEVEMENT_MAP[achievementId]
if (!config) return null
const currentTier = getCurrentTier(achievementId)
const nextTier = getNextTier(currentTier)
if (!nextTier) return null
const tierConfig = config.tiers.find(t => t.tier === nextTier)
return tierConfig?.target ?? null
}
const getNextTierConfig = (achievementId: string): AchievementTierConfig | null => {
const config = ACHIEVEMENT_MAP[achievementId]
if (!config) return null
const currentTier = getCurrentTier(achievementId)
const nextTier = getNextTier(currentTier)
if (!nextTier) return null
return config.tiers.find(t => t.tier === nextTier) || null
}
const getProgressPercentage = (achievementId: string) => {
const currentValue = getCurrentValue(achievementId)
const currentTier = getCurrentTier(achievementId)
return getAchievementProgress(achievementId, currentValue, currentTier)
}
// 按类别获取成就
const getAchievementsByCategory = (category: AchievementCategory) => {
return ACHIEVEMENTS.filter(a => a.category === category)
}
// 统计
const unlockedCount = computed(() => {
if (!gameStore.player.achievements) return 0
return Object.values(gameStore.player.achievements).filter(p => p.currentTier !== null).length
})
const totalCount = computed(() => ACHIEVEMENTS.length)
const getCategoryUnlockedCount = (category: AchievementCategory) => {
if (!gameStore.player.achievements) return 0
const categoryAchievements = ACHIEVEMENTS.filter(a => a.category === category)
return categoryAchievements.filter(a => {
const progress = gameStore.player.achievements?.[a.id]
return progress?.currentTier !== null
}).length
}
// 样式函数
const getTierBarClass = (achievementId: string, tier: AchievementTier) => {
const progress = getProgress(achievementId)
if (!progress) return 'bg-muted'
const tierUnlock = progress.tierUnlocks[tier]
if (tierUnlock !== null) {
// 已解锁
switch (tier) {
case AchievementTier.Bronze:
return 'bg-amber-600'
case AchievementTier.Silver:
return 'bg-gray-400'
case AchievementTier.Gold:
return 'bg-yellow-500'
case AchievementTier.Platinum:
return 'bg-cyan-400'
case AchievementTier.Diamond:
return 'bg-purple-500'
}
}
return 'bg-muted'
}
const getTierBadgeClass = (tier: AchievementTier) => {
switch (tier) {
case AchievementTier.Bronze:
return 'bg-amber-600 text-white'
case AchievementTier.Silver:
return 'bg-gray-400 text-white'
case AchievementTier.Gold:
return 'bg-yellow-500 text-black'
case AchievementTier.Platinum:
return 'bg-cyan-400 text-black'
case AchievementTier.Diamond:
return 'bg-gradient-to-r from-purple-500 to-blue-500 text-white'
}
}
const getIconBgClass = (achievementId: string) => {
const tier = getCurrentTier(achievementId)
if (!tier) return 'bg-muted'
switch (tier) {
case AchievementTier.Bronze:
return 'bg-amber-100 dark:bg-amber-900/30'
case AchievementTier.Silver:
return 'bg-gray-100 dark:bg-gray-800'
case AchievementTier.Gold:
return 'bg-yellow-100 dark:bg-yellow-900/30'
case AchievementTier.Platinum:
return 'bg-cyan-100 dark:bg-cyan-900/30'
case AchievementTier.Diamond:
return 'bg-purple-100 dark:bg-purple-900/30'
}
}
const getIconClass = (achievementId: string) => {
const tier = getCurrentTier(achievementId)
if (!tier) return 'text-muted-foreground'
switch (tier) {
case AchievementTier.Bronze:
return 'text-amber-600'
case AchievementTier.Silver:
return 'text-gray-500'
case AchievementTier.Gold:
return 'text-yellow-600'
case AchievementTier.Platinum:
return 'text-cyan-500'
case AchievementTier.Diamond:
return 'text-purple-500'
}
}
</script>

View File

@@ -194,6 +194,7 @@
import * as buildingValidation from '@/logic/buildingValidation'
import * as publicLogic from '@/logic/publicLogic'
import * as officerLogic from '@/logic/officerLogic'
import * as gameLogic from '@/logic/gameLogic'
const gameStore = useGameStore()
const detailDialog = useDetailDialogStore()
@@ -227,8 +228,9 @@
return (Object.values(BuildingType) as BuildingType[]).filter(buildingType => {
const config = BUILDINGS.value[buildingType]
if (planet.value!.isMoon) {
// 月球只能建造月球专属建筑
return config.moonOnly === true
// 月球可以建造月球专属建筑 + 非行星专属建筑(如机器人工厂、船坞、机库等)
// OGame规则月球不能建造 planetOnly 的建筑(矿场、研究实验室、纳米工厂等)
return config.planetOnly !== true
} else {
// 行星不能建造月球专属建筑
return config.moonOnly !== true
@@ -245,6 +247,12 @@
gameStore.player.officers
)
if (!validation.valid) return { success: false, reason: validation.reason }
// 追踪资源消耗(在扣除前计算成本)
const currentLevel = gameStore.currentPlanet.buildings[buildingType] || 0
const cost = buildingLogic.calculateBuildingCost(buildingType, currentLevel + 1)
gameLogic.trackResourceConsumption(gameStore.player, cost)
const queueItem = buildingValidation.executeBuildingUpgrade(gameStore.currentPlanet, buildingType, gameStore.player.officers)
gameStore.currentPlanet.buildQueue.push(queueItem)
return { success: true }

View File

@@ -180,6 +180,7 @@
import * as publicLogic from '@/logic/publicLogic'
import * as shipValidation from '@/logic/shipValidation'
import * as shipLogic from '@/logic/shipLogic'
import * as gameLogic from '@/logic/gameLogic'
const gameStore = useGameStore()
const detailDialog = useDetailDialogStore()
@@ -236,6 +237,11 @@
if (!currentPlanet) return { success: false }
const validation = shipValidation.validateDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.technologies)
if (!validation.valid) return { success: false, reason: validation.reason }
// 追踪资源消耗(在扣除前计算成本)
const totalCost = shipLogic.calculateDefenseCost(defenseType, quantity)
gameLogic.trackResourceConsumption(gameStore.player, totalCost)
const queueItem = shipValidation.executeDefenseBuild(currentPlanet, defenseType, quantity, gameStore.player.officers)
currentPlanet.buildQueue.push(queueItem)
return { success: true }

View File

@@ -7,12 +7,13 @@
<!-- 标签切换 -->
<Tabs v-model="activeTab" class="w-full">
<TabsList class="grid w-full grid-cols-3">
<TabsTrigger v-for="tab in fleetTabs" :key="tab.value" :value="tab.value">
<TabsList :class="['grid', 'w-full', showJumpGateTab ? 'grid-cols-4' : 'grid-cols-3']">
<TabsTrigger v-for="tab in visibleTabs" :key="tab.value" :value="tab.value">
{{ t(`fleetView.${tab.labelKey}`) }}
<Badge v-if="tab.value === 'missions' && gameStore.player.fleetMissions.length > 0" variant="destructive" class="ml-1">
{{ gameStore.player.fleetMissions.length }}
</Badge>
<Badge v-if="tab.value === 'jumpGate' && jumpGateReady" variant="default" class="ml-1"></Badge>
</TabsTrigger>
</TabsList>
@@ -59,6 +60,80 @@
</CardContent>
</Card>
<!-- 舰队预设 -->
<Card>
<CardHeader>
<div class="flex justify-between items-center">
<div>
<CardTitle>{{ t('fleetView.fleetPresets') }}</CardTitle>
<CardDescription>{{ t('fleetView.fleetPresetsDescription') }}</CardDescription>
</div>
<Button @click="saveAsPreset" variant="outline" size="sm" :disabled="fleetPresets.length >= MAX_PRESETS">
<Save class="h-4 w-4 mr-1" />
{{ t('fleetView.savePreset') }}
</Button>
</div>
</CardHeader>
<CardContent>
<div v-if="fleetPresets.length === 0" class="text-center py-4 text-muted-foreground text-sm">
{{ t('fleetView.noPresets') }}
</div>
<div v-else class="space-y-2">
<div
v-for="preset in fleetPresets"
:key="preset.id"
class="flex items-center justify-between p-3 border rounded-lg hover:bg-muted/50 transition-colors"
:class="{ 'ring-2 ring-primary': editingPresetId === preset.id }"
>
<div class="flex-1 cursor-pointer" @click="loadPreset(preset)">
<div class="flex items-center gap-2">
<Star class="h-4 w-4 text-yellow-500" />
<span class="font-medium">{{ preset.name }}</span>
</div>
<div class="text-xs text-muted-foreground mt-1 flex flex-wrap gap-2">
<span v-if="preset.targetPosition">
[{{ preset.targetPosition.galaxy }}:{{ preset.targetPosition.system }}:{{ preset.targetPosition.position }}]
</span>
<span v-if="preset.missionType">
{{ getMissionName(preset.missionType) }}
</span>
<span>{{ Object.entries(preset.fleet).filter(([_, count]) => count > 0).length }} {{ t('fleetView.shipTypes') }}</span>
</div>
</div>
<div class="flex items-center gap-1">
<Button v-if="editingPresetId === preset.id" @click="updatePreset(preset.id)" variant="default" size="sm">
{{ t('common.save') }}
</Button>
<Button
v-if="editingPresetId !== preset.id"
@click="editingPresetId = preset.id"
variant="ghost"
size="sm"
:title="t('fleetView.editPreset')"
>
<Pencil class="h-4 w-4" />
</Button>
<Button @click.stop="startRenamePreset(preset)" variant="ghost" size="sm" :title="t('fleetView.renamePreset')">
<Type class="h-4 w-4" />
</Button>
<Button
@click.stop="deletePreset(preset.id)"
variant="ghost"
size="sm"
class="text-destructive hover:text-destructive"
:title="t('fleetView.deletePreset')"
>
<Trash2 class="h-4 w-4" />
</Button>
</div>
</div>
</div>
<p v-if="editingPresetId" class="text-xs text-muted-foreground mt-2">
{{ t('fleetView.editingPresetHint') }}
</p>
</CardContent>
</Card>
<!-- 选择舰队 -->
<Card>
<CardHeader>
@@ -93,13 +168,27 @@
<CardHeader>
<CardTitle>{{ t('fleetView.targetCoordinates') }}</CardTitle>
</CardHeader>
<CardContent>
<CardContent class="space-y-4">
<div class="grid grid-cols-3 gap-2 sm:gap-4">
<div v-for="coord in coordinateFields" :key="coord.key" class="space-y-2">
<Label :for="coord.key" class="text-xs sm:text-sm">{{ t(`fleetView.${coord.key}`) }}</Label>
<Input :id="coord.key" v-model.number="targetPosition[coord.key]" type="number" :min="1" :max="coord.max" placeholder="1" />
</div>
</div>
<!-- 目标类型选择行星/月球 -->
<div v-if="hasMoonAtTargetPosition" class="flex items-center gap-4 p-3 bg-muted/50 rounded-lg">
<span class="text-sm font-medium">{{ t('fleetView.targetType') }}:</span>
<div class="flex gap-2">
<Button @click="targetIsMoon = false" :variant="!targetIsMoon ? 'default' : 'outline'" size="sm">
<Globe class="h-4 w-4 mr-1" />
{{ t('fleetView.planet') }}
</Button>
<Button @click="targetIsMoon = true" :variant="targetIsMoon ? 'default' : 'outline'" size="sm">
<Moon class="h-4 w-4 mr-1" />
{{ t('fleetView.moon') }}
</Button>
</div>
</div>
</CardContent>
</Card>
@@ -278,6 +367,108 @@
</CardContent>
</Card>
</TabsContent>
<!-- 跳跃门 -->
<TabsContent v-if="showJumpGateTab" value="jumpGate" class="mt-4 space-y-4">
<!-- 跳跃门状态 -->
<Card>
<CardHeader>
<CardTitle class="flex items-center gap-2">
<Zap class="h-5 w-5" />
{{ t('fleetView.jumpGate') }}
</CardTitle>
<CardDescription>{{ t('fleetView.jumpGateDescription') }}</CardDescription>
</CardHeader>
<CardContent>
<!-- 冷却状态 -->
<div v-if="!jumpGateReady" class="p-4 bg-muted/50 rounded-lg">
<div class="flex items-center gap-2 text-amber-600 dark:text-amber-400">
<Clock class="h-4 w-4" />
<span class="font-medium">{{ t('fleetView.jumpGateCooldown') }}</span>
</div>
<div class="mt-2 flex items-center gap-2">
<span class="text-sm text-muted-foreground">{{ t('fleetView.jumpGateCooldownRemaining') }}:</span>
<span class="font-bold">{{ formatTime(Math.floor(jumpGateCooldownRemaining / 1000)) }}</span>
</div>
<Progress :model-value="100 - (jumpGateCooldownRemaining / 3600000) * 100" class="mt-2" />
</div>
<!-- 就绪状态 -->
<div v-else class="p-4 bg-green-500/10 rounded-lg">
<div class="flex items-center gap-2 text-green-600 dark:text-green-400">
<Check class="h-4 w-4" />
<span class="font-medium">{{ t('fleetView.jumpGateReady') }}</span>
</div>
</div>
</CardContent>
</Card>
<!-- 选择目标月球 -->
<Card v-if="jumpGateReady">
<CardHeader>
<CardTitle>{{ t('fleetView.jumpGateSelectTarget') }}</CardTitle>
</CardHeader>
<CardContent>
<div v-if="availableJumpGateMoons.length === 0" class="text-center py-4 text-muted-foreground">
{{ t('fleetView.jumpGateNoTargetMoons') }}
</div>
<div v-else class="space-y-2">
<div
v-for="moon in availableJumpGateMoons"
:key="moon.id"
class="p-3 border rounded-lg cursor-pointer transition-colors"
:class="selectedJumpGateTarget?.id === moon.id ? 'ring-2 ring-primary bg-primary/10' : 'hover:bg-muted/50'"
@click="selectedJumpGateTarget = moon"
>
<div class="flex items-center justify-between">
<div>
<span class="font-medium">{{ moon.name }}</span>
<span class="text-sm text-muted-foreground ml-2">
[{{ moon.position.galaxy }}:{{ moon.position.system }}:{{ moon.position.position }}]
</span>
</div>
<Badge v-if="isJumpGateMoonReady(moon)" variant="default">{{ t('fleetView.jumpGateReady') }}</Badge>
</div>
</div>
</div>
</CardContent>
</Card>
<!-- 选择传送舰队 -->
<Card v-if="jumpGateReady && selectedJumpGateTarget">
<CardHeader>
<CardTitle>{{ t('fleetView.jumpGateSelectFleet') }}</CardTitle>
</CardHeader>
<CardContent>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
<div v-for="(count, shipType) in planet.fleet" :key="shipType" class="space-y-2">
<Label :for="`jump-ship-${shipType}`" class="text-xs sm:text-sm">
{{ SHIPS[shipType].name }} ({{ t('fleetView.available') }}: {{ count }})
</Label>
<div class="flex gap-2">
<Input
:id="`jump-ship-${shipType}`"
v-model.number="jumpGateFleet[shipType]"
type="number"
min="0"
:max="count"
placeholder="0"
class="text-sm"
/>
<Button @click="jumpGateFleet[shipType] = count" variant="outline" size="sm">{{ t('fleetView.all') }}</Button>
</div>
</div>
</div>
<!-- 传送按钮 -->
<div class="mt-6">
<Button @click="executeJumpGateTransfer" :disabled="!canExecuteJumpGate" class="w-full">
<Zap class="h-4 w-4 mr-2" />
{{ t('fleetView.jumpGateTransfer') }}
</Button>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
<!-- 提示对话框 -->
@@ -295,6 +486,40 @@
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<!-- 预设名称对话框 -->
<AlertDialog :open="showPresetNameDialog" @update:open="showPresetNameDialog = $event">
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>
{{ pendingPresetAction === 'save' ? t('fleetView.savePresetTitle') : t('fleetView.renamePresetTitle') }}
</AlertDialogTitle>
<AlertDialogDescription>
{{ pendingPresetAction === 'save' ? t('fleetView.savePresetDescription') : t('fleetView.renamePresetDescription') }}
</AlertDialogDescription>
</AlertDialogHeader>
<div class="py-4">
<Label for="preset-name">{{ t('fleetView.presetName') }}</Label>
<Input
id="preset-name"
v-model="editingPresetName"
:placeholder="t('fleetView.presetNamePlaceholder')"
class="mt-2"
@keyup.enter="handlePresetNameConfirm"
/>
</div>
<AlertDialogFooter>
<AlertDialogCancel
@click="() => { showPresetNameDialog = false; pendingPresetAction = null }"
>
{{ t('common.cancel') }}
</AlertDialogCancel>
<AlertDialogAction @click="handlePresetNameConfirm" :disabled="!editingPresetName.trim()">
{{ t('common.confirm') }}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
</template>
@@ -307,7 +532,7 @@
import { computed, ref, onMounted, onUnmounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { ShipType, MissionType, BuildingType, TechnologyType } from '@/types/game'
import type { Fleet, Resources } from '@/types/game'
import type { Fleet, Resources, FleetPreset } from '@/types/game'
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'
@@ -329,7 +554,27 @@
} from '@/components/ui/alert-dialog'
import UnlockRequirement from '@/components/UnlockRequirement.vue'
import { Empty, EmptyContent, EmptyDescription } from '@/components/ui/empty'
import { Sword, Package, Rocket as RocketIcon, Eye, Users, Recycle, Skull, Gift, Compass } from 'lucide-vue-next'
import {
Sword,
Package,
Rocket as RocketIcon,
Eye,
Users,
Recycle,
Skull,
Gift,
Compass,
Save,
Trash2,
Pencil,
Star,
Type,
Zap,
Clock,
Check,
Globe,
Moon
} from 'lucide-vue-next'
import { formatNumber, formatTime } from '@/utils/format'
import * as shipValidation from '@/logic/shipValidation'
import * as fleetLogic from '@/logic/fleetLogic'
@@ -337,6 +582,8 @@
import * as officerLogic from '@/logic/officerLogic'
import * as publicLogic from '@/logic/publicLogic'
import * as diplomaticLogic from '@/logic/diplomaticLogic'
import * as gameLogic from '@/logic/gameLogic'
import * as moonLogic from '@/logic/moonLogic'
const route = useRoute()
const gameStore = useGameStore()
@@ -363,15 +610,127 @@
return publicLogic.getMaxFleetMissions(bonuses.additionalFleetSlots, computerTechLevel)
})
const activeTab = ref<'fleet' | 'send' | 'missions'>('fleet')
const activeTab = ref<'fleet' | 'send' | 'missions' | 'jumpGate'>('fleet')
// Tab 配置
const fleetTabs = [
{ value: 'fleet', labelKey: 'fleetOverview' },
{ value: 'send', labelKey: 'sendFleet' },
{ value: 'missions', labelKey: 'flightMissions' }
{ value: 'missions', labelKey: 'flightMissions' },
{ value: 'jumpGate', labelKey: 'jumpGate' }
] as const
// 跳跃门相关
const selectedJumpGateTarget = ref<typeof planet.value | null>(null)
const jumpGateFleet = ref<Partial<Fleet>>({
[ShipType.LightFighter]: 0,
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
})
// 是否显示跳跃门标签页(当前在月球上且有跳跃门)
const showJumpGateTab = computed(() => {
if (!planet.value) return false
if (!planet.value.isMoon) return false
const jumpGateLevel = planet.value.buildings[BuildingType.JumpGate] || 0
return jumpGateLevel > 0
})
// 跳跃门是否就绪(冷却完成)
const jumpGateReady = computed(() => {
if (!planet.value) return false
return moonLogic.isJumpGateReady(planet.value)
})
// 跳跃门剩余冷却时间
const jumpGateCooldownRemaining = computed(() => {
if (!planet.value) return 0
return moonLogic.getJumpGateCooldownRemaining(planet.value)
})
// 可用的目标月球(有跳跃门且冷却完成的其他月球)
const availableJumpGateMoons = computed(() => {
if (!planet.value) return []
return gameStore.player.planets.filter(p => {
if (p.id === planet.value?.id) return false // 排除当前月球
if (!p.isMoon) return false
const jumpGateLevel = p.buildings[BuildingType.JumpGate] || 0
if (jumpGateLevel <= 0) return false
return moonLogic.isJumpGateReady(p)
})
})
// 检查目标月球的跳跃门是否就绪
const isJumpGateMoonReady = (moon: typeof planet.value) => {
if (!moon) return false
return moonLogic.isJumpGateReady(moon)
}
// 是否可以执行跳跃门传送
const canExecuteJumpGate = computed(() => {
if (!planet.value || !selectedJumpGateTarget.value) return false
if (!jumpGateReady.value) return false
// 检查是否选择了至少一艘舰船
const totalShips = Object.values(jumpGateFleet.value).reduce((sum, count) => sum + (count || 0), 0)
return totalShips > 0
})
// 执行跳跃门传送
const executeJumpGateTransfer = () => {
if (!planet.value || !selectedJumpGateTarget.value) return
if (!canExecuteJumpGate.value) return
const sourceMoon = planet.value
const targetMoon = selectedJumpGateTarget.value
// 转移舰队
Object.entries(jumpGateFleet.value).forEach(([shipType, count]) => {
if (count && count > 0) {
const ship = shipType as ShipType
// 从源月球扣除
if (sourceMoon.fleet[ship] >= count) {
sourceMoon.fleet[ship] -= count
// 添加到目标月球
targetMoon.fleet[ship] = (targetMoon.fleet[ship] || 0) + count
}
}
})
// 设置两个跳跃门的冷却时间
moonLogic.useJumpGate(sourceMoon)
moonLogic.useJumpGate(targetMoon)
// 重置跳跃门舰队选择
Object.keys(jumpGateFleet.value).forEach(key => {
jumpGateFleet.value[key as ShipType] = 0
})
selectedJumpGateTarget.value = null
// 显示成功对话框
alertDialogTitle.value = t('fleetView.jumpGateSuccess')
alertDialogMessage.value = t('fleetView.jumpGateSuccessMessage', {
target: `${targetMoon.name} [${targetMoon.position.galaxy}:${targetMoon.position.system}:${targetMoon.position.position}]`
})
alertDialogCallback.value = null
alertDialogOpen.value = true
}
// 可见的标签页(根据是否有跳跃门动态显示)
const visibleTabs = computed(() => {
if (showJumpGateTab.value) {
return fleetTabs
}
return fleetTabs.filter(tab => tab.value !== 'jumpGate')
})
// 选择的舰队
const selectedFleet = ref<Partial<Fleet>>({
[ShipType.LightFighter]: 0,
@@ -390,6 +749,20 @@
// 目标坐标
const targetPosition = ref({ galaxy: 1, system: 1, position: 1 })
// 目标是否为月球(用于区分同坐标的行星和月球)
const targetIsMoon = ref(false)
// 检查目标位置是否有月球(玩家自己的)
const hasMoonAtTargetPosition = computed(() => {
return gameStore.player.planets.some(
p =>
p.isMoon &&
p.position.galaxy === targetPosition.value.galaxy &&
p.position.system === targetPosition.value.system &&
p.position.position === targetPosition.value.position
)
})
// 坐标字段配置
const coordinateFields: { key: keyof typeof targetPosition.value; max: number }[] = [
{ key: 'galaxy', max: 9 },
@@ -463,6 +836,227 @@
// 是否为赠送模式
const isGiftMode = ref(false)
// 舰队预设相关状态
const MAX_PRESETS = 3
const editingPresetId = ref<string | null>(null)
const editingPresetName = ref('')
const showPresetNameDialog = ref(false)
const pendingPresetAction = ref<'save' | 'rename' | null>(null)
// 获取预设列表
const fleetPresets = computed(() => gameStore.player.fleetPresets || [])
// 生成唯一ID
const generatePresetId = (): string => {
return `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
}
// 保存当前配置为预设
const saveAsPreset = () => {
if (fleetPresets.value.length >= MAX_PRESETS) {
alertDialogTitle.value = t('fleetView.presetLimitReached')
alertDialogMessage.value = t('fleetView.presetLimitReachedMessage', { max: MAX_PRESETS.toString() })
alertDialogCallback.value = null
alertDialogOpen.value = true
return
}
// 检查是否有选择舰船
const hasShips = Object.values(selectedFleet.value).some(count => count > 0)
if (!hasShips) {
alertDialogTitle.value = t('fleetView.presetError')
alertDialogMessage.value = t('fleetView.presetNoShips')
alertDialogCallback.value = null
alertDialogOpen.value = true
return
}
pendingPresetAction.value = 'save'
editingPresetName.value = t('fleetView.presetDefaultName', { number: (fleetPresets.value.length + 1).toString() })
showPresetNameDialog.value = true
}
// 确认保存预设
const confirmSavePreset = () => {
if (!editingPresetName.value.trim()) return
// 只保存数量大于0的舰船
const fleetToSave: Partial<Fleet> = {}
for (const [shipType, count] of Object.entries(selectedFleet.value)) {
if (count && count > 0) {
fleetToSave[shipType as ShipType] = count
}
}
// 只保存数量大于0的资源
const cargoToSave: Partial<Resources> | undefined =
selectedMission.value === MissionType.Transport
? {
metal: cargo.value.metal || 0,
crystal: cargo.value.crystal || 0,
deuterium: cargo.value.deuterium || 0,
darkMatter: cargo.value.darkMatter || 0
}
: undefined
const newPreset: FleetPreset = {
id: generatePresetId(),
name: editingPresetName.value.trim(),
fleet: fleetToSave,
targetPosition: {
galaxy: targetPosition.value.galaxy,
system: targetPosition.value.system,
position: targetPosition.value.position
},
missionType: selectedMission.value,
cargo: cargoToSave
}
if (!gameStore.player.fleetPresets) {
gameStore.player.fleetPresets = []
}
gameStore.player.fleetPresets.push(newPreset)
showPresetNameDialog.value = false
editingPresetName.value = ''
pendingPresetAction.value = null
}
// 加载预设
const loadPreset = (preset: FleetPreset) => {
// 加载舰队配置
Object.keys(selectedFleet.value).forEach(key => {
selectedFleet.value[key as ShipType] = preset.fleet[key as ShipType] || 0
})
// 加载目标坐标
if (preset.targetPosition) {
targetPosition.value = { ...preset.targetPosition }
}
// 加载任务类型
if (preset.missionType) {
selectedMission.value = preset.missionType
}
// 加载运输资源
if (preset.cargo && preset.missionType === MissionType.Transport) {
cargo.value = {
metal: preset.cargo.metal || 0,
crystal: preset.cargo.crystal || 0,
deuterium: preset.cargo.deuterium || 0,
darkMatter: preset.cargo.darkMatter || 0,
energy: 0
}
}
}
// 更新预设(点击预设后修改内容)
const updatePreset = (presetId: string) => {
const presetIndex = gameStore.player.fleetPresets?.findIndex(p => p.id === presetId)
if (presetIndex === undefined || presetIndex === -1) return
const hasShips = Object.values(selectedFleet.value).some(count => count > 0)
if (!hasShips) {
alertDialogTitle.value = t('fleetView.presetError')
alertDialogMessage.value = t('fleetView.presetNoShips')
alertDialogCallback.value = null
alertDialogOpen.value = true
return
}
const existingPreset = gameStore.player.fleetPresets![presetIndex]
if (!existingPreset) return
// 只保存数量大于0的舰船
const fleetToSave: Partial<Fleet> = {}
for (const [shipType, count] of Object.entries(selectedFleet.value)) {
if (count && count > 0) {
fleetToSave[shipType as ShipType] = count
}
}
// 只保存数量大于0的资源
const cargoToSave: Partial<Resources> | undefined =
selectedMission.value === MissionType.Transport
? {
metal: cargo.value.metal || 0,
crystal: cargo.value.crystal || 0,
deuterium: cargo.value.deuterium || 0,
darkMatter: cargo.value.darkMatter || 0
}
: undefined
const updatedPreset: FleetPreset = {
id: existingPreset.id,
name: existingPreset.name,
fleet: fleetToSave,
targetPosition: {
galaxy: targetPosition.value.galaxy,
system: targetPosition.value.system,
position: targetPosition.value.position
},
missionType: selectedMission.value,
cargo: cargoToSave
}
gameStore.player.fleetPresets![presetIndex] = updatedPreset
editingPresetId.value = null
}
// 开始编辑预设名称
const startRenamePreset = (preset: FleetPreset) => {
// 保存要重命名的预设ID但不进入编辑内容模式
editingPresetName.value = preset.name
pendingPresetAction.value = 'rename'
// 使用临时变量存储要重命名的预设ID
renameTargetPresetId.value = preset.id
showPresetNameDialog.value = true
}
// 要重命名的预设ID与编辑预设内容分开
const renameTargetPresetId = ref<string | null>(null)
// 确认重命名预设
const confirmRenamePreset = () => {
if (!editingPresetName.value.trim() || !renameTargetPresetId.value) return
const preset = gameStore.player.fleetPresets?.find(p => p.id === renameTargetPresetId.value)
if (preset) {
preset.name = editingPresetName.value.trim()
}
showPresetNameDialog.value = false
renameTargetPresetId.value = null
editingPresetName.value = ''
pendingPresetAction.value = null
}
// 删除预设
const deletePreset = (presetId: string) => {
const preset = gameStore.player.fleetPresets?.find(p => p.id === presetId)
if (!preset) return
alertDialogTitle.value = t('fleetView.deletePresetTitle')
alertDialogMessage.value = t('fleetView.deletePresetMessage', { name: preset.name })
alertDialogCallback.value = () => {
const index = gameStore.player.fleetPresets?.findIndex(p => p.id === presetId)
if (index !== undefined && index > -1) {
gameStore.player.fleetPresets!.splice(index, 1)
}
}
alertDialogOpen.value = true
}
// 处理预设名称对话框确认
const handlePresetNameConfirm = () => {
if (pendingPresetAction.value === 'save') {
confirmSavePreset()
} else if (pendingPresetAction.value === 'rename') {
confirmRenamePreset()
}
}
// 监听目标NPC变化当目标不再是NPC时自动禁用赠送模式
watch(targetNpc, newValue => {
if (!newValue && isGiftMode.value) {
@@ -543,8 +1137,16 @@
if (!hasShips) return { valid: false, errorKey: 'fleetView.noShipsSelected' }
// 检查是否派遣到自己的星球
// 回收任务部署任务除外(回收残骸可能在同位置,部署可能到自己的月球)
if (planet.value && selectedMission.value !== MissionType.Recycle && selectedMission.value !== MissionType.Deploy) {
// 回收任务部署任务和运输任务除外:
// - 回收任务:可能回收同位置的残骸
// - 部署任务:可能部署到自己的月球
// - 运输任务可能从行星向同位置的月球运输资源OGame规则允许
if (
planet.value &&
selectedMission.value !== MissionType.Recycle &&
selectedMission.value !== MissionType.Deploy &&
selectedMission.value !== MissionType.Transport
) {
const isSamePlanet =
targetPosition.value.galaxy === planet.value.position.galaxy &&
targetPosition.value.system === planet.value.position.system &&
@@ -591,7 +1193,8 @@
targetPosition: { galaxy: number; system: number; position: number },
missionType: MissionType,
fleet: Partial<Fleet>,
cargo: Resources = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
cargo: Resources = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 },
isMoonTarget: boolean = false
): boolean => {
if (!gameStore.currentPlanet) return false
const currentMissions = gameStore.player.fleetMissions.length
@@ -604,6 +1207,13 @@
gameStore.player.technologies
)
if (!validation.valid) return false
// 追踪燃料消耗(同时计入资源消耗和燃料统计)
if (validation.fuelNeeded && validation.fuelNeeded > 0) {
gameLogic.trackResourceConsumption(gameStore.player, { deuterium: validation.fuelNeeded })
gameLogic.trackFuelConsumption(gameStore.player, validation.fuelNeeded)
}
const shouldDeductCargo = missionType === MissionType.Transport
shipValidation.executeFleetDispatch(gameStore.currentPlanet, fleet, validation.fuelNeeded!, shouldDeductCargo, cargo)
const distance = fleetLogic.calculateDistance(gameStore.currentPlanet.position, targetPosition)
@@ -620,6 +1230,11 @@
flightTime
)
// 如果目标是月球,设置标记
if (isMoonTarget) {
mission.targetIsMoon = true
}
// 如果是赠送模式,标记任务
if (missionType === MissionType.Transport && isGiftMode.value && targetNpc.value) {
mission.isGift = true
@@ -655,7 +1270,8 @@
targetPosition.value,
selectedMission.value,
fleet,
selectedMission.value === MissionType.Transport ? cargo.value : undefined
selectedMission.value === MissionType.Transport ? cargo.value : undefined,
targetIsMoon.value
)
if (success) {

View File

@@ -229,6 +229,15 @@
</p>
</PopoverContent>
</Popover>
<!-- NPC难度等级徽章 -->
<Badge
v-if="getNpcDifficultyLevel(slot.planet) !== null"
:variant="getDifficultyBadgeVariant(getNpcDifficultyLevel(slot.planet))"
class="text-xs flex-shrink-0"
:class="getDifficultyLevelColor(getNpcDifficultyLevel(slot.planet))"
>
Lv.{{ getNpcDifficultyLevel(slot.planet) }}
</Badge>
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
<PopoverTrigger as-child>
<Badge
@@ -260,6 +269,16 @@
</div>
</PopoverContent>
</Popover>
<!-- 月球徽章 -->
<Badge
v-if="slot.moon"
variant="outline"
class="text-xs cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-800 border-slate-400 dark:border-slate-600 text-slate-600 dark:text-slate-400 gap-1"
@click.stop="switchToPlanet(slot.moon.id)"
>
<Moon class="h-3 w-3" />
<span>{{ slot.moon.name }}</span>
</Badge>
</div>
</div>
<!-- 空位置 -->
@@ -345,6 +364,16 @@
<p>{{ t('galaxyView.sendGift') }}</p>
</TooltipContent>
</Tooltip>
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && canScanPlanet(slot.planet)">
<TooltipTrigger as-child>
<Button @click="showPhalanxScanDialog(slot.planet)" variant="outline" size="sm" class="h-8 w-8 p-0">
<Radar class="h-3 w-3" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{{ t('galaxyView.phalanxScan') }}</p>
</TooltipContent>
</Tooltip>
<Tooltip v-if="!slot.planet">
<TooltipTrigger as-child>
<Button @click="showPlanetActions(null, 'colonize', slot.position)" variant="outline" size="sm" class="h-8 w-8 p-0">
@@ -417,6 +446,15 @@
</TooltipContent>
</Tooltip>
</TooltipProvider>
<!-- NPC难度等级徽章 -->
<Badge
v-if="getNpcDifficultyLevel(slot.planet) !== null"
:variant="getDifficultyBadgeVariant(getNpcDifficultyLevel(slot.planet))"
class="text-xs"
:class="getDifficultyLevelColor(getNpcDifficultyLevel(slot.planet))"
>
Lv.{{ getNpcDifficultyLevel(slot.planet) }}
</Badge>
<!-- 残骸场徽章 -->
<Popover v-if="getDebrisFieldAt(currentGalaxy, currentSystem, slot.position)">
<PopoverTrigger as-child>
@@ -450,6 +488,16 @@
</div>
</PopoverContent>
</Popover>
<!-- 月球徽章 -->
<Badge
v-if="slot.moon"
variant="outline"
class="text-xs cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-800 border-slate-400 dark:border-slate-600 text-slate-600 dark:text-slate-400 gap-1"
@click.stop="switchToPlanet(slot.moon.id)"
>
<Moon class="h-3 w-3" />
<span>{{ slot.moon.name }}</span>
</Badge>
</div>
<!-- PC端坐标 -->
<p class="text-xs text-muted-foreground">
@@ -539,6 +587,16 @@
<p>{{ t('galaxyView.sendGift') }}</p>
</TooltipContent>
</Tooltip>
<Tooltip v-if="slot.planet && !isMyPlanet(slot.planet) && canScanPlanet(slot.planet)">
<TooltipTrigger as-child>
<Button @click="showPhalanxScanDialog(slot.planet)" variant="outline" size="sm" class="h-8 w-8 p-0">
<Radar class="h-3 w-3 sm:h-4 sm:w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{{ t('galaxyView.phalanxScan') }}</p>
</TooltipContent>
</Tooltip>
<Tooltip v-if="!slot.planet">
<TooltipTrigger as-child>
<Button @click="showPlanetActions(null, 'colonize', slot.position)" variant="outline" size="sm" class="h-8 w-8 p-0">
@@ -650,6 +708,97 @@
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<!-- 传感器阵列扫描对话框 -->
<Dialog :open="phalanxDialogOpen" @update:open="phalanxDialogOpen = $event">
<DialogContent class="max-w-lg">
<DialogHeader>
<DialogTitle class="flex items-center gap-2">
<Radar class="h-5 w-5" />
{{ t('galaxyView.phalanxScanTitle') }}
</DialogTitle>
<DialogDescription v-if="phalanxTargetPlanet">
{{
t('galaxyView.phalanxScanDescription').replace(
'{coordinates}',
`${phalanxTargetPlanet.position.galaxy}:${phalanxTargetPlanet.position.system}:${phalanxTargetPlanet.position.position}`
)
}}
</DialogDescription>
</DialogHeader>
<div class="space-y-4">
<!-- 扫描信息 -->
<div class="flex items-center justify-between text-sm">
<span class="text-muted-foreground">{{ t('galaxyView.phalanxCost') }}:</span>
<div class="flex items-center gap-1">
<ResourceIcon type="deuterium" size="sm" />
<span>{{ formatNumber(PHALANX_SCAN_COST) }}</span>
</div>
</div>
<!-- 扫描按钮 -->
<Button v-if="phalanxScanResults.length === 0 && !phalanxScanning" @click="executePhalanxScan" class="w-full">
<Radar class="h-4 w-4 mr-2" />
{{ t('galaxyView.phalanxScan') }}
</Button>
<!-- 扫描中 -->
<div v-if="phalanxScanning" class="flex items-center justify-center py-8">
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
<!-- 扫描结果 -->
<div v-if="!phalanxScanning && phalanxScanResults.length > 0" class="space-y-3">
<div class="text-sm font-medium">
{{ t('galaxyView.phalanxFleetDetected').replace('{count}', String(phalanxScanResults.length)) }}
</div>
<div class="space-y-2 max-h-64 overflow-y-auto">
<div v-for="fleet in phalanxScanResults" :key="fleet.id" class="p-3 border rounded-lg space-y-2 text-sm">
<div class="flex items-center justify-between">
<Badge>{{ getMissionTypeText(fleet.missionType) }}</Badge>
<Badge :variant="fleet.status === 'outbound' ? 'default' : 'secondary'">
{{ fleet.status === 'outbound' ? t('galaxyView.phalanxStatusOutbound') : t('galaxyView.phalanxStatusReturning') }}
</Badge>
</div>
<div class="grid grid-cols-2 gap-2 text-xs">
<div>
<span class="text-muted-foreground">{{ t('galaxyView.phalanxOrigin') }}:</span>
<span class="ml-1">
{{ formatCoords(getPlanetPositionById(fleet.originPlanetId) || { galaxy: 0, system: 0, position: 0 }) }}
</span>
</div>
<div>
<span class="text-muted-foreground">{{ t('galaxyView.phalanxDestination') }}:</span>
<span class="ml-1">{{ formatCoords(fleet.targetPosition) }}</span>
</div>
<div>
<span class="text-muted-foreground">{{ t('galaxyView.phalanxArrival') }}:</span>
<span class="ml-1">{{ formatTime(Math.max(0, Math.floor((fleet.arrivalTime - Date.now()) / 1000))) }}</span>
</div>
<div v-if="fleet.returnTime">
<span class="text-muted-foreground">{{ t('galaxyView.phalanxReturn') }}:</span>
<span class="ml-1">{{ formatTime(Math.max(0, Math.floor((fleet.returnTime - Date.now()) / 1000))) }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 无舰队 -->
<div
v-if="!phalanxScanning && phalanxScanResults.length === 0 && phalanxDialogOpen"
class="text-center py-4 text-muted-foreground"
>
{{ t('galaxyView.phalanxNoFleets') }}
</div>
</div>
<DialogFooter>
<Button variant="outline" @click="phalanxDialogOpen = false">{{ t('common.close') }}</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</template>
@@ -681,10 +830,13 @@
AlertDialogTitle
} from '@/components/ui/alert-dialog'
import ResourceIcon from '@/components/ResourceIcon.vue'
import { Home, Eye, Sword, Rocket, Recycle, Gift, Globe, Bomb } from 'lucide-vue-next'
import { Home, Eye, Sword, Rocket, Recycle, Gift, Globe, Bomb, Moon, Radar } from 'lucide-vue-next'
import { useRouter, useRoute } from 'vue-router'
import * as gameLogic from '@/logic/gameLogic'
import { formatNumber } from '@/utils/format'
import * as moonLogic from '@/logic/moonLogic'
import { formatNumber, formatTime } from '@/utils/format'
import { BuildingType, MissionType } from '@/types/game'
import type { FleetMission } from '@/types/game'
const gameStore = useGameStore()
const universeStore = useUniverseStore()
@@ -704,6 +856,12 @@
const missileTargetPlanet = ref<Planet | null>(null)
const missileCount = ref(1)
// 传感器阵列扫描对话框状态
const phalanxDialogOpen = ref(false)
const phalanxTargetPlanet = ref<Planet | null>(null)
const phalanxScanResults = ref<FleetMission[]>([])
const phalanxScanning = ref(false)
const selectedGalaxy = ref(1)
const selectedSystem = ref(1)
const currentGalaxy = ref(1)
@@ -718,7 +876,7 @@
return npcStore.npcs.find(n => n.id === highlightNpcId.value) || null
})
const systemSlots = ref<Array<{ position: number; planet: Planet | null }>>([])
const systemSlots = ref<Array<{ position: number; planet: Planet | null; moon: Planet | null }>>([])
// 获取玩家的母星
const homePlanet = computed(() => {
@@ -770,18 +928,26 @@
}
})
const getSystemPlanets = (galaxy: number, system: number): Array<{ position: number; planet: Planet | null }> => {
const getSystemPlanets = (galaxy: number, system: number): Array<{ position: number; planet: Planet | null; moon: Planet | null }> => {
const positions = gameLogic.generateSystemPositions(galaxy, system)
return positions.map(pos => {
const key = gameLogic.generatePositionKey(galaxy, system, pos.position)
// 先从玩家星球中查找,再从宇宙地图中查找
// 先从玩家星球中查找(非月球),再从宇宙地图中查找
const planet =
gameStore.player.planets.find(
p => p.position.galaxy === galaxy && p.position.system === system && p.position.position === pos.position
p => !p.isMoon && p.position.galaxy === galaxy && p.position.system === system && p.position.position === pos.position
) ||
universeStore.planets[key] ||
null
return { position: pos.position, planet }
// 查找该位置的月球(如果有星球的话)
let moon: Planet | null = null
if (planet) {
// 从玩家星球中查找月球
moon = gameStore.player.planets.find(p => p.isMoon && p.parentPlanetId === planet.id) || null
}
return { position: pos.position, planet, moon }
})
}
@@ -898,6 +1064,32 @@
return planet.name
}
// 获取NPC难度等级
const getNpcDifficultyLevel = (planet: Planet | null): number | null => {
const npc = getPlanetNPC(planet)
return npc?.difficultyLevel ?? null
}
// 获取NPC难度等级颜色
const getDifficultyLevelColor = (level: number | null): string => {
if (level === null) return 'text-muted-foreground'
if (level <= 1) return 'text-green-600 dark:text-green-400' // 新手
if (level <= 2) return 'text-lime-600 dark:text-lime-400' // 简单
if (level <= 3) return 'text-yellow-600 dark:text-yellow-400' // 普通
if (level <= 4) return 'text-orange-600 dark:text-orange-400' // 困难
if (level <= 5) return 'text-red-600 dark:text-red-400' // 专家
if (level <= 6) return 'text-purple-600 dark:text-purple-400' // 大师
return 'text-pink-600 dark:text-pink-400' // 传奇及以上
}
// 获取NPC难度等级Badge样式
const getDifficultyBadgeVariant = (level: number | null): 'default' | 'secondary' | 'destructive' | 'outline' => {
if (level === null) return 'outline'
if (level <= 2) return 'secondary'
if (level <= 4) return 'default'
return 'destructive'
}
// 切换到指定星球
const switchToPlanet = (planetId: string) => {
gameStore.currentPlanetId = planetId
@@ -1030,4 +1222,172 @@
const secs = seconds % 60
return `${minutes}:${secs.toString().padStart(2, '0')}`
}
// ========== 传感器阵列扫描功能 ==========
// 获取拥有传感器阵列的月球列表
const moonsWithPhalanx = computed(() => {
return gameStore.player.planets.filter(p => {
if (!p.isMoon) return false
const phalanxLevel = p.buildings[BuildingType.SensorPhalanx] || 0
return phalanxLevel > 0
})
})
// 检查是否可以扫描目标(需要有传感器阵列的月球在范围内)
const canScanPlanet = (targetPlanet: Planet | null): boolean => {
if (!targetPlanet) return false
if (isMyPlanet(targetPlanet)) return false
// 检查是否有月球的传感器阵列可以扫描目标
return moonsWithPhalanx.value.some(moon => {
const phalanxLevel = moon.buildings[BuildingType.SensorPhalanx] || 0
return moonLogic.isInSensorPhalanxRange(moon.position, targetPlanet.position, phalanxLevel)
})
}
// 获取可以扫描目标的月球
const getMoonForScan = (targetPlanet: Planet): Planet | null => {
return (
moonsWithPhalanx.value.find(moon => {
const phalanxLevel = moon.buildings[BuildingType.SensorPhalanx] || 0
return moonLogic.isInSensorPhalanxRange(moon.position, targetPlanet.position, phalanxLevel)
}) || null
)
}
// 计算扫描消耗的氘每次扫描消耗5000氘
const PHALANX_SCAN_COST = 5000
// 显示传感器阵列扫描对话框
const showPhalanxScanDialog = (planet: Planet) => {
phalanxTargetPlanet.value = planet
phalanxScanResults.value = []
phalanxScanning.value = false
phalanxDialogOpen.value = true
}
// 根据星球ID获取星球坐标
const getPlanetPositionById = (planetId: string): { galaxy: number; system: number; position: number } | null => {
// 先从玩家星球中查找
const playerPlanet = gameStore.player.planets.find(p => p.id === planetId)
if (playerPlanet) return playerPlanet.position
// 再从NPC星球中查找
for (const npc of npcStore.npcs) {
const npcPlanet = npc.planets.find(p => p.id === planetId)
if (npcPlanet) return npcPlanet.position
}
// 从宇宙地图中查找
for (const key in universeStore.planets) {
const planet = universeStore.planets[key]
if (planet && planet.id === planetId) return planet.position
}
return null
}
// 执行传感器阵列扫描
const executePhalanxScan = () => {
if (!phalanxTargetPlanet.value) return
const scanMoon = getMoonForScan(phalanxTargetPlanet.value)
if (!scanMoon) {
alertDialogTitle.value = t('errors.scanFailed')
alertDialogMessage.value = t('galaxyView.phalanxNoMoon')
alertDialogOpen.value = true
return
}
// 检查氘是否足够
if (scanMoon.resources.deuterium < PHALANX_SCAN_COST) {
alertDialogTitle.value = t('errors.scanFailed')
alertDialogMessage.value = t('galaxyView.phalanxInsufficientDeuterium')
alertDialogOpen.value = true
return
}
// 扣除氘
scanMoon.resources.deuterium -= PHALANX_SCAN_COST
phalanxScanning.value = true
// 模拟扫描延迟
setTimeout(() => {
// 扫描NPC的舰队任务
const targetPos = phalanxTargetPlanet.value!.position
const npc = getPlanetNPC(phalanxTargetPlanet.value)
// 收集相关的舰队任务
const detectedFleets: FleetMission[] = []
// 检查NPC的舰队任务
if (npc) {
npc.fleetMissions?.forEach(mission => {
// 获取出发地坐标
const originPos = getPlanetPositionById(mission.originPlanetId)
// 检查任务是否与目标星球相关(出发地或目的地)
const isFromTarget =
originPos &&
originPos.galaxy === targetPos.galaxy &&
originPos.system === targetPos.system &&
originPos.position === targetPos.position
const isToTarget =
mission.targetPosition.galaxy === targetPos.galaxy &&
mission.targetPosition.system === targetPos.system &&
mission.targetPosition.position === targetPos.position
if (isFromTarget || isToTarget) {
detectedFleets.push(mission)
}
})
}
// 也检查玩家自己发往该星球的任务(自己的任务自己当然知道,但扫描也能看到)
gameStore.player.fleetMissions?.forEach(mission => {
const isToTarget =
mission.targetPosition.galaxy === targetPos.galaxy &&
mission.targetPosition.system === targetPos.system &&
mission.targetPosition.position === targetPos.position
if (isToTarget) {
detectedFleets.push(mission)
}
})
phalanxScanResults.value = detectedFleets
phalanxScanning.value = false
}, 1000)
}
// 获取任务类型文本
const getMissionTypeText = (missionType: MissionType): string => {
switch (missionType) {
case MissionType.Attack:
return t('fleetView.attack')
case MissionType.Transport:
return t('fleetView.transport')
case MissionType.Deploy:
return t('fleetView.deploy')
case MissionType.Spy:
return t('fleetView.spy')
case MissionType.Colonize:
return t('fleetView.colonize')
case MissionType.Recycle:
return t('fleetView.recycle')
case MissionType.Destroy:
return t('fleetView.destroy')
case MissionType.Expedition:
return t('fleetView.expedition')
default:
return missionType
}
}
// 格式化坐标
const formatCoords = (pos: { galaxy: number; system: number; position: number }): string => {
return `[${pos.galaxy}:${pos.system}:${pos.position}]`
}
</script>

View File

@@ -176,6 +176,7 @@
import { formatNumber, formatTime, formatDate, getResourceCostColor } from '@/utils/format'
import * as officerLogic from '@/logic/officerLogic'
import * as resourceLogic from '@/logic/resourceLogic'
import * as gameLogic from '@/logic/gameLogic'
import { useI18n } from '@/composables/useI18n'
import { useGameConfig } from '@/composables/useGameConfig'
@@ -250,6 +251,8 @@
if (!resourceLogic.checkResourcesAvailable(gameStore.currentPlanet.resources, cost)) {
return false
}
// 追踪资源消耗(在扣除前)
gameLogic.trackResourceConsumption(gameStore.player, cost)
resourceLogic.deductResources(gameStore.currentPlanet.resources, cost)
gameStore.player.officers[officerType] = officerLogic.createActiveOfficer(officerType, duration)
return true
@@ -276,6 +279,8 @@
if (!resourceLogic.checkResourcesAvailable(gameStore.currentPlanet.resources, cost)) {
return false
}
// 追踪资源消耗(在扣除前)
gameLogic.trackResourceConsumption(gameStore.player, cost)
resourceLogic.deductResources(gameStore.currentPlanet.resources, cost)
const now = Date.now()
gameStore.player.officers[officerType] = officerLogic.renewOfficerExpiration(gameStore.player.officers[officerType], duration, now)

View File

@@ -12,7 +12,6 @@
<!-- 月球信息 -->
<div v-if="!planet.isMoon && moon" class="mt-2">
<Button @click="switchToMoon" variant="outline" size="sm">
<span class="mr-2">🌙</span>
{{ t('planet.switchToMoon') }}
</Button>
</div>

View File

@@ -1,7 +1,7 @@
<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>
@@ -107,12 +107,14 @@
AlertDialogHeader,
AlertDialogTitle
} from '@/components/ui/alert-dialog'
import UnlockRequirement from '@/components/UnlockRequirement.vue'
import CardUnlockOverlay from '@/components/CardUnlockOverlay.vue'
import { Check, X } from 'lucide-vue-next'
import { formatNumber, getResourceCostColor } from '@/utils/format'
import * as publicLogic from '@/logic/publicLogic'
import * as researchLogic from '@/logic/researchLogic'
import * as researchValidation from '@/logic/researchValidation'
import * as gameLogic from '@/logic/gameLogic'
const gameStore = useGameStore()
const detailDialog = useDetailDialogStore()
@@ -146,6 +148,11 @@
)
if (!validation.valid) return false
const currentLevel = gameStore.player.technologies[techType] || 0
// 追踪资源消耗(在扣除前计算成本)
const cost = researchLogic.calculateTechnologyCost(techType, currentLevel + 1)
gameLogic.trackResourceConsumption(gameStore.player, cost)
const { queueItem } = researchValidation.executeTechnologyResearch(
gameStore.currentPlanet,
techType,

View File

@@ -31,13 +31,20 @@
<div class="grid grid-cols-2 sm:grid-cols-3 gap-3 sm:gap-4">
<Card v-for="shipType in Object.values(ShipType)" :key="shipType" class="relative">
<CardUnlockOverlay :requirements="SHIPS[shipType].requirements" />
<CardHeader class="pb-3">
<CardTitle
class="text-sm sm:text-base lg:text-lg cursor-pointer hover:text-primary transition-colors underline decoration-dotted underline-offset-4 mb-2"
@click="detailDialog.openShip(shipType)"
>
{{ SHIPS[shipType].name }}
</CardTitle>
<CardHeader>
<div class="mb-2">
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-start gap-2">
<CardTitle
class="text-sm sm:text-base lg:text-lg cursor-pointer hover:text-primary transition-colors underline decoration-dotted underline-offset-4 order-2 sm:order-1"
@click="detailDialog.openShip(shipType)"
>
{{ SHIPS[shipType].name }}
</CardTitle>
<Badge variant="secondary" class="text-xs whitespace-nowrap self-start order-1 sm:order-2">
{{ formatNumber(planet.fleet[shipType] || 0) }}
</Badge>
</div>
</div>
<CardDescription class="text-xs sm:text-sm">{{ SHIPS[shipType].description }}</CardDescription>
</CardHeader>
<CardContent>
@@ -146,6 +153,7 @@
import { computed, ref } from 'vue'
import { ShipType, BuildingType } from '@/types/game'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
@@ -165,6 +173,8 @@
import * as shipValidation from '@/logic/shipValidation'
import * as publicLogic from '@/logic/publicLogic'
import * as fleetStorageLogic from '@/logic/fleetStorageLogic'
import * as shipLogic from '@/logic/shipLogic'
import * as gameLogic from '@/logic/gameLogic'
const gameStore = useGameStore()
const detailDialog = useDetailDialogStore()
@@ -220,6 +230,11 @@
if (!gameStore.currentPlanet) return { success: false }
const validation = shipValidation.validateShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.technologies)
if (!validation.valid) return { success: false, reason: validation.reason }
// 追踪资源消耗(在扣除前计算成本)
const totalCost = shipLogic.calculateShipCost(shipType, quantity)
gameLogic.trackResourceConsumption(gameStore.player, totalCost)
const queueItem = shipValidation.executeShipBuild(gameStore.currentPlanet, shipType, quantity, gameStore.player.officers)
gameStore.currentPlanet.buildQueue.push(queueItem)
return { success: true }