№ · 玩法设计 CH.06.25
主页 · 🎲 玩法设计 · 《同福客栈》Buff系统技术实现文档

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

📅 2026-06-25 · 📦 15.1 KB · 🎲 玩法设计 · 📝 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. 配置 id、name、targetType、category 等基础属性
  3. 设置 tags 用于状态机判定
  4. 配置 duration 和 stack 规则
  5. 添加 modifiers 定义数值效果
  6. 设置 visible 提供UI显示信息

9.2 添加新的触发条件

  1. 在 buffEngine.ts 的 generateBuffCandidates 函数中添加判定逻辑
  2. 使用 shouldStayBelowThreshold 或 shouldStayAboveThreshold 实现带回差的判定
  3. 返回 BuffCandidate 对象

9.3 添加新的修饰符类型

  1. 在 types.ts 的 BuffModifierKey 类型中添加新键
  2. 在 buffEngine.ts 的 summarizeBuffs 函数中处理新修饰符
  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_hour、staff_mood_low
  • 标签使用驼峰命名加#前缀:#PeakHour、#SanitationCrisis
  • 修饰符键使用驼峰命名:incomeMultiplier、customerFlowMultiplier

12.2 配置原则

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

12.3 扩展建议

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