Metin2 arena sistemi, oyuncuların güvenli ve adil bir alanda birbirleriyle 1v1 düello yaptığı, sonunda bir kazananın belirlendiği özel bir PvP içeriğidir. Bu rehberde sıfırdan bir dövüş arenasını; oyuncuları eşleştirme, ikilisini izole bir haritaya ışınlama, sadece o iki kişi arasında PvP'yi açma, kazananı tespit etme ve istersen bunu bir turnuva ağacına bağlama mantığıyla baştan sona Lua quest motoruyla nasıl kuracağını anlatıyorum. Amaç hazır kod yapıştırmak değil; sistemin parçalarını anlayıp kendi sunucuna göre uyarlayabilmen.
Arena nasıl çalışır?
Bir düello arenası, temelde üç soruyu çözen bir durum makinesidir: kim kiminle dövüşecek, nerede dövüşecek ve kim kazandı. Metin2'de bunu en temiz şekilde, dövüşü ortak haritadan ayırıp kendine ait bir harita örneğinde (instance) oynatarak çözersin. Böylece iki rakip dışarıdan müdahaleye uğramaz, başka oyuncular maça karışamaz ve kazanan/kaybeden ışınlaması temiz olur.
- Kayıt NPC'si veya eşyası — oyuncu buraya gelip sıraya/eşleşmeye katılır.
- Bekleme kuyruğu (queue) — eşleşmeyi bekleyen oyuncuları tutan liste; en basit hâliyle global bir flag çiftidir.
- Arena harita indeksi — düellonun oynanacağı boş haritanın
indexdosyasında tanımlı numarası. - Durum flag'leri — kimin sırada olduğu, kimin maçta olduğu ve kazananın kim olduğu
pc.setqfve globalset_quest_flagile saklanır.
Önemli ayrım: kişiye bağlı veriler (bu oyuncu maçta mı, rakibinin id'si ne) pc.setqf ile; tüm sunucuyu ilgilendiren veriler (kuyrukta bekleyen var mı) set_quest_flag ile tutulur. Metin2'nin yerleşik düello (challenge) özelliği de vardır ama o, açık haritada karşılıklı meydan okumadır; izole, kontrollü ve turnuvaya uygun bir arena için quest tabanlı kendi sistemini kurmak çok daha esnektir.
Oyuncuları eşleştirmek (matchmaking)
Arenanın ilk parçası kuyruktur. Bir oyuncu kayıt NPC'siyle konuşunca, kuyrukta bekleyen biri varsa onunla eşleşir; yoksa kendisi beklemeye geçer. En basit eşleştirme, "bekleyen oyuncunun id'sini" tutan global bir flag ile yapılır:
quest arena_kayit begin
state start begin
when 20399.chat."Arenaya katil" begin
local benim_id = pc.get_player_id()
local bekleyen = get_quest_flag("arena.bekleyen_pid")
if bekleyen == 0 then
-- kuyruk bos: bu oyuncuyu beklemeye al
set_quest_flag("arena.bekleyen_pid", benim_id)
pc.setqf("arena_durum", 1) -- 1 = sirada bekliyor
say("Bir rakip aranirken bekle...")
return
end
if bekleyen == benim_id then
say("Zaten kuyrukta bekliyorsun.")
return
end
-- eslesme bulundu: kuyrugu temizle ve maci baslat
set_quest_flag("arena.bekleyen_pid", 0)
arena_baslat(bekleyen, benim_id)
end
end
end
Burada get_player_id() oyuncunun benzersiz id'sini verir; bu id, oyuncu haritalar arasında dolaşsa bile değişmez, dolayısıyla rakipleri eşleştirmek için güvenli bir anahtardır. arena_baslat kendi yardımcı fonksiyonumuzdur; iki id'yi alıp dövüşü kurar. Gerçek bir sunucuda kuyruğa seviye/kıyafet sınırı veya bekleyenin hâlâ çevrimiçi olup olmadığı kontrolü de eklersin.
İzole arena haritasına ışınlamak
Eşleşme bulununca iki oyuncuyu da taze bir arena örneğine taşırız. d.new_jump_all yeni bir harita örneği yaratıp çağıran oyuncuyu oraya ışınlar; ikinci oyuncuyu da aynı örneğe sokmak için onun pid'sini hedefleyip aynı harita indeksine warp ederiz. Maç başlamadan önce kısa bir geri sayım, oyunculara hazırlanma fırsatı verir:
function arena_baslat(pid1, pid2)
-- her iki oyuncuya rakibini ve durumunu yaz
pc.select_pc(pid1)
pc.setqf("arena_durum", 2) -- 2 = macta
pc.setqf("arena_rakip", pid2)
pc.warp_to_dungeon... -- asagidaki notlara bak
-- ortak ornek + iki ayri warp icin pratik yontem:
-- once bir oyuncuyla yeni ornek yarat, sonra digerini ayni
-- haritaya pid uzerinden warp et.
end
Pratikte en sağlam yöntem şudur: bir oyuncuyla d.new_jump_all(ARENA_MAP_INDEX, x, y) çağırıp yeni örneği yaratırsın, sonra ikinci oyuncuyu pc.select_pc(pid2) ile seçip aynı örnekteki ikinci başlangıç noktasına warp edersin. Koordinatların haritanın yerel (local) koordinatları olduğunu unutma — iki oyuncuyu karşılıklı iki köşeye koy ki dövüş sahnesi adil başlasın. Işınlama sonrası küçük bir server_timer ile "3... 2... 1... Başla!" geri sayımı göstermek deneyimi ciddi şekilde iyileştirir.
Sadece o iki kişi arasında PvP'yi açmak
Arenanın can alıcı noktası, dövüşün yalnızca eşleşen iki kişi arasında geçmesidir. Bunun en güvenilir yolu, arenayı bir serbest-PvP haritası olarak ayarlamaktır: harita ayarlarında o map_index'i PvP'ye açık işaretlersin, böylece arena içinde herkes saldırabilir; izolasyonu da örnek başına yalnız iki oyuncu sokarak sağlarsın. Quest tarafında ise oyuncuların açık haritalardaki gibi loncalar/lonca-savaşı bayraklarına takılmadan vurabilmesini garantilersin.
-- oyuncu arena haritasina girince
when <ARENA_MAP_INDEX>.enter begin
pc.setqf("arena_durum", 2)
-- kacisi engelle: cikis tuslarini/teleport esyalarini kilitle
pc.remove_affect(...) -- istenmeyen koruma etkilerini kaldir
syschat("Dovus 1v1 arenasindasin. Iyi sanslar!")
end
Çekirdeğe (source) erişimin varsa, en temiz çözüm CHARACTER::IsAttackableMap benzeri bir kontrolü arena map_index'ine açacak şekilde düzenlemek ve barış bölgesi (peace zone) kurallarını arenada devre dışı bırakmaktır. Source'a dokunmadan da, salt-quest yaklaşımıyla haritayı serbest-PvP olarak işaretleyip izolasyonla idare edebilirsin; küçük ve orta ölçekli sunucularda bu fazlasıyla yeterlidir.
Kazananı tespit etmek
Bir düello, taraflardan biri öldüğünde biter. Metin2 quest motorunda when dead tetikleyicisi, ölen oyuncunun bağlamında çalışır; orada o oyuncunun "kaybeden", rakibinin ise "kazanan" olduğunu işaretlersin. Ölen oyuncunun pc.setqf("arena_rakip") içine yazdığımız rakip id'si sayesinde kazananı doğrudan biliriz:
when dead with pc.getqf("arena_durum") == 2 begin
local kaybeden = pc.get_player_id()
local kazanan = pc.getqf("arena_rakip")
-- kaybedeni temizle ve disari isinla
pc.setqf("arena_durum", 0)
pc.setqf("arena_rakip", 0)
pc.warp(ANA_KOY_X * 100, ANA_KOY_Y * 100)
-- kazanani sec, odullendir ve disari al
if pc.select_pc(kazanan) then
pc.setqf("arena_durum", 0)
pc.setqf("arena_rakip", 0)
notice_all(pc.get_name() .. " arenayi kazandi!")
pc.give_item2(50300, 1) -- ornek odul: bir kupa/eswya
pc.warp(ANA_KOY_X * 100, ANA_KOY_Y * 100)
end
end
Dikkat: pc.warp dünya koordinatlarını 100 ile çarpılmış ister (oyun içi karo koordinatı × 100), oysa örnek içi ışınlamada yerel koordinat kullanılır — bu ikisini karıştırmak yeni başlayanların en sık yaptığı hatadır. Ayrıca bağlantı kopması (disconnect) bir kaçış yolu olmasın diye, when logout içinde "maçtaysan rakibin kazanır" kuralını da eklemelisin; aksi halde kaybetmek üzere olan oyuncular fişi çekerek sonucu silebilir.
Turnuva ağacına bağlamak
Tekil düelloyu kurduktan sonra turnuva, aynı mantığın tekrarıdır: kazananlar bir sonraki tura yükselir, kaybedenler elenir. Katılımcı id'lerini sıralı flag'lerde tutarsın (arena.tur1_0, arena.tur1_1, ...), her maç bittiğinde kazananı bir sonraki turun listesine yazarsın ve tur dolunca eşleşmeleri yeniden kurarsın.
- Katılım penceresi: Turnuva başlamadan önce belirli süre kayıt al; süre dolunca listeyi dondur.
- Tek sayı (bye): Katılımcı tek sayıysa birine otomatik geçiş (bye) ver; bunu listeyi kurarken hesapla.
- Sıralı maçlar: Aynı anda tek bir maç oynatıp arenayı meşgul tutabilir ya da birden çok örnek açıp turları paralel koşturabilirsin.
- Final ödülü: Şampiyona özel bir kıyafet, unvan ya da yang ödülünü yalnızca son maçın kazananına ver.
Turnuvayı bir server_timer ile yöneterek "her 5 dakikada bir tur eşleşmelerini kur" gibi bir ritim verebilirsin. Mantık tekil düelloyla birebir aynı kaldığı için, sağlam bir 1v1 çekirdeği kurduysan turnuva sadece o çekirdeğin etrafına örülen bir kabuktur.
Sık karşılaşılan hatalar
- Koordinat sistemi karışıklığı:
pc.warpiçin karo × 100, örnek içi ışınlama için yerel koordinat. Karıştırınca oyuncu haritanın dışına düşer. - Disconnect istismarı:
when logoutkuralı yoksa, kaybedecek oyuncu çıkış yaparak sonucu siler. Çıkışı "rakip kazandı" olarak işle. - Örnek sızıntısı: Maç bitince iki oyuncu da çıkmazsa boş arena örnekleri birikir. Hem ölüm hem çıkış yolunu
warpile kapat. - Flag temizliği:
arena_durumvearena_rakipflag'lerini maç sonunda sıfırlamazsan, oyuncu bir sonraki maça "yarım kalmış" durumla girer ve sistem bozulur.
Sık Sorulan Sorular
Metin2'nin yerleşik düello sistemi yetmiyor mu?
Yerleşik meydan okuma (challenge) açık haritada, herkesin gözü önünde ve müdahaleye açık bir dövüştür. İzole bir alan, adil başlangıç, kazanan ödülü ve özellikle turnuva istiyorsan quest tabanlı kendi arenan çok daha kontrollü ve esnektir. İkisi bir arada da bulunabilir.
Source'a dokunmadan arena yapılır mı?
Evet, çoğu durumda. Arena haritasını serbest-PvP olarak işaretleyip her örneğe yalnız iki oyuncu sokarsan, salt quest mantığıyla çalışan bir 1v1 sistemi elde edersin. Çok ince PvP kuralları (örn. belirli affect'leri zorla kapatma) için source erişimi işleri kolaylaştırır ama şart değildir.
Aynı anda kaç maç oynatabilirim?
Her maç kendi harita örneğinde oynandığı için teorik sınır donanımındır; her örnek bellek ve CPU tüketir. Küçük-orta sunucularda onlarca eşzamanlı maç sorun olmaz. Çok yoğun turnuvalarda örnek sayısını sınırlandırıp maçları sıraya almak daha güvenlidir.
Sunucun için adil, izole ve sömürüye kapalı bir arena mı istiyorsun? Eşleştirme kuyruğundan izole düello haritasına, kazanan tespitinden tam turnuva ağacına kadar sistemi kurup test edebilirim. Projeni konuşmak için benimle iletişime geç.