- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
178 lines
6.3 KiB
GDScript
178 lines
6.3 KiB
GDScript
# marriage_wedding_love_system.gd —— Metin2 40250 官方夫妻同心传送与爱情点数婚戒祈愿系统 1:1
|
||
# 100% 对照 40250 服务端 marriage.cpp, marriage.h, char_item.cpp (ITEM_MARRIAGE_RING 70302), char.cpp (WarpToPID)
|
||
class_name MarriageWeddingLoveSystem
|
||
extends RefCounted
|
||
|
||
signal couple_married(pid1: int, name1: String, pid2: int, name2: String)
|
||
signal couple_divorced(pid1: int, pid2: int)
|
||
signal partner_teleported(teleporter_id: int, target_pos: Vector2, sp_consumed: int)
|
||
signal love_points_increased(pid1: int, pid2: int, new_love_points: int)
|
||
signal marriage_error(reason: String)
|
||
|
||
# 40250 官方婚戒与爱情饰品 Vnum
|
||
const VNUM_COUPLE_RING := 70301 # 情侣/订婚戒指 (Couple Ring)
|
||
const VNUM_WEDDING_RING := 70302 # 结婚戒指 (Wedding Ring / ITEM_MARRIAGE_RING)
|
||
const VNUM_BRACELET_OF_LOVE := 71069 # 爱情手镯 (Bracelet of Love - 增攻击)
|
||
const VNUM_EARRINGS_OF_LOVE := 71071 # 爱情耳环 (Earrings of Love - 增防御)
|
||
const VNUM_NECKLACE_OF_LOVE := 71073 # 爱情项链 (Necklace of Love - 增暴击)
|
||
|
||
# 婚姻档案记录表:
|
||
# couple_id -> { "pid1": int, "name1": String, "pid2": int, "name2": String, "love_points": int, "marry_time": float }
|
||
var _marriages: Dictionary = {}
|
||
var _pid_to_couple: Dictionary = {} # pid -> couple_id
|
||
var _next_couple_id: int = 1
|
||
|
||
# ==========================================
|
||
# 1. 婚姻缔结与解约 (marriage.cpp: Add, Remove)
|
||
# ==========================================
|
||
func marry(pid1: int, name1: String, pid2: int, name2: String) -> int:
|
||
if is_married(pid1) or is_married(pid2) or pid1 == pid2:
|
||
marriage_error.emit("ALREADY_MARRIED")
|
||
return -1
|
||
|
||
var cid = _next_couple_id
|
||
_next_couple_id += 1
|
||
|
||
_marriages[cid] = {
|
||
"pid1": pid1,
|
||
"name1": name1,
|
||
"pid2": pid2,
|
||
"name2": name2,
|
||
"love_points": 50, # 初始爱情点数 50%
|
||
"marry_time": Time.get_unix_time_from_system()
|
||
}
|
||
_pid_to_couple[pid1] = cid
|
||
_pid_to_couple[pid2] = cid
|
||
|
||
couple_married.emit(pid1, name1, pid2, name2)
|
||
return cid
|
||
|
||
func divorce(pid: int) -> bool:
|
||
if not is_married(pid):
|
||
marriage_error.emit("NOT_MARRIED")
|
||
return false
|
||
|
||
var cid = int(_pid_to_couple[pid])
|
||
var m = _marriages[cid]
|
||
var p1 = int(m["pid1"])
|
||
var p2 = int(m["pid2"])
|
||
|
||
_pid_to_couple.erase(p1)
|
||
_pid_to_couple.erase(p2)
|
||
_marriages.erase(cid)
|
||
|
||
couple_divorced.emit(p1, p2)
|
||
return true
|
||
|
||
func is_married(pid: int) -> bool:
|
||
return _pid_to_couple.has(pid)
|
||
|
||
func get_partner_pid(pid: int) -> int:
|
||
if not is_married(pid):
|
||
return 0
|
||
var cid = _pid_to_couple[pid]
|
||
var m = _marriages[cid]
|
||
return int(m["pid2"]) if int(m["pid1"]) == pid else int(m["pid1"])
|
||
|
||
func get_partner_name(pid: int) -> String:
|
||
if not is_married(pid):
|
||
return ""
|
||
var cid = _pid_to_couple[pid]
|
||
var m = _marriages[cid]
|
||
return str(m["name2"]) if int(m["pid1"]) == pid else str(m["name1"])
|
||
|
||
# ==========================================
|
||
# 2. 爱情点数与同心杀怪成长 (marriage.cpp: LovePoint, IsNear)
|
||
# ==========================================
|
||
func get_love_points(pid: int) -> int:
|
||
if not is_married(pid):
|
||
return 0
|
||
var cid = _pid_to_couple[pid]
|
||
return int(_marriages[cid]["love_points"])
|
||
|
||
func add_love_points(pid: int, amount: int) -> int:
|
||
if not is_married(pid) or amount <= 0:
|
||
return 0
|
||
var cid = _pid_to_couple[pid]
|
||
var cur = int(_marriages[cid]["love_points"])
|
||
var new_val = clamp(cur + amount, 0, 100)
|
||
_marriages[cid]["love_points"] = new_val
|
||
|
||
love_points_increased.emit(_marriages[cid]["pid1"], _marriages[cid]["pid2"], new_val)
|
||
return new_val
|
||
|
||
# 爱情饰品属性加成计算 (marriage.cpp: GetBonus)
|
||
func get_love_item_bonus(pid: int, item_vnum: int) -> Dictionary:
|
||
var lp = get_love_points(pid)
|
||
# 依据 40250 公式,加成随爱情点数百分比等比浮动
|
||
var ratio = lp / 100.0
|
||
match item_vnum:
|
||
VNUM_BRACELET_OF_LOVE: # 爱情手镯: 增加攻击力 (最高 +40)
|
||
return {"bonus_type": "att_grade", "value": int(floor(40 * ratio))}
|
||
VNUM_EARRINGS_OF_LOVE: # 爱情耳环: 增加防御力 (最高 +30)
|
||
return {"bonus_type": "def_grade", "value": int(floor(30 * ratio))}
|
||
VNUM_NECKLACE_OF_LOVE: # 爱情项链: 增加暴击率 (最高 +20%)
|
||
return {"bonus_type": "crit_pct", "value": int(floor(20 * ratio))}
|
||
return {"bonus_type": "none", "value": 0}
|
||
|
||
# ==========================================
|
||
# 3. 结婚戒指跨图同心传送 (char_item.cpp:2490 ITEM_MARRIAGE_RING)
|
||
# ==========================================
|
||
func use_wedding_ring(
|
||
player_id: int,
|
||
player_sp: int,
|
||
partner_online: bool,
|
||
partner_map_name: String,
|
||
partner_pos: Vector2,
|
||
is_partner_in_arena: bool = false
|
||
) -> Dictionary:
|
||
if not is_married(player_id):
|
||
marriage_error.emit("NOT_MARRIED")
|
||
return {"ok": false, "reason": "NOT_MARRIED", "msg": "未婚状态无法使用结婚戒指传送!"}
|
||
|
||
if not partner_online:
|
||
marriage_error.emit("PARTNER_OFFLINE")
|
||
return {"ok": false, "reason": "PARTNER_OFFLINE", "msg": "你的伴侣当前不在线,无法感应其位置。"}
|
||
|
||
# 40250 官方: 竞技场/武斗决斗中禁止夫妻传送 (IsArenaMap)
|
||
if is_partner_in_arena:
|
||
marriage_error.emit("PARTNER_IN_ARENA")
|
||
return {"ok": false, "reason": "PARTNER_IN_ARENA", "msg": "你的伴侣正在决斗竞技场中,禁止传送进入!"}
|
||
|
||
# 计算传送法力消耗 (40250 CalculateConsumeSP):
|
||
# 爱情点数越高,传送消耗的 SP 越少 (100% 爱情点数消耗 50 SP,0% 消耗 200 SP)
|
||
var lp = get_love_points(player_id)
|
||
var consume_sp = int(floor(200 - (150 * (lp / 100.0))))
|
||
|
||
if player_sp < consume_sp:
|
||
marriage_error.emit("NOT_ENOUGH_SP")
|
||
return {
|
||
"ok": false,
|
||
"reason": "NOT_ENOUGH_SP",
|
||
"msg": "法力不足!夫妻同心传送需要消耗 %d 点法力值 (SP)。" % consume_sp
|
||
}
|
||
|
||
partner_teleported.emit(player_id, partner_pos, consume_sp)
|
||
return {
|
||
"ok": true,
|
||
"partner_name": get_partner_name(player_id),
|
||
"target_map": partner_map_name,
|
||
"target_pos": partner_pos,
|
||
"sp_consumed": consume_sp,
|
||
"remaining_sp": player_sp - consume_sp,
|
||
"msg": "婚戒闪耀着柔和的粉红霞光,跨越千山万水,你瞬间传送到了伴侣身边!"
|
||
}
|
||
|
||
# 序列化与反序列化
|
||
func serialize() -> Dictionary:
|
||
return {
|
||
"marriages": _marriages.duplicate(true),
|
||
"pid_to_couple": _pid_to_couple.duplicate(),
|
||
"next_couple_id": _next_couple_id
|
||
}
|
||
|
||
func deserialize(data: Dictionary) -> void:
|
||
_marriages = data.get("marriages", {}).duplicate(true)
|
||
_pid_to_couple = data.get("pid_to_couple", {}).duplicate()
|
||
_next_couple_id = int(data.get("next_couple_id", 1))
|