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

217 lines
6.7 KiB
GDScript

# party_management_system.gd —— 40250 8人组队面板、经验分配与领导力光环系统 1:1
# 严格对照:
# metin2/src/server/game/src/party.h, party.cpp, char_party.cpp
# Client/Eternexus/root/uiparty.py
# ClientVS22/source/UserInterface/PythonPlayer.h (PARTY_AFFECT_*)
extends RefCounted
const PARTY_MAX_MEMBER := 8 # 官方最大 8 人队伍
# 经验分配模式 (char_party.cpp)
const EXP_MODE_LEVEL := 0 # 按等级比例分配 (NON_PARITY)
const EXP_MODE_PARITY := 1 # 均分模式 (PARITY)
# 队长战术领导力职权角色分配 (uiparty.py / PythonPlayer.h)
const ROLE_NORMAL := 0 # 普通队员
const ROLE_ATTACKER := 1 # 攻击者 (增加物理攻击力)
const ROLE_TANKER := 2 # 肉盾 (增加生命值上限)
const ROLE_BUFFER := 3 # 增益者 (增加法术/技能攻击力)
const ROLE_SKILL_MASTER := 4 # 技能大师 (减少技能冷却时间)
const ROLE_DEFENDER := 5 # 防御者 (增加防御力)
const ROLE_BERSERKER := 6 # 狂战士 (增加攻击速度)
# 角色光环属性加成表
const ROLE_BUFF_BONUSES := {
ROLE_ATTACKER: { "stat": "att_grade", "val": 30 },
ROLE_TANKER: { "stat": "max_hp", "val": 2500 },
ROLE_BUFFER: { "stat": "magic_att", "val": 25 },
ROLE_SKILL_MASTER: { "stat": "cooldown_reduction", "val": 15 },
ROLE_DEFENDER: { "stat": "def_grade", "val": 40 },
ROLE_BERSERKER: { "stat": "att_speed", "val": 15 }
}
class PartyMember:
var pid: int
var name: String
var level: int
var is_leader: bool = false
var role: int = ROLE_NORMAL
var cur_hp: int = 1000
var max_hp: int = 1000
func _init(p_pid: int, p_name: String, p_lvl: int, p_leader: bool = false) -> void:
pid = p_pid
name = p_name
level = p_lvl
is_leader = p_leader
role = ROLE_NORMAL
cur_hp = 1000
max_hp = 1000
func get_hp_percent() -> float:
if max_hp <= 0:
return 0.0
return clamp(float(cur_hp) / float(max_hp), 0.0, 1.0)
var members: Dictionary = {} # pid -> PartyMember
var leader_pid: int = -1
var exp_distribution_mode: int = EXP_MODE_PARITY
func _init(p_leader_pid: int = -1, p_leader_name: String = "", p_leader_lvl: int = 1) -> void:
members.clear()
leader_pid = -1
exp_distribution_mode = EXP_MODE_PARITY
if p_leader_pid > 0:
create_party(p_leader_pid, p_leader_name, p_leader_lvl)
# 1. 创建队伍
func create_party(p_pid: int, p_name: String, p_lvl: int) -> bool:
members.clear()
leader_pid = p_pid
var leader = PartyMember.new(p_pid, p_name, p_lvl, true)
members[p_pid] = leader
return true
# 2. 邀请/加入队员 (CParty::Join)
func add_member(p_pid: int, p_name: String, p_lvl: int) -> Dictionary:
var res := { "success": false, "error_code": "OK", "message": "" }
if members.size() >= PARTY_MAX_MEMBER:
res["error_code"] = "PARTY_FULL"
res["message"] = "队伍人数已达到 8 人上限。"
return res
if members.has(p_pid):
res["error_code"] = "ALREADY_IN_PARTY"
res["message"] = "该玩家已在队伍中。"
return res
var m = PartyMember.new(p_pid, p_name, p_lvl, false)
members[p_pid] = m
res["success"] = true
res["message"] = "%s 加入了队伍。" % p_name
return res
# 3. 踢出队员 (CParty::Quit / Expel)
func expel_member(caller_pid: int, target_pid: int) -> Dictionary:
var res := { "success": false, "error_code": "OK", "message": "" }
if caller_pid != leader_pid:
res["error_code"] = "NOT_LEADER"
res["message"] = "只有队长才能踢出队员。"
return res
if not members.has(target_pid):
res["error_code"] = "MEMBER_NOT_FOUND"
return res
if target_pid == leader_pid:
res["error_code"] = "CANNOT_EXPEL_LEADER"
res["message"] = "队长不能踢出自己。"
return res
var target_name: String = members[target_pid].name
members.erase(target_pid)
res["success"] = true
res["message"] = "%s 已被移出队伍。" % target_name
return res
# 4. 离队与队长顺延/解散 (CParty::Quit)
func leave_party(p_pid: int) -> Dictionary:
var res := { "success": false, "dissolved": false, "new_leader": -1 }
if not members.has(p_pid):
return res
var was_leader: bool = (p_pid == leader_pid)
members.erase(p_pid)
if members.is_empty():
leader_pid = -1
res["dissolved"] = true
res["success"] = true
return res
# 若队长离队,移交给第一位队员
if was_leader:
var new_lead_key: int = members.keys()[0]
leader_pid = new_lead_key
members[new_lead_key].is_leader = true
res["new_leader"] = new_lead_key
res["success"] = true
return res
# 5. 队长战术领导力角色光环配置 (CParty::SetRole)
func set_member_role(caller_pid: int, target_pid: int, role: int) -> Dictionary:
var res := { "success": false, "error_code": "OK", "message": "" }
if caller_pid != leader_pid:
res["error_code"] = "NOT_LEADER"
res["message"] = "只有队长有权分配战术光环。"
return res
if not members.has(target_pid):
res["error_code"] = "MEMBER_NOT_FOUND"
return res
# 检查角色是否已被队伍中其他成员占用 (非 ROLE_NORMAL)
if role != ROLE_NORMAL:
for k in members:
if k != target_pid and members[k].role == role:
res["error_code"] = "ROLE_ALREADY_ASSIGNED"
res["message"] = "该战术光环已被其他队员占用。"
return res
members[target_pid].role = role
res["success"] = true
res["role"] = role
res["buff"] = ROLE_BUFF_BONUSES.get(role, {})
res["message"] = "成功为队员分配战术角色。"
return res
# 6. 获取队员当前享有的领导力光环加成
func get_member_buff(p_pid: int) -> Dictionary:
if not members.has(p_pid):
return {}
var r: int = members[p_pid].role
return ROLE_BUFF_BONUSES.get(r, {})
# 7. 切换经验分配模式
func set_exp_distribution_mode(caller_pid: int, mode: int) -> bool:
if caller_pid != leader_pid:
return false
if mode == EXP_MODE_PARITY or mode == EXP_MODE_LEVEL:
exp_distribution_mode = mode
return true
return false
# 8. 队伍击杀经验结算与分发 (1:1 CParty::DistributeExp)
func distribute_exp(total_exp: int) -> Dictionary:
var distribution := {}
if members.is_empty() or total_exp <= 0:
return distribution
var count := members.size()
if exp_distribution_mode == EXP_MODE_PARITY:
# 均分模式: 每个队员获得 total_exp / count
var per_member: int = total_exp / count
for k in members:
distribution[k] = per_member
else:
# 等级权重模式: 按队员等级比例分配
var sum_levels: int = 0
for k in members:
sum_levels += members[k].level
for k in members:
var share: int = int(float(total_exp) * float(members[k].level) / float(sum_levels))
distribution[k] = share
return distribution
# 9. 同步队员生命值百分比
func sync_member_hp(p_pid: int, cur: int, max_h: int) -> void:
if members.has(p_pid):
members[p_pid].cur_hp = cur
members[p_pid].max_hp = max_h