KONZEPT
Programmierung von Fertigkeiten (Abilities: Skills/Spells)
von Holger@Wunderland (20.09.1999)
INHALT
A. EINLEITUNG
B. UEBERSICHT
C. ANMELDUNG UND ABMELDUNG
D. ERLERNEN
E. SPERREN UND FREIGEBEN
F. PROGRAMMIERUNG VON SPELLS UND SKILLS
F.1. SKILLS/SPELLS MIT PRE-DELAY (POLLING)
F.2. SKILLS/SPELLS OHNE PRE-DELAY
F.2.1. AUTOMATISCHE EINSCHRAENKUNGEN
F.2.2. SPELLBOOK-ARBEITSWEISE
F.2.3. LERNEFFEKT UND AUSFUEHRUNGSDELAY
F.3. ZUSAMMENFASSUNG
G. PROGRAMMIERUNG VON COMBAT-SKILLS
A. EINLEITUNG
Dieser Text beschreibt, wie im Wunderland die Programmierung und
Ausfuehrung von Fertigkeiten (Skills/Spells/Combatskills) funktio-
niert. Beispiele fuer Fertigkeiten sind in /spellbooks/beispiel.c
zu finden. Da im Moment nicht absehbar ist, was mit dem Combatskills
werden wird, oder ob es sie in Zukunft ueberhaupt in dieser Form
geben wird, sind die Dokus dazu mit Vorbehalt zu geniessen. *g*
B. UEBERSICHT
Grundsaetzlich gibt es derzeit 3 verschiedene Typen von Fertigkeiten
im Wunderland.
a. Spells - Spells sind magische Zaubersprueche, wie z.B. ein Eis-
blitz, der mit Hilfe psychisch/magischer Anspannung irgend-
einen Effekt hervorruft. Spells koennen nicht ausgefuehrt
werden oder werden mit einer gewissen Wahrscheinlichkeit da
gehemmt, wo im Raum oder im Opfer P_NOMAGIC gesetzt ist.
Spells sollten deshalb vorwiegend von Lebewesen verwendet
werden, die ueber mehr intellektuelle als koerperliche Vor-
teile haben.
b. Skills - Skills sind physisch erlernbare Faehigkeiten, wie z.B.
ein Kampftritt. Der Skill soll grundsaetzlich kaum von der
Intelligenz des 'Benutzers' abhaengig sein. Er stellt viel-
mehr eine koerperliche Faehigkeit dar, die durch einen
Lernprozess abhaengig von Geschicklichkeit, Kraft und Aus-
dauer einen bestimmten Erfolg bringt.
c. Combat-Skills - Die Kampf-Faehigkeiten werden automatisch im
Kampf aufgerufen, wie z.B. 'Schnell'. Die Zukunft der Kampf-
skills ist im Wunderland noch nicht ganz klar. Man kann sie
noch einmal unterteilen in Attack-Combat-Skills und Defend-
Combat-Skills. Die Attack-Skills werden in jeder 'Angriffs-
runde' des Benutzers aufgerufen und koennen den Angriff mo-
difizieren. Die Defend-Skills werden in der 'Verteidigungs-
runde' aufgerufen und koennen den Angriff des Gegners modi-
fizieren, sprich: abschwaechen.
C. ANMELDUNG UND ABMELDUNG
Alle echten Fertigkeiten im Wunderland muessen beim Skillmaster
angemeldet werden, damit Spieler sie ueberhaupt lernen koennen. Dies
hat zum einen Effizenzgruende, zum anderen soll es dazu beitragen,
ein wenig die Uebersicht zu behalten, wer wo welche Fertigkeit be-
nutzen kann. Eine Fertigkeit wird im Skillmaster angemeldet mit
der Skillmaster-Funktion:
skillmaster->AddAbility( fertigkeit, ... );
Die Manpage sollte die einzelnen Argumente ganz gut erklaeren. Der-
zeit koennen nur Magier ab Regionsmagierstufe eine Fertigkeit an-
melden. Abmelden kann man eine Fertigkeit mittels:
skillmaster->RemoveAbility( fertigkeit );
Es ist darauf zu achten, dass 'abgemeldete' Fertigkeiten noch im
Spieler aktiv bleiben, bis dieser renewt wird oder Reboot ist. Er
kann die Fertigkeit dann zwar nicht mehr benutzen, erhaelt jedoch
eine Meldung. Man sollte dringend darauf achten, dass Spieler die
eventuell laengere Zeit nicht da waren, nicht ihre Fertigkeiten
verlieren, weil irgendein Magier diese ab- und unter anderem Namen
wieder angemeldet hat. Abgemeldete Fertigkeiten verliert der Spieler
beim Einloggen UNWIEDERBRINGLICH.
VORM Anmelden (und auch VORM Abmelden) ist unbedingt der Gleichge-
wichtsmagier oder ein Erzmagier zu informieren. Die Regionsmagier,
die in der Lage sind, Fertigkeiten anzumelden, tragen dafuer die
Verantwortung!
D. ERLERNEN
Nach dem Anmelden kann die Fertigkeit einem Spieler verliehen werden
(z.B. von einer Gilde oder Spruchrolle) mit der Funktion:
spieler->GiveAbility( fertigkeit, lernwert );
Die Manpage erklaert die Argumente. Grundsaetzlich besteht kein
Unterschied in der Behandlung der Fertigkeiten zwischen NPCs und
Spielern. Inwieweit eine Fertigkeit aber auch bei einem NPC funk-
tioniert, haengt von der Fertigkeit selbst und damit von dem Magier
ab, der die Fertigkeit geschrieben hat. Mit GiveAbility() kann man
dem Spieler die fertigkeit auch wieder wegnehmen, indem man einen
Lernwert von -1 angibt. Es ist darauf zu achten, dass das Objekt,
welches die Fertigkeit vergibt, eine P_SKILLS_ID gesetzt hat, um
Ueberschneidungen beim Vergabe/Wegnahme zu vermeiden! Die gleiche
Funktion kann man auch verwenden, um den Lernwert zu erhoehen oder
zu senken. Dies sollte man aber tunlichst vermeiden, denn das Er-
lernen/Erhoehen wird automatisch geregelt, wenn der Spieler seine
Fertigkeit verpatzt.
Um herauszufinden, wie gut oder schlecht ein Spieler/NPC eine Fer-
tigkeit beherrscht, kann man folgende Funktion benutzen:
spieler->GetProbability( fertigkeit );
Man erhaelt den Lernwert von 'name' in Promille. Wichtig und notwen-
dig ist diese Funktion vor allem in den sogenannten Spellbooks, um
die Wahrscheinlichkeit zu berechnen, mit der die Fertigkeit ausge-
fuehrt wird.
Um dem Spieler andere alternative Verben zu geben, mit denen er eine
Fertigkeit ausfuehren kann, kann man die Funktion:
spieler->SetSkillVerbs( fertigkeit, verben );
benutzen. Um zu ermitteln, welche Verben der Spieler aktuell fuer
eine Fertigkeit benutzt, kann man:
spieler->QuerySkillVerbs( fertigkeit );
verwenden. Um zu erfahren welche ID das Objekt hat, von dem der
Spieler/NPC die Fertigkeit erhalten hat, kann man:
spieler->QuerySkillIDs( fertigkeit );
verwenden.
E. SPERREN UND FREIGEBEN
Man kann eine Fertigkeit eines Spielers sperren, indem man die
Funktion:
spieler->DisableAbility( fertigkeit );
verwendet. Der Spieler kann dann diese Fertigkeit solange nicht ver-
wenden, bis man sie mittels:
spieler->EnableAbility( fertigkeit );
wieder freigibt. Es ist darauf zu achten, dass das sperrende Objekt
wiederum eine eindeutige P_SKILLS_ID gesetzt hat, um Ueberschnei-
dungen mehrerer Sperrer/Freigeber zu vermeiden! Um zu erfahren, wel-
che ID die Objekte haben, die eine Fertigkeit momentan sperren, bzw.
um ueberhaupt zu erfahren, ob eine Fertigkeit gerade gesperrt ist,
kann man:
spieler->QueryDisablerIDs( fertigkeit );
verwenden.
F. PROGRAMMIERUNG VON SPELLS UND SKILLS
Wenn man sich fuer einen Spell oder Skill entschieden hat, ist die
Programmierung beider fast identisch. Deshalb wird hier beides ge-
meinsam beschrieben. Zunaechst sollte man sich noch entscheiden, ob
man einen Spell/Skill schreibt, der sofort wirkt, oder ob es ein
Pre-Delay, also eine Vorbereitungsphase geben soll. Bei echten
Spells, die starke Wirkung haben sollen, ist dies weitaus schoener
und realistischer, als wenn es sofort ausgefuehrt wird. Wenn es sich
um einen Skill/Spell mit Pre-Delay handelt, darf sich der Spieler
waehrend der Ausfuehrung auch nicht bewegen, das heisst, die belieb-
te Rein-Feuerball-Raus-Taktik wird damit unmoeglich. Es wird zu-
naechst beschrieben, wie man Skills/Spells mit Pre-Delay schreibt,
denn ohne Pre-Delay muss man nur einiges weglassen. Dazu mehr in
Punkt F.2.
Grundsaetzlich braucht man ein Spellbook. Ein Spellbook ist ein
praktisch beliebiges Objekt, in dem der Spell/Skill geschrieben ist.
Spellbooks sollten nach Moeglichkeit in /spellbooks/* liegen. Das
Spellbook darf natuerlich kein Klon sein, da die Anmeldung ansonsten
sinnlos wird. Ein Beispiel-Spellbook mit einem Spell und einem Skill
findet man in /spellbooks/beispiel.c.
F.1. SKILLS/SPELLS MIT PRE-DELAY (POLLING)
Spells/Skills mit Pre-Delay muessen beim Anmelden im Skillmaster mit
Flag SM_F_POLL angemeldet werden. Das Spellbook MUSS mindest zwei
Funktionen enthalten, deren Name fest vorgegeben ist:
int _poll_XYZ(object player, mixed args, int count)
int _cast_XYZ(object player, mixed args)
Wobei XYZ durch den Name der Fertigkeit zu ersetzen ist. Ist der
Name der Fertigkeit zum Beispiel 'pfeil', so hiessen die beiden
Funktionen _poll_pfeil() und _cast_pfeil(). Aendert sich der Name
einer Fertigkeit MUSS man auch die Namen der Funktionen aendern!
Die Funktion _poll_XYZ() wird waehrend des Pre-Delays solange aufge-
rufen, wie sie einen Wert ungleich 0 zurueckgibt. Sie wird dann
in <Returnwert> Sekunden erneut aufgerufen. Gibt also _poll_XYZ()
als Returnwert 5 zurueck, wird _poll_XYZ() nach 5 Sekunden wieder
aufgerufen. Damit kann man selbst Textsequenzen und aehnliches rea-
lisieren. Man bekommt bei jedem Aufruf von _poll_XYZ() den ausfueh-
renden Spieler als Objektpointer mit, die eingegebenen Argumente als
Array aus Strings (einzelne Woerter) und als drittes Argument be-
kommt man einen Zaehler mit, damit man weiss, wie oft _poll_XYZ()
schon aufgerufen wurde. Beim ersten Aufruf ist der Zaehler 0. Bei
jedem weiteren Poll wird der Zaehler um 1 erhoeht. Man kann also
die Vorbereitungs-Sequenz sehr bequem mit switch(count) { ... }
von 0 beginnend realisieren. Wie lang die Sequenz ist, spielt keine
Rolle.
Die Sequenz wird abgebrochen, wenn der Spieler sich in der Vorberei-
tungsphase BEWEGT. Ausserdem wird sie abgebrochen, wenn der Spieler
versucht noch eine Fertigkeit innerhalb dieser Phase zu starten.
Normalerweise darf also nur ein Polling gleichzeitig laufen. Berei-
tet sich der Spieler gerade auf 'pfeil' vor und versucht waehrend
dessen noch einen 'feuerball' verhaspelt sich der Spieler. Man kann
die Anzahl gleichzeitig ausfuehrbarer Polling-Fertigkeiten im Spie-
ler jedoch auch erhoehen, indem man P_MAX_SKILLS erhoeht.
Gibt _poll_XYZ() einen Wert kleiner 0 zurueck, wird die weitere Ab-
arbeitung abgebrochen und die Fertigkeit nicht weiter ausgefuehrt.
So kann man selbst Abbrueche durchfuehren, wenn zum Beispiel das
'Opfer' eines Angriffsspruches sich waehrend der Vorbereitungs-
sequenz entfernt hat, oder andere Bedingungen nicht mehr erfuellt
sind.
Gibt _poll_XYZ() einen Wert von 0 zurueck, wird SOFORT (!) die Fun-
ktion _cast_XYZ() aufgerufen. Die Argumente entsprechen denen von
_poll_XYZ(), jedoch ohne den Zaehler. Die Cast-Funktion macht jetzt
die eigentliche Fertigkeit. Da dies mit den Fertigkeiten ohne
Polling zu 100% uebereinstimmt, wird jetzt der Rest im naechsten
Punkt gemeinsam beschrieben.
F.2. SKILLS/SPELLS OHNE PRE-DELAY
Gibt der Spieler ein passendes Verb zu einer Fertigkeit ein, wird
im Spellbook-Objekt die Funktion:
int _cast_XYZ(object player, mixed args)
aufgerufen. Wobei XYZ durch den Name der Fertigkeit zu ersetzen ist.
Bei Polling-Skills wird die Cast-Funktion erst aufgerufen, wenn die
Poll-Funktion einen Returnwert von 0 liefert. Argument 'player' ist
das ausfuehrende Lebewesen, Argument 'args' ist ein Array aus
Strings, welches die weiteren Argumente als einzelne Woerter ent-
haelt, die der Spieler beim Aufruf eingegeben hat. (z.B. die ID des
Opfers oder aehnliches)
F.2.1. AUTOMATISCHE EINSCHRAENKUNGEN
Der Spieler/NPC kann die Fertigkeit nur ausfuehren, wenn er kein
Geist ist. Wenn das stoert, muss man die Fertigkeit mit Flag
SM_F_GHOST anmelden.
Weiterhin kann der Spieler die Fertigkeit nicht ausfuehren, wenn er
das notwendige Erhol-Delay (siehe unten) dieser Fertigkeit noch
nicht gewartet hat.
Wenn der Spieler gelaehmt ist, oder die Fertigkeit gesperrt ist kann
er es ebenfalls nicht ausfuehren.
Und letztlich, wenn der Spieler nicht genuegend Magiepunkte hat, um
diese Fertigkeit auszufuehren.
Handelt es sich um einen Spell, kann es sein, dass in dem Raum
P_NOMAGIC gesetzt wurde und der Spell dann mit einer bestimmten
Wahrscheinlichkeit fehlschlaegt.
F.2.2. SPELLBOOK-ARBEITSWEISE
Geht bis hierhin alles gut, bekommt jetzt das Spellbook den oben
genannten Aufruf von _cast_XYZ(). Das Spellbook kann damit jetzt
theoretisch verfahren wie es will. Es gibt aber einen gewissen Stil,
den sollte man einhalten. :-)
Zunaechst sollte man ueberpruefen, ob die vom Spieler eingegebenen
Argumente in Ordnung sind. Also das angegebene Opfer sich im Raum
befindet, eventuell sollte ermittelt werden, ob sich der Spieler im
Kampf befindet und das 'Opfer' daher automatisch bestimmt werden
soll, ohne dass es der Spieler angeben muss etc. Stimmen die Argu-
ment NICHT, dann sollte man jetzt mit return -1; (Define FEHLER in
sys/living/skills.h) abbrechen. Der Fehler bewirkt, dass die Fertig-
keit ohne Abzug von Punkten oder aehnliches einfach abgebrochen
wird.
Weiterhin sollte man ueberpruefen, ob der Spieler benoetigte Aus-
ruestungsgegenstaende dabei hat. Ganz wichtig vor allem bei Polling-
Fertigkeiten mit einer Sequenz, in der man bestimmte Zutaten mischt
oder aehnliches. Dann empfiehlt es sich jedoch diese Pruefungen
bereits in der Poll-Funktion (_poll_XYZ()) durchzufuehren. Stimmt
irgendwas nicht ueberein, dann wiederum -> return FEHLER;
Sind die Argument ok und der Spieler hat eventuell auch alles dabei,
muss man errechnen, ob der die Fertigkeit gelingt oder nicht. Dazu
sollte man sich den aktuellen Lernwert dieser Fertigkeit vom Spieler
mittels:
lernwert=spieler->GetProbability( fertigkeit );
holen. Da der Lernwert in Promille von 0 bis 1000 angegeben ist, und
Spieler sich immer totaergern, wenn sie nie auf 100% kommen, weil
Berechung falsch ist, sollte folgendes verwendet werden:
if(random(1000)<lernwert)
ERFOLG!
else
MISSERFOLG!
So kann selbst bei einem Lernwert von 999 Promille der Spruch noch
schief gehen, naemlich wenn der Randomwert auch 999 ist. Natuerlich
steht es jedem Spellbook-Schreiber frei, die Wahrscheinlichkeit des
Erfolgs selbst anhand anderer Parameter, wie zum Beispiel des Levels
oder der Intelligenz usw. zu berechnen. Es ist auch denkbar den Er-
folg oder Misserfolg abgestufter zu gestalten als dieses Modell, wo
es nur totalen Erfolg oder totalen Fehlschlag gibt. Das muss jeder
selbst entscheiden. Wichtig ist im Grunde nur eins, naemlich, dass
bei erfolgreicher Ausfuehrung ein Returnwert von 1 (Define ERFOLG in
sys/living/skills.h) zurueckgegeben wird oder bei einem Fehlschlag
ein Wert von 0 (Define MISSERFOLG in sys/living/skills.h).
Vorher muss das Spellbook natuerlich noch irgendwas tun. Die Fertig-
keit eben. Dies kann ein (ggf. magischer) Angriff auf ein Opfer oder
nur ein netter Gag oder irgendwas anderes sein. Ist es gelungen ->
return ERFOLG ansonsten return MISSERFOLG.
Und das wars eigentlich schon. Wichtig bei Spells die einen magi-
schen Angriff durchfuehren (Aufruf des gegnerischen Defend()) ist,
dass das Spelllevel mit uebergeben wird und auch der magische Scha-
denstyp mit gesetzt wird. Mehr muss das Spellbook nicht tun, denn
alles weitere wird wieder automatisch gemacht.
F.2.3. LERNEFFEKT UND AUSFUEHRUNGSDELAY
Ist der Zauberspruch geglueckt (ERFOLG), werden jetzt automatisch
die Kosten abgezogen, die bei der Anmeldung bei Skillmaster angege-
ben wurden. Weiterhin wird das dort angegebene Delay fuer diese Fer-
tigkeit gesetzt. Der Spieler kann dann innerhalb eines bestimmten
Zeitraums diese Fertigkeit nicht mehr anwenden. Existiert im Spell-
book eine Funktion:
int get_delay( string fertigkeit, object spieler );
und diese Funktion liefert daraufhin einen Wert ungleich Null, wird
dieser Wert als Delay gesetzt. Gibt es sogar eine Funktion:
string get_delay_msg( string fertigkeit, object spieler );
dann wird deren Returnwert als Text ausgegeben, wenn der Spieler
versucht, innerhalb des Delays die Fertigkeit anzuwenden. Damit
lassen sich originellere Meldungen als die Standard-Message ausge-
ben.
War die Ausfuehrung ein Fehlschlag (MISSERFOLG), werden die Kosten
auch abgezogen und das Delay gesetzt. Zusaetzlich lernt der Spieler
jedoch aus seinem Fehler. Und zwar um den Wert:
random( ( ( stat * factor ) / 40 ) + 1 )
Dabei ist 'stat' der Wert des Attributs welches man beim Anmelden
der Fertigkeit als Argument 'stat' angegeben hat. Variable 'factor'
entspricht dem Argument 'factor' beim Anmelden der Fertigkeit beim
Skillmaster.
Ein Rechenbeispiel: Wurde die Fertigkeit mit 'stat' A_INT und
'factor' 15 angemeldet und der aufrufende Spieler hat eine Intelli-
genz von 12, dann lernt der Spieler beim MISSERFOLG um folgenden
Wert dazu:
random( ( ( 12 * 15 ) / 40 ) + 1 ) -> entspricht -> random( 5 );
Der Spieler lernt die Fertigkeit also um einen Wert zwischen Null
und 4. Wem das zu wenig ist, der muss den Faktor entsprechend hoeher
setzen beim Anmelden.
Es kann bei zu niedrigen Stats passieren, dass ein Spieler den
Spruch nicht verbessern kann, da ( stat * factor ) / 40 ) immer
kleiner 1 ist. In diesem Fall erhaelt der Spieler eine entsprechende
Meldung, dass seine Stats zu klein sind. Will man diese Meldung
unterdruecken - z.b. weil das Lernen ohnehin 'per hand' vom Spellbook
gemacht wird, dann kann man den Factor auf 0 setzen. Aber dann wird
der Spieler den Spruch niemals automatisch verbessern! Dann muss man
das wirklich ueber das Spellbook oder die Gilde machen lassen.
F.2.4 KOSTEN
Ist im Spellbook die Funktion get_cost_reduction definiert, so wird
diese vor dem Abzug der Kosten als
get_cost_reduction( string fertigkeit, object spieler,
int kosten, int resultat );
aufgerufen. Die zurueckgegebene Zahl wird von den Kosten abgezogen.
Dabei sind kosten die nicht reduzierten Kosten und resultat entweder
ERFOLG oder MISSERFOLG.
F.3. ZUSAMMENFASSUNG
Das alles sieht komplizierter aus, als es ist. Wichtig ist:
a. Ein Spellbook-Objekt mit mindestens einer Funktion _cast_XYZ()
Fuer Polling-Fertigkeiten auch noch _poll_XYZ()
b. In _cast_XYZ() checken, ob die Argumente ok sind.
c. Wahrscheinlichkeit des Erfolgs berechnen.
d. Wenn Erfolg -> irgendwas Tolles machen. :-)
e. Returnwerte richtig zurueckgeben FEHLER, ERFOLG oder MISSERFOLG.
f. Gnadenlos von /spellbooks/beispiel.c abkupfern. ;-)
g. Den Gleichgewichtsmagier oder einem Erzmagier abnicken lassen.
h. Die Fertigkeit richtig anmelden (lassen).
Fuer Polling-Fertigkeiten unbedingt mit Flag SM_F_POLL !
G. PROGRAMMIERUNG VON COMBAT-SKILLS
Fuer Combat-Skills kann _cast_XYZ() den Zusatzschaden zurueckgeben.
Da die Zukunft dieses Skill-Typs zur zeit etwas unklar ist, gibts
dazu im Moment keine weitere Doku. Wenn es nicht anders geht, bitte
Holger fragen...
Fuer ein Beispiel zu den Combat-Skills ist in /spellbooks/race_skills.c
der Sprungangriff der Marranen einsehbar.
SIEHE AUCH:
AddAbility(L), AddSkillAction(L), CountPolls(L), Execute(L),
FindPoll(L), GetProbability(L), GiveAbility(L), QueryDelay(L),
QuerySkillVerbs(L), RemoveAbility(L), RemovePolls(L),
SetActiveSkill(L), SetDelayTime(L), SetSkillVerbs(L), Valid(L),
_cast_(L), P_ACTIVE_SKILLS, P_LAST_SKILL, P_SKILLS,
P_COMBAT_SKILLS, P_MAX_SKILLS, P_SM_VERBS, skillmaster(SEC),
P_SKILLS_ID, QuerySkillIDs(L), DisableAbility(L), EnableAbility(L),
QueryDisablerIDs(L), get_delay(L), get_delay_msg(L)
|