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

175 lines
5.3 KiB
GDScript

# 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