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

225 lines
6.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.
# mounted_combat_skill_system.gd —— Metin2 40250 野外军马冲杀与马上技能协同打击 1:1
# 对照 40250 服务端 char_horse.cpp, char_battle.cpp, skill.cpp
class_name MountedCombatSkillSystem
extends RefCounted
signal horse_mounted(tier: int, horse_level: int, speed_bonus: int)
signal horse_unmounted()
signal mounted_attack_executed(target_count: int, total_dmg: int)
signal horse_skill_cast(skill_vnum: int, skill_name: String, dmg: int, hit_count: int)
signal horse_fed(item_name: String, recovered_stamina: int)
enum HorseTier {
NONE = 0,
NORMAL = 1, # 1~10 级 (代步)
COMBAT = 2, # 11~20 级 (马上普攻)
MILITARY = 3 # 21~30 级 (马上 3 大绝技)
}
const SKILL_HORSE_CHARGE := 121 # 马上突刺 (Horse Charge)
const SKILL_HORSE_STOMP := 122 # 踏马震地 (Horse Stomp)
const SKILL_HORSE_WAVE := 123 # 马尾狂风 (Horse Wave)
const HORSE_SKILLS: Dictionary = {
SKILL_HORSE_CHARGE: {
"name": "马背突刺",
"mult": 2.5,
"cd": 12.0,
"sp": 60,
"range": 12.0,
"desc": "战马极速直线突进贯穿群怪,造成击飞与重创"
},
SKILL_HORSE_STOMP: {
"name": "踏马震地",
"mult": 2.0,
"cd": 15.0,
"sp": 75,
"range": 6.0,
"desc": "战马双蹄扬起践踏大地,造成 360 度范围伤害与强力眩晕"
},
SKILL_HORSE_WAVE: {
"name": "马尾狂风",
"mult": 1.8,
"cd": 10.0,
"sp": 50,
"range": 5.0,
"desc": "战马回旋摆尾卷起狂风,大范围震退近身敌群"
}
}
const FOOD_ITEMS: Dictionary = {
50054: {"name": "干草", "stamina": 30},
50055: {"name": "胡萝卜", "stamina": 60},
50056: {"name": "红参", "stamina": 100}
}
var is_mounted: bool = false
var horse_level: int = 1
var horse_tier: int = HorseTier.NONE
var speed_bonus: int = 0
var stamina: int = 100
var max_stamina: int = 100
var skill_cooldowns: Dictionary = {
SKILL_HORSE_CHARGE: 0.0,
SKILL_HORSE_STOMP: 0.0,
SKILL_HORSE_WAVE: 0.0
}
# 骑乘战马
func mount_horse(level: int = 21) -> Dictionary:
if is_mounted:
return {"ok": false, "reason": "ALREADY_MOUNTED"}
horse_level = clamp(level, 1, 30)
if horse_level >= 21:
horse_tier = HorseTier.MILITARY
speed_bonus = 50
elif horse_level >= 11:
horse_tier = HorseTier.COMBAT
speed_bonus = 40
else:
horse_tier = HorseTier.NORMAL
speed_bonus = 30
is_mounted = true
horse_mounted.emit(horse_tier, horse_level, speed_bonus)
return {
"ok": true,
"tier": horse_tier,
"level": horse_level,
"speed_bonus": speed_bonus,
"msg": "跨上战马!移动速度提升 %d 点。" % speed_bonus
}
# 下马
func unmount_horse() -> Dictionary:
if not is_mounted:
return {"ok": false, "reason": "NOT_MOUNTED"}
is_mounted = false
horse_tier = HorseTier.NONE
speed_bonus = 0
horse_unmounted.emit()
return {"ok": true, "msg": "翻身下马。"}
# 心跳刷新技能冷却
func update(delta: float) -> void:
for sk in skill_cooldowns.keys():
if skill_cooldowns[sk] > 0.0:
skill_cooldowns[sk] = max(0.0, skill_cooldowns[sk] - delta)
# 马背普通连斩反击 (需战斗马以上)
func execute_mounted_attack(base_atk: int, targets: Array) -> Dictionary:
if not is_mounted:
return {"ok": false, "reason": "NOT_MOUNTED"}
if horse_tier < HorseTier.COMBAT:
return {"ok": false, "reason": "TIER_TOO_LOW", "msg": "普通马匹无法在马背上挥砍!需升级至 11 级战斗战马。"}
if stamina <= 0:
return {"ok": false, "reason": "EXHAUSTED", "msg": "战马体力耗尽,无法施展马战挥砍!请喂食干草或胡萝卜。"}
# 每次攻击消耗 1 点马匹耐力
stamina = max(0, stamina - 1)
var hit_count = 0
var total_dmg = 0
# 战马提供 1.2x 基础冲击加成
var dmg_per_hit = int(float(base_atk) * 1.2)
for tgt in targets:
hit_count += 1
total_dmg += dmg_per_hit
mounted_attack_executed.emit(hit_count, total_dmg)
return {
"ok": true,
"hit_count": hit_count,
"dmg_per_hit": dmg_per_hit,
"total_damage": total_dmg,
"stamina": stamina
}
# 释放马上专属技能 (需军用武装战马)
func cast_horse_skill(skill_vnum: int, base_atk: int, player_data: Dictionary, targets: Array) -> Dictionary:
if not is_mounted:
return {"ok": false, "reason": "NOT_MOUNTED"}
if horse_tier < HorseTier.MILITARY:
return {"ok": false, "reason": "TIER_TOO_LOW", "msg": "只有 21 级以上的【军用战马】才能释放马背专属技能!"}
if not HORSE_SKILLS.has(skill_vnum):
return {"ok": false, "reason": "INVALID_HORSE_SKILL"}
var cfg = HORSE_SKILLS[skill_vnum]
var cd: float = skill_cooldowns[skill_vnum]
if cd > 0.0:
return {"ok": false, "reason": "SKILL_ON_COOLDOWN", "msg": "技能正在冷却中!剩余 %.1f 秒" % cd}
var sp_cost: int = cfg["sp"]
var cur_sp: int = int(player_data.get("sp", 0))
if cur_sp < sp_cost:
return {"ok": false, "reason": "NOT_ENOUGH_SP", "msg": "内力法力不足!需要 %d 点 SP" % sp_cost}
if stamina < 5:
return {"ok": false, "reason": "NOT_ENOUGH_STAMINA", "msg": "战马体力不足,无法施展大招!"}
# 消耗资源
player_data["sp"] = cur_sp - sp_cost
stamina = max(0, stamina - 5)
skill_cooldowns[skill_vnum] = cfg["cd"]
var skill_dmg = int(float(base_atk) * cfg["mult"])
var hit_count = targets.size()
horse_skill_cast.emit(skill_vnum, cfg["name"], skill_dmg, hit_count)
return {
"ok": true,
"skill_vnum": skill_vnum,
"skill_name": cfg["name"],
"damage": skill_dmg,
"hit_count": hit_count,
"stamina": stamina,
"remaining_sp": player_data["sp"],
"msg": "战马嘶鸣!施展了马上绝技【%s】!" % cfg["name"]
}
# 喂食战马恢复体力
func feed_horse(food_vnum: int, inventory: Array) -> Dictionary:
if not FOOD_ITEMS.has(food_vnum):
return {"ok": false, "reason": "INVALID_FOOD"}
var slot = -1
for i in range(inventory.size()):
if inventory[i] != null and int(inventory[i].get("vnum", 0)) == food_vnum:
slot = i
break
if slot == -1:
return {"ok": false, "reason": "FOOD_NOT_IN_INVENTORY", "msg": "背包中没有此马粮!"}
var item = inventory[slot]
var cnt = int(item.get("count", 1))
if cnt > 1:
item["count"] = cnt - 1
else:
inventory[slot] = null
var food = FOOD_ITEMS[food_vnum]
var recovered = food["stamina"]
stamina = min(max_stamina, stamina + recovered)
horse_fed.emit(food["name"], recovered)
return {
"ok": true,
"food_name": food["name"],
"recovered": recovered,
"current_stamina": stamina,
"msg": "喂食了【%s】,战马体力恢复至 %d/%d" % [food["name"], stamina, max_stamina]
}