Files
mtgodot-poc/project/private_shop_system.gd
T
shenandshen 66d217b313 feat(client): 完成40250客户端核心功能1:1对齐与桥梁高度采样修复
- 桥梁与静态物体高度采样修复:
  - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
2026-09-19 08:51:25 -07:00

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", "")
}