Files
ogame-vue-ts/src/views/BattleSimulatorView.vue
谦君 9b9fda0400 feat: 新增NPC与外交逻辑,优化UI组件结构
重构并精简了部分UI组件,移除冗余弹窗与详情组件,新增NPC相关逻辑(npcBehaviorLogic、npcGrowthLogic、npcStore等)及外交逻辑(diplomaticLogic、DiplomacyView)。完善分页、标签、复选框等通用UI组件。优化战报弹窗,调整README下载链接为相对路径,修复部分国际化内容。
2025-12-15 08:23:45 +08:00

306 lines
11 KiB
Vue

<template>
<div class="container mx-auto p-4 sm:p-6 space-y-6">
<h1 class="text-2xl sm:text-3xl font-bold">{{ t('simulatorView.title') }}</h1>
<!-- 标签切换 -->
<Tabs v-model="activeTab" class="w-full">
<TabsList class="grid w-full grid-cols-2">
<TabsTrigger value="attacker">
<Sword class="h-4 w-4 mr-2" />
{{ t('simulatorView.attacker') }}
</TabsTrigger>
<TabsTrigger value="defender">
<Shield class="h-4 w-4 mr-2" />
{{ t('simulatorView.defender') }}
</TabsTrigger>
</TabsList>
<!-- 攻击方配置 -->
<TabsContent value="attacker" class="mt-4">
<Card>
<CardHeader>
<CardTitle>{{ t('simulatorView.attackerConfig') }}</CardTitle>
<CardDescription>{{ t('simulatorView.attackerConfigDesc') }}</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<!-- 舰队配置 -->
<div>
<h3 class="text-sm font-medium mb-3">{{ t('simulatorView.fleet') }}</h3>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
<div v-for="shipType in Object.values(ShipType)" :key="shipType" class="space-y-1">
<Label :for="`attacker-${shipType}`" class="text-xs">{{ SHIPS[shipType].name }}</Label>
<Input
:id="`attacker-${shipType}`"
:model-value="attackerFleet[shipType] ?? 0"
@update:model-value="val => (attackerFleet[shipType] = typeof val === 'number' ? val : 0)"
type="number"
min="0"
class="h-8"
/>
</div>
</div>
</div>
<!-- 科技等级 -->
<div>
<h3 class="text-sm font-medium mb-3">{{ t('simulatorView.techLevels') }}</h3>
<div class="grid grid-cols-3 gap-3">
<div v-for="techType in techTypes" :key="techType" class="space-y-1">
<Label :for="`attacker-${techType}`" class="text-xs">{{ t(`simulatorView.${techType}`) }}</Label>
<Input :id="`attacker-${techType}`" v-model.number="attackerTech[techType]" type="number" min="0" class="h-8" />
</div>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
<!-- 防守方配置 -->
<TabsContent value="defender" class="mt-4">
<Card>
<CardHeader>
<CardTitle>{{ t('simulatorView.defenderConfig') }}</CardTitle>
<CardDescription>{{ t('simulatorView.defenderConfigDesc') }}</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<!-- 舰队配置 -->
<div>
<h3 class="text-sm font-medium mb-3">{{ t('simulatorView.fleet') }}</h3>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
<div v-for="shipType in Object.values(ShipType)" :key="shipType" class="space-y-1">
<Label :for="`defender-${shipType}`" class="text-xs">{{ SHIPS[shipType].name }}</Label>
<Input
:id="`defender-${shipType}`"
:model-value="defenderFleet[shipType] ?? 0"
@update:model-value="val => (defenderFleet[shipType] = typeof val === 'number' ? val : 0)"
type="number"
min="0"
class="h-8"
/>
</div>
</div>
</div>
<!-- 防御设施 -->
<div>
<h3 class="text-sm font-medium mb-3">{{ t('simulatorView.defenseStructures') }}</h3>
<div class="grid grid-cols-2 md:grid-cols-3 gap-3">
<div v-for="defenseType in Object.values(DefenseType)" :key="defenseType" class="space-y-1">
<Label :for="`defense-${defenseType}`" class="text-xs">{{ DEFENSES[defenseType].name }}</Label>
<Input
:id="`defense-${defenseType}`"
:model-value="defenderDefense[defenseType] ?? 0"
@update:model-value="val => (defenderDefense[defenseType] = typeof val === 'number' ? val : 0)"
type="number"
min="0"
class="h-8"
/>
</div>
</div>
</div>
<!-- 科技等级 -->
<div>
<h3 class="text-sm font-medium mb-3">{{ t('simulatorView.techLevels') }}</h3>
<div class="grid grid-cols-3 gap-3">
<div v-for="techType in techTypes" :key="techType" class="space-y-1">
<Label :for="`defender-${techType}`" class="text-xs">{{ t(`simulatorView.${techType}`) }}</Label>
<Input :id="`defender-${techType}`" v-model.number="defenderTech[techType]" type="number" min="0" class="h-8" />
</div>
</div>
</div>
<!-- 防守方资源 -->
<div>
<h3 class="text-sm font-medium mb-3">{{ t('simulatorView.defenderResources') }}</h3>
<div class="grid grid-cols-3 gap-3">
<div v-for="resourceType in resourceTypes" :key="resourceType.key" class="space-y-1">
<Label :for="`defender-${resourceType.key}`" class="text-xs flex items-center gap-1">
<ResourceIcon :type="resourceType.key" size="sm" />
{{ t(`resources.${resourceType.key}`) }}
</Label>
<Input
:id="`defender-${resourceType.key}`"
v-model.number="defenderResources[resourceType.key]"
type="number"
min="0"
class="h-8"
/>
</div>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
<!-- 操作按钮 -->
<div class="flex gap-2">
<Button @click="runSimulation" class="flex-1" size="lg">
<Zap class="h-4 w-4 mr-2" />
{{ t('simulatorView.startSimulation') }}
</Button>
<Button @click="resetSimulation" variant="outline" size="lg">
<RotateCcw class="h-4 w-4 mr-2" />
{{ t('simulatorView.reset') }}
</Button>
</div>
<!-- 战斗结果对话框 -->
<BattleReportDialog v-model:open="showResultDialog" :report="simulationResult" />
</div>
</template>
<script setup lang="ts">
import { ref, toRaw } from 'vue'
import { useI18n } from '@/composables/useI18n'
import { useGameConfig } from '@/composables/useGameConfig'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { ShipType, DefenseType } from '@/types/game'
import type { Fleet, BattleResult } from '@/types/game'
import { workerManager } from '@/workers/workerManager'
import ResourceIcon from '@/components/ResourceIcon.vue'
import BattleReportDialog from '@/components/BattleReportDialog.vue'
import { Sword, Shield, Zap, RotateCcw } from 'lucide-vue-next'
import * as planetLogic from '@/logic/planetLogic'
const { t } = useI18n()
const { SHIPS, DEFENSES } = useGameConfig()
// 科技类型配置
const techTypes = ['weapon', 'shield', 'armor'] as const
// 资源类型配置(用于防守方资源输入)
const resourceTypes = [{ key: 'metal' as const }, { key: 'crystal' as const }, { key: 'deuterium' as const }]
// 动态初始化所有舰船类型为0
const initializeFleet = (): Partial<Fleet> => {
const fleet: Partial<Fleet> = {}
Object.values(ShipType).forEach(shipType => {
fleet[shipType] = 0
})
return fleet
}
// 攻击方配置
const attackerFleet = ref<Partial<Fleet>>(initializeFleet())
const activeTab = ref('attacker')
const attackerTech = ref({
weapon: 0,
shield: 0,
armor: 0
})
// 防守方配置
const defenderFleet = ref<Partial<Fleet>>(initializeFleet())
// 动态初始化所有防御类型为0
const initializeDefense = (): Partial<Record<DefenseType, number>> => {
const defense: Partial<Record<DefenseType, number>> = {}
Object.values(DefenseType).forEach(defenseType => {
defense[defenseType] = 0
})
return defense
}
const defenderDefense = ref<Partial<Record<DefenseType, number>>>(initializeDefense())
const defenderTech = ref({
weapon: 0,
shield: 0,
armor: 0
})
const defenderResources = ref({
metal: 100000,
crystal: 50000,
deuterium: 25000,
darkMatter: 100,
energy: 0
})
// 模拟结果
const simulationResult = ref<BattleResult | null>(null)
const showResultDialog = ref<boolean>(false)
// 运行模拟(使用 Web Worker 进行计算)
const runSimulation = async () => {
// 使用 toRaw 将 Vue 响应式对象转换为普通对象,以便传递给 Worker
const attackerSide = {
ships: toRaw(attackerFleet.value),
weaponTech: attackerTech.value.weapon,
shieldTech: attackerTech.value.shield,
armorTech: attackerTech.value.armor
}
const defenderSide = {
ships: toRaw(defenderFleet.value),
defense: toRaw(defenderDefense.value),
weaponTech: defenderTech.value.weapon,
shieldTech: defenderTech.value.shield,
armorTech: defenderTech.value.armor
}
// 使用 Worker 执行战斗模拟
const result = await workerManager.simulateBattle({
attacker: attackerSide,
defender: defenderSide
})
// 计算掠夺和残骸场
const plunder =
result.winner === 'attacker'
? await workerManager.calculatePlunder({
defenderResources: toRaw(defenderResources.value),
attackerFleet: result.attackerRemaining
})
: { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
const debrisField = await workerManager.calculateDebris({
attackerLosses: result.attackerLosses,
defenderLosses: result.defenderLosses
})
const moonChance = planetLogic.calculateMoonChance(debrisField) / 100 // 转换为 0-1 范围
simulationResult.value = {
id: `sim_${Date.now()}`,
timestamp: Date.now(),
attackerId: 'simulator_attacker',
defenderId: 'simulator_defender',
attackerPlanetId: 'sim_attacker',
defenderPlanetId: 'sim_defender',
attackerFleet: attackerFleet.value,
defenderFleet: defenderFleet.value,
defenderDefense: defenderDefense.value,
attackerLosses: result.attackerLosses,
defenderLosses: result.defenderLosses,
winner: result.winner,
plunder,
debrisField,
rounds: result.rounds,
attackerRemaining: result.attackerRemaining,
defenderRemaining: result.defenderRemaining,
roundDetails: result.roundDetails,
moonChance
}
// 显示结果对话框
showResultDialog.value = true
}
// 重置模拟
const resetSimulation = () => {
attackerFleet.value = initializeFleet()
defenderFleet.value = initializeFleet()
defenderDefense.value = initializeDefense()
attackerTech.value = { weapon: 0, shield: 0, armor: 0 }
defenderTech.value = { weapon: 0, shield: 0, armor: 0 }
simulationResult.value = null
showResultDialog.value = false
}
</script>