№ · 技术设计 CH.06.25
主页 · 🔧 技术设计 · 《同福客栈》账号天赋与传承系统技术文档

《同福客栈》账号天赋与传承系统技术文档

📅 2026-06-25 · 📦 27.1 KB · 🔧 技术设计 · 📝 MD 文档 🏷️ 装备🏷️ Buff🏷️ 设计🏷️ 时间🏷️ 规范🏷️ 终局

1. 文档概述

本文档定义账号天赋系统与传承道具系统的数据结构、接口规范和实现方案。基于现有Buff系统和道具系统进行扩展。

1.1 技术栈

  • 前端框架:React + TypeScript
  • 状态管理:Zustand
  • 存储方案:LocalStorage(无服务器架构)

1.2 依赖系统

  • Buff系统(RuntimeBuff结构)
  • 道具系统(GameItem, BaggageItem)
  • 成就系统(GlobalProfile)
  • 存档系统(PlaythroughSaveSlot)

2. 数据结构定义

2.1 天赋配置接口(静态配置表)

/**
 * 天赋系统 - 静态配置表接口
 * 存储位置:configs/talents.json(只读静态资源)
 */

// 天赋分支枚举
export type TalentBranch = 'BUSINESS' | 'JIANGHU' | 'MYSTIC';

// 天赋作用目标类型
export type TalentTargetType = 'INN' | 'STAFF' | 'GLOBAL';

// 天赋配置接口
export interface StaticTalentConfig {
  id: string;                          // 天赋唯一ID (如: TALENT_BUSINESS_01)
  name: string;                        // 天赋名称 (如: 佟家祖训)
  desc: string;                        // 天赋描述
  branch: TalentBranch;                // 所属分支
  tier: number;                        // 天赋层级 (1-4,用于解锁前置)
  cost: number;                        // 消耗天赋点
  prerequisiteId?: string;             // 前置天赋ID(可选)
  icon: string;                        // 图标路径
  
  // 生成的Buff配置
  buffConfig: {
    configId: string;                  // Buff配置ID (如: BUFF_TALENT_BUSINESS_01)
    targetType: TalentTargetType;      // 作用目标类型
    targetId?: string;                 // 作用目标ID(如员工ID 'bai',INN类型时为空)
    tags: string[];                    // Buff标签
    duration: number;                  // 持续时间 (-1为永久,周目内有效)
    modifiers: BuffModifier[];         // 属性修饰器列表
  };
  
  unlockCondition?: {                  // 解锁条件(可选,部分天赋有额外条件)
    type: 'REPUTATION' | 'ACHIEVEMENT' | 'PLAYTHROUGH_COUNT';
    value: number | string;            // 条件值
  };
  
  sortOrder: number;                   // 排序权重
}

// Buff修饰器接口(复用现有结构)
export interface BuffModifier {
  targetAttribute: string;             // 目标属性
  calcType: 'ADD' | 'MULTIPLY' | 'SET'; // 计算类型
  value: number;                       // 数值
}

2.2 传承道具接口(扩展自GameItem)

/**
 * 传承道具系统 - 扩展数据结构
 * 基于现有 ItemCategory = 'LEGACY' 扩展
 */

// 传承道具子类型
export type LegacyItemSubType = 
  | 'CURRENCY_PACK'      // 货币包(铜钱/银票)
  | 'CONSUMABLE'         // 跨周目消耗品(稀有食材、药材)
  | 'EQUIPMENT'          // 可传承装备
  | 'TOKEN'              // 信物/凭证(解锁剧情或NPC)
  | 'BLUEPRINT';         // 图纸(解锁建筑/设施)

// 传承道具接口(扩展自GameItem)
export interface LegacyItemConfig {
  id: string;                          // 道具唯一ID (如: LEGACY_CURRENCY_500)
  name: string;                        // 道具名称
  desc: string;                        // 道具描述
  category: 'LEGACY';                  // 固定为LEGACY
  subType: LegacyItemSubType;          // 传承道具子类型
  rarity: 'RARE' | 'EPIC' | 'LEGENDARY'; // 稀有度(传承道具最低RARE)
  
  icon: string;                        // 图标路径
  flavorText: string;                  // 风味文本
  
  // 传承特性
  isPermanent: boolean;                // 是否永久保留(true=不会被消耗)
  maxHeld: number;                     // 最大持有数量
  
  // 效果定义(周目开始时生效)
  effects: LegacyItemEffect[];
  
  // 获取来源
  acquisitionSource: 'ACHIEVEMENT' | 'LEGACY_SHOP' | 'SPECIAL_EVENT';
  acquisitionId?: string;              // 来源ID(成就ID或商店商品ID)
  
  // UI相关
  sortOrder: number;                   // 排序权重
}

// 传承道具效果接口
export interface LegacyItemEffect {
  type: 'STARTUP_CURRENCY' |           // 开局货币
        'STARTUP_BUFF' |                // 开局Buff(生成RuntimeBuff)
        'UNLOCK_NPC' |                  // 解锁特殊NPC
        'UNLOCK_FACILITY' |             // 解锁设施
        'UNLOCK_RECIPE' |               // 解锁菜谱
        'LEGACY_SLOT' |                 // 增加传承槽位
        'UNLOCK_TALENT_BRANCH';         // 解锁天赋分支
  
  value?: number;                      // 数值效果
  buffConfigId?: string;               // 关联的Buff配置ID(STARTUP_BUFF类型时使用)
  unlockId?: string;                   // 解锁目标ID
}

2.3 GlobalProfile扩展

/**
 * GlobalProfile 扩展 - 账号天赋与传承道具
 * 基于现有 GlobalProfile 接口扩展
 */

// 名望等级枚举
export type ReputationLevel = 
  | 'UNKNOWN'        // 默默无闻 (0-99)
  | 'FAMOUS'         // 小有名气 (100-499)
  | 'RENOWNED'       // 声名远扬 (500-1499)
  | 'LEGENDARY'      // 威震江湖 (1500-4999)
  | 'MYTHICAL';      // 传说永存 (5000+)

// 名望等级配置
export const REPUTATION_LEVEL_CONFIG: Record<ReputationLevel, {
  min: number;
  max: number;
  title: string;
  legacySlots: number;
  maxTalentTier: number;
}> = {
  UNKNOWN: { min: 0, max: 99, title: '新掌柜', legacySlots: 1, maxTalentTier: 2 },
  FAMOUS: { min: 100, max: 499, title: '七侠镇掌柜', legacySlots: 2, maxTalentTier: 3 },
  RENOWNED: { min: 500, max: 1499, title: '关中名店', legacySlots: 3, maxTalentTier: 4 },
  LEGENDARY: { min: 1500, max: 4999, title: '天下第一楼', legacySlots: 4, maxTalentTier: 4 },
  MYTHICAL: { min: 5000, max: Infinity, title: '同福传奇', legacySlots: 5, maxTalentTier: 4 }
};

// 传承道具库存接口
export interface LegacyItemInventory {
  itemId: string;                            // 传承道具ID
  quantity: number;                          // 持有数量
  acquiredAt: number;                        // 获取时间戳
  source: string;                            // 获取来源
}

// 周目印记接口
export interface PlaythroughStamp {
  playthroughId: number;                     // 周目序号
  outcome: PlaythroughOutcome;               // 结局类型
  survivalDays: number;                      // 存活天数
  maxReputation: number;                     // 最高声望
  totalSilverEarned: number;                 // 赚取银两
  achievementsUnlocked: string[];            // 本局解锁成就
  talentPointsEarned: number;                // 本局获得天赋点
  specialFlags: string[];                    // 特殊标记
  timestamp: number;                         // 结算时间戳
}

// 周目结局类型
export type PlaythroughOutcome = 
  | 'SUCCESS'        // 通关(存活30天+)
  | 'BANKRUPTCY'     // 破产
  | 'SHUTDOWN'       // 被查封
  | 'SPECIAL';       // 特殊结局

// 扩展的GlobalProfile接口
export interface GlobalProfileExtended {
  // ========== 现有字段 ==========
  legacyPoints: number;                       // 江湖阅历
  unlockedAchievements: string[];            // 已解锁成就
  globalCounters: Record<string, number>;    // 全局计数器
  purchasedLegacies: string[];               // 已购买传承商品
  lastSavedAt: number;
  signature: string;
  
  // ========== 新增:江湖名望 ==========
  accountReputation: number;                 // 账号名望值
  reputationLevel: ReputationLevel;          // 名望等级
  
  // ========== 新增:天赋系统 ==========
  talentPoints: number;                      // 当前可用天赋点
  totalTalentPointsEarned: number;           // 历史累计获得天赋点
  unlockedTalents: string[];                 // 已解锁天赋ID列表
  
  // ========== 新增:传承道具 ==========
  legacyItems: LegacyItemInventory[];        // 传承道具库存
  legacySlots: number;                       // 当前传承槽位数
  
  // ========== 新增:周目印记 ==========
  playthroughStamps: PlaythroughStamp[];     // 周目历史记录
}

2.4 Buff标签系统

// Buff标签枚举
export type BuffTag = 
  // 来源标签
  | '#Talent'           // 来自天赋系统
  | '#Legacy'           // 来自传承道具
  | '#Event'            // 来自随机事件
  | '#Equipment'        // 来自装备
  
  // 作用域标签
  | '#Inn'              // 客栈级Buff
  | '#Staff'            // 角色级Buff
  | '#Global'           // 全局级Buff
  
  // 效果类型标签
  | '#Business'         // 经营类
  | '#Jianghu'          // 江湖类
  | '#Mystic'           // 奇门类
  
  // 具体效果标签
  | '#CostReduction'    // 费用降低
  | '#IncomeBoost'      // 收入提升
  | '#Security'         // 治安相关
  | '#Cleaning'         // 清洁相关
  | '#Cooking'          // 烹饪相关
  | '#Accounting'       // 账房相关
  | '#Reputation'       // 声望相关
  | '#EventMitigation'  // 事件减益
  | '#EventBoost'       // 事件增益
  ;

3. 静态配置文件规范

3.1 天赋配置文件

文件位置configs/talents.json

[
  {
    "id": "TALENT_BIZ_01",
    "name": "佟家祖训",
    "desc": "佟家世代经商的祖训,开局自带启动资金",
    "branch": "BUSINESS",
    "tier": 1,
    "cost": 5,
    "prerequisiteId": null,
    "icon": "icons/talents/biz_01.png",
    "buffConfig": {
      "configId": "BUFF_TALENT_BIZ_01",
      "targetType": "INN",
      "targetId": null,
      "tags": ["#Talent", "#Business", "#Startup"],
      "duration": -1,
      "modifiers": [
        {
          "targetAttribute": "startup_currency",
          "calcType": "ADD",
          "value": 100
        }
      ]
    },
    "unlockCondition": null,
    "sortOrder": 1
  },
  {
    "id": "TALENT_BIZ_02",
    "name": "精打细算",
    "desc": "佟家世代经商的精明头脑,所有升级费用降低",
    "branch": "BUSINESS",
    "tier": 2,
    "cost": 10,
    "prerequisiteId": "TALENT_BIZ_01",
    "icon": "icons/talents/biz_02.png",
    "buffConfig": {
      "configId": "BUFF_TALENT_BIZ_02",
      "targetType": "INN",
      "targetId": null,
      "tags": ["#Talent", "#Business", "#CostReduction"],
      "duration": -1,
      "modifiers": [
        {
          "targetAttribute": "upgrade_cost_multiplier",
          "calcType": "MULTIPLY",
          "value": 0.97
        }
      ]
    },
    "unlockCondition": null,
    "sortOrder": 2
  }
]

3.2 传承道具配置文件

文件位置configs/legacy-items.json

[
  {
    "id": "LEGACY_CURRENCY_500",
    "name": "佟家家底",
    "desc": "佟湘玉从汉中带来的私房钱,虽然不多但足够应急",
    "category": "LEGACY",
    "subType": "CURRENCY_PACK",
    "rarity": "RARE",
    "icon": "icons/legacy/coin_pouch.png",
    "flavorText": "这钱是额从汉中带来的...",
    "isPermanent": false,
    "maxHeld": 5,
    "effects": [
      {
        "type": "STARTUP_CURRENCY",
        "value": 500
      }
    ],
    "acquisitionSource": "LEGACY_SHOP",
    "acquisitionId": "SHOP_CURRENCY_500",
    "sortOrder": 1
  },
  {
    "id": "LEGACY_TOKEN_BAI",
    "name": "盗圣玉牌",
    "desc": "白展堂的信物,持有者可在开局直接雇佣白展堂",
    "category": "LEGACY",
    "subType": "TOKEN",
    "rarity": "EPIC",
    "icon": "icons/legacy/bai_token.png",
    "flavorText": "这块玉牌是老白行走江湖的凭证...",
    "isPermanent": true,
    "maxHeld": 1,
    "effects": [
      {
        "type": "UNLOCK_NPC",
        "unlockId": "bai"
      }
    ],
    "acquisitionSource": "ACHIEVEMENT",
    "acquisitionId": "ACH_BAI_LOYALTY_MAX",
    "sortOrder": 10
  },
  {
    "id": "LEGACY_BUFF_FORTUNE",
    "name": "财神符",
    "desc": "七侠镇财神庙求来的符咒,开局自动获得财运Buff",
    "category": "LEGACY",
    "subType": "CONSUMABLE",
    "rarity": "RARE",
    "icon": "icons/legacy/fortune_charm.png",
    "flavorText": "财神爷保佑,日进斗金...",
    "isPermanent": false,
    "maxHeld": 3,
    "effects": [
      {
        "type": "STARTUP_BUFF",
        "buffConfigId": "BUFF_LEGACY_FORTUNE"
      }
    ],
    "acquisitionSource": "LEGACY_SHOP",
    "acquisitionId": "SHOP_FORTUNE_CHAR",
    "sortOrder": 20
  }
]

4. 核心业务逻辑

4.1 天赋点计算服务

/**
 * 天赋点计算服务
 * 用于周目结算时计算天赋点奖励
 */

export interface TalentPointReward {
  base: number;                    // 基础点数
  survivalBonus: number;           // 存活天数奖励
  reputationBonus: number;         // 声望奖励
  achievementBonus: number;        // 成就奖励
  outcomeBonus: number;            // 结局奖励
  total: number;                   // 总计
}

export class TalentPointCalculator {
  /**
   * 计算天赋点奖励
   * @param stamp 周目印记
   * @returns 天赋点奖励明细
   */
  static calculate(stamp: PlaythroughStamp): TalentPointReward {
    // 基础点数:每完成1个周目获得10点
    const base = 10;
    
    // 存活天数奖励:每存活1天+0.5点(上限30点)
    const survivalBonus = Math.min(stamp.survivalDays * 0.5, 30);
    
    // 声望奖励:最高声望/100(上限20点)
    const reputationBonus = Math.min(Math.floor(stamp.maxReputation / 100), 20);
    
    // 成就奖励:每个成就+2点
    const achievementBonus = stamp.achievementsUnlocked.length * 2;
    
    // 通关额外奖励
    const outcomeBonus = stamp.outcome === 'SUCCESS' ? 20 : 0;
    
    return {
      base,
      survivalBonus,
      reputationBonus,
      achievementBonus,
      outcomeBonus,
      total: base + survivalBonus + reputationBonus + achievementBonus + outcomeBonus
    };
  }
}

4.2 名望等级服务

/**
 * 名望等级服务
 * 用于计算名望等级和相关解锁
 */

export class ReputationService {
  /**
   * 根据名望值获取等级
   * @param reputation 名望值
   * @returns 名望等级
   */
  static getLevel(reputation: number): ReputationLevel {
    if (reputation >= 5000) return 'MYTHICAL';
    if (reputation >= 1500) return 'LEGENDARY';
    if (reputation >= 500) return 'RENOWNED';
    if (reputation >= 100) return 'FAMOUS';
    return 'UNKNOWN';
  }
  
  /**
   * 获取名望等级配置
   * @param level 名望等级
   * @returns 等级配置
   */
  static getConfig(level: ReputationLevel) {
    return REPUTATION_LEVEL_CONFIG[level];
  }
  
  /**
   * 计算单局声望折算名望(边际递减)
   * @param runReputation 单局最高声望
   * @returns 折算后的名望值
   */
  static convertRunReputation(runReputation: number): number {
    // 声望100以下不折算
    if (runReputation <= 100) return 0;
    
    // 100-500:1:1折算
    if (runReputation <= 500) return runReputation - 100;
    
    // 500-1000:0.5折算
    if (runReputation <= 1000) return 400 + (runReputation - 500) * 0.5;
    
    // 1000以上:0.25折算
    return 650 + (runReputation - 1000) * 0.25;
  }
}

4.3 天赋Buff激活服务

/**
 * 天赋Buff激活服务
 * 用于周目开始时激活天赋Buff
 */

export class TalentBuffActivator {
  /**
   * 激活所有已解锁天赋的Buff
   * @param profile 全局账号数据
   * @param talentConfigs 天赋配置列表
   * @returns 生成的Buff实例列表
   */
  static activateTalents(
    profile: GlobalProfileExtended,
    talentConfigs: StaticTalentConfig[]
  ): { innBuffs: RuntimeBuff[]; staffBuffs: Record<string, RuntimeBuff[]> } {
    const innBuffs: RuntimeBuff[] = [];
    const staffBuffs: Record<string, RuntimeBuff[]> = {};
    
    // 筛选已解锁天赋
    const activeTalents = talentConfigs.filter(config => 
      profile.unlockedTalents.includes(config.id)
    );
    
    // 检查名望等级限制
    const reputationConfig = ReputationService.getConfig(profile.reputationLevel);
    const validTalents = activeTalents.filter(talent => 
      talent.tier <= reputationConfig.maxTalentTier
    );
    
    // 生成Buff实例
    validTalents.forEach(talent => {
      const buff: RuntimeBuff = {
        instanceId: generateUUID(),
        configId: talent.buffConfig.configId,
        tags: talent.buffConfig.tags,
        durationRemaining: talent.buffConfig.duration,
        sourceType: 'TALENT',
        modifiers: talent.buffConfig.modifiers
      };
      
      if (talent.buffConfig.targetType === 'INN' || talent.buffConfig.targetType === 'GLOBAL') {
        innBuffs.push(buff);
      } else if (talent.buffConfig.targetType === 'STAFF' && talent.buffConfig.targetId) {
        const staffId = talent.buffConfig.targetId;
        if (!staffBuffs[staffId]) {
          staffBuffs[staffId] = [];
        }
        staffBuffs[staffId].push(buff);
      }
    });
    
    return { innBuffs, staffBuffs };
  }
}

4.4 传承道具应用服务

/**
 * 传承道具应用服务
 * 用于周目开始时应用传承道具效果
 */

export class LegacyItemApplicator {
  /**
   * 应用传承道具效果
   * @param selectedItems 选中的传承道具配置
   * @param profile 全局账号数据
   * @returns 初始游戏状态修改
   */
  static applyItems(
    selectedItems: LegacyItemConfig[],
    profile: GlobalProfileExtended
  ): {
    startupCurrency: number;
    innBuffs: RuntimeBuff[];
    unlockedNpcs: string[];
    unlockedFacilities: string[];
    unlockedRecipes: string[];
  } {
    let startupCurrency = 0;
    const innBuffs: RuntimeBuff[] = [];
    const unlockedNpcs: string[] = [];
    const unlockedFacilities: string[] = [];
    const unlockedRecipes: string[] = [];
    
    selectedItems.forEach(item => {
      item.effects.forEach(effect => {
        switch (effect.type) {
          case 'STARTUP_CURRENCY':
            startupCurrency += effect.value || 0;
            break;
            
          case 'STARTUP_BUFF':
            if (effect.buffConfigId) {
              const buffConfig = loadBuffConfig(effect.buffConfigId);
              if (buffConfig) {
                innBuffs.push({
                  instanceId: generateUUID(),
                  configId: buffConfig.configId,
                  tags: [...buffConfig.tags, '#Legacy'],
                  durationRemaining: buffConfig.duration,
                  sourceType: 'LEGACY',
                  modifiers: buffConfig.modifiers
                });
              }
            }
            break;
            
          case 'UNLOCK_NPC':
            if (effect.unlockId) {
              unlockedNpcs.push(effect.unlockId);
            }
            break;
            
          case 'UNLOCK_FACILITY':
            if (effect.unlockId) {
              unlockedFacilities.push(effect.unlockId);
            }
            break;
            
          case 'UNLOCK_RECIPE':
            if (effect.unlockId) {
              unlockedRecipes.push(effect.unlockId);
            }
            break;
        }
      });
    });
    
    // 消耗非永久道具
    this.consumeItems(selectedItems, profile);
    
    return {
      startupCurrency,
      innBuffs,
      unlockedNpcs,
      unlockedFacilities,
      unlockedRecipes
    };
  }
  
  /**
   * 消耗非永久传承道具
   */
  private static consumeItems(
    selectedItems: LegacyItemConfig[],
    profile: GlobalProfileExtended
  ): void {
    selectedItems
      .filter(item => !item.isPermanent)
      .forEach(item => {
        const inventory = profile.legacyItems.find(i => i.itemId === item.id);
        if (inventory) {
          inventory.quantity--;
          if (inventory.quantity <= 0) {
            profile.legacyItems = profile.legacyItems.filter(i => i.itemId !== item.id);
          }
        }
      });
  }
}

5. 存档兼容性设计

5.1 存档版本号

// 存档版本常量
export const SAVE_SCHEMA_VERSION = 3; // 当前版本

// 版本迁移映射
export const SAVE_MIGRATIONS: Record<number, (data: any) => any> = {
  1: (data) => migrateV1ToV2(data),
  2: (data) => migrateV2ToV3(data),
};

5.2 V2到V3迁移(新增天赋与传承字段)

function migrateV2ToV3(data: any): GlobalProfileExtended {
  return {
    ...data,
    // 新增字段默认值
    accountReputation: data.accountReputation || 0,
    reputationLevel: data.reputationLevel || 'UNKNOWN',
    talentPoints: data.talentPoints || 0,
    totalTalentPointsEarned: data.totalTalentPointsEarned || 0,
    unlockedTalents: data.unlockedTalents || [],
    legacyItems: data.legacyItems || [],
    legacySlots: data.legacySlots || 1,
    playthroughStamps: data.playthroughStamps || [],
  };
}

6. 事件总线集成

6.1 天赋相关事件

// 天赋系统事件类型
export const TALENT_EVENTS = {
  // 天赋解锁
  TALENT_UNLOCKED: 'TALENT_UNLOCKED',
  
  // 天赋点变化
  TALENT_POINTS_CHANGED: 'TALENT_POINTS_CHANGED',
  
  // 名望变化
  REPUTATION_CHANGED: 'REPUTATION_CHANGED',
  
  // 名望等级提升
  REPUTATION_LEVEL_UP: 'REPUTATION_LEVEL_UP',
  
  // 传承道具获取
  LEGACY_ITEM_ACQUIRED: 'LEGACY_ITEM_ACQUIRED',
  
  // 传承道具使用
  LEGACY_ITEM_USED: 'LEGACY_ITEM_USED',
  
  // 周目结算完成
  PLAYTHROUGH_SETTLED: 'PLAYTHROUGH_SETTLED',
} as const;

6.2 事件数据接口

// 天赋解锁事件数据
export interface TalentUnlockedEventData {
  talentId: string;
  talentName: string;
  branch: TalentBranch;
  cost: number;
}

// 名望变化事件数据
export interface ReputationChangedEventData {
  oldValue: number;
  newValue: number;
  delta: number;
  source: string;
}

// 周目结算事件数据
export interface PlaythroughSettledEventData {
  stamp: PlaythroughStamp;
  talentPointReward: TalentPointReward;
  reputationGained: number;
}

7. UI组件接口

7.1 天赋树组件Props

export interface TalentTreePanelProps {
  profile: GlobalProfileExtended;
  talentConfigs: StaticTalentConfig[];
  onUnlockTalent: (talentId: string) => void;
}

7.2 传承道具选择组件Props

export interface LegacyItemSelectionProps {
  profile: GlobalProfileExtended;
  legacyItemConfigs: LegacyItemConfig[];
  maxSlots: number;
  onConfirm: (selectedItemIds: string[]) => void;
  onCancel: () => void;
}

7.3 周目结算组件Props

export interface PlaythroughSettlementProps {
  stamp: PlaythroughStamp;
  talentPointReward: TalentPointReward;
  reputationGained: number;
  newReputationLevel: ReputationLevel;
  onConfirm: () => void;
}

8. 存储Key规范

// 存储Key常量
export const STORAGE_KEYS = {
  // 全局账号数据(扩展版)
  GLOBAL_PROFILE: 'tongfu_global_profile_v3',
  
  // 天赋配置缓存
  TALENT_CONFIGS_CACHE: 'tongfu_talent_configs_cache',
  
  // 传承道具配置缓存
  LEGACY_ITEMS_CACHE: 'tongfu_legacy_items_cache',
} as const;

9. 测试用例

9.1 天赋点计算测试

describe('TalentPointCalculator', () => {
  it('应正确计算基础天赋点', () => {
    const stamp: PlaythroughStamp = {
      playthroughId: 1,
      outcome: 'BANKRUPTCY',
      survivalDays: 10,
      maxReputation: 200,
      totalSilverEarned: 5000,
      achievementsUnlocked: ['ACH_001', 'ACH_002'],
      talentPointsEarned: 0,
      specialFlags: [],
      timestamp: Date.now()
    };
    
    const result = TalentPointCalculator.calculate(stamp);
    
    expect(result.base).toBe(10);
    expect(result.survivalBonus).toBe(5);  // 10 * 0.5
    expect(result.reputationBonus).toBe(2); // 200 / 100
    expect(result.achievementBonus).toBe(4); // 2 * 2
    expect(result.outcomeBonus).toBe(0);   // 非通关
    expect(result.total).toBe(21);
  });
  
  it('应正确计算通关天赋点', () => {
    const stamp: PlaythroughStamp = {
      playthroughId: 2,
      outcome: 'SUCCESS',
      survivalDays: 30,
      maxReputation: 1000,
      totalSilverEarned: 50000,
      achievementsUnlocked: ['ACH_001', 'ACH_002', 'ACH_003'],
      talentPointsEarned: 0,
      specialFlags: [],
      timestamp: Date.now()
    };
    
    const result = TalentPointCalculator.calculate(stamp);
    
    expect(result.base).toBe(10);
    expect(result.survivalBonus).toBe(15);  // 30 * 0.5
    expect(result.reputationBonus).toBe(10); // 1000 / 100
    expect(result.achievementBonus).toBe(6); // 3 * 2
    expect(result.outcomeBonus).toBe(20);   // 通关
    expect(result.total).toBe(61);
  });
});

9.2 名望等级测试

describe('ReputationService', () => {
  it('应正确计算名望等级', () => {
    expect(ReputationService.getLevel(0)).toBe('UNKNOWN');
    expect(ReputationService.getLevel(99)).toBe('UNKNOWN');
    expect(ReputationService.getLevel(100)).toBe('FAMOUS');
    expect(ReputationService.getLevel(500)).toBe('RENOWNED');
    expect(ReputationService.getLevel(1500)).toBe('LEGENDARY');
    expect(ReputationService.getLevel(5000)).toBe('MYTHICAL');
  });
  
  it('应正确折算声望', () => {
    expect(ReputationService.convertRunReputation(50)).toBe(0);
    expect(ReputationService.convertRunReputation(150)).toBe(50);
    expect(ReputationService.convertRunReputation(500)).toBe(400);
    expect(ReputationService.convertRunReputation(750)).toBe(525);
    expect(ReputationService.convertRunReputation(1000)).toBe(650);
    expect(ReputationService.convertRunReputation(1500)).toBe(775);
  });
});

10. 性能优化建议

10.1 配置缓存

  • 天赋配置和传承道具配置在游戏初始化时一次性加载并缓存
  • 使用useMemo缓存计算结果

10.2 懒加载

  • 天赋树UI组件采用懒加载
  • 传承道具详情弹窗按需加载

10.3 批量更新

  • 周目结算时批量更新GlobalProfile
  • 使用事务性更新避免中间状态

11. 后续扩展预留

11.1 天赋重置功能

// 预留接口
export interface TalentResetService {
  resetTalent(talentId: string, profile: GlobalProfileExtended): number; // 返回返还的天赋点
  resetAllTalents(profile: GlobalProfileExtended): number;
  getResetCost(profile: GlobalProfileExtended): number; // 重置费用(阅历)
}

11.2 传承道具合成

// 预留接口
export interface LegacyItemSynthesisService {
  canSynthesize(itemIds: string[]): boolean;
  synthesize(itemIds: string[]): LegacyItemConfig;
  getSynthesisResult(itemIds: string[]): LegacyItemConfig;
}