- 桥梁与静态物体高度采样修复: - 严格对齐 40250 CMapOutdoor::GetHeight 与 CAttributeInstance::GetHeight - 解析 .mdatr 中的 AttributeHeight 网格,使用 is_in_triangle_2d 准确计算桥面多边形平面方程 - sample_height 查询邻近区块并返回 fMAX(fObjectHeight, fTerrainHeight),彻底解决走上桥面穿透掉入水底/河床的问题 - 新增 test_bridge_height_parity.gd 自动化对拍测试 - 40250 怪物击杀经验动效: - 1:1 实现 FLY_EXP(0) / FLY_HP / FLY_SP 粒子轨迹与爆炸吸附 - 40250 客户端全系统功能对齐(Batches 1-31): - 包含公会、交易、骑乘、变身、钓鱼、采矿、商城、信件、结婚、地牢等 134 套对拍系统与自动化回归测试 - 文档沉淀: - 新增 docs/CLIENT-PARITY-AUDIT-AND-FIX-GUIDE.md 客户端对拍缺陷发现与修复工程指南
89 lines
3.5 KiB
GDScript
89 lines
3.5 KiB
GDScript
# private_shop_system.gd —— 个人私有商店摆摊系统(40250 官方 1:1 对齐)
|
|
# 对齐源码:
|
|
# - 40250 server/game/src/char_item.cpp (ITEM_BUNDLE 50200, OpenMyShop)
|
|
# - 40250 root/uiprivateshopbuilder.py (PrivateShopBuilder)
|
|
# - 40250 uiscript/privateshopbuilder.py (40 格摊位格)
|
|
class_name PrivateShopSystem
|
|
extends RefCounted
|
|
|
|
const VNUM_ITEM_BUNDLE := 50200 # 束发包袱皮
|
|
const VNUM_SHOP_HOST_STALL := 30000 # 摆摊木箱/桌子外观模型 Vnum
|
|
const PRIVATE_SHOP_SLOTS_MAX := 39 # 官方上限 39 件商品
|
|
const SIGN_LENGTH_MAX := 25 # 摊位招牌字数上限
|
|
|
|
## 检查物品是否为包袱皮
|
|
static func is_bundle(vnum: int) -> bool:
|
|
return vnum == VNUM_ITEM_BUNDLE
|
|
|
|
## 校验是否能开店 (40250 官方前置条件:非战斗、非骑乘、非死亡)
|
|
static func can_open_private_shop(is_fighting: bool, is_mounted: bool, is_dead: bool) -> Dictionary:
|
|
if is_dead:
|
|
return {"ok": false, "code": "CANNOT_DEAD", "msg": "死亡状态下无法摆摊!"}
|
|
if is_mounted:
|
|
return {"ok": false, "code": "CANNOT_MOUNTED", "msg": "骑乘状态下无法摆摊,请先下马!"}
|
|
if is_fighting:
|
|
return {"ok": false, "code": "CANNOT_FIGHTING", "msg": "战斗中无法摆摊!"}
|
|
return {"ok": true}
|
|
|
|
## 校验摊位招牌名称
|
|
static func validate_sign(sign_text: String) -> Dictionary:
|
|
var trimmed := sign_text.strip_edges()
|
|
if trimmed.is_empty():
|
|
return {"ok": false, "code": "SIGN_EMPTY", "msg": "请输入有效的摊位名称!"}
|
|
if trimmed.length() > SIGN_LENGTH_MAX:
|
|
return {"ok": false, "code": "SIGN_TOO_LONG", "msg": "摊位名称不能超过 25 个字符!"}
|
|
return {"ok": true, "sign": trimmed}
|
|
|
|
## 验证并将物品上架到摊位
|
|
static func add_item_to_stock(stock_dict: Dictionary, slot_idx: int, inv_item: Dictionary, price: int) -> Dictionary:
|
|
if slot_idx < 0 or slot_idx >= PRIVATE_SHOP_SLOTS_MAX:
|
|
return {"ok": false, "code": "INVALID_SLOT", "msg": "无效的摊位格子!"}
|
|
if price <= 0:
|
|
return {"ok": false, "code": "INVALID_PRICE", "msg": "出售价格必须大于 0 金币!"}
|
|
if stock_dict.size() >= PRIVATE_SHOP_SLOTS_MAX and not stock_dict.has(slot_idx):
|
|
return {"ok": false, "code": "SHOP_FULL", "msg": "摊位已达到最大商品容量 (39 件)!"}
|
|
|
|
var cell := int(inv_item.get("cell", -1))
|
|
var vnum := int(inv_item.get("vnum", 0))
|
|
var count := int(inv_item.get("count", 1))
|
|
|
|
stock_dict[slot_idx] = {
|
|
"slot": slot_idx,
|
|
"cell": cell,
|
|
"vnum": vnum,
|
|
"count": count,
|
|
"price": price,
|
|
}
|
|
return {
|
|
"ok": true,
|
|
"code": "SUCCESS",
|
|
"slot": slot_idx,
|
|
"price": price,
|
|
"msg": "商品成功上架到摊位第 %d 格,单价 %d 金币。" % [slot_idx + 1, price]
|
|
}
|
|
|
|
## 撤下摊位商品
|
|
static func remove_item_from_stock(stock_dict: Dictionary, slot_idx: int) -> bool:
|
|
if stock_dict.has(slot_idx):
|
|
stock_dict.erase(slot_idx)
|
|
return true
|
|
return false
|
|
|
|
## 开市创建摆摊实体
|
|
static func build_private_shop(sign_text: String, stock_dict: Dictionary, stall_pos: Vector3) -> Dictionary:
|
|
var v_sign := validate_sign(sign_text)
|
|
if not bool(v_sign.get("ok", false)):
|
|
return v_sign
|
|
if stock_dict.is_empty():
|
|
return {"ok": false, "code": "NO_ITEMS", "msg": "摊位上没有任何商品,无法开市!"}
|
|
|
|
return {
|
|
"ok": true,
|
|
"code": "SHOP_OPENED",
|
|
"stall_vnum": VNUM_SHOP_HOST_STALL,
|
|
"sign": v_sign.get("sign", ""),
|
|
"items_count": stock_dict.size(),
|
|
"position": stall_pos,
|
|
"msg": "【%s】正式开市!摊位已在当前位置建立。" % v_sign.get("sign", "")
|
|
}
|