《同福客栈》Buff系统技术实现文档
1. 系统架构概述
Buff系统是《同福客栈》的核心数值框架,通过标签驱动的状态机实现经营状态的动态切换。
1.1 核心组件
src/data/buffs/
├── catalog.ts # Buff定义目录(15个核心Buff)
├── types.ts # TypeScript类型定义
├── configs.ts # Buff配置(4个示例配置)
├── buff-config-template.ts # Buff配置模板(46个角色Buff + 8个客栈Buff)
└── index.ts # 模块导出
src/utils/
├── buffEngine.ts # Buff引擎核心实现(944行)
└── innStatusBuffBridge.ts # 客栈状态与Buff桥接(509行)
src/components/
└── BuffPanel.tsx # Buff面板UI组件(194行)
1.2 数据流
游戏状态快照 (BuffSnapshot)
↓
Buff候选生成 (generateBuffCandidates)
↓
Buff调和 (reconcileBuffCandidates)
↓
运行时Buff列表 (RuntimeBuff[])
↓
数值拦截计算 (summarizeBuffs)
↓
UI展示 (BuffPanel)
2. 核心数据结构
2.1 Buff定义 (BuffDefinition)
interface BuffDefinition {
id: string // 唯一标识符,如 'inn_peak_hour'
name: string // 显示名称
targetType: BuffTargetType // 作用目标:inn/staff/zone/meta
category: BuffCategory // 分类:business/mood/health/risk/official/night/social/meta
source: BuffSourceDefinition // 来源定义
tags: string[] // 标签列表,用于状态机判定
duration: BuffDurationSpec // 持续时间规格
stack: BuffStackRule // 堆叠规则
conflict?: BuffConflictRule // 冲突规则
timeWindow?: BuffTimeWindow // 时间窗口限制
modifiers: BuffModifierDefinition[] // 修饰符列表
visible: BuffVisibility // UI显示信息
notes?: string // 备注
reserved?: boolean // 是否为预留Buff
}
2.2 运行时Buff (RuntimeBuff)
interface RuntimeBuff {
instanceId: string // 实例ID,格式:definitionId::targetId::sourceId
definitionId: string // 关联的定义ID
targetType: BuffTargetType // 作用目标类型
targetId: string // 作用目标ID
sourceType: BuffSourceType // 来源类型
sourceId: string // 来源ID
stacks: number // 当前堆叠数
appliedAtSlot: number // 应用时的游戏时间槽
lastUpdatedAtSlot: number // 最后更新时间槽
expiresAtSlot?: number // 过期时间槽(仅时长类)
remainingSlots?: number // 剩余时间槽(仅时长类)
suspended: boolean // 是否被挂起
suspendedBy?: string // 被哪个Buff挂起
tags: string[] // 标签列表
metadata: BuffMetadata // 元数据
}
2.3 游戏状态快照 (BuffSnapshot)
interface BuffSnapshot {
gameTime: GameTimeState // 游戏时间状态
businessPhase?: BusinessPhase // 营业阶段
coins?: number // 金币数量
dailyExpense?: number // 每日支出
balanceExpenseRatio?: number // 余额支出比
reputation: number // 声望值
cleanliness: number // 清洁度
safety: number // 安防值
foodSupplyDays?: number // 食材供应天数
negativeRumor?: number // 负面传闻值
healthyStreakDays?: number // 健康连胜天数
staffMoodAverage?: number // 员工平均心情
staffHealthAverage?: number // 员工平均健康
staff?: StaffBuffSnapshot[] // 员工快照
factionFavor?: Partial<Record<BuffFactionKey, number>> // 势力好感
coopSupportLevel?: number // 联机支援等级
newGamePlusLevel?: number // 周目等级
flags?: BuffStateFlags // 状态标志
}
3. 核心算法
3.1 Buff候选生成算法
export const generateBuffCandidates = (
snapshot: BuffSnapshot,
currentBuffs: RuntimeBuff[] = []
): BuffCandidate[] => {
const normalizedSnapshot = normalizeBuffSnapshot(snapshot)
const candidates: BuffCandidate[] = []
// 1. 半营业状态判定
const halfBusinessShouldStay =
normalizedSnapshot.cleanliness < 30 ||
normalizedSnapshot.staffMoodAverage < 30 ||
normalizedSnapshot.foodSupplyDays < 1
candidates.push({
definitionId: 'inn_half_business',
action: halfBusinessShouldStay ? 'activate' : 'deactivate',
reason: halfBusinessShouldStay
? '清洁 / 供给 / 心情存在至少一项经营短板。'
: '核心短板已回到安全区间,可恢复完整营业。',
})
// 2. 卫生危机判定(带回差)
const sanitationShouldStay = shouldStayBelowThreshold(
normalizedSnapshot.cleanliness,
hasSanitationCrisis,
30, // 激活阈值
40 // 保留阈值
)
// 3. 高峰营业判定
const peakHourShouldStay = (
isOpenLike &&
(timeOfDay === 'day' || timeOfDay === 'dusk') &&
normalizedSnapshot.reputation >= 60 &&
normalizedSnapshot.cleanliness >= 40 &&
normalizedSnapshot.safety >= 40 &&
!halfBusinessShouldStay
)
// ... 更多Buff判定逻辑
return candidates
}
3.2 数值拦截计算
export const summarizeBuffs = (
currentBuffs: RuntimeBuff[],
gameTime: GameTimeState
): BuffRuntimeSummary => {
const normalized = normalizeRuntimeBuffs(currentBuffs, gameTime)
const activeBuffs = normalized.filter(buff => !buff.suspended)
const activeTags = Array.from(new Set(activeBuffs.flatMap(buff => buff.tags)))
const byKey: Partial<Record<BuffModifierKey, BuffMetricBreakdown>> = {}
activeBuffs.forEach(buff => {
const definition = BUFF_DEFINITIONS_BY_ID[buff.definitionId]
if (!definition) return
definition.modifiers.forEach(modifier => {
const existing = byKey[modifier.key] ?? {
key: modifier.key,
add: 0,
multiplier: 1,
sources: [],
}
const scaledValue = modifier.op === 'mul'
? Math.pow(modifier.value, buff.stacks)
: modifier.value * buff.stacks
if (modifier.op === 'add') {
existing.add += scaledValue
} else if (modifier.op === 'mul') {
existing.multiplier *= scaledValue
} else {
existing.set = scaledValue
}
byKey[modifier.key] = existing
})
})
return {
activeBuffs,
activeTags,
byKey,
// ... 其他汇总数据
}
}
4. 核心Buff定义
4.1 客栈级Buff
| Buff ID | 名称 | 标签 | 触发条件 | 效果 |
|---------|------|------|----------|------|
| inn_peak_hour | 高朋满座 | #PeakHour #BusinessBonus | 白天/傍晚 + 口碑≥60 + 无经营短板 | 客流+50%, 营收+50%, 消耗+40% |
| inn_half_business | 半营业状态 | #HalfBusiness #OperationalCrisis | 清洁<30 或 心情<30 或 食材<1天 | 客位-50%, 菜品-50%, 营收-40% |
| inn_sanitation_crisis | 垃圾堆积 | #SanitationCrisis | 清洁<30 (回升至40解除) | 投诉率+200%, 负面事件+60% |
| inn_security_crisis | 门户大开 | #SecurityCrisis | 安防<30 (回升至40解除) | 偷窃+300%, 斗殴+200% |
| inn_hygiene_suspend | 临时卫生整顿 | #HygieneSuspend | 清洁<20 或 卫生抽检 | 强制停业, 营收归零 |
4.2 员工级Buff
| Buff ID | 名称 | 标签 | 触发条件 | 效果 |
|---------|------|------|----------|------|
| staff_mood_low | 罢工边缘 | #StaffMoodLow | 心情<30 (恢复至40解除) | 工作效率-30%, 出错率+20% |
| staff_health_low | 带病硬撑 | #StaffHealthLow | 健康<35 (恢复至50解除) | 工作效率-25%, 健康持续流失 |
4.3 势力合璧Buff
| Buff ID | 名称 | 标签 | 触发条件 | 效果 |
|---------|------|------|----------|------|
| faction_guest_station | 官商一体 | #GuestStation #FactionCombo | 衙门≥80 + 乡绅≥80 | 贵客订单+30%, 高质量客流+12% |
| faction_black_white | 黑白通吃 | #BlackWhiteAlliance #FactionCombo | 衙门≥80 + 丐帮≥80 | 偷窃/斗殴-50%, 安防衰减-50% |
4.4 特殊Buff
| Buff ID | 名称 | 标签 | 触发条件 | 效果 |
|---------|------|------|----------|------|
| night_closed_house | 正常打烊 | #ClosedAtNight | 夜间闭店时段 | 营收归零, 心情/健康恢复 |
| night_patrol_gap | 夜巡空档 | #NightRisk | 闭店 + 无巡夜 + 安防<40 | 夜间风险+80%, 安防持续流失 |
| new_game_plus_foundation | 周目传承 | #NewGamePlus #MetaProgression | 周目等级>0 | 周目奖励+8%/层, 口碑+5%/层 |
5. 冲突与堆叠规则
5.1 冲突组
| 冲突组 | 模式 | 成员 | 优先级 |
|--------|------|------|--------|
| inn_business_state | exclusive | 高朋满座, 半营业, 卫生整顿, 正常打烊 | 95 > 60 > 50 > 20 |
| inn_sanitation | override | 垃圾堆积, 卫生整顿 | 95 > 80 |
| inn_security | override | 门户大开, 黑白通吃 | 90 > 80 |
| night_security | override | 夜巡空档, 联机巡夜, 黑白通吃 | 90 > 85 > 75 |
| staff_morale | override | 罢工边缘 | 70 |
| staff_health | override | 带病硬撑 | 70 |
5.2 堆叠模式
- replace - 替换现有实例
- refresh - 刷新持续时间
- extend - 延长持续时间
- stack - 增加堆叠数
6. 时间窗口系统
6.1 时间段 (TimeOfDay)
night- 夜间 (22:00-06:00)dawn- 黎明 (06:00-10:00)day- 白天 (10:00-18:00)dusk- 傍晚 (18:00-22:00)
6.2 营业阶段 (BusinessPhase)
open- 营业中closing- 打烊中closed- 已关闭
6.3 时间窗口配置示例
// 高峰营业只在白天/傍晚且营业时生效
timeWindow: {
timeOfDay: ['day', 'dusk'],
businessPhase: ['open', 'closing'],
}
// 正常打烊只在闭店时段生效
timeWindow: {
businessPhase: ['closed'],
}
7. 桥接层实现
7.1 客栈状态评估
innStatusBuffBridge.ts 负责将客栈经营状态评估结果转换为Buff候选:
export const selectInnStatusBuffRecommendations = (
evaluation: InnStatusEvaluation,
currentBuffIds: string[] = []
): InnStatusBuffSelectionResult => {
// 根据游戏阶段选择Buff
if (stage === 'CLOSED_REST') {
addActivate('night_closed_house', '夜间闭店应保持正常打烊。')
addDeactivate('inn_peak_hour', '闭店状态不应保留高峰营业 Buff。')
}
if (stage === 'PEAK_BUSINESS') {
addActivate('inn_peak_hour', '当前适合保留高峰营业 Buff。')
}
// 根据风险标签选择Buff
if (hasRisk('security_low')) {
addActivate('inn_security_crisis', '治安压力已到预警线。')
}
// ... 更多逻辑
return { activate, deactivate, recommendedDefinitionIds, ... }
}
8. UI组件实现
8.1 BuffPanel组件
export const BuffPanel: React.FC<BuffPanelProps> = ({ buffSummary }) => {
if (!buffSummary) {
return <EmptyPanel title="未接入运行态" />
}
const { activeBuffs, visibleBuffs, activeTags } = buffSummary
return (
<div>
{/* 状态总览 */}
<div className="grid grid-cols-2 gap-2">
<div>激活项: {activeBuffs.length}</div>
<div>系统标签: {activeTags.length}</div>
</div>
{/* 系统标签池 */}
<div>
{activeTags.map(tag => (
<span key={tag}>{tag}</span>
))}
</div>
{/* 可见Buff列表 */}
<div>
{visibleBuffs.map(buff => (
<div key={buff.instanceId} className={buff.positive ? 'positive' : 'negative'}>
<h4>{buff.label}</h4>
<p>{buff.detail}</p>
{buff.remainingSlots && (
<span>剩 {buff.remainingSlots} 刻</span>
)}
</div>
))}
</div>
</div>
)
}
9. 扩展指南
9.1 添加新Buff
- 在
catalog.ts中添加新的BuffDefinition - 配置
id、name、targetType、category等基础属性 - 设置
tags用于状态机判定 - 配置
duration和stack规则 - 添加
modifiers定义数值效果 - 设置
visible提供UI显示信息
9.2 添加新的触发条件
- 在
buffEngine.ts的generateBuffCandidates函数中添加判定逻辑 - 使用
shouldStayBelowThreshold或shouldStayAboveThreshold实现带回差的判定 - 返回
BuffCandidate对象
9.3 添加新的修饰符类型
- 在
types.ts的BuffModifierKey类型中添加新键 - 在
buffEngine.ts的summarizeBuffs函数中处理新修饰符 - 在游戏逻辑中使用
calculateFinalValue计算最终值
10. 性能优化
10.1 缓存策略
BUFF_DEFINITIONS_BY_ID- 按ID索引的定义缓存BUFF_DEFINITIONS_BY_CATEGORY- 按分类索引的定义缓存BUFF_DEFINITIONS_BY_TAG- 按标签索引的定义缓存
10.2 计算优化
- 使用
normalizeRuntimeBuffs一次性规范化所有Buff - 使用
refreshConflictStates批量处理冲突状态 - 使用
summarizeBuffs一次性计算所有修饰符
10.3 内存优化
- RuntimeBuff 使用实例ID避免重复创建
- 使用
suspended标志而非删除挂起的Buff - 使用
metadata存储动态数据而非修改定义
11. 调试工具
11.1 状态检查
// 检查当前活跃的Buff
const activeBuffs = normalizeRuntimeBuffs(currentBuffs, gameTime)
.filter(buff => !buff.suspended)
// 检查当前生效的标签
const activeTags = Array.from(new Set(
activeBuffs.flatMap(buff => buff.tags)
))
// 检查某个修饰符的最终值
const summary = summarizeBuffs(currentBuffs, gameTime)
const incomeMultiplier = summary.byKey.incomeMultiplier
// incomeMultiplier = { add: 0, multiplier: 1.5, sources: [...] }
11.2 候选检查
// 检查当前应该激活哪些Buff
const candidates = generateBuffCandidates(snapshot, currentBuffs)
const result = reconcileBuffCandidates(currentBuffs, candidates, gameTime)
console.log('激活:', result.activated.map(b => b.definitionId))
console.log('停用:', result.deactivated.map(b => b.definitionId))
12. 最佳实践
12.1 命名规范
- Buff ID 使用蛇形命名:
inn_peak_hour、staff_mood_low - 标签使用驼峰命名加#前缀:
#PeakHour、#SanitationCrisis - 修饰符键使用驼峰命名:
incomeMultiplier、customerFlowMultiplier
12.2 配置原则
- 单一职责 - 每个Buff只负责一个明确的数值效果
- 标签驱动 - 使用标签而非硬编码条件进行状态判定
- 防抖设计 - 临界点设置回差区间,避免频繁状态切换
- 冲突明确 - 使用冲突组管理互斥关系
12.3 扩展建议
- 优先使用条件触发类Buff,减少时长类Buff
- 使用
reserved标志预留未来扩展的Buff - 使用
metadata存储动态数据,保持定义的稳定性 - 定期清理过期的Buff实例,避免内存泄漏