Metin2 dungeon yapımı, oyunculara grup hâlinde girilen, dalga dalga canavarların geldiği ve sonunda bir boss ile ödül bekleyen özel bir harita deneyimi sunmanın yoludur. Bu rehberde sıfırdan bir zindanı; giriş koşulları, oyuncuları haritaya ışınlama, dalga (wave) sistemi, boss aşaması, süre limiti ve ödül dağıtımıyla birlikte baştan sona Lua quest motoruyla nasıl kurduğunu anlatıyorum. Amaç, kopyala-yapıştır değil, mantığı anlayıp kendi içeriğini tasarlayabilmen.
Dungeon nasıl çalışır?
Metin2'de bir zindan, aslında oyuncuya özel olarak yaratılan bir harita örneğidir (instance). Normal haritalar herkese ortakken, dungeon her grup için ayrı bir kopya olarak doğar; böylece iki farklı parti birbirini görmeden aynı zindanı oynar. Bu örneği oluşturan ve yöneten katman, quest motorundaki d. (dungeon) ad alanıdır.
- Giriş NPC'si veya eşyası — oyuncu bununla etkileşince zindan tetiklenir.
- Harita indeksi (map_index) — zindanın oynanacağı boş haritanın numarası;
indexdosyasında tanımlı olmalı. - Regen/spawn dosyaları — canavarların hangi noktada doğacağını tarif eden metin dosyaları.
- Dungeon flag'leri — o zindana özel sayaçlar (kalan canavar, dalga numarası, başlangıç zamanı)
d.setf/d.getfile tutulur.
Önemli ayrım şu: oyuncuya bağlı veriler pc.setqf ile, zindan örneğine bağlı veriler d.setf ile saklanır. Dalga sayısı tüm partiyi ilgilendirdiği için zindana, kişisel ilerleme ise oyuncuya yazılır.
Giriş koşulları ve haritaya ışınlama
İyi bir zindan, kapısından adil koşullarla girilir. Tipik kontroller: minimum seviye, parti lideri olma şartı, bekleme süresi (cooldown) ve gerekirse bir anahtar eşyası. Aşağıdaki örnek, bir giriş NPC'si ile konuşunca koşulları kontrol edip partiyi yeni bir zindan örneğine ışınlar:
quest dungeon_giris begin
state start begin
when 20355.chat."Zindana gir" begin
if pc.get_level() < 40 then
say("Bu zindana en az 40. seviye girebilir.")
return
end
if party.is_party() and not party.is_leader() then
say("Zindani yalnizca parti lideri acabilir.")
return
end
-- yeni bir harita ornegi yarat ve partiyi isinla
d.new_jump_party(NEW_DUNGEON_MAP_INDEX, 32000, 32000)
end
end
end
Burada d.new_jump_party taze bir harita örneği oluşturur ve partideki herkesi verdiğin koordinata taşır. Tek başına oynanacak bir zindan için d.new_jump_all da kullanılabilir. Koordinatlar haritanın yerel (local) koordinatlarıdır; tüm haritayı kapsayan global koordinatlar değil.
Dalga (wave) sistemini kurmak
Zindanların kalbi dalga sistemidir: bir grup canavar doğar, hepsi öldürülünce bir sonraki dalga gelir, en son boss çıkar. Mantığın temeli iki parçadır — doğurma (d.spawn_mob / d.regen_file) ve sayım (kalan canavar bittiğinde bir sonraki adıma geç). Burada zindana özel d.setf("wave", n) flag'i hayat kurtarır.
-- harita ornegi yaratildiginda calisir
when <map_index>.enter begin
d.setf("wave", 1)
d.notice("1. dalga basliyor!")
d.spawn_mob(101, 32000, 32000)
d.spawn_mob(101, 32050, 32010)
d.spawn_mob(101, 31980, 32030)
end
-- zindandaki bir canavar her oldugunde tetiklenir
when kill begin
if d.count_monster() > 0 then return end -- hala canavar var
local wave = d.getf("wave")
if wave == 1 then
d.setf("wave", 2)
d.notice("2. dalga geliyor!")
d.spawn_mob(102, 32000, 32000)
d.spawn_mob(102, 32040, 32020)
elseif wave == 2 then
d.setf("wave", 3)
d.notice("Boss belirdi!")
d.set_unique("boss", d.spawn_mob(2493, 32000, 32000))
end
end
d.count_monster() haritada kalan canavar sayısını verir; sıfıra inince yeni dalgayı tetiklersin. Boss'u d.set_unique ile isimlendirmek, onu ayrı kontrol edebilmen için önemlidir; aşağıda buna geleceğiz.
Boss aşaması ve zindanı bitirme
Son dalgada genelde tekil bir boss doğar. Bu boss öldüğünde zindan başarıyla tamamlanır: ödül verilir, kısa bir geri sayım gösterilir ve oyuncular dışarı ışınlanır. Boss'u d.set_unique ile etiketlediğimiz için, d.is_unique_dead ile özel olarak onun ölümünü yakalayabiliriz.
when kill with d.is_unique_dead("boss") begin
d.notice("Zindan tamamlandi! Tebrikler.")
-- odul: tum partiye yang ve bir esya
d.kill_all() -- kalan her seyi temizle
d.spawn_mob(20356, 32000, 32000) -- odul sandigi NPC'si
d.exit_all_at_time(10) -- 10 sn sonra herkesi disari isinla
end
Burada d.kill_all() haritadaki artık canavarları temizler, d.exit_all_at_time ise belirttiğin saniye sonunda tüm oyuncuları geri ışınlar. Ödülü doğrudan dağıtmak istersen, boss ölünce parti üyeleri arasında dönüp pc.give_item2 ya da mob_drop ayarlarıyla ganimet bırakabilirsin.
Süre limiti ve başarısızlık koşulu
Zindanlar süre baskısıyla heyecan kazanır. Bir geri sayım sayacı koy; süre dolunca zindan başarısız sayılır ve oyuncular dışarı atılır. Quest motorunun zamanlayıcısı bunun için idealdir:
when <map_index>.enter begin
-- 15 dakikalik sayac baslat
d.set_warp_location(NEW_DUNGEON_MAP_INDEX, 32000, 32000)
server_timer("dungeon_timeout", 60 * 15, get_server_timer_arg())
end
when dungeon_timeout.server_timer begin
d.notice("Sure doldu! Zindan basarisiz.")
d.exit_all()
end
Zamanlayıcıyı bir UI sayacıyla göstermek istersen oyunculara d.set_timer benzeri bir çağrı ya da notice ile periyodik uyarılar gönderebilirsin. Önemli olan, başarı (boss ölümü) gerçekleşince zamanlayıcıyı iptal etmeyi unutmamaktır; aksi halde ödül alındıktan sonra "süre doldu" mesajı düşer.
Zorluğu parti büyüklüğüne göre ölçeklemek
Aynı zindanı hem üç kişilik hem yedi kişilik bir parti oynayacaksa, sabit canavar sayısı dengeyi bozar: kalabalık grup için fazla kolay, küçük grup için imkânsız olur. Çözüm, dalgaları parti boyutuna göre ölçeklemektir. Giriş anında üye sayısını oku ve dalga başına doğacak canavar sayısını ona göre belirle:
when <map_index>.enter begin
local n = 1
if party.is_party() then n = party.get_member_count() end
d.setf("scale", n) -- zindana yaz, sonraki dalgalarda kullan
-- her uye icin bir ek canavar dogur
for i = 1, n do
d.spawn_mob(101, 32000 + i * 30, 32000)
end
end
Boss'un canını da benzer şekilde ölçekleyebilir ya da büyük partilerde ikinci bir mini-boss ekleyebilirsin. Ölçek katsayısını d.setf("scale", n) ile zindana yazarsan, sonraki dalgalar ve ödül hesabı boyunca aynı değere güvenle erişirsin; oyuncu sayısı zindan içinde değişse bile mantık tutarlı kalır.
Sık karşılaşılan hatalar
- Yanlış koordinat sistemi: Global koordinat verince oyuncu haritanın dışına/yanlış yere düşer. Zindan içi ışınlamada her zaman yerel koordinat kullan.
- Sayım hatası:
d.count_monster()yerine kendi sayacını elle azaltırsan, bir canavar yanlışlıkla öldüğünde (örn. düşme hasarı) sayı kayar. Mümkünse motorun saydığı değere güven. - Örnek sızıntısı: Parti çıkınca veya kopunca zindan örneği temizlenmezse boş haritalar birikir. Boss ölümü ve süre dolması iki çıkışı da
d.exit_allile kapatmalı. - flag karışıklığı: Dalga numarasını
pc.setqfile tutarsan her oyuncuda ayrı sayılır ve sistem bozulur. Dalga zindana aittir →d.setf.
Sık Sorulan Sorular
Dungeon için yeni bir harita şart mı?
Zindanın oynanacağı bir harita indeksine ihtiyacın var, ama bu illa sıfırdan çizilmiş bir harita olmak zorunda değil; mevcut boş bir alanı zindan haritası olarak ayırabilirsin. Önemli olan, o indeksin örnek (instance) olarak çoğaltılmaya uygun tanımlanmış olmasıdır.
Tek kişilik ve grup zindanını aynı koddan yapabilir miyim?
Evet. Giriş kısmında party.is_party() ile dallanıp grup için d.new_jump_party, yalnız oyuncu için d.new_jump_all çağırman yeterli. Dalga ve boss mantığı ikisinde de aynı kalır; sadece canavar sayısını/zorluğu parti büyüklüğüne göre ölçekleyebilirsin.
Ödülleri suistimale karşı nasıl korurum?
Ödülü yalnızca boss ölümü tetikleyicisi içinde, zindan flag'iyle "tamamlandı" işaretledikten sonra ver. Aynı örnekte ikinci kez ödül vermeyi d.getf("done") kontrolüyle engelle; süreyle birlikte giriş cooldown'u da ekle.
Sunucun için baştan sona dengeli, sömürüye kapalı bir zindan mı istiyorsun? Giriş koşullarından dalga ve boss mantığına, ödül ve süre sistemine kadar tüm zindanı kurup test edebilirim. Projeni konuşmak için benimle iletişime geç.