# guild_war_observer_arena_system.gd —— Metin2 40250 官方公会战备观战与武斗擂台传送系统 1:1 # 100% 对照 40250 服务端 war_map.cpp, questlua_pc.cpp (warp_to_guild_war_observer_position), guild_war.cpp class_name GuildWarObserverArenaSystem extends RefCounted signal observer_entered(player_id: int, war_id: int, observer_pos: Vector2) signal observer_left(player_id: int, return_map: String, return_pos: Vector2) signal war_ended_evacuated(war_id: int, observer_count: int) signal observer_error(reason: String) # 40250 官方公会战竞技场地标与地图常数 const MAP_GUILD_ARENA := 110 # 公会战擂台竞技场地图 const MAP_GUILD_FLAG_WAR := 111 # 公会夺旗战场地图 const OBSERVER_PLATFORM_POS := Vector2(2000.0, 2000.0) # 观战席高台坐标 (Start Location Index 2) # 主城公会守卫 NPC (11001, 11003, 11005) const NPC_TOWN_GUARD_SHINSOO := 11001 const NPC_TOWN_GUARD_CHUNJO := 11003 const NPC_TOWN_GUARD_JINNO := 11005 # 正在进行的公会战清单: war_id -> { ... } var _active_wars: Dictionary = {} # 观战者档案: player_id -> { "war_id": int, "saved_map": String, "saved_pos": Vector2, "is_observer": bool } var _observers: Dictionary = {} var _next_war_id: int = 1 # ========================================== # 1. 公会战状态登记与告示 (war_map.cpp) # ========================================== func register_active_war( guild1_id: int, guild1_name: String, guild2_id: int, guild2_name: String, war_type: int = 1, # 1 为 Arena 擂台赛 target_score: int = 50 ) -> int: var wid = _next_war_id _next_war_id += 1 _active_wars[wid] = { "war_id": wid, "guild1_id": guild1_id, "guild1_name": guild1_name, "guild2_id": guild2_id, "guild2_name": guild2_name, "war_type": war_type, "target_score": target_score, "score1": 0, "score2": 0, "map_index": MAP_GUILD_ARENA if war_type == 1 else MAP_GUILD_FLAG_WAR, "is_ended": false, "winner_id": 0 } return wid # 主城守卫公告栏:获取当前全部可观战的公会对决列表 func get_active_wars_for_display() -> Array[Dictionary]: var list: Array[Dictionary] = [] for wid in _active_wars.keys(): var w = _active_wars[wid] if not bool(w.get("is_ended", false)): list.append(w.duplicate(true)) return list # 更新战局比分 func update_war_score(war_id: int, score1: int, score2: int) -> bool: if not _active_wars.has(war_id): return false var w = _active_wars[war_id] w["score1"] = score1 w["score2"] = score2 if score1 >= int(w["target_score"]): end_war(war_id, int(w["guild1_id"])) elif score2 >= int(w["target_score"]): end_war(war_id, int(w["guild2_id"])) return true # ========================================== # 2. 传送入场观战 (pc_warp_to_guild_war_observer_position) # ========================================== func enter_as_observer( player_id: int, war_id: int, current_map: String, current_pos: Vector2 ) -> Dictionary: if not _active_wars.has(war_id) or bool(_active_wars[war_id].get("is_ended", false)): observer_error.emit("WAR_NOT_ACTIVE") return {"ok": false, "reason": "WAR_NOT_ACTIVE", "msg": "该场公会战尚未开启或已经落下帷幕!"} if is_player_observing(player_id): observer_error.emit("ALREADY_OBSERVING") return {"ok": false, "reason": "ALREADY_OBSERVING", "msg": "你已经在观战席中!"} var war = _active_wars[war_id] # 保存玩家原始位置 (ch->SaveExitLocation) _observers[player_id] = { "war_id": war_id, "saved_map": current_map, "saved_pos": current_pos, "is_observer": true, "can_attack": false, # 幽灵模式禁止攻击 "can_be_attacked": false # 幽灵模式免疫任何伤害 } observer_entered.emit(player_id, war_id, OBSERVER_PLATFORM_POS) return { "ok": true, "war_id": war_id, "map_index": war["map_index"], "observer_pos": OBSERVER_PLATFORM_POS, "is_observer_mode": true, "msg": "穿过虚空之门,你抵达了公会战竞技场高台观战席!已开启隐匿观摩形态。" } # 离开观战席重返主城 func leave_observer_mode(player_id: int) -> Dictionary: if not is_player_observing(player_id): observer_error.emit("NOT_OBSERVING") return {"ok": false, "reason": "NOT_OBSERVING", "msg": "你当前并不在观战模式中。"} var obs = _observers[player_id] var return_map = str(obs["saved_map"]) var return_pos = Vector2(obs["saved_pos"].x, obs["saved_pos"].y) _observers.erase(player_id) observer_left.emit(player_id, return_map, return_pos) return { "ok": true, "return_map": return_map, "return_pos": return_pos, "msg": "你退出了观战高台,平安返回了主城集结地。" } func is_player_observing(player_id: int) -> bool: return _observers.has(player_id) and bool(_observers[player_id].get("is_observer", false)) func can_player_attack_or_be_attacked(player_id: int) -> bool: # 若处于观战模式,彻底屏蔽战斗互攻判定 return not is_player_observing(player_id) # ========================================== # 3. 终局结算与观战席全员疏散 (war_map.cpp) # ========================================== func end_war(war_id: int, winner_guild_id: int) -> void: if not _active_wars.has(war_id): return var w = _active_wars[war_id] w["is_ended"] = true w["winner_id"] = winner_guild_id # 遣返该场战斗的所有观战者 var evacuated_count := 0 for pid in _observers.keys().duplicate(): if int(_observers[pid].get("war_id", 0)) == war_id: var obs = _observers[pid] observer_left.emit(pid, obs["saved_map"], obs["saved_pos"]) _observers.erase(pid) evacuated_count += 1 war_ended_evacuated.emit(war_id, evacuated_count) # 序列化与反序列化 func serialize() -> Dictionary: var obs_copy = {} for pid in _observers: var item = _observers[pid].duplicate() item["saved_pos"] = {"x": item["saved_pos"].x, "y": item["saved_pos"].y} obs_copy[pid] = item return { "active_wars": _active_wars.duplicate(true), "observers": obs_copy, "next_war_id": _next_war_id } func deserialize(data: Dictionary) -> void: _active_wars = data.get("active_wars", {}).duplicate(true) _next_war_id = int(data.get("next_war_id", 1)) _observers.clear() var raw_obs = data.get("observers", {}) for pid in raw_obs: var item = raw_obs[pid].duplicate() var p_dict = item.get("saved_pos", {"x": 0.0, "y": 0.0}) item["saved_pos"] = Vector2(float(p_dict.get("x", 0.0)), float(p_dict.get("y", 0.0))) _observers[int(pid)] = item