# 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] }