mirror of
https://github.com/setube/ogame-vue-ts.git
synced 2026-05-12 07:55:11 +08:00
feat: 初始化项目结构与核心功能
引入项目基础目录结构,包含多语言支持、主要页面与组件、核心游戏逻辑、UI 组件库、加密与本地持久化、自动化 Docker 构建流程、GitHub issue 模板(中英文)、README(中英文)、LICENSE 及开发配置文件。实现 OGame 单机版主要功能模块,为后续开发和扩展奠定基础。
This commit is contained in:
353
src/logic/battleLogic.ts
Normal file
353
src/logic/battleLogic.ts
Normal 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
194
src/logic/buildingLogic.ts
Normal 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
|
||||
}
|
||||
}
|
||||
161
src/logic/buildingValidation.ts
Normal file
161
src/logic/buildingValidation.ts
Normal 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
374
src/logic/fleetLogic.ts
Normal 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
132
src/logic/gameLogic.ts
Normal 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
111
src/logic/moonLogic.ts
Normal 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
|
||||
)
|
||||
}
|
||||
58
src/logic/moonValidation.ts
Normal file
58
src/logic/moonValidation.ts
Normal 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
96
src/logic/officerLogic.ts
Normal 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
193
src/logic/planetLogic.ts
Normal 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
137
src/logic/pointsLogic.ts
Normal 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
102
src/logic/publicLogic.ts
Normal 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)
|
||||
}
|
||||
96
src/logic/researchLogic.ts
Normal file
96
src/logic/researchLogic.ts
Normal 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
|
||||
})
|
||||
}
|
||||
81
src/logic/researchValidation.ts
Normal file
81
src/logic/researchValidation.ts
Normal 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
161
src/logic/resourceLogic.ts
Normal 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
231
src/logic/shipLogic.ts
Normal 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
168
src/logic/shipValidation.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user