Files
text-adventure-game/utils/taskSystem.js
Claude cef974d94f feat: 优化游戏体验和系统平衡性
- 修复商店物品名称显示问题,添加堆叠物品出售数量选择
- 自动战斗状态持久化,战斗结束显示"寻找中"状态
- 战斗日志显示经验获取详情(战斗经验、武器经验)
- 技能进度条显示当前/最大经验值
- 阅读自动解锁技能并持续获得阅读经验,背包可直接阅读
- 优化训练平衡:时长60秒,经验5点/秒,耐力消耗降低
- 实现自然回复系统:基于体质回复HP/耐力,休息提供3倍加成
- 战斗和训练时不进行自然回复

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-23 19:40:55 +08:00

777 lines
22 KiB
JavaScript
Raw Permalink 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.
/**
* 任务系统 - 挂机任务管理、互斥检测
* 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
}