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

139 lines
4.8 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.
# 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 扩至 500cm1: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": "战马饱餐了一顿,恢复了活力!"}