- 桥梁与静态物体高度采样修复: - 严格对齐 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 客户端对拍缺陷发现与修复工程指南
187 lines
5.8 KiB
GDScript
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))
|