feat: 新增战报弹窗与舰队模拟器,重构UI组件

新增 BattleReportDialog、SpyReportDialog、NumberWithTooltip 等组件,完善舰队模拟器功能。重构并引入 Sheet、Sidebar、Tooltip、Skeleton 等 UI 组件,优化界面结构。实现 battle.worker 支持战斗计算,增加 universeStore、fleetStorageLogic 等核心逻辑,完善多语言与类型定义。
This commit is contained in:
谦君
2025-12-13 11:14:23 +08:00
parent 8637e50115
commit 731d79673b
160 changed files with 6302 additions and 1931 deletions

View File

@@ -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

View File

@@ -1,17 +1,35 @@
import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer } from '@/types/game'
import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer, DebrisField } from '@/types/game'
import { ShipType, DefenseType, MissionType, BuildingType, OfficerType } from '@/types/game'
import { FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
import * as battleLogic from './battleLogic'
import * as moonLogic from './moonLogic'
import * as moonValidation from './moonValidation'
/**
* 计算两个星球之间的距离
* 使用类似 OGame 的距离计算公式
*/
export const calculateDistance = (
from: { galaxy: number; system: number; position: number },
to: { galaxy: number; system: number; position: number }
): number => {
return Math.sqrt(Math.pow(to.galaxy - from.galaxy, 2) + Math.pow(to.system - from.system, 2) + Math.pow(to.position - from.position, 2))
// 同一位置
if (from.galaxy === to.galaxy && from.system === to.system && from.position === to.position) {
return 5
}
// 同星系内不同位置
if (from.galaxy === to.galaxy && from.system === to.system) {
return 1000 + Math.abs(to.position - from.position) * 5
}
// 同系统内不同星系
if (from.galaxy === to.galaxy) {
return 2700 + Math.abs(to.system - from.system) * 95
}
// 不同系统
return 20000 + Math.abs(to.galaxy - from.galaxy) * 20000
}
/**
@@ -66,20 +84,20 @@ export const processTransportArrival = (mission: FleetMission, targetPlanet: Pla
/**
* 处理攻击任务到达
*/
export const processAttackArrival = (
export const processAttackArrival = async (
mission: FleetMission,
targetPlanet: Planet | undefined,
attacker: Player,
defender: Player | null,
allPlanets: Planet[]
): { battleResult: BattleResult; moon: Planet | null } | null => {
): Promise<{ battleResult: BattleResult; moon: Planet | null; debrisField: DebrisField | null } | null> => {
if (!targetPlanet || targetPlanet.ownerId === attacker.id) {
mission.status = 'returning'
return null
}
// 执行战斗
const battleResult = battleLogic.simulateBattle(
// 执行战斗(使用 Worker 进行异步计算)
const battleResult = await battleLogic.simulateBattle(
mission.fleet,
targetPlanet.fleet,
targetPlanet.defense,
@@ -141,7 +159,22 @@ export const processAttackArrival = (
}
}
return { battleResult, moon }
// 创建残骸场(如果有残骸)
let debrisField: DebrisField | null = null
const totalDebris = battleResult.debrisField.metal + battleResult.debrisField.crystal
if (totalDebris > 0) {
debrisField = {
id: `debris_${targetPlanet.position.galaxy}_${targetPlanet.position.system}_${targetPlanet.position.position}`,
position: targetPlanet.position,
resources: {
metal: battleResult.debrisField.metal,
crystal: battleResult.debrisField.crystal
},
createdAt: Date.now()
}
}
return { battleResult, moon, debrisField }
}
/**
@@ -177,7 +210,8 @@ export const processColonizeArrival = (
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
@@ -187,11 +221,13 @@ export const processColonizeArrival = (
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
[DefenseType.LargeShieldDome]: 0,
[DefenseType.PlanetaryShield]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: 200,
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
isMoon: false
}
@@ -250,6 +286,156 @@ export const processDeployArrival = (mission: FleetMission, targetPlanet: Planet
return true
}
/**
* 处理回收任务到达
*/
export const processRecycleArrival = (
mission: FleetMission,
debrisField: DebrisField | undefined
): { collectedResources: Pick<Resources, 'metal' | 'crystal'>; remainingDebris: Pick<Resources, 'metal' | 'crystal'> | null } | null => {
if (!debrisField) {
mission.status = 'returning'
return null
}
// 计算回收船的货舱容量
const recyclerCount = mission.fleet[ShipType.Recycler] || 0
const recyclerCapacity = 20000 // 每艘回收船容量20000
const totalCapacity = recyclerCount * recyclerCapacity
// 计算已装载的货物
const currentCargo = mission.cargo.metal + mission.cargo.crystal + mission.cargo.deuterium
// 剩余容量
const availableCapacity = totalCapacity - currentCargo
// 计算可以收集的资源
const totalDebris = debrisField.resources.metal + debrisField.resources.crystal
const collectedAmount = Math.min(totalDebris, availableCapacity)
// 按比例收集金属和晶体
const metalRatio = debrisField.resources.metal / totalDebris
const crystalRatio = debrisField.resources.crystal / totalDebris
const collectedMetal = Math.floor(collectedAmount * metalRatio)
const collectedCrystal = Math.floor(collectedAmount * crystalRatio)
// 更新任务货物
mission.cargo.metal += collectedMetal
mission.cargo.crystal += collectedCrystal
// 更新残骸场
const remainingMetal = debrisField.resources.metal - collectedMetal
const remainingCrystal = debrisField.resources.crystal - collectedCrystal
mission.status = 'returning'
return {
collectedResources: {
metal: collectedMetal,
crystal: collectedCrystal
},
remainingDebris:
remainingMetal > 0 || remainingCrystal > 0
? {
metal: remainingMetal,
crystal: remainingCrystal
}
: null
}
}
/**
* 计算行星毁灭概率
*/
export const calculateDestructionChance = (
deathstarCount: number,
planetaryShieldCount: number,
planetDefensePower: number
): number => {
// 基础摧毁概率:每艘死星 10%
let baseChance = deathstarCount * 10
// 行星护盾减少概率:每个护盾 -5%
const shieldReduction = planetaryShieldCount * 5
// 防御力量减少概率:每 10000 防御力量 -1%
const defensePowerReduction = Math.floor(planetDefensePower / 10000)
// 最终概率
let finalChance = baseChance - shieldReduction - defensePowerReduction
// 限制在 1% - 99% 之间
return Math.max(1, Math.min(99, finalChance))
}
/**
* 计算星球总防御力量
*/
export const calculatePlanetDefensePower = (
fleet: Partial<Fleet>,
defense: Partial<Record<DefenseType, number>>
): number => {
let totalPower = 0
// 计算舰队力量
Object.entries(fleet).forEach(([_shipType, count]) => {
if (count > 0) {
// 简单估算:每艘船的攻击力 + 护盾 + 装甲 / 10
totalPower += count * 100 // 简化计算
}
})
// 计算防御设施力量
Object.entries(defense).forEach(([_defenseType, count]) => {
if (count > 0) {
totalPower += count * 50 // 简化计算
}
})
return totalPower
}
/**
* 处理行星毁灭任务到达
*/
export const processDestroyArrival = (
mission: FleetMission,
targetPlanet: Planet | undefined,
attacker: Player
): { success: boolean; destructionChance: number; planetId?: string } | null => {
if (!targetPlanet || targetPlanet.ownerId === attacker.id) {
mission.status = 'returning'
return null
}
// 检查是否有死星
const deathstarCount = mission.fleet[ShipType.Deathstar] || 0
if (deathstarCount === 0) {
mission.status = 'returning'
return null
}
// 计算目标星球的防御力量
const planetaryShieldCount = targetPlanet.defense[DefenseType.PlanetaryShield] || 0
const defensePower = calculatePlanetDefensePower(targetPlanet.fleet, targetPlanet.defense)
// 计算摧毁概率
const destructionChance = calculateDestructionChance(deathstarCount, planetaryShieldCount, defensePower)
// 随机判断是否成功
const randomValue = Math.random() * 100
const success = randomValue < destructionChance
mission.status = 'returning'
return {
success,
destructionChance,
planetId: success ? targetPlanet.id : undefined
}
}
/**
* 处理舰队任务返回
*/
@@ -271,29 +457,39 @@ export const processFleetReturn = (mission: FleetMission, originPlanet: Planet):
/**
* 更新舰队任务状态
*/
export const updateFleetMissions = (
export const updateFleetMissions = async (
missions: FleetMission[],
planets: Map<string, Planet>,
debrisFields: Map<string, DebrisField>,
attacker: Player,
defender: Player | null,
now: number
): {
): Promise<{
completedMissions: string[]
battleReports: BattleResult[]
spyReports: SpyReport[]
newColonies: Planet[]
newMoons: Planet[]
} => {
newDebrisFields: DebrisField[]
updatedDebrisFields: DebrisField[]
removedDebrisFieldIds: string[]
destroyedPlanetIds: string[]
}> => {
const completedMissions: string[] = []
const battleReports: BattleResult[] = []
const spyReports: SpyReport[] = []
const newColonies: Planet[] = []
const newMoons: Planet[] = []
const newDebrisFields: DebrisField[] = []
const updatedDebrisFields: DebrisField[] = []
const removedDebrisFieldIds: string[] = []
const destroyedPlanetIds: string[] = []
// 获取所有星球列表(用于月球生成检查)
const allPlanets = Array.from(planets.values())
missions.forEach(mission => {
// 使用 for...of 以支持 await
for (const mission of missions) {
const originPlanet = attacker.planets.find(p => p.id === mission.originPlanetId)
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
@@ -306,8 +502,8 @@ export const updateFleetMissions = (
processTransportArrival(mission, targetPlanet)
break
case MissionType.Attack:
const attackResult = processAttackArrival(mission, targetPlanet, attacker, defender, allPlanets)
case MissionType.Attack: {
const attackResult = await processAttackArrival(mission, targetPlanet, attacker, defender, allPlanets)
if (attackResult) {
battleReports.push(attackResult.battleResult)
if (attackResult.moon) {
@@ -316,8 +512,12 @@ export const updateFleetMissions = (
const moonKey = `${attackResult.moon.position.galaxy}:${attackResult.moon.position.system}:${attackResult.moon.position.position}`
planets.set(moonKey, attackResult.moon)
}
if (attackResult.debrisField) {
newDebrisFields.push(attackResult.debrisField)
}
}
break
}
case MissionType.Colonize:
const newColony = processColonizeArrival(mission, targetPlanet, attacker.id)
@@ -340,6 +540,36 @@ export const updateFleetMissions = (
completedMissions.push(mission.id)
}
break
case MissionType.Recycle:
const debrisId = `debris_${mission.targetPosition.galaxy}_${mission.targetPosition.system}_${mission.targetPosition.position}`
const debrisField = debrisFields.get(debrisId)
const recycleResult = processRecycleArrival(mission, debrisField)
if (recycleResult) {
if (recycleResult.remainingDebris) {
// 更新残骸场
const updatedDebris: DebrisField = {
...debrisField!,
resources: recycleResult.remainingDebris
}
debrisFields.set(debrisId, updatedDebris)
updatedDebrisFields.push(updatedDebris)
} else {
// 残骸场已被完全收集,删除
debrisFields.delete(debrisId)
removedDebrisFieldIds.push(debrisId)
}
}
break
case MissionType.Destroy:
const destroyResult = processDestroyArrival(mission, targetPlanet, attacker)
if (destroyResult && destroyResult.success && destroyResult.planetId) {
// 星球被摧毁
destroyedPlanetIds.push(destroyResult.planetId)
planets.delete(targetKey)
}
break
}
}
@@ -350,9 +580,9 @@ export const updateFleetMissions = (
}
completedMissions.push(mission.id)
}
})
}
return { completedMissions, battleReports, spyReports, newColonies, newMoons }
return { completedMissions, battleReports, spyReports, newColonies, newMoons, newDebrisFields, updatedDebrisFields, removedDebrisFieldIds, destroyedPlanetIds }
}
/**

View File

@@ -0,0 +1,93 @@
/**
* 舰队仓储逻辑模块
* 处理舰队仓储容量计算和使用量统计
*/
import type { Planet, Fleet } from '@/types/game'
import { ShipType, BuildingType, TechnologyType } from '@/types/game'
import { SHIPS, FLEET_STORAGE_CONFIG, BUILDINGS, TECHNOLOGIES } from '@/config/gameConfig'
/**
* 计算舰队当前使用的仓储量
* @param fleet 舰队对象
* @returns 当前使用的仓储量
*/
export const calculateFleetStorageUsage = (fleet: Fleet): number => {
let totalUsage = 0
for (const shipType of Object.values(ShipType)) {
const shipCount = fleet[shipType] || 0
const shipConfig = SHIPS[shipType]
totalUsage += shipCount * shipConfig.storageUsage
}
return totalUsage
}
/**
* 计算星球的最大舰队仓储容量
* @param planet 星球对象
* @param technologies 玩家的科技等级
* @returns 最大舰队仓储容量
*/
export const calculateMaxFleetStorage = (
planet: Planet,
technologies: Record<TechnologyType, number>
): number => {
// 1. 基础仓储
let maxStorage = FLEET_STORAGE_CONFIG.baseStorage
// 2. 造船厂建筑加成(每个星球独立)
const shipyardLevel = planet.buildings[BuildingType.Shipyard] || 0
const shipyardBonus = BUILDINGS[BuildingType.Shipyard].fleetStorageBonus || 0
maxStorage += shipyardLevel * shipyardBonus
// 3. 计算机技术全局加成
const computerTechLevel = technologies[TechnologyType.ComputerTechnology] || 0
const computerTechBonus = TECHNOLOGIES[TechnologyType.ComputerTechnology].fleetStorageBonus || 0
maxStorage += computerTechLevel * computerTechBonus
return maxStorage
}
/**
* 检查是否有足够的舰队仓储空间建造新舰船
* @param planet 星球对象
* @param shipType 要建造的舰船类型
* @param quantity 要建造的数量
* @param technologies 玩家的科技等级
* @returns 是否有足够的空间
*/
export const hasEnoughFleetStorage = (
planet: Planet,
shipType: ShipType,
quantity: number,
technologies: Record<TechnologyType, number>
): boolean => {
const currentUsage = calculateFleetStorageUsage(planet.fleet)
const maxStorage = calculateMaxFleetStorage(planet, technologies)
const newShipUsage = SHIPS[shipType].storageUsage * quantity
return currentUsage + newShipUsage <= maxStorage
}
/**
* 计算当前可以建造的最大舰船数量(基于仓储限制)
* @param planet 星球对象
* @param shipType 要建造的舰船类型
* @param technologies 玩家的科技等级
* @returns 最大可建造数量
*/
export const getMaxBuildableShips = (
planet: Planet,
shipType: ShipType,
technologies: Record<TechnologyType, number>
): number => {
const currentUsage = calculateFleetStorageUsage(planet.fleet)
const maxStorage = calculateMaxFleetStorage(planet, technologies)
const availableStorage = maxStorage - currentUsage
const shipStorageUsage = SHIPS[shipType].storageUsage
if (shipStorageUsage === 0) return Number.MAX_SAFE_INTEGER
return Math.floor(availableStorage / shipStorageUsage)
}

View File

@@ -3,8 +3,9 @@ import { TechnologyType, OfficerType } from '@/types/game'
import * as officerLogic from './officerLogic'
import * as buildingLogic from './buildingLogic'
import * as researchLogic from './researchLogic'
import * as resourceLogic from './resourceLogic'
import * as pointsLogic from './pointsLogic'
import * as planetLogic from './planetLogic'
import * as resourceLogic from './resourceLogic'
/**
* 初始化玩家数据
@@ -102,22 +103,27 @@ export const processGameUpdate = (
pointsLogic.addPoints(player, points)
}
// 更新所有星球
// 更新所有星球资源(直接同步计算,避免 Worker 通信开销)
player.planets.forEach(planet => {
// 更新资源
resourceLogic.updatePlanetResources(planet, now, bonuses)
})
// 更新所有星球其他状态
player.planets.forEach(planet => {
// 检查建造队列
buildingLogic.completeBuildQueue(planet, now, onPointsEarned)
// 更新星球最大空间
if (planet.isMoon) {
planet.maxSpace = planetLogic.calculateMoonMaxSpace(planet)
} else {
const terraformingTechLevel = player.technologies[TechnologyType.TerraformingTechnology] || 0
planet.maxSpace = planetLogic.calculatePlanetMaxSpace(planet, terraformingTechLevel)
}
})
// 检查研究队列
const updatedResearchQueue = researchLogic.completeResearchQueue(
player.researchQueue,
player.technologies,
now,
onPointsEarned
)
const updatedResearchQueue = researchLogic.completeResearchQueue(player.researchQueue, player.technologies, now, onPointsEarned)
return {
updatedResearchQueue

View File

@@ -1,6 +1,6 @@
import type { Planet, Resources } from '@/types/game'
import { BuildingType, ShipType, DefenseType } from '@/types/game'
import { MOON_CONFIG } from '@/config/gameConfig'
import { MOON_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
/**
* 计算月球生成概率
@@ -67,7 +67,8 @@ export const tryGenerateMoon = (
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
@@ -77,11 +78,13 @@ export const tryGenerateMoon = (
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
[DefenseType.LargeShieldDome]: 0,
[DefenseType.PlanetaryShield]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: MOON_CONFIG.baseSize,
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
isMoon: true,
parentPlanetId: planetId
}

View File

@@ -1,6 +1,6 @@
import type { Planet, Resources } from '@/types/game'
import { ShipType, DefenseType, BuildingType } from '@/types/game'
import { MOON_CONFIG } from '@/config/gameConfig'
import { MOON_CONFIG, PLANET_CONFIG, FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
/**
* 创建初始星球
@@ -29,7 +29,8 @@ export const createInitialPlanet = (playerId: string, planetName: string = 'Home
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
@@ -39,11 +40,13 @@ export const createInitialPlanet = (playerId: string, planetName: string = 'Home
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
[DefenseType.LargeShieldDome]: 0,
[DefenseType.PlanetaryShield]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: 200,
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
isMoon: false
}
@@ -86,7 +89,8 @@ export const createNPCPlanet = (
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
},
defense: {
[DefenseType.RocketLauncher]: Math.floor(Math.random() * 100),
@@ -96,11 +100,13 @@ export const createNPCPlanet = (
[DefenseType.IonCannon]: Math.floor(Math.random() * 10),
[DefenseType.PlasmaTurret]: Math.floor(Math.random() * 5),
[DefenseType.SmallShieldDome]: Math.random() > 0.5 ? 1 : 0,
[DefenseType.LargeShieldDome]: Math.random() > 0.8 ? 1 : 0
[DefenseType.LargeShieldDome]: Math.random() > 0.8 ? 1 : 0,
[DefenseType.PlanetaryShield]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: 200,
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
isMoon: false
}
@@ -156,7 +162,8 @@ export const createMoon = (
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
@@ -166,11 +173,13 @@ export const createMoon = (
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
[DefenseType.LargeShieldDome]: 0,
[DefenseType.PlanetaryShield]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: MOON_CONFIG.baseSize,
maxFleetStorage: FLEET_STORAGE_CONFIG.baseStorage,
isMoon: true,
parentPlanetId: parentPlanet.id
}
@@ -191,3 +200,22 @@ export const calculateMoonMaxSpace = (moon: Planet): number => {
const lunarBaseLevel = moon.buildings[BuildingType.LunarBase] || 0
return MOON_CONFIG.baseSize + lunarBaseLevel * MOON_CONFIG.lunarBaseSpaceBonus
}
/**
* 计算行星空间上限
*/
export const calculatePlanetMaxSpace = (planet: Planet, terraformingTechLevel: number): number => {
if (planet.isMoon) return 0
// 基础空间
let maxSpace = PLANET_CONFIG.baseSize
// 地形改造器增加的空间
const terraformerLevel = planet.buildings[BuildingType.Terraformer] || 0
maxSpace += terraformerLevel * PLANET_CONFIG.terraformerSpaceBonus
// 地形改造技术全局增加空间
maxSpace += terraformingTechLevel * PLANET_CONFIG.terraformingTechSpaceBonus
return maxSpace
}

View File

@@ -4,11 +4,53 @@
*/
import { BuildingType, TechnologyType } from '@/types/game'
import type { Planet, Resources, Officer } from '@/types/game'
import type { Planet, Resources, Officer, BuildingConfig, TechnologyConfig } from '@/types/game'
import { OfficerType } from '@/types/game'
import * as officerLogic from '@/logic/officerLogic'
import * as resourceLogic from '@/logic/resourceLogic'
/**
* 获取特定等级的升级条件
* 合并基础 requirements 和等级门槛 levelRequirements
* @param config 建筑或科技配置
* @param targetLevel 目标等级
* @returns 合并后的前置条件
*/
export const getLevelRequirements = (
config: BuildingConfig | TechnologyConfig,
targetLevel: number
): Partial<Record<BuildingType | TechnologyType, number>> => {
const requirements: Partial<Record<BuildingType | TechnologyType, number>> = {}
// 1. 添加基础 requirements如果存在
if (config.requirements) {
Object.assign(requirements, config.requirements)
}
// 2. 添加等级门槛 requirements如果存在
if (config.levelRequirements) {
// 找出所有小于等于目标等级的门槛
const applicableLevels = Object.keys(config.levelRequirements)
.map(Number)
.filter(level => level <= targetLevel)
.sort((a, b) => a - b)
// 依次合并所有适用的等级要求(后面的覆盖前面的)
for (const level of applicableLevels) {
const levelReqs = config.levelRequirements[level]
if (levelReqs) {
// 合并要求,取最大值
for (const [key, value] of Object.entries(levelReqs)) {
const currentValue = requirements[key as BuildingType | TechnologyType] || 0
requirements[key as BuildingType | TechnologyType] = Math.max(currentValue, value)
}
}
}
}
return requirements
}
/**
* 检查建造/研发前置条件是否满足
* @param planet 星球对象

View File

@@ -52,20 +52,20 @@ export const calculateResourceProduction = (
const resourceBonus = 1 + (bonuses.resourceProductionBonus || 0) / 100
const darkMatterBonus = 1 + (bonuses.darkMatterProductionBonus || 0) / 100
// 计算电量情况
// 计算能量产出(每小时)
const energyProduction = calculateEnergyProduction(planet, { energyProductionBonus: bonuses.energyProductionBonus })
const energyConsumption = calculateEnergyConsumption(planet)
const energyBalance = energyProduction - energyConsumption
// 如果电量不足,资源产量按比例减少
const productionEfficiency = energyBalance >= 0 ? 1 : Math.max(0, energyProduction / energyConsumption)
// 检查当前能量是否充足
// 如果当前能量 <= 0矿场停止生产
const hasEnergy = planet.resources.energy > 0
const productionEfficiency = hasEnergy ? 1 : 0
return {
metal: metalMineLevel * 150 * Math.pow(1.1, metalMineLevel) * resourceBonus * productionEfficiency,
crystal: crystalMineLevel * 100 * Math.pow(1.1, crystalMineLevel) * resourceBonus * productionEfficiency,
deuterium: deuteriumSynthesizerLevel * 50 * Math.pow(1.1, deuteriumSynthesizerLevel) * resourceBonus * productionEfficiency,
darkMatter: darkMatterCollectorLevel * 2.5 * Math.pow(1.1, darkMatterCollectorLevel) * darkMatterBonus,
energy: energyBalance
metal: metalMineLevel * 1500 * Math.pow(1.5, metalMineLevel) * resourceBonus * productionEfficiency,
crystal: crystalMineLevel * 1000 * Math.pow(1.5, crystalMineLevel) * resourceBonus * productionEfficiency,
deuterium: deuteriumSynthesizerLevel * 500 * Math.pow(1.5, deuteriumSynthesizerLevel) * resourceBonus * productionEfficiency,
darkMatter: darkMatterCollectorLevel * 25 * Math.pow(1.5, darkMatterCollectorLevel) * darkMatterBonus,
energy: energyProduction
}
}
@@ -77,6 +77,7 @@ export const calculateResourceCapacity = (planet: Planet, storageCapacityBonus:
const crystalStorageLevel = planet.buildings[BuildingType.CrystalStorage] || 0
const deuteriumTankLevel = planet.buildings[BuildingType.DeuteriumTank] || 0
const darkMatterCollectorLevel = planet.buildings[BuildingType.DarkMatterCollector] || 0
const solarPlantLevel = planet.buildings[BuildingType.SolarPlant] || 0
const bonus = 1 + (storageCapacityBonus || 0) / 100
@@ -86,7 +87,7 @@ export const calculateResourceCapacity = (planet: Planet, storageCapacityBonus:
crystal: baseCapacity * Math.pow(2, crystalStorageLevel) * bonus,
deuterium: baseCapacity * Math.pow(2, deuteriumTankLevel) * bonus,
darkMatter: 1000 + darkMatterCollectorLevel * 100, // 暗物质容量较小
energy: 0 // 电量不存储,实时计算
energy: 1000 + solarPlantLevel * 500 // 能量容量基于太阳能电站等级
}
}
@@ -105,7 +106,24 @@ export const updatePlanetResources = (
): void => {
const timeDiff = (now - planet.lastUpdate) / 1000 // 转换为秒
// 计算资源产量(每小时)
// 计算能量消耗(每小时)
const energyConsumption = calculateEnergyConsumption(planet)
// 先增加能量产出
const energyProduction = calculateEnergyProduction(planet, { energyProductionBonus: bonuses.energyProductionBonus })
planet.resources.energy += (energyProduction * timeDiff) / 3600
// 限制能量上限
const capacity = calculateResourceCapacity(planet, bonuses.storageCapacityBonus)
planet.resources.energy = Math.min(planet.resources.energy, capacity.energy)
// 扣除能量消耗
planet.resources.energy -= (energyConsumption * timeDiff) / 3600
// 能量不能为负数最低为0
planet.resources.energy = Math.max(0, planet.resources.energy)
// 计算资源产量(会检查能量是否充足)
const production = calculateResourceProduction(planet, {
resourceProductionBonus: bonuses.resourceProductionBonus,
darkMatterProductionBonus: bonuses.darkMatterProductionBonus,
@@ -119,7 +137,6 @@ export const updatePlanetResources = (
planet.resources.darkMatter += (production.darkMatter * timeDiff) / 3600
// 限制资源上限
const capacity = calculateResourceCapacity(planet, bonuses.storageCapacityBonus)
planet.resources.metal = Math.min(planet.resources.metal, capacity.metal)
planet.resources.crystal = Math.min(planet.resources.crystal, capacity.crystal)
planet.resources.deuterium = Math.min(planet.resources.deuterium, capacity.deuterium)
@@ -159,3 +176,216 @@ export const addResources = (currentResources: Resources, amount: Resources): vo
currentResources.deuterium += amount.deuterium
currentResources.darkMatter += amount.darkMatter
}
/**
* 资源产量详细信息用于UI展示
*/
export interface ProductionBreakdown {
metal: ProductionDetail
crystal: ProductionDetail
deuterium: ProductionDetail
darkMatter: ProductionDetail
energy: ProductionDetail
}
export interface ProductionDetail {
baseProduction: number // 建筑基础产量
buildingLevel: number // 建筑等级
buildingName: string // 建筑名称(用于显示)
bonuses: ProductionBonus[] // 加成列表
finalProduction: number // 最终产量
}
export interface ProductionBonus {
name: string // 加成名称
value: number // 加成百分比或固定值
type: 'percentage' | 'multiplier' // 百分比加成或倍率
}
/**
* 能量消耗详细信息
*/
export interface ConsumptionBreakdown {
metalMine: ConsumptionDetail
crystalMine: ConsumptionDetail
deuteriumSynthesizer: ConsumptionDetail
total: number
}
export interface ConsumptionDetail {
buildingLevel: number
buildingName: string
consumption: number
}
/**
* 计算资源产量详细breakdown
*/
export const calculateProductionBreakdown = (
planet: Planet,
bonuses: {
resourceProductionBonus: number
darkMatterProductionBonus: number
energyProductionBonus: number
}
): ProductionBreakdown => {
const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0
const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0
const deuteriumSynthesizerLevel = planet.buildings[BuildingType.DeuteriumSynthesizer] || 0
const darkMatterCollectorLevel = planet.buildings[BuildingType.DarkMatterCollector] || 0
const solarPlantLevel = planet.buildings[BuildingType.SolarPlant] || 0
const hasEnergy = planet.resources.energy > 0
const productionEfficiency = hasEnergy ? 1 : 0
// 金属矿产量
const metalBase = metalMineLevel * 1500 * Math.pow(1.5, metalMineLevel)
const metalBonuses: ProductionBonus[] = []
if (bonuses.resourceProductionBonus > 0) {
metalBonuses.push({
name: 'officers.resourceBonus',
value: bonuses.resourceProductionBonus,
type: 'percentage'
})
}
if (!hasEnergy) {
metalBonuses.push({
name: 'resources.noEnergy',
value: -100,
type: 'percentage'
})
}
const metalFinal = metalBase * (1 + bonuses.resourceProductionBonus / 100) * productionEfficiency
// 晶体矿产量
const crystalBase = crystalMineLevel * 1000 * Math.pow(1.5, crystalMineLevel)
const crystalBonuses: ProductionBonus[] = []
if (bonuses.resourceProductionBonus > 0) {
crystalBonuses.push({
name: 'officers.resourceBonus',
value: bonuses.resourceProductionBonus,
type: 'percentage'
})
}
if (!hasEnergy) {
crystalBonuses.push({
name: 'resources.noEnergy',
value: -100,
type: 'percentage'
})
}
const crystalFinal = crystalBase * (1 + bonuses.resourceProductionBonus / 100) * productionEfficiency
// 重氢合成器产量
const deuteriumBase = deuteriumSynthesizerLevel * 500 * Math.pow(1.5, deuteriumSynthesizerLevel)
const deuteriumBonuses: ProductionBonus[] = []
if (bonuses.resourceProductionBonus > 0) {
deuteriumBonuses.push({
name: 'officers.resourceBonus',
value: bonuses.resourceProductionBonus,
type: 'percentage'
})
}
if (!hasEnergy) {
deuteriumBonuses.push({
name: 'resources.noEnergy',
value: -100,
type: 'percentage'
})
}
const deuteriumFinal = deuteriumBase * (1 + bonuses.resourceProductionBonus / 100) * productionEfficiency
// 暗物质收集器产量
const darkMatterBase = darkMatterCollectorLevel * 25 * Math.pow(1.5, darkMatterCollectorLevel)
const darkMatterBonuses: ProductionBonus[] = []
if (bonuses.darkMatterProductionBonus > 0) {
darkMatterBonuses.push({
name: 'officers.darkMatterBonus',
value: bonuses.darkMatterProductionBonus,
type: 'percentage'
})
}
const darkMatterFinal = darkMatterBase * (1 + bonuses.darkMatterProductionBonus / 100)
// 太阳能电站产量
const energyBase = solarPlantLevel * 50 * Math.pow(1.1, solarPlantLevel)
const energyBonuses: ProductionBonus[] = []
if (bonuses.energyProductionBonus > 0) {
energyBonuses.push({
name: 'officers.energyBonus',
value: bonuses.energyProductionBonus,
type: 'percentage'
})
}
const energyFinal = energyBase * (1 + bonuses.energyProductionBonus / 100)
return {
metal: {
baseProduction: metalBase,
buildingLevel: metalMineLevel,
buildingName: 'buildings.metalMine',
bonuses: metalBonuses,
finalProduction: metalFinal
},
crystal: {
baseProduction: crystalBase,
buildingLevel: crystalMineLevel,
buildingName: 'buildings.crystalMine',
bonuses: crystalBonuses,
finalProduction: crystalFinal
},
deuterium: {
baseProduction: deuteriumBase,
buildingLevel: deuteriumSynthesizerLevel,
buildingName: 'buildings.deuteriumSynthesizer',
bonuses: deuteriumBonuses,
finalProduction: deuteriumFinal
},
darkMatter: {
baseProduction: darkMatterBase,
buildingLevel: darkMatterCollectorLevel,
buildingName: 'buildings.darkMatterCollector',
bonuses: darkMatterBonuses,
finalProduction: darkMatterFinal
},
energy: {
baseProduction: energyBase,
buildingLevel: solarPlantLevel,
buildingName: 'buildings.solarPlant',
bonuses: energyBonuses,
finalProduction: energyFinal
}
}
}
/**
* 计算能量消耗详细breakdown
*/
export const calculateConsumptionBreakdown = (planet: Planet): ConsumptionBreakdown => {
const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0
const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0
const deuteriumSynthesizerLevel = planet.buildings[BuildingType.DeuteriumSynthesizer] || 0
const metalConsumption = metalMineLevel * 10 * Math.pow(1.1, metalMineLevel)
const crystalConsumption = crystalMineLevel * 10 * Math.pow(1.1, crystalMineLevel)
const deuteriumConsumption = deuteriumSynthesizerLevel * 15 * Math.pow(1.1, deuteriumSynthesizerLevel)
return {
metalMine: {
buildingLevel: metalMineLevel,
buildingName: 'buildings.metalMine',
consumption: metalConsumption
},
crystalMine: {
buildingLevel: crystalMineLevel,
buildingName: 'buildings.crystalMine',
consumption: crystalConsumption
},
deuteriumSynthesizer: {
buildingLevel: deuteriumSynthesizerLevel,
buildingName: 'buildings.deuteriumSynthesizer',
consumption: deuteriumConsumption
},
total: metalConsumption + crystalConsumption + deuteriumConsumption
}
}

View File

@@ -4,6 +4,7 @@ import * as shipLogic from './shipLogic'
import * as resourceLogic from './resourceLogic'
import * as officerLogic from './officerLogic'
import * as publicLogic from './publicLogic'
import * as fleetStorageLogic from './fleetStorageLogic'
/**
* 验证舰船建造的所有条件
@@ -29,6 +30,11 @@ export const validateShipBuild = (
return { valid: false, reason: 'errors.insufficientResources' }
}
// 检查舰队仓储空间
if (!fleetStorageLogic.hasEnoughFleetStorage(planet, shipType, quantity, technologies as Record<TechnologyType, number>)) {
return { valid: false, reason: 'errors.insufficientFleetStorage' }
}
return { valid: true }
}