Auch wenn die Pros hier bestimmt gleich viel zu meckern haben denke ich, dass ich in der Lage bin, den Anfängern unter uns etwas beizubringen. Mir ist bewusst, dass es schon gute Guides gibt ( , ) allerdings fehlt mir da die eine oder andere Sache. Ich hoffe, dass ich einigen mit diesem Guide weiterhelfen kann und verbessert mich bitte, falls ich hier etwas falsches schreibe.
Einleitung
Die Quests in Metin2 basieren auf der Programmier-/Skriptsprache LUA ( Lua ? Wikipedia ). Man kann also (fast) alle LUA-Funktionen problemlos in Quests anwenden. Ein paar Ausnahmen gibt es (z.B. mag der Questcompiler modulo (%) überhaupt nicht, weshalb ich auch eine Questfunktion dafür geschrieben und veröffentlicht habe.
Nice to know / FAQ
Was ist der Unterschied zwischen "pc.give_item()" und pc.give_item2()
Hierzu können wir einen kurzen Blick in den Sourcecode werfen:
Code:
{ "give_item2", pc_give_or_drop_item },
Muss eine Quest immer die Dateiendung .quest haben?
Nein! Man kann eine Quest auch mit der Dateiendung .roflxd abspeichern und problemlos benutzen. Solange man die Dateiendung auch in der quest_list / locale_list einträgt ist es vollkommen egal, welche Dateiendung man wählt.
Was macht der Questcompiler?
Der Questcompiler (meistens "qc" / "qc_64") wandelt die von Euch geschriebenen Quests in reinen LUA Code um.
Mehrere Quests in einer Datei?
Es ist kein Problem mehrere Quests in einer Datei zu speichern. Ob das Sinn ergibt ist eine andere Sache, das muss jeder für sich entscheiden.
Gibt es eine Liste mit allen Questfunktionen?
Ja, die gibt es. Alle "aktivierten" Questfunktionen findet ihr in Eurer quest_functions-Datei im Questordner, allerdings kann es auch mal vorkommen, dass eine Questfunktion in der Game existiert und nicht in den quest_functions eingetragen ist.
Hier die quest_functions aus den aktuellen Viruz-Files:
Welche Questfunktionen benutzt du am häufigsten?
Wie füge ich neue Questfunktionen ein?
Einfach in die questlib.lua gehen und die Funktion dort einfügen, anschließend in der quest_functions-Datei den Funktionsnamen (ohne "()") eintragen. Zum reloaden der Questfunktionen reicht es, Ingame "reload q" einzugeben!
Die Quest-Trigger und wann sie auslösen
- enter
- login
- logout
- levelup
- servertimer
- timer
- click
- chat
- kill
- use
- take
- button
- info
Code:
enter -> wenn die State betreten wird login -> wenn der Spieler sich einloggt logout -> wenn der Spieler sich ausloggt levelup -> wenn der Spieler ein Level aufsteigt servertimer -> (name.servertimer) wenn der Servertimer "name" ausgelöst wird timer -> (name.timer) wenn der Timer "name" ausgelöst wird click -> (npcvnum.click) wenn der NPC "npcvnum" angeklickt wird chat -> (npcvnum.chat."Text") wenn die Auswahl "Text" bei dem NPC "npcvnum" ausgewählt wird kill -> wenn der Spieler ein Monster oder einen Spieler tötet use -> (vnum.use) wenn der Spieler "vnum" benutzt (muss Type 18 sein!) take -> (npcvnum.take) wenn der Spieler ein Item auf "npcvnum" zieht button -> Questbutton (Links an der Seite) info -> Questbutton (Im Questreiter des Charakterfensters)
Die erste Quest
Jetzt fangen wir also an, unsere erste Quest zu schreiben. Eine Quest fängt immer wie folgt an:
Code:
quest questname begin
Danach kommt die state, man fängt immer mit state start an. Das ist die state in der jede Quest startet
Code:
quest questname begin state start begin
Code:
quest questname begin state start begin when login begin
Code:
quest questname begin state start begin when login or levelup or 70007.use begin
Man kann hinter dem Questtrigger auch noch eine Bedingung schreiben, ohne diese Bedingung oder auch mehrere Bedingungen wird der Code nicht ausgeführt.
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin
Bei folgendem Code wird also in einem Questfenster "Hallo" geschrieben, wenn der Spieler sich einloggt, ein Level aufsteigt oder das Item mit der Vnum 70007 benutzt und gleichzeitig mindestens Level 10 ist:
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin say("Hallo")
Ends müssen an folgenden stellen gesetzt werden:
- quest ... begin
- state ... begin
- when ... begin
- if ... then
- for ... do
- while ... do
Hierbei muss beachtet werden, dass man jedes "if" mit einem "end" beenden muss. Folgt auf das "if" noch ein "else" oder mehrere "elseif"s muss man das "end" erst am Ende aller Abfragen setzen!
Unsere Quest sollte also nun so aussehen:
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin say("Hallo") end end end
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin if pc.get_level() == 25 then say("Hallo, du bist genau Level 25") elseif pc.get_level() == 15 then say("Hallo, du bist genau Level 15") else say("Hallo, du bist weder Level 25 noch Level 15 aber mindestens Level 10") end end end end
Wenn wir das ganze nur ein mal pro Spieler anzeigen lassen möchten setzen wir ihn einfach in eine leere State. Man könnte es z.B. so machen:
-- Achtung schlechter Code
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin if pc.get_level() == 25 then say("Hallo, du bist genau Level 25") set_state(_COMPLETE_) elseif pc.get_level() == 15 then say("Hallo, du bist genau Level 15") set_state(_COMPLETE_) else say("Hallo, du bist weder Level 25 noch Level 15 aber mindestens Level 10") set_state(_COMPLETE_) end end end state _COMPLETE_ begin end end
Code:
-> Spieler loggt sich sein -> Spieler ist mindestens Level 10 -> Spieler ist nicht Level 25 -> Spieler ist Level 15 -> Spieler bekommt die Nachricht "Hallo, du bist genau Level 15" -> Spieler wird in dieser Quest in die State _COMPLETE_ gesetzt -> If-Block wird beendet -> When-Block wird beendet -> State-Block wird beendet -> Spieler ist in State _COMPLETE_
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin if pc.get_level() == 25 then say("Hallo, du bist genau Level 25") elseif pc.get_level() == 15 then say("Hallo, du bist genau Level 15") else say("Hallo, du bist weder Level 25 noch Level 15 aber mindestens Level 10") end set_state(_COMPLETE_) end end state _COMPLETE_ begin end end
Code:
-> Spieler loggt sich sein -> Spieler ist mindestens Level 10 -> Spieler ist nicht Level 25 -> Spieler ist Level 15 -> Spieler bekommt die Nachricht "Hallo, du bist genau Level 15" -> If-Block wird beendet -> Spieler wird in dieser Quest in die State _COMPLETE_ gesetzt -> When-Block wird beendet -> State-Block wird beendet -> Spieler ist in State _COMPLETE_
Unsere Erste Quest haben wir jetzt geschrieben, bevor wir weitermachen müssen wir aber noch ein paar Sachen kennenlernen.
Lokale und Globale Variablen
Euch ist vielleicht schon aufgefallen, dass viele in ihren Quests "local" vor den Variablen stehen haben. Das heisst einfach, dass die Variable nur in dem Block zugreifbar ist, in dem sie deklariert wurde. Eine globale Variable ist Blockübergreifend gültig. Wenn man also folgendes macht:
Code:
local str = "hi" say(str)
Folgendes geht auch:
Code:
local str = "hi" if pc.get_level() >= 10 then say(str) end
Code:
if pc.get_level() >= 10 then local str = "hi" end say(str)
Code:
if pc.get_level() >= 10 then str = "hi" end say(str)
Selects sind z.B. auch Dinge, die einen Wert annehmen. Der Wert eines selects ist immer der ausgewählte Punkt. Man kann also bei einem select auch ohne Variable einen Wert abfragen:
Code:
if select("Weiter", "Abbrechen") == 2 then return end
Break und Return
Ein Break beendet die aktuelle Schleife sofort, ein return bricht den kompletten Code ab. In Funktionen gibt man mit einem return auch den Rückgabewert an.
Tables
(mehr Infos zu Tables findet ihr hier: )
Wir haben in LUA natürlich auch Tables. Sie sind vergleichbar mit Arrays, eigentlich sogar das gleiche. Aber wie wird ein Table erstellt? Das ist ganz simpel:
Code:
{}
Code:
local mytbl = {}
Jetzt packen wir doch mal ein paar Sachen in unseren Table:
Code:
local mytbl = {"genau Level 25", "genau Level 15", "weder Level 25 noch Level 15 aber mindestens Level 10"}
Code:
mytbl[1] --gibt den ersten Eintrag im Table zurück mytbl[2] --gibt den zweiten Eintrag im Table zurück ...
Code:
local mytbl = { {"Hallo 10", "Hallo 11"}, {"Hallo 20", "Hallo 21"}, {"Hallo 30", "Hallo 31"} }
Code:
mytbl[1][1] -> "Hallo 10" mytbl[1][2] -> "Hallo 11" mytbl[3][1] -> "Hallo 30"
Code:
local mytbl = { eins = {"Hallo 10", "Hallo 11"}, zwei = {"Hallo 20", "Hallo 21"}, drei = {"Hallo 30", "Hallo 31"} }
Code:
mytbl.eins[1] -> "Hallo 10" mytbl.eins[2] -> "Hallo 11" mytbl.drei[2] -> "Hallo 31"
Code:
local mytbl = { [1] = {"Hallo 10", "Hallo 11"}, ["zwei"] = {"Hallo 20", "Hallo 21"}, [31] = {"Hallo 30", "Hallo 31"} }
Code:
mytbl[1][1] -> "Hallo 10" mytbl["zwei"][2] -> "Hallo 21" mytbl[31][1] -> "Hallo 30"
Code:
({"Hallo", "Tschüss"})[1] -> "Hallo" ({"Hallo", "Tschüss"})[2] -> "Tschüss"
Nutzen von Tables innerhalb von Quests
Wir haben also unsere Quest von oben:
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin if pc.get_level() == 25 then say("Hallo, du bist genau Level 25") elseif pc.get_level() == 15 then say("Hallo, du bist genau Level 15") else say("Hallo, du bist weder Level 25 noch Level 15 aber mindestens Level 10") end set_state(_COMPLETE_) end end state _COMPLETE_ begin end end
Code:
quest questname begin state start begin when login or levelup or 70007.use with pc.get_level() >= 10 begin local str = ({ [25] = "genau Level 25", [15] = "genau Level 15" })[pc.get_level()] or "weder Level 25 noch Level 15 aber mindestens Level 10" say("Hallo, du bist "..str) set_state(_COMPLETE_) end end state _COMPLETE_ begin end end
Richtig! Aber dafür haben wir das "or". Wir deklarieren hier die Variable str sofort, wie auch schon im vorherigen Kapitel besprochen, und fragen nach dem Spieler Level ab. Aber eine Sache ist neu: Das or. Das or macht an der Stelle eine wichtige Sache: Wenn der Table nil ist, also wenn unter dem Spielerlevel kein Eintrag gefunden wurde, trifft das or zu. Die Variable "str" ist dann also "weder Level 25 noch Level 15 aber mindestens Level 10".
Wenn ihr es bis hier hin geschafft habt könnt ihr schon viele Sachen machen, die schon von einem Anfänger entfernt sind. Mit Tables und den Abfragen die ich Euch gezeigt habe gibt es viele Dinge, die man mit Quests machen kann. Jetzt habe ich noch einen Punkt, den ich abhaken möchte:
"Endlos"-Menüs
Wenn ihr aktiv auf Epvp seid und vielleicht meine Releases verfolgt habt, solltet ihr gemerkt habe, dass ich immer häufiger ein für Metin-Quests untypisches "Endloses"-Menü in meinen Quests verwende. Wie genau man das macht ist eigentlich sehr simpel.
Es gibt 2 Möglichkeiten:
1. Möglichkeit: Dauerschleife
Das ist die Variante, die ich in meinen Quests verwende. Ich setze um das Hauptmenü eine Dauerschleife:
Code:
quest questname begin state start begin when 11001.chat."Endlos-Menü" begin repeat until false end end end
Hier mal eine Quest mit einem "Endlosen" Menü:
Code:
quest questname begin state start begin when 11001.chat."Endlos-Menü" begin repeat say("Was möchtest du machen?") local s = select("Item geben", "Monster spawnen", "Abbrechen") if s == 1 then repeat say("Welches Item?") vnum = tonumber(input()) if vnum then if item_name(vnum) != "" then say("Möchtest du wirklich "..item_name(vnum).." haben?") local s = select(item_name(vnum).." nehmen", "andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then pc.give_item2(vnum, 1) return elseif s == 3 then break elseif s == 4 then return end else say("Dieses Item gibt es nicht!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 2 then break elseif s == 3 then return end end else say("Du kannst nur Zahlen eingeben!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 2 then break elseif s == 3 then return end end until false elseif s == 2 then repeat say("Welches Monster?") vnum = tonumber(input()) if vnum then if mob_name(vnum) != "" then say("Möchtest du wirklich "..mob_name(vnum).." spawnen?") local s = select(mob_name(vnum).." spawnen", "andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then mob.spawn(vnum, pc.get_local_x(), pc.get_local_y(), 1) return elseif s == 3 then break elseif s == 4 then return end else say("Dieses Monster gibt es nicht!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 2 then break elseif s == 3 then return end end else say("Du kannst nur Zahlen eingeben!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 2 then break elseif s == 3 then return end end until false else return end until false end end end
Bei dem Punkt "zum Hauptmenü" breche ich mit einem "break" aus der aktuellen Schleife (der Schleife in der man die VNUM eingibt) aus und werde von der Hauptmenü Schleife gefangen.
2. Möglichkeit: Eine Funktion für jeden Menüpunkt
Diese Möglichkeit mag ich nicht so gerne, allerdings funktioniert sie auch.
Code:
quest questname begin state start begin when 11001.chat."Endlos-Menü" begin main_menu() end end end function main_menu() say("Was möchtest du machen?") local s = select("Item geben", "Monster spawnen", "Abbrechen") if s == 1 then item_geben() elseif s == 2 then monster_spawnen() else return end end function item_geben() say("Welches Item?") local vnum = tonumber(input()) if vnum then if item_name(vnum) != "" then say("Möchtest du wirklich "..item_name(vnum).." haben?") local s = select(item_name(vnum).." nehmen", "andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then pc.give_item2(vnum, 1) return elseif s == 2 then item_geben() elseif s == 3 then main_menu() elseif s == 4 then return end else say("Dieses Item gibt es nicht!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then item_geben() elseif s == 2 then main_menu() elseif s == 3 then return end end else say("Du kannst nur Zahlen eingeben!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then item_geben() elseif s == 2 then main_menu() elseif s == 3 then return end end end function monster_spawnen() say("Welches Monster?") local vnum = tonumber(input()) if vnum then if item_name(vnum) != "" then say("Möchtest du wirklich "..mob_name(vnum).." spawnen?") local s = select(mob_name(vnum).." spawnen", "andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then mob.spawn(vnum, pc.get_local_x(), pc.get_local_y(), 1) return elseif s == 2 then monster_spawnen() elseif s == 3 then main_menu() elseif s == 4 then return end else say("Dieses Item gibt es nicht!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then monster_spawnen() elseif s == 2 then main_menu() elseif s == 3 then return end end else say("Du kannst nur Zahlen eingeben!") local s = select("andere VNUM eingeben", "zum Hauptmenü", "Abbrechen") if s == 1 then monster_spawnen() elseif s == 2 then main_menu() elseif s == 3 then return end end end
Special Thanks
- .Xilent
- Poccix
- Yiv
- Luki
- Akkelos
- Kilroy
Ich hoffe, dass ich mit diesem Thread dem ein oder anderen helfen kann. Falls ihr Fehler findet meldet euch bitte direkt hier im Thread!