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:
Claude
2026-01-21 17:13:51 +08:00
commit cb412544e9
90 changed files with 17149 additions and 0 deletions

26
config/constants.js Normal file
View File

@@ -0,0 +1,26 @@
// 游戏数值常量
export const GAME_CONSTANTS = {
// 时间流速现实1秒 = 游戏时间5分钟
TIME_SCALE: 5,
// 离线收益时间(小时)
OFFLINE_HOURS_BASE: 0.5,
OFFLINE_HOURS_AD: 2.5,
// 耐力相关
STAMINA_FULL_EFFECT: 50, // 耐力高于此值时无惩罚
STAMINA_EMPTY_PENALTY: 0.5, // 耐力耗尽时效率减半
// 经验倍率
EXP_PARTIAL_SUCCESS: 0.5, // 部分成功时经验比例
// 品质等级
QUALITY_LEVELS: {
1: { name: '垃圾', color: '#808080', range: [0, 49], multiplier: 1.0 },
2: { name: '普通', color: '#ffffff', range: [50, 99], multiplier: 1.0 },
3: { name: '优秀', color: '#4ade80', range: [100, 129], multiplier: 1.1 },
4: { name: '稀有', color: '#60a5fa', range: [130, 159], multiplier: 1.3 },
5: { name: '史诗', color: '#a855f7', range: [160, 199], multiplier: 1.6 },
6: { name: '传说', color: '#f97316', range: [200, 250], multiplier: 2.0 }
}
}

45
config/enemies.js Normal file
View File

@@ -0,0 +1,45 @@
// 敌人配置
export const ENEMY_CONFIG = {
wild_dog: {
id: 'wild_dog',
name: '野狗',
level: 1,
baseStats: {
health: 30,
attack: 8,
defense: 2,
speed: 1.0
},
derivedStats: {
ap: 10, // 攻击点数
ep: 8 // 闪避点数
},
expReward: 15,
skillExpReward: 10,
drops: [
{ itemId: 'dog_skin', chance: 0.8, count: { min: 1, max: 1 } }
]
},
test_boss: {
id: 'test_boss',
name: '测试Boss',
level: 5,
baseStats: {
health: 200,
attack: 25,
defense: 10,
speed: 0.8
},
derivedStats: {
ap: 25,
ep: 15
},
expReward: 150,
skillExpReward: 50,
drops: [
{ itemId: 'basement_key', chance: 1.0, count: { min: 1, max: 1 } }
],
isBoss: true
}
}

329
config/events.js Normal file
View File

@@ -0,0 +1,329 @@
/**
* 事件配置
* Phase 6 核心系统实现
*/
/**
* 事件配置
* 每个事件包含:
* - id: 事件唯一标识
* - type: 事件类型 (story/tips/unlock/dialogue)
* - title: 事件标题
* - text: 事件文本内容
* - textTemplate: 可选的文本模板(支持变量替换)
* - npcId: 关联的NPC ID
* - choices: 选项列表
* - actions: 事件触发的动作
* - triggers: 触发条件
*/
export const EVENT_CONFIG = {
// 初始剧情事件
intro: {
id: 'intro',
type: 'story',
title: '苏醒',
text: '你在一个废弃的实验室中醒来,头痛欲裂。记忆模糊不清,周围一片狼藉。\n\n桌上放着一根粗糙的木棍和一本破旧的日记。门外隐约传来野兽的嚎叫声...\n\n你必须活下去。',
choices: [
{ text: '拿起木棍', next: null, action: 'give_stick' },
{ text: '先看看日记', next: 'intro_diary' }
]
},
intro_diary: {
id: 'intro_diary',
type: 'story',
title: '破旧的日记',
text: '日记的纸张已经泛黄,上面的字迹有些模糊。你翻开了第一页:\n\n"第X天\n世界已经变了...那些东西...它们无处不在。\n\n如果你看到这个日记记住\n1. 找武器,任何武器都比空手强\n2. 避开黑暗的地方,除非你有办法\n3. 商人老张在市场,他可能会帮你\n\n活下去..."',
choices: [
{ text: '收好日记,拿起木棍', next: null, action: 'give_stick' }
]
},
// 首次战斗提示
first_combat: {
id: 'first_combat',
type: 'tips',
title: '战斗提示',
textTemplate: '你来到了{locationName}。这里是危险区域,随时可能遭遇敌人!\n\n战斗提示\n- 攻击姿态:高伤害,低防御\n- 防御姿态:低伤害,高防御\n- 平衡姿态:攻守平衡\n\n注意耐力耐力耗尽会大幅降低战斗力',
choices: [
{ text: '明白了', next: null }
]
},
// 黑暗环境警告
dark_warning: {
id: 'dark_warning',
type: 'tips',
title: '黑暗警告',
textTemplate: '你进入了{locationName}。这里一片漆黑,几乎看不清任何东西。\n\n黑暗效果\n- AP攻击点数-20%\n- EP闪避点数-20%\n- 阅读效率-50%\n\n提示在这里待着会逐渐获得"夜视"技能经验,技能等级提高后可以减少惩罚。',
choices: [
{ text: '小心前行', next: null }
]
},
// Boss解锁提示
boss_unlock: {
id: 'boss_unlock',
type: 'unlock',
title: '新的挑战',
text: '你击败了足够多的野狗,对这片区域有了更深的了解。\n\n在探索中你发现了一个通往更深处的入口...那里似乎盘踞着什么强大的东西。\n\n[解锁区域测试Boss巢]',
choices: [
{ text: '做好准备', next: null }
]
},
// Boss击败剧情
boss_defeat: {
id: 'boss_defeat',
type: 'story',
title: '胜利',
text: '经过一番激战,你终于击败了那只强大的野兽!\n\n在它的巢穴深处你发现了一把生锈的钥匙上面刻着「B」字母。\n\n这应该能打开某个地方...',
choices: [
{ text: '收起钥匙', next: null, action: 'give_item', actionData: { itemId: 'basement_key', count: 1 } }
]
},
// 夜视技能解锁
night_vision_unlock: {
id: 'night_vision_unlock',
type: 'tips',
title: '新技能',
text: '在黑暗中待了一段时间后,你的眼睛逐渐适应了...\n\n[解锁被动技能:夜视]\n\n效果\n- Lv.5: 黑暗惩罚-10%\n- Lv.10: 黑暗惩罚-25%\n\n在黑暗环境中待着会自动获得经验。',
choices: [
{ text: '继续', next: null }
]
},
// 阅读完成奖励
book_complete: {
id: 'book_complete',
type: 'reward',
title: '阅读完成',
textTemplate: '你读完了《{bookName}》,对里面的内容有了更深的理解。\n\n获得阅读经验+{exp}',
choices: [
{ text: '收起书籍', next: null }
]
},
// 商人老张首次对话
merchant_first: {
id: 'merchant_first',
type: 'dialogue',
title: '商人老张',
text: '哟,新人?看你的样子,刚醒来不久吧。\n\n这里的东西你可以看看不过生意就是生意我也要吃饭。\n\n商人售价200%基础价格\n商人收购价30%基础价格\n\n觉得贵那是因为外面太危险了',
choices: [
{ text: '查看商品', next: null, action: 'open_shop' },
{ text: '为什么这么贵?', next: 'merchant_explain' },
{ text: '离开', next: null }
]
},
merchant_explain: {
id: 'merchant_explain',
type: 'dialogue',
title: '商人老张',
text: '嘿,你想想看,外面那些野狗会咬人,外面那些地方暗得要命。\n\n我能活着把这些东西运回来就已经很不容易了。\n\n不过...你多来几次,我也可以给你点优惠。要学会讲价!',
choices: [
{ text: '查看商品', next: null, action: 'open_shop' },
{ text: '离开', next: null }
]
},
// 神秘人对话
mysterious_first: {
id: 'mysterious_first',
type: 'dialogue',
title: '神秘人',
text: '......\n\n你还没死呢。\n\n看在你还活着的份上给你一个建议\n\n有些东西不是靠蛮力能得到的。\n\n有些技能要用心去感受。',
choices: [
{ text: '什么意思?', next: 'mysterious_hint' },
{ text: '离开', next: null }
]
},
mysterious_hint: {
id: 'mysterious_hint',
type: 'dialogue',
title: '神秘人',
text: '有些技能,不需要你主动去做。\n\n在黑暗中待着眼睛会适应。\n\n在狭窄的地方战斗身体会学会闪避。\n\n在恶劣的环境中生存意志会变得坚韧。\n\n......这就是"适应"。',
choices: [
{ text: '我明白了', next: null },
{ text: '再详细说说', next: 'mysterious_detail' }
]
},
mysterious_detail: {
id: 'mysterious_detail',
type: 'dialogue',
title: '神秘人',
text: '被动技能,懂吗?\n\n夜视 - 黑暗环境中自动获得经验\n狭窄空间战斗 - 狭窄环境中自动获得经验\n生存专家 - 恶劣环境中自动获得经验\n\n这些技能不需要你训练只需要你在相应的环境中待着。\n\n不过...前提是你得先去过那些地方。',
choices: [
{ text: '受教了', next: null }
]
},
// 技能里程碑奖励提示
skill_milestone: {
id: 'skill_milestone',
type: 'reward',
title: '技能突破',
textTemplate: '你的{skillName}达到了{level}级!\n\n获得里程碑奖励\n{bonus}',
choices: [
{ text: '太好了', next: null }
]
},
// 等级提升提示
level_up: {
id: 'level_up',
type: 'reward',
title: '等级提升',
textTemplate: '你的等级提升到了 {level}\n\n各项属性得到提升。\n\n现在可以学习更高等级的技能了。',
choices: [
{ text: '继续', next: null }
]
},
// 死亡提示
defeat: {
id: 'defeat',
type: 'story',
title: '失败',
text: '你的视线逐渐模糊,意识消散...\n\n......\n\n等你再次醒来时发现自己回到了营地。\n\n那家伙救了你一命。\n\n"小心点,下次可能就没这么好运了。"',
choices: [
{ text: '...', next: null, action: 'heal', actionData: { amount: 50 } }
]
},
// 物品获得提示
item_get: {
id: 'item_get',
type: 'reward',
title: '获得物品',
textTemplate: '获得了:{itemName} x{count}',
choices: [
{ text: '收下', next: null }
]
},
// 技能解锁提示
skill_unlock: {
id: 'skill_unlock',
type: 'tips',
title: '新技能',
textTemplate: '解锁了新技能:{skillName}\n\n{description}',
choices: [
{ text: '了解了', next: null }
]
},
// 黑市开放提示
blackmarket_open: {
id: 'blackmarket_open',
type: 'tips',
title: '黑市开放',
text: '现在是黑市开放时间!\n\n黑市商人带来了稀有的商品品质可能比普通市场更高。\n\n回收价格也比普通市场更优惠50% vs 30%)。\n\n开放时间周三、周六 20:00-24:00',
choices: [
{ text: '去看看', next: null, action: 'teleport', actionData: { locationId: 'blackmarket' } },
{ text: '稍后再说', next: null }
]
},
// 区域解锁提示
area_unlock: {
id: 'area_unlock',
type: 'unlock',
title: '新区域',
textTemplate: '解锁了新区域:{areaName}\n\n{description}',
choices: [
{ text: '前往探索', next: null, action: 'teleport', actionData: { locationId: '{areaId}' } },
{ text: '稍后再说', next: null }
]
}
}
/**
* 事件触发条件配置
* 用于定时事件或条件触发的事件
*/
export const EVENT_TRIGGERS = {
// 每日事件
daily: {
blackmarket_reminder: {
condition: (gameTime) => {
// 周三或周六的20:00
const dayOfWeek = (gameTime.day - 1) % 7
const isMarketDay = dayOfWeek === 2 || dayOfWeek === 5 // 0=周一, 2=周三, 5=周六
const isMarketTime = gameTime.hour === 20 && gameTime.minute === 0
return isMarketDay && isMarketTime
},
eventId: 'blackmarket_open',
oncePerDay: true
}
},
// 一次性事件
once: {
night_vision_first: {
condition: (playerStore) => {
const skill = playerStore.skills.night_vision
return skill && skill.unlocked && skill.level === 1 && !playerStore.flags.nightVisionHintShown
},
eventId: 'night_vision_unlock',
setFlag: 'nightVisionHintShown'
},
first_book_complete: {
condition: (playerStore) => {
return playerStore.readBooks && playerStore.readBooks.length > 0 && !playerStore.flags.firstBookHintShown
},
eventId: 'book_complete',
setFlag: 'firstBookHintShown'
}
},
// 环境触发事件
environment: {
basement_first_enter: {
condition: (playerStore, locationId) => {
return locationId === 'basement' && !playerStore.flags.basementFirstEnter
},
eventId: 'dark_warning'
},
narrow_first_enter: {
condition: (playerStore, locationId) => {
const location = LOCATION_CONFIG[locationId]
return location?.environment === 'narrow' && !playerStore.flags[`narrow_${locationId}_enter`]
},
eventId: null, // 只记录,不显示事件
setFlag: (locationId) => `narrow_${locationId}_enter`
}
}
}
/**
* 事件选择默认动作
* 用于处理没有指定next的选项
*/
export const DEFAULT_EVENT_ACTIONS = {
close: true, // 关闭事件
log: true // 记录日志
}
/**
* 优先级事件配置
* 这些事件会优先显示,不会被其他事件打断
*/
export const PRIORITY_EVENTS = [
'intro',
'defeat',
'boss_defeat'
]
/**
* 可跳过的事件类型
* 这些类型的事件可以被玩家跳过
*/
export const SKIPPABLE_EVENT_TYPES = [
'tips',
'reward'
]

87
config/items.js Normal file
View File

@@ -0,0 +1,87 @@
// 物品配置
export const ITEM_CONFIG = {
// 武器
wooden_stick: {
id: 'wooden_stick',
name: '木棍',
type: 'weapon',
subtype: 'one_handed',
icon: '🪵',
baseValue: 10, // 基础价值(铜币)
baseDamage: 5,
attackSpeed: 1.0,
quality: 100, // 默认品质
unlockSkill: 'stick_mastery',
description: '一根粗糙的木棍,至少比空手强。'
},
// 消耗品
bread: {
id: 'bread',
name: '面包',
type: 'consumable',
subtype: 'food',
icon: '🍞',
baseValue: 10,
effect: {
stamina: 20
},
description: '普通的面包,可以恢复耐力。',
stackable: true,
maxStack: 99
},
healing_herb: {
id: 'healing_herb',
name: '草药',
type: 'consumable',
subtype: 'medicine',
icon: '🌿',
baseValue: 15,
effect: {
health: 15
},
description: '常见的治疗草药,可以恢复生命值。',
stackable: true,
maxStack: 99
},
// 书籍
old_book: {
id: 'old_book',
name: '破旧书籍',
type: 'book',
icon: '📖',
baseValue: 50,
readingTime: 60, // 秒
expReward: {
reading: 10
},
completionBonus: null,
description: '一本破旧的书籍,记录着一些基础知识。',
consumable: false // 书籍不消耗
},
// 素材
dog_skin: {
id: 'dog_skin',
name: '狗皮',
type: 'material',
icon: '🐕',
baseValue: 5,
description: '野狗的皮毛,可以用来制作简单装备。',
stackable: true,
maxStack: 99
},
// 关键道具
basement_key: {
id: 'basement_key',
name: '地下室钥匙',
type: 'key',
icon: '🔑',
baseValue: 0,
description: '一把生锈的钥匙上面刻着「B」字母。',
stackable: false
}
}

69
config/locations.js Normal file
View File

@@ -0,0 +1,69 @@
// 区域配置
export const LOCATION_CONFIG = {
camp: {
id: 'camp',
name: '测试营地',
type: 'safe',
environment: 'normal',
description: '一个临时的幸存者营地,相对安全。',
connections: ['market', 'blackmarket', 'wild1'],
npcs: ['injured_adventurer'],
activities: ['rest', 'talk', 'trade']
},
market: {
id: 'market',
name: '测试市场',
type: 'safe',
environment: 'normal',
description: '商人老张在这里摆摊。',
connections: ['camp'],
npcs: ['merchant_zhang'],
activities: ['trade']
},
blackmarket: {
id: 'blackmarket',
name: '测试黑市',
type: 'safe',
environment: 'normal',
description: '神秘人偶尔会在这里出现。',
connections: ['camp'],
npcs: ['mysterious_man'],
activities: ['trade']
},
wild1: {
id: 'wild1',
name: '测试野外1',
type: 'danger',
environment: 'normal',
description: '野狗经常出没的区域。',
connections: ['camp', 'boss_lair'],
enemies: ['wild_dog'],
activities: ['explore', 'combat']
},
boss_lair: {
id: 'boss_lair',
name: '测试Boss巢',
type: 'danger',
environment: 'normal',
description: '强大的野兽盘踞在这里。',
connections: ['wild1', 'basement'],
enemies: ['test_boss'],
unlockCondition: { type: 'kill', target: 'wild_dog', count: 5 },
activities: ['combat']
},
basement: {
id: 'basement',
name: '地下室',
type: 'dungeon',
environment: 'dark',
description: '黑暗潮湿的地下室,需要照明。',
connections: ['boss_lair'],
unlockCondition: { type: 'item', item: 'basement_key' },
activities: ['explore', 'read']
}
}

44
config/npcs.js Normal file
View File

@@ -0,0 +1,44 @@
// NPC配置
export const NPC_CONFIG = {
injured_adventurer: {
id: 'injured_adventurer',
name: '受伤的冒险者',
location: 'camp',
dialogue: {
first: {
text: '你终于醒了...这里是营地,暂时安全。外面的世界很危险,带上这根木棍防身吧。',
choices: [
{ text: '谢谢', next: 'thanks' },
{ text: '这是什么地方?', next: 'explain' }
]
},
thanks: {
text: '小心野狗,它们通常成群出现。如果你能击败五只野狗,或许能找到通往深处的路。',
choices: [
{ text: '明白了', next: null, action: 'give_stick' }
]
},
explain: {
text: '这是末世后的世界...具体细节我也记不清了。总之,活下去是第一要务。',
choices: [
{ text: '我会的', next: 'thanks', action: 'give_stick' }
]
}
}
},
merchant_zhang: {
id: 'merchant_zhang',
name: '商人老张',
location: 'market',
dialogue: {
first: {
text: '欢迎光临!看看有什么需要的?',
choices: [
{ text: '查看商品', next: null, action: 'open_shop' },
{ text: '离开', next: null }
]
}
}
}
}

177
config/shop.js Normal file
View File

@@ -0,0 +1,177 @@
/**
* 商店配置
* 定义不同位置商店的出售物品
*/
import { ITEM_CONFIG } from './items.js'
// 商店配置
export const SHOP_CONFIG = {
// 营地商店 - 基础物品
camp: {
name: '营地杂货铺',
owner: '老杰克',
icon: '🏪',
// 出售的物品列表
items: [
{
itemId: 'bread',
stock: 99, // 库存,-1表示无限
baseStock: 99
},
{
itemId: 'healing_herb',
stock: 20,
baseStock: 20
},
{
itemId: 'old_book',
stock: 5,
baseStock: 5
}
],
// 收购的物品类型
buyItems: ['material', 'consumable'],
// 收购价格倍率
buyRate: 0.3
},
// 测试市场 - 商人老张
market: {
name: '商人老张的摊位',
owner: '商人老张',
icon: '🛒',
items: [
{
itemId: 'bread',
stock: -1,
baseStock: -1
},
{
itemId: 'healing_herb',
stock: 50,
baseStock: 50
},
{
itemId: 'old_book',
stock: 10,
baseStock: 10
}
],
buyItems: ['material', 'consumable', 'weapon', 'armor'],
buyRate: 0.4
},
// 测试黑市 - 神秘人
blackmarket: {
name: '神秘人的黑市',
owner: '神秘人',
icon: '🌑',
items: [
{
itemId: 'healing_herb',
stock: 10,
baseStock: 10
}
],
buyItems: ['material', 'weapon'],
buyRate: 0.2
}
}
/**
* 获取指定位置的商店配置
* @param {string} locationId - 位置ID
* @returns {Object|null} 商店配置
*/
export function getShopConfig(locationId) {
return SHOP_CONFIG[locationId] || null
}
/**
* 获取商店的可售物品列表
* @param {string} locationId - 位置ID
* @returns {Array} 可售物品列表
*/
export function getShopItems(locationId) {
const shop = getShopConfig(locationId)
if (!shop) return []
return shop.items
.filter(item => item.stock === -1 || item.stock > 0)
.map(shopItem => {
const itemConfig = ITEM_CONFIG[shopItem.itemId]
if (!itemConfig) return null
return {
...itemConfig,
stock: shopItem.stock,
baseStock: shopItem.baseStock,
shopItemId: shopItem.itemId
}
})
.filter(Boolean)
}
/**
* 检查物品是否可以被商店收购
* @param {string} locationId - 位置ID
* @param {Object} item - 物品对象
* @returns {boolean} 是否可以收购
*/
export function canShopBuyItem(locationId, item) {
const shop = getShopConfig(locationId)
if (!shop || !shop.buyItems) return false
// 关键道具不能出售
if (item.type === 'key') return false
return shop.buyItems.includes(item.type)
}
/**
* 计算物品的购买价格(玩家买)
* @param {Object} gameStore - 游戏Store
* @param {string} itemId - 物品ID
* @returns {number} 购买价格(铜币)
*/
export function getBuyPrice(gameStore, itemId) {
const item = ITEM_CONFIG[itemId]
if (!item || !item.baseValue) return 0
const marketData = gameStore.marketPrices?.prices?.[itemId]
const basePrice = item.baseValue
if (marketData) {
return Math.floor(basePrice * marketData.buyRate)
}
return Math.floor(basePrice * 2)
}
/**
* 计算物品的出售价格(玩家卖)
* @param {Object} gameStore - 游戏Store
* @param {string} itemId - 物品ID
* @param {number} quality - 物品品质
* @returns {number} 出售价格(铜币)
*/
export function getSellPrice(gameStore, itemId, quality = 100) {
const item = ITEM_CONFIG[itemId]
if (!item || !item.baseValue) return 0
const marketData = gameStore.marketPrices?.prices?.[itemId]
const basePrice = item.baseValue
// 品质影响价格
const qualityMultiplier = quality / 100
let price = Math.floor(basePrice * qualityMultiplier)
if (marketData) {
price = Math.floor(price * marketData.sellRate)
} else {
price = Math.floor(price * 0.3)
}
return Math.max(1, price)
}

53
config/skills.js Normal file
View File

@@ -0,0 +1,53 @@
// 技能配置
export const SKILL_CONFIG = {
// 战斗技能
stick_mastery: {
id: 'stick_mastery',
name: '木棍精通',
type: 'combat',
category: 'weapon',
icon: '',
maxLevel: 20,
expPerLevel: (level) => level * 100,
milestones: {
5: { desc: '所有武器暴击率+2%', effect: { critRate: 2 } },
10: { desc: '所有武器攻击力+5%', effect: { attackBonus: 5 } },
15: { desc: '武器熟练度获取速度+20%', effect: { expRate: 1.2 } },
20: { desc: '所有武器暴击伤害+0.3', effect: { critMult: 0.3 } }
},
unlockCondition: null, // 初始解锁
unlockItem: 'wooden_stick'
},
reading: {
id: 'reading',
name: '阅读',
type: 'life',
category: 'reading',
icon: '',
maxLevel: 20,
expPerLevel: (level) => level * 50,
milestones: {
3: { desc: '所有技能经验获取+5%', effect: { globalExpRate: 5 } },
5: { desc: '阅读速度+50%', effect: { readingSpeed: 1.5 } },
10: { desc: '完成书籍给予额外主经验+100', effect: { bookExpBonus: 100 } }
},
unlockCondition: null,
unlockItem: 'old_book'
},
night_vision: {
id: 'night_vision',
name: '夜视',
type: 'passive',
category: 'environment',
icon: '',
maxLevel: 10,
expPerLevel: (level) => level * 30,
milestones: {
5: { desc: '黑暗惩罚-10%', effect: { darkPenaltyReduce: 10 } },
10: { desc: '黑暗惩罚-25%', effect: { darkPenaltyReduce: 25 } }
},
unlockCondition: { location: 'basement' }
}
}