Files
text-adventure-game/utils/environmentSystem.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

483 lines
13 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 { LOCATION_CONFIG } from '@/config/locations.js'
import { SKILL_CONFIG } from '@/config/skills.js'
/**
* 环境类型枚举
*/
export const ENVIRONMENT_TYPES = {
NORMAL: 'normal', // 普通环境
DARK: 'dark', // 黑暗环境
NARROW: 'narrow', // 狭窄空间
HARSH: 'harsh' // 恶劣环境
}
/**
* 环境惩罚配置
*/
const ENVIRONMENT_PENALTIES = {
[ENVIRONMENT_TYPES.DARK]: {
apPenalty: 0.2, // AP -20%
epPenalty: 0.2, // EP -20%
readingPenalty: 0.5, // 阅读效率 -50%
adaptSkill: 'night_vision', // 适应技能
description: '黑暗'
},
[ENVIRONMENT_TYPES.NARROW]: {
apPenalty: 0, // AP 无惩罚
epPenalty: 0.3, // EP -30%
readingPenalty: 0, // 阅读无惩罚
adaptSkill: 'narrow_fighting', // 适应技能
description: '狭窄'
},
[ENVIRONMENT_TYPES.HARSH]: {
apPenalty: 0.1, // AP -10%
epPenalty: 0.1, // EP -10%
readingPenalty: 0.2, // 阅读效率 -20%
adaptSkill: 'survivalist', // 适应技能
description: '恶劣'
},
[ENVIRONMENT_TYPES.NORMAL]: {
apPenalty: 0,
epPenalty: 0,
readingPenalty: 0,
adaptSkill: null,
description: '普通'
}
}
/**
* 获取当前位置的环境惩罚
* @param {String} locationId - 位置ID
* @param {Object} playerStore - 玩家Store
* @returns {Object} 惩罚信息
*/
export function getEnvironmentPenalty(locationId, playerStore) {
const location = LOCATION_CONFIG[locationId]
if (!location) {
return getDefaultPenalty()
}
const environmentType = location.environment || ENVIRONMENT_TYPES.NORMAL
const basePenalty = ENVIRONMENT_PENALTIES[environmentType] || ENVIRONMENT_PENALTIES[ENVIRONMENT_TYPES.NORMAL]
// 应用技能减少惩罚
const penalty = { ...basePenalty }
// 夜视技能减少黑暗惩罚
if (environmentType === ENVIRONMENT_TYPES.DARK) {
const darkPenaltyReduce = playerStore.globalBonus?.darkPenaltyReduce || 0
penalty.apPenalty = Math.max(0, penalty.apPenalty * (1 - darkPenaltyReduce / 100))
penalty.epPenalty = Math.max(0, penalty.epPenalty * (1 - darkPenaltyReduce / 100))
penalty.readingPenalty = Math.max(0, penalty.readingPenalty * (1 - darkPenaltyReduce / 100))
}
return penalty
}
/**
* 获取默认惩罚(普通环境)
*/
function getDefaultPenalty() {
return {
apPenalty: 0,
epPenalty: 0,
readingPenalty: 0,
adaptSkill: null,
description: '普通'
}
}
/**
* 计算AP环境修正
* @param {String} locationId - 位置ID
* @param {Object} playerStore - 玩家Store
* @returns {Number} 修正倍率 (0-1)
*/
export function getAPModifier(locationId, playerStore) {
const penalty = getEnvironmentPenalty(locationId, playerStore)
return 1 - penalty.apPenalty
}
/**
* 计算EP环境修正
* @param {String} locationId - 位置ID
* @param {Object} playerStore - 玩家Store
* @returns {Number} 修正倍率 (0-1)
*/
export function getEPModifier(locationId, playerStore) {
const penalty = getEnvironmentPenalty(locationId, playerStore)
return 1 - penalty.epPenalty
}
/**
* 计算阅读效率修正
* @param {String} locationId - 位置ID
* @param {Object} playerStore - 玩家Store
* @returns {Number} 修正倍率 (0-1)
*/
export function getReadingModifier(locationId, playerStore) {
const penalty = getEnvironmentPenalty(locationId, playerStore)
return 1 - penalty.readingPenalty
}
/**
* 处理环境被动经验
* 在特定环境中被动获得技能经验
* @param {Object} gameStore - 游戏Store
* @param {Object} playerStore - 玩家Store
* @param {String} locationId - 位置ID
* @param {Number} deltaTime - 时间增量(秒)
* @returns {Object} 获得的经验 { skillId: exp }
*/
export function processEnvironmentExp(gameStore, playerStore, locationId, deltaTime = 1) {
const location = LOCATION_CONFIG[locationId]
if (!location) return {}
const environmentType = location.environment || ENVIRONMENT_TYPES.NORMAL
const penalty = ENVIRONMENT_PENALTIES[environmentType]
if (!penalty.adaptSkill) {
return {}
}
const adaptSkillId = penalty.adaptSkill
const skillConfig = SKILL_CONFIG[adaptSkillId]
if (!skillConfig) {
return {}
}
// 检查技能是否已解锁
if (!playerStore.skills[adaptSkillId] || !playerStore.skills[adaptSkillId].unlocked) {
// 检查解锁条件
if (skillConfig.unlockCondition) {
const condition = skillConfig.unlockCondition
if (condition.location === locationId) {
// 首次进入该环境,解锁技能
if (!playerStore.skills[adaptSkillId]) {
playerStore.skills[adaptSkillId] = {
level: 0,
exp: 0,
unlocked: true
}
if (gameStore.addLog) {
gameStore.addLog(`解锁了被动技能: ${skillConfig.name}`, 'system')
}
}
}
}
return {}
}
// 计算被动经验(每秒获得基础经验)
const baseExpRate = 0.5 // 每秒0.5经验
const expGain = baseExpRate * deltaTime
// 应用阅读速度加成
const readingSpeedBonus = playerStore.globalBonus?.readingSpeed || 1
const finalExp = expGain * readingSpeedBonus
// 添加经验
playerStore.skills[adaptSkillId].exp += finalExp
// 检查升级
const exp = playerStore.skills[adaptSkillId].exp
const maxExp = skillConfig.expPerLevel(playerStore.skills[adaptSkillId].level + 1)
if (exp >= maxExp && playerStore.skills[adaptSkillId].level < skillConfig.maxLevel) {
playerStore.skills[adaptSkillId].level++
playerStore.skills[adaptSkillId].exp -= maxExp
if (gameStore.addLog) {
gameStore.addLog(`${skillConfig.name} 提升到了 Lv.${playerStore.skills[adaptSkillId].level}!`, 'reward')
}
// 检查里程碑奖励(需要调用技能系统)
// TODO: 调用 applyMilestoneBonus
}
return { [adaptSkillId]: finalExp }
}
/**
* 检查位置解锁条件
* @param {String} locationId - 位置ID
* @param {Object} playerStore - 玩家Store
* @returns {Object} { unlocked: boolean, reason: string }
*/
export function checkLocationUnlock(locationId, playerStore) {
const location = LOCATION_CONFIG[locationId]
if (!location) {
return { unlocked: false, reason: '位置不存在' }
}
if (!location.unlockCondition) {
return { unlocked: true, reason: '' }
}
const condition = location.unlockCondition
switch (condition.type) {
case 'kill':
// 击杀条件
const killCount = (playerStore.killCount || {})[condition.target] || 0
if (killCount < condition.count) {
return {
unlocked: false,
reason: `需要击杀 ${condition.count}${getEnemyName(condition.target)} (当前: ${killCount})`
}
}
break
case 'item':
// 物品条件
const hasItem = playerStore.inventory?.some(i => i.id === condition.item)
if (!hasItem) {
const itemName = getItemName(condition.item)
return { unlocked: false, reason: `需要 ${itemName}` }
}
break
case 'level':
// 等级条件
const playerLevel = playerStore.level?.current || 1
if (playerLevel < condition.level) {
return { unlocked: false, reason: `需要等级 ${condition.level}` }
}
break
case 'skill':
// 技能条件
const skill = playerStore.skills[condition.skillId]
if (!skill || skill.level < condition.level) {
return { unlocked: false, reason: `需要技能达到指定等级` }
}
break
case 'flag':
// 标志条件
if (!playerStore.flags?.[condition.flag]) {
return { unlocked: false, reason: '需要完成特定事件' }
}
break
}
return { unlocked: true, reason: '' }
}
/**
* 获取敌人名称(辅助函数)
*/
function getEnemyName(enemyId) {
// 简化实现,实际应该从 ENEMY_CONFIG 获取
const names = {
wild_dog: '野狗',
test_boss: '测试Boss'
}
return names[enemyId] || enemyId
}
/**
* 获取物品名称(辅助函数)
*/
function getItemName(itemId) {
// 简化实现,实际应该从 ITEM_CONFIG 获取
const names = {
basement_key: '地下室钥匙',
old_book: '破旧书籍'
}
return names[itemId] || itemId
}
/**
* 检查位置是否安全
* @param {String} locationId - 位置ID
* @returns {Boolean}
*/
export function isSafeLocation(locationId) {
const location = LOCATION_CONFIG[locationId]
return location?.type === 'safe'
}
/**
* 检查位置是否为战斗区域
* @param {String} locationId - 位置ID
* @returns {Boolean}
*/
export function isCombatLocation(locationId) {
const location = LOCATION_CONFIG[locationId]
return location?.type === 'danger' || location?.type === 'dungeon'
}
/**
* 获取位置可用的活动
* @param {String} locationId - 位置ID
* @returns {Array} 活动列表
*/
export function getLocationActivities(locationId) {
const location = LOCATION_CONFIG[locationId]
return location?.activities || []
}
/**
* 获取位置连接
* @param {String} locationId - 位置ID
* @returns {Array} 连接的位置ID列表
*/
export function getLocationConnections(locationId) {
const location = LOCATION_CONFIG[locationId]
return location?.connections || []
}
/**
* 检查位置是否可到达
* @param {String} fromId - 起始位置ID
* @param {String} toId - 目标位置ID
* @param {Object} playerStore - 玩家Store
* @returns {Object} { reachable: boolean, reason: string }
*/
export function canTravelTo(fromId, toId, playerStore) {
const fromLocation = LOCATION_CONFIG[fromId]
if (!fromLocation) {
return { reachable: false, reason: '当前位置不存在' }
}
// 检查是否直接连接
if (!fromLocation.connections?.includes(toId)) {
return { reachable: false, reason: '无法直接到达' }
}
// 检查目标位置解锁条件
return checkLocationUnlock(toId, playerStore)
}
/**
* 计算位置转移时间
* @param {String} fromId - 起始位置ID
* @param {String} toId - 目标位置ID
* @returns {Number} 转移时间(秒)
*/
export function getTravelTime(fromId, toId) {
// 简化实现所有位置间转移时间为5秒
return 5
}
/**
* 获取环境描述
* @param {String} locationId - 位置ID
* @param {Object} playerStore - 玩家Store
* @returns {String} 环境描述
*/
export function getEnvironmentDescription(locationId, playerStore) {
const location = LOCATION_CONFIG[locationId]
if (!location) return ''
const penalty = getEnvironmentPenalty(locationId, playerStore)
let description = location.description || ''
// 添加环境效果描述
if (location.environment === ENVIRONMENT_TYPES.DARK) {
const hasNightVision = playerStore.skills.night_vision?.level > 0
if (!hasNightVision) {
description += ' [黑暗: AP/EP-20% 阅读效率-50%]'
} else {
const reduce = playerStore.globalBonus?.darkPenaltyReduce || 0
description += ` [黑暗: 惩罚减少${reduce}%]`
}
}
return description
}
/**
* 获取位置的可敌人列表
* @param {String} locationId - 位置ID
* @returns {Array} 敌人ID列表
*/
export function getLocationEnemies(locationId) {
const location = LOCATION_CONFIG[locationId]
return location?.enemies || []
}
/**
* 获取位置的NPC列表
* @param {String} locationId - 位置ID
* @returns {Array} NPC ID列表
*/
export function getLocationNPCs(locationId) {
const location = LOCATION_CONFIG[locationId]
return location?.npcs || []
}
/**
* 检查是否有负面状态来自环境
* @param {String} locationId - 位置ID
* @param {Object} playerStore - 玩家Store
* @returns {Array} 负面状态列表
*/
export function getEnvironmentNegativeStatus(locationId, playerStore) {
const location = LOCATION_CONFIG[locationId]
if (!location) return []
const status = []
// 黑暗环境可能造成精神压力
if (location.environment === ENVIRONMENT_TYPES.DARK) {
const hasNightVision = playerStore.skills.night_vision?.level || 0
if (hasNightVision < 5) {
status.push({
type: 'darkness',
name: '黑暗恐惧',
effect: '精神逐渐下降',
severity: hasNightVision > 0 ? 'mild' : 'moderate'
})
}
}
return status
}
/**
* 处理环境负面状态效果
* @param {Object} playerStore - 玩家Store
* @param {String} locationId - 位置ID
* @param {Number} deltaTime - 时间增量(秒)
*/
export function processEnvironmentEffects(playerStore, locationId, deltaTime = 1) {
const location = LOCATION_CONFIG[locationId]
if (!location) return
// 黑暗环境的精神压力
if (location.environment === ENVIRONMENT_TYPES.DARK) {
const nightVisionLevel = playerStore.skills.night_vision?.level || 0
// 夜视5级以下会缓慢消耗精神
if (nightVisionLevel < 5) {
const sanityDrain = 0.1 * (1 - nightVisionLevel * 0.1) * deltaTime
playerStore.currentStats.sanity = Math.max(
0,
playerStore.currentStats.sanity - sanityDrain
)
}
}
// 恶劣环境的持续影响
if (location.environment === ENVIRONMENT_TYPES.HARSH) {
const survivalistLevel = playerStore.skills.survivalist?.level || 0
// 生存技能可以减少恶劣环境影响
if (survivalistLevel < 5) {
const staminaDrain = 0.2 * (1 - survivalistLevel * 0.1) * deltaTime
playerStore.currentStats.stamina = Math.max(
0,
playerStore.currentStats.stamina - staminaDrain
)
}
}
}