找回密码
 register
搜索
查看: 25|回复: 0

洱海月 摆摊坐标、税率、地图限制分析

[复制链接]
  • 打卡等级:本地老炮
  • 打卡总天数:533
  • 打卡月天数:22
  • 打卡总奖励:530
  • 最近打卡:2026-06-24 01:45:59
Waylee 发表于 2026-6-16 14:34 | 显示全部楼层 |阅读模式 | Google Chrome | Windows 10

马上注册,查看网站隐藏内容!!

您需要 登录 才可以下载或查看,没有账号?register

×

结论摘要

当前服务端的普通角色摆摊规则主要由 configs/Stall_Info.txt 生效控制:

  • 摆摊场景:按 Stall_Info.txt摆摊场景 字段匹配当前 scene id。
  • 摆摊坐标:按 摊位定位点X_1/X_2/Z_1/Z_2 组成的矩形区域判断。
  • 摊位类型:摊位类型(0:money 1:yuanbao) 决定成交货币是金币还是元宝。
  • 摊位费:摊位费 在正式开摊时一次性从金币 money 扣除。
  • 交易税率:交易税率% 在成交时从卖家到账金额里扣除。

configs/scene/*.scn 里的 stallinfodata=xxx_StallInfo.stall 当前只被识别到入口,但没有真正读取对应 .stall 文件内容。代码里存在 lualib/stallinforeader.lua,但当前场景初始化没有调用它。因此在现有代码路径下,.stall 文件基本属于历史遗留/半废弃数据,不参与普通摆摊坐标、税率、地图限制的最终判断。

silindao.scn 当前没有 stallinfodata= 配置项,configs/scene/ 下也没有 silindao_StallInfo.stall。所以私林岛/会所这张图不是“配置了 .stall 但没加载”,而是场景配置本身没有挂摆摊格子文件。

配置数据来源

Stall_Info.txt

路径:

configs/Stall_Info.txt

读取函数:

lualib/cfghelper/shenbing_dfeng_tail.lua:114

读取逻辑:

function cfghelper:read_stall_info()
    local transferd = {}
    local read = require "txtreader".new()
    local configs = read:load("Stall_Info.txt")
    for _, conf in pairs(configs) do
        local id = conf["序号"]
        if id then
            local t = {}
            id = conf["摆摊场景"]
            t.ntype = conf["摊位类型(0:money 1:yuanbao)"]
            t.pos_tax = conf["摊位费"]
            t.trade_tax = conf["交易税率%"]
            t.pos_left = conf["摊位定位点X_1"]
            t.pos_right = conf["摊位定位点X_2"]
            t.pos_top = conf["摊位定位点Z_1"]
            t.pos_bottom = conf["摊位定位点Z_2"]
            if transferd[id] then
                table.insert(transferd[id], t)
            else
                transferd[id] = { t }
            end
        end
    end
    self.stall_info = transferd
end

它把配置转换成:

stall_info[scene_id] = {
    {
        ntype = 0 or 1,
        pos_tax = 摊位费,
        trade_tax = 交易税率,
        pos_left = X1,
        pos_right = X2,
        pos_top = Z1,
        pos_bottom = Z2,
    },
    ...
}

因此同一个场景可以配置多个摆摊矩形区。例如 摆摊场景 = 0 可以出现多行,表示同一张地图多个摆摊范围。

ConfigInfo.ini 的 PlayerShop

路径:

configs/ConfigInfo.ini

其中:

[PlayerShop]
MaxCount=256
PaymentPerHour=0

这块属于固定玩家商店系统 PlayerShop,不是普通角色原地摆摊的坐标/税率来源。普通摆摊走的是 scenecore/stall.lua,不要和 scenecore/player_shop.lua 混在一起判断。

普通摆摊协议入口

普通摆摊相关包定义在:

services/game/packet_parts/social_trade/stall_bbs.lua

关键包:

  • CGStallApply:申请摆摊,客户端到服务端,包体为空。
  • GCStallApply:服务端返回是否允许摆摊、摊位费、交易税率、是否元宝摊位。
  • CGStallEstablish:正式开摊,客户端到服务端,包体为空。
  • GCStallEstablish:正式开摊成功,包体为空。
  • CGStallBuy/GCStallBuy:摆摊购买,带物品/珍兽 GUID、serial、成交金额等结果字段。

请求入口在:

services/msgagent.lua:4371
services/msgagent.lua:4395
services/msgagent.lua:4444

调用链:

CGStallApply
  -> msgagent.request:CGStallApply()
  -> scene.char_stall()

CGStallEstablish
  -> msgagent.request:CGStallEstablish()
  -> scene.char_stall_establish()

CGStallBuy
  -> msgagent.request:CGStallBuy()
  -> scene.char_stall_buy()

CGStallApply 会检查:

  • 小密码是否解锁。
  • 账号是否交易限制。
  • 当前场景类型是否为普通场景 scene_type == 0

CGStallEstablish 会额外检查角色数据版本号,避免数据保存版本异常时开摊。

申请摆摊时如何解析地图、坐标、税率

核心函数:

services/scene/scenecore/stall.lua:86

核心逻辑:

local sceneId = self:get_id()
local stall_info = configenginer:get_config("stall_info")
stall_info = stall_info[sceneId]
if not stall_info then
    me:notify_tips("#{GCStallApplyHandler_Info_Stall_Err}")
    return
end

local pos = me:get_world_pos()
local me_posx = pos.x
local me_posz = pos.y

for _, info in ipairs(stall_info) do
    if me_posx >= info.pos_left
        and me_posx <= info.pos_right
        and me_posz >= info.pos_top
        and me_posz <= info.pos_bottom
    then
        pos_tax = info.pos_tax
        trade_tax = info.trade_tax
        is_yuanbao_stall = info.ntype == 1
        break
    end
end

判断顺序:

  1. 用当前场景 id 查 stall_info[sceneId]
  2. 如果当前场景在 Stall_Info.txt 没有任何配置,直接失败。
  3. 取玩家当前世界坐标 xy,这里代码把 pos.y 当作地图 Z 坐标使用。
  4. 遍历该场景下所有矩形区域。
  5. 命中第一个矩形后取得摊位费、交易税率、摊位类型。
  6. 如果没有命中任何矩形,返回摆摊错误。

申请成功后返回:

msg.is_can_stall = can_stall and 1 or 0
msg.pos_tax = pos_tax
msg.trade_tax = trade_tax
msg.is_yuanbao_stall = is_yuanbao_stall and 1 or 0

正式开摊时如何扣摊位费

核心函数:

services/scene/scenecore/stall.lua:158

正式开摊会重新计算一次场景和坐标,不信任申请阶段的客户端状态:

local sceneId = self:get_id()
local stall_info = configenginer:get_config("stall_info")
stall_info = stall_info[sceneId]
...
for _, info in ipairs(stall_info) do
    if me_posx >= info.pos_left
        and me_posx <= info.pos_right
        and me_posz >= info.pos_top
        and me_posz <= info.pos_bottom
    then
        pos_tax = info.pos_tax
        trade_tax = info.trade_tax
        is_yuanbao_stall = info.ntype == 1
        break
    end
end

正式开摊前要求状态必须是 STALL_READY

if stall_box:get_stall_status() ~= stall_box.STALL_STATUS.STALL_READY then
    msg.result = msg.STALL_MSG.ERR_ILLEGAL
    self:send2client(me, msg)
    stall_box:clean_up()
    return
end

扣摊位费:

if me:get_money() < pos_tax then
    msg.result = msg.STALL_MSG.ERR_NOT_ENOUGH_MONEY_TO_OPEN
    self:send2client(me, msg)
    return
end
me:set_money(me:get_money() - pos_tax, "摆摊交易-税费")

注意:这里无论 ntype 是金币摊还是元宝摊,开摊费都扣 moneyntype == 1 只被保存到 stall_box:set_is_yuanbao_stall(is_yuanbao_stall),后续交易才决定买卖货币。

开摊成功后写入摊位状态:

stall_box:set_stall_status(stall_box.STALL_STATUS.STALL_OPEN)
stall_box:set_stall_is_open(true)
stall_box:set_pos_tax(pos_tax)
stall_box:set_trade_tax(trade_tax)
stall_box:set_is_yuanbao_stall(is_yuanbao_stall)

并占用当前位置:

local world_pos = me:get_world_pos()
self:set_pos_can_stall(world_pos.x, world_pos.y, false)
stall_box:set_stall_pos(world_pos.x, world_pos.y)
me:get_ai():change_state("stall")

成交时如何使用摊位类型和交易税

核心函数:

services/scene/scenecore/stall.lua:551

物品和珍兽购买逻辑基本一致。

先从摊位取价格:

local need_money = stall_box:get_price_by_index(index)

按摊位类型选择买家余额:

if stall_box:get_is_yuanbao_stall() then
    my_money = obj:get_yuanbao()
else
    my_money = obj:get_money()
end

成交后按摊位类型扣买家货币:

if stall_box:get_is_yuanbao_stall() then
    obj:set_yuanbao(obj:get_yuanbao() - need_money, "元宝摊位-购买消费", ...)
else
    obj:set_money(obj:get_money() - need_money, "金币摊位-购买消费", ...)
end

卖家到账金额按交易税率计算:

local trade_tax = stall_box:get_trade_tax()
trade_tax = trade_tax > 100 and 100 or trade_tax

local profit = need_money * (1 - trade_tax / 100)
profit = math.floor(profit)

再按摊位类型给卖家加货币:

if stall_box:get_is_yuanbao_stall() then
    stall_human:set_yuanbao(stall_human:get_yuanbao() + profit, "元宝摊位-卖出道具", ...)
else
    stall_human:set_money(stall_human:get_money() + profit, "金币摊位-卖出道具", ...)
end

因此税率实际效果是:

卖家到账 = floor(售价 * (1 - 交易税率 / 100))
系统税收 = 售价 - 卖家到账

地图限制的实际规则

普通摆摊能否开始,至少需要同时满足:

  1. 当前场景类型是普通场景,scene_type == 0
  2. 当前场景 id 在 Stall_Info.txt 中存在配置。
  3. 当前坐标落在该场景某条配置的矩形范围内。
  4. 角色等级大于等于 30。
  5. 不在双人骑乘等禁止状态。
  6. 账号没有交易限制。
  7. 小密码已解锁。
  8. 道具限制交易检查通过。
  9. 申请摆摊后正式开摊时,状态仍为 STALL_READY
  10. 金币足够支付 pos_tax

其中第 2、3 点是地图和坐标限制的核心。

.stall 文件当前是否作废

代码中确实存在 .stall 文件读取器:

lualib/stallinforeader.lua

设计上的二进制格式:

stall_info.ver = is:readint()
stall_info.width = is:readint()
stall_info.height = is:readint()
for y = 1, stall_info.height do
    for x = 1, stall_info.width do
        inf.can_stall = is:readuchar() == 1
        inf.trade_tax = is:readuchar()
        inf.pos_tax = is:readint()
        inf.ext = is:readuchar()
        stall_info.map[x][y] = inf
    end
end

如果这条链路完整生效,它应该提供逐格的:

  • 是否可摆摊 can_stall
  • 交易税率 trade_tax
  • 摊位费 pos_tax
  • 扩展字段 ext

但当前场景初始化不是这样写的。

场景 .scn 解析到 stallinfodata 后,调用:

services/scene/scenecore.lua:417
if self.scn.System.stallinfodata then
    self:init_stall_info(self.scn.System.stallinfodata)
end

而实际初始化函数:

services/scene/scenecore/scene_state.lua:172
function scenecore:init_stall_info(stallinfofile)
    self.stallinfo = { map = {} }
end

这里没有使用传入的 stallinfofile,也没有:

require "stallinforeader"

更没有:

stallinforeader:load(stallinfofile)

所以结论是:

当前普通摆摊运行链路下,*.stall 文件没有被加载。

它不是文件不存在意义上的废弃,而是代码路径中保留了接口和读取器,但当前生效初始化把它绕过去了。实际效果等同于废弃。

为什么有些 .scn 仍然写 stallinfodata

例如:

configs/scene/luoyang.scn

有:

stallinfodata=luoyang_StallInfo.stall

这会触发:

self:init_stall_info("luoyang_StallInfo.stall")

但因为 init_stall_info 只做:

self.stallinfo = { map = {} }

所以文件名只起到了“让该场景拥有 self.stallinfo 空表”的效果,没有读取文件内容。

随后 char_stall 中有一段兜底:

local m = self.stallinfo.map
local world_pos = me:get_world_pos()
local x = math.ceil(world_pos.x)
local y = math.ceil(world_pos.y)
m[x] = m[x] or {}
m[x][y] = m[x][y] or { can_stall = true, trade_tax = trade_tax, pos_tax = pos_tax }
local d = m[x][y]
if d.can_stall then
    can_stall = true
    trade_tax = d.trade_tax
    pos_tax = d.pos_tax
end

由于 self.stallinfo.map 是空表,第一次申请某坐标时会自动补:

can_stall = true
trade_tax = Stall_Info.txt 命中的 trade_tax
pos_tax = Stall_Info.txt 命中的 pos_tax

也就是说,当前真正限制坐标的是 Stall_Info.txt 的矩形。.stall 逐格限制没有生效。

silindao.scn / 私林岛 / 会所的情况

当前文件:

configs/scene/silindao.scn

内容:

[System]
navmapname=silindao.nav
monsterfile=silindao_monster.ini
patrolpoint=silindao_patrolpoint.ini
growpointdata=silindao_growpoint.txt
growpointsetup=silindao_growpointsetup.txt
eventfile=silindao_area.ini

没有:

stallinfodata=...

同时 configs/scene/ 下也没有发现:

silindao_StallInfo.stall

所以 silindao.scn 当前不会调用 init_stall_info,场景对象上不会因为 .scn 配置而初始化 self.stallinfo

再结合普通摆摊申请逻辑:

local stall_info = configenginer:get_config("stall_info")
stall_info = stall_info[sceneId]
if not stall_info then
    me:notify_tips("#{GCStallApplyHandler_Info_Stall_Err}")
    return
end

如果 Stall_Info.txt 中没有私林岛对应 scene id,那么私林岛无法摆摊。

如果 Stall_Info.txt 中有私林岛对应 scene id,但 silindao.scn 没有 stallinfodata,当前代码在命中矩形后仍然会进入:

if self.stallinfo then
    ...
    stall_box:set_stall_status(STALL_READY)
end

由于 self.stallinfo 不存在,can_stall 不会被置为 true,STALL_READY 也不会设置。申请返回会带 is_can_stall = 0,正式开摊时也会因为状态不是 STALL_READY 报非法。

因此私林岛/会所想允许普通摆摊,当前至少需要:

  1. Stall_Info.txt 增加对应 scene id 的矩形区域。
  2. silindao.scn 增加任意 stallinfodata=...,让 self.stallinfo 被初始化。
  3. 如果保持当前代码不改,.stall 文件内容本身仍不会被读取;文件是否真实存在,对当前 init_stall_info 不产生影响。

如果要恢复 .stall 逐格限制,则需要修改 init_stall_info,真正调用 stallinforeader 并处理文件缺失、旧数据兼容和税率/摊位费来源优先级。

风险点

1. .scn 有 stallinfodata 但文件内容不生效

运维或策划可能以为修改 xxx_StallInfo.stall 会改变摆摊格子、摊位费、税率,但当前服务端不会读取这个文件。

2. Stall_Info.txt 是真正权威

当前实际权威是 Stall_Info.txt。如果只改 .stall,线上行为不会变。

3. stallinfodata 目前仍影响是否有 self.stallinfo

虽然 .stall 内容不生效,但 .scn 里有没有 stallinfodata 会影响 self.stallinfo 是否初始化。没有 self.stallinfo 时,即使 Stall_Info.txt 命中矩形,也无法把角色置为 STALL_READY

所以不能简单说 .scnstallinfodata 完全没用。更精确的说法是:

stallinfodata 这个配置项仍影响摆摊流程开关;
stallinfodata 指向的 *.stall 文件内容当前不生效。

4. 元宝摊位的开摊费仍扣金币

ntype == 1 只控制成交货币。正式开摊时 pos_tax 统一扣 money。如果设计期望元宝摊位开摊也扣元宝,需要另行调整。

排查或修改建议

只想调整某地图能否摆摊

优先查:

configs/Stall_Info.txt
configs/scene/目标地图.scn

确认:

  1. Stall_Info.txt 是否有对应 scene id。
  2. 玩家坐标是否落入矩形范围。
  3. .scn 是否存在 stallinfodata=,用来初始化 self.stallinfo

只想调整税率或摊位费

改:

configs/Stall_Info.txt

当前不要改 .stall,因为不生效。

您需要登录后才可以回帖 登录 | register

本版积分规则

QQ|雪舞知识库 ( 浙ICP备15015590号-1 | 萌ICP备20232229号|浙公网安备33048102000118号 )|天天打卡

GMT+8, 2026-6-24 04:30 , Processed in 0.064175 second(s), 25 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表