# horse_system.gd —— 40250 战马与骑乘系统 1:1 规则引擎 # 严格对照: # metin2/src/server/game/src/cmd_general.cpp:45-100 (do_user_horse_ride / back / feed) # metin2/src/server/game/src/cmd_general.cpp:2426-2465 (do_ride) # root/game.py:344, 461-490 (Ctrl+G, Ctrl+H, Ctrl+B, Ctrl+F) # ClientVS22/UserInterface/InstanceBase.cpp (IsMountingHorse, 5.0m attack range) extends RefCounted const ITEM_HORSE_PICTURE := 50051 # 初级马画 (Lv 1 - 10) const ITEM_HORSE_MEDAL := 50052 # 战斗马牌 (Lv 11 - 20) const ITEM_HORSE_MILITARY := 50053 # 军马牌 (Lv 21 - 30) const ITEM_FOOD_HAY := 50057 # 干草 const ITEM_FOOD_CARROT := 50054 # 胡萝卜 const ITEM_FOOD_GINSENG := 50058 # 人参 const HORSE_TIER_NONE := 0 const HORSE_TIER_BEGINNER := 1 # 普通马 const HORSE_TIER_COMBAT := 2 # 战斗马(可马上砍怪) const HORSE_TIER_MILITARY := 3 # 军马(可使用马术技能) # 召唤消耗 SP const SUMMON_SP_COST := { HORSE_TIER_BEGINNER: 100, HORSE_TIER_COMBAT: 200, HORSE_TIER_MILITARY: 300, } # 基础召唤成功率 const BASE_SUMMON_CHANCE := { HORSE_TIER_BEGINNER: 10, HORSE_TIER_COMBAT: 20, HORSE_TIER_MILITARY: 30, } # 骑乘时战斗与拾取距离加成(从 300cm 扩至 500cm,1:1 ClientVS22) const WALKING_ATTACK_RANGE := 3.0 const MOUNTED_ATTACK_RANGE := 5.0 # 判定马牌对应的级别与需求 static func get_horse_tier_from_item(vnum: int) -> int: match vnum: ITEM_HORSE_PICTURE: return HORSE_TIER_BEGINNER ITEM_HORSE_MEDAL: return HORSE_TIER_COMBAT ITEM_HORSE_MILITARY: return HORSE_TIER_MILITARY _: return HORSE_TIER_NONE # 所需饲料 static func get_required_food(tier: int) -> int: match tier: HORSE_TIER_BEGINNER: return ITEM_FOOD_HAY HORSE_TIER_COMBAT: return ITEM_FOOD_CARROT HORSE_TIER_MILITARY: return ITEM_FOOD_GINSENG _: return 0 # 召唤几率计算(1:1 char_horse.cpp: HorseSummon) # chance = base_chance + horse_summon_skill_level * 10, max 100 static func calculate_summon_chance(tier: int, summon_skill_level: int) -> int: var base: int = BASE_SUMMON_CHANCE.get(tier, 10) return mini(100, base + summon_skill_level * 10) # 执行召唤判定 static func try_summon_horse( tier: int, current_sp: int, summon_skill_level: int, roll_val := -1 ) -> Dictionary: var sp_cost: int = SUMMON_SP_COST.get(tier, 100) if current_sp < sp_cost: return {"success": false, "err": "insufficient_sp", "sp_cost": sp_cost} var chance := calculate_summon_chance(tier, summon_skill_level) var dice := roll_val if roll_val > 0 else (randi() % 100 + 1) var success := (dice <= chance) return { "success": success, "sp_cost": sp_cost, "tier": tier, "err": "" if success else "summon_failed", } # 执行 /user_horse_ride (Ctrl+H) 状态机 (cmd_general.cpp:45) static func handle_user_horse_ride( is_riding: bool, is_horse_summoned: bool, is_other_mount: bool, is_dead_or_stun: bool ) -> Dictionary: if is_dead_or_stun: return {"action": "none", "err": "dead_or_stun"} if not is_riding: if is_other_mount: return {"action": "none", "err": "already_mounting_other", "msg": "已经在使用其他坐骑。"} if not is_horse_summoned: return {"action": "none", "err": "horse_not_summoned", "msg": "请先召唤马匹。"} return {"action": "start_riding", "err": "", "is_riding": true} else: return {"action": "stop_riding", "err": "", "is_riding": false} # 执行 /user_horse_back (Ctrl+B) 状态机 (cmd_general.cpp:75) static func handle_user_horse_back( is_riding: bool, is_horse_summoned: bool ) -> Dictionary: if is_riding: return {"action": "none", "err": "must_dismount_first", "msg": "请先下马。"} if not is_horse_summoned: return {"action": "none", "err": "horse_not_summoned", "msg": "请先召唤马匹。"} return {"action": "unsummon", "err": "", "is_horse_summoned": false, "msg": "已送回马匹。"} # 执行 /ride (Ctrl+G) 状态机 (cmd_general.cpp:2426) static func handle_ride( is_riding: bool, is_horse_summoned: bool, has_ride_item: bool ) -> Dictionary: if is_riding: return {"action": "stop_riding", "err": "", "is_riding": false} if is_horse_summoned: return {"action": "start_riding", "err": "", "is_riding": true} if has_ride_item: return {"action": "mount_item", "err": "", "is_riding": true} return {"action": "none", "err": "cannot_ride", "msg": "没有可骑乘的马匹或坐骑。"} # 喂食逻辑 (Ctrl+F) static func handle_user_horse_feed( tier: int, food_item_vnum: int ) -> Dictionary: var req_food := get_required_food(tier) if req_food == 0: return {"success": false, "msg": "当前没有出战马匹。"} if food_item_vnum != req_food: return {"success": false, "msg": "这种食物不适合当前马匹。"} return {"success": true, "consumed_vnum": req_food, "msg": "战马饱餐了一顿,恢复了活力!"}