- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
226 lines
8.3 KiB
GDScript
226 lines
8.3 KiB
GDScript
# demon_tower_system.gd —— 恶魔之塔 1~9 层单机爬塔挑战循环与塔顶免费铁匠(40250 官方 1:1 对齐)
|
||
# 对齐源码:
|
||
# - 40250 server/share/locale/english/quest/deviltower_zone.quest
|
||
# - 40250 server/game/src/dungeon.cpp
|
||
# - 40250 server/game/src/char_battle.cpp
|
||
class_name DemonTowerSystem
|
||
extends RefCounted
|
||
|
||
signal floor_changed(floor_num: int, floor_name: String)
|
||
signal floor_completed(floor_num: int)
|
||
signal tower_completed
|
||
signal tower_failed(reason: String)
|
||
signal blacksmith_spawned(blacksmith_vnum: int, blacksmith_name: String)
|
||
|
||
# 核心怪物与 NPC Vnum (40250 官方标准)
|
||
const VNUM_METIN_FLOOR1 := 8015 # 坚韧之石 (1层入口封印)
|
||
const VNUM_DEMON_KING := 1091 # 恶魔王 (3层 BOSS)
|
||
const VNUM_METIN_FLOOR4 := 8016 # 恶魔封印石 (4层真伪封印石)
|
||
const VNUM_ELITE_DEMON_KING := 1092 # 精英恶魔王 (6层 BOSS)
|
||
const VNUM_METIN_DEATH := 8017 # 死亡之石 (7层)
|
||
const VNUM_DEATH_REAPER := 1093 # 死神 (9层 最终 BOSS)
|
||
|
||
# 塔顶免费铁匠 NPC
|
||
const NPC_TOWER_WEAPON_SMITH := 20074 # 恶魔之塔武器铁匠
|
||
const NPC_TOWER_ARMOR_SMITH := 20075 # 恶魔之塔防具铁匠
|
||
const NPC_TOWER_JEWELRY_SMITH := 20076 # 恶魔之塔首饰铁匠
|
||
|
||
# 核心任务道具
|
||
const VNUM_ZIN_KEY_FLOOR5 := 50084 # 解除 5 层石碑钥匙
|
||
const VNUM_ZIN_MAP_FLOOR7 := 30302 # 7 层恶魔封印地图
|
||
const VNUM_ZIN_KEY_FLOOR8 := 30304 # 8 层通往塔顶钥匙
|
||
const VNUM_DEATH_REAPER_CHEST := 50082 # 9 层死神宝箱
|
||
|
||
const TOTAL_FLOORS := 9
|
||
const FLOOR_NAMES := {
|
||
1: "第 1 层:恶魔入口",
|
||
2: "第 2 层:恶魔集结所",
|
||
3: "第 3 层:恶魔王大殿",
|
||
4: "第 4 层:迷幻封印之阵",
|
||
5: "第 5 层:五柱封印禁地",
|
||
6: "第 6 层:恶魔之塔顶峰",
|
||
7: "第 7 层:死神迷宫前厅",
|
||
8: "第 8 层:死神结界禁地",
|
||
9: "第 9 层:死神王座决战",
|
||
}
|
||
|
||
# 运行期状态
|
||
var current_floor := 1
|
||
var is_active := false
|
||
var floor_time_left := 1200.0 # 20 分钟单层时限
|
||
var floor4_real_metin_idx := -1
|
||
var floor5_unlocked_pillars := 0
|
||
var floor6_blacksmith_vnum := 0
|
||
var floor6_free_refine_used := false
|
||
|
||
func start_tower() -> void:
|
||
current_floor = 1
|
||
is_active = true
|
||
floor_time_left = 1200.0
|
||
floor4_real_metin_idx = -1
|
||
floor5_unlocked_pillars = 0
|
||
floor6_blacksmith_vnum = 0
|
||
floor6_free_refine_used = false
|
||
floor_changed.emit(1, FLOOR_NAMES[1])
|
||
|
||
func get_floor() -> int:
|
||
return current_floor
|
||
|
||
func get_floor_name() -> String:
|
||
return FLOOR_NAMES.get(current_floor, "未知层")
|
||
|
||
## 第 1 层判定:击碎封印石 8015 即可通关
|
||
func on_floor1_metin_destroyed(vnum: int) -> bool:
|
||
if current_floor != 1 or not is_active:
|
||
return false
|
||
if vnum == VNUM_METIN_FLOOR1:
|
||
_advance_floor()
|
||
return true
|
||
return false
|
||
|
||
## 第 2 层判定:清理全图怪物
|
||
func on_floor2_mobs_cleared(mob_count: int) -> bool:
|
||
if current_floor != 2 or not is_active:
|
||
return false
|
||
if mob_count <= 0:
|
||
_advance_floor()
|
||
return true
|
||
return false
|
||
|
||
## 第 3 层判定:击杀恶魔王 1091
|
||
func on_floor3_boss_killed(boss_vnum: int) -> bool:
|
||
if current_floor != 3 or not is_active:
|
||
return false
|
||
if boss_vnum == VNUM_DEMON_KING:
|
||
_advance_floor()
|
||
return true
|
||
return false
|
||
|
||
## 第 4 层判定:初始化 7 座封印石并在打破真石时通关
|
||
func setup_floor4_metins() -> void:
|
||
# 7 座封印石 (索引 0..6),随机指定 1 座为真石
|
||
floor4_real_metin_idx = randi() % 7
|
||
|
||
func on_floor4_metin_hit(stone_idx: int) -> Dictionary:
|
||
if current_floor != 4 or not is_active:
|
||
return {"ok": false, "msg": "当前不在第 4 层!"}
|
||
if stone_idx == floor4_real_metin_idx:
|
||
_advance_floor()
|
||
return {"ok": true, "is_real": true, "msg": "找到了真实的恶魔封印石!通往第 5 层的传送门开启!"}
|
||
else:
|
||
return {"ok": true, "is_real": false, "msg": "这是一座虚假的封印石,迷雾尚未散开!"}
|
||
|
||
## 第 5 层判定:使用钥匙解除 5 座恶魔石柱
|
||
func on_floor5_unlock_pillar(key_vnum: int) -> Dictionary:
|
||
if current_floor != 5 or not is_active:
|
||
return {"ok": false, "msg": "当前不在第 5 层!"}
|
||
if key_vnum != VNUM_ZIN_KEY_FLOOR5:
|
||
return {"ok": false, "msg": "钥匙不匹配!"}
|
||
|
||
floor5_unlocked_pillars += 1
|
||
var remain := 5 - floor5_unlocked_pillars
|
||
if floor5_unlocked_pillars >= 5:
|
||
_advance_floor()
|
||
return {"ok": true, "completed": true, "msg": "5 座恶魔石碑全部解除封印!传送至第 6 层!"}
|
||
else:
|
||
return {"ok": true, "completed": false, "remain": remain, "msg": "成功解除了一座石碑封印,还剩 %d 座!" % remain}
|
||
|
||
## 第 6 层判定:击杀精英恶魔王 1092 并召唤免费铁匠
|
||
func on_floor6_boss_killed(boss_vnum: int) -> Dictionary:
|
||
if current_floor != 6 or not is_active:
|
||
return {"ok": false, "msg": "当前不在第 6 层!"}
|
||
if boss_vnum == VNUM_ELITE_DEMON_KING:
|
||
# 随机召唤三位塔顶铁匠之一
|
||
var smiths := [
|
||
{"vnum": NPC_TOWER_WEAPON_SMITH, "name": "恶魔之塔武器铁匠"},
|
||
{"vnum": NPC_TOWER_ARMOR_SMITH, "name": "恶魔之塔防具铁匠"},
|
||
{"vnum": NPC_TOWER_JEWELRY_SMITH, "name": "恶魔之塔首饰铁匠"}
|
||
]
|
||
var chosen: Dictionary = smiths[randi() % smiths.size()]
|
||
floor6_blacksmith_vnum = chosen["vnum"]
|
||
floor6_free_refine_used = false
|
||
blacksmith_spawned.emit(chosen["vnum"], chosen["name"])
|
||
return {
|
||
"ok": true,
|
||
"blacksmith_vnum": chosen["vnum"],
|
||
"blacksmith_name": chosen["name"],
|
||
"msg": "击杀了精英恶魔王!塔顶现身了一位【%s】!" % chosen["name"]
|
||
}
|
||
return {"ok": false, "msg": "目标不是精英恶魔王!"}
|
||
|
||
## 第 6 层塔顶免费铁匠强化 (无需材料,单人爬塔经典福利)
|
||
func perform_tower_free_refine(item: Dictionary, item_type: int) -> Dictionary:
|
||
if current_floor != 6 or floor6_blacksmith_vnum == 0:
|
||
return {"ok": false, "code": "NO_BLACKSMITH", "msg": "塔顶铁匠尚未出现!"}
|
||
if floor6_free_refine_used:
|
||
return {"ok": false, "code": "ALREADY_USED", "msg": "恶魔之塔铁匠本次只能免费强化一次!"}
|
||
|
||
# 校验铁匠类型与物品类型匹配
|
||
if floor6_blacksmith_vnum == NPC_TOWER_WEAPON_SMITH and item_type != 1:
|
||
return {"ok": false, "code": "TYPE_MISMATCH", "msg": "武器铁匠只能强化武器!"}
|
||
if floor6_blacksmith_vnum == NPC_TOWER_ARMOR_SMITH and item_type != 2:
|
||
return {"ok": false, "code": "TYPE_MISMATCH", "msg": "防具铁匠只能强化防具!"}
|
||
|
||
floor6_free_refine_used = true
|
||
var cur_vnum := int(item.get("vnum", 0))
|
||
var new_vnum := cur_vnum + 1
|
||
item["vnum"] = new_vnum
|
||
return {
|
||
"ok": true,
|
||
"code": "SUCCESS",
|
||
"old_vnum": cur_vnum,
|
||
"new_vnum": new_vnum,
|
||
"msg": "【%s】为你施展了恶魔神工,免费强化成功!装备晋升为 #%d!" % [
|
||
"武器铁匠" if floor6_blacksmith_vnum == NPC_TOWER_WEAPON_SMITH else "防具铁匠",
|
||
new_vnum
|
||
]
|
||
}
|
||
|
||
## 从第 6 层前往第 7 层 (要求角色等级 >= 75)
|
||
func advance_to_floor7(player_level: int) -> Dictionary:
|
||
if current_floor != 6:
|
||
return {"ok": false, "msg": "不在第 6 层!"}
|
||
if player_level < 75:
|
||
return {"ok": false, "code": "LEVEL_TOO_LOW", "msg": "只有等级达到 75 级的真正勇者,才能踏入恶魔之塔第 7 层禁地!"}
|
||
_advance_floor()
|
||
return {"ok": true, "msg": "踏入了死神禁地第 7 层!"}
|
||
|
||
## 第 7 层判定:使用恶魔地图 30302 通关
|
||
func on_floor7_map_used(map_vnum: int) -> bool:
|
||
if current_floor != 7 or not is_active:
|
||
return false
|
||
if map_vnum == VNUM_ZIN_MAP_FLOOR7:
|
||
_advance_floor()
|
||
return true
|
||
return false
|
||
|
||
## 第 8 层判定:使用钥匙 30304 通关
|
||
func on_floor8_key_used(key_vnum: int) -> bool:
|
||
if current_floor != 8 or not is_active:
|
||
return false
|
||
if key_vnum == VNUM_ZIN_KEY_FLOOR8:
|
||
_advance_floor()
|
||
return true
|
||
return false
|
||
|
||
## 第 9 层终极对决:击杀死神 1093
|
||
func on_floor9_boss_killed(boss_vnum: int) -> Dictionary:
|
||
if current_floor != 9 or not is_active:
|
||
return {"ok": false, "msg": "当前不在第 9 层!"}
|
||
if boss_vnum == VNUM_DEATH_REAPER:
|
||
is_active = false
|
||
tower_completed.emit()
|
||
return {
|
||
"ok": true,
|
||
"code": "TOWER_CLEAR",
|
||
"drop_vnum": VNUM_DEATH_REAPER_CHEST,
|
||
"msg": "壮举!你成功斩杀了死神,恶魔之塔被彻底征服!获得了【死神宝箱】!"
|
||
}
|
||
return {"ok": false, "msg": "目标不是死神!"}
|
||
|
||
func _advance_floor() -> void:
|
||
floor_completed.emit(current_floor)
|
||
current_floor += 1
|
||
floor_time_left = 1200.0
|
||
floor_changed.emit(current_floor, FLOOR_NAMES.get(current_floor, "第 %d 层" % current_floor))
|