feat: 新增NPC与外交逻辑,优化UI组件结构

重构并精简了部分UI组件,移除冗余弹窗与详情组件,新增NPC相关逻辑(npcBehaviorLogic、npcGrowthLogic、npcStore等)及外交逻辑(diplomaticLogic、DiplomacyView)。完善分页、标签、复选框等通用UI组件。优化战报弹窗,调整README下载链接为相对路径,修复部分国际化内容。
This commit is contained in:
谦君
2025-12-15 08:23:45 +08:00
parent 44580909a3
commit 9b9fda0400
164 changed files with 18628 additions and 2775 deletions

View File

@@ -1,9 +1,10 @@
import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer, DebrisField } from '@/types/game'
import { ShipType, DefenseType, MissionType, BuildingType, OfficerType } from '@/types/game'
import type { FleetMission, Planet, Resources, Fleet, BattleResult, SpyReport, Player, Officer, DebrisField, NPC } from '@/types/game'
import { ShipType, DefenseType, MissionType, BuildingType, OfficerType, TechnologyType } from '@/types/game'
import { FLEET_STORAGE_CONFIG } from '@/config/gameConfig'
import * as battleLogic from './battleLogic'
import * as moonLogic from './moonLogic'
import * as moonValidation from './moonValidation'
import * as diplomaticLogic from './diplomaticLogic'
/**
* 计算两个星球之间的距离
@@ -70,15 +71,44 @@ export const createFleetMission = (
/**
* 处理运输任务到达
*/
export const processTransportArrival = (mission: FleetMission, targetPlanet: Planet | undefined): void => {
export const processTransportArrival = (
mission: FleetMission,
targetPlanet: Planet | undefined,
player?: Player,
allNpcs?: NPC[]
): { success: boolean; reputationGain?: number } => {
// 检查是否是赠送任务
if (mission.isGift && mission.giftTargetNpcId && player && allNpcs) {
const targetNpc = allNpcs.find(n => n.id === mission.giftTargetNpcId)
if (targetNpc) {
const giftResult = diplomaticLogic.handleGiftArrival(mission, player, targetNpc)
mission.status = 'returning'
// 如果礼物被拒绝,资源返还给玩家
if (!giftResult.accepted) {
// 资源保留在cargo中返回时会退还给玩家
return { success: false, reputationGain: undefined }
}
// 礼物被接受,清空货物
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
return { success: true, reputationGain: giftResult.reputationGain }
}
}
// 正常运输任务
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 }
return { success: true }
}
mission.status = 'returning'
mission.cargo = { metal: 0, crystal: 0, deuterium: 0, darkMatter: 0, energy: 0 }
return { success: false }
}
/**
@@ -103,7 +133,9 @@ export const processAttackArrival = async (
targetPlanet.defense,
targetPlanet.resources,
attacker.officers,
defender?.officers || ({} as Record<OfficerType, Officer>)
defender?.officers || ({} as Record<OfficerType, Officer>),
attacker.technologies,
defender?.technologies || ({} as Record<TechnologyType, number>)
)
// 更新战斗报告ID
@@ -177,13 +209,134 @@ export const processAttackArrival = async (
return { battleResult, moon, debrisField }
}
/**
* 处理NPC攻击玩家星球
* 专门用于NPC作为攻击方的情况
*/
export const processNPCAttackArrival = async (
npc: NPC,
mission: FleetMission,
targetPlanet: Planet,
defender: Player,
allPlanets: Planet[]
): Promise<{ battleResult: BattleResult; moon: Planet | null; debrisField: DebrisField | null } | null> => {
// 执行战斗(使用 Worker 进行异步计算)
const battleResult = await battleLogic.simulateBattle(
mission.fleet, // NPC舰队
targetPlanet.fleet, // 玩家舰队
targetPlanet.defense, // 玩家防御
targetPlanet.resources, // 玩家资源
{} as Record<OfficerType, Officer>, // NPC没有军官系统
defender.officers || ({} as Record<OfficerType, Officer>), // 玩家军官
npc.technologies, // NPC科技等级
defender.technologies // 玩家科技等级
)
// 更新战斗报告ID和参与者信息
battleResult.id = `battle_${Date.now()}`
battleResult.attackerId = npc.id
battleResult.defenderId = defender.id
battleResult.attackerPlanetId = mission.originPlanetId
battleResult.defenderPlanetId = targetPlanet.id
battleResult.timestamp = Date.now()
// 如果NPC获胜掠夺的资源给NPC的起始星球
if (battleResult.winner === 'attacker' && battleResult.plunder) {
const npcOriginPlanet = npc.planets.find(p => p.id === mission.originPlanetId)
if (npcOriginPlanet) {
// NPC获得掠夺的资源当舰队返回时
mission.cargo = battleResult.plunder
}
}
// 更新NPC舰队 - 计算幸存舰船
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] || 0) - lost)
})
Object.entries(battleResult.defenderLosses.defense).forEach(([defenseType, lost]) => {
targetPlanet.defense[defenseType as DefenseType] = Math.max(0, (targetPlanet.defense[defenseType as DefenseType] || 0) - 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>
// 扣除玩家星球被掠夺的资源
if (battleResult.plunder) {
targetPlanet.resources.metal = Math.max(0, targetPlanet.resources.metal - battleResult.plunder.metal)
targetPlanet.resources.crystal = Math.max(0, targetPlanet.resources.crystal - battleResult.plunder.crystal)
targetPlanet.resources.deuterium = Math.max(0, 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')
}
}
// 创建残骸场(如果有残骸)
let debrisField: DebrisField | null = null
const totalDebris = battleResult.debrisField.metal + battleResult.debrisField.crystal
if (totalDebris > 0) {
debrisField = {
id: `debris_${targetPlanet.position.galaxy}_${targetPlanet.position.system}_${targetPlanet.position.position}`,
position: targetPlanet.position,
resources: {
metal: battleResult.debrisField.metal,
crystal: battleResult.debrisField.crystal
},
createdAt: Date.now()
}
}
return { battleResult, moon, debrisField }
}
/**
* 计算玩家最大星球数量
* 基于天体物理学技术等级
*/
export const calculateMaxPlanets = (astrophysicsLevel: number): number => {
// 基础1个星球 + 每2级天体物理学增加1个殖民地槽位
return 1 + Math.floor(astrophysicsLevel / 2)
}
/**
* 检查玩家是否可以殖民新星球
*/
export const canColonize = (player: Player): boolean => {
const astrophysicsLevel = player.technologies[TechnologyType.Astrophysics] || 0
const maxPlanets = calculateMaxPlanets(astrophysicsLevel)
const currentPlanets = player.planets.length
return currentPlanets < maxPlanets
}
/**
* 处理殖民任务到达
*/
export const processColonizeArrival = (
mission: FleetMission,
targetPlanet: Planet | undefined,
playerId: string,
player: Player,
colonyNameTemplate: string = 'Colony'
): Planet | null => {
if (targetPlanet) {
@@ -192,11 +345,18 @@ export const processColonizeArrival = (
return null
}
// 检查殖民地槽位限制
if (!canColonize(player)) {
// 超出殖民地数量限制,殖民船返回
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,
ownerId: player.id,
position: mission.targetPosition,
resources: { metal: 500, crystal: 500, deuterium: 0, darkMatter: 0, energy: 0 },
buildings: {} as Record<BuildingType, number>,
@@ -205,11 +365,15 @@ export const processColonizeArrival = (
[ShipType.HeavyFighter]: 0,
[ShipType.Cruiser]: 0,
[ShipType.Battleship]: 0,
[ShipType.Battlecruiser]: 0,
[ShipType.Bomber]: 0,
[ShipType.Destroyer]: 0,
[ShipType.SmallCargo]: 0,
[ShipType.LargeCargo]: 0,
[ShipType.ColonyShip]: 0,
[ShipType.Recycler]: 0,
[ShipType.EspionageProbe]: 0,
[ShipType.SolarSatellite]: 0,
[ShipType.DarkMatterHarvester]: 0,
[ShipType.Deathstar]: 0
},
@@ -222,6 +386,8 @@ export const processColonizeArrival = (
[DefenseType.PlasmaTurret]: 0,
[DefenseType.SmallShieldDome]: 0,
[DefenseType.LargeShieldDome]: 0,
[DefenseType.AntiBallisticMissile]: 0,
[DefenseType.InterplanetaryMissile]: 0,
[DefenseType.PlanetaryShield]: 0
},
buildQueue: [],
@@ -242,27 +408,104 @@ export const processColonizeArrival = (
return newPlanet
}
/**
* 计算间谍侦查信息可见度
* 根据双方间谍技术等级差异决定显示哪些信息
*/
export const calculateSpyVisibility = (
attackerSpyLevel: number,
defenderSpyLevel: number,
spyProbeCount: number
): {
showFleet: boolean
showDefense: boolean
showBuildings: boolean
showTechnologies: boolean
} => {
// 技术等级差异 (考虑间谍探测器数量加成)
const levelDiff = attackerSpyLevel - defenderSpyLevel + Math.floor(spyProbeCount / 5)
return {
showFleet: levelDiff >= -1, // Level 2: Resources + fleet
showDefense: levelDiff >= 1, // Level 4: Resources + fleet + defense
showBuildings: levelDiff >= 3, // Level 6: Resources + fleet + defense + buildings
showTechnologies: levelDiff >= 5 // Level 8: Resources + fleet + defense + buildings + technologies
}
}
/**
* 计算间谍侦查被发现概率
*/
export const calculateSpyDetectionChance = (attackerSpyLevel: number, defenderSpyLevel: number, spyProbeCount: number): number => {
// 基础被发现概率
let baseChance = 0.25
// 技术等级差异影响
const levelDiff = defenderSpyLevel - attackerSpyLevel
if (levelDiff > 0) {
baseChance += levelDiff * 0.1 // 防守方技术高,增加被发现概率
} else {
baseChance += levelDiff * 0.05 // 攻击方技术高,减少被发现概率
}
// 间谍探测器数量影响
baseChance -= spyProbeCount * 0.01 // 每个探测器降低1%被发现概率
// 限制在 1% - 99% 之间
return Math.max(0.01, Math.min(0.99, baseChance))
}
/**
* 处理间谍任务到达
*/
export const processSpyArrival = (mission: FleetMission, targetPlanet: Planet | undefined, playerId: string): SpyReport | null => {
export const processSpyArrival = (
mission: FleetMission,
targetPlanet: Planet | undefined,
attacker: Player,
defender: Player | null,
allNpcs?: NPC[]
): SpyReport | null => {
if (!targetPlanet) {
mission.status = 'returning'
return null
}
// 获取间谍技术等级
const attackerSpyLevel = attacker.technologies[TechnologyType.EspionageTechnology] || 0
const defenderSpyLevel = defender?.technologies[TechnologyType.EspionageTechnology] || 0
const spyProbeCount = mission.fleet[ShipType.EspionageProbe] || 0
// 计算信息可见度
const visibility = calculateSpyVisibility(attackerSpyLevel, defenderSpyLevel, spyProbeCount)
// 计算被发现概率
const detectionChance = calculateSpyDetectionChance(attackerSpyLevel, defenderSpyLevel, spyProbeCount)
// 判断是否被发现
const wasDetected = Math.random() < detectionChance
const spyReport: SpyReport = {
id: `spy_${Date.now()}`,
timestamp: Date.now(),
spyId: playerId,
spyId: attacker.id,
targetPlanetId: targetPlanet.id,
targetPlanetName: targetPlanet.name,
targetPosition: { ...targetPlanet.position },
targetPlayerId: targetPlanet.ownerId || 'unknown',
resources: { ...targetPlanet.resources },
fleet: { ...targetPlanet.fleet },
defense: { ...targetPlanet.defense },
buildings: { ...targetPlanet.buildings },
technologies: {},
detectionChance: 0.3
resources: { ...targetPlanet.resources }, // 资源信息始终可见
fleet: visibility.showFleet ? { ...targetPlanet.fleet } : undefined,
defense: visibility.showDefense ? { ...targetPlanet.defense } : undefined,
buildings: visibility.showBuildings ? { ...targetPlanet.buildings } : undefined,
technologies: visibility.showTechnologies && defender ? { ...defender.technologies } : undefined,
detectionChance
}
// 如果目标是NPC星球调用外交逻辑
if (allNpcs && targetPlanet.ownerId) {
const targetNpc = allNpcs.find(npc => npc.planets.some(p => p.id === targetPlanet.id))
if (targetNpc) {
diplomaticLogic.handleSpyReputation(attacker, targetNpc, wasDetected)
}
}
mission.status = 'returning'
@@ -291,7 +534,9 @@ export const processDeployArrival = (mission: FleetMission, targetPlanet: Planet
*/
export const processRecycleArrival = (
mission: FleetMission,
debrisField: DebrisField | undefined
debrisField: DebrisField | undefined,
player?: Player,
allNpcs?: NPC[]
): { collectedResources: Pick<Resources, 'metal' | 'crystal'>; remainingDebris: Pick<Resources, 'metal' | 'crystal'> | null } | null => {
if (!debrisField) {
mission.status = 'returning'
@@ -330,6 +575,11 @@ export const processRecycleArrival = (
mission.status = 'returning'
// 检查是否在NPC星球位置回收残骸如果是则降低好感度
if (player && allNpcs && collectedAmount > 0) {
diplomaticLogic.handleDebrisRecycleReputation(player, debrisField.position, allNpcs)
}
return {
collectedResources: {
metal: collectedMetal,
@@ -348,11 +598,7 @@ export const processRecycleArrival = (
/**
* 计算行星毁灭概率
*/
export const calculateDestructionChance = (
deathstarCount: number,
planetaryShieldCount: number,
planetDefensePower: number
): number => {
export const calculateDestructionChance = (deathstarCount: number, planetaryShieldCount: number, planetDefensePower: number): number => {
// 基础摧毁概率:每艘死星 10%
let baseChance = deathstarCount * 10
@@ -372,10 +618,7 @@ export const calculateDestructionChance = (
/**
* 计算星球总防御力量
*/
export const calculatePlanetDefensePower = (
fleet: Partial<Fleet>,
defense: Partial<Record<DefenseType, number>>
): number => {
export const calculatePlanetDefensePower = (fleet: Partial<Fleet>, defense: Partial<Record<DefenseType, number>>): number => {
let totalPower = 0
// 计算舰队力量
@@ -463,7 +706,8 @@ export const updateFleetMissions = async (
debrisFields: Map<string, DebrisField>,
attacker: Player,
defender: Player | null,
now: number
now: number,
allNpcs?: NPC[]
): Promise<{
completedMissions: string[]
battleReports: BattleResult[]
@@ -499,7 +743,7 @@ export const updateFleetMissions = async (
switch (mission.missionType) {
case MissionType.Transport:
processTransportArrival(mission, targetPlanet)
processTransportArrival(mission, targetPlanet, attacker, allNpcs)
break
case MissionType.Attack: {
@@ -520,7 +764,7 @@ export const updateFleetMissions = async (
}
case MissionType.Colonize:
const newColony = processColonizeArrival(mission, targetPlanet, attacker.id)
const newColony = processColonizeArrival(mission, targetPlanet, attacker)
if (newColony) {
newColonies.push(newColony)
planets.set(targetKey, newColony)
@@ -528,7 +772,7 @@ export const updateFleetMissions = async (
break
case MissionType.Spy:
const spyReport = processSpyArrival(mission, targetPlanet, attacker.id)
const spyReport = processSpyArrival(mission, targetPlanet, attacker, defender)
if (spyReport) {
spyReports.push(spyReport)
}
@@ -544,7 +788,7 @@ export const updateFleetMissions = async (
case MissionType.Recycle:
const debrisId = `debris_${mission.targetPosition.galaxy}_${mission.targetPosition.system}_${mission.targetPosition.position}`
const debrisField = debrisFields.get(debrisId)
const recycleResult = processRecycleArrival(mission, debrisField)
const recycleResult = processRecycleArrival(mission, debrisField, attacker, allNpcs)
if (recycleResult) {
if (recycleResult.remainingDebris) {
// 更新残骸场
@@ -582,7 +826,17 @@ export const updateFleetMissions = async (
}
}
return { completedMissions, battleReports, spyReports, newColonies, newMoons, newDebrisFields, updatedDebrisFields, removedDebrisFieldIds, destroyedPlanetIds }
return {
completedMissions,
battleReports,
spyReports,
newColonies,
newMoons,
newDebrisFields,
updatedDebrisFields,
removedDebrisFieldIds,
destroyedPlanetIds
}
}
/**