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

187 lines
5.8 KiB
GDScript

# guild_war_system.gd —— Metin2 40250 官方公会战备宣战与积分决斗裁定系统 1:1
# 对照 40250 服务端 guild_war.cpp, guild_manager.cpp, guild.h
class_name GuildWarSystem
extends RefCounted
signal war_declared(opp_guild_name: String, war_type: int, target_score: int)
signal war_started(war_type: int, duration: float)
signal score_updated(score_a: int, score_b: int)
signal war_ended(winner_side: String, score_a: int, score_b: int, reward: Dictionary)
# 40250 官方公会战模式类型
const WAR_TYPE_FIELD := 0 # 野外遭遇战 (Field War)
const WAR_TYPE_ARENA := 1 # 竞技场擂台战 (Arena War, Map 110/111)
const WAR_TYPE_FLAG := 2 # 夺旗战 (Flag War)
# 公会战状态机
const STATE_NONE := 0
const STATE_DECLARED := 1 # 宣战中
const STATE_PREPARING := 2 # 备战中
const STATE_FIGHTING := 3 # 激战中
const STATE_FINISHED := 4 # 战役结束
# 单机竞争公会预设库
const RIVAL_GUILDS: Dictionary = {
101: {"id": 101, "name": "暗影刺客团", "master": "夜刃", "level": 20, "ladder_points": 1200},
102: {"id": 102, "name": "赤血狂战盟", "master": "狂屠", "level": 20, "ladder_points": 1350},
103: {"id": 103, "name": "青云修罗殿", "master": "冥火", "level": 20, "ladder_points": 1400}
}
var war_state: int = STATE_NONE
var active_war_type: int = WAR_TYPE_ARENA
var active_opp_guild: Dictionary = {}
var target_score: int = 50 # 目标获胜积分 (50分/100分)
var time_remaining: float = 1800.0 # 30 分钟 (1800 秒)
var total_duration: float = 1800.0
var score_a: int = 0 # 玩家公会得分
var score_b: int = 0 # 敌方公会得分
var player_guild_ladder: int = 1000 # 玩家公会天梯积分
var total_wars_won: int = 0
# 发起公会宣战 (40250 CGuild::GuildWarPacket / UnderDeclaration)
func declare_war(opp_guild_id: int, war_type: int = WAR_TYPE_ARENA, win_score: int = 50, duration: float = 1800.0) -> Dictionary:
if war_state == STATE_FIGHTING or war_state == STATE_PREPARING:
return {"ok": false, "reason": "ALREADY_IN_WAR", "msg": "当前公会已处于战役交锋中!"}
if not RIVAL_GUILDS.has(opp_guild_id):
return {"ok": false, "reason": "INVALID_OPP_GUILD", "msg": "未知的敌对公会!"}
active_opp_guild = RIVAL_GUILDS[opp_guild_id].duplicate(true)
active_war_type = war_type
target_score = win_score
time_remaining = duration
total_duration = duration
score_a = 0
score_b = 0
war_state = STATE_DECLARED
war_declared.emit(active_opp_guild["name"], active_war_type, target_score)
return {
"ok": true,
"opp_name": active_opp_guild["name"],
"war_type": active_war_type,
"target_score": target_score,
"msg": "战书已下达!向【%s】发起公会宣战。" % active_opp_guild["name"]
}
# 响应接受战书开启战场
func start_war() -> Dictionary:
if war_state != STATE_DECLARED:
return {"ok": false, "reason": "NOT_IN_DECLARED_STATE"}
war_state = STATE_FIGHTING
war_started.emit(active_war_type, total_duration)
return {
"ok": true,
"war_type": active_war_type,
"duration": total_duration,
"msg": "战役正式打响!双方公会成员进入战场拼杀。"
}
# 记录战场击杀或占旗得分
func record_score(side: String, points: int = 1) -> Dictionary:
if war_state != STATE_FIGHTING:
return {"ok": false, "reason": "WAR_NOT_ACTIVE"}
if side == "A":
score_a += points
elif side == "B":
score_b += points
score_updated.emit(score_a, score_b)
# 检查是否率先达到目标胜分
if score_a >= target_score or score_b >= target_score:
var winner := "A" if score_a >= target_score else "B"
return end_war(winner)
return {
"ok": true,
"score_a": score_a,
"score_b": score_b,
"target_score": target_score
}
# 结束战役与结算奖励
func end_war(winner: String) -> Dictionary:
war_state = STATE_FINISHED
var reward: Dictionary = {}
if winner == "A":
total_wars_won += 1
player_guild_ladder += 30
reward = {
"guild_exp": 5000,
"guild_gold": 1000000,
"ladder_change": 30,
"new_ladder": player_guild_ladder
}
elif winner == "B":
player_guild_ladder = maxi(0, player_guild_ladder - 20)
reward = {
"guild_exp": 1000,
"guild_gold": 200000,
"ladder_change": -20,
"new_ladder": player_guild_ladder
}
else:
reward = {
"guild_exp": 2000,
"guild_gold": 500000,
"ladder_change": 0,
"new_ladder": player_guild_ladder
}
war_ended.emit(winner, score_a, score_b, reward)
var msg_title := "大获全胜" if winner == "A" else ("虽败犹荣" if winner == "B" else "战局握手言和")
return {
"ok": true,
"winner": winner,
"score_a": score_a,
"score_b": score_b,
"reward": reward,
"msg": "公会战结束【%s】!比分:%d - %d。" % [msg_title, score_a, score_b]
}
# 战役时钟心跳
func update(delta: float) -> void:
if war_state != STATE_FIGHTING:
return
time_remaining -= delta
if time_remaining <= 0.0:
# 时间耗尽,按现有比分决出
var winner := "A" if score_a > score_b else ("B" if score_b > score_a else "DRAW")
end_war(winner)
# 序列化
func serialize() -> Dictionary:
return {
"war_state": war_state,
"active_war_type": active_war_type,
"active_opp_guild": active_opp_guild.duplicate(true),
"target_score": target_score,
"time_remaining": time_remaining,
"score_a": score_a,
"score_b": score_b,
"player_guild_ladder": player_guild_ladder,
"total_wars_won": total_wars_won
}
# 反序列化
func deserialize(data: Dictionary) -> void:
war_state = int(data.get("war_state", STATE_NONE))
active_war_type = int(data.get("active_war_type", WAR_TYPE_ARENA))
active_opp_guild = data.get("active_opp_guild", {}).duplicate(true)
target_score = int(data.get("target_score", 50))
time_remaining = float(data.get("time_remaining", 1800.0))
score_a = int(data.get("score_a", 0))
score_b = int(data.get("score_b", 0))
player_guild_ladder = int(data.get("player_guild_ladder", 1000))
total_wars_won = int(data.get("total_wars_won", 0))