Partea cea mai vizibilă a unui dungeon e trecerea de la o etapă la alta: „ai omorât toți monștrii, se deschide ușa”, „ai spart piatra, apare bossul”. Tot aici apar și cele mai grele bug-uri: grupuri blocate pentru că serverul a numărat greșit monștrii, etaje sărite și chiar căderi ale serverului (crash) care apar abia după câteva ore. În această lecție înveți modelul pe care îl folosesc dungeon-urile stabile: **evenimentul doar notează, ceasul decide**.
when kill doar setezi un semn („piatra a căzut”). Un server_timer care se verifică singur la câteva secunde citește semnul și face trecerea: oprește spawnul, curăță monștrii cu d.kill_all(), pune bossul. Testează fiecare etapă separat.Cuvinte pe care le vei întâlni
| Cuvânt | Ce înseamnă |
|---|---|
when kill | Bucata de quest care rulează când un monstru moare. |
server_timer | Un ceas de quest legat de instanță. Rulează cod după X secunde. |
d.count_monster() | Câți monștri vii (inclusiv pietre) sunt în instanță. |
d.regen_file / d.set_regen_file | Pune monștrii dintr-un fișier de spawn. Al doilea îi și reînvie periodic. |
d.clear_regen() | Oprește reînvierea. Nu șterge monștrii deja existenți. |
d.kill_all() | Omoară toți monștrii din instanță. |
| Crash | Căderea procesului game. Jucătorii sunt deconectați. |
Cum fac questurile din pachet
Am citit două dungeon-uri reale din pachetul verificat:
flame_dungeon.quest: după ce pune monștrii unei camere, armează ceasulkilled_A_1. La fiecare 6 secunde, ceasul verificăd.count_monster() <= 0. Dacă mai sunt monștri, se re-armează singur. Dacă nu mai sunt, anunță grupul și deschide etapa următoare.deviltower_zone.quest: la etajul 4 pune 7 pietre, dintre care una e „cea adevărată” (d.set_unique("real", vid)). Un ceas verifică la 10 secunde dacă piatra adevărată a murit, cud.is_unique_dead("real").
Ambele folosesc același principiu: un ceas verifică starea, nu fiecare monstru ucis.
De ce nu faci totul direct în when kill
Când un monstru moare, codul din when kill rulează în timp ce serverul încă „termină” monstrul: împarte EXP-ul, dă dropul, abia apoi îl șterge. Dacă în acel moment ștergi brusc toți monștrii din hartă, serverul poate rămâne cu o „adresă” spre un monstru care nu mai există. Rezultatul nu e un crash imediat, ci memorie stricată, care cade mai târziu, la un kill oarecare. E unul dintre cele mai greu de găsit bug-uri.
Regula practică:
- În
when kill: setezi semne (d.setf), numeri, dai un drop mic (game.drop_item). Cel mult pui un singur NPC. - În
server_timer: schimbi etapa, oprești spawnul, curăți harta (d.kill_all()), pui valurile noi și bossul.
d.kill_all() sau d.purge()?
Ambele golesc instanța, dar diferit. În sursa verificată, d.purge() distruge pe loc fiecare monstru, chiar în timp ce serverul parcurge lista lor. d.kill_all() îi omoară normal, iar ștergerea se face mai târziu, în siguranță.
Pentru un începător, regula e simplă: folosește d.kill_all(), niciodată d.purge() într-un eveniment de kill. Vei găsi d.purge() și în unele questuri din pachete. Asta nu înseamnă că e sigur pe sursa ta: pe un server cu pietre care cheamă monștri la spargere, a produs crash-uri repetate.
Un detaliu de verificat la tine: în sursa clasică, monștrii omorâți cu d.kill_all() care fuseseră deja loviți de jucători pot lăsa drop. În unele surse modificate, d.kill_all() nu lasă niciun drop. Testează pe serverul de test ce face la tine.
Pasul 1: Etapa „omoară toți monștrii”
when verifica_camera.server_timer begin
if d.select(get_server_timer_arg()) then
if d.getf("level") == 1 then
local n = d.count_monster()
if n > 0 then
d.setf("vazut", 1)
end
if d.getf("vazut") == 1 and n <= 0 then
d.setf("level", 2)
d.notice("Camera e curata! Sparge piatra Metin.")
d.spawn_mob(PIATRA, 320, 394)
end
end
if d.getf("level") < 4 then
server_timer("verifica_camera", 5, get_server_timer_arg())
end
end
end| Bucată | Ce face |
|---|---|
d.select(get_server_timer_arg()) | „Lucrez pe instanța pentru care a fost armat ceasul.” Dacă instanța nu mai există, nu face nimic. |
d.count_monster() | Câți monștri vii mai sunt. |
d.setf("vazut", 1) | Notează că am văzut cel puțin un monstru. |
d.getf("vazut") == 1 and n <= 0 | Trecem mai departe doar dacă au existat monștri și acum nu mai sunt. |
server_timer(..., 5, ...) | Se re-armează peste 5 secunde, cât timp dungeonul nu s-a terminat. |
De ce „vazut”? Imediat după d.regen_file, monștrii pot apărea abia la următorul „puls” al serverului. Dacă verifici în aceeași clipă, d.count_monster() poate da 0 și grupul sare etapa fără să fi omorât nimic. Semnul „vazut” și o primă verificare după câteva secunde rezolvă problema.
Ceasul se pornește o singură dată, la intrare, de exemplu cu server_timer("verifica_camera", 5, d.get_map_index()), exact cum face flame_dungeon.quest.
Pasul 2: Piatra Metin și bossul final
when PIATRA.kill with pc.in_dungeon() and d.getf("level") == 2 begin
d.setf("piatra_cazuta", 1)
endAtât. Evenimentul doar notează. Apoi, în același ceas de mai sus, adaugi o ramură:
if d.getf("level") == 2 and d.getf("piatra_cazuta") == 1 then
d.setf("piatra_cazuta", 0)
d.setf("level", 3)
d.clear_regen()
d.kill_all()
d.notice("Piatra a cazut! Bossul s-a trezit.")
d.spawn_mob(BOSS, 686, 637)
endObservă condiția with ... d.getf("level") == 2. Fără ea, o piatră identică dintr-o altă etapă ar declanșa bossul. Fiecare etapă are un singur declanșator, valabil doar în etapa ei.
Pasul 3: Oprește ceasurile la final
Un ceas care se re-armează la nesfârșit e o resursă irosită. În exemplul nostru, ceasul se oprește singur când level ajunge la 4 (bossul a murit). Ceasurile cu nume fix le poți opri și explicit, ca în pachet: clear_server_timer("verifica_camera", d.get_map_index()). Când instanța dispare, d.select întoarce fals și ceasul nu mai face nimic.
Despre monștri, grupuri și pietre ai mai citit în grupuri de monștri și respawnul pietrelor Metin.
Ce ar trebui să vezi: după ultimul monstru, în maxim 5 secunde apare anunțul și piatra; după piatră, monștrii rămași dispar și apare bossul. Serverul rulează ore întregi fără erori în syserr.
Greșeli des întâlnite
| Ce vezi | Ce înseamnă | Ce faci |
|---|---|---|
| Grupul sare etapa imediat după intrare | count_monster a dat 0 înainte să apară monștrii | Adaugă semnul „vazut” și o primă verificare după 3–5 secunde. |
| Grupul rămâne blocat, deși nu mai vede monștri | A rămas o piatră sau un monstru ascuns, care se numără | Caută-l pe hartă; verifică fișierul de spawn. |
| Crash la câteva ore după ce ai adăugat dungeonul | Ștergere de monștri în when kill, probabil cu d.purge() | Mută curățenia în ceas și folosește d.kill_all(). |
| Bossul apare de două ori | Declanșatorul nu are condiția de etapă | Adaugă d.getf("level") == ... și resetează semnul. |
| Monștrii continuă să apară după boss | Ai uitat d.clear_regen() | Oprește spawnul la fiecare trecere. |
Întrebări frecvente
De ce 5 secunde și nu 1?
O verificare la o secundă, în zeci de instanțe deodată, încarcă serverul degeaba. 3–6 secunde e un compromis bun; jucătorii nu simt diferența.
Pot folosi numărul total de kill-uri ca progres?
Nu e recomandat. Monștrii din alte etape, pietrele și monștrii chemați de pietre se amestecă. Folosește o condiție clară pentru fiecare etapă.
Cât testez?
Mult. Fiecare etapă, separat, de mai multe ori, apoi tot dungeonul cap-coadă, cu grup complet și incomplet, pe serverul de test.
Ce urmează
Bossul a murit. Acum vine partea sensibilă: recompensele fără pierderi și fără dubluri. Pentru dropul bossului în sine, vezi și cum testezi dropul unui boss.
Toate lecțiile sunt în lista tutorialelor Metin2.
Server Metin2 gata de joc
Găzduire Metin2 în România, cu instalare de serverfiles, bază de date MySQL și protecție Anti-DDoS inclusă.