- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
162 lines
5.1 KiB
GDScript
162 lines
5.1 KiB
GDScript
# spider_dungeon_system.gd —— Metin2 40250 蜘蛛洞穴三层与男爵夫人巢穴 1:1
|
|
# 对照 40250 服务端 spider_dungeon_3.quest, dungeon.cpp, Boss 2094 & Mob 2095
|
|
class_name SpiderDungeonSystem
|
|
extends RefCounted
|
|
|
|
signal dungeon_started(duration: float)
|
|
signal egg_destroyed(eggs_left: int, current_atk: int, current_def: int)
|
|
signal minions_summoned()
|
|
signal dungeon_cleared(drops: Array[Dictionary])
|
|
signal dungeon_failed(reason: String)
|
|
|
|
const NPC_GRAVESTONE := 20371 # 守墓人碑文
|
|
const ITEM_ARACHNID_KEY := 30327 # 蜘蛛之钥
|
|
const BOSS_SPIDER_BARONESS := 2094 # 蜘蛛男爵夫人
|
|
const MOB_SPIDER_EGG := 2095 # 蜘蛛卵
|
|
const ITEM_BARONESS_CHEST := 50157 # 男爵宝箱
|
|
const ITEM_TITAN_SHIELD := 13129 # 泰坦盾牌+9
|
|
|
|
const REQUIRED_LEVEL := 50
|
|
const DUNGEON_TIME_LIMIT := 1200.0 # 20 分钟 (秒)
|
|
|
|
const BASE_BARONESS_MAX_HP := 250000
|
|
const BASE_BARONESS_DEF := 400
|
|
const BASE_BARONESS_ATK := 800
|
|
|
|
var is_active: bool = false
|
|
var remaining_time: float = 0.0
|
|
var eggs_remaining: int = 4
|
|
var baroness_hp: int = 0
|
|
var baroness_max_hp: int = BASE_BARONESS_MAX_HP
|
|
var baroness_def: int = BASE_BARONESS_DEF
|
|
var baroness_atk: int = BASE_BARONESS_ATK
|
|
var minions_spawned: bool = false
|
|
var dungeon_completed: bool = false
|
|
|
|
# 开启地牢密室 (spider_dungeon_3.quest)
|
|
func enter_dungeon(player_level: int, inventory: Array) -> Dictionary:
|
|
if is_active:
|
|
return {"ok": false, "reason": "ALREADY_ACTIVE", "msg": "地牢密室已经在进行中!"}
|
|
if player_level < REQUIRED_LEVEL:
|
|
return {"ok": false, "reason": "LEVEL_TOO_LOW", "msg": "等级未满 50 级,无法承受蜘蛛巢穴的剧毒!"}
|
|
|
|
# 查找并扣除蜘蛛之钥 (30327)
|
|
var key_slot := -1
|
|
for i in range(inventory.size()):
|
|
var it = inventory[i]
|
|
if it != null and int(it.get("vnum", 0)) == ITEM_ARACHNID_KEY:
|
|
key_slot = i
|
|
break
|
|
|
|
if key_slot == -1:
|
|
return {"ok": false, "reason": "MISSING_KEY", "msg": "进入蜘蛛男爵夫人巢穴需要消耗【蜘蛛钥匙】!"}
|
|
|
|
inventory[key_slot] = null
|
|
|
|
# 初始化地牢状态
|
|
is_active = true
|
|
remaining_time = DUNGEON_TIME_LIMIT
|
|
eggs_remaining = 4
|
|
baroness_max_hp = BASE_BARONESS_MAX_HP
|
|
baroness_hp = baroness_max_hp
|
|
baroness_def = BASE_BARONESS_DEF
|
|
baroness_atk = BASE_BARONESS_ATK
|
|
minions_spawned = false
|
|
dungeon_completed = false
|
|
|
|
dungeon_started.emit(remaining_time)
|
|
|
|
return {
|
|
"ok": true,
|
|
"time_limit": remaining_time,
|
|
"eggs_count": eggs_remaining,
|
|
"baroness_hp": baroness_hp,
|
|
"baroness_atk": baroness_atk,
|
|
"baroness_def": baroness_def,
|
|
"msg": "密室封印已破除!蜘蛛男爵夫人苏醒,你有 20 分钟时间击杀它!"
|
|
}
|
|
|
|
# 击碎一枚蜘蛛卵 (狂暴激怒机制: 防御 -15%, 攻击 +20%)
|
|
func destroy_egg() -> Dictionary:
|
|
if not is_active:
|
|
return {"ok": false, "reason": "DUNGEON_NOT_ACTIVE"}
|
|
if eggs_remaining <= 0:
|
|
return {"ok": false, "reason": "NO_EGGS_LEFT", "msg": "所有蜘蛛卵均已被击碎!"}
|
|
|
|
eggs_remaining -= 1
|
|
var eggs_broken := 4 - eggs_remaining
|
|
|
|
# 每碎 1 卵:防御降低 15% (最高 -60%), 攻击提升 20% (最高 +80%)
|
|
var def_reduction_pct: float = float(eggs_broken) * 0.15
|
|
var atk_bonus_pct: float = float(eggs_broken) * 0.20
|
|
|
|
baroness_def = int(round(float(BASE_BARONESS_DEF) * (1.0 - def_reduction_pct)))
|
|
baroness_atk = int(round(float(BASE_BARONESS_ATK) * (1.0 + atk_bonus_pct)))
|
|
|
|
egg_destroyed.emit(eggs_remaining, baroness_atk, baroness_def)
|
|
|
|
return {
|
|
"ok": true,
|
|
"eggs_left": eggs_remaining,
|
|
"eggs_broken": eggs_broken,
|
|
"baroness_def": baroness_def,
|
|
"baroness_atk": baroness_atk,
|
|
"msg": "蜘蛛卵被击碎!男爵夫人防御大幅削弱,但陷入了极度狂暴!"
|
|
}
|
|
|
|
# 攻击男爵夫人
|
|
func damage_baroness(raw_damage: int) -> Dictionary:
|
|
if not is_active:
|
|
return {"ok": false, "reason": "DUNGEON_NOT_ACTIVE"}
|
|
if dungeon_completed:
|
|
return {"ok": false, "reason": "ALREADY_COMPLETED"}
|
|
|
|
var eff_dmg: int = maxi(1, raw_damage - baroness_def)
|
|
baroness_hp = maxi(0, baroness_hp - eff_dmg)
|
|
|
|
# 50% 血量召唤蜘蛛群
|
|
var triggered_minions := false
|
|
if not minions_spawned and baroness_hp <= int(baroness_max_hp * 0.5):
|
|
minions_spawned = true
|
|
triggered_minions = true
|
|
minions_summoned.emit()
|
|
|
|
if baroness_hp <= 0:
|
|
dungeon_completed = true
|
|
is_active = false
|
|
var drops: Array[Dictionary] = [
|
|
{"vnum": ITEM_BARONESS_CHEST, "count": 1, "name": "男爵宝箱"},
|
|
{"vnum": ITEM_TITAN_SHIELD, "count": 1, "name": "泰坦盾牌+9"}
|
|
]
|
|
dungeon_cleared.emit(drops)
|
|
return {
|
|
"ok": true,
|
|
"dead": true,
|
|
"baroness_hp": 0,
|
|
"damage_dealt": eff_dmg,
|
|
"drops": drops,
|
|
"msg": "蜘蛛男爵夫人轰然倒地!巢穴通关!"
|
|
}
|
|
|
|
return {
|
|
"ok": true,
|
|
"dead": false,
|
|
"baroness_hp": baroness_hp,
|
|
"damage_dealt": eff_dmg,
|
|
"minions_summoned": triggered_minions
|
|
}
|
|
|
|
# 逐帧更新倒计时
|
|
func update(dt: float) -> Dictionary:
|
|
if not is_active:
|
|
return {}
|
|
|
|
remaining_time -= dt
|
|
if remaining_time <= 0.0:
|
|
remaining_time = 0.0
|
|
is_active = false
|
|
dungeon_failed.emit("TIME_OUT")
|
|
return {"failed": true, "reason": "TIME_OUT", "msg": "挑战超时!你已被移出蜘蛛男爵夫人巢穴!"}
|
|
|
|
return {"failed": false, "remaining_time": remaining_time}
|