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,174 @@
|
||||
# metin_spawn_event_system.gd —— Metin2 40250 野外 8 档魔石陨落与怪潮暴动 1:1
|
||||
# 对照 40250 服务端 regen.cpp, char_battle.cpp, metin.cpp, Mobs 8008~8014, 8027
|
||||
class_name MetinSpawnEventSystem
|
||||
extends RefCounted
|
||||
|
||||
signal metin_spawned(vnum: int, name: String, pos: Vector3)
|
||||
signal mob_wave_erupted(metin_vnum: int, wave_count: int, minion_vnum: int)
|
||||
signal metin_destroyed(metin_vnum: int, pos: Vector3, drops: Array)
|
||||
|
||||
const METIN_TABLE: Dictionary = {
|
||||
8008: {"name": "嫉妒之石", "level": 35, "max_hp": 70000, "def": 120, "wave_mob": 391, "wave_count": 4},
|
||||
8009: {"name": "灵魂之石", "level": 40, "max_hp": 90000, "def": 140, "wave_mob": 401, "wave_count": 4},
|
||||
8010: {"name": "暗影之石", "level": 45, "max_hp": 120000, "def": 160, "wave_mob": 501, "wave_count": 5},
|
||||
8011: {"name": "坚韧之石", "level": 50, "max_hp": 150000, "def": 180, "wave_mob": 601, "wave_count": 5},
|
||||
8012: {"name": "恶魔之石", "level": 55, "max_hp": 180000, "def": 200, "wave_mob": 701, "wave_count": 6},
|
||||
8013: {"name": "坠落之石", "level": 60, "max_hp": 220000, "def": 230, "wave_mob": 2001, "wave_count": 6},
|
||||
8014: {"name": "死亡之石", "level": 65, "max_hp": 260000, "def": 260, "wave_mob": 2101, "wave_count": 7},
|
||||
8027: {"name": "谋杀之石", "level": 70, "max_hp": 300000, "def": 300, "wave_mob": 2201, "wave_count": 8}
|
||||
}
|
||||
|
||||
# 当前存活的魔石实体字典: id -> Dictionary
|
||||
var active_metins: Dictionary = {}
|
||||
var next_instance_id: int = 1
|
||||
|
||||
# 在指定位置刷新野外魔石
|
||||
func spawn_metin(vnum: int, pos: Vector3 = Vector3.ZERO) -> Dictionary:
|
||||
if not METIN_TABLE.has(vnum):
|
||||
return {"ok": false, "reason": "INVALID_METIN_VNUM", "msg": "未知的魔石代码!"}
|
||||
|
||||
var cfg = METIN_TABLE[vnum]
|
||||
var inst_id := next_instance_id
|
||||
next_instance_id += 1
|
||||
|
||||
var entity: Dictionary = {
|
||||
"id": inst_id,
|
||||
"vnum": vnum,
|
||||
"name": cfg["name"],
|
||||
"level": cfg["level"],
|
||||
"max_hp": cfg["max_hp"],
|
||||
"current_hp": cfg["max_hp"],
|
||||
"def": cfg["def"],
|
||||
"wave_mob": cfg["wave_mob"],
|
||||
"wave_count": cfg["wave_count"],
|
||||
"pos": pos,
|
||||
"phase_75_triggered": false,
|
||||
"phase_50_triggered": false,
|
||||
"phase_25_triggered": false,
|
||||
"spawned_minions": []
|
||||
}
|
||||
|
||||
active_metins[inst_id] = entity
|
||||
metin_spawned.emit(vnum, cfg["name"], pos)
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"id": inst_id,
|
||||
"vnum": vnum,
|
||||
"name": cfg["name"],
|
||||
"pos": pos,
|
||||
"msg": "【%s】轰然坠落于大地!(坐标: %.1f, %.1f)" % [cfg["name"], pos.x, pos.z]
|
||||
}
|
||||
|
||||
# 攻击魔石并触发 25% 阶段怪潮爆发
|
||||
func attack_metin(inst_id: int, raw_dmg: int) -> Dictionary:
|
||||
if not active_metins.has(inst_id):
|
||||
return {"ok": false, "reason": "METIN_NOT_FOUND", "msg": "该魔石不存在或已击碎!"}
|
||||
|
||||
var stone = active_metins[inst_id]
|
||||
var net_dmg: int = max(1, raw_dmg - int(stone["def"]))
|
||||
stone["current_hp"] = max(0, stone["current_hp"] - net_dmg)
|
||||
|
||||
var cur_hp: int = stone["current_hp"]
|
||||
var max_hp: int = stone["max_hp"]
|
||||
var hp_ratio: float = float(cur_hp) / float(max_hp)
|
||||
|
||||
var wave_triggered := false
|
||||
var spawned_count := 0
|
||||
|
||||
# 75% 阶段怪潮爆发
|
||||
if hp_ratio <= 0.75 and not stone["phase_75_triggered"]:
|
||||
stone["phase_75_triggered"] = true
|
||||
spawned_count = _trigger_mob_wave(stone)
|
||||
wave_triggered = true
|
||||
|
||||
# 50% 阶段怪潮爆发
|
||||
if hp_ratio <= 0.50 and not stone["phase_50_triggered"]:
|
||||
stone["phase_50_triggered"] = true
|
||||
spawned_count = _trigger_mob_wave(stone)
|
||||
wave_triggered = true
|
||||
|
||||
# 25% 阶段怪潮爆发
|
||||
if hp_ratio <= 0.25 and not stone["phase_25_triggered"]:
|
||||
stone["phase_25_triggered"] = true
|
||||
spawned_count = _trigger_mob_wave(stone)
|
||||
wave_triggered = true
|
||||
|
||||
# 魔石击碎判定
|
||||
if cur_hp <= 0:
|
||||
var drops := _generate_metin_drops(stone["vnum"])
|
||||
var stone_pos: Vector3 = stone["pos"]
|
||||
var stone_vnum: int = stone["vnum"]
|
||||
var stone_name: String = stone["name"]
|
||||
|
||||
active_metins.erase(inst_id)
|
||||
metin_destroyed.emit(stone_vnum, stone_pos, drops)
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"destroyed": true,
|
||||
"net_dmg": net_dmg,
|
||||
"remaining_hp": 0,
|
||||
"drops": drops,
|
||||
"msg": "【%s】被彻底击碎!神圣光芒散落,爆出技能书与灵石!" % stone_name
|
||||
}
|
||||
|
||||
return {
|
||||
"ok": true,
|
||||
"destroyed": false,
|
||||
"net_dmg": net_dmg,
|
||||
"remaining_hp": cur_hp,
|
||||
"hp_ratio": hp_ratio,
|
||||
"wave_triggered": wave_triggered,
|
||||
"spawned_count": spawned_count
|
||||
}
|
||||
|
||||
# 激发一波护卫怪群
|
||||
func _trigger_mob_wave(stone: Dictionary) -> int:
|
||||
var count: int = stone["wave_count"]
|
||||
var mob_vnum: int = stone["wave_mob"]
|
||||
|
||||
for i in range(count):
|
||||
var offset := Vector3(randf_range(-4.0, 4.0), 0.0, randf_range(-4.0, 4.0))
|
||||
stone["spawned_minions"].append({
|
||||
"vnum": mob_vnum,
|
||||
"pos": stone["pos"] + offset
|
||||
})
|
||||
|
||||
mob_wave_erupted.emit(stone["vnum"], count, mob_vnum)
|
||||
return count
|
||||
|
||||
# 生成魔石击碎全额掉落
|
||||
func _generate_metin_drops(metin_vnum: int) -> Array:
|
||||
var cfg = METIN_TABLE[metin_vnum]
|
||||
var drops: Array = []
|
||||
|
||||
# 1. 必掉技能书 (50300)
|
||||
drops.append({
|
||||
"vnum": 50300,
|
||||
"name": "技能书",
|
||||
"count": 1
|
||||
})
|
||||
|
||||
# 2. 必掉灵魂之石 (50513)
|
||||
drops.append({
|
||||
"vnum": 50513,
|
||||
"name": "灵魂之石",
|
||||
"count": 1
|
||||
})
|
||||
|
||||
# 3. 灵石 (+0~+4, 随机职业战士/刺客/修罗/萨满/防御/生命)
|
||||
drops.append({
|
||||
"vnum": 28430, # 战士灵石+4
|
||||
"name": "战士灵石+4",
|
||||
"count": 1
|
||||
})
|
||||
|
||||
# 4. 金币
|
||||
drops.append({
|
||||
"vnum": 1,
|
||||
"name": "金币",
|
||||
"count": randi_range(cfg["level"] * 500, cfg["level"] * 1500)
|
||||
})
|
||||
|
||||
return drops
|
||||
Reference in New Issue
Block a user