feat: 初始化项目结构与核心功能

引入项目基础目录结构,包含多语言支持、主要页面与组件、核心游戏逻辑、UI 组件库、加密与本地持久化、自动化 Docker 构建流程、GitHub issue 模板(中英文)、README(中英文)、LICENSE 及开发配置文件。实现 OGame 单机版主要功能模块,为后续开发和扩展奠定基础。
This commit is contained in:
谦君
2025-12-11 14:49:25 +08:00
commit 705ee8c3db
178 changed files with 17258 additions and 0 deletions

353
src/logic/battleLogic.ts Normal file
View File

@@ -0,0 +1,353 @@
import type { Fleet, Resources, BattleResult, Officer } from '@/types/game'
import { DefenseType, ShipType, OfficerType } from '@/types/game'
import { SHIPS, DEFENSES } from '@/config/gameConfig'
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
}
/**
* 执行战斗模拟
*/
export const simulateBattle = (
attackerFleet: Partial<Fleet>,
defenderFleet: Partial<Fleet>,
defenderDefense: Partial<Record<DefenseType, number>>,
defenderResources: Resources,
attackerOfficers: Record<OfficerType, Officer>,
defenderOfficers: Record<OfficerType, Officer>
): 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)
// 复制战斗方用于战斗
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)
// 计算掠夺(仅攻击方胜利时)
const plunder =
winner === 'attacker'
? calculatePlunder(defenderResources, attackerFleet, 10000)
: { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
// 生成战斗报告
const battleResult: BattleResult = {
id: `battle_${Date.now()}`,
timestamp: Date.now(),
attackerId: '',
defenderId: '',
attackerPlanetId: '',
defenderPlanetId: '',
attackerFleet,
defenderFleet,
defenderDefense,
attackerLosses,
defenderLosses,
winner,
plunder,
debrisField
}
return battleResult
}
/**
* 计算防御设施修复防御有70%概率修复)
*/
export const repairDefense = (
defenseBeforeBattle: Partial<Record<DefenseType, number>>,
defenseAfterBattle: Partial<Record<DefenseType, number>>
): Partial<Record<DefenseType, number>> => {
const repaired: Partial<Record<DefenseType, number>> = { ...defenseAfterBattle }
Object.keys(defenseBeforeBattle).forEach(defenseType => {
const before = defenseBeforeBattle[defenseType as DefenseType] || 0
const after = defenseAfterBattle[defenseType as DefenseType] || 0
const lost = before - after
if (lost > 0) {
// 70%修复概率
const repairedCount = Math.floor(lost * 0.7)
repaired[defenseType as DefenseType] = after + repairedCount
}
})
return repaired
}

194
src/logic/buildingLogic.ts Normal file
View File

@@ -0,0 +1,194 @@
import type { Planet, Resources, BuildQueueItem } from '@/types/game'
import { BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game'
import { BUILDINGS } from '@/config/gameConfig'
import * as pointsLogic from './pointsLogic'
/**
* 计算建筑升级成本
*/
export const calculateBuildingCost = (buildingType: BuildingType, targetLevel: number): Resources => {
const config = BUILDINGS[buildingType]
const multiplier = Math.pow(config.costMultiplier, targetLevel - 1)
return {
metal: Math.floor(config.baseCost.metal * multiplier),
crystal: Math.floor(config.baseCost.crystal * multiplier),
deuterium: Math.floor(config.baseCost.deuterium * multiplier),
darkMatter: Math.floor(config.baseCost.darkMatter * multiplier),
energy: 0
}
}
/**
* 计算建筑升级时间
*/
export const calculateBuildingTime = (buildingType: BuildingType, targetLevel: number, buildingSpeedBonus: number = 0): number => {
const config = BUILDINGS[buildingType]
const multiplier = Math.pow(config.costMultiplier, targetLevel - 1)
const baseTime = config.baseTime * multiplier
const speedMultiplier = 1 - buildingSpeedBonus / 100
return Math.floor(baseTime * speedMultiplier)
}
/**
* 计算已用空间
*/
export const calculateUsedSpace = (planet: Planet): number => {
let usedSpace = 0
Object.entries(planet.buildings).forEach(([buildingType, level]) => {
if (level > 0) {
const config = BUILDINGS[buildingType as BuildingType]
usedSpace += config.spaceUsage * level
}
})
return usedSpace
}
/**
* 检查建筑升级条件
*/
export const checkBuildingRequirements = (
buildingType: BuildingType,
planet: Planet,
technologies: Partial<Record<TechnologyType, number>>
): boolean => {
const config = BUILDINGS[buildingType]
const requirements = (config as any).requirements
if (!requirements) return true
for (const [key, level] of Object.entries(requirements)) {
const requiredLevel = level as number
if (Object.values(BuildingType).includes(key as BuildingType)) {
if ((planet.buildings[key as BuildingType] || 0) < requiredLevel) {
return false
}
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
if ((technologies[key as TechnologyType] || 0) < requiredLevel) {
return false
}
}
}
return true
}
/**
* 检查是否有足够空间建造
*/
export const checkSpaceAvailable = (planet: Planet, buildingType: BuildingType): boolean => {
const usedSpace = calculateUsedSpace(planet)
const buildingConfig = BUILDINGS[buildingType]
const requiredSpace = buildingConfig.spaceUsage
return usedSpace + requiredSpace <= planet.maxSpace
}
/**
* 创建建造队列项
*/
export const createBuildQueueItem = (buildingType: BuildingType, targetLevel: number, buildTime: number): BuildQueueItem => {
const now = Date.now()
return {
id: `build_${now}`,
type: 'building',
itemType: buildingType,
targetLevel,
startTime: now,
endTime: now + buildTime * 1000
}
}
/**
* 处理建造完成
*/
export const completeBuildQueue = (planet: Planet, now: number, onPointsEarned?: (points: number, type: 'building' | 'ship' | 'defense', itemType: string, level?: number, quantity?: number) => void): void => {
planet.buildQueue = planet.buildQueue.filter(item => {
if (now >= item.endTime) {
// 建造完成
if (item.type === 'building') {
const oldLevel = planet.buildings[item.itemType as BuildingType] || 0
const newLevel = item.targetLevel || 0
planet.buildings[item.itemType as BuildingType] = newLevel
// 计算并累积积分
if (onPointsEarned && newLevel > oldLevel) {
const points = pointsLogic.calculateBuildingPoints(item.itemType as BuildingType, oldLevel, newLevel)
onPointsEarned(points, 'building', item.itemType, newLevel)
}
} else if (item.type === 'ship') {
const shipType = item.itemType as ShipType
const quantity = item.quantity || 0
planet.fleet[shipType] = (planet.fleet[shipType] || 0) + quantity
// 计算并累积积分
if (onPointsEarned && quantity > 0) {
const points = pointsLogic.calculateShipPoints(shipType, quantity)
onPointsEarned(points, 'ship', item.itemType, undefined, quantity)
}
} else if (item.type === 'defense') {
const defenseType = item.itemType as DefenseType
const quantity = item.quantity || 0
planet.defense[defenseType] = (planet.defense[defenseType] || 0) + quantity
// 计算并累积积分
if (onPointsEarned && quantity > 0) {
const points = pointsLogic.calculateDefensePoints(defenseType, quantity)
onPointsEarned(points, 'defense', item.itemType, undefined, quantity)
}
} else if (item.type === 'demolish') {
// 拆除完成,降低建筑等级
// 注意:拆除不会扣除积分,积分只增不减
const buildingType = item.itemType as BuildingType
const currentLevel = planet.buildings[buildingType] || 0
planet.buildings[buildingType] = Math.max(0, currentLevel - 1)
}
return false
}
return true
})
}
/**
* 计算拆除返还资源
* @param buildingType 建筑类型
* @param currentLevel 当前等级
* @returns 返还50%的当前等级建造成本
*/
export const calculateDemolishRefund = (buildingType: BuildingType, currentLevel: number): Resources => {
const cost = calculateBuildingCost(buildingType, currentLevel)
return {
metal: Math.floor(cost.metal * 0.5),
crystal: Math.floor(cost.crystal * 0.5),
deuterium: Math.floor(cost.deuterium * 0.5),
darkMatter: Math.floor(cost.darkMatter * 0.5),
energy: 0
}
}
/**
* 计算拆除时间
* @param buildingType 建筑类型
* @param currentLevel 当前等级
* @param buildingSpeedBonus 建筑速度加成
* @returns 拆除时间建造时间的50%
*/
export const calculateDemolishTime = (buildingType: BuildingType, currentLevel: number, buildingSpeedBonus: number = 0): number => {
const buildTime = calculateBuildingTime(buildingType, currentLevel, buildingSpeedBonus)
return Math.floor(buildTime * 0.5)
}
/**
* 创建拆除队列项
* @param buildingType 建筑类型
* @param currentLevel 当前等级
* @param demolishTime 拆除时间
* @returns 拆除队列项
*/
export const createDemolishQueueItem = (buildingType: BuildingType, currentLevel: number, demolishTime: number): BuildQueueItem => {
const now = Date.now()
return {
id: `demolish_${now}`,
type: 'demolish',
itemType: buildingType,
targetLevel: currentLevel - 1, // 目标等级为当前等级-1
startTime: now,
endTime: now + demolishTime * 1000
}
}

View File

@@ -0,0 +1,161 @@
import type { Planet, Resources, BuildQueueItem, Officer } from '@/types/game'
import { BuildingType, TechnologyType, OfficerType } from '@/types/game'
import * as buildingLogic from './buildingLogic'
import * as resourceLogic from './resourceLogic'
import * as publicLogic from './publicLogic'
import * as officerLogic from './officerLogic'
import { BUILDINGS } from '@/config/gameConfig'
/**
* 验证建筑升级的所有条件
*/
export const validateBuildingUpgrade = (
planet: Planet,
buildingType: BuildingType,
technologies: Partial<Record<TechnologyType, number>>,
officers: Record<OfficerType, Officer>
): {
valid: boolean
reason?: string
} => {
const currentLevel = planet.buildings[buildingType] || 0
const targetLevel = currentLevel + 1
const cost = buildingLogic.calculateBuildingCost(buildingType, targetLevel)
const buildingConfig = BUILDINGS[buildingType]
// 检查星球/月球限制
if (buildingConfig.planetOnly && planet.isMoon) {
return { valid: false, reason: 'errors.planetOnly' }
}
if (buildingConfig.moonOnly && !planet.isMoon) {
return { valid: false, reason: 'errors.moonOnly' }
}
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 检查建造队列是否已满
const maxQueue = publicLogic.getMaxBuildQueue(planet, bonuses.additionalBuildQueue)
if (planet.buildQueue.length >= maxQueue) {
return { valid: false, reason: 'errors.buildQueueFull' }
}
// 检查空间
if (!buildingLogic.checkSpaceAvailable(planet, buildingType)) {
return { valid: false, reason: 'errors.insufficientSpace' }
}
// 检查资源
if (!resourceLogic.checkResourcesAvailable(planet.resources, cost)) {
return { valid: false, reason: 'errors.insufficientResources' }
}
// 检查前置条件
if (!buildingLogic.checkBuildingRequirements(buildingType, planet, technologies)) {
return { valid: false, reason: 'errors.requirementsNotMet' }
}
return { valid: true }
}
/**
* 执行建筑升级(扣除资源,添加到队列)
*/
export const executeBuildingUpgrade = (planet: Planet, buildingType: BuildingType, officers: Record<OfficerType, Officer>): BuildQueueItem => {
const currentLevel = planet.buildings[buildingType] || 0
const targetLevel = currentLevel + 1
const cost = buildingLogic.calculateBuildingCost(buildingType, targetLevel)
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
const time = buildingLogic.calculateBuildingTime(buildingType, targetLevel, bonuses.buildingSpeedBonus)
// 扣除资源
resourceLogic.deductResources(planet.resources, cost)
// 创建队列项
return buildingLogic.createBuildQueueItem(buildingType, targetLevel, time)
}
/**
* 取消建造并计算返还资源
*/
export const cancelBuildingUpgrade = (_planet: Planet, queueItem: BuildQueueItem): Resources => {
const cost = buildingLogic.calculateBuildingCost(queueItem.itemType as BuildingType, queueItem.targetLevel || 1)
return {
metal: Math.floor(cost.metal * 0.5),
crystal: Math.floor(cost.crystal * 0.5),
deuterium: Math.floor(cost.deuterium * 0.5),
darkMatter: Math.floor(cost.darkMatter * 0.5),
energy: 0
}
}
/**
* 查找队列项
*/
export const findQueueItem = (
queue: BuildQueueItem[],
queueId: string
): {
item: BuildQueueItem | null
index: number
} => {
const index = queue.findIndex(q => q.id === queueId)
if (index === -1) {
return { item: null, index: -1 }
}
return { item: queue[index] || null, index }
}
/**
* 验证建筑拆除的所有条件
*/
export const validateBuildingDemolish = (
planet: Planet,
buildingType: BuildingType,
officers: Record<OfficerType, Officer>
): {
valid: boolean
reason?: string
} => {
const currentLevel = planet.buildings[buildingType] || 0
// 检查建筑等级
if (currentLevel <= 0) {
return { valid: false, reason: 'errors.buildingLevelZero' }
}
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 检查建造队列是否已满
const maxQueue = publicLogic.getMaxBuildQueue(planet, bonuses.additionalBuildQueue)
if (planet.buildQueue.length >= maxQueue) {
return { valid: false, reason: 'errors.buildQueueFull' }
}
return { valid: true }
}
/**
* 执行建筑拆除(返还资源,添加到队列)
*/
export const executeBuildingDemolish = (planet: Planet, buildingType: BuildingType, officers: Record<OfficerType, Officer>): BuildQueueItem => {
const currentLevel = planet.buildings[buildingType] || 0
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
const demolishTime = buildingLogic.calculateDemolishTime(buildingType, currentLevel, bonuses.buildingSpeedBonus)
// 返还50%资源
const refund = buildingLogic.calculateDemolishRefund(buildingType, currentLevel)
planet.resources.metal += refund.metal
planet.resources.crystal += refund.crystal
planet.resources.deuterium += refund.deuterium
planet.resources.darkMatter += refund.darkMatter
// 创建拆除队列项
return buildingLogic.createDemolishQueueItem(buildingType, currentLevel, demolishTime)
}

374
src/logic/fleetLogic.ts Normal file
View File

@@ -0,0 +1,374 @@
import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer } from '@/types/game'
import { ShipType, DefenseType, MissionType, BuildingType, OfficerType } from '@/types/game'
import * as battleLogic from './battleLogic'
import * as moonLogic from './moonLogic'
import * as moonValidation from './moonValidation'
/**
* 计算两个星球之间的距离
*/
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))
}
/**
* 计算飞行时间
*/
export const calculateFlightTime = (distance: number, minSpeed: number): number => {
return Math.max(10, Math.floor((distance * 10000) / minSpeed)) // 至少10秒
}
/**
* 创建舰队任务
*/
export const createFleetMission = (
playerId: string,
originPlanetId: string,
targetPosition: { galaxy: number; system: number; position: number },
missionType: MissionType,
fleet: Partial<Fleet>,
cargo: Resources,
flightTime: number
): FleetMission => {
const now = Date.now()
return {
id: `mission_${now}`,
playerId,
originPlanetId,
targetPosition,
missionType,
fleet,
cargo,
departureTime: now,
arrivalTime: now + flightTime * 1000,
returnTime: now + flightTime * 2 * 1000,
status: 'outbound'
}
}
/**
* 处理运输任务到达
*/
export const processTransportArrival = (mission: FleetMission, targetPlanet: Planet | undefined): void => {
if (targetPlanet) {
targetPlanet.resources.metal += mission.cargo.metal
targetPlanet.resources.crystal += mission.cargo.crystal
targetPlanet.resources.deuterium += mission.cargo.deuterium
targetPlanet.resources.darkMatter += mission.cargo.darkMatter
}
mission.status = 'returning'
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
}
/**
* 处理攻击任务到达
*/
export const processAttackArrival = (
mission: FleetMission,
targetPlanet: Planet | undefined,
attacker: Player,
defender: Player | null,
allPlanets: Planet[]
): { battleResult: BattleResult; moon: Planet | null } | null => {
if (!targetPlanet || targetPlanet.ownerId === attacker.id) {
mission.status = 'returning'
return null
}
// 执行战斗
const battleResult = battleLogic.simulateBattle(
mission.fleet,
targetPlanet.fleet,
targetPlanet.defense,
targetPlanet.resources,
attacker.officers,
defender?.officers || ({} as Record<OfficerType, Officer>)
)
// 更新战斗报告ID
battleResult.id = `battle_${Date.now()}`
battleResult.attackerId = attacker.id
battleResult.defenderId = targetPlanet.ownerId || 'unknown'
battleResult.attackerPlanetId = mission.originPlanetId
battleResult.defenderPlanetId = targetPlanet.id
// 如果攻击方获胜,掠夺资源已经在战斗模拟中计算
mission.cargo = battleResult.plunder
// 更新舰队 - 计算幸存舰船
const survivingFleet: Partial<Fleet> = {}
Object.entries(mission.fleet).forEach(([shipType, initialCount]) => {
const lost = battleResult.attackerLosses[shipType as ShipType] || 0
const surviving = initialCount - lost
if (surviving > 0) {
survivingFleet[shipType as ShipType] = surviving
}
})
mission.fleet = survivingFleet
// 更新目标星球舰队和防御
Object.entries(battleResult.defenderLosses.fleet).forEach(([shipType, lost]) => {
targetPlanet.fleet[shipType as ShipType] = Math.max(0, targetPlanet.fleet[shipType as ShipType] - lost)
})
Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => {
targetPlanet.defense[defenseType as DefenseType] = Math.max(0, targetPlanet.defense[defenseType as DefenseType] - lost)
})
// 防御设施修复70%概率)
const defenseBeforeBattle: Partial<Record<DefenseType, number>> = { ...targetPlanet.defense }
Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => {
defenseBeforeBattle[defenseType as DefenseType] = (defenseBeforeBattle[defenseType as DefenseType] || 0) + lost
})
targetPlanet.defense = battleLogic.repairDefense(defenseBeforeBattle, targetPlanet.defense) as Record<DefenseType, number>
// 扣除掠夺的资源
targetPlanet.resources.metal -= battleResult.plunder.metal
targetPlanet.resources.crystal -= battleResult.plunder.crystal
targetPlanet.resources.deuterium -= battleResult.plunder.deuterium
mission.status = 'returning'
// 尝试生成月球(如果该位置还没有月球)
let moon: Planet | null = null
const moonCheck = moonValidation.canCreateMoon(allPlanets, targetPlanet.position, battleResult.debrisField)
if (moonCheck.canCreate && moonCheck.chance) {
if (moonValidation.shouldGenerateMoon(moonCheck.chance)) {
moon = moonLogic.tryGenerateMoon(battleResult.debrisField, targetPlanet.position, targetPlanet.id, targetPlanet.ownerId || 'unknown')
}
}
return { battleResult, moon }
}
/**
* 处理殖民任务到达
*/
export const processColonizeArrival = (
mission: FleetMission,
targetPlanet: Planet | undefined,
playerId: string,
colonyNameTemplate: string = 'Colony'
): Planet | null => {
if (targetPlanet) {
// 位置已被占用
mission.status = 'returning'
return null
}
// 创建新殖民地
const newPlanet: Planet = {
id: `planet_${Date.now()}`,
name: `${colonyNameTemplate} ${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`,
ownerId: playerId,
position: mission.targetPosition,
resources: { metal: 500, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 },
buildings: {} as Record<BuildingType, number>,
fleet: {
[ShipType.LightFighter]: 0,
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
[DefenseType.LightLaser]: 0,
[DefenseType.HeavyLaser]: 0,
[DefenseType.GaussCannon]: 0,
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: 200,
isMoon: false
}
Object.values(BuildingType).forEach(building => {
newPlanet.buildings[building] = 0
})
// 殖民船被消耗
mission.fleet[ShipType.ColonyShip] = (mission.fleet[ShipType.ColonyShip] || 1) - 1
mission.status = 'returning'
return newPlanet
}
/**
* 处理间谍任务到达
*/
export const processSpyArrival = (mission: FleetMission, targetPlanet: Planet | undefined, playerId: string): SpyReport | null => {
if (!targetPlanet) {
mission.status = 'returning'
return null
}
const spyReport: SpyReport = {
id: `spy_${Date.now()}`,
timestamp: Date.now(),
spyId: playerId,
targetPlanetId: targetPlanet.id,
targetPlayerId: targetPlanet.ownerId || 'unknown',
resources: { ...targetPlanet.resources },
fleet: { ...targetPlanet.fleet },
defense: { ...targetPlanet.defense },
buildings: { ...targetPlanet.buildings },
technologies: {},
detectionChance: 0.3
}
mission.status = 'returning'
return spyReport
}
/**
* 处理部署任务到达
*/
export const processDeployArrival = (mission: FleetMission, targetPlanet: Planet | undefined, playerId: string): boolean => {
if (!targetPlanet || targetPlanet.ownerId !== playerId) {
mission.status = 'returning'
return false
}
for (const [shipType, count] of Object.entries(mission.fleet)) {
targetPlanet.fleet[shipType as ShipType] += count
}
// 部署任务直接完成,不返回
return true
}
/**
* 处理舰队任务返回
*/
export const processFleetReturn = (mission: FleetMission, originPlanet: Planet): void => {
// 舰船返回
Object.entries(mission.fleet).forEach(([shipType, count]) => {
if (count > 0) {
originPlanet.fleet[shipType as ShipType] += count
}
})
// 资源返回(掠夺物或运输货物)
originPlanet.resources.metal += mission.cargo.metal
originPlanet.resources.crystal += mission.cargo.crystal
originPlanet.resources.deuterium += mission.cargo.deuterium
originPlanet.resources.darkMatter += mission.cargo.darkMatter
}
/**
* 更新舰队任务状态
*/
export const updateFleetMissions = (
missions: FleetMission[],
planets: Map<string, Planet>,
attacker: Player,
defender: Player | null,
now: number
): {
completedMissions: string[]
battleReports: BattleResult[]
spyReports: SpyReport[]
newColonies: Planet[]
newMoons: Planet[]
} => {
const completedMissions: string[] = []
const battleReports: BattleResult[] = []
const spyReports: SpyReport[] = []
const newColonies: Planet[] = []
const newMoons: Planet[] = []
// 获取所有星球列表(用于月球生成检查)
const allPlanets = Array.from(planets.values())
missions.forEach(mission => {
const originPlanet = attacker.planets.find(p => p.id === mission.originPlanetId)
if (mission.status === 'outbound' && now >= mission.arrivalTime) {
// 任务到达目标
const targetKey = `${mission.targetPosition.galaxy}:${mission.targetPosition.system}:${mission.targetPosition.position}`
const targetPlanet = planets.get(targetKey)
switch (mission.missionType) {
case MissionType.Transport:
processTransportArrival(mission, targetPlanet)
break
case MissionType.Attack:
const attackResult = processAttackArrival(mission, targetPlanet, attacker, defender, allPlanets)
if (attackResult) {
battleReports.push(attackResult.battleResult)
if (attackResult.moon) {
newMoons.push(attackResult.moon)
// 将月球添加到planets map中
const moonKey = `${attackResult.moon.position.galaxy}:${attackResult.moon.position.system}:${attackResult.moon.position.position}`
planets.set(moonKey, attackResult.moon)
}
}
break
case MissionType.Colonize:
const newColony = processColonizeArrival(mission, targetPlanet, attacker.id)
if (newColony) {
newColonies.push(newColony)
planets.set(targetKey, newColony)
}
break
case MissionType.Spy:
const spyReport = processSpyArrival(mission, targetPlanet, attacker.id)
if (spyReport) {
spyReports.push(spyReport)
}
break
case MissionType.Deploy:
const deployed = processDeployArrival(mission, targetPlanet, attacker.id)
if (deployed) {
completedMissions.push(mission.id)
}
break
}
}
if (mission.status === 'returning' && mission.returnTime && now >= mission.returnTime) {
// 舰队返回
if (originPlanet) {
processFleetReturn(mission, originPlanet)
}
completedMissions.push(mission.id)
}
})
return { completedMissions, battleReports, spyReports, newColonies, newMoons }
}
/**
* 召回舰队
*/
export const recallFleetMission = (mission: FleetMission, now: number): boolean => {
if (mission.status !== 'outbound') return false
const elapsedTime = now - mission.departureTime
// 如果还在飞行途中,立即返回
if (now < mission.arrivalTime) {
mission.status = 'returning'
mission.returnTime = now + elapsedTime // 返回时间等于已飞行的时间
return true
}
return false
}

132
src/logic/gameLogic.ts Normal file
View File

@@ -0,0 +1,132 @@
import type { Planet, Player, BuildQueueItem, Officer } from '@/types/game'
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'
/**
* 初始化玩家数据
*/
export const initializePlayer = (playerId: string, playerName: string = 'Commander'): Player => {
const player: Player = {
id: playerId,
name: playerName,
planets: [],
technologies: {} as Record<TechnologyType, number>,
officers: {} as Record<OfficerType, Officer>,
researchQueue: [],
fleetMissions: [],
battleReports: [],
spyReports: [],
points: 0
}
// 初始化科技等级
Object.values(TechnologyType).forEach(tech => {
player.technologies[tech] = 0
})
// 初始化军官状态
Object.values(OfficerType).forEach(officer => {
player.officers[officer] = officerLogic.createInactiveOfficer(officer)
})
return player
}
/**
* 检查是否需要初始化游戏
*/
export const shouldInitializeGame = (planets: Planet[]): boolean => {
return planets.length === 0
}
/**
* 更新所有星球的最后更新时间
*/
export const updatePlanetsLastUpdate = (planets: Planet[], now: number): void => {
planets.forEach(planet => {
planet.lastUpdate = now
})
}
/**
* 生成星系位置列表
*/
export const generateSystemPositions = (
_galaxy: number,
_system: number,
count: number = 10
): Array<{ position: number; planet: Planet | null }> => {
const result: Array<{ position: number; planet: Planet | null }> = []
for (let pos = 1; pos <= count; pos++) {
result.push({ position: pos, planet: null })
}
return result
}
/**
* 生成随机NPC星球位置
*/
export const generateRandomPosition = (): { galaxy: number; system: number; position: number } => {
return {
galaxy: Math.floor(Math.random() * 9) + 1,
system: Math.floor(Math.random() * 10) + 1,
position: Math.floor(Math.random() * 10) + 1
}
}
/**
* 生成位置键
*/
export const generatePositionKey = (galaxy: number, system: number, position: number): string => {
return `${galaxy}:${system}:${position}`
}
/**
* 更新游戏状态 - 处理所有星球和任务
*/
export const processGameUpdate = (
player: Player,
now: number
): {
updatedResearchQueue: BuildQueueItem[]
} => {
// 获取军官加成
const bonuses = officerLogic.calculateActiveBonuses(player.officers, now)
// 创建积分回调函数
const onPointsEarned = (points: number, _type: string, _itemType: string, _level?: number, _quantity?: number) => {
pointsLogic.addPoints(player, points)
}
// 更新所有星球
player.planets.forEach(planet => {
// 更新资源
resourceLogic.updatePlanetResources(planet, now, bonuses)
// 检查建造队列
buildingLogic.completeBuildQueue(planet, now, onPointsEarned)
})
// 检查研究队列
const updatedResearchQueue = researchLogic.completeResearchQueue(
player.researchQueue,
player.technologies,
now,
onPointsEarned
)
return {
updatedResearchQueue
}
}
/**
* 检查并返回过期的军官列表
*/
export const checkOfficersExpiration = (officers: Record<OfficerType, Officer>, now: number): void => {
officerLogic.checkAndDeactivateExpiredOfficers(officers, now)
}

111
src/logic/moonLogic.ts Normal file
View File

@@ -0,0 +1,111 @@
import type { Planet, Resources } from '@/types/game'
import { BuildingType, ShipType, DefenseType } from '@/types/game'
import { MOON_CONFIG } from '@/config/gameConfig'
/**
* 计算月球生成概率
* @param debrisField 战斗产生的残骸场
* @returns 生成概率0-100
*/
export const calculateMoonGenerationChance = (debrisField: Resources): number => {
const totalDebris = debrisField.metal + debrisField.crystal
// 残骸不足最小值,无法生成月球
if (totalDebris < MOON_CONFIG.minDebrisField) {
return 0
}
// 计算概率:基础概率 + (残骸量 / 每单位增加量) * 1%
const additionalChance = Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)
const chance = MOON_CONFIG.baseChance + additionalChance
// 限制在最大概率内
return Math.min(chance, MOON_CONFIG.maxChance)
}
/**
* 尝试生成月球
* @param debrisField 战斗产生的残骸场
* @param planetPosition 星球坐标
* @param playerId 玩家ID
* @returns 生成的月球对象如果未生成则返回null
*/
export const tryGenerateMoon = (
debrisField: Resources,
planetPosition: { galaxy: number; system: number; position: number },
planetId: string,
playerId: string
): Planet | null => {
const chance = calculateMoonGenerationChance(debrisField)
// 无法生成
if (chance === 0) {
return null
}
// 随机判断是否生成
const roll = Math.random() * 100
if (roll >= chance) {
return null
}
// 生成月球
const moon: Planet = {
id: `moon_${Date.now()}`,
name: `Moon [${planetPosition.galaxy}:${planetPosition.system}:${planetPosition.position}]`,
ownerId: playerId,
position: planetPosition,
resources: { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 },
buildings: {} as Record<BuildingType, number>,
fleet: {
[ShipType.LightFighter]: 0,
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
[DefenseType.LightLaser]: 0,
[DefenseType.HeavyLaser]: 0,
[DefenseType.GaussCannon]: 0,
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: MOON_CONFIG.baseSize,
isMoon: true,
parentPlanetId: planetId
}
// 初始化所有建筑为0级
Object.values(BuildingType).forEach(building => {
moon.buildings[building] = 0
})
return moon
}
/**
* 检查坐标是否已有月球
* @param planets 所有星球列表
* @param position 坐标
* @returns 是否已有月球
*/
export const hasMoonAtPosition = (planets: Planet[], position: { galaxy: number; system: number; position: number }): boolean => {
return planets.some(
p =>
p.isMoon &&
p.position.galaxy === position.galaxy &&
p.position.system === position.system &&
p.position.position === position.position
)
}

View File

@@ -0,0 +1,58 @@
import type { Planet, Resources } from '@/types/game'
import * as planetLogic from './planetLogic'
/**
* 检查是否可以生成月球
*/
export const canCreateMoon = (
planets: Planet[],
position: { galaxy: number; system: number; position: number },
debrisField: Resources
): {
canCreate: boolean
reason?: string
chance?: number
} => {
// 检查该位置是否已有月球
const existingMoon = planets.find(
p =>
p.position.galaxy === position.galaxy &&
p.position.system === position.system &&
p.position.position === position.position &&
p.isMoon
)
if (existingMoon) {
return { canCreate: false, reason: 'errors.moonExists' }
}
const chance = planetLogic.calculateMoonChance(debrisField)
if (chance === 0) {
return { canCreate: false, reason: 'errors.insufficientDebris', chance }
}
return { canCreate: true, chance }
}
/**
* 计算月球生成概率并判断是否生成
*/
export const shouldGenerateMoon = (chance: number): boolean => {
const random = Math.random() * 100
return random <= chance
}
/**
* 查找母星
*/
export const findParentPlanet = (planets: Planet[], position: { galaxy: number; system: number; position: number }): Planet | null => {
return (
planets.find(
p =>
p.position.galaxy === position.galaxy &&
p.position.system === position.system &&
p.position.position === position.position &&
!p.isMoon
) || null
)
}

96
src/logic/officerLogic.ts Normal file
View File

@@ -0,0 +1,96 @@
import type { Officer, Resources } from '@/types/game'
import { OfficerType } from '@/types/game'
import { OFFICERS } from '@/config/gameConfig'
/**
* 获取军官成本
*/
export const getOfficerCost = (officerType: OfficerType): Resources => {
const config = OFFICERS[officerType]
return config.cost
}
/**
* 检查军官是否激活
*/
export const isOfficerActive = (officer: Officer, now: number): boolean => {
return officer.active && (!officer.expiresAt || officer.expiresAt > now)
}
/**
* 创建激活的军官
*/
export const createActiveOfficer = (officerType: OfficerType, duration: number): Officer => {
const now = Date.now()
return {
type: officerType,
active: true,
hiredAt: now,
expiresAt: now + duration * 24 * 60 * 60 * 1000 // duration天后过期
}
}
/**
* 创建未激活的军官
*/
export const createInactiveOfficer = (officerType: OfficerType): Officer => {
return {
type: officerType,
active: false
}
}
/**
* 续约军官
*/
export const renewOfficerExpiration = (officer: Officer, duration: number, now: number): Officer => {
const expiresAt = officer.expiresAt && officer.expiresAt > now ? officer.expiresAt : now
return {
...officer,
active: true,
expiresAt: expiresAt + duration * 24 * 60 * 60 * 1000
}
}
/**
* 计算所有激活军官的加成
*/
export const calculateActiveBonuses = (officers: Record<OfficerType, Officer>, now: number) => {
const bonuses = {
buildingSpeedBonus: 0,
researchSpeedBonus: 0,
resourceProductionBonus: 0,
darkMatterProductionBonus: 0,
energyProductionBonus: 0,
fleetSpeedBonus: 0,
fuelConsumptionReduction: 0,
defenseBonus: 0,
additionalBuildQueue: 0,
additionalFleetSlots: 0,
storageCapacityBonus: 0
}
Object.values(officers).forEach(officer => {
if (isOfficerActive(officer, now)) {
const config = OFFICERS[officer.type]
Object.entries(config.benefits).forEach(([key, value]) => {
if (value !== undefined) {
bonuses[key as keyof typeof bonuses] += value
}
})
}
})
return bonuses
}
/**
* 检查并停用过期的军官
*/
export const checkAndDeactivateExpiredOfficers = (officers: Record<OfficerType, Officer>, now: number): void => {
Object.values(officers).forEach(officer => {
if (officer.active && officer.expiresAt && officer.expiresAt <= now) {
officer.active = false
}
})
}

193
src/logic/planetLogic.ts Normal file
View File

@@ -0,0 +1,193 @@
import type { Planet, Resources } from '@/types/game'
import { ShipType, DefenseType, BuildingType } from '@/types/game'
import { MOON_CONFIG } from '@/config/gameConfig'
/**
* 创建初始星球
*/
export const createInitialPlanet = (playerId: string, planetName: string = 'Home Planet'): Planet => {
const initialPlanet: Planet = {
id: 'planet1',
name: planetName,
ownerId: playerId,
position: { galaxy: 1, system: 1, position: 1 },
resources: {
metal: 500,
crystal: 500,
deuterium: 0,
darkMatter: 0,
energy: 0
},
buildings: {} as Record<BuildingType, number>,
fleet: {
[ShipType.LightFighter]: 0,
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
[DefenseType.LightLaser]: 0,
[DefenseType.HeavyLaser]: 0,
[DefenseType.GaussCannon]: 0,
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: 200,
isMoon: false
}
// 初始化建筑等级
Object.values(BuildingType).forEach(building => {
initialPlanet.buildings[building] = 0
})
return initialPlanet
}
/**
* 创建NPC星球
*/
export const createNPCPlanet = (
npcId: number,
position: { galaxy: number; system: number; position: number },
planetPrefix: string = 'Planet'
): Planet => {
const npcPlanet: Planet = {
id: `npc_planet_${npcId}`,
name: `${planetPrefix} ${position.galaxy}:${position.system}:${position.position}`,
ownerId: `npc_${npcId}`,
position,
resources: {
metal: Math.floor(Math.random() * 10000) + 5000,
crystal: Math.floor(Math.random() * 5000) + 2000,
deuterium: Math.floor(Math.random() * 2000) + 500,
darkMatter: Math.floor(Math.random() * 100),
energy: 0
},
buildings: {} as Record<BuildingType, number>,
fleet: {
[ShipType.LightFighter]: Math.floor(Math.random() * 50),
[ShipType.HeavyFighter]: Math.floor(Math.random() * 20),
[ShipType.Cruiser]: Math.floor(Math.random() * 10),
[ShipType.Battleship]: Math.floor(Math.random() * 5),
[ShipType.SmallCargo]: Math.floor(Math.random() * 10),
[ShipType.LargeCargo]: Math.floor(Math.random() * 5),
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
},
defense: {
[DefenseType.RocketLauncher]: Math.floor(Math.random() * 100),
[DefenseType.LightLaser]: Math.floor(Math.random() * 50),
[DefenseType.HeavyLaser]: Math.floor(Math.random() * 20),
[DefenseType.GaussCannon]: Math.floor(Math.random() * 10),
[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
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: 200,
isMoon: false
}
// 随机初始化建筑等级
Object.values(BuildingType).forEach(building => {
npcPlanet.buildings[building] = Math.floor(Math.random() * 10)
})
return npcPlanet
}
/**
* 计算月球生成概率
*/
export const calculateMoonChance = (debrisField: Resources): number => {
const totalDebris = debrisField.metal + debrisField.crystal
if (totalDebris < MOON_CONFIG.minDebrisField) return 0
const chance = MOON_CONFIG.baseChance + Math.floor(totalDebris / MOON_CONFIG.chancePerDebris)
return Math.min(chance, MOON_CONFIG.maxChance)
}
/**
* 创建月球
*/
export const createMoon = (
parentPlanet: Planet,
position: { galaxy: number; system: number; position: number },
playerId: string,
moonSuffix: string = "'s Moon"
): Planet => {
const moonId = `moon_${Date.now()}`
const moon: Planet = {
id: moonId,
name: `${parentPlanet.name}${moonSuffix}`,
ownerId: playerId,
position: { ...position },
resources: {
metal: 0,
crystal: 0,
deuterium: 0,
darkMatter: 0,
energy: 0
},
buildings: {} as Record<BuildingType, number>,
fleet: {
[ShipType.LightFighter]: 0,
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.DarkMatterHarvester]: 0
},
defense: {
[DefenseType.RocketLauncher]: 0,
[DefenseType.LightLaser]: 0,
[DefenseType.HeavyLaser]: 0,
[DefenseType.GaussCannon]: 0,
[DefenseType.IonCannon]: 0,
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0
},
buildQueue: [],
lastUpdate: Date.now(),
maxSpace: MOON_CONFIG.baseSize,
isMoon: true,
parentPlanetId: parentPlanet.id
}
// 初始化建筑等级
Object.values(BuildingType).forEach(building => {
moon.buildings[building] = 0
})
return moon
}
/**
* 计算月球空间上限
*/
export const calculateMoonMaxSpace = (moon: Planet): number => {
if (!moon.isMoon) return 0
const lunarBaseLevel = moon.buildings[BuildingType.LunarBase] || 0
return MOON_CONFIG.baseSize + lunarBaseLevel * MOON_CONFIG.lunarBaseSpaceBonus
}

137
src/logic/pointsLogic.ts Normal file
View File

@@ -0,0 +1,137 @@
import type { Player, Resources, BuildingType, TechnologyType, ShipType, DefenseType } from '@/types/game'
import { SHIPS } from '@/config/gameConfig'
import { DEFENSES } from '@/config/gameConfig'
import * as buildingLogic from './buildingLogic'
import * as researchLogic from './researchLogic'
/**
* 计算资源总和(仅计算金属+晶体+重氢)
* 根据游戏规则:不包括暗物质和能量
*/
export const calculateResourceCost = (resources: Resources): number => {
return resources.metal + resources.crystal + resources.deuterium
}
/**
* 将资源总和转换为积分
* 规则每1000资源 = 1分
*/
export const calculatePointsFromResources = (resourceCost: number): number => {
return Math.floor(resourceCost / 1000)
}
/**
* 为玩家添加积分
*/
export const addPoints = (player: Player, points: number): void => {
player.points += points
}
/**
* 计算建筑升级到指定等级所获得的积分
* @param buildingType 建筑类型
* @param fromLevel 起始等级(不包括)
* @param toLevel 目标等级(包括)
* @returns 积分数
*/
export const calculateBuildingPoints = (buildingType: BuildingType, fromLevel: number, toLevel: number): number => {
let totalPoints = 0
for (let level = fromLevel + 1; level <= toLevel; level++) {
const cost = buildingLogic.calculateBuildingCost(buildingType, level)
const resourceCost = calculateResourceCost(cost)
totalPoints += calculatePointsFromResources(resourceCost)
}
return totalPoints
}
/**
* 计算科技研究到指定等级所获得的积分
* @param technologyType 科技类型
* @param fromLevel 起始等级(不包括)
* @param toLevel 目标等级(包括)
* @returns 积分数
*/
export const calculateTechnologyPoints = (technologyType: TechnologyType, fromLevel: number, toLevel: number): number => {
let totalPoints = 0
for (let level = fromLevel + 1; level <= toLevel; level++) {
const cost = researchLogic.calculateTechnologyCost(technologyType, level)
const resourceCost = calculateResourceCost(cost)
totalPoints += calculatePointsFromResources(resourceCost)
}
return totalPoints
}
/**
* 计算舰船建造所获得的积分
* @param shipType 舰船类型
* @param quantity 数量
* @returns 积分数
*/
export const calculateShipPoints = (shipType: ShipType, quantity: number): number => {
const config = SHIPS[shipType]
const resourceCost = calculateResourceCost(config.cost)
const pointsPerShip = calculatePointsFromResources(resourceCost)
return pointsPerShip * quantity
}
/**
* 计算防御建造所获得的积分
* @param defenseType 防御类型
* @param quantity 数量
* @returns 积分数
*/
export const calculateDefensePoints = (defenseType: DefenseType, quantity: number): number => {
const config = DEFENSES[defenseType]
const resourceCost = calculateResourceCost(config.cost)
const pointsPerDefense = calculatePointsFromResources(resourceCost)
return pointsPerDefense * quantity
}
/**
* 计算玩家当前的总积分(用于数据迁移或重新计算)
* 会计算所有建筑、科技、舰船、防御的累积积分
*/
export const calculateTotalPlayerPoints = (player: Player): number => {
let totalPoints = 0
// 计算所有星球的建筑积分
for (const planet of player.planets) {
for (const [buildingType, level] of Object.entries(planet.buildings)) {
if (level > 0) {
totalPoints += calculateBuildingPoints(buildingType as BuildingType, 0, level)
}
}
// 计算所有星球的舰船积分
for (const [shipType, quantity] of Object.entries(planet.fleet)) {
if (quantity > 0) {
totalPoints += calculateShipPoints(shipType as ShipType, quantity)
}
}
// 计算所有星球的防御积分
for (const [defenseType, quantity] of Object.entries(planet.defense)) {
if (quantity > 0) {
totalPoints += calculateDefensePoints(defenseType as DefenseType, quantity)
}
}
}
// 计算所有科技的积分
for (const [technologyType, level] of Object.entries(player.technologies)) {
if (level > 0) {
totalPoints += calculateTechnologyPoints(technologyType as TechnologyType, 0, level)
}
}
// 计算正在飞行的舰队积分(舰队已经建造,所以也计入积分)
for (const mission of player.fleetMissions) {
for (const [shipType, quantity] of Object.entries(mission.fleet)) {
if (quantity && quantity > 0) {
totalPoints += calculateShipPoints(shipType as ShipType, quantity)
}
}
}
return totalPoints
}

102
src/logic/publicLogic.ts Normal file
View File

@@ -0,0 +1,102 @@
/**
* 公共业务逻辑模块
* 提供跨模块共享的通用业务逻辑功能
*/
import { BuildingType, TechnologyType } from '@/types/game'
import type { Planet, Resources, Officer } from '@/types/game'
import { OfficerType } from '@/types/game'
import * as officerLogic from '@/logic/officerLogic'
import * as resourceLogic from '@/logic/resourceLogic'
/**
* 检查建造/研发前置条件是否满足
* @param planet 星球对象
* @param technologies 已研究的科技等级
* @param requirements 前置条件要求(建筑等级或科技等级)
* @returns 是否满足前置条件
*/
export const checkRequirements = (
planet: Planet | undefined,
technologies: Partial<Record<TechnologyType, number>>,
requirements?: Partial<Record<BuildingType | TechnologyType, number>>
): boolean => {
// 如果星球不存在或没有前置条件,默认返回 true
if (!planet || !requirements) return true
// 检查所有前置条件
for (const [key, requiredLevel] of Object.entries(requirements)) {
// 检查是否为建筑类型
if (Object.values(BuildingType).includes(key as BuildingType)) {
const currentLevel = planet.buildings[key as BuildingType] || 0
if (currentLevel < requiredLevel) {
return false
}
}
// 检查是否为科技类型
else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
const currentLevel = technologies[key as TechnologyType] || 0
if (currentLevel < requiredLevel) {
return false
}
}
}
return true
}
/**
* 计算星球的资源产量(包含军官加成)
* @param planet 星球对象
* @param officers 玩家的军官对象
* @returns 每小时各类资源的产量
*/
export const getResourceProduction = (planet: Planet, officers: Record<OfficerType, Officer>): Resources => {
// 计算当前激活的军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 根据建筑等级和军官加成计算资源产量
return resourceLogic.calculateResourceProduction(planet, bonuses)
}
/**
* 计算星球的资源存储容量(包含军官加成)
* @param planet 星球对象
* @param officers 玩家的军官对象
* @returns 各类资源的最大存储容量
*/
export const getResourceCapacity = (planet: Planet, officers: Record<OfficerType, Officer>): Resources => {
// 计算当前激活的军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 根据仓库建筑等级和军官加成计算存储容量
return resourceLogic.calculateResourceCapacity(planet, bonuses.storageCapacityBonus)
}
/**
* 计算最大建造队列数量
* @param planet 星球对象
* @param additionalBuildQueue 军官提供的额外队列数量
* @returns 最大建造队列数量基础1个 + 纳米工厂等级 + 军官加成最多10个
*/
export const getMaxBuildQueue = (planet: Planet, additionalBuildQueue: number = 0): number => {
const naniteFactoryLevel = planet.buildings[BuildingType.NaniteFactory] || 0
return Math.min(1 + naniteFactoryLevel + additionalBuildQueue, 10)
}
/**
* 计算最大研究队列数量
* @param technologies 已研究的科技等级
* @returns 最大研究队列数量基础1个 + 计算机技术等级最多10个
*/
export const getMaxResearchQueue = (technologies: Partial<Record<TechnologyType, number>>): number => {
const computerTechLevel = technologies[TechnologyType.ComputerTechnology] || 0
return Math.min(1 + computerTechLevel, 10)
}
/**
* 计算最大舰队任务数量
* @param additionalFleetSlots 军官提供的额外槽位数量
* @returns 最大舰队任务数量基础1个 + 军官加成最多10个
*/
export const getMaxFleetMissions = (additionalFleetSlots: number = 0): number => {
return Math.min(1 + additionalFleetSlots, 10)
}

View File

@@ -0,0 +1,96 @@
import type { Resources, BuildQueueItem } from '@/types/game'
import { TechnologyType, BuildingType } from '@/types/game'
import { TECHNOLOGIES } from '@/config/gameConfig'
import * as pointsLogic from './pointsLogic'
/**
* 计算科技研究成本
*/
export const calculateTechnologyCost = (techType: TechnologyType, targetLevel: number): Resources => {
const config = TECHNOLOGIES[techType]
const multiplier = Math.pow(config.costMultiplier, targetLevel - 1)
return {
metal: Math.floor(config.baseCost.metal * multiplier),
crystal: Math.floor(config.baseCost.crystal * multiplier),
deuterium: Math.floor(config.baseCost.deuterium * multiplier),
darkMatter: Math.floor(config.baseCost.darkMatter * multiplier),
energy: 0
}
}
/**
* 计算科技研究时间
*/
export const calculateTechnologyTime = (techType: TechnologyType, currentLevel: number, researchSpeedBonus: number = 0): number => {
const config = TECHNOLOGIES[techType]
const baseTime = config.baseTime * Math.pow(config.costMultiplier, currentLevel)
const speedMultiplier = 1 - researchSpeedBonus / 100
return Math.floor(baseTime * speedMultiplier)
}
/**
* 检查科技研究条件
*/
export const checkTechnologyRequirements = (
techType: TechnologyType,
buildings: Partial<Record<BuildingType, number>>,
technologies: Partial<Record<TechnologyType, number>>
): boolean => {
const config = TECHNOLOGIES[techType]
if (!config.requirements) return true
for (const [key, level] of Object.entries(config.requirements)) {
if (Object.values(BuildingType).includes(key as BuildingType)) {
if ((buildings[key as BuildingType] || 0) < level) {
return false
}
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
if ((technologies[key as TechnologyType] || 0) < level) {
return false
}
}
}
return true
}
/**
* 创建研究队列项
*/
export const createResearchQueueItem = (techType: TechnologyType, targetLevel: number, researchTime: number): BuildQueueItem => {
const now = Date.now()
return {
id: `research_${now}`,
type: 'technology',
itemType: techType,
targetLevel,
startTime: now,
endTime: now + researchTime * 1000
}
}
/**
* 处理研究完成
*/
export const completeResearchQueue = (
researchQueue: BuildQueueItem[],
technologies: Partial<Record<TechnologyType, number>>,
now: number,
onPointsEarned?: (points: number, type: 'technology', itemType: string, level: number) => void
): BuildQueueItem[] => {
return researchQueue.filter(item => {
if (now >= item.endTime) {
// 研究完成
const oldLevel = technologies[item.itemType as TechnologyType] || 0
const newLevel = item.targetLevel || 0
technologies[item.itemType as TechnologyType] = newLevel
// 计算并累积积分
if (onPointsEarned && newLevel > oldLevel) {
const points = pointsLogic.calculateTechnologyPoints(item.itemType as TechnologyType, oldLevel, newLevel)
onPointsEarned(points, 'technology', item.itemType, newLevel)
}
return false
}
return true
})
}

View File

@@ -0,0 +1,81 @@
import type { Planet, Resources, BuildQueueItem, Officer } from '@/types/game'
import { TechnologyType, OfficerType } from '@/types/game'
import * as researchLogic from './researchLogic'
import * as resourceLogic from './resourceLogic'
import * as publicLogic from './publicLogic'
import * as officerLogic from './officerLogic'
/**
* 验证科技研究的所有条件
*/
export const validateTechnologyResearch = (
planet: Planet,
techType: TechnologyType,
technologies: Partial<Record<TechnologyType, number>>,
researchQueue: BuildQueueItem[]
): {
valid: boolean
reason?: string
} => {
const currentLevel = technologies[techType] || 0
const targetLevel = currentLevel + 1
const cost = researchLogic.calculateTechnologyCost(techType, targetLevel)
// 检查研究队列是否已满
const maxQueue = publicLogic.getMaxResearchQueue(technologies)
if (researchQueue.length >= maxQueue) {
return { valid: false, reason: 'errors.researchQueueFull' }
}
// 检查前置条件
if (!researchLogic.checkTechnologyRequirements(techType, planet.buildings, technologies)) {
return { valid: false, reason: 'errors.requirementsNotMet' }
}
// 检查资源
if (!resourceLogic.checkResourcesAvailable(planet.resources, cost)) {
return { valid: false, reason: 'errors.insufficientResources' }
}
return { valid: true }
}
/**
* 执行科技研究(扣除资源,创建队列项)
*/
export const executeTechnologyResearch = (
planet: Planet,
techType: TechnologyType,
currentLevel: number,
officers: Record<OfficerType, Officer>
): { queueItem: BuildQueueItem } => {
const targetLevel = currentLevel + 1
const cost = researchLogic.calculateTechnologyCost(techType, targetLevel)
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
const time = researchLogic.calculateTechnologyTime(techType, currentLevel, bonuses.researchSpeedBonus)
// 扣除资源
resourceLogic.deductResources(planet.resources, cost)
// 创建队列项
const queueItem = researchLogic.createResearchQueueItem(techType, targetLevel, time)
return { queueItem }
}
/**
* 取消研究并计算返还资源
*/
export const cancelTechnologyResearch = (queueItem: BuildQueueItem): Resources => {
const cost = researchLogic.calculateTechnologyCost(queueItem.itemType as TechnologyType, queueItem.targetLevel || 1)
return {
metal: Math.floor(cost.metal * 0.5),
crystal: Math.floor(cost.crystal * 0.5),
deuterium: Math.floor(cost.deuterium * 0.5),
darkMatter: Math.floor(cost.darkMatter * 0.5),
energy: 0
}
}

161
src/logic/resourceLogic.ts Normal file
View File

@@ -0,0 +1,161 @@
import type { Planet, Resources } from '@/types/game'
import { BuildingType } from '@/types/game'
/**
* 计算电量产出
*/
export const calculateEnergyProduction = (
planet: Planet,
bonuses: {
energyProductionBonus: number
}
): number => {
const solarPlantLevel = planet.buildings[BuildingType.SolarPlant] || 0
const energyBonus = 1 + (bonuses.energyProductionBonus || 0) / 100
// 太阳能电站每级产出50 * 1.1^等级
return solarPlantLevel * 50 * Math.pow(1.1, solarPlantLevel) * energyBonus
}
/**
* 计算电量消耗
*/
export const calculateEnergyConsumption = (planet: Planet): number => {
const metalMineLevel = planet.buildings[BuildingType.MetalMine] || 0
const crystalMineLevel = planet.buildings[BuildingType.CrystalMine] || 0
const deuteriumSynthesizerLevel = planet.buildings[BuildingType.DeuteriumSynthesizer] || 0
// 矿场每级消耗10 * 1.1^等级
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 metalConsumption + crystalConsumption + deuteriumConsumption
}
/**
* 计算资源产量(每小时)
*/
export const calculateResourceProduction = (
planet: Planet,
bonuses: {
resourceProductionBonus: number
darkMatterProductionBonus: number
energyProductionBonus: number
}
): Resources => {
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 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)
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
}
}
/**
* 计算资源容量
*/
export const calculateResourceCapacity = (planet: Planet, storageCapacityBonus: number): Resources => {
const metalStorageLevel = planet.buildings[BuildingType.MetalStorage] || 0
const crystalStorageLevel = planet.buildings[BuildingType.CrystalStorage] || 0
const deuteriumTankLevel = planet.buildings[BuildingType.DeuteriumTank] || 0
const darkMatterCollectorLevel = planet.buildings[BuildingType.DarkMatterCollector] || 0
const bonus = 1 + (storageCapacityBonus || 0) / 100
const baseCapacity = 10000
return {
metal: baseCapacity * Math.pow(2, metalStorageLevel) * bonus,
crystal: baseCapacity * Math.pow(2, crystalStorageLevel) * bonus,
deuterium: baseCapacity * Math.pow(2, deuteriumTankLevel) * bonus,
darkMatter: 1000 + darkMatterCollectorLevel * 100, // 暗物质容量较小
energy: 0 // 电量不存储,实时计算
}
}
/**
* 更新星球资源
*/
export const updatePlanetResources = (
planet: Planet,
now: number,
bonuses: {
resourceProductionBonus: number
darkMatterProductionBonus: number
energyProductionBonus: number
storageCapacityBonus: number
}
): void => {
const timeDiff = (now - planet.lastUpdate) / 1000 // 转换为秒
// 计算资源产量(每小时)
const production = calculateResourceProduction(planet, {
resourceProductionBonus: bonuses.resourceProductionBonus,
darkMatterProductionBonus: bonuses.darkMatterProductionBonus,
energyProductionBonus: bonuses.energyProductionBonus
})
// 更新资源(转换为每秒产量)
planet.resources.metal += (production.metal * timeDiff) / 3600
planet.resources.crystal += (production.crystal * timeDiff) / 3600
planet.resources.deuterium += (production.deuterium * timeDiff) / 3600
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)
planet.resources.darkMatter = Math.min(planet.resources.darkMatter, capacity.darkMatter)
planet.lastUpdate = now
}
/**
* 检查资源是否足够
*/
export const checkResourcesAvailable = (currentResources: Resources, cost: Resources): boolean => {
return (
currentResources.metal >= cost.metal &&
currentResources.crystal >= cost.crystal &&
currentResources.deuterium >= cost.deuterium &&
currentResources.darkMatter >= cost.darkMatter
)
}
/**
* 扣除资源
*/
export const deductResources = (currentResources: Resources, cost: Resources): void => {
currentResources.metal -= cost.metal
currentResources.crystal -= cost.crystal
currentResources.deuterium -= cost.deuterium
currentResources.darkMatter -= cost.darkMatter
}
/**
* 添加资源
*/
export const addResources = (currentResources: Resources, amount: Resources): void => {
currentResources.metal += amount.metal
currentResources.crystal += amount.crystal
currentResources.deuterium += amount.deuterium
currentResources.darkMatter += amount.darkMatter
}

231
src/logic/shipLogic.ts Normal file
View File

@@ -0,0 +1,231 @@
import type { Resources, BuildQueueItem, Fleet } from '@/types/game'
import { ShipType, DefenseType, BuildingType, TechnologyType } from '@/types/game'
import { SHIPS, DEFENSES } from '@/config/gameConfig'
/**
* 计算舰船建造成本
*/
export const calculateShipCost = (shipType: ShipType, quantity: number): Resources => {
const config = SHIPS[shipType]
return {
metal: config.cost.metal * quantity,
crystal: config.cost.crystal * quantity,
deuterium: config.cost.deuterium * quantity,
darkMatter: config.cost.darkMatter * quantity,
energy: 0
}
}
/**
* 计算防御设施建造成本
*/
export const calculateDefenseCost = (defenseType: DefenseType, quantity: number): Resources => {
const config = DEFENSES[defenseType]
return {
metal: config.cost.metal * quantity,
crystal: config.cost.crystal * quantity,
deuterium: config.cost.deuterium * quantity,
darkMatter: config.cost.darkMatter * quantity,
energy: 0
}
}
/**
* 计算舰船建造时间
*/
export const calculateShipBuildTime = (shipType: ShipType, quantity: number, buildingSpeedBonus: number = 0): number => {
const config = SHIPS[shipType]
const baseTime = config.buildTime * quantity
const speedMultiplier = 1 - buildingSpeedBonus / 100
return Math.floor(baseTime * speedMultiplier)
}
/**
* 计算防御设施建造时间
*/
export const calculateDefenseBuildTime = (defenseType: DefenseType, quantity: number, buildingSpeedBonus: number = 0): number => {
const config = DEFENSES[defenseType]
const baseTime = config.buildTime * quantity
const speedMultiplier = 1 - buildingSpeedBonus / 100
return Math.floor(baseTime * speedMultiplier)
}
/**
* 检查舰船建造条件
*/
export const checkShipRequirements = (
shipType: ShipType,
buildings: Partial<Record<BuildingType, number>>,
technologies: Partial<Record<TechnologyType, number>>
): boolean => {
const config = SHIPS[shipType]
if (!config.requirements) return true
for (const [key, level] of Object.entries(config.requirements)) {
if (Object.values(BuildingType).includes(key as BuildingType)) {
if ((buildings[key as BuildingType] || 0) < level) {
return false
}
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
if ((technologies[key as TechnologyType] || 0) < level) {
return false
}
}
}
return true
}
/**
* 检查防御设施建造条件
*/
export const checkDefenseRequirements = (
defenseType: DefenseType,
buildings: Partial<Record<BuildingType, number>>,
technologies: Partial<Record<TechnologyType, number>>
): boolean => {
const config = DEFENSES[defenseType]
if (!config.requirements) return true
for (const [key, level] of Object.entries(config.requirements)) {
if (Object.values(BuildingType).includes(key as BuildingType)) {
if ((buildings[key as BuildingType] || 0) < level) {
return false
}
} else if (Object.values(TechnologyType).includes(key as TechnologyType)) {
if ((technologies[key as TechnologyType] || 0) < level) {
return false
}
}
}
return true
}
/**
* 检查防御罩数量限制
*/
export const checkShieldDomeLimit = (
defenseType: DefenseType,
currentDefense: Partial<Record<DefenseType, number>>,
quantity: number
): boolean => {
if (defenseType === DefenseType.SmallShieldDome || defenseType === DefenseType.LargeShieldDome) {
if ((currentDefense[defenseType] || 0) > 0) {
return false
}
if (quantity > 1) {
return false
}
}
return true
}
/**
* 创建舰船建造队列项
*/
export const createShipQueueItem = (shipType: ShipType, quantity: number, buildTime: number): BuildQueueItem => {
const now = Date.now()
return {
id: `ship_${now}`,
type: 'ship',
itemType: shipType,
quantity,
startTime: now,
endTime: now + buildTime * 1000
}
}
/**
* 创建防御设施建造队列项
*/
export const createDefenseQueueItem = (defenseType: DefenseType, quantity: number, buildTime: number): BuildQueueItem => {
const now = Date.now()
return {
id: `defense_${now}`,
type: 'defense',
itemType: defenseType,
quantity,
startTime: now,
endTime: now + buildTime * 1000
}
}
/**
* 检查舰队是否足够
*/
export const checkFleetAvailable = (currentFleet: Partial<Fleet>, requiredFleet: Partial<Fleet>): boolean => {
for (const [shipType, count] of Object.entries(requiredFleet)) {
if ((currentFleet[shipType as ShipType] || 0) < count) {
return false
}
}
return true
}
/**
* 计算舰队燃料消耗(包含货物重量影响)
* @param fleet 舰队组成
* @param fuelConsumptionReduction 燃料消耗减少百分比
* @param cargo 携带的货物(可选)
* @returns 总燃料消耗(重氢)
*/
export const calculateFleetFuelConsumption = (
fleet: Partial<Fleet>,
fuelConsumptionReduction: number = 0,
cargo?: Resources
): number => {
// 计算舰船基础燃料消耗
let baseFuelNeeded = 0
for (const [shipType, count] of Object.entries(fleet)) {
const config = SHIPS[shipType as ShipType]
baseFuelNeeded += config.fuelConsumption * count
}
// 计算货物额外燃料消耗
// 每1000单位资源增加1点燃料消耗
let cargoFuelNeeded = 0
if (cargo) {
const totalCargo = cargo.metal + cargo.crystal + cargo.deuterium + cargo.darkMatter
cargoFuelNeeded = Math.floor(totalCargo / 1000)
}
// 应用燃料消耗减少加成(仅应用于基础燃料,不影响货物燃料)
const reductionMultiplier = 1 - fuelConsumptionReduction / 100
const reducedBaseFuel = Math.floor(baseFuelNeeded * reductionMultiplier)
return reducedBaseFuel + cargoFuelNeeded
}
/**
* 计算舰队最慢速度
*/
export const calculateFleetMinSpeed = (fleet: Partial<Fleet>, fleetSpeedBonus: number = 0): number => {
let minSpeed = Infinity
for (const [shipType, count] of Object.entries(fleet)) {
if (count > 0) {
const config = SHIPS[shipType as ShipType]
minSpeed = Math.min(minSpeed, config.speed)
}
}
const speedMultiplier = 1 + fleetSpeedBonus / 100
return Math.floor(minSpeed * speedMultiplier)
}
/**
* 扣除舰队
*/
export const deductFleet = (currentFleet: Fleet, fleet: Partial<Fleet>): void => {
for (const [shipType, count] of Object.entries(fleet)) {
currentFleet[shipType as ShipType] -= count
}
}
/**
* 添加舰队
*/
export const addFleet = (currentFleet: Fleet, fleet: Partial<Fleet>): void => {
for (const [shipType, count] of Object.entries(fleet)) {
if (count > 0) {
currentFleet[shipType as ShipType] += count
}
}
}

168
src/logic/shipValidation.ts Normal file
View File

@@ -0,0 +1,168 @@
import type { Planet, Resources, BuildQueueItem, Fleet, Officer } from '@/types/game'
import { ShipType, DefenseType, TechnologyType, OfficerType } from '@/types/game'
import * as shipLogic from './shipLogic'
import * as resourceLogic from './resourceLogic'
import * as officerLogic from './officerLogic'
import * as publicLogic from './publicLogic'
/**
* 验证舰船建造的所有条件
*/
export const validateShipBuild = (
planet: Planet,
shipType: ShipType,
quantity: number,
technologies: Partial<Record<TechnologyType, number>>
): {
valid: boolean
reason?: string
} => {
const totalCost = shipLogic.calculateShipCost(shipType, quantity)
// 检查前置条件
if (!shipLogic.checkShipRequirements(shipType, planet.buildings, technologies)) {
return { valid: false, reason: 'errors.requirementsNotMet' }
}
// 检查资源
if (!resourceLogic.checkResourcesAvailable(planet.resources, totalCost)) {
return { valid: false, reason: 'errors.insufficientResources' }
}
return { valid: true }
}
/**
* 执行舰船建造
*/
export const executeShipBuild = (
planet: Planet,
shipType: ShipType,
quantity: number,
officers: Record<OfficerType, Officer>
): BuildQueueItem => {
const totalCost = shipLogic.calculateShipCost(shipType, quantity)
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
const buildTime = shipLogic.calculateShipBuildTime(shipType, quantity, bonuses.buildingSpeedBonus)
// 扣除资源
resourceLogic.deductResources(planet.resources, totalCost)
// 创建队列项
return shipLogic.createShipQueueItem(shipType, quantity, buildTime)
}
/**
* 验证防御建造的所有条件
*/
export const validateDefenseBuild = (
planet: Planet,
defenseType: DefenseType,
quantity: number,
technologies: Partial<Record<TechnologyType, number>>
): {
valid: boolean
reason?: string
} => {
const totalCost = shipLogic.calculateDefenseCost(defenseType, quantity)
// 检查前置条件
if (!shipLogic.checkDefenseRequirements(defenseType, planet.buildings, technologies)) {
return { valid: false, reason: 'errors.requirementsNotMet' }
}
// 检查资源
if (!resourceLogic.checkResourcesAvailable(planet.resources, totalCost)) {
return { valid: false, reason: 'errors.insufficientResources' }
}
// 护盾罩限制
if (!shipLogic.checkShieldDomeLimit(defenseType, planet.defense, quantity)) {
return { valid: false, reason: 'errors.shieldDomeLimit' }
}
return { valid: true }
}
/**
* 执行防御建造
*/
export const executeDefenseBuild = (
planet: Planet,
defenseType: DefenseType,
quantity: number,
officers: Record<OfficerType, Officer>
): BuildQueueItem => {
const totalCost = shipLogic.calculateDefenseCost(defenseType, quantity)
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
const buildTime = shipLogic.calculateDefenseBuildTime(defenseType, quantity, bonuses.buildingSpeedBonus)
// 扣除资源
resourceLogic.deductResources(planet.resources, totalCost)
// 创建队列项
return shipLogic.createDefenseQueueItem(defenseType, quantity, buildTime)
}
/**
* 验证舰队派遣的所有条件
*/
export const validateFleetDispatch = (
planet: Planet,
fleet: Partial<Fleet>,
cargo: Resources,
officers: Record<OfficerType, Officer>,
currentFleetMissions: number = 0
): {
valid: boolean
reason?: string
fuelNeeded?: number
} => {
// 计算军官加成
const bonuses = officerLogic.calculateActiveBonuses(officers, Date.now())
// 检查舰队任务槽位是否已满
const maxFleetMissions = publicLogic.getMaxFleetMissions(bonuses.additionalFleetSlots)
if (currentFleetMissions >= maxFleetMissions) {
return { valid: false, reason: 'errors.fleetMissionsFull' }
}
// 检查舰队是否足够
if (!shipLogic.checkFleetAvailable(planet.fleet, fleet)) {
return { valid: false, reason: 'errors.insufficientFleet' }
}
// 检查是否有足够的重氢作为燃料(包含货物重量影响)
const fuelNeeded = shipLogic.calculateFleetFuelConsumption(fleet, bonuses.fuelConsumptionReduction, cargo)
if (planet.resources.deuterium < fuelNeeded) {
return { valid: false, reason: 'errors.insufficientFuel', fuelNeeded }
}
return { valid: true, fuelNeeded }
}
/**
* 执行舰队派遣(扣除舰队和燃料)
*/
export const executeFleetDispatch = (
planet: Planet,
fleet: Partial<Fleet>,
fuelNeeded: number,
shouldDeductCargo: boolean,
cargo: Resources
): void => {
// 扣除舰队
shipLogic.deductFleet(planet.fleet, fleet)
// 扣除燃料
planet.resources.deuterium -= fuelNeeded
// 扣除运输的资源
if (shouldDeductCargo) {
resourceLogic.deductResources(planet.resources, cargo)
}
}