İçeriğe geç

Metin2 Quest Nasıl Yazılır? Lua Quest Yapısı, State ve When Blokları

Hazırlayan: Metin2VIP Editörleri Yayın: 10 dk okuma

Kısaca: Metin2 questleri Lua 5.0 ile yazılan, quest ad begin, state ad begin ve when olay begin bloklarından oluşan metin dosyalarıdır. Dosya quest klasöründeki quest_list'e eklenir, pre_qc.py ve qc derleyicisiyle object klasörüne derlenir; oyun çekirdeği derlenmiş hali açılışta ya da /reload q komutuyla okur. Oyuncunun hangi durumda olduğu ve quest bayrakları player veritabanındaki quest tablosunda saklanır.
İçindekiler
  1. Quest dosyaları nerede durur?
  2. Bir quest'in iskeleti: quest, state ve when
    1. Lua 5.0 farkları
  3. Durumlar nasıl çalışır?
  4. Bayraklar: quest'in oyuncuya özel hafızası
  5. Diyalog penceresi: say, select, wait, input
  6. Örnek: kurt avı görevi
  7. Quest nasıl derlenir?
  8. Hata ayıklama ve sık yapılan hatalar

Metin2'de questler Lua 5.0 ile yazılan metin dosyalarıdır: bir quest ad begin bloğunun içinde durumlar (state), durumların içinde olay blokları (when) bulunur. Dosya quest klasöründeki quest_list'e eklenir, pre_qc.py ve qc derleyicisiyle object klasörüne derlenir; oyun çekirdeği derlenmiş hali açılışta ya da /reload q komutuyla okur. Oyuncunun quest'te hangi durumda olduğu ve quest'in tuttuğu bayraklar player veritabanındaki quest tablosunda saklanır.

Bu yazı quest dilinin yapısını, durumların ve bayrakların arka planda nasıl çalıştığını ve küçük bir görevin baştan sona yazılıp derlenmesini anlatır. Anlatılanlar incelenen sunucu kaynağına ve paketle gelen quest dosyalarına dayanır. Quest motorunun kendisi orijinal koddan gelir ve sunucuların büyük çoğunluğunda aynıdır; questlib.lua gibi Lua dosyaları ise sunucudan sunucuya değişebilir.

Quest dosyaları nerede durur?

Questlerin hepsi sunucunun yerel ayar klasörünün altındaki quest klasöründedir; turkey yerel ayarında bu srv1/share/locale/turkey/quest olur. Klasörün genel yerleşimi server dosyaları yazısında anlatılıyor; quest klasörünün içi şöyledir:

Dosya ya da klasör Ne işe yarar
collection/ (ya da istediğin başka bir klasör) Quest kaynak dosyaları (.lua)
quest_list Derlenecek kaynak dosyaların listesi, her satırda bir yol, # ile yorum
pre_qc.py define tanımlarını yerine koyup her dosyayı qc ile derleyen Python 2.7 betiği
qc Quest derleyicisi (32 bit FreeBSD ikilisi)
quest_functions Derleyicinin okuduğu fonksiyon adları listesi
object/ Derlenmiş çıktı; oyun çekirdeği yalnız bunu okur
questlib.lua select, say_title, send_letter gibi Lua yardımcıları ve sabitler
locale.lua Questlerin kullandığı metin tablosu (locale.*)
questnpc.txt NPC'lere quest içinde kullanılacak ad verir (9006 oldwoman gibi)
questcategory.txt Görev penceresindeki kategori numaraları (bu kaynakta ek özellik)

Çekirdek açılırken önce C++ fonksiyon tablolarını kaydeder, sonra sırasıyla locale/turkey/settings.lua, quest/questlib.lua, locale/turkey/translate.lua, quest/locale.lua ve object/state altındaki derlenmiş quest dosyalarını yükler. İlk dört dosyadan biri Lua hatası verirse oyun çekirdeği açılmadan kapanır; bu yüzden questlib.lua ya da locale.lua üzerinde yapılan bir yazım hatası bütün kanalı durdurabilir. Derlenmiş quest dosyalarındaki yükleme hatası ise yalnız syslog'a yazılır.

Bir quest'in iskeleti: quest, state ve when

En küçük quest bir quest bloğu, en az bir start durumu ve bir olay bloğundan oluşur:

quest ilk_quest begin
    state start begin
        when 9006.chat."Hal hatır sor" begin
            say_title("Yaşlı Kadın:")
            say("Hoş geldin, yolcu.")
        end
    end
end
  • quest ilk_quest begin ... end quest'in adını verir. Ad en çok 31 karakter olabilir; daha uzun adlı quest'in bayrakları veritabanına yazılmaz.
  • state start begin ... end bir durumdur. Her oyuncu her quest için tek bir durumdadır ve hiç başlamamış oyuncu start durumunda sayılır.
  • when <hedef>.<olay> begin ... end bir olay bloğudur. Hedef bir NPC ya da canavar vnum'u, questnpc.txt'deki bir ad, bir eşya vnum'u ya da bir zamanlayıcı adı olabilir. Hedefsiz olaylar when login begin biçiminde yazılır. Olayların tam listesi quest olayları yazısında.

Yukarıdaki örnekte 9006 Yaşlı Kadın NPC'sidir; questnpc.txt'de bu NPC oldwoman adıyla tanımlı olduğu için when oldwoman.chat."Hal hatır sor" yazmak da aynı işi görür.

Olay bloğuna with ile bir koşul eklenebilir, birden çok olay or ile birleştirilebilir:

when login or levelup with pc.get_level() >= 5 and pc.get_skill_group() == 0 begin
    set_state("run")
end

Derleyici with koşulunu if <koşul> then <gövde> return end biçimine çevirir. Aynı durumda aynı olaya bağlı birden çok when bloğu varsa derlenmiş dosyada art arda dizilir ve koşulu tutan ilk blok çalışır. Durumun içine function ad(...) ... end biçiminde yardımcı fonksiyon da yazılabilir; bu fonksiyon quest'in adıyla, ilk_quest.ad() diye her yerden çağrılır.

Lua 5.0 farkları

Quest motoru Lua 5.0.3 kullanır ve Metin2 için birkaç ekleme yapılmıştır:

  • begin, Lua'daki do sözcüğünün karşılığıdır; if, for, while blokları her zamanki gibi then ... end, do ... end ile yazılır.
  • != ve ~= ikisi de "eşit değil" anlamına gelir, ! da not yerine geçer. && ve || yoktur, and ve or kullanılır.
  • Tablo uzunluğu için # işleci yoktur, table.getn(t) yazılır; kalan için % yerine math.mod(a, b) kullanılır. Değişken sayıda argüman ... ile alınır ve arg tablosuna düşer.
  • quest, state, when, with, define ayrılmış sözcüklerdir, değişken adı olarak kullanılamaz. Quest fonksiyon tablosunun adının quest değil q olmasının nedeni budur.

Durumlar nasıl çalışır?

Durum değiştirmek için set_state("ad") yazılır; setstate, newstate ve q.setstate aynı fonksiyondur. Derleyici tırnaksız yazımı (set_state(run)) da metne çevirir. set_state betiği durdurmaz: ondan sonra yazılan satırlar da çalışır, durum değişikliği betik bitince uygulanır. Geçiş yapılan her durumun quest içinde tanımlı olması gerekir; görevi bitirmek için kullanılan __COMPLETE__ gibi durumlar bu yüzden boş bir blok olarak yazılır:

state __COMPLETE__ begin
end

Değişiklik betik bitince işlenir: önce eski durumun leave olayı, sonra yeni durumun enter olayı, en son yeni durumun letter olayı çalışır. Mektup ve hedef işareti gibi işler bu yüzden genellikle when letter begin bloğuna yazılır.

Derleyici her durum adını bir sayıya çevirir. start her zaman 0'dır, diğerleri adın CRC32 değeridir: run 1349952704 olur. Oyuncunun durumu veritabanına quest tablosunda szName sütununa quest adı, szState sütununa __status, lValue sütununa bu sayı yazılarak saklanır. Bunun pratik sonucu önemlidir: canlı sunucuda bir durumun adını değiştirirsen o durumda bekleyen oyuncuların kaydı artık hiçbir duruma eşleşmez.

Bayraklar: quest'in oyuncuya özel hafızası

Durum dışında tutulması gereken sayılar bayraklara yazılır. pc.setqf("ad", değer) geçerli quest'in, pc.setf("quest_adı", "ad", değer) başka bir quest'in bayrağını yazar; okumak için pc.getqf ve pc.getf kullanılır. Değer tamsayıdır, hiç yazılmamış bayrak 0 okunur ve 0 yazmak kaydı siler. Bayrak adı en çok 63 karakter olabilir.

Bayraklar bellekte tutulur ve karakterle birlikte toplu kaydedilir. Bir bayrak tek oyuncuya aittir; sunucunun geneline ait bir değer gerekiyorsa game.set_event_flag ile olay bayrağı, bir zindana ait değer gerekiyorsa d.setf, bir gruba ait değer gerekiyorsa party.setf kullanılır. Olay bayrakları veritabanı sunucusu üzerinden bütün çekirdeklere dağıtılır ve kalıcıdır; zindan ve grup bayrakları yalnız bellekte durur.

Diyalog penceresi: say, select, wait, input

say("metin") metni hemen göndermez, diyalog tamponuna bir satır olarak ekler. Tampon, betik bir bekleme noktasına geldiğinde ya da bittiğinde tek seferde istemciye gider. Bekleme noktaları şunlardır:

  • select("Evet", "Hayır") seçenek düğmeleri gösterir ve seçilen düğmenin sırasını 1'den başlayarak döndürür.
  • wait() "devam" düğmesi gösterir ve oyuncu tıklayana kadar bekler.
  • input() metin kutusu açar ve yazılanı (en çok 64 karakter) döndürür.
  • confirm(vid, ileti, saniye) başka bir oyuncuya evet/hayır kutusu gösterir.

Bu fonksiyonlar questlib.lua'da tanımlıdır ve betiği bir Lua korutini olarak askıya alır. Diyalog açık kaldığı sürece o oyuncu için başka quest olayı çalışmaz; zamanlayıcı olayları ise diyalog kapanana kadar yarım saniyede bir yeniden denenir. say_title ve say_reward metni başlık ve ödül renginde yazar, setskin(NOWINDOW) pencere hiç açmadan işlem yapmayı sağlar.

Aynı NPC'yi birden çok oyuncunun aynı anda kullanması sorun çıkaracaksa npc.lock() ve npc.unlock() ile NPC o oyuncuya kilitlenebilir.

Örnek: kurt avı görevi

Aşağıdaki görevde oyuncu Yaşlı Kadın ile konuşup görevi alıyor, 10 Kurt öldürüyor, sayaç görev penceresinde azalıyor ve geri dönünce ödülünü alıyor:

quest kurt_avi begin
    state start begin
        when oldwoman.chat."Kurtlar köye iniyor" with pc.get_level() >= 3 begin
            say_title("Yaşlı Kadın:")
            say("Geceleri kurtlar köyün kapısına kadar geliyor.")
            say("Bana 10 kurt öldürür müsün?")
            local s = select("Tamam, hallederim.", "Şimdi olmaz.")
            if s == 2 then
                return
            end
            pc.setqf("oldurulen", 0)
            set_state("avda")
        end
    end

    state avda begin
        when letter begin
            send_letter("Kurt avı")
            q.set_counter("Kalan kurt", 10 - pc.getqf("oldurulen"))
        end

        when button or info begin
            say_title("Kurt avı")
            say("Köyün çevresindeki kurtlardan 10 tane öldür,")
            say("sonra Yaşlı Kadın'a dön.")
            say_reward("Öldürülen: "..pc.getqf("oldurulen").." / 10")
        end

        when 102.kill begin
            local n = pc.getqf("oldurulen") + 1
            pc.setqf("oldurulen", n)
            q.set_counter("Kalan kurt", 10 - n)
            if n >= 10 then
                syschat("Yeterince kurt avladın. Yaşlı Kadın'a dön.")
                set_state("dondu")
            end
        end
    end

    state dondu begin
        when letter begin
            send_letter("Kurt avı: ödül seni bekliyor")
        end

        when oldwoman.chat."Kurtları temizledim" begin
            say_title("Yaşlı Kadın:")
            say("Eline sağlık evladım, bunlar senin.")
            pc.give_exp2(3000)
            pc.give_item2(27001, 20)
            clear_letter()
            set_state("__COMPLETE__")
        end
    end

    state __COMPLETE__ begin
    end
end

Akış adım adım şöyle işler:

  1. Oyuncu 3. seviyeye ulaşmışsa Yaşlı Kadın'a tıkladığında menüde "Kurtlar köye iniyor" seçeneği çıkar. Aynı NPC'ye bağlı bütün quest'lerin chat seçenekleri tek bir menüde toplanır ve sonuna "Kapat" eklenir.
  2. Görevi kabul edince oldurulen bayrağı sıfırlanır ve durum avda olur. Betik bitince letter olayı çalışır: send_letter ekranda görev simgesi gösterir, quest'i görev penceresine ekler ve sayaç satırı yazılır.
  3. Oyuncu simgeye tıklarsa button, görev penceresinden quest'e tıklarsa info olayı çalışır; ikisi de aynı bilgi penceresini açar.
  4. Her kurt öldüğünde 102.kill bloğu bayrağı bir artırır. Kill olayı canavarın EXP'sini alan oyuncuya, yani genellikle en çok hasarı veren oyuncuya gelir.
  5. Onuncu kurtta durum dondu olur ve yeni mektup gelir. Yaşlı Kadın'la konuşunca ödül verilir: pc.give_exp2 EXP'yi hemen ekler, pc.give_item2 20 Kırmızı İksir(K) verir ve envanter doluysa iksirleri oyuncunun ayağına düşürür. clear_letter quest'i görev penceresinden kaldırır.

Ödül için pc.give_item ya da pc.give_exp kullanılacaksa dikkat: bu ikisi etiketli ödül verir, etiketle aynı adlı bayrak 0 değilse ödülü vermez, ama bayrağı kendisi yazmaz. Kullandığın fonksiyonların ayrıntıları quest fonksiyonları yazısında, tam liste quest fonksiyonları listesinde.

Quest nasıl derlenir?

Yeni quest'i kaydedip quest klasöründeki quest_list dosyasına yolunu yazman gerekir:

collection/talk/warehouse.lua
collection/talk/kurt_avi.lua

Derlemeyi paketteki yönetim betiği yapar: sh admin_panel.sh 777 quest klasörüne geçer, qc'ye çalıştırma izni verir ve python2.7 pre_qc.py -ac çalıştırır. Elle yapmak istersen aynı komutu quest klasöründe çalıştırabilirsin:

cd srv1/share/locale/turkey/quest
chmod u+x qc
python2.7 pre_qc.py -ac

pre_qc.py önce listedeki her dosyayı pre_qc/ klasörüne kopyalar ve dosyanın başındaki define satırlarını yerine koyar, sonra object/ klasörünü silip her dosya için ./qc pre_qc/<dosya> çalıştırır. -a seçeneği define içermeyen dosyaları da kopyalar; onsuz -c kullanılırsa kopyalanmayan dosyalar derlenemez. Bir dosyada hata çıkınca betik "Error occured on quest" yazıp durur ve hata, derleyicinin "expecting 'state'" ya da "when doesn't have begin-end clause" gibi iletisiyle ekranda görünür.

define ile quest'in başına sabit yazılabilir; tanımlar quest satırından önce gelmelidir:

define ODUL_IKSIR 27001
quest kurt_avi begin
    ...
            pc.give_item2(ODUL_IKSIR, 20)

Derleyici object/state/kurt_avi dosyasına durum tablosunu, object/oldwoman/chat/, object/102/kill/, object/notarget/letter/ gibi klasörlere de her olay bloğunu ayrı dosya olarak yazar. Oyun çekirdeği bu klasörü açılışta okur. Çalışan sunucuda değişikliği almak için GM_IMPLEMENTOR yetkisiyle /reload q yazılabilir; ancak komut yalnız yazıldığı çekirdekte işler, diğer çekirdekler yeniden başlatılmalı ya da her birinde komut tekrarlanmalıdır. GM komutlarının geri kalanı GM komutları rehberinde.

Hata ayıklama ve sık yapılan hatalar

Çalışma anındaki Lua hataları oyun çekirdeğinin syserr dosyasına LUA_ERROR: quest <ad>.<durum> <olay> biçiminde yazılır; CONFIG'de test sunucusu açıksa aynı satır oyuncuya da gider. sys_err("metin") ve test_chat ile kendi kayıtlarını bırakabilirsin.

  • Derlenen her şey çalışmaz. Derleyici yazım hatalarını yakalar ama tanımsız bir fonksiyon çağrısını her zaman yakalamaz. İncelenen pakette bile, hiçbir yerde tanımlı olmayan bir zindan fonksiyonunu çağıran bir zamanlayıcı bloğu derlenmiş halde duruyor; bu blok çalıştığı anda hata verir. C++ tarafına yeni bir quest fonksiyonu eklediğinde adını quest_functions dosyasına da eklemek gerekir.
  • login içinde bekletme olmaz. Giriş olayında select, wait ya da input ile bekleyen betik zorla kapatılır ve syserr'e "QUEST NOT END RUNNING on Login/Logout" yazılır. Oyuncuyla girişte konuşmak için send_letter ile mektup gönderip diyalogu button ve info olaylarına bırak.
  • Sunucu zamanlayıcısında oyuncu yoktur. server_timer olaylarında geçerli oyuncu bulunmadığından pc, npc, item fonksiyonlarının çoğu orada kullanılamaz ve bazıları çekirdeği çökertebilir. Zindan zamanlayıcıları harita numarasını argüman olarak taşıyıp d.select(get_server_timer_arg()) ile zindanı seçer.
  • Oyuncu zamanlayıcıları ışınlanmada kaybolur. Işınlanma bağlantıyı kapatıp yeniden açtığı için timer ve loop_timer iptal olur, logout ve ardından login olayları da yeniden çalışır.
  • Koordinat birimleri karışmasın. pc.get_x() metre döndürür, pc.warp ise metrenin 100 katı ister: pc.warp(x * 100, y * 100). Zindan fonksiyonlarının bir kısmı metre, bir kısmı oyun birimi alır; listede her fonksiyonun birimi yazıyor.
  • Para ve eşya fonksiyonları sessiz kalabilir. pc.change_money sonucu sıfırın altına düşürecek işlemi hata vermeden yapmaz, pc.remove_item eksik eşyada elindekini siler. Önce pc.get_money ve pc.count_item ile denetle.

Quest'in oyuncu verisi player veritabanında durduğu için yedeklemeyi ve tablo yapısını veritabanları yazısıyla birlikte düşünmek gerekir. Sunucunun ilk kez derlenip açılması ise server kurulumu yazısında adım adım anlatılıyor.

Sık sorulan sorular

Quest dosyasını değiştirdikten sonra sunucuyu yeniden başlatmak gerekir mi?
Gerekmez ama derlemek gerekir. pre_qc.py ve qc ile object klasörünü yeniden üretip oyuna GM_IMPLEMENTOR yetkisiyle /reload q yazmak quest'leri yeniden yükler; ancak komut yalnız yazıldığı oyun çekirdeğinde işler, diğer çekirdekler yeniden başlatılmalı ya da her birinde komut tekrarlanmalıdır.
Quest'te Türkçe karakterler neden bozuk görünüyor?
Türkçe istemci metinleri Windows-1254 (cp1254) kodlamasıyla okur. Quest dosyası UTF-8 kaydedilirse ğ, ş, ı gibi harfler oyunda bozuk çıkar; dosyayı cp1254 olarak kaydetmek gerekir.
Bir durumun adını değiştirirsem ne olur?
Oyuncunun durumu veritabanına adın kendisi değil, adından üretilen bir sayı olarak yazılır. Ad değişince sayı da değişir ve o durumda bekleyen oyuncular hiçbir duruma eşleşmez; canlı sunucuda durum adlarını değiştirmemek gerekir.
Lua'daki # işleci quest'te neden çalışmıyor?
Metin2 quest motoru Lua 5.0 kullanır. Bu sürümde tablo uzunluğu için # yoktur, table.getn kullanılır; kalan için % yerine math.mod yazılır.

İlgili sayfalar

Kategori: Server Kurma Quest Geliştirme quest sunucu kurulumu lua qc quest_list

Bu sayfadaki bilgiler Metin2'nin sunucu yazılımındaki hesaplamalara ve standart oyun dosyalarına dayanır. Oyun dosyalarını değiştiren sunucularda değerler farklı olabilir. İçerikleri nasıl hazırlıyoruz?

Bu sayfa işine yaradı mı?

Giriş yap, oy ver ya da sayfayı kaydet. Okumak için üyelik gerekmez. WhatsApp X'te paylaş
Yanlış ya da eksik bir bilgi mi gördün? Bildir
Neyin yanlış olduğunu, doğrusunun ne olduğunu ve bunu nereden bildiğini yaz. Mesajın editörlere gider; yazı, kontrol edildikten sonra düzeltilir.
Yalnız editör görür.

Tartışma

Kurallar

Henüz yorum yok. Bu konuda bir deneyimin ya da sorun varsa ilk yorumu sen yaz.

Giriş yap ya da kayıt ol, sonra yorum yaz. Metin2VIP üyeliği hiçbir oyun sunucusunun hesabı değildir.