[[http://www.siedler-games.de|{{:sg-link.jpg|}}]]
====== Quest-Funktionen ======
Im Spielverlauf gibt es oft immer die selben, wiederkehrenden Aufgaben zu erledigen: Zerstöre dieses und jenes, finde den und den, baue das und das etc...\\
Dafür bietet das Spiel selbst schon diese Funktionen:
* [[reference:SetupCaravan]]
* [[reference:SetupDestroy]]
* [[reference:SetupEstablish]]
* [[reference:SetupExpedition]]
Wenn man nun eigene Ideen hat, für die es keine Comfort Funktionen gibt, dann muss man das mit mehr oder weniger viel Aufwand irgendwie anders machen. Also warum nutzt man nicht gleich die vom Spiel eigens für solche Zwecke bereitgestellten Funktionen, um seine Vorstellung zu realisieren? Das geht schneller, sicherer und ist außerdem noch schönerer Stil. Wenn man dann die selbe Art Quest mehr als einmal auf einer Map machen muss, spart das auch noch eine Menge Code. Mit Hilfe dieser Anleitung solltest du in der Lage sein, solche Quest-Funktionen selbst erstellen zu können.
===== Das Quest-Interface =====
In diesem Abschnitt soll verständlich werden, wie die Quest-Funktionen intern funktionieren und welche Schnittstellen man benutzen kann, um eigene Funktionen schreiben zu können.
==== Konventionen ====
Damit sofort klar ist, dass es sich bei einer Funktion um eine Quest-Funktion handelt, sollten die eigenen Funktionen immer mit dem Präfix "Setup" beginnen (z.B. **Setup**Conversation, **Setup**Trade, siehe Beispiele).
Interne Hilfsfunktionen beginnen immer mit dem Präfix Quest (z.B. **Quest**Setup, **Quest**TargetsDestroyed). Solltest du dir eigene solcher Funktionen schreiben, beginne sie ebenfalls mit Quest.
==== SetupXXX, Condition- und Action-Funktion ====
Eine eigene Quest-Funktion gliedert sich in drei Teile: Eine Start-, eine Condition- und eine Actionfunktion.
Die Startfunktion dient, wie der Name schon vermuten lässt, zum Starten eines Quests. Die Funktion bekommt als einzigen Parameter das [[#Quest-Table|Quest-Table]] übergeben.
function SetupXXX( _Quest )
Die Conditionfunktion übernimmt die meißte Arbeit. Sie wird im Normalfall wie ein Job jede Sekunde aufgerufen und prüft, ob das Questziel erreicht wurde. Ist dies der Fall, muss sie true zurückgeben. Die Conditionfunktion bekommt die eindeutige QuestID als Parameter übergeben.
function XXX_Condition( _QuestID )
Die Actionfunktion wird aufgerufen, sobald das Questziel erfüllt ist. Oft wird hier einfach nur die Callback-Funktion aus dem Quest-Table ausgeführt. Auch die Actionfunktion bekommt die QuestID übergeben.
function XXX_Action( _QuestID )
==== Quest-Table ====
Das Quest-Table wird bei einem Aufruf von SetupXXX als Parameter übergeben. In ihm werden alle nötigen Quest-Infomationen gespeichert. Es gibt einige vorbelegte Index-Namen, die man verwenden kann. Sie sind in folgender Tabelle aufgelistet:
^Index-Name^Beschreibung^Siehe^
|Army|Ein Armee-Table. Die Armee sollte irgendetwas mit dem Quest zu tun haben, z.B. könnte der Auftrag lauten, diese Armee zu vernichten.|QuestArmyIsDead|
|ArmyPos|Ist diese Position angegeben, wird der Armee-Anker beim Aufruf der Quest-Funktion auf diese Position verlegt. ArmyRange muss dann ebenfalls existieren.|-|
|ArmyRange|Aktionsradius der Armee um ArmyPos.|-|
|Target|Der Name einer einzelnen Entity, die etwas mit dem Quest zu tun hat.|QuestTargetsDestroyed|
|Targets|Ein String, der eine Gruppe von Entities bezeichnet. Gibt man hier "Haus" an, werden damit alle Entities mit dem Namen "Haus1", "Haus2", "Haus3" usw. erfasst. Die Anzahl der gefundenen Entities wird nach dem Aufruf der Quest-Funktion unter dem Index TargetCount abgespeichert.|QuestTargetsDestroyed||
|TargetCount|reserviert, siehe Targets|-|
|AreaPos|Eine Positionsangabe.|QuestAreaCleared|
|AreaSize|Radius um AreaPos.|-|
|AreaPlayerID|SpielerID die erfasst werden soll.|-|
|triggerId|reserviert|-|
==== Schnittstellen ====
Im Folgenden werden diejenigen Funktionen beschrieben, die man braucht wenn man sich eigene Funktionen schreiben möchte.
=== QuestSetup ===
function QuestSetup( _QuestTable, _QuestName, _EventType )
QuestSetup() initialisiert den eigentlichen Quest. Der Funktion wird das Quest-Table übergeben, sowie der Präfix (QuestName) der Condition- bzw. Actionfunktion. Die Funktion wird üblicherweise in der Funktion SetupXXX aufgerufen:
function SetupExampleQuest(_Quest)
QuestSetup(_Quest, "ExampleQuest")
end
Der Parameter _EventType ist optional. Hier kann ein Event-Typ angegeben werden (Standardwert: LOGIC_EVENT_EVERY_SECOND). Bei jedem Auftreten des Events wird dann die Conditionfunktion aufgerufen. \\
Siehe auch: [[#Beispiel 2|Beispiel 2]]
=== QuestCallback ===
function QuestCallback( _QuestID )
Ruft die Callback-Funktion des Quest-Tables zu einem passenden Zeitpunkt auf. Die Actionfunktion muss diese Funktion aufrufen und ihren Rückgabewert "durchreichen":
function ExampleQuest_Action(_QuestID)
-- Rückgabewert von QuestCallback() wird wiederum mit return zurckgegeben
return QuestCallback(_QuestID)
end
=== QuestArmyIsDead ===
function QuestArmyIsDead( _Quest )
Hiermit kann man prüfen, ob die Questarmee tot ist oder nicht.
=== QuestTargetsDestroyed ===
function QuestTargetsDestroyed( _Quest )
Hiermit kann man prüfen, ob die im Quest-Table angegebenen Ziele (Target bzw. Targets) zerstört wurden.
=== QuestAreaCleared ===
function QuestAreaCleared( _Quest )
Hiermit kann man prüfen, ob sich keine Einheiten/Gebäude von Spieler AreaPlayerID mehr im Radius um AreaPos befinden.
=== DestroyQuest ===
function DestroyQuest( _Quest )
Hiermit kann man einen Quest vorzeitig beenden.
===== Beispiele =====
Anhand dieser Beispiele sollte die "Theorie" oben verständlich werden.
==== Beispiel 1 ====
__Questbeschreibung:__ Der Spieler soll mit einem bestimmten NPC sprechen, danach soll es automatisch anfangen zu schneien, damit man einen Fluss überqueren kann.
Dazu brauchen wir erstmal eine Funktion, mit der man den Quest starten kann. Diese sieht folgendermaßen aus:
-- Eine Funktion namens SetupXXX mit dem Parameter _Quest
-- Das "Setup" sollte wegen dem einheitlichen Stil immer dabei sein
-- Der Parameter _Quest muss ein Table sein und ist unbedingt erforderlich
function SetupConversation( _Quest )
-- Die Funktion enthält nur eine einzige Zeile, nämlich die hier:
QuestSetup( _Quest, "Conversation" )
end
Jetzt müssen wir uns überlegen, welche Informationen wir brauchen, um zu überprüfen, ob der Quest erfüllt wurde. Diese Informationen werden für die spätere Verwendung in dem Quest-Table abgelegt.
-- Für diesen Quest brauchen wir eigentlich nur eine einzige Information: Den NPC mit dem gesprochen werden muss.
-- Allerdings reicht hier nicht allein der Name aus, da wir später für "TalkedToNPC" das komplette NPC_Table brauchen.
-- Also erstmal den NPC erstellen
local NPC_Wettermann = {
name = "Wettermann",
briefing = ... -- das Briefing ist jetzt nicht relevant, darum hab ich das etwas verkürzt. Es darf aber natürlich nicht fehlen
}
CreateNPC( NPC_Wettermann )
-- Jetzt das Quest-Table erstellen
local quest = {
-- Zur Erinnerung: Wir merken uns das NPC Table für später
Npc = NPC_Wettermann,
-- Außerdem kann jeder Quest-Table eine Callback Funktion beinhalten
-- Ich verwende hier die alternative (und bedeutend kürzere) Schreibweise für Callback/Finished Funktionen
Callback = function( _Quest )
-- Es soll ja nachher schneien..
StartWinter( 500 )
end
}
-- Dann unsere vorbereitete Funktion aufrufen
SetupConversation( quest )
Soweit so gut. Jetzt brauchen wir noch eine Funktion, die überprüft, ob mit dem NPC gesprochen wurde.
Diese Funktion muss so aussehen und wird dann jede Sekunde aufgerufen (genau wie ein SimpleJob):
-- Der Funktionsname bildet sich aus unserem frei gewählten Namen beim Aufruf von "SetupQuest" und "_Condition".
-- Die Funktion bekommt den Parameter _QuestID übergeben
function Conversation_Condition( _QuestID )
-- In dieser Funktion soll nun mittels der Funktion "TalkedToNPC()" überprüft werden,
-- ob mit dem NPC, den wir im Quest Table bestimmt haben, gesprochen wurde.
-- "TalkedToNPC()" erwartet jetzt also das NPC-Table, welches wir oben in das Quest-Table gespeichert haben.
-- Das Quest-Table, wurde dann an "SetupConversation()" und schließlich an "QuestSetup()" übergeben - und wo ist es jetzt?
-- Es befindet sich in einem internen Speicher, den wir über den Parameter _QuestID erreichen können:
-- DataTable[_QuestID]
-- Den NPC bekommen wir also über "DataTable[_QuestID].Npc"
-- Da wir uns in einem Job befinden, gilt der Quest als erfüllt, sobald wir true zurückgeben
-- TalkedToNPC() gibt von sich aus entweder true oder false zurück, daher kann man das in dieser Kurzform schreiben ohne if-then-end:
return TalkedToNPC( DataTable[_QuestID].Npc )
end
Jetzt ist die Sache auch schon fast fertig. Fehlt nur noch der Aufruf der Callback Funktion. Das funktioniert so:
-- Es muss eine Funktion mit dem gewählten Namen und dem Zusatz "_Action" geben
-- Auch diese Funktion bekommt den Parameter _QuestID zugewiesen
function Conversation_Action( _QuestID )
-- In der Funktion existiert wieder nur eine Zeile die genau so lauten muss:
return QuestCallback( _QuestID )
end
Das wars schon. Jetzt kann man ganz leicht mehrere solcher Quests in das Script einbauen, ohne ständig von Hand Jobs zu starten, die immer die selben Bedingungen prüfen oder viele globale Variablen benutzen müssen.
==== Beispiel 2 ====
__Questbeschreibung:__ Auf einem Marktplatz soll eine gewisse Menge Schwefel und Eisen gekauft werden.
Den Quest nennen wir "Trade".
-- Jetzt fangen wir erst mit dem Quest-Table an
local quest = {
-- optional: Auf einem bestimmten Marktplatz soll gehandelt werden? Dann hier den Namen oder die Id angeben
Market = "Basar",
-- Die Ressource(n) und die Menge die gehandelt werden soll als Table (Ressource = Menge)
Ressources = {
Sulfur = 1000,
Iron = 500
},
-- optional: Die Ressourcen sollen verkauft statt gekauft werden? Dann hier true setzen
Sell = nil,
-- Und noch eine Callback Funktion
Callback = function()
ChangePlayer( "Hauptquartier", 1 )
end
}
-- Quest starten
SetupTrade( quest )
Jetzt zu den benötigten Quest-Funktionen:
-- Erst die Setup-Funktion
function SetupTrade( _Quest )
-- Hier können wir jetzt auch den dritten Parameter von QuestSetup() benutzen.
-- Dieser dritte Parameter muss ein Event sein (bekannt aus dem Trigger Tutorial),
-- bei dessen Auftreten die Bedingungen des Quest getestet werden
-- In diesem Fall müssen wir nur testen, wenn Waren gehandelt wurden (Events.LOGIC_EVENT_GOODS_TRADED)
QuestSetup( _Quest, "Trade", Events.LOGIC_EVENT_GOODS_TRADED )
end
-- Jetzt die Condition Funktion, die diesmal etwas umfangreicher ausfällt
function Trade_Condition( _QuestID )
-- Erst schauen, ob nur ein bestimmter Marktplatz gültig ist
if DataTable[_QuestID].Market ~= nil then
if GetEntityId( DataTable[_QuestID].Market ) ~= Event.GetEntityID() then
-- Auf dem falschen Marktplatz wurde gehandelt: abbrechen
return false
end
end
-- Die gehandelte Ressource/Menge ermitteln
local ressource, amount
if DataTable[_QuestID].Sell ~= nil then
ressource = Event.GetSellResource()
amount = Event.GetSellAmount()
else
ressource = Event.GetBuyResource()
amount = Event.GetBuyAmount()
end
-- Ein Zwischenspeicher-Table erstellen, falls noch nicht vorhanden
if DataTable[_QuestID].Traded == nil then
DataTable[_QuestID].Traded = {}
end
-- Die Menge in den Zwischenspeicher schreiben
DataTable[_QuestID].Traded[ressource] = (DataTable[_QuestID].Traded[ressource] or 0) + amount
-- Jetzt noch prüfen, ob alle benötigten Waren gehandelt wurden:
for k, v in pairs(DataTable[_QuestID].Ressources) do
-- Wurde die erforderliche Ressource bereits gehandelt?
if DataTable[_QuestID].Traded[ResourceType[k]] ~= nil then
-- Wurde die erforderliche Menge noch nicht gehandelt, dann hier abbrechen
if DataTable[_QuestID].Traded[ResourceType[k]] < v then
return false
end
end
end
-- Wenn wir bis hierhin kommen, wurde alles gehandelt
return true
end
-- So und zum Schluss noch die Action-Funktion
function Trade_Action( _QuestID )
return QuestCallback( _QuestID )
end