工程参考/回收系统

来自雪舞天龙知识库
跳到导航跳到搜索
回收系统
2 张表 · 214 条回收价 · 13 个结果状态

回收系统把背包里的物品按表价换成元宝:客户端选物品 → 服务端逐格报价 → 玩家确认 → 在同一个背包事务里扣物品、加元宝、记当日额度。

查一件物品能回收多少 → ItemRecycle.txt;改每日上限 → ItemRecycleSettings.txt;判断某件物品为什么收不了 → 看下面的资格判定。

先分清三个「回收」

名字里都有「回收」,但货币、配置表和代码路径完全不同,先对号入座再改表。

系统 玩家看到的效果 数据来源 入口
物品回收(本页) 背包物品按表价换成元宝 ItemRecycle.txt、ItemRecycleSettings.txt 客户端回收界面
玩家商店收购 把物品卖给店主换交子或金币 RecycleItem.txt、RecycleItemClass.txt、RecycleItemType.txt 玩家商店的收购菜单
物品箱回收次数 控制副本掉落箱允许被回收的次数 无配置表 副本脚本调 Lua 接口

RecycleItem.txt 那三张是商店收购白名单,和元宝回收没有任何关系;把物品加进那三张表不会让它变成可回收物品。

两张表

文件 结构 关键约束 当前样本
ItemRecycle.txt 2 列 INT:ItemID、YuanBao ItemID 必须严格递增;价格 1 到 2000000000 214 条,2 到 25000 元宝
ItemRecycleSettings.txt 2 列 INT,恰好 1 行:ID、DailyLimit ID 必须为 1;DailyLimit 1 到 2000000000 1 行,10000000

字段逐条解释见两张表的单独页面。两张表都放在 Public/Config/ 下,客户端不加载,价格与资格全部由服务端算完随包下发。

加载与生效

环节 实现 结果
打开文件 GameTable.cpp 里 SetStrictIntegers(true) 后 OpenFromTXT 非整数直接解析失败
校验并载入 ItemRecycle::Config().Load(recycleItems, recycleSettings) 任一约束不满足,返回 FALSE 并清空已读价格
失败后果 TableInit::Init() 返回 FALSE,调用方是 Server.cpp 的 Assert(ret) 表错是起不了服,不是关掉功能
  • 没有热重载入口:改完表要停服、替换实际 Public/Config/ 副本、重启 Server。
  • 没有功能开关:FeatureOpen.ini 里没有回收项,表加载成功系统就可用。
  • 没有 GM 指令:GMList.ini 里没有回收命令,验证只能走客户端界面。

一次回收的五个动作

动作 服务端做什么 可能的返回
Open 分配会话 context,把整包 240 个格子逐格描述(编号、数量、单价、不可回收原因)回给客户端 Catalog、Busy、Processing
Quote 按客户端选中的格子做快照与报价,算当日已用额度 Quoted、Changed、DailyExceeded、BalanceExceeded、Invalid
Commit 复核快照后原子结算 Succeeded、Failed、Expired、Changed、Busy
Query 查询某个请求的最终状态,用于断线或超时后确认 原状态、Processing、OutcomeUnknown
Close 清掉 context 与报价 不返回结果

时序约束:

  • 两次报价间隔小于 200 毫秒会被拒(Busy),防止连点刷价。
  • 报价有效期 30000 毫秒;超时提交返回 Expired。
  • 提交一旦匹配上报价,无论后续校验是否通过,这份报价都会被消费掉;重试必须重新报价。
  • context 绑定打开时的场景;换场景后心跳会清空 context,旧报价全部作废。

资格判定

整次回收的门禁

下列任一不满足,打开回收界面只会拿到 Busy:

条件 说明
角色与场景 玩家对象、Player、所在场景都存在,且处于可执行逻辑的状态
没有进行中的回收 同一个角色同时只能有一个回收会话
元宝入口空闲 元宝票没有待发邮件,CurrencyBlocked 为假
不在切场景 IsHaveChangeSceneFlag 为假
没有待领奖 IsHaveAskPrizeFlag 为假
不在摆摊 IsInStall 为假
交易盒空闲 m_ExchangBox.m_Status 为 EXCHANGE_NONE
没有特定锁位 m_BitLock.m_uLowFlags 的 0x800 位为 0
数据库不忙 不处于 DDZ 奖励结算的忙碌状态(HappyDDZRewardBusy 为假)

逐格判定

服务端对每个背包格按固定顺序判定,命中即停,这就是客户端提示「不可回收」的原因:

顺序 判定 返回原因 玩家看到的含义
1 物品类型是任务物品(ICLASS_TASKITEM) Quest 任务物品永不回收
2 物品已绑定 Bound 绑定物品不可回收
3 有密码锁或物品锁 Locked 先解锁
4 已加锁或正在交易 Occupied 物品被占用
5 表里查不到价格 NotConfigured 这件物品没配回收价
6 装备强化过或镶了宝石 保护位 需要玩家勾选确认后才报价
7 以上都不命中 Eligible 可以回收

价格查不到与价格为 0 是同一件事:服务端用 Config().Price() 取价,取不到就是 0,直接判 NotConfigured。但把价格写成 0 会让整表加载失败,所以要让某件物品不可回收只能删行。

价格、额度与上限

  • 单格收益 = 表价 × 该格数量。按格计算,不跨格合并,也不看物品在其它格子的堆叠。
  • 每日额度取 ItemRecycleSettings.txt 的第二列,按角色记在任务数据第 180、181 槽:180 存日序号,181 存当日已得元宝。
  • 日序号 = (Unix 秒 + 8 小时)÷ 86400,即东八区自然日。跨天不写回 0,而是「180 与今天不相等时,已用额度按 0 算」。
  • 报价与提交都校验三件事:当日额度、元宝上限 2000000000、以及两者相加不溢出。
  • 当前样本:额度 10000000(一千万)元宝每天,表内最高单价 25000 元宝,价格分布最集中的是 490 元宝 60 条、99 元宝 27 条、149 元宝 22 条。

额度是「每天最多换出多少元宝」,不是「每天能回收几次」。单价高的物品一两件就能打满,之后当天所有报价都会被判 DailyExceeded。

结算是一致性事务

  1. 报价时把每个选中格子的完整 _ITEM 结构存进事务,不是只存编号和数量。
  2. 提交时重新逐格比对:GUID、物品编号、可回收原因、保护确认,以及整块 _ITEM 的内存比较。任一不同判 Changed。
  3. 通过后走 CommitBagPlan,扣物品、SetYuanBao(..., YUANBAO_ITEM_RECYCLE, "物品回收")、写 180 与 181 三件事在同一个事务里;任一步失败整体回滚,元宝与额度一起还原。
  4. 成功后客户端收到「回收成功,获得N元宝。」的系统提示。

元宝流水里这笔记录的备注是「物品回收」,回滚是「物品回收回滚」,对账时按这两个备注筛。

报价里还记了配置版本(FNV-1a,含额度)和额度值,提交时再比一次,不一致判 Expired。当前没有热重载入口,同一进程内这两项不会变;这段比较是给将来加热重载或跨进程留的保险。

结果状态

状态 含义 客户端处理
Catalog 打开成功,回包带整包目录 渲染列表
Quoted 报价成功,回包带本次选中的格子与总收益 展示确认框
Processing 上一次请求还在结算 等待,可继续发 Query
Succeeded 结算成功 刷新背包与元宝
Failed 背包事务提交失败,已整体回滚 提示失败,可重新报价
Changed 选中物品或角色状态与报价时不一致 重新报价
Expired 报价超过 30 秒;或提交时配置版本、额度与报价时不一致(当前没有热重载,正常流程只会因超时触发) 重新报价
Busy 门禁不满足、请求过快或正在结算 稍后重试
DailyExceeded 本次收益超过当日剩余额度 提示今日额度已满
BalanceExceeded 元宝余额加上本次收益会超过上限 提示元宝已达上限
Invalid 事务结构校验失败 重新报价
OutcomeUnknown 找不到对应的请求(重登、请求号不匹配) 重新打开界面
Unavailable 结果结构未初始化 正常流程不会收到

和其余系统的互斥

回收会话进行中时,服务端会拦掉所有可能动到「已预留格子」的包:使用物品、拆分、背包换位、存仓与取仓、交易加物品、丢弃、卖给商店、摆摊上架;元宝相关包整类拦掉。被拦时提示「物品回收正在结算,相关物品暂不能操作。」

另外,元宝票有邮件在处理时整次回收直接判 Busy,避免两个元宝入口同时改余额。

常见改法

让一件物品可回收

在 ItemRecycle.txt 追加一行,物品编号必须大于当前最后一条(50613006),价格填 1 到 2000000000。停服重启后生效。

改价

只改第二列,保持第一列顺序不动。服务端不读客户端,改完重启即可,客户端不需要更新。

改每日额度

改 ItemRecycleSettings.txt 的第二列,第一列必须保持 1,且文件只能有一行数据。

只清某个角色的额度

把该角色任务数据第 180 槽改成 0 或昨天、181 槽改成 0。服务端读到时发现日序号不等于今天,就把已用额度按 0 算。

停用整个系统

服务端没有开关,也没有「空表即关闭」的写法:Load() 要求物品表至少一条记录,清空同样会让启动失败。要停用只能改客户端脚本,让它不再调用打开回收界面的方法。

容易踩的坑

是「严格递增」,不是「唯一」

Load() 逐行比较当前 ItemID 必须大于上一行。乱序、重复、倒序都会让整表清空并导致启动失败。插入新条目只能追加到末尾,或者整段重排。

表头与类型是硬约束

两张表都必须恰好 2 列且两列都是 INT;SetStrictIntegers(true) 下任何空值、非整数都会让解析失败。不要加第三列备注,注释行也无法保留。

settings 只能有一行

Load() 要求 GetRecordsNum() == 1,且第一列必须是 1。多写一行、把 ID 写成 2 都会让服务端起不来。

额度按角色,不按账号

额度存在角色的任务数据里,换角色重新计算;同账号不同角色各自有额度。清额度也要按角色清。

只认背包

回收遍历的是 0 到 239 号背包格(数组长度 240)。装备栏、银行、临时背包里的物品不会出现在回收列表里,也不会被回收。

回收日志不是流水

结算只写 CacheLog 的调试日志,没有可重放的账本。道具找回、玩家申诉不能拿这份日志当依据,要查元宝流水里的「物品回收」备注。

材料价与单测基线绑定

20500、20501、20502 三个家族(精铁、棉布、秘银)的 1 到 3 级,样本价是 8、40、200,与 ItemRecycle::BuiltinPrice() 的基准值一致。改这几条价格,Common/tests/ItemRecycle 的基线断言会失败,需要同步测试。

相关页面