- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
169 lines
5.7 KiB
GDScript
169 lines
5.7 KiB
GDScript
# wedding_marriage_system.gd —— Metin2 40250 官方结婚典礼与夫妻同心传送婚戒系统 1:1
|
|
# 对照 40250 服务端 wedding.cpp, wedding.h, marriage.h, char_item.cpp
|
|
class_name WeddingMarriageSystem
|
|
extends RefCounted
|
|
|
|
signal wedding_started(spouse_name: String)
|
|
signal marriage_completed(spouse_name: String)
|
|
signal spouse_warped(target_map: String, target_pos: Vector2)
|
|
signal love_points_updated(new_points: int)
|
|
signal divorced()
|
|
|
|
const NPC_OLD_WOMAN := 9006 # 老妪/月老花仙子 (Old Woman)
|
|
const WEDDING_MAP_INDEX := 81 # 官方婚礼殿堂地图 (Wedding Map)
|
|
|
|
const VNUM_ENGAGEMENT_RING := 70301 # 订婚戒指
|
|
const VNUM_WEDDING_RING := 70302 # 结婚戒指 (支持一键夫妻瞬间传送)
|
|
const VNUM_TUXEDO := 11901 # 新郎礼服
|
|
const VNUM_WEDDING_DRESS := 11903 # 新娘婚纱
|
|
|
|
const VNUM_RING_OF_LOVE := 71070 # 爱情之戒 (夫妻经验加成)
|
|
const VNUM_BRACELET_OF_LOVE:= 71073 # 爱情手镯 (夫妻暴击加成)
|
|
|
|
# 婚姻状态
|
|
var is_married: bool = false
|
|
var spouse_name: String = ""
|
|
var love_points: int = 0 # 爱情度 (0~100)
|
|
var marry_timestamp: int = 0
|
|
|
|
# 举行结婚典礼 (40250 WeddingManager::Request)
|
|
func conduct_wedding(partner_name: String, inventory: Array, player_gender: String = "M") -> Dictionary:
|
|
if is_married:
|
|
return {"ok": false, "reason": "ALREADY_MARRIED", "msg": "已有婚姻关系,无法重复结婚!"}
|
|
|
|
if partner_name.strip_edges().is_empty():
|
|
return {"ok": false, "reason": "INVALID_PARTNER_NAME", "msg": "伴侣角色名称无效!"}
|
|
|
|
# 检查背包是否拥有订婚戒指 (70301)
|
|
var ring_slot := -1
|
|
for i in range(inventory.size()):
|
|
if inventory[i] != null and int(inventory[i].get("vnum", 0)) == VNUM_ENGAGEMENT_RING:
|
|
ring_slot = i
|
|
break
|
|
|
|
if ring_slot == -1:
|
|
return {"ok": false, "reason": "NO_ENGAGEMENT_RING", "msg": "需要持有【订婚戒指】(70301)方可举办婚礼!"}
|
|
|
|
# 检查是否穿戴礼服或背包携带礼服
|
|
var required_dress := VNUM_TUXEDO if player_gender == "M" else VNUM_WEDDING_DRESS
|
|
var has_dress := false
|
|
for i in range(inventory.size()):
|
|
if inventory[i] != null and int(inventory[i].get("vnum", 0)) == required_dress:
|
|
has_dress = true
|
|
break
|
|
|
|
if not has_dress:
|
|
var dress_name := "新郎礼服" if player_gender == "M" else "新娘婚纱"
|
|
return {"ok": false, "reason": "NO_WEDDING_DRESS", "msg": "举办婚礼需身着【%s】!" % dress_name}
|
|
|
|
# 消耗订婚戒指,替换为正式的【结婚戒指】(70302)
|
|
inventory[ring_slot] = {
|
|
"vnum": VNUM_WEDDING_RING,
|
|
"name": "结婚戒指",
|
|
"spouse": partner_name,
|
|
"count": 1
|
|
}
|
|
|
|
is_married = true
|
|
spouse_name = partner_name
|
|
love_points = 50 # 初始爱情度 50
|
|
marry_timestamp = int(Time.get_unix_time_from_system())
|
|
|
|
wedding_started.emit(spouse_name)
|
|
marriage_completed.emit(spouse_name)
|
|
|
|
return {
|
|
"ok": true,
|
|
"spouse_name": spouse_name,
|
|
"love_points": love_points,
|
|
"wedding_ring_slot": ring_slot,
|
|
"msg": "恭喜!您与【%s】在月老见证下喜结良缘,获得【结婚戒指】!" % spouse_name
|
|
}
|
|
|
|
# 使用结婚戒指瞬间传送至配偶身旁 (40250 char_item.cpp: Wedding Ring Warp)
|
|
func use_wedding_ring(spouse_online: bool, spouse_map: String, spouse_pos: Vector2, current_map: String) -> Dictionary:
|
|
if not is_married:
|
|
return {"ok": false, "reason": "NOT_MARRIED", "msg": "您当前未婚,无法使用结婚戒指!"}
|
|
|
|
if not spouse_online:
|
|
return {"ok": false, "reason": "SPOUSE_OFFLINE", "msg": "您的配偶当前不在线!"}
|
|
|
|
# 禁止传送的特殊限制地图 (如竞技场、武斗场)
|
|
if current_map == "Arena" or spouse_map == "Arena":
|
|
return {"ok": false, "reason": "FORBIDDEN_MAP", "msg": "比武擂台区域禁止使用结婚戒指传送!"}
|
|
|
|
spouse_warped.emit(spouse_map, spouse_pos)
|
|
|
|
return {
|
|
"ok": true,
|
|
"target_map": spouse_map,
|
|
"target_pos": spouse_pos,
|
|
"msg": "心灵相通!已跨越山海传送至配偶【%s】身侧。" % spouse_name
|
|
}
|
|
|
|
# 夫妻相伴增加爱情度 (40250 marriage.h: love_point)
|
|
func add_love_points(amount: int) -> void:
|
|
if not is_married:
|
|
return
|
|
love_points = clampi(love_points + amount, 0, 100)
|
|
love_points_updated.emit(love_points)
|
|
|
|
# 计算夫妻同心定情信物光环加成
|
|
func calculate_marriage_buff(has_ring_of_love: bool, has_bracelet_of_love: bool, is_near_spouse: bool) -> Dictionary:
|
|
if not is_married:
|
|
return {}
|
|
|
|
var buffs: Dictionary = {}
|
|
if is_near_spouse:
|
|
# 夫妻在 10 米范围内享有同心基础增益
|
|
buffs["couple_exp_bonus_pct"] = 10 + int(love_points / 10)
|
|
|
|
if has_ring_of_love:
|
|
buffs["couple_exp_bonus_pct"] += 20 # 爱情之戒再加 20%
|
|
|
|
if has_bracelet_of_love:
|
|
buffs["crit_pct"] = 10 # 爱情手镯增加 10% 暴击
|
|
|
|
return buffs
|
|
|
|
# 协议离婚 (40250 WeddingManager: End Marriage)
|
|
func divorce(inventory: Array) -> Dictionary:
|
|
if not is_married:
|
|
return {"ok": false, "reason": "NOT_MARRIED"}
|
|
|
|
# 回收/作废背包中的结婚戒指
|
|
for i in range(inventory.size()):
|
|
if inventory[i] != null and int(inventory[i].get("vnum", 0)) == VNUM_WEDDING_RING:
|
|
inventory[i] = null
|
|
break
|
|
|
|
var old_spouse := spouse_name
|
|
is_married = false
|
|
spouse_name = ""
|
|
love_points = 0
|
|
marry_timestamp = 0
|
|
|
|
divorced.emit()
|
|
|
|
return {
|
|
"ok": true,
|
|
"old_spouse": old_spouse,
|
|
"msg": "已与【%s】解除婚姻关系,收回结婚戒指。" % old_spouse
|
|
}
|
|
|
|
# 序列化
|
|
func serialize() -> Dictionary:
|
|
return {
|
|
"is_married": is_married,
|
|
"spouse_name": spouse_name,
|
|
"love_points": love_points,
|
|
"marry_timestamp": marry_timestamp
|
|
}
|
|
|
|
# 反序列化
|
|
func deserialize(data: Dictionary) -> void:
|
|
is_married = bool(data.get("is_married", false))
|
|
spouse_name = str(data.get("spouse_name", ""))
|
|
love_points = int(data.get("love_points", 0))
|
|
marry_timestamp = int(data.get("marry_timestamp", 0))
|