mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 16:05:12 +08:00
feat: 新增战报弹窗与舰队模拟器,重构UI组件
新增 BattleReportDialog、SpyReportDialog、NumberWithTooltip 等组件,完善舰队模拟器功能。重构并引入 Sheet、Sidebar、Tooltip、Skeleton 等 UI 组件,优化界面结构。实现 battle.worker 支持战斗计算,增加 universeStore、fleetStorageLogic 等核心逻辑,完善多语言与类型定义。
This commit is contained in:
@@ -1,312 +1,65 @@
|
||||
import type { Fleet, Resources, BattleResult, Officer } from '@/types/game'
|
||||
import { DefenseType, ShipType, OfficerType } from '@/types/game'
|
||||
import { SHIPS, DEFENSES } from '@/config/gameConfig'
|
||||
import { DefenseType, OfficerType } from '@/types/game'
|
||||
import * as officerLogic from './officerLogic'
|
||||
|
||||
/**
|
||||
* 战斗单位(舰船或防御)
|
||||
*/
|
||||
interface BattleUnit {
|
||||
type: ShipType | DefenseType
|
||||
count: number
|
||||
attack: number
|
||||
shield: number
|
||||
armor: number
|
||||
isShip: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 战斗方
|
||||
*/
|
||||
interface BattleSide {
|
||||
fleet: BattleUnit[]
|
||||
defense: BattleUnit[]
|
||||
totalShields: number
|
||||
totalArmor: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备战斗方数据
|
||||
*/
|
||||
const prepareBattleSide = (fleet: Partial<Fleet>, defense: Partial<Record<DefenseType, number>>, defenseBonus: number = 0): BattleSide => {
|
||||
const side: BattleSide = {
|
||||
fleet: [],
|
||||
defense: [],
|
||||
totalShields: 0,
|
||||
totalArmor: 0
|
||||
}
|
||||
|
||||
// 添加舰船
|
||||
Object.entries(fleet).forEach(([shipType, count]) => {
|
||||
if (count > 0) {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
const unit: BattleUnit = {
|
||||
type: shipType as ShipType,
|
||||
count,
|
||||
attack: config.attack,
|
||||
shield: config.shield * (1 + defenseBonus / 100),
|
||||
armor: config.armor * (1 + defenseBonus / 100),
|
||||
isShip: true
|
||||
}
|
||||
side.fleet.push(unit)
|
||||
side.totalShields += unit.shield * count
|
||||
side.totalArmor += unit.armor * count
|
||||
}
|
||||
})
|
||||
|
||||
// 添加防御
|
||||
Object.entries(defense).forEach(([defenseType, count]) => {
|
||||
if (count > 0) {
|
||||
const config = DEFENSES[defenseType as DefenseType]
|
||||
const unit: BattleUnit = {
|
||||
type: defenseType as DefenseType,
|
||||
count,
|
||||
attack: config.attack,
|
||||
shield: config.shield * (1 + defenseBonus / 100),
|
||||
armor: config.armor * (1 + defenseBonus / 100),
|
||||
isShip: false
|
||||
}
|
||||
side.defense.push(unit)
|
||||
side.totalShields += unit.shield * count
|
||||
side.totalArmor += unit.armor * count
|
||||
}
|
||||
})
|
||||
|
||||
return side
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算一方的总攻击力
|
||||
*/
|
||||
const calculateTotalAttack = (side: BattleSide): number => {
|
||||
let total = 0
|
||||
side.fleet.forEach(unit => {
|
||||
total += unit.attack * unit.count
|
||||
})
|
||||
side.defense.forEach(unit => {
|
||||
total += unit.attack * unit.count
|
||||
})
|
||||
return total
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行一轮战斗
|
||||
*/
|
||||
const executeBattleRound = (attacker: BattleSide, defender: BattleSide): void => {
|
||||
// 攻击方对防御方造成伤害
|
||||
const attackerDamage = calculateTotalAttack(attacker)
|
||||
applyDamage(defender, attackerDamage)
|
||||
|
||||
// 防御方对攻击方造成伤害
|
||||
const defenderDamage = calculateTotalAttack(defender)
|
||||
applyDamage(attacker, defenderDamage)
|
||||
}
|
||||
|
||||
/**
|
||||
* 对一方施加伤害
|
||||
*/
|
||||
const applyDamage = (side: BattleSide, totalDamage: number): void => {
|
||||
let remainingDamage = totalDamage
|
||||
|
||||
// 先消耗护盾
|
||||
const totalShields = side.totalShields
|
||||
if (totalShields > 0) {
|
||||
const shieldAbsorption = Math.min(remainingDamage, totalShields)
|
||||
remainingDamage -= shieldAbsorption
|
||||
side.totalShields -= shieldAbsorption
|
||||
}
|
||||
|
||||
// 剩余伤害穿透护盾,破坏单位
|
||||
if (remainingDamage > 0) {
|
||||
destroyUnits(side, remainingDamage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据伤害摧毁单位
|
||||
*/
|
||||
const destroyUnits = (side: BattleSide, damage: number): void => {
|
||||
let remainingDamage = damage
|
||||
|
||||
// 随机选择单位摧毁
|
||||
const allUnits = [...side.fleet, ...side.defense]
|
||||
|
||||
while (remainingDamage > 0 && allUnits.some(u => u.count > 0)) {
|
||||
// 随机选择一个有数量的单位
|
||||
const availableUnits = allUnits.filter(u => u.count > 0)
|
||||
if (availableUnits.length === 0) break
|
||||
|
||||
const targetUnit = availableUnits[Math.floor(Math.random() * availableUnits.length)]
|
||||
if (!targetUnit) break // 安全检查
|
||||
|
||||
// 计算破坏概率(伤害 / 装甲)
|
||||
const destructionChance = Math.min(remainingDamage / targetUnit.armor, 1)
|
||||
|
||||
if (Math.random() < destructionChance) {
|
||||
targetUnit.count--
|
||||
side.totalArmor -= targetUnit.armor
|
||||
remainingDamage -= targetUnit.armor
|
||||
} else {
|
||||
// 未破坏,但消耗一部分伤害
|
||||
remainingDamage -= targetUnit.armor * destructionChance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查战斗是否结束
|
||||
*/
|
||||
const isBattleOver = (attacker: BattleSide, defender: BattleSide): boolean => {
|
||||
const attackerHasUnits = attacker.fleet.some(u => u.count > 0) || attacker.defense.some(u => u.count > 0)
|
||||
const defenderHasUnits = defender.fleet.some(u => u.count > 0) || defender.defense.some(u => u.count > 0)
|
||||
|
||||
return !attackerHasUnits || !defenderHasUnits
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算损失
|
||||
*/
|
||||
const calculateLosses = (
|
||||
initialSide: BattleSide,
|
||||
finalSide: BattleSide
|
||||
): { fleet: Partial<Fleet>; defense: Partial<Record<DefenseType, number>> } => {
|
||||
const losses: { fleet: Partial<Fleet>; defense: Partial<Record<DefenseType, number>> } = {
|
||||
fleet: {},
|
||||
defense: {}
|
||||
}
|
||||
|
||||
// 计算舰船损失
|
||||
initialSide.fleet.forEach((initialUnit, index) => {
|
||||
const finalUnit = finalSide.fleet[index]
|
||||
const lost = initialUnit.count - (finalUnit?.count || 0)
|
||||
if (lost > 0) {
|
||||
losses.fleet[initialUnit.type as ShipType] = lost
|
||||
}
|
||||
})
|
||||
|
||||
// 计算防御损失
|
||||
initialSide.defense.forEach((initialUnit, index) => {
|
||||
const finalUnit = finalSide.defense[index]
|
||||
const lost = initialUnit.count - (finalUnit?.count || 0)
|
||||
if (lost > 0) {
|
||||
losses.defense[initialUnit.type as DefenseType] = lost
|
||||
}
|
||||
})
|
||||
|
||||
return losses
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算残骸场
|
||||
*/
|
||||
const calculateDebrisField = (
|
||||
attackerLosses: Partial<Fleet>,
|
||||
defenderLosses: { fleet: Partial<Fleet>; defense: Partial<Record<DefenseType, number>> }
|
||||
): Resources => {
|
||||
const debris: Resources = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
const debrisRate = 0.3 // 30%的残骸回收率
|
||||
|
||||
// 攻击方舰船损失
|
||||
Object.entries(attackerLosses).forEach(([shipType, count]) => {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
debris.metal += config.cost.metal * count * debrisRate
|
||||
debris.crystal += config.cost.crystal * count * debrisRate
|
||||
})
|
||||
|
||||
// 防御方舰船损失
|
||||
Object.entries(defenderLosses.fleet).forEach(([shipType, count]) => {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
debris.metal += config.cost.metal * count * debrisRate
|
||||
debris.crystal += config.cost.crystal * count * debrisRate
|
||||
})
|
||||
|
||||
// 防御设施不产生残骸场(或产生较少)
|
||||
|
||||
return debris
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算掠夺资源
|
||||
*/
|
||||
const calculatePlunder = (availableResources: Resources, attackerFleet: Partial<Fleet>, cargoCapacity: number): Resources => {
|
||||
// 计算攻击方剩余运载能力
|
||||
let totalCapacity = 0
|
||||
Object.entries(attackerFleet).forEach(([shipType, count]) => {
|
||||
const config = SHIPS[shipType as ShipType]
|
||||
totalCapacity += config.cargoCapacity * count
|
||||
})
|
||||
|
||||
// 最多掠夺50%的资源
|
||||
const maxPlunder = Math.min(totalCapacity, cargoCapacity)
|
||||
const plunderRate = 0.5
|
||||
|
||||
const plunder: Resources = {
|
||||
metal: Math.min(availableResources.metal * plunderRate, maxPlunder * 0.5),
|
||||
crystal: Math.min(availableResources.crystal * plunderRate, maxPlunder * 0.3),
|
||||
deuterium: Math.min(availableResources.deuterium * plunderRate, maxPlunder * 0.2),
|
||||
darkMatter: 0, // 暗物质无法掠夺
|
||||
energy: 0
|
||||
}
|
||||
|
||||
return plunder
|
||||
}
|
||||
import { workerManager } from '@/workers/workerManager'
|
||||
|
||||
/**
|
||||
* 执行战斗模拟
|
||||
* 使用 Web Worker 在后台线程中执行计算密集型的战斗模拟
|
||||
*/
|
||||
export const simulateBattle = (
|
||||
export const simulateBattle = async (
|
||||
attackerFleet: Partial<Fleet>,
|
||||
defenderFleet: Partial<Fleet>,
|
||||
defenderDefense: Partial<Record<DefenseType, number>>,
|
||||
defenderResources: Resources,
|
||||
attackerOfficers: Record<OfficerType, Officer>,
|
||||
defenderOfficers: Record<OfficerType, Officer>
|
||||
): BattleResult => {
|
||||
): Promise<BattleResult> => {
|
||||
// 计算军官加成
|
||||
const attackerBonuses = officerLogic.calculateActiveBonuses(attackerOfficers, Date.now())
|
||||
const defenderBonuses = officerLogic.calculateActiveBonuses(defenderOfficers, Date.now())
|
||||
|
||||
// 准备战斗方
|
||||
const initialAttacker = prepareBattleSide(attackerFleet, {}, attackerBonuses.defenseBonus)
|
||||
const initialDefender = prepareBattleSide(defenderFleet, defenderDefense, defenderBonuses.defenseBonus)
|
||||
// 将防御加成转换为科技等级(简化:10%加成 = 1级科技)
|
||||
const attackerTechLevel = Math.floor(attackerBonuses.defenseBonus / 10)
|
||||
const defenderTechLevel = Math.floor(defenderBonuses.defenseBonus / 10)
|
||||
|
||||
// 复制战斗方用于战斗
|
||||
const attacker = JSON.parse(JSON.stringify(initialAttacker)) as BattleSide
|
||||
const defender = JSON.parse(JSON.stringify(initialDefender)) as BattleSide
|
||||
|
||||
// 战斗回合(最多6回合)
|
||||
let rounds = 0
|
||||
const maxRounds = 6
|
||||
|
||||
while (rounds < maxRounds && !isBattleOver(attacker, defender)) {
|
||||
executeBattleRound(attacker, defender)
|
||||
rounds++
|
||||
}
|
||||
|
||||
// 计算损失
|
||||
const attackerLosses = calculateLosses(initialAttacker, attacker).fleet
|
||||
const defenderLosses = calculateLosses(initialDefender, defender)
|
||||
|
||||
// 判断胜负
|
||||
let winner: 'attacker' | 'defender' | 'draw' = 'draw'
|
||||
const attackerSurvived = attacker.fleet.some(u => u.count > 0)
|
||||
const defenderSurvived = defender.fleet.some(u => u.count > 0) || defender.defense.some(u => u.count > 0)
|
||||
|
||||
if (attackerSurvived && !defenderSurvived) {
|
||||
winner = 'attacker'
|
||||
} else if (!attackerSurvived && defenderSurvived) {
|
||||
winner = 'defender'
|
||||
}
|
||||
|
||||
// 计算残骸场
|
||||
const debrisField = calculateDebrisField(attackerLosses, defenderLosses)
|
||||
// 使用 Worker 执行战斗模拟
|
||||
const simulationResult = await workerManager.simulateBattle({
|
||||
attacker: {
|
||||
ships: attackerFleet,
|
||||
weaponTech: 0, // 暂时不考虑武器科技
|
||||
shieldTech: attackerTechLevel,
|
||||
armorTech: attackerTechLevel
|
||||
},
|
||||
defender: {
|
||||
ships: defenderFleet,
|
||||
defense: defenderDefense,
|
||||
weaponTech: 0,
|
||||
shieldTech: defenderTechLevel,
|
||||
armorTech: defenderTechLevel
|
||||
},
|
||||
maxRounds: 6 // 最多6回合
|
||||
})
|
||||
|
||||
// 计算掠夺(仅攻击方胜利时)
|
||||
const plunder =
|
||||
winner === 'attacker'
|
||||
? calculatePlunder(defenderResources, attackerFleet, 10000)
|
||||
simulationResult.winner === 'attacker'
|
||||
? await workerManager.calculatePlunder({
|
||||
defenderResources,
|
||||
attackerFleet: simulationResult.attackerRemaining
|
||||
})
|
||||
: { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
|
||||
|
||||
// 计算残骸场
|
||||
const debrisField = await workerManager.calculateDebris({
|
||||
attackerLosses: simulationResult.attackerLosses,
|
||||
defenderLosses: simulationResult.defenderLosses
|
||||
})
|
||||
|
||||
// 计算月球生成概率(根据残骸场总量)
|
||||
const totalDebris = debrisField.metal + debrisField.crystal
|
||||
const moonChance = Math.min(totalDebris / 100000, 0.2) // 最高20%概率
|
||||
|
||||
// 生成战斗报告
|
||||
const battleResult: BattleResult = {
|
||||
id: `battle_${Date.now()}`,
|
||||
@@ -318,11 +71,17 @@ export const simulateBattle = (
|
||||
attackerFleet,
|
||||
defenderFleet,
|
||||
defenderDefense,
|
||||
attackerLosses,
|
||||
defenderLosses,
|
||||
winner,
|
||||
attackerLosses: simulationResult.attackerLosses,
|
||||
defenderLosses: simulationResult.defenderLosses,
|
||||
winner: simulationResult.winner,
|
||||
plunder,
|
||||
debrisField
|
||||
debrisField,
|
||||
// 新增详细信息
|
||||
rounds: simulationResult.rounds,
|
||||
attackerRemaining: simulationResult.attackerRemaining,
|
||||
defenderRemaining: simulationResult.defenderRemaining,
|
||||
roundDetails: simulationResult.roundDetails,
|
||||
moonChance
|
||||
}
|
||||
|
||||
return battleResult
|
||||
|
||||
Reference in New Issue
Block a user