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 客户端对拍缺陷发现与修复工程指南
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user