Files
text-adventure-game/utils/taskSystem.js
Claude cb412544e9 Initial commit: Text Adventure Game
Features:
- Combat system with AP/EP hit calculation and three-layer defense
- Auto-combat/farming mode
- Item system with stacking support
- Skill system with levels, milestones, and parent skill sync
- Shop system with dynamic pricing
- Inventory management with bulk selling
- Event system
- Game loop with offline earnings
- Save/Load system

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-21 17:13:51 +08:00

601 lines
16 KiB
JavaScript
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.
/**
* 任务系统 - 挂机任务管理、互斥检测
* Phase 6 核心系统实现
*/
import { SKILL_CONFIG } from '@/config/skills.js'
import { ITEM_CONFIG } from '@/config/items.js'
/**
* 任务类型枚举
*/
export const TASK_TYPES = {
READING: 'reading', // 阅读
RESTING: 'resting', // 休息
TRAINING: 'training', // 训练
WORKING: 'working', // 工作
COMBAT: 'combat', // 战斗
EXPLORE: 'explore', // 探索
PRAYING: 'praying' // 祈祷
}
/**
* 任务互斥规则
*/
const MUTEX_RULES = {
// 完全互斥(不能同时进行)
full: [
[TASK_TYPES.COMBAT, TASK_TYPES.TRAINING], // 战斗 vs 训练
[TASK_TYPES.COMBAT, TASK_TYPES.WORKING], // 战斗 vs 工作
[TASK_TYPES.TRAINING, TASK_TYPES.WORKING], // 训练 vs 工作
[TASK_TYPES.EXPLORE, TASK_TYPES.RESTING] // 探索 vs 休息
],
// 部分互斥(需要一心多用技能)
partial: [
[TASK_TYPES.READING, TASK_TYPES.COMBAT], // 阅读 vs 战斗
[TASK_TYPES.READING, TASK_TYPES.TRAINING], // 阅读 vs 训练
[TASK_TYPES.READING, TASK_TYPES.WORKING] // 阅读 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
}
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)
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
// 检查环境惩罚
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 effectiveProgress = task.progress * timeMultiplier
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}`, '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 = 2 * elapsedSeconds
if (playerStore.currentStats.stamina < staminaCost) {
// 耐力不足,任务结束
return { completed: true, rewards: {} }
}
playerStore.currentStats.stamina -= staminaCost
// 给予技能经验
const expPerSecond = 1
const expGain = expPerSecond * elapsedSeconds
if (!task.accumulatedExp) {
task.accumulatedExp = {}
}
if (!task.accumulatedExp[skillId]) {
task.accumulatedExp[skillId] = 0
}
task.accumulatedExp[skillId] += expGain
// 检查是否完成
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 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') {
// 信仰技能经验
if (!playerStore.skills.faith) {
playerStore.skills.faith = { level: 0, exp: 0, unlocked: true }
}
playerStore.skills.faith.exp += exp
continue
}
// 普通技能经验
if (playerStore.skills[skillId]) {
playerStore.skills[skillId].exp += exp
}
}
}
/**
* 获取任务类型名称
*/
function getTaskTypeName(taskType) {
const names = {
reading: '阅读',
resting: '休息',
training: '训练',
working: '工作',
combat: '战斗',
explore: '探索',
praying: '祈祷'
}
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
}