Files

777 lines
22 KiB
JavaScript
Raw Permalink Normal View History

/**
* 任务系统 - 挂机任务管理互斥检测
* Phase 6 核心系统实现
*/
import { SKILL_CONFIG } from '@/config/skills.js'
import { ITEM_CONFIG } from '@/config/items.js'
import { RECIPE_CONFIG } from '@/config/recipes.js'
import { addSkillExp, unlockSkill } from './skillSystem.js'
/**
* 任务类型枚举
*/
export const TASK_TYPES = {
READING: 'reading', // 阅读
RESTING: 'resting', // 休息
TRAINING: 'training', // 训练
WORKING: 'working', // 工作
COMBAT: 'combat', // 战斗
EXPLORE: 'explore', // 探索
PRAYING: 'praying', // 祈祷
CRAFTING: 'crafting' // 制造
}
/**
* 任务互斥规则
*/
const MUTEX_RULES = {
// 完全互斥(不能同时进行)
full: [
[TASK_TYPES.COMBAT, TASK_TYPES.TRAINING], // 战斗 vs 训练
[TASK_TYPES.COMBAT, TASK_TYPES.WORKING], // 战斗 vs 工作
[TASK_TYPES.COMBAT, TASK_TYPES.CRAFTING], // 战斗 vs 制造
[TASK_TYPES.TRAINING, TASK_TYPES.WORKING], // 训练 vs 工作
[TASK_TYPES.TRAINING, TASK_TYPES.CRAFTING], // 训练 vs 制造
[TASK_TYPES.EXPLORE, TASK_TYPES.RESTING], // 探索 vs 休息
[TASK_TYPES.EXPLORE, TASK_TYPES.CRAFTING] // 探索 vs 制造
],
// 部分互斥(需要一心多用技能)
partial: [
[TASK_TYPES.READING, TASK_TYPES.COMBAT], // 阅读 vs 战斗
[TASK_TYPES.READING, TASK_TYPES.TRAINING], // 阅读 vs 训练
[TASK_TYPES.READING, TASK_TYPES.WORKING], // 阅读 vs 工作
[TASK_TYPES.READING, TASK_TYPES.CRAFTING] // 阅读 vs 制造
]
}
/**
* 开始任务
* @param {Object} gameStore - 游戏Store
* @param {Object} playerStore - 玩家Store
* @param {String} taskType - 任务类型
* @param {Object} taskData - 任务数据
* @returns {Object} { success: boolean, message: string, taskId: string|null }
*/
export function startTask(gameStore, playerStore, taskType, taskData = {}) {
// 检查是否可以开始任务
const canStart = canStartTask(gameStore, playerStore, taskType)
if (!canStart.canStart) {
return { success: false, message: canStart.reason }
}
// 检查任务特定条件
const taskCheck = checkTaskConditions(playerStore, taskType, taskData)
if (!taskCheck.canStart) {
return { success: false, message: taskCheck.reason }
}
// 创建任务
const task = {
id: Date.now(),
type: taskType,
data: taskData,
startTime: Date.now(),
progress: 0,
totalDuration: taskData.duration || 0,
lastTickTime: Date.now()
}
gameStore.activeTasks.push(task)
return {
success: true,
message: '任务已开始',
taskId: task.id
}
}
/**
* 检查是否可以开始任务
* @param {Object} gameStore - 游戏Store
* @param {Object} playerStore - 玩家Store
* @param {String} taskType - 任务类型
* @returns {Object} { canStart: boolean, reason: string }
*/
export function canStartTask(gameStore, playerStore, taskType) {
// 检查是否已有相同类型的任务
const existingTask = gameStore.activeTasks.find(t => t.type === taskType)
if (existingTask) {
return { canStart: false, reason: '已有相同类型的任务正在进行' }
}
// 检查互斥规则
for (const [type1, type2] of MUTEX_RULES.full) {
if (taskType === type1) {
const conflictTask = gameStore.activeTasks.find(t => t.type === type2)
if (conflictTask) {
return { canStart: false, reason: `${getTaskTypeName(type2)}任务冲突` }
}
}
if (taskType === type2) {
const conflictTask = gameStore.activeTasks.find(t => t.type === type1)
if (conflictTask) {
return { canStart: false, reason: `${getTaskTypeName(type1)}任务冲突` }
}
}
}
// 检查部分互斥(需要一心多用技能)
const multitaskingLevel = playerStore.skills.multitasking?.level || 0
const maxConcurrentTasks = 1 + Math.floor(multitaskingLevel / 5) // 每5级一心多用可多1个任务
const activePartialTasks = gameStore.activeTasks.filter(t =>
MUTEX_RULES.partial.some(pair => pair.includes(t.type))
).length
const isPartialConflict = MUTEX_RULES.partial.some(pair =>
pair.includes(taskType) && pair.some(type =>
gameStore.activeTasks.some(t => t.type === type)
)
)
if (isPartialConflict && activePartialTasks >= maxConcurrentTasks) {
return { canStart: false, reason: `需要一心多用技能Lv.${multitaskingLevel + 1}` }
}
return { canStart: true, reason: '' }
}
/**
* 检查任务特定条件
*/
function checkTaskConditions(playerStore, taskType, taskData) {
switch (taskType) {
case TASK_TYPES.READING:
// 需要书籍
if (!taskData.itemId) {
return { canStart: false, reason: '需要选择一本书' }
}
const bookConfig = ITEM_CONFIG[taskData.itemId]
if (!bookConfig || bookConfig.type !== 'book') {
return { canStart: false, reason: '这不是一本书' }
}
// 检查是否已拥有
const hasBook = playerStore.inventory.some(i => i.id === taskData.itemId)
if (!hasBook) {
return { canStart: false, reason: '你没有这本书' }
}
break
case TASK_TYPES.TRAINING:
// 需要耐力
if (playerStore.currentStats.stamina < 10) {
return { canStart: false, reason: '耐力不足' }
}
// 需要武器技能
if (!taskData.skillId) {
return { canStart: false, reason: '需要选择训练技能' }
}
const skill = playerStore.skills[taskData.skillId]
if (!skill || !skill.unlocked) {
return { canStart: false, reason: '技能未解锁' }
}
break
case TASK_TYPES.RESTING:
// 只能在安全区休息
const safeZones = ['camp', 'market', 'blackmarket']
if (!safeZones.includes(playerStore.currentLocation)) {
return { canStart: false, reason: '只能在安全区休息' }
}
break
case TASK_TYPES.PRAYING:
// 需要圣经
const hasBible = playerStore.inventory.some(i => i.id === 'bible')
if (!hasBible) {
return { canStart: false, reason: '需要圣经' }
}
break
case TASK_TYPES.CRAFTING:
// 需要配方ID
if (!taskData.recipeId) {
return { canStart: false, reason: '需要选择配方' }
}
// 需要耐力
if (playerStore.currentStats.stamina < 5) {
return { canStart: false, reason: '耐力不足' }
}
break
}
return { canStart: true, reason: '' }
}
/**
* 结束任务
* @param {Object} gameStore - 游戏Store
* @param {Object} playerStore - 玩家Store
* @param {String|Number} taskId - 任务ID
* @param {Boolean} completed - 是否完成
* @returns {Object} { success: boolean, rewards: Object }
*/
export function endTask(gameStore, playerStore, taskId, completed = false) {
const taskIndex = gameStore.activeTasks.findIndex(t => t.id === taskId)
if (taskIndex === -1) {
return { success: false, message: '任务不存在' }
}
const task = gameStore.activeTasks[taskIndex]
const rewards = {}
// 如果完成,给予奖励
if (completed) {
const taskRewards = getTaskRewards(playerStore, task)
Object.assign(rewards, taskRewards)
applyTaskRewards(playerStore, task, taskRewards)
}
// 移除任务
gameStore.activeTasks.splice(taskIndex, 1)
return {
success: true,
message: completed ? '任务完成' : '任务已取消',
rewards
}
}
/**
* 处理任务tick每秒调用
* @param {Object} gameStore - 游戏Store
* @param {Object} playerStore - 玩家Store
* @param {Number} deltaTime - 时间增量毫秒
* @returns {Array} 完成的任务列表
*/
export function processTaskTick(gameStore, playerStore, deltaTime = 1000) {
const completedTasks = []
for (const task of gameStore.activeTasks) {
const result = processSingleTask(gameStore, playerStore, task, deltaTime)
if (result.completed) {
completedTasks.push({ task, rewards: result.rewards })
}
}
// 移除已完成的任务
for (const { task } of completedTasks) {
const index = gameStore.activeTasks.findIndex(t => t.id === task.id)
if (index !== -1) {
gameStore.activeTasks.splice(index, 1)
}
}
return completedTasks
}
/**
* 处理单个任务
*/
function processSingleTask(gameStore, playerStore, task, deltaTime) {
const now = Date.now()
const elapsedSeconds = (now - task.lastTickTime) / 1000
task.lastTickTime = now
task.progress += elapsedSeconds
switch (task.type) {
case TASK_TYPES.READING:
return processReadingTask(gameStore, playerStore, task, elapsedSeconds)
case TASK_TYPES.RESTING:
return processRestingTask(gameStore, playerStore, task, elapsedSeconds)
case TASK_TYPES.TRAINING:
return processTrainingTask(gameStore, playerStore, task, elapsedSeconds)
case TASK_TYPES.WORKING:
return processWorkingTask(gameStore, playerStore, task, elapsedSeconds)
case TASK_TYPES.PRAYING:
return processPrayingTask(gameStore, playerStore, task, elapsedSeconds)
case TASK_TYPES.CRAFTING:
return processCraftingTask(gameStore, playerStore, task, elapsedSeconds)
default:
return { completed: false }
}
}
/**
* 处理阅读任务
*/
function processReadingTask(gameStore, playerStore, task, elapsedSeconds) {
const bookConfig = ITEM_CONFIG[task.data.itemId]
if (!bookConfig) {
return { completed: true, error: '书籍不存在' }
}
const readingTime = bookConfig.readingTime || 60
// 首次阅读时解锁阅读技能
unlockSkill(playerStore, 'reading')
// 检查环境惩罚
const location = playerStore.currentLocation
let timeMultiplier = 1.0
// 黑暗区域阅读效率-50%
if (location === 'basement') {
const darkPenaltyReduce = playerStore.globalBonus?.darkPenaltyReduce || 0
timeMultiplier = 0.5 + (darkPenaltyReduce / 100) * 0.5
}
// 阅读技能速度加成
const readingSkillLevel = playerStore.skills.reading?.level || 0
const readingSpeedBonus = playerStore.globalBonus?.readingSpeed || 1
timeMultiplier *= readingSpeedBonus
// 计算有效进度
const effectiveProgress = task.progress * timeMultiplier
// 给予阅读技能经验(每秒给予经验)
const readingExpPerSecond = 1
const readingExpGain = readingExpPerSecond * elapsedSeconds * timeMultiplier
const readingResult = addSkillExp(playerStore, 'reading', readingExpGain)
// 检查技能是否升级
if (readingResult.leveledUp) {
if (gameStore.addLog) {
gameStore.addLog(`阅读技能升级到了 Lv.${readingResult.newLevel}!`, 'reward')
}
}
if (effectiveProgress >= readingTime) {
// 阅读完成
const rewards = {}
// 给予技能经验
if (bookConfig.expReward) {
for (const [skillId, exp] of Object.entries(bookConfig.expReward)) {
rewards[skillId] = exp
}
}
// 完成奖励
if (bookConfig.completionBonus) {
rewards.completionBonus = bookConfig.completionBonus
}
// 添加日志
if (gameStore.addLog) {
gameStore.addLog(`读完了《${bookConfig.name}》,阅读经验+${Math.floor(readingExpGain * task.progress / readingTime)}`, 'reward')
}
return { completed: true, rewards }
}
// 阅读进行中,给予其他技能经验
if (bookConfig.expReward) {
for (const [skillId, expPerSecond] of Object.entries(bookConfig.expReward)) {
const expPerTick = (expPerSecond / readingTime) * elapsedSeconds * timeMultiplier
// 累积经验,到完成时一次性给予
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp[skillId]) {
task.accumulatedExp[skillId] = 0
}
task.accumulatedExp[skillId] += expPerTick
}
}
return { completed: false }
}
/**
* 处理休息任务
*/
function processRestingTask(gameStore, playerStore, task, elapsedSeconds) {
// 恢复耐力和生命值
const staminaRecovery = 5 * elapsedSeconds // 每秒恢复5点
const healthRecovery = 2 * elapsedSeconds // 每秒恢复2点
const oldStamina = playerStore.currentStats.stamina
const oldHealth = playerStore.currentStats.health
playerStore.currentStats.stamina = Math.min(
playerStore.currentStats.maxStamina,
playerStore.currentStats.stamina + staminaRecovery
)
playerStore.currentStats.health = Math.min(
playerStore.currentStats.maxHealth,
playerStore.currentStats.health + healthRecovery
)
// 检查是否完成
if (task.data.duration && task.progress >= task.data.duration) {
return { completed: true, rewards: {} }
}
// 满状态自动完成
if (playerStore.currentStats.stamina >= playerStore.currentStats.maxStamina &&
playerStore.currentStats.health >= playerStore.currentStats.maxHealth) {
return { completed: true, rewards: {} }
}
return { completed: false }
}
/**
* 处理训练任务
*/
function processTrainingTask(gameStore, playerStore, task, elapsedSeconds) {
const skillId = task.data.skillId
const skillConfig = SKILL_CONFIG[skillId]
if (!skillConfig) {
return { completed: true, error: '技能不存在' }
}
// 消耗耐力(降低消耗)
const staminaCost = 1 * elapsedSeconds
if (playerStore.currentStats.stamina < staminaCost) {
// 耐力不足,任务结束
return { completed: true, rewards: {} }
}
playerStore.currentStats.stamina -= staminaCost
// 给予技能经验(提升获取速度)
const expPerSecond = 5 // 从1提升到5
const expGain = expPerSecond * elapsedSeconds
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp[skillId]) {
task.accumulatedExp[skillId] = 0
}
task.accumulatedExp[skillId] += expGain
// 检查技能是否升级
const result = addSkillExp(playerStore, skillId, expGain)
if (result.leveledUp && gameStore.addLog) {
gameStore.addLog(`${skillConfig.name}升级到了 Lv.${result.newLevel}!`, 'reward')
}
// 检查是否完成
if (task.data.duration && task.progress >= task.data.duration) {
const rewards = { [skillId]: task.accumulatedExp[skillId] || 0 }
return { completed: true, rewards }
}
return { completed: false }
}
/**
* 处理工作任务
*/
function processWorkingTask(gameStore, playerStore, task, elapsedSeconds) {
// 消耗耐力
const staminaCost = 1.5 * elapsedSeconds
if (playerStore.currentStats.stamina < staminaCost) {
return { completed: true, rewards: {} }
}
playerStore.currentStats.stamina -= staminaCost
// 累积货币
if (!task.accumulatedCurrency) {
task.accumulatedCurrency = 0
}
task.accumulatedCurrency += 1 * elapsedSeconds // 每秒1铜币
// 给予相关技能经验(如果有)
if (task.data.skillId) {
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp[task.data.skillId]) {
task.accumulatedExp[task.data.skillId] = 0
}
task.accumulatedExp[task.data.skillId] += 0.5 * elapsedSeconds
}
// 检查是否完成
if (task.data.duration && task.progress >= task.data.duration) {
const rewards = {
currency: Math.floor(task.accumulatedCurrency || 0),
...task.accumulatedExp
}
return { completed: true, rewards }
}
return { completed: false }
}
/**
* 处理祈祷任务
*/
function processPrayingTask(gameStore, playerStore, task, elapsedSeconds) {
// 祈祷获得信仰经验
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp.faith) {
task.accumulatedExp.faith = 0
}
task.accumulatedExp.faith += 0.8 * elapsedSeconds
// 恢复精神
const sanityRecovery = 3 * elapsedSeconds
playerStore.currentStats.sanity = Math.min(
playerStore.currentStats.maxSanity,
playerStore.currentStats.sanity + sanityRecovery
)
// 检查是否完成
if (task.data.duration && task.progress >= task.data.duration) {
const rewards = { faith: task.accumulatedExp.faith || 0 }
return { completed: true, rewards }
}
return { completed: false }
}
/**
* 处理制造任务
*/
function processCraftingTask(gameStore, playerStore, task, elapsedSeconds) {
const recipeId = task.data.recipeId
const recipe = RECIPE_CONFIG[recipeId]
if (!recipe) {
return { completed: true, error: '配方不存在' }
}
// 消耗耐力(制造也需要体力)
const staminaCost = 1 * elapsedSeconds
if (playerStore.currentStats.stamina < staminaCost) {
return { completed: true, rewards: {}, failed: true }
}
playerStore.currentStats.stamina -= staminaCost
// 计算实际制造时间(受技能加成影响)
const craftingSkill = playerStore.skills[recipe.requiredSkill]
let timeMultiplier = 1.0
if (craftingSkill && craftingSkill.level > 0) {
// 制造技能每级减少5%时间
timeMultiplier = 1 - (Math.min(craftingSkill.level, 20) * 0.05)
}
// 累积制造进度(考虑时间加成)
const effectiveProgress = task.progress * timeMultiplier
// 检查是否完成
if (effectiveProgress >= recipe.baseTime) {
// 计算成功率
const successRate = calculateCraftingSuccessRate(playerStore, recipe)
// 判定是否成功
const success = Math.random() < successRate
if (success) {
// 给予制造技能经验
const expGain = recipe.baseTime * 0.5 // 基于制造时间的经验
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp[recipe.requiredSkill]) {
task.accumulatedExp[recipe.requiredSkill] = 0
}
task.accumulatedExp[recipe.requiredSkill] += expGain
const rewards = {
[recipe.requiredSkill]: task.accumulatedExp[recipe.requiredSkill] || 0,
craftedItem: recipe.resultItem,
craftedCount: recipe.resultCount
}
// 添加制造物品到背包(这将在任务完成回调中处理)
if (gameStore.addLog) {
const itemConfig = ITEM_CONFIG[recipe.resultItem]
gameStore.addLog(`制造成功:${itemConfig?.name || recipe.resultItem}`, 'reward')
}
return { completed: true, rewards, success: true }
} else {
// 制造失败
if (gameStore.addLog) {
gameStore.addLog(`制造失败:${recipeId}`, 'warning')
}
// 失败也给予少量经验
const expGain = recipe.baseTime * 0.1
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp[recipe.requiredSkill]) {
task.accumulatedExp[recipe.requiredSkill] = 0
}
task.accumulatedExp[recipe.requiredSkill] += expGain
const rewards = {
[recipe.requiredSkill]: task.accumulatedExp[recipe.requiredSkill] || 0
}
return { completed: true, rewards, success: false }
}
}
// 进行中,给予少量经验
const expPerTick = 0.5 * elapsedSeconds
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp[recipe.requiredSkill]) {
task.accumulatedExp[recipe.requiredSkill] = 0
}
task.accumulatedExp[recipe.requiredSkill] += expPerTick
return { completed: false }
}
/**
* 计算制造成功率
* @param {Object} playerStore - 玩家Store
* @param {Object} recipe - 配方对象
* @returns {Number} 成功率 (0-1)
*/
function calculateCraftingSuccessRate(playerStore, recipe) {
let successRate = recipe.baseSuccessRate
// 技能等级加成(每级+2%
const skill = playerStore.skills[recipe.requiredSkill]
if (skill && skill.level > 0) {
successRate += Math.min(skill.level, 20) * 0.02
}
// 运气加成
const luck = playerStore.baseStats?.luck || 10
successRate += luck * 0.001
// 检查是否有制造技能的全局加成
if (playerStore.globalBonus?.craftingSuccessRate) {
successRate += playerStore.globalBonus.craftingSuccessRate / 100
}
return Math.min(0.98, Math.max(0.05, successRate))
}
/**
* 获取任务奖励
*/
function getTaskRewards(playerStore, task) {
const rewards = {}
if (task.accumulatedExp) {
for (const [skillId, exp] of Object.entries(task.accumulatedExp)) {
rewards[skillId] = Math.floor(exp)
}
}
if (task.accumulatedCurrency) {
rewards.currency = Math.floor(task.accumulatedCurrency)
}
return rewards
}
/**
* 应用任务奖励
*/
function applyTaskRewards(playerStore, task, rewards) {
// 应用技能经验
for (const [skillId, exp] of Object.entries(rewards)) {
if (skillId === 'currency') {
playerStore.currency.copper += exp
continue
}
if (skillId === 'faith') {
// 信仰技能经验 - 使用addSkillExp处理
if (!playerStore.skills.faith) {
unlockSkill(playerStore, 'faith')
}
addSkillExp(playerStore, 'faith', exp)
continue
}
// 普通技能经验 - 使用addSkillExp处理升级和里程碑
if (playerStore.skills[skillId]) {
addSkillExp(playerStore, skillId, exp)
}
}
}
/**
* 获取任务类型名称
*/
function getTaskTypeName(taskType) {
const names = {
reading: '阅读',
resting: '休息',
training: '训练',
working: '工作',
combat: '战斗',
explore: '探索',
praying: '祈祷',
crafting: '制造'
}
return names[taskType] || taskType
}
/**
* 获取任务进度信息
* @param {Object} task - 任务对象
* @returns {Object} 进度信息
*/
export function getTaskProgress(task) {
const progress = {
current: task.progress,
total: task.totalDuration || 0,
percentage: task.totalDuration > 0 ? Math.min(100, (task.progress / task.totalDuration) * 100) : 0
}
if (task.type === TASK_TYPES.READING) {
const bookConfig = ITEM_CONFIG[task.data.itemId]
if (bookConfig) {
progress.total = bookConfig.readingTime || 60
progress.percentage = Math.min(100, (task.progress / progress.total) * 100)
}
}
return progress
}
/**
* 获取活动中的任务列表
* @param {Object} gameStore - 游戏Store
* @returns {Array} 任务列表
*/
export function getActiveTasks(gameStore) {
return gameStore.activeTasks.map(task => ({
...task,
progress: getTaskProgress(task),
typeName: getTaskTypeName(task.type)
}))
}
/**
* 取消所有任务
* @param {Object} gameStore - 游戏Store
* @returns {Number} 取消的任务数量
*/
export function cancelAllTasks(gameStore) {
const count = gameStore.activeTasks.length
gameStore.activeTasks = []
return count
}