İçeriğe geç

Metin2 Quest Örnekleri: Işınlayıcı NPC, Ödül Sandığı, Günlük Görev

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

Kısaca: Bu yazıdaki altı quest, paketteki quest motoru ve questlib.lua ile derlenip çalışacak biçimde yazıldı: ışınlayıcı NPC'de select_table ve pc.warp, ödül sandığında ağırlıklı number zarı, günlük görevde get_time ile gün numarası ve kill olayı, seviye ödüllerinde login ve levelup, etkinlikte game.get_event_flag, duyuruda server_loop_timer kullanılır. Her dosya quest_list'e eklenip derlenir.
İçindekiler
  1. 1. Işınlayıcı NPC: tablodan menü
  2. 2. Ödül sandığı: ağırlıklı zar
  3. 3. Günlük görev: günde bir ork avı
  4. 4. Seviye ödülleri: atlanan seviyeyi kaçırmadan
  5. 5. GM'in açtığı etkinlik
  6. 6. Saatlik sunucu duyurusu
  7. Örnekleri sunucuya eklemek

Aşağıdaki altı quest, bu sunucu paketindeki quest motoruna ve questlib.lua'ya göre yazıldı; kullanılan her fonksiyon quest_functions listesinde ve oyun çekirdeğinin kayıtlarında var. Her örnek tek başına bir dosyadır: dosyayı quest klasörüne koyup yolunu quest_list'e yazmak ve derlemek yeter. Dil kuralları, durumlar ve derleme quest yazma yazısında, fonksiyonların ayrıntısı quest fonksiyonları yazısında anlatılıyor; burada doğrudan kullanılabilecek kalıplar var.

Dosyaları Windows-1254 kodlamasıyla kaydet, yoksa Türkçe harfler oyunda bozuk görünür. Ücretler, ödüller ve seviye şartları örnektir, kendi sunucuna göre değiştir.

1. Işınlayıcı NPC: tablodan menü

Gidilecek yerleri bir tabloya yazmak, her yeni hedef için yalnız bir satır eklemeyi sağlar. Koordinatlar paketteki ışınlayıcı quest'inin kullandığı noktalardır ve krallıktan bağımsızdır.

quest uzak_isinlama begin
    state start begin
        when 9012.chat."Uzak bölgelere git" begin
            local yerler = {
                -- ad, seviye, Yang, x, y
                {"Hwang Tapınağı", 52, 20000, 590500, 110500},
                {"Hayalet Ormanı", 65, 40000, 290000, 5600},
                {"Kırmızı Orman", 74, 60000, 1119500, 70200},
            }
            local menu = {}
            for i = 1, table.getn(yerler) do
                menu[i] = yerler[i][1]
            end
            table.insert(menu, "Vazgeç")

            say_title("Işınlayıcı:")
            say("Nereye gitmek istiyorsun?")
            local s = select_table(menu)
            if s > table.getn(yerler) then
                return
            end

            local y = yerler[s]
            if pc.get_level() < y[2] then
                say(string.format("%s için en az %d. seviye olmalısın.", y[1], y[2]))
                return
            end
            if pc.get_gold() < y[3] then
                say(string.format("Bu ışınlanma %d Yang tutuyor.", y[3]))
                return
            end
            if not pc.can_warp() then
                say("Açık pencereleri kapatıp biraz sonra tekrar dene.")
                return
            end
            pc.change_money(-y[3])
            pc.warp(y[4], y[5])
        end
    end
end

Işınlayıcı bütün köylerde durduğu için seçenek her yerde çıkar; aynı NPC'nin mevcut ışınlama menüsü de yerinde kalır, iki quest'in seçenekleri tek menüde toplanır. select_table seçilen satırın sırasını döndürür. Yang, ışınlanmanın engellenmeyeceği anlaşıldıktan sonra alınır: pc.can_warp() ticaret, pazar ya da depo penceresi açıkken false döner ve bu durumda Yang gitmez. Koordinatların birimi ve haritaların oyuncu tarafı ışınlanma yazısında.

2. Ödül sandığı: ağırlıklı zar

Sandık, item_proto'ya ITEM_QUEST türüyle eklenmiş bir eşyadır; bu türdeki eşya kullanılınca when <vnum>.use olayı çalışır. Örnekteki 95000 varsayılan item_proto'da boş bir numaradır; eşyayı istemcinin item_proto'suna ve simge listesine de eklemek gerekir, sütunlar item_proto yazısında.

quest odul_sandigi begin
    state start begin
        when 95000.use begin
            if pc.get_empty_inventory_count() < 1 then
                syschat("Envanterinde boş yer yok.")
                return
            end

            -- vnum, adet, ağırlık
            local havuz = {
                {27002, 20, 40},
                {27005, 20, 40},
                {71084, 2, 15},
                {25040, 1, 5},
            }
            local toplam = 0
            for i = 1, table.getn(havuz) do
                toplam = toplam + havuz[i][3]
            end

            local zar = number(1, toplam)
            local odul = havuz[1]
            for i = 1, table.getn(havuz) do
                zar = zar - havuz[i][3]
                if zar <= 0 then
                    odul = havuz[i]
                    break
                end
            end

            pc.remove_item(95000, 1)
            pc.give_item2(odul[1], odul[2])
            say_title("Ödül Sandığı")
            say_item_vnum(odul[1])
            say_reward(odul[2].." adet "..item_name(odul[1]))
        end
    end
end

Her satırın çıkma şansı, ağırlığının toplam ağırlığa oranıdır; satır eklemek ya da ağırlığı değiştirmek diğerlerinin şansını da değiştirir. Sandık, ödülden önce envanterden düşülür. item.remove() yerine pc.remove_item(95000, 1) kullanıldı, çünkü ilki bütün yığını siler. Havuzda Efsun Nesnesi ve Kırmızı İksir(O) gibi yığınlanabilen eşyalar var; tek boş yer denetimi bu yüzden yeterli. Yalnız rastgele eşya veren bir sandık için quest gerekmez, aynı iş special_item_group ile yapılır; biçimi drop dosyaları yazısında.

3. Günlük görev: günde bir ork avı

Oyuncu Şehir Bekçisi'nden görevi alır, 30 ork öldürür, ödülünü alır ve ertesi güne kadar görevi yeniden alamaz.

quest gunluk_ork begin
    state start begin
        function bugun()
            return math.floor((get_time() + 10800) / 86400)
        end

        when 20354.chat."Günlük görev: ork avı" with pc.get_level() >= 25 begin
            say_title("Şehir Bekçisi:")
            if pc.getqf("son_gun") == gunluk_ork.bugun() then
                say("Bugünkü işini yaptın, yarın yine gel.")
                return
            end
            say("Orklar yolları kesiyor. 30 tanesini temizler misin?")
            if select("Kabul ediyorum", "Şimdi değil") == 2 then
                return
            end
            pc.setqf("sayac", 0)
            set_state("avda")
        end
    end

    state avda begin
        when letter begin
            send_letter("Günlük görev: ork avı")
            q.set_counter("Kalan ork", 30 - pc.getqf("sayac"))
        end

        when button or info begin
            say_title("Ork avı")
            say_reward("Öldürülen: "..pc.getqf("sayac").." / 30")
        end

        when 601.kill or 602.kill or 603.kill or 604.kill begin
            local n = pc.getqf("sayac") + 1
            pc.setqf("sayac", n)
            q.set_counter("Kalan ork", 30 - n)
            if n >= 30 then
                syschat("Görev tamam, Şehir Bekçisi'ne dön.")
                set_state("dondu")
            end
        end
    end

    state dondu begin
        when letter begin
            send_letter("Ork avı: ödülünü al")
        end

        when 20354.chat."Orkları temizledim" begin
            say_title("Şehir Bekçisi:")
            say("Eline sağlık, yarın yine beklerim.")
            pc.give_exp2(50000)
            pc.change_money(30000)
            pc.setqf("son_gun", gunluk_ork.bugun())
            clear_letter()
            set_state("start")
        end
    end
end

Günü saymak için bitiş zamanı yerine gün numarası tutuldu: get_time() Unix zamanıdır ve UTC'dir, 10.800 saniye eklemek günü Türkiye saatiyle gece yarısında değiştirir. Böylece akşam görevi bitiren oyuncu 24 saat beklemez, ertesi sabah yeniden alır. start içinde tanımlanan bugun fonksiyonu gunluk_ork.bugun() diye her durumdan çağrılır. Dört olay or ile bağlandığı için Ork, Ork Gözcüsü, Ork Savaşçısı ve Ork Büyücüsü'nün her biri sayaca yazılır. Kill olayı canavarın EXP'sini alan oyuncuya gelir; grup avında sayılmasını istiyorsan olaylar yazısındaki party_kill'e bak. Görev yalnız avda durumundaki oyuncuda dinlendiği için diğer oyuncuların ork öldürmesi bu quest'i hiç çalıştırmaz.

4. Seviye ödülleri: atlanan seviyeyi kaçırmadan

Ödülü levelup olayında == 30 diye vermek iki durumda ödülü kaçırır: tek seferde birkaç seviye atlayan oyuncu ve ödül sonradan eklendiğinde zaten 30'u geçmiş karakterler. Liste ve bayrakla her ikisi de kapanır:

quest seviye_odulleri begin
    state start begin
        function ver()
            local liste = {
                -- seviye, vnum, adet
                {10, 27002, 50},
                {20, 71084, 5},
                {30, 25040, 2},
            }
            for i = 1, table.getn(liste) do
                local bayrak = "odul_"..liste[i][1]
                if pc.get_level() >= liste[i][1] and pc.getqf(bayrak) == 0 then
                    if pc.get_empty_inventory_count() < 1 then
                        syschat("Seviye ödülün için envanterde yer aç.")
                        return
                    end
                    pc.setqf(bayrak, 1)
                    pc.give_item2(liste[i][2], liste[i][3])
                    syschat(liste[i][1]..". seviye ödülü: "..item_name(liste[i][2]))
                end
            end
        end

        when login or levelup begin
            seviye_odulleri.ver()
        end
    end
end

Giriş olayında select ya da wait gibi bekleyen fonksiyonlar kullanılmaz, bekleyen betik zorla kapatılır; bu yüzden bilgi syschat ile verilir. Envanter doluysa bayrak yazılmaz ve ödül bir sonraki girişte ya da seviyede yeniden denenir.

5. GM'in açtığı etkinlik

Etkinlik bir olay bayrağıyla açılıp kapanır. Bayrağın değeri etkinliğin numarasıdır; her oyuncu her numaradan bir kez hediye alır.

quest bayram_etkinligi begin
    state start begin
        when login with game.get_event_flag("bayram") > 0 begin
            syschat("Bayram etkinliği açık, Uriel'e uğra.")
        end

        when 20011.chat."Bayram hediyesi" with game.get_event_flag("bayram") > 0 begin
            local no = game.get_event_flag("bayram")
            say_title("Uriel:")
            if pc.getqf("alinan") == no then
                say("Bu etkinliğin hediyesini zaten aldın.")
                return
            end
            if pc.get_empty_inventory_count() < 1 then
                say("Envanterinde yer aç, sonra gel.")
                return
            end
            pc.setqf("alinan", no)
            pc.give_item2(71084, 3)
            say("İyi bayramlar.")
        end
    end
end

GM etkinliği /eventflag bayram 1 ile açar, /eventflag bayram 0 ile kapatır; komut GM_HIGH_WIZARD yetkisi ister. Bir sonraki bayramda /eventflag bayram 2 yazılır ve herkes yeniden hediye alabilir. Olay bayrakları db üzerinden bütün çekirdeklere dağıtılır ve yeniden başlatmada korunur. mob_exp, mob_item gibi bazı adlar oyunun oranlarını değiştirdiği için kendi bayrağına bunlardan farklı bir ad ver; o adlar etkinlik bonusları yazısında. Uriel yalnız birinci köylerde durur; etkinliği başka bir NPC'ye taşımak için vnum'u değiştirmek yeter.

6. Saatlik sunucu duyurusu

Sunucu zamanlayıcısı bir oyuncuya bağlı değildir ve çekirdek açık kaldıkça çalışır:

quest saatlik_duyuru begin
    state start begin
        when 20011.chat."GM: duyuruyu başlat" with pc.is_gm() begin
            clear_server_timer("duyuru_saat", 1)
            server_loop_timer("duyuru_saat", 3600, 1)
            say("Duyuru saatte bir gönderilecek.")
        end

        when 20011.chat."GM: duyuruyu durdur" with pc.is_gm() begin
            clear_server_timer("duyuru_saat", 1)
            say("Duyuru durduruldu.")
        end

        when duyuru_saat.server_timer begin
            notice_all("Etkinlik ve bakım duyuruları için Uriel'e uğrayın.")
        end
    end
end

Bu örnekte üç ayrıntı bu kaynağa özgüdür ya da kolay atlanır:

  • Argüman 0 olmamalı. Bu kaynakta clear_server_timer argümanı 0 olan zamanlayıcıyı silmez ve syserr'e LUA PREVENT: Wrong argument on ClearServerTimer! yazar. Zamanlayıcı bu yüzden 1 argümanıyla kurulur ve silinir.
  • Önce sil, sonra kur. Aynı ad ve argümanla ikinci kez kurulan zamanlayıcı syserr'e already registered server timer yazdırır, listeye girmez ama çalışmaya devam eder ve artık silinemez; duyuru çift gelir. Başlatırken önce silmek bunu önler.
  • Olay start durumunda olmalı. Sunucu zamanlayıcısı oyuncu olmadan çalıştığı için quest her zaman start durumunda sayılır; paketteki Şeytan Kulesi quest'i de bütün zamanlayıcılarını orada tutar. notice_all duyuruyu P2P üzerinden bütün çekirdeklere yollar.

Zamanlayıcı yalnız kurulduğu çekirdekte yaşar ve çekirdek yeniden başlatılınca kaybolur. pc.is_gm() ile gizlenen seçenekler CONFIG'de test sunucusu açıkken herkese görünür, çünkü o ayarda herkes GM sayılır. GM yetkileri GM komutları rehberinde.

Örnekleri sunucuya eklemek

Her dosyayı örneğin collection/custom/ altına koy, yolunu quest_list'e yaz ve quest klasöründe derle; ardından değişikliği almak için çekirdekleri yeniden başlat ya da her çekirdekte /reload q yaz. Çalışırken oluşan hatalar syserr'e LUA_ERROR: quest <ad>.<durum> <olay> biçiminde düşer. Olayların tam listesi quest olayları yazısında, bütün fonksiyonlar quest fonksiyonları listesinde.

Sık sorulan sorular

Günlük görev gece yarısı mı sıfırlanıyor?
get_time() sunucunun Unix zamanını, yani UTC'yi verir. Örnekteki gün numarası 10.800 saniye (3 saat) eklenerek hesaplandığı için gün Türkiye saatiyle gece yarısı değişir. Başka bir saat diliminde bu sayıyı değiştirmek gerekir.
server_loop_timer ile kurduğum duyuru neden clear_server_timer ile durmuyor?
Bu kaynakta clear_server_timer argümanı 0 olan zamanlayıcıyı silmez, syserr'e 'LUA PREVENT: Wrong argument on ClearServerTimer!' yazar. Zamanlayıcıyı 0'dan farklı bir argümanla kurup aynı argümanla silmek gerekir. Aynı ad ve argümanla ikinci kez kurulan zamanlayıcı ise listeye girmeden çalışmaya devam eder ve hiç silinemez.
Ödül sandığını special_item_group ile mi quest ile mi yapmalıyım?
Yalnız rastgele eşya veriliyorsa special_item_group daha kısa yoldur, quest gerekmez. Seviye, krallık ya da bayrak denetimi, diyalog veya Yang ve EXP'nin hesapla verilmesi gerekiyorsa quest kullanılır.

İlgili sayfalar

Kategori: Server Kurma Quest Geliştirme quest sandık ışınlayıcı lua günlük görev

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.