A Metin2 arena system is custom PvP content where players fight each other in safe, fair 1v1 duels and a winner is decided at the end. In this guide I walk through building a duel arena from scratch with the Lua quest engine: matching players, warping the pair into an isolated map, enabling PvP only between those two, detecting the winner, and — if you want — wiring it into a tournament bracket. The goal isn't to paste finished code; it's to understand the parts so you can adapt the system to your own server.
How an arena works
A duel arena is essentially a state machine that answers three questions: who fights whom, where they fight, and who won. In Metin2 the cleanest way to solve this is to pull the fight off the shared map and run it in its own map instance. That way the two opponents are shielded from outside interference, no other players can crash the match, and the win/loss teleport stays clean.
- A registration NPC or item — the player interacts with it to join the queue/matchmaking.
- A waiting queue — the list of players waiting to be matched; at its simplest, a pair of global flags.
- An arena map index — the number of the empty map, defined in the
indexfile, where the duel is played. - State flags — who is queued, who is in a match, and who won are stored with
pc.setqfand globalset_quest_flag.
The key distinction: per-player data (is this player in a match, what's the opponent's id) goes in pc.setqf; server-wide data (is anyone in the queue) goes in set_quest_flag. Metin2 does have a built-in duel (challenge) feature, but that's an open-map face-off; for an isolated, controlled, tournament-ready arena, building your own quest-based system is far more flexible.
Matching players (matchmaking)
The first part of the arena is the queue. When a player talks to the registration NPC, they get matched with whoever is already waiting; if no one is, they go into the queue themselves. The simplest matchmaking uses a single global flag that holds "the waiting player's id":
quest arena_register begin
state start begin
when 20399.chat."Join the arena" begin
local my_id = pc.get_player_id()
local waiting = get_quest_flag("arena.waiting_pid")
if waiting == 0 then
-- queue empty: put this player on hold
set_quest_flag("arena.waiting_pid", my_id)
pc.setqf("arena_state", 1) -- 1 = queued
say("Waiting while an opponent is found...")
return
end
if waiting == my_id then
say("You are already in the queue.")
return
end
-- match found: clear the queue and start the bout
set_quest_flag("arena.waiting_pid", 0)
arena_start(waiting, my_id)
end
end
end
Here get_player_id() returns the player's unique id; it stays the same even as the player moves between maps, so it's a safe key for pairing opponents. arena_start is our own helper function that takes the two ids and sets up the fight. On a real server you'd also add a level/gear restriction to the queue, or a check that the waiting player is still online.
Warping into an isolated arena map
Once a match is found we move both players into a fresh arena instance. d.new_jump_all creates a new map instance and warps the calling player there; to get the second player into the same instance, we target their pid and warp them to the same map index. A short countdown before the match gives both players a moment to prepare:
function arena_start(pid1, pid2)
-- write opponent and state to both players
pc.select_pc(pid1)
pc.setqf("arena_state", 2) -- 2 = in a match
pc.setqf("arena_opponent", pid2)
-- practical pattern for a shared instance + two warps:
-- first create the new instance with one player, then warp
-- the other to the same map via their pid.
end
In practice the most robust pattern is: call d.new_jump_all(ARENA_MAP_INDEX, x, y) with one player to create the instance, then select the second player with pc.select_pc(pid2) and warp them to the second spawn point in that same instance. Remember the coordinates are the map's local coordinates — place the two players at opposite corners so the fight starts fairly. A small "3... 2... 1... Fight!" countdown via server_timer after the warp dramatically improves the experience.
Enabling PvP between just those two
The crucial point of the arena is that the fight happens only between the two matched players. The most reliable way is to set the arena up as a free-PvP map: in the map settings you flag that map_index as PvP-enabled, so everyone inside the arena can attack — and you guarantee isolation by letting only two players into each instance. On the quest side, you make sure players can strike without being blocked by the guild / guild-war flags that govern open maps.
-- when a player enters the arena map
when <ARENA_MAP_INDEX>.enter begin
pc.setqf("arena_state", 2)
-- block escape: lock teleport items / unwanted protections
pc.remove_affect(...) -- strip unwanted protection effects
syschat("You are in the 1v1 arena. Good luck!")
end
If you have source access, the cleanest solution is to adjust a check like CHARACTER::IsAttackableMap to open the arena map_index and to disable peace-zone rules inside the arena. Without touching the source, you can still manage it with a pure-quest approach by flagging the map as free-PvP and relying on isolation; for small and mid-sized servers that's more than enough.
Detecting the winner
A duel ends when one side dies. In the Metin2 quest engine the when dead trigger runs in the context of the player who died; there you mark that player as the "loser" and their opponent as the "winner." Thanks to the opponent id we stored in pc.setqf("arena_opponent"), we know the winner directly:
when dead with pc.getqf("arena_state") == 2 begin
local loser = pc.get_player_id()
local winner = pc.getqf("arena_opponent")
-- clear the loser and warp them out
pc.setqf("arena_state", 0)
pc.setqf("arena_opponent", 0)
pc.warp(HOME_TOWN_X * 100, HOME_TOWN_Y * 100)
-- select the winner, reward and warp them out
if pc.select_pc(winner) then
pc.setqf("arena_state", 0)
pc.setqf("arena_opponent", 0)
notice_all(pc.get_name() .. " won the arena!")
pc.give_item2(50300, 1) -- example reward: a trophy/item
pc.warp(HOME_TOWN_X * 100, HOME_TOWN_Y * 100)
end
end
Careful: pc.warp expects world coordinates multiplied by 100 (in-game tile coordinate × 100), whereas in-instance warping uses local coordinates — mixing these two up is the most common beginner mistake. Also, so that a disconnect isn't an escape hatch, add a "if you're in a match, your opponent wins" rule inside when logout; otherwise players about to lose can pull the plug and erase the result.
Wiring it into a tournament bracket
Once the single duel works, a tournament is just the same logic repeated: winners advance to the next round, losers are eliminated. You keep the participant ids in sequential flags (arena.round1_0, arena.round1_1, ...), write each match's winner into the next round's list when it ends, and rebuild the pairings when the round fills up.
- Registration window: Accept sign-ups for a set period before the tournament starts; freeze the list when it closes.
- Odd count (bye): If the number of entrants is odd, give one an automatic pass (bye); compute this while building the list.
- Sequential matches: You can run a single match at a time and keep the arena busy, or open multiple instances and run rounds in parallel.
- Final reward: Give a special costume, title or yang prize only to the winner of the last match.
By driving the tournament with a server_timer you can give it a rhythm like "build the round's pairings every 5 minutes." Because the logic is identical to a single duel, once you have a solid 1v1 core the tournament is just a shell wrapped around it.
Common mistakes
- Coordinate confusion: tile × 100 for
pc.warp, local coordinates for in-instance warps. Mix them up and the player drops off the map. - Disconnect exploit: without a
when logoutrule, a player about to lose can log out and erase the result. Treat a logout as "the opponent wins." - Instance leak: if both players don't leave when the match ends, empty arena instances pile up. Close both the death and the exit paths with
warp. - Flag cleanup: if you don't reset the
arena_stateandarena_opponentflags at the end of a match, the player enters their next match in a "half-finished" state and the system breaks.
Frequently Asked Questions
Isn't Metin2's built-in duel system enough?
The built-in challenge is a fight on the open map, in plain view and open to interference. If you want an isolated arena, a fair start, a winner's reward, and especially a tournament, your own quest-based arena is far more controlled and flexible. You can also offer both side by side.
Can you build an arena without touching the source?
Yes, in most cases. If you flag the arena map as free-PvP and let only two players into each instance, you get a 1v1 system that runs on pure quest logic. For very fine-grained PvP rules (e.g. forcibly removing certain affects) source access makes things easier, but it isn't required.
How many matches can run at once?
Since each match runs in its own map instance, the theoretical limit is your hardware; every instance consumes memory and CPU. On small-to-mid servers, dozens of concurrent matches are no problem. In very busy tournaments it's safer to cap the number of instances and queue the matches.
Want a fair, isolated, exploit-proof arena for your server? From the matchmaking queue to the isolated duel map, from win detection to a full tournament bracket, I can build and test the whole system. To talk about your project, get in touch with me.