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

226 lines
8.3 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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))