فهرست منبع

doc:文档整理

pigflower 3 هفته پیش
والد
کامیت
983cf4a829
3فایلهای تغییر یافته به همراه572 افزوده شده و 0 حذف شده
  1. 404 0
      docs/代币系统技术设计文档.md
  2. 17 0
      docs/协议.md
  3. 151 0
      docs/需求整理.txt

+ 404 - 0
docs/代币系统技术设计文档.md

@@ -0,0 +1,404 @@
+# 代币系统技术设计文档
+
+## 1. 需求概述
+
+新增两种道具:**代币** 和 **绑定代币**,支持以下核心功能:
+
+1. 代币/绑定代币可消耗用于购买部分付费商品(配置项控制哪些商品支持)
+2. 代币购买商品时触发累充VIP、首充、每日累充等功能
+3. 玩家可通过代币交易功能与其他玩家交换获得泪滴石
+4. 通过交易获得的代币为绑定代币,绑定代币不可再次交易
+5. 出售代币也触发累充VIP、首充、每日累充等功能
+6. 消耗比例:100个代币/绑定代币 = 1元付费货币等值商品
+
+---
+
+## 2. 道具定义
+
+### 2.1 道具类型
+
+两个道具均属于**代币类(Token,ItemToolType=3)**,进入背包,通过 `RoleItemData.Items` 存储,不加入 `ItemsNotInPackage`。
+
+| 道具名称 | 建议ItemId | ItemInstanceId | 是否可交易 | 备注 |
+|---------|-----------|----------------|-----------|------|
+| 代币    | 36        | 3600           | ✅ 可交易  | 正常获得 |
+| 绑定代币 | 37        | 3700           | ❌ 不可交易 | 仅通过交易获得 |
+
+> **注意**:实际 ItemId 需对照现有 `item.xlsx` 最大值后续分配,不得与已有道具冲突。
+
+### 2.2 配置表改动(item.xlsx)
+
+新增两行:
+
+| 字段        | 代币       | 绑定代币   |
+|-----------|-----------|-----------|
+| Id        | 36        | 37        |
+| ItemType  | 3(Token) | 3(Token) |
+| ItemName  | 代币       | 绑定代币   |
+| Stack     | 99999999  | 99999999  |
+| AutoUse   | 0         | 0         |
+| HideInBag | 0         | 0         |
+| DataItemName | Token  | BoundToken |
+| ItemTypeDataName | Token | BoundToken |
+
+### 2.3 配置表改动(iteminstance.xlsx)
+
+新增两行:
+
+| 字段       | 代币  | 绑定代币 |
+|----------|------|---------|
+| InstanceId | 3600 | 3700   |
+| ItemId   | 36   | 37      |
+| ItemType | 1    | 1       |
+
+### 2.4 代码常量(ItemDefines.cs)
+
+文件:`server/src/server/OpenCards.Server.Logic/Module/Item/ItemDefines.cs`
+
+```csharp
+public static int TokenItemId = 36;         // 代币
+public static int BoundTokenItemId = 37;    // 绑定代币
+public static int TokenItemInstanceId = 3600;      // 代币捆绑实例ID
+public static int BoundTokenItemInstanceId = 3700; // 绑定代币捆绑实例ID
+```
+
+---
+
+## 3. 商品代币购买功能
+
+### 3.1 配置表改动(Market.xlsx - market_gift 表)
+
+在 `Table_MarketGift` 对应的 `Market.xlsx` 的 `market_gift` sheet 新增字段:
+
+| 字段名       | 类型 | 说明 |
+|------------|-----|-----|
+| TokenEnable | int | 是否支持代币购买:0=不支持,1=支持 |
+
+对应代码改动,文件 `server/src/server/OpenCards.Server.Core/Table/Table_Market.cs`:
+
+```csharp
+/// <summary>
+/// 是否支持代币购买 0=不支持 1=支持
+/// </summary>
+public int TokenEnable;
+```
+
+### 3.2 消耗比例
+
+- 1元付费货币(CNY)= 100 代币/绑定代币
+- 下单时根据商品的 `Table_IAP.CNY` 字段计算所需代币数:`tokenCost = (int)(iap.CNY * 100)`
+
+### 3.3 代币购买流程(服务端)
+
+新增 `ChargeType.Token` 枚举值,文件 `MarketModule.cs`:
+
+```csharp
+private enum ChargeType
+{
+    Charge,
+    Ticket,
+    GMSend,
+    Token,      // 代币购买
+}
+```
+
+在下单处理 `ClientGetRechargeOrderRequest` 的处理函数中,在现有 `Ticket` 判断之后追加代币购买逻辑:
+
+```
+if (giftConfig.TokenEnable == 1)
+{
+    // 1. 计算所需代币数量
+    tokenCost = (int)(iap.CNY * 100)
+
+    // 2. 优先尝试扣代币,失败则扣绑定代币,两者都不足则返回代币不足错误码
+    // 3. 扣款成功后调用 OnPaySuccess(productId, ext1, ..., ChargeType.Token)
+    // 4. 同时调用 RecordChargeToken(iap.CNY) 记录代币消费金额
+    // 5. 返回 CODE_CHARGE_BY_TOKEN 告知客户端
+}
+```
+
+**扣款优先级**:优先扣代币,代币不足时扣绑定代币,两者数量之和不足时返回失败。
+
+在 `ClientGetRechargeOrderResponse` 中新增响应码:
+
+```csharp
+public const int CODE_CHARGE_BY_TOKEN = 4;   // 代币购买成功
+public const int CODE_TOKEN_NOT_ENOUGH = 505; // 代币不足
+public const int CODE_TOKEN_NOT_SUPPORT = 506; // 该商品不支持代币购买
+```
+
+### 3.4 代币购买对累充系统的触发
+
+在 `OnPaySuccess(ChargeType.Token)` 分支中,与 `ChargeType.Ticket` 处理方式相同:
+
+- `mRoleFlag.AddFlag(FlagDefines.ChargeFlag, convertPrice)` → 触发累计充值
+- `mRoleFlag.AddFlag(FlagDefines.UnrealRMBChargeFlag, rmbConvertPrice)` → 非真实RMB累计(不影响真实充值统计)
+- `mRoleFlag.AddFlag(FlagDefines.BuyCountFlag, 1)` → 购买次数累计
+- `CheckFosterRecharge(convertPrice)` → 触发每日累充(养成豪礼)
+- `CheckRechargeInfo()` → 触发动态难度检查
+- `GetModule<HeroModule>().AddRechargBUFData(convertPrice)` → 充值BUFF
+- `DispatchEvent(EventDefines.EventAfterPayDelivery, productId)` → 发货后事件(触发首充、VIP等检测)
+- `DispatchEvent(EventDefines.EventAfterPayDeliveryWithPrice, productId, price)` → 带价格的发货后事件
+
+> **关键**:代币购买同样走完整的 `OnPaySuccess` 流程,确保所有充值触发链均正常响应。VipExp 通过 `GiftDropID` 掉落表配置自动发放,无需单独处理。
+
+### 3.5 客户端二次确认弹窗
+
+当商品 `TokenEnable == 1` 时,客户端在点击购买按钮后弹出二次确认弹窗。
+
+**弹窗内容**(服务端下发商品信息中含 `TokenEnable` 字段,客户端据此决定是否展示):
+
+```
+标题:购买确认
+正文:该商品支持代币购买
+  · 直接充值:使用真实货币支付,立即购买
+  · 代币购买:消耗 {tokenCost} 个代币(当前拥有: {ownToken})
+[直接充值]  [代币购买]
+```
+
+**服务端协议新增**:在商品列表同步协议中,将 `TokenEnable` 和 `TokenCost` 下发给客户端。
+
+---
+
+## 4. 代币交易系统
+
+### 4.1 功能概述
+
+玩家A(卖方)挂出代币,玩家B(买方)支付泪滴石,完成交易后:
+- 玩家B消耗泪滴石,获得**绑定代币**(非代币)
+- 玩家A获得泪滴石,此次出售触发累充系统
+
+绑定代币不可再次挂出交易。
+
+### 4.2 新增数据表(TokenTradeData)
+
+新增 `TokenTradeData.cs`(`OpenCards.Core/ORM/` 目录):
+
+```csharp
+[PersistType]
+public class TokenTradeOrder : ISerializable, IObjectMapping
+{
+    [PersistField(PersistStrategy.Primary)]
+    public string OrderId;          // 订单唯一ID(UUID)
+
+    [PersistField]
+    public string SellerRoleId;     // 卖方角色ID
+
+    [PersistField]
+    public int TokenAmount;         // 挂出的代币数量
+
+    [PersistField]
+    public int TearStonePrice;      // 要求的泪滴石数量
+
+    [PersistField]
+    public long CreateTime;         // 创建时间(ms时间戳)
+
+    [PersistField]
+    public long ExpireTime;         // 过期时间(ms时间戳)
+
+    [PersistField]
+    public int Status;              // 0=挂单中,1=已成交,2=已取消,3=已过期
+}
+```
+
+持久化类型常量在 `PersistenceConstants` 中新增:
+
+```csharp
+public const string TYPE_TOKEN_TRADE_ORDER = "token_trade_order";
+```
+
+### 4.3 新增协议(TokenTrade 协议文件)
+
+新建 `0x23A00.TokenTrade.cs`(`OpenCards.Core/Protocol/Client/` 目录):
+
+```
+消息码段:0x23A00 起
+```
+
+| 协议名 | 方向 | 说明 |
+|-------|-----|-----|
+| `ClientTokenTradeListRequest` | C→S | 获取交易列表 |
+| `ClientTokenTradeListResponse` | S→C | 返回交易列表 |
+| `ClientTokenTradePutRequest` | C→S | 挂出代币 |
+| `ClientTokenTradePutResponse` | S→C | 挂出结果 |
+| `ClientTokenTradeCancelRequest` | C→S | 取消挂单 |
+| `ClientTokenTradeCancelResponse` | S→C | 取消结果 |
+| `ClientTokenTradeBuyRequest` | C→S | 购买(用泪滴石换绑定代币) |
+| `ClientTokenTradeBuyResponse` | S→C | 购买结果 |
+| `ClientTokenTradeMyOrderRequest` | C→S | 查询自己的挂单 |
+| `ClientTokenTradeMyOrderResponse` | S→C | 返回自己的挂单 |
+
+**关键字段说明**:
+
+`ClientTokenTradePutRequest`:
+- `c2s_tokenAmount`:挂出代币数量
+- `c2s_tearStonePrice`:期望收取的泪滴石数量
+
+`ClientTokenTradeBuyRequest`:
+- `c2s_orderId`:目标订单ID
+
+### 4.4 新增模块(TokenTradeModule)
+
+新建 `server/src/server/OpenCards.Server.Logic/Module/TokenTradeModule.cs`。
+
+**挂单逻辑(DoTokenTradePut)**:
+
+```
+1. 校验:挂出的必须是代币(ItemId=36),不得挂绑定代币(ItemId=37)
+2. 校验代币余量是否足够
+3. 校验玩家当前挂单数量是否超上限(配置表控制,默认5单)
+4. 扣除玩家代币(EventRemoveItem,RemoveItemReason.TokenTrade)
+5. 生成 TokenTradeOrder,写入全服 Redis/DB(中心服维护订单列表)
+6. 返回成功
+```
+
+**购买逻辑(DoTokenTradeBuy)**:
+
+```
+1. 从中心服获取订单,校验订单状态=挂单中
+2. 校验买方泪滴石余量 >= 订单价格
+3. 原子操作:
+   a. 扣除买方泪滴石(EventRemoveItem,RemoveItemReason.TokenTradeBuy)
+   b. 发放买方 绑定代币(EventAddItem,ItemInstanceId=3700,AddItemReason.TokenTrade)
+   c. 向卖方发邮件,附带泪滴石奖励(EventSendMailWithItems)
+   d. 订单状态改为已成交
+4. 触发卖方出售代币的累充事件(见 4.5)
+```
+
+**取消挂单逻辑(DoTokenTradeCancel)**:
+
+```
+1. 校验订单属于本玩家且状态=挂单中
+2. 退还代币给玩家(EventAddItem,ItemInstanceId=3600,AddItemReason.TokenTradeCancel)
+3. 订单状态改为已取消
+```
+
+**定时过期**:订单过期时间由配置表控制(建议7天),服务端定时扫描过期订单,自动取消并退还代币。
+
+### 4.5 出售代币触发累充
+
+出售成交后,对**卖方**触发累充系统,等效金额按 `泪滴石数量 / 100` CNY 计算:
+
+```csharp
+float equivalentCNY = tearStoneAmount / 100f;
+long convertPrice = Convert.ToInt64(equivalentCNY * 100);
+
+// 触发累充系列
+GetModule<MarketModule>().CheckFosterRecharge(convertPrice);
+GetModule<MarketModule>().CheckRechargeInfo();
+mRoleFlag.AddFlag(FlagDefines.ChargeFlag, convertPrice, Flag.MapType.EPersist);
+mRoleFlag.AddFlag(FlagDefines.UnrealRMBChargeFlag, convertPrice, Flag.MapType.EPersist);
+mRoleFlag.AddFlag(FlagDefines.BuyCountFlag, 1, Flag.MapType.EPersist);
+DispatchEvent(EventDefines.EventAfterPayDeliveryWithPrice, 0, equivalentCNY);
+```
+
+> 出售代币不触发首充(`EventAfterPayDelivery` 不传 productId,或传 0 使相关模块跳过首充发货)。首充只在商品购买时触发,按现有逻辑执行。
+
+### 4.6 新增 AddItemReason / RemoveItemReason
+
+在 `ItemDefines.cs` 中追加:
+
+```csharp
+// AddItemReason
+public static int TokenTrade = 129;         // 代币交易获得绑定代币
+public static int TokenTradeCancel = 130;   // 代币交易取消退还代币
+
+// RemoveItemReason
+public static int TokenTrade = 60;          // 挂出代币
+public static int TokenTradeBuy = 61;       // 购买时消耗泪滴石
+```
+
+---
+
+## 5. 泪滴石道具确认
+
+泪滴石为现有道具,已在英雄重置、斗技场等功能中使用(见客户端语言文件)。实现前需确认服务端 ItemId,通过查询 `item.xlsx` 获取,代币交易模块中引用该 ItemId 作为常量。
+
+```csharp
+// 待确认实际ID,填入后在 ItemDefines 中注册
+public static int TearStoneItemId = ???;
+```
+
+---
+
+## 6. 涉及文件清单
+
+### 6.1 配置表
+
+| 文件 | Sheet | 操作 |
+|-----|------|-----|
+| `item.xlsx` | item | 新增代币(36)、绑定代币(37)两行 |
+| `iteminstance.xlsx` | item_instance | 新增 3600、3700 两行 |
+| `Market.xlsx` | market_gift | 新增 `TokenEnable` 字段列 |
+
+### 6.2 服务端代码
+
+| 文件 | 操作 |
+|-----|-----|
+| `OpenCards.Core/ORM/TokenTradeData.cs` | **新建**:订单数据结构 |
+| `OpenCards.Core/Protocol/Client/0x23A00.TokenTrade.cs` | **新建**:交易协议定义 |
+| `OpenCards.Core/Data/Constants.cs` | 新增消息码段常量 `TOKEN_TRADE_START` |
+| `OpenCards.Server.Logic/Module/Item/ItemDefines.cs` | 新增道具ID常量、新增 AddItemReason/RemoveItemReason |
+| `OpenCards.Server.Logic/Module/TokenTradeModule.cs` | **新建**:代币交易业务模块 |
+| `OpenCards.Server.Logic/Module/MarketModule.cs` | 新增 `ChargeType.Token`;在下单处理中追加代币购买分支;新增 `RecordChargeToken()` |
+| `OpenCards.Server.Core/Table/Table_Market.cs` | `Table_MarketGift` 新增 `TokenEnable` 字段 |
+| `OpenCards.Server.Core/Table/Table_IAP.cs` | 无需改动(复用 `CNY` 字段计算代币数) |
+| `OpenCards.GenCodec/` | 重新生成序列化代码(自动生成,运行代码生成工具) |
+| `OpenCards.Server.GenORM/` | 重新生成 ORM 代码(自动生成) |
+
+### 6.3 客户端(服务端接口侧)
+
+| 文件 | 操作 |
+|-----|-----|
+| 商品列表协议 | 在现有商品信息中新增 `TokenEnable`、`TokenCost` 字段下发 |
+| 下单协议响应 | 新增 `CODE_CHARGE_BY_TOKEN`、`CODE_TOKEN_NOT_ENOUGH` 等响应码 |
+
+---
+
+## 7. 关键设计决策
+
+### 7.1 代币/绑定代币均进背包,不走 CurrencyModule
+
+代币属于代币类(`ItemToolType.Token=3`),存储在 `RoleItemData.Items`,无需改动 `CurrencyModule.cs` 和 `RoleData.cs`。查询数量、增删均通过事件 `EventGetItemCount` / `EventAddItemImp` / `EventRemoveItem` 走背包通用逻辑。
+
+### 7.2 绑定代币以独立 ItemId 区分
+
+绑定代币(ItemId=37)与代币(ItemId=36)是两个独立道具,通过 ItemId 区分是否可交易,无需额外绑定标记字段,逻辑简单清晰。
+
+### 7.3 代币购买触发累充等同于 Ticket 机制
+
+现有 `ChargeType.Ticket` 已实现"非真实充值走完整充值流程"的模式,`ChargeType.Token` 与其保持一致,复用 `OnPaySuccess` 完整逻辑,确保首充、VIP、每日累充等所有依赖充值事件的功能均正常触发。
+
+### 7.4 代币交易为全服功能
+
+代币交易订单是全服共享的(非单角色),需中心服(或独立的交易服务)维护订单列表。订单数据存储在全服 Redis/DB,而非角色本地数据。
+
+### 7.5 购买代币时扣款优先级
+
+优先消耗代币,代币不足时消耗绑定代币。两者合计不足才返回错误。逻辑在服务端实现,客户端展示合计数量。
+
+---
+
+## 8. 错误码汇总
+
+| 错误码常量 | 值 | 说明 |
+|----------|---|-----|
+| `CODE_CHARGE_BY_TOKEN` | 4 | 代币购买成功(下单响应) |
+| `CODE_TOKEN_NOT_ENOUGH` | 505 | 代币不足 |
+| `CODE_TOKEN_NOT_SUPPORT` | 506 | 该商品不支持代币购买 |
+| `CODE_TRADE_ORDER_NOT_FOUND` | 507 | 交易订单不存在 |
+| `CODE_TRADE_ORDER_SOLD` | 508 | 订单已被购买 |
+| `CODE_TRADE_TOKEN_NOT_TRADEABLE` | 509 | 绑定代币不可交易 |
+| `CODE_TRADE_EXCEED_MAX_ORDER` | 510 | 超出最大挂单数量 |
+| `CODE_TRADE_TEARSTONE_NOT_ENOUGH` | 511 | 泪滴石不足 |
+
+---
+
+## 9. 开发顺序建议
+
+1. **配置表**:`item.xlsx`、`iteminstance.xlsx`、`Market.xlsx` 新增字段,更新到 ServerData
+2. **基础道具**:`ItemDefines.cs` 新增常量,导表,验证背包可正常收发
+3. **商品购买**:`Table_Market.cs` 加字段 → `MarketModule.cs` 加代币购买分支 → 联调充值触发链
+4. **交易系统**:新建协议 → 新建 `TokenTradeModule` → 接入中心服订单存储 → 联调全流程
+5. **代码生成**:重新生成 GenCodec / GenORM
+6. **GM指令**:新增 GM 指令发放/扣除代币以方便测试

+ 17 - 0
docs/协议.md

@@ -0,0 +1,17 @@
+协议文件分布在以下位置:
+协议定义(手写源文件)
+客户端服务端协议(按消息ID分模块):server/src/core/0penCards.Core/Protocol/Client/-主协议定义-0x35000.Logic.Common.cs、0x35200.Logic.Fight.cs、0x35300.Logic.Role.cs0x39000.Logic.Arena.cs、0x37300.Logic.Mail.cs等,按功能模块拆分server/src/core/0penCards.Core/Protocol/Constants.cs -消息 ID 段常量定义server/src/core/0penCards.Core/Protocol/MsgAttribute.cs - [RequestMsg]/[ResponseMsg] 标记
+服务端内部 RPC协议:-server/src/server/0penCards.Server.Core/RPC/ -服务器间 RPC 消息(Account、Arena、Guild、Role等)
+共享数据结构:-server/src/core/0penCards.Core/Data/ -协议里用到的数据对象(0x22000.Role.cs、0x23800.Arena.cs 等)
+自动生成文件(不要手改)
+目录
+server/src/core/0penCards.GenCodec/generated_msg/
+server/src/server/0pencards.Server.GenORM/generated_msg/
+data/clientscript/Protocol/generated/
+内容
+C#序列化/反序列化 codec(gitignored)
+服务端 RPC codec (gitignored)
+Lua 版 codec,给客户端用
+整体架构
+-协议在 C#的 OpenCards.Core.Protocol.Client 命名空间里手写定义,用[RequestMsg]标记配对OpenCards.GenCodec项目的 PostBuild 把这些定义生成对应的 C#codec和 Lua codec-客户端(Lua)用 data/ClientScript/Protocol/generated/下的 Lua 文件通信-服务端用C#codeC,消息ID按模块分段(Arena在0x39000段,等等)
+改协议的正确流程:移改 DpenCards.Core/ProtocoU/CLient/ 里的源文件  重新编译 DpenCards.TooLs (触发 PostBuild 生成新 codec)  前后端 codec 同步更新

+ 151 - 0
docs/需求整理.txt

@@ -0,0 +1,151 @@
+  货币类 vs 代币类 — 核心区别
+
+  ┌───────────────────────┬──────────────────────────────┬───────────────────────────────────────┐
+  │                       │       货币(Currency)       │             代币(Token)             │
+  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
+  │ 举例                  │ 钻石(1)、金币(5)、友情点(15) │ 迷宫币(9)、血战筹码(12)、悬赏点(35)   │
+  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
+  │ 存储位置              │ RoleData 字段(专用字段)    │ RoleItemData.Items 背包(通用道具格) │
+  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
+  │ 不进背包              │ ✅ 加入 ItemsNotInPackage    │ ❌ 不用加                             │
+  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
+  │ 需要改 CurrencyModule │ ✅ 必须加                    │ ❌ 不需要                             │
+  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
+  │ 需要改 RoleData       │ ✅ 需要加字段                │ ❌ 不需要                             │
+  ├───────────────────────┼──────────────────────────────┼───────────────────────────────────────┤
+  │ 客户端同步            │ 通过 SyncClientRoleDataType  │ 通过背包增量同步                      │
+  └───────────────────────┴──────────────────────────────┴───────────────────────────────────────┘
+
+  ---
+  一、新增货币类道具(如新货币)
+
+  步骤 1:item.xlsx 新增一行
+
+  Id=36, ItemType=1(Currency), ItemName=XX货币, Stack=0, AutoUse=0
+
+  步骤 2:iteminstance.xlsx 新增捆绑实例
+
+  InstanceId=3600, ItemId=36, ItemType=1
+
+  步骤 3:ItemDefines.cs 新增 ID 常量 + 加入 ItemsNotInPackage
+
+  文件:server/src/server/OpenCards.Server.Logic/Module/Item/ItemDefines.cs
+
+  public static int NewCurrencyItemId = 36; // 新货币
+
+  public static List<int> ItemsNotInPackage = new List<int>(){
+      DiamondItemId, FreeDiamondItemId, CoinItemId, VipExpItemId,
+      FreeVipExpItemId, HeroExpItemId, FriendPointId, RoleExpId,
+      NewCurrencyItemId  // ← 加这里
+  };
+
+  步骤 4:RoleData.cs 新增存储字段
+
+  文件:server/src/core/OpenCards.Core/ORM/RoleData.cs
+
+  [PersistField]
+  [SyncClientData(SyncClientRoleDataType.NewCurrency)]  // 如需客户端实时同步
+  public int NewCurrency;
+
+  步骤 5:0x35300.Logic.Role.cs 新增同步枚举值
+
+  文件:server/src/core/OpenCards.Core/Protocol/Client/0x35300.Logic.Role.cs
+
+  public enum SyncClientRoleDataType
+  {
+      // ... 现有值 ...
+      PresetHeroSelect2 = 45,
+      NewCurrency = 46,  // ← 新增
+  }
+
+  步骤 6:CurrencyModule.cs 新增增删查逻辑
+
+  文件:server/src/server/OpenCards.Server.Logic/Module/CurrencyModule.cs
+
+  在 OnEventAddItemImp 中加:
+  else if (itemInstance.ItemId == ItemDefines.NewCurrencyItemId)
+  {
+      if (ItemDefines.ItemsNotInPackage.Contains(itemInstance.ItemId))
+      {
+          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceID, itemInstance.ItemId);
+          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceName, itemName);
+          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceNum, roleModule.roleDataMapping.NewCurrency);
+          needDig = true;
+      }
+      roleModule.roleDataMapping.NewCurrency += itemInstance.Count;
+  }
+
+  在 OnEventRemoveItem 中加:
+  else if (itemid == ItemDefines.NewCurrencyItemId)
+  {
+      if (ItemDefines.ItemsNotInPackage.Contains(itemid))
+          beforeItemJObject.Add(EventTrackPropertyDefines.ResourceNum, roleDataMapping.NewCurrency);
+      if (roleModule.roleDataMapping.NewCurrency < count)
+          success.value = false;
+      else
+      {
+          roleModule.roleDataMapping.NewCurrency -= count;
+          success.value = true;
+      }
+  }
+
+  在 OnEventGetItemCount 中加:
+  else if (itemid == ItemDefines.NewCurrencyItemId)
+  {
+      count.value = roleDataMapping.NewCurrency;
+  }
+
+  ---
+  二、新增代币类道具(不进背包,但走通用背包存储)
+
+  代币比货币简单得多,只需要配置表 + 常量,不需要改 CurrencyModule 和 RoleData。
+
+  步骤 1:item.xlsx 新增一行
+
+  Id=37, ItemType=3(Token), ItemName=XX代币, Stack=999999, AutoUse=0
+
+  步骤 2:iteminstance.xlsx 新增捆绑实例
+
+  InstanceId=3700, ItemId=37, ItemType=1
+
+  步骤 3:ItemDefines.cs 新增 ID 常量(不加 ItemsNotInPackage)
+
+  public static int NewTokenItemId = 37; // 新代币(走背包存储)
+
+  步骤 4:业务代码中使用
+
+  发放:
+  DispatchEvent(EventDefines.EventAddItem,
+      新的ItemInstanceId,  // iteminstance.xlsx 中的 InstanceId
+      count,
+      AddItemReason.YourReason, 0, LogicUtils.FileLine);
+
+  查询数量:
+  Ref<long> count = new Ref<long>();
+  DispatchEvent(EventDefines.EventGetItemCount, ItemDefines.NewTokenItemId, count);
+
+  扣除:
+  Ref<bool> success = new Ref<bool>();
+  DispatchEvent(EventDefines.EventRemoveItem, ItemDefines.NewTokenItemId,
+      amount, RemoveItemReason.YourReason, 0, LogicUtils.FileLine, success);
+
+  ---
+  总结对比
+
+  ┌────────────────────────┬───────────────┬───────────┐
+  │         修改点         │    货币类     │  代币类   │
+  ├────────────────────────┼───────────────┼───────────┤
+  │ item.xlsx              │ ✅            │ ✅        │
+  ├────────────────────────┼───────────────┼───────────┤
+  │ iteminstance.xlsx      │ ✅            │ ✅        │
+  ├────────────────────────┼───────────────┼───────────┤
+  │ ItemDefines.cs 常量    │ ✅            │ ✅        │
+  ├────────────────────────┼───────────────┼───────────┤
+  │ ItemsNotInPackage      │ ✅ 必须加     │ ❌ 不加   │
+  ├────────────────────────┼───────────────┼───────────┤
+  │ RoleData.cs 新字段     │ ✅ 必须加     │ ❌ 不需要 │
+  ├────────────────────────┼───────────────┼───────────┤
+  │ SyncClientRoleDataType │ ✅ 新增枚举   │ ❌ 不需要 │
+  ├────────────────────────┼───────────────┼───────────┤
+  │ CurrencyModule.cs      │ ✅ 三处加逻辑 │ ❌ 不需要 │
+  └────────────────────────┴───────────────┴───────────┘