Files
mtgodot-poc/project/mall_affect_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

195 lines
6.3 KiB
GDScript

# mall_affect_system.gd —— Metin2 40250 商城常驻 Affect 增益与双倍特权系统 1:1
# 对照 40250 服务端 affect.h, char_affect.cpp 及客户端 uiaffectshower.py:647-670
class_name MallAffectSystem
extends RefCounted
signal affect_added(affect_type: int, data: Dictionary)
signal affect_removed(affect_type: int)
# 40250 官方常驻增益 Affect 常量 (uiaffectshower.py)
const AFFECT_EXP_BONUS := 500
const AFFECT_ITEM_BONUS := 501
const AFFECT_SAFEBOX := 502
const AFFECT_AUTOLOOT := 503
const AFFECT_FISH_MIND := 504
const AFFECT_MARRIAGE_FAST := 505
const AFFECT_GOLD_BONUS := 506
const AFFECT_NO_DEATH_PENALTY := 507
const AFFECT_AUTO_HP_RECOVERY := 508
const AFFECT_AUTO_SP_RECOVERY := 509
# 官方图标资源映射 (uiaffectshower.py:647-670)
const AFFECT_INFO: Dictionary = {
AFFECT_EXP_BONUS: {
"name": "经验值获取提升",
"icon_sub": "d:/ymir work/ui/skill/common/affect/exp_bonus.sub",
"desc": "打怪获得的经验值提升!"
},
AFFECT_ITEM_BONUS: {
"name": "盗贼手套掉宝增益",
"icon_sub": "d:/ymir work/ui/skill/common/affect/item_bonus.sub",
"desc": "击杀怪物掉落物品的概率翻倍!"
},
AFFECT_SAFEBOX: {
"name": "仓库空间扩展",
"icon_sub": "d:/ymir work/ui/skill/common/affect/safebox.sub",
"desc": "享有二号与三号高级扩展储物箱!"
},
AFFECT_AUTOLOOT: {
"name": "自动拾取金币",
"icon_sub": "d:/ymir work/ui/skill/common/affect/autoloot.sub",
"desc": "击杀怪物掉落的金币自动拾取至钱包。"
},
AFFECT_FISH_MIND: {
"name": "渔神专注",
"icon_sub": "d:/ymir work/ui/skill/common/affect/fishmind.sub",
"desc": "钓鱼成功率获得大幅提升。"
},
AFFECT_MARRIAGE_FAST: {
"name": "爱神羽毛",
"icon_sub": "d:/ymir work/ui/skill/common/affect/marriage_fast.sub",
"desc": "同伴好感度积累速度倍增。"
},
AFFECT_GOLD_BONUS: {
"name": "幸运金币",
"icon_sub": "d:/ymir work/ui/skill/common/affect/gold_bonus.sub",
"desc": "怪物掉落的金币数量翻倍!"
},
AFFECT_AUTO_HP_RECOVERY: {
"name": "太阳仙丹",
"icon_sub": "d:/ymir work/ui/pattern/auto_hpgauge/05.dds",
"desc": "生命值低于阈值时自动回复。"
},
AFFECT_AUTO_SP_RECOVERY: {
"name": "月亮仙丹",
"icon_sub": "d:/ymir work/ui/pattern/auto_spgauge/05.dds",
"desc": "法力值低于阈值时自动回复。"
}
}
# 经典商城道具 Vnum 映射
const MALL_ITEM_TABLE: Dictionary = {
70005: {"affect": AFFECT_EXP_BONUS, "duration": 3600.0, "value": 50}, # 经验戒指 1小时 (+50%)
72001: {"affect": AFFECT_EXP_BONUS, "duration": 604800.0, "value": 100}, # 经验戒指 7天 (+100%)
70043: {"affect": AFFECT_ITEM_BONUS, "duration": 7200.0, "value": 100}, # 盗贼手套 2小时 (2x)
72004: {"affect": AFFECT_ITEM_BONUS, "duration": 604800.0, "value": 100}, # 盗贼手套 7天 (2x)
70044: {"affect": AFFECT_GOLD_BONUS, "duration": 10800.0, "value": 100}, # 幸运金币 3小时 (2x)
72007: {"affect": AFFECT_GOLD_BONUS, "duration": 604800.0, "value": 100}, # 幸运金币 7天 (2x)
72006: {"affect": AFFECT_SAFEBOX, "duration": 604800.0, "value": 1}, # 仓库扩展卷 7天
71069: {"affect": AFFECT_MARRIAGE_FAST, "duration": 604800.0, "value": 1} # 爱神羽毛 7天
}
# 当前生效的增益字典: affect_type -> {name, icon_sub, value, duration, total_duration, desc}
var active_affects: Dictionary = {}
func use_mall_item(item: Dictionary) -> Dictionary:
var vnum: int = int(item.get("vnum", 0))
if not MALL_ITEM_TABLE.has(vnum):
return {"ok": false, "reason": "NOT_MALL_ITEM", "msg": "这不是商城特权道具!"}
var cfg: Dictionary = MALL_ITEM_TABLE[vnum]
var atype: int = int(cfg["affect"])
var dur: float = float(cfg["duration"])
var val: int = int(cfg["value"])
add_affect(atype, dur, val)
var info: Dictionary = AFFECT_INFO.get(atype, {})
return {
"ok": true,
"affect_type": atype,
"name": info.get("name", "特权增益"),
"duration": dur,
"value": val,
"msg": "已成功激活【%s】特权!" % info.get("name", "")
}
func add_affect(atype: int, duration: float, value: int = 0) -> void:
var info: Dictionary = AFFECT_INFO.get(atype, {
"name": "未知特权",
"icon_sub": "",
"desc": ""
})
var data: Dictionary = {
"name": info["name"],
"icon_sub": info["icon_sub"],
"value": value,
"duration": duration,
"total_duration": duration,
"desc": info["desc"]
}
active_affects[atype] = data
affect_added.emit(atype, data)
func remove_affect(atype: int) -> void:
if active_affects.has(atype):
active_affects.erase(atype)
affect_removed.emit(atype)
func has_affect(atype: int) -> bool:
return active_affects.has(atype)
func get_affect(atype: int) -> Dictionary:
return active_affects.get(atype, {})
# 经验倍率加成 (char.cpp:PointChange)
func get_exp_multiplier() -> float:
var mult: float = 1.0
if has_affect(AFFECT_EXP_BONUS):
var bonus_pct: float = float(active_affects[AFFECT_EXP_BONUS].get("value", 50))
mult += bonus_pct / 100.0
return mult
# 物品掉率倍率加成 (item.cpp:Drop)
func get_drop_multiplier() -> float:
if has_affect(AFFECT_ITEM_BONUS):
return 2.0
return 1.0
# 金币掉落倍率加成 (char_battle.cpp:GiveGold)
func get_gold_multiplier() -> float:
if has_affect(AFFECT_GOLD_BONUS):
return 2.0
return 1.0
# 仓库扩充权限 (safebox.cpp)
func has_safebox_expansion() -> bool:
return has_affect(AFFECT_SAFEBOX)
# 逐帧衰减更新
func update(dt: float) -> Array[int]:
var expired: Array[int] = []
for atype in active_affects.keys():
var data: Dictionary = active_affects[atype]
data["duration"] = maxf(0.0, float(data["duration"]) - dt)
if float(data["duration"]) <= 0.0:
expired.append(atype)
for atype in expired:
remove_affect(atype)
return expired
# 获取供 HUD 渲染的图标列表
func get_hud_icons() -> Array[Dictionary]:
var icons: Array[Dictionary] = []
for atype in active_affects:
var data: Dictionary = active_affects[atype]
var total_sec := int(ceil(float(data["duration"])))
var hours := total_sec / 3600
var mins := (total_sec % 3600) / 60
var secs := total_sec % 60
var time_str := "%02d:%02d:%02d" % [hours, mins, secs] if hours > 0 else "%02d:%02d" % [mins, secs]
icons.append({
"affect_type": atype,
"name": data["name"],
"icon_sub": data["icon_sub"],
"time_remaining_str": time_str,
"remaining_seconds": float(data["duration"]),
"desc": data["desc"]
})
return icons