# battle_arena_duel_system.gd —— Metin2 40250 官方武斗竞技场切磋与裁判规则 1:1 # 对照 40250 服务端 arena.cpp, arena.h, BattleArena.cpp, questlua_arena.cpp class_name BattleArenaDuelSystem extends RefCounted signal duel_started(opponent_name: String, set_count: int, time_limit: float) signal round_started(current_round: int) signal round_ended(winner_side: String, score_a: int, score_b: int) signal duel_finished(winner_side: String, final_score_a: int, final_score_b: int, reward: Dictionary) signal rule_violation_intercepted(reason: String) const NPC_REFEREE := 20017 # 比武场裁判 (Arena Referee) const ARENA_MAP_INDEX := 112 # 擂台对立出生点 (Point A 玩家, Point B 对手) const START_POINT_A := Vector2(100.0, 100.0) const START_POINT_B := Vector2(200.0, 200.0) const ARENA_BOUNDS := Rect2(80.0, 80.0, 140.0, 140.0) # 擂台活动合法矩形区域 # 决斗状态枚举 const STATE_IDLE := "IDLE" const STATE_COUNTDOWN := "COUNTDOWN" const STATE_FIGHTING := "FIGHTING" const STATE_ROUND_OVER:= "ROUND_OVER" const STATE_FINISHED := "FINISHED" # 单机官方 4 大门派守擂试炼 NPC 导师 const OPPONENT_PRESETS: Dictionary = { "warrior": { "id": "warrior", "name": "战神武者教头", "job": "warrior", "max_hp": 12000, "attack_power": 450, "defense": 200, "skills": ["三连斩", "剑气破空"] }, "assassin": { "id": "assassin", "name": "暗夜游侠神箭手", "job": "assassin", "max_hp": 9000, "attack_power": 520, "defense": 150, "skills": ["连环毒矢", "疾影步"] }, "sura": { "id": "sura", "name": "冥界暗影修罗", "job": "sura", "max_hp": 11000, "attack_power": 480, "defense": 220, "skills": ["幽冥炎刃", "魔灵护体"] }, "shaman": { "id": "shaman", "name": "雷鸣灵术萨满", "job": "shaman", "max_hp": 10000, "attack_power": 400, "defense": 180, "skills": ["天顶奔雷", "金钟守护"] } } # 比赛运行时状态 var match_state: String = STATE_IDLE var current_round: int = 1 var max_sets: int = 3 # 3 局 2 胜 var sets_to_win: int = 2 var time_limit_per_round: float = 300.0 # 5 分钟 var round_timer: float = 300.0 var countdown_timer: float = 3.0 var score_a: int = 0 # 玩家得分 var score_b: int = 0 # 对手得分 var player_pos: Vector2 = START_POINT_A var opponent_pos: Vector2 = START_POINT_B var active_opponent: Dictionary = {} var opponent_current_hp: int = 0 # 启动武斗切磋 func start_duel(opponent_id: String, sets: int = 3, time_limit: float = 300.0) -> Dictionary: if not OPPONENT_PRESETS.has(opponent_id): return {"ok": false, "reason": "INVALID_OPPONENT", "msg": "未知的守擂武者!"} active_opponent = OPPONENT_PRESETS[opponent_id].duplicate(true) max_sets = sets sets_to_win = int(ceil(float(max_sets) / 2.0)) time_limit_per_round = time_limit score_a = 0 score_b = 0 current_round = 1 _prepare_round() duel_started.emit(active_opponent["name"], max_sets, time_limit_per_round) return { "ok": true, "opponent": active_opponent["name"], "max_sets": max_sets, "sets_to_win": sets_to_win, "pos_a": player_pos, "pos_b": opponent_pos } # 准备新回合 func _prepare_round() -> void: match_state = STATE_COUNTDOWN countdown_timer = 3.0 round_timer = time_limit_per_round player_pos = START_POINT_A opponent_pos = START_POINT_B opponent_current_hp = int(active_opponent.get("max_hp", 10000)) # 心跳逻辑推进 func update(delta: float) -> void: if match_state == STATE_COUNTDOWN: countdown_timer -= delta if countdown_timer <= 0.0: match_state = STATE_FIGHTING round_started.emit(current_round) elif match_state == STATE_FIGHTING: round_timer -= delta if round_timer <= 0.0: # 回合超时:双方均未击倒,判定为平局或根据剩余生命决出 _handle_round_over("timeout") # 40250 禁药规则拦截 func use_potion_check(item_vnum: int) -> bool: if match_state == STATE_FIGHTING or match_state == STATE_COUNTDOWN: # 27001~27006 为红蓝药水 if item_vnum >= 27001 and item_vnum <= 27006: rule_violation_intercepted.emit("POTION_FORBIDDEN_IN_ARENA") return false return true # 40250 禁马规则拦截 func mount_check() -> bool: if match_state == STATE_FIGHTING or match_state == STATE_COUNTDOWN: rule_violation_intercepted.emit("MOUNT_FORBIDDEN_IN_ARENA") return false return true # 40250 擂台边界巡检 func check_boundary(pos: Vector2) -> bool: if not ARENA_BOUNDS.has_point(pos): rule_violation_intercepted.emit("OUT_OF_ARENA_BOUNDS") return false return true # 击倒判定处理 func record_defeat(defeated_side: String) -> Dictionary: if match_state != STATE_FIGHTING: return {"ok": false, "reason": "NOT_IN_FIGHTING_STATE"} var round_winner := "" if defeated_side == "B": # 对手倒下,玩家得分 score_a += 1 round_winner = "A" elif defeated_side == "A": # 玩家倒下,对手得分 score_b += 1 round_winner = "B" return _handle_round_over(round_winner) # 回合结束与大局胜负判定 func _handle_round_over(winner_side: String) -> Dictionary: match_state = STATE_ROUND_OVER round_ended.emit(winner_side, score_a, score_b) # 检查大局是否已有胜者 if score_a >= sets_to_win or score_b >= sets_to_win or current_round >= max_sets: match_state = STATE_FINISHED var grand_winner := "A" if score_a > score_b else ("B" if score_b > score_a else "DRAW") var reward: Dictionary = {} if grand_winner == "A": reward = { "arena_points": 100 * sets_to_win, "gold": 500000, "title": "【竞技场常胜军】" } duel_finished.emit(grand_winner, score_a, score_b, reward) return { "ok": true, "match_finished": true, "winner": grand_winner, "score_a": score_a, "score_b": score_b, "reward": reward } # 进入下一回合 current_round += 1 _prepare_round() return { "ok": true, "match_finished": false, "current_round": current_round, "score_a": score_a, "score_b": score_b } # 重置武斗场状态 func reset_arena() -> void: match_state = STATE_IDLE current_round = 1 score_a = 0 score_b = 0 active_opponent.clear()