№ · BUFF 系统 CH.06.25
主页 · ⚡ Buff 系统 · 《同福客栈》Buff系统技术实现文档

《同福客栈》Buff系统技术实现文档

📅 2026-06-25 · 📦 15.1 KB · ⚡ Buff 系统 · 📝 MD 文档 🏷️ 员工🏷️ 时间🏷️ 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

  1. catalog.ts 中添加新的 BuffDefinition
  2. 配置 idnametargetTypecategory 等基础属性
  3. 设置 tags 用于状态机判定
  4. 配置 durationstack 规则
  5. 添加 modifiers 定义数值效果
  6. 设置 visible 提供UI显示信息

9.2 添加新的触发条件

  1. buffEngine.tsgenerateBuffCandidates 函数中添加判定逻辑
  2. 使用 shouldStayBelowThresholdshouldStayAboveThreshold 实现带回差的判定
  3. 返回 BuffCandidate 对象

9.3 添加新的修饰符类型

  1. types.tsBuffModifierKey 类型中添加新键
  2. buffEngine.tssummarizeBuffs 函数中处理新修饰符
  3. 在游戏逻辑中使用 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_hourstaff_mood_low
  • 标签使用驼峰命名加#前缀:#PeakHour#SanitationCrisis
  • 修饰符键使用驼峰命名:incomeMultipliercustomerFlowMultiplier

12.2 配置原则

  1. 单一职责 - 每个Buff只负责一个明确的数值效果
  2. 标签驱动 - 使用标签而非硬编码条件进行状态判定
  3. 防抖设计 - 临界点设置回差区间,避免频繁状态切换
  4. 冲突明确 - 使用冲突组管理互斥关系

12.3 扩展建议

  1. 优先使用条件触发类Buff,减少时长类Buff
  2. 使用 reserved 标志预留未来扩展的Buff
  3. 使用 metadata 存储动态数据,保持定义的稳定性
  4. 定期清理过期的Buff实例,避免内存泄漏