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>
This commit is contained in:
482
utils/environmentSystem.js
Normal file
482
utils/environmentSystem.js
Normal file
@@ -0,0 +1,482 @@
|
||||
/**
|
||||
* 环境系统 - 环境惩罚计算、被动技能获取
|
||||
* 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user