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,167 @@
|
||||
# monster_card_system.gd —— Metin2 40250 怪物图鉴与百科全书系统 1:1
|
||||
# 对照 40250 服务端 monster_card.cpp, uimonstercard.py, char_battle.cpp
|
||||
class_name MonsterCardSystem
|
||||
extends RefCounted
|
||||
|
||||
signal card_collected(mob_vnum: int, mob_name: String, count: int)
|
||||
signal zone_completed(zone_name: String, bonuses: Dictionary)
|
||||
|
||||
# 区域图鉴数据库配置
|
||||
const BESTIARY_DATA: Dictionary = {
|
||||
"village": {
|
||||
"zone_name": "新手原野",
|
||||
"bonus": {"max_hp": 300},
|
||||
"monsters": {
|
||||
101: {
|
||||
"name": "野狼",
|
||||
"level": 1,
|
||||
"element": "无",
|
||||
"weakness": "物理",
|
||||
"drops": ["木剑+0", "小红药水"],
|
||||
"card_vnum": 70201
|
||||
},
|
||||
391: {
|
||||
"name": "白灵女将",
|
||||
"level": 25,
|
||||
"element": "风",
|
||||
"weakness": "火",
|
||||
"drops": ["狼剑+9", "红药水(中)"],
|
||||
"card_vnum": 70202
|
||||
}
|
||||
}
|
||||
},
|
||||
"desert": {
|
||||
"zone_name": "永壁沙漠",
|
||||
"bonus": {"monster_dmg_pct": 3},
|
||||
"monsters": {
|
||||
2108: {
|
||||
"name": "沙漠强盗",
|
||||
"level": 50,
|
||||
"element": "地",
|
||||
"weakness": "风",
|
||||
"drops": ["红药水(大)", "胡萝卜"],
|
||||
"card_vnum": 70203
|
||||
},
|
||||
2191: {
|
||||
"name": "巨型沙漠龟",
|
||||
"level": 67,
|
||||
"element": "地",
|
||||
"weakness": "风",
|
||||
"drops": ["沙漠龟宝箱", "灵魂之石"],
|
||||
"card_vnum": 70204
|
||||
}
|
||||
}
|
||||
},
|
||||
"catacomb": {
|
||||
"zone_name": "地下墓穴",
|
||||
"bonus": {"def": 50},
|
||||
"monsters": {
|
||||
2597: {
|
||||
"name": "塔耳塔洛斯",
|
||||
"level": 82,
|
||||
"element": "暗",
|
||||
"weakness": "圣",
|
||||
"drops": ["冷酷之石"],
|
||||
"card_vnum": 70205
|
||||
},
|
||||
2598: {
|
||||
"name": "冥王阿兹瑞尔",
|
||||
"level": 91,
|
||||
"element": "暗",
|
||||
"weakness": "圣",
|
||||
"drops": ["阿兹瑞尔宝箱", "泰坦之盾"],
|
||||
"card_vnum": 70206
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var collected_cards: Dictionary = {} # mob_vnum -> count
|
||||
var completed_zones: Dictionary = {} # zone_key -> bool
|
||||
|
||||
# 使用并收集怪物卡片
|
||||
func register_card(card_vnum: int) -> Dictionary:
|
||||
# 查找对应怪物
|
||||
var target_mob_vnum := -1
|
||||
var target_mob_name := ""
|
||||
var target_zone := ""
|
||||
|
||||
for z_key in BESTIARY_DATA.keys():
|
||||
var z_data = BESTIARY_DATA[z_key]
|
||||
for mv in z_data["monsters"].keys():
|
||||
var minfo = z_data["monsters"][mv]
|
||||
if minfo["card_vnum"] == card_vnum:
|
||||
target_mob_vnum = mv
|
||||
target_mob_name = minfo["name"]
|
||||
target_zone = z_key
|
||||
break
|
||||
if target_mob_vnum != -1:
|
||||
break
|
||||
|
||||
if target_mob_vnum == -1:
|
||||
return {"ok": false, "reason": "INVALID_CARD", "msg": "这不是有效的怪物图鉴卡片!"}
|
||||
|
||||
var new_count: int = int(collected_cards.get(target_mob_vnum, 0)) + 1
|
||||
collected_cards[target_mob_vnum] = new_count
|
||||
|
||||
card_collected.emit(target_mob_vnum, target_mob_name, new_count)
|
||||
|
||||
# 检查所属区域是否全部完成收集
|
||||
var is_newly_completed := false
|
||||
var zone_bonus: Dictionary = {}
|
||||
if not completed_zones.get(target_zone, false):
|
||||
var all_done := true
|
||||
var z_mobs = BESTIARY_DATA[target_zone]["monsters"]
|
||||
for m_id in z_mobs.keys():
|
||||
if int(collected_cards.get(m_id, 0)) < 1:
|
||||
all_done = false
|
||||
break
|
||||
|
||||
if all_done:
|
||||
completed_zones[target_zone] = true
|
||||
is_newly_completed = true
|
||||
zone_bonus = BESTIARY_DATA[target_zone]["bonus"]
|
||||
zone_completed.emit(BESTIARY_DATA[target_zone]["zone_name"], zone_bonus)
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"mob_vnum": target_mob_vnum,
|
||||
"mob_name": target_mob_name,
|
||||
"count": new_count,
|
||||
"zone_completed": is_newly_completed,
|
||||
"zone_bonus": zone_bonus,
|
||||
"msg": "已将【%s】录入怪物图鉴!" % target_mob_name
|
||||
}
|
||||
|
||||
# 查看图鉴词条 (未解锁则模糊显示)
|
||||
func get_monster_entry(mob_vnum: int) -> Dictionary:
|
||||
for z_data in BESTIARY_DATA.values():
|
||||
if z_data["monsters"].has(mob_vnum):
|
||||
var info: Dictionary = z_data["monsters"][mob_vnum].duplicate(true)
|
||||
var count: int = int(collected_cards.get(mob_vnum, 0))
|
||||
if count >= 1:
|
||||
info["is_unlocked"] = true
|
||||
info["collected_count"] = count
|
||||
return info
|
||||
else:
|
||||
return {
|
||||
"is_unlocked": false,
|
||||
"name": "???",
|
||||
"level": "??",
|
||||
"element": "??",
|
||||
"weakness": "??",
|
||||
"drops": ["未知"],
|
||||
"collected_count": 0
|
||||
}
|
||||
|
||||
return {"is_unlocked": false, "reason": "NOT_FOUND"}
|
||||
|
||||
# 汇总所有已完成区域图鉴的常驻被动战力加成
|
||||
func get_active_collection_bonuses() -> Dictionary:
|
||||
var total_bonuses: Dictionary = {}
|
||||
for z_key in completed_zones.keys():
|
||||
if completed_zones[z_key]:
|
||||
var b = BESTIARY_DATA[z_key]["bonus"]
|
||||
for k in b.keys():
|
||||
total_bonuses[k] = total_bonuses.get(k, 0) + int(b[k])
|
||||
return total_bonuses
|
||||
Reference in New Issue
Block a user