Files
ogame-vue-ts/src/logic/campaignLogic.ts
wenyu 8e49998205 feat: 为 ID 生成添加随机后缀并增强 GM 面板功能
- 为战斗报告、星球、月球、任务通知和舰队任务等 ID 添加随机后缀,避免重复 ID
- 在 GM 面板中添加预设管理系统,支持保存和应用建筑、研究、舰船和防御的配置
- 在外交界面添加 NPC 排序功能,支持按声望、星球数量、难度和盟友数量排序
- 修复数据迁移中的重复星球 ID 问题,确保月球与母星关联正确
- 优化 GM 面板的资源最大化功能,基于实际存储容量设置资源
- 为所有支持的语言添加相关翻译文本
2026-03-18 17:59:00 +08:00

604 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 战役系统逻辑
* 处理任务进度、解锁、完成等核心逻辑
*/
import {
QuestStatus,
ObjectiveType,
type Player,
type PlayerCampaignProgress,
type QuestObjective,
type QuestReward,
type QuestNotification,
type CampaignQuestConfig,
type NPC,
RelationStatus,
type BuildingType,
type TechnologyType,
type ShipType
} from '@/types/game'
import { MAIN_CAMPAIGN, getAllQuests, getQuestById, getQuestsByChapter } from '@/config/campaignConfig'
import * as resourceLogic from './resourceLogic'
/**
* 初始化玩家战役进度
*/
export const initializeCampaignProgress = (player: Player): PlayerCampaignProgress => {
// 如果已有进度,返回现有进度
if (player.campaignProgress) {
return player.campaignProgress
}
// 创建初始进度,第一个任务默认解锁
const firstQuest = getQuestsByChapter(1)[0]
const progress: PlayerCampaignProgress = {
campaignId: MAIN_CAMPAIGN.id,
currentChapter: 1,
currentQuestId: firstQuest?.id,
questProgress: {},
completedQuests: [],
unlockedQuests: firstQuest ? [firstQuest.id] : [],
branchChoices: {}
}
// 初始化第一个任务为可接取状态
if (firstQuest) {
progress.questProgress[firstQuest.id] = {
questId: firstQuest.id,
status: QuestStatus.Available,
objectives: {}
}
// 初始化目标进度
const questProgress = progress.questProgress[firstQuest.id]
if (questProgress) {
firstQuest.objectives.forEach(obj => {
questProgress.objectives[obj.id] = {
current: 0,
completed: false
}
})
}
}
return progress
}
/**
* 获取任务状态
*/
export const getQuestStatus = (progress: PlayerCampaignProgress, questId: string): QuestStatus => {
const questProgress = progress.questProgress[questId]
if (questProgress) {
return questProgress.status
}
// 检查是否已解锁
if (progress.unlockedQuests.includes(questId)) {
return QuestStatus.Available
}
return QuestStatus.Locked
}
/**
* 开始任务
*/
export const startQuest = (player: Player, questId: string): { success: boolean; error?: string } => {
const progress = player.campaignProgress
if (!progress) {
return { success: false, error: 'campaign.errors.notInitialized' }
}
const quest = getQuestById(questId)
if (!quest) {
return { success: false, error: 'campaign.errors.questNotFound' }
}
// 检查是否已解锁
if (!progress.unlockedQuests.includes(questId)) {
return { success: false, error: 'campaign.errors.questLocked' }
}
// 检查是否已完成
if (progress.completedQuests.includes(questId)) {
return { success: false, error: 'campaign.errors.questAlreadyCompleted' }
}
// 初始化或更新任务进度
if (!progress.questProgress[questId]) {
progress.questProgress[questId] = {
questId,
status: QuestStatus.Active,
objectives: {},
startedAt: Date.now()
}
}
const questProgress = progress.questProgress[questId]
if (questProgress) {
questProgress.status = QuestStatus.Active
questProgress.startedAt = Date.now()
// 初始化目标进度
quest.objectives.forEach(obj => {
if (!questProgress.objectives[obj.id]) {
questProgress.objectives[obj.id] = {
current: 0,
completed: false
}
}
})
}
progress.currentQuestId = questId
return { success: true }
}
/**
* 检查单个目标的进度
*/
export const checkObjectiveProgress = (player: Player, objective: QuestObjective, npcs: NPC[]): number => {
switch (objective.type) {
case ObjectiveType.BuildBuilding: {
// 检查玩家所有星球上该建筑的最高等级
const targetLevel = objective.targetSecondary as number
const buildingType = objective.target as BuildingType
const hasBuilding = player.planets.some(planet => {
const level = planet.buildings[buildingType] || 0
return level >= targetLevel
})
return hasBuilding ? 1 : 0
}
case ObjectiveType.ResearchTech: {
const targetLevel = objective.targetSecondary as number
const techType = objective.target as TechnologyType
const currentLevel = player.technologies[techType] || 0
return currentLevel >= targetLevel ? 1 : 0
}
case ObjectiveType.ProduceShips: {
// 统计所有星球上该类型舰船的总数
const shipType = objective.target as ShipType
let totalShips = 0
player.planets.forEach(planet => {
totalShips += planet.fleet[shipType] || 0
})
return Math.min(totalShips, objective.required)
}
case ObjectiveType.AccumulateResources: {
// 统计所有星球的资源总量
let totalResources = 0
player.planets.forEach(planet => {
totalResources += planet.resources.metal + planet.resources.crystal + planet.resources.deuterium
})
return Math.min(totalResources, objective.required)
}
case ObjectiveType.Colonize: {
// 统计殖民地数量(不包括母星)
const colonyCount = player.planets.length - 1
return Math.min(colonyCount, objective.required)
}
case ObjectiveType.SpyTarget: {
// 统计侦查报告数量
const target = objective.target
if (target === 'any') {
return Math.min(player.spyReports.length, objective.required)
} else if (target === 'hostile') {
// 统计敌对NPC的侦查报告
const hostileNpcIds = npcs
.filter(npc => {
const relation = npc.relations?.[player.id]
return relation?.status === RelationStatus.Hostile
})
.map(npc => npc.id)
const hostileReports = player.spyReports.filter(report => hostileNpcIds.includes(report.targetPlayerId || ''))
return Math.min(hostileReports.length, objective.required)
}
return 0
}
case ObjectiveType.SendGift: {
// 从成就统计中获取送礼次数
return Math.min(player.achievementStats?.giftsSent || 0, objective.required)
}
case ObjectiveType.Expedition: {
// 从成就统计中获取探险次数(使用 expeditionsTotal
return Math.min(player.achievementStats?.expeditionsTotal || 0, objective.required)
}
case ObjectiveType.WinBattles: {
const target = objective.target
if (target === 'attack') {
return Math.min(player.achievementStats?.attacksWon || 0, objective.required)
} else if (target === 'defense') {
// 使用 defensesSuccessful 代替 defensesWon
return Math.min(player.achievementStats?.defensesSuccessful || 0, objective.required)
}
const totalWins = (player.achievementStats?.attacksWon || 0) + (player.achievementStats?.defensesSuccessful || 0)
return Math.min(totalWins, objective.required)
}
case ObjectiveType.RecycleDebris: {
return Math.min(player.achievementStats?.recyclingMissions || 0, objective.required)
}
case ObjectiveType.ReachRelation: {
// 检查是否有任何NPC达到指定关系等级
const targetStatus = objective.target as string
const hasRelation = npcs.some(npc => {
const relation = npc.relations?.[player.id]
return relation?.status === targetStatus
})
return hasRelation ? 1 : 0
}
case ObjectiveType.FormAlliance: {
// 检查是否有盟友(使用 Friendly 作为最高关系等级)
const hasAlliance = npcs.some(npc => {
const relation = npc.relations?.[player.id]
return relation?.status === RelationStatus.Friendly && (relation.reputation || 0) >= 80
})
return hasAlliance ? 1 : 0
}
case ObjectiveType.DefeatNPC: {
// 检查攻击NPC获胜次数
return Math.min(player.achievementStats?.attacksWon || 0, objective.required)
}
case ObjectiveType.DestroyPlanet: {
return Math.min(player.achievementStats?.planetDestructions || 0, objective.required)
}
default:
return 0
}
}
/**
* 更新任务进度
*/
export const updateQuestProgress = (
player: Player,
questId: string,
npcs: NPC[]
): {
updated: boolean
completedObjectives: string[]
questCompleted: boolean
} => {
const result = {
updated: false,
completedObjectives: [] as string[],
questCompleted: false
}
const progress = player.campaignProgress
if (!progress) return result
const questProgress = progress.questProgress[questId]
if (!questProgress || questProgress.status !== QuestStatus.Active) return result
const quest = getQuestById(questId)
if (!quest) return result
// 检查每个目标的进度
quest.objectives.forEach(objective => {
const objProgress = questProgress.objectives[objective.id]
if (!objProgress || objProgress.completed) return
const newProgress = checkObjectiveProgress(player, objective, npcs)
if (newProgress !== objProgress.current) {
objProgress.current = newProgress
result.updated = true
// 检查目标是否完成
if (newProgress >= objective.required && !objProgress.completed) {
objProgress.completed = true
result.completedObjectives.push(objective.id)
}
}
})
// 检查任务是否全部完成
const allCompleted = quest.objectives.every(obj => questProgress.objectives[obj.id]?.completed)
if (allCompleted && questProgress.status === QuestStatus.Active) {
questProgress.status = QuestStatus.Completed
questProgress.completedAt = Date.now()
result.questCompleted = true
}
return result
}
/**
* 领取任务奖励
*/
export const claimQuestRewards = (
player: Player,
questId: string
): {
success: boolean
rewards?: QuestReward
error?: string
} => {
const progress = player.campaignProgress
if (!progress) {
return { success: false, error: 'campaign.errors.notInitialized' }
}
const questProgress = progress.questProgress[questId]
if (!questProgress) {
return { success: false, error: 'campaign.errors.questNotFound' }
}
if (questProgress.status !== QuestStatus.Completed) {
return { success: false, error: 'campaign.errors.questNotCompleted' }
}
if (questProgress.rewardsClaimed) {
return { success: false, error: 'campaign.errors.rewardsAlreadyClaimed' }
}
const quest = getQuestById(questId)
if (!quest) {
return { success: false, error: 'campaign.errors.questNotFound' }
}
// 发放奖励
const rewards = quest.rewards
const currentPlanet = player.planets[0] // 默认发放到第一个星球
if (rewards.resources && currentPlanet) {
resourceLogic.addResources(currentPlanet.resources, rewards.resources)
}
if (rewards.darkMatter && currentPlanet) {
currentPlanet.resources.darkMatter += rewards.darkMatter
}
if (rewards.points) {
// 奖励积分存入 bonusPoints不会被 calculatePlayerPoints 覆盖
player.bonusPoints = (player.bonusPoints || 0) + rewards.points
}
if (rewards.ships && currentPlanet) {
Object.entries(rewards.ships).forEach(([shipType, count]) => {
const ship = shipType as ShipType
currentPlanet.fleet[ship] = (currentPlanet.fleet[ship] || 0) + count
})
}
// 标记奖励已领取
questProgress.rewardsClaimed = true
// 将任务添加到已完成列表
if (!progress.completedQuests.includes(questId)) {
progress.completedQuests.push(questId)
}
// 解锁后续任务
unlockNextQuests(player, questId)
return { success: true, rewards }
}
/**
* 解锁后续任务
*/
export const unlockNextQuests = (player: Player, _completedQuestId: string): string[] => {
const progress = player.campaignProgress
if (!progress) return []
const unlockedQuests: string[] = []
const allQuests = getAllQuests()
allQuests.forEach(quest => {
// 跳过已解锁的任务
if (progress.unlockedQuests.includes(quest.id)) return
// 检查前置任务是否完成
if (quest.requiredQuestIds && quest.requiredQuestIds.length > 0) {
const allRequiredCompleted = quest.requiredQuestIds.every(reqId => progress.completedQuests.includes(reqId))
if (allRequiredCompleted) {
progress.unlockedQuests.push(quest.id)
unlockedQuests.push(quest.id)
// 初始化任务进度
progress.questProgress[quest.id] = {
questId: quest.id,
status: QuestStatus.Available,
objectives: {}
}
const questProgress = progress.questProgress[quest.id]
if (questProgress) {
quest.objectives.forEach(obj => {
questProgress.objectives[obj.id] = {
current: 0,
completed: false
}
})
}
// 更新当前章节
if (quest.chapter > progress.currentChapter) {
progress.currentChapter = quest.chapter
}
}
}
})
return unlockedQuests
}
/**
* 计算战役总进度百分比
*/
export const calculateCampaignProgress = (progress: PlayerCampaignProgress): number => {
const totalQuests = getAllQuests().length
const completedCount = progress.completedQuests.length
return Math.round((completedCount / totalQuests) * 100)
}
/**
* 计算章节进度百分比
*/
export const calculateChapterProgress = (progress: PlayerCampaignProgress, chapterNumber: number): number => {
const chapterQuests = getQuestsByChapter(chapterNumber)
const completedCount = chapterQuests.filter(quest => progress.completedQuests.includes(quest.id)).length
return Math.round((completedCount / chapterQuests.length) * 100)
}
/**
* 计算单个任务的进度百分比
*/
export const calculateQuestProgress = (progress: PlayerCampaignProgress, questId: string): number => {
const questProgress = progress.questProgress[questId]
if (!questProgress) return 0
const quest = getQuestById(questId)
if (!quest) return 0
// 计算所有目标的平均进度
const objectives = quest.objectives
if (objectives.length === 0) return 0
let totalProgress = 0
objectives.forEach(obj => {
const objProgress = questProgress.objectives[obj.id]
if (objProgress) {
if (objProgress.completed) {
totalProgress += 100
} else {
totalProgress += Math.min((objProgress.current / obj.required) * 100, 100)
}
}
})
return Math.round(totalProgress / objectives.length)
}
/**
* 获取当前可进行的任务列表
*/
export const getAvailableQuests = (progress: PlayerCampaignProgress): CampaignQuestConfig[] => {
return getAllQuests().filter(quest => {
const status = getQuestStatus(progress, quest.id)
return status === QuestStatus.Available || status === QuestStatus.Active
})
}
/**
* 获取当前激活的任务
*/
export const getActiveQuest = (progress: PlayerCampaignProgress): CampaignQuestConfig | undefined => {
if (!progress.currentQuestId) return undefined
return getQuestById(progress.currentQuestId)
}
/**
* 创建任务通知
*/
export const createQuestNotification = (
questId: string,
eventType: QuestNotification['eventType'],
messageKey: string,
messageParams?: Record<string, string | number>,
rewards?: QuestReward
): QuestNotification => {
const quest = getQuestById(questId)
return {
id: `quest_notification_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
timestamp: Date.now(),
questId,
questTitleKey: quest?.titleKey || '',
eventType,
messageKey,
messageParams,
rewards,
read: false
}
}
/**
* 添加任务通知到玩家
*/
export const addQuestNotification = (player: Player, notification: QuestNotification): void => {
if (!player.questNotifications) {
player.questNotifications = []
}
player.questNotifications.unshift(notification)
// 限制通知数量
if (player.questNotifications.length > 50) {
player.questNotifications = player.questNotifications.slice(0, 50)
}
}
/**
* 标记通知为已读
*/
export const markQuestNotificationRead = (player: Player, notificationId: string): void => {
const notification = player.questNotifications?.find(n => n.id === notificationId)
if (notification) {
notification.read = true
}
}
/**
* 获取未读通知数量
*/
export const getUnreadQuestNotificationCount = (player: Player): number => {
return player.questNotifications?.filter(n => !n.read).length || 0
}
/**
* 检查并更新所有激活任务的进度
*/
export const checkAllActiveQuestsProgress = (player: Player, npcs: NPC[]): QuestNotification[] => {
const notifications: QuestNotification[] = []
const progress = player.campaignProgress
if (!progress) return notifications
// 遍历所有激活的任务
Object.entries(progress.questProgress).forEach(([questId, questProgress]) => {
if (questProgress.status !== QuestStatus.Active) return
const result = updateQuestProgress(player, questId, npcs)
// 为完成的目标创建通知
result.completedObjectives.forEach(objId => {
const quest = getQuestById(questId)
const objective = quest?.objectives.find(o => o.id === objId)
if (objective) {
notifications.push(
createQuestNotification(questId, 'objective_completed', 'campaign.notifications.objectiveCompleted', {
objective: objective.descriptionKey
})
)
}
})
// 任务完成通知
if (result.questCompleted) {
const quest = getQuestById(questId)
notifications.push(createQuestNotification(questId, 'quest_completed', 'campaign.notifications.questCompleted', {}, quest?.rewards))
}
})
// 添加通知到玩家
notifications.forEach(notification => {
addQuestNotification(player, notification)
})
return notifications
}