mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +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
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
93
src/logic/fleetStorageLogic.ts
Normal file
93
src/logic/fleetStorageLogic.ts
Normal 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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 星球对象
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user