Returnbug und Gamecache

[nocontentad]

Return Bug
+ Game-Cache

zum
Anfang
1. Vorwort

Achtung: Die meisten in diesem Tutorial beschriebenen Verfahren funktionieren seit dem Patch 1.24 nicht mehr und waren auch davor schon veraltet.

Dieses Tutorial richtet sich an all jene, die schon Erfahrung
mit JASS haben und nun wirklich daran Interesse haben JASS
für Trigger zu nutzen die mit dem GUI unmöglich sind.
Alle anderen werden in diesem Tutorial auch einiges neues
erfahren, dennoch ist es sinnvoller, wenn man bereits einige
Grundkenntnisse hat, zu diesem Zweck sei an dieser Stelle noch
einmal die Einführung
sowie die
Grundlagen
von JASS erwähnt.

zum
Anfang
2. Return Bug

Jetzt stellt sich vermutlich die Frage, worum es sich hierbei
handelt. Dies ist eigentlich relativ einfach erklärt.

Wenn man eine JASS Funktion schreibt, so muss man im Header
bereits angeben, welchen Typ diese Funktion später an den
Aufrufer zurückgibt. Dieser im Header definierte Typ muss
dann mit dem tatsächlich zurückgegebenen
übereinstimmen, ansonsten meckert der Compiler:

function FOO takes unit u returns unit

    return u

endfunction

ist zulässig, während dies hier nicht funktioniert:

function FOO takes unit u returns integer

    return u

endfunction

Soweit sehen wir also noch keinen Unterschied zu einer
herkömmlichen Programmiersprache, doch hier kommt der
Return Bug ins Spiel. Zuerst worum es sich hierbei handelt. Bei
einer Funktion wird nämlich immer nur das letzte return
auf den richtigen Typ überprüft, dh. wenn man vor
diesem return noch einige andere hat, ist es vollkommen egal
was diese zurückgeben, der Compiler findet keinen Fehler
und auch ingame funktionieren diese returns (auch wenn der Typ
falsch ist).

Ein Beispiel hierfür:

function U2I takes unit u returns integer

    return u

    return 0

endfunction

Dieser Code ruft keinen Fehler hervor und funktioniert auch
ingame ohne Probleme. Doch was passiert hier eigentlich? Es
wird eine Einheit zurückgegeben, obwohl eigentlich ein
Integer zurückkommen sollte. Nun ganz einfach. Bei einer
unit handelt es sich um Objekt (handle), und wenn diese
Einheitenreferenz nun zurückgegeben wird, so erfolgt eine
Umwandlung in einen (den ja eigentlich erwarteten) Integer.

Der Integer der hierbei erzeugt wird ist die numerische
Darstellung der Einheitenreferenz. (Für alle die jetzt
sich jetzt fragen was das ist: hierbei ist WC3 in gewissem
Maße JAVA ähnlich, also die definierten Variablen
vom Typ handle sind Referenzen, dh. sie ermöglichen den
Zugriff auf Objekte die tatsächlich vorhanden sind (also
zB.: Einheiten, Einheitengruppen, usw.) sind jedoch selber
nicht diese Objekte (deswegen kommt es auch zu Speicherlecks,
denn die Objekte sind auch noch vorhanden wenn die Variablen
(dh. die Referenzen auf diese Objekte) mit denen man auf diese
zugegriffen hat nicht mehr existieren…)

Mittels dieses Bugs kann man also alle nicht-primitiven
Datentypen von WC3 in primitive umwandeln. Zwar kann man auch
gleich direkt in String oder Real konvertieren, allerdings ist
dies nicht empehlenswert, und man sollte zuerst immer in einen
Integer umwandeln und diesen dann in Real oder String, da es
sonst zu Datenverlust kommt (bzw. ein real hat immer den Wert
0.0 und ein String null).

zum
Anfang
3. Game-Cache

Jetzt stellt sich für viele vermutlich die Frage wie man
dies praktisch nutzen kann. Nun eines vorweg, die Probleme bei
denen man dies wirklich sehr gut einsetzen kann sind meistens
ein wenig komplexer und nur im MP anzutreffen (wo die Anzahl
von benötigten Werten im Grunde unendlich sein kann oder
könnte). Auch wird man den Game-Cache selber nur dann
brauchen wenn man den Custom-Value nicht nutzen will oder kann.
Alles in allem ist das ein Themengebiet das wohl nur
Fortgeschrittene/Profis relevant sein dürfte.

Nun ein kleines Beispiel wie man dies einsetzen
kann:

Man möchte einen Channeling Spell der während der
Wirkungsdauer einen Turm beschwört. Sobald der Spell
beendet wird (egal ob nach Ende der gesamten Zeit oder durch
vorzeitigen Abbruch) soll der Turm natürlich entfernt
werden. Die Problematik ist klar, man muss eine Zuordnung von
der castenden Einheit zu dem beschworenen Turm schaffen.

  1. Man beschafft sich die Referenznummer von Caster und
    Turm
  2. Man speichert die Nummer des Turms ( = Value) unter
    Nummer des Casters (= Key) ab
  3. Wenn nun aufgehört wird die Fähigkeit zu
    wirken, dann wandelt man die Referenz des Caster wieder in
    einen Integer um und liest dann den Wert aus dem Game
  4. Cache aus, der über dieses Schlüssel
    verfügt.
  5. Den ausgelesenen Wert (von dem wir ja wissen, dass es
    sich um eine Einheit handelt) wandeln wir wieder in eine
    Einheit um, diese wird dann entfernt.

Speichern:

    call StoreIntegerBJ(
Unit2Integer(dummy),

                      
I2S(Unit2Integer(caster)), „AoD“, udg_GC_Cache )

Laden

   local integer data =
GetStoredIntegerBJ(I2S(Unit2Integer(caster)),

                                         
„AoD“, udg_GC_Cache)

  local unit dummy = Integer2Unit(data)

bzw.

    local unit dummy =
Integer2Unit(GetStoredIntegerBJ(

                                 
I2S(Unit2Integer(caster)),

                                      „AoD“,
udg_GC_Cache))

udg_GC_Cache ist hierbei der Cache, dummy der Turm und caster
eben die castende Einheit, das „AoD“ ist eine freiwählbare
Kategorie (muss dann aber bei speichern und laden gleich sein).


Alternativen:

Man kann mit Customvalue arbeiten, in dem eben die
Referenznummer des Turmes im Customvalue des jeweiligen Casters
speichert, dies ist schneller, jedoch kann es ja sein, dass der
Customvalue schon benutzt ist oder man ihn noch für etwas
anderes braucht.

Auch kann man mit (Einheiten) Arrays arbeiten, dies ist
empfehlenswert wenn es sich zB um einen Heldenspell handelt,
und man weiß, dass eben jeder Spieler maximal 1x
über dieses Spell verfügen kann oder man braucht
überhaupt nur eine einfache Variable, da
der Spell nur von einem Held benutzt werden kann (zB. in einer
AoS).

Wenn man jedoch nicht weiß wie oft ein Spell auf einer
Map vorhanden ist (oder sonst etwas, muss ja kein Spell sein)
handelt es sich hierbei um eine sehr elegante Variante, da man
nicht umständlich erst Werte vergleichen muss, wie es der
Fall wäre wenn man trotzdem mit Arrays arbeitet, sondern
direkt auf den gewünschten Wert zugreifen kann.

zum
Anfang
4. Schlußwort

Dieses Tutorial mag ein wenig abstrakt wirken, jedoch
dürfte es ohnehin nur für die interessant sein, die
schon ein wenig Basiswissen bezüglich JASS angehäuft
haben. Diejenigen welche sich jedoch ein wenig mit der Materie
auskennen werden hoffentlich ihre Freude mit diesem Know-How
haben, da es teilweise ganz neue Möglichkeiten
eröffnet. Sollte es noch Fragen geben, dann schaut einfach
im Forum oder im IRC-Channel #inwc.de-maps vorbei.

zum Anfang

  • 29.07.2008 um 01:59
30.07. 19:00 :de: aTn 2:3 :de: raptor Spells mit Jass

Returnbug und Gamecache

[nocontentad]

Return Bug
+ Game-Cache

zum
Anfang
1. Vorwort

Achtung: Die meisten in diesem Tutorial beschriebenen Verfahren funktionieren seit dem Patch 1.24 nicht mehr und waren auch davor schon veraltet.

Dieses Tutorial richtet sich an all jene, die schon Erfahrung
mit JASS haben und nun wirklich daran Interesse haben JASS
für Trigger zu nutzen die mit dem GUI unmöglich sind.
Alle anderen werden in diesem Tutorial auch einiges neues
erfahren, dennoch ist es sinnvoller, wenn man bereits einige
Grundkenntnisse hat, zu diesem Zweck sei an dieser Stelle noch
einmal die Einführung
sowie die
Grundlagen
von JASS erwähnt.

zum
Anfang
2. Return Bug

Jetzt stellt sich vermutlich die Frage, worum es sich hierbei
handelt. Dies ist eigentlich relativ einfach erklärt.

Wenn man eine JASS Funktion schreibt, so muss man im Header
bereits angeben, welchen Typ diese Funktion später an den
Aufrufer zurückgibt. Dieser im Header definierte Typ muss
dann mit dem tatsächlich zurückgegebenen
übereinstimmen, ansonsten meckert der Compiler:

function FOO takes unit u returns unit

    return u

endfunction

ist zulässig, während dies hier nicht funktioniert:

function FOO takes unit u returns integer

    return u

endfunction

Soweit sehen wir also noch keinen Unterschied zu einer
herkömmlichen Programmiersprache, doch hier kommt der
Return Bug ins Spiel. Zuerst worum es sich hierbei handelt. Bei
einer Funktion wird nämlich immer nur das letzte return
auf den richtigen Typ überprüft, dh. wenn man vor
diesem return noch einige andere hat, ist es vollkommen egal
was diese zurückgeben, der Compiler findet keinen Fehler
und auch ingame funktionieren diese returns (auch wenn der Typ
falsch ist).

Ein Beispiel hierfür:

function U2I takes unit u returns integer

    return u

    return 0

endfunction

Dieser Code ruft keinen Fehler hervor und funktioniert auch
ingame ohne Probleme. Doch was passiert hier eigentlich? Es
wird eine Einheit zurückgegeben, obwohl eigentlich ein
Integer zurückkommen sollte. Nun ganz einfach. Bei einer
unit handelt es sich um Objekt (handle), und wenn diese
Einheitenreferenz nun zurückgegeben wird, so erfolgt eine
Umwandlung in einen (den ja eigentlich erwarteten) Integer.

Der Integer der hierbei erzeugt wird ist die numerische
Darstellung der Einheitenreferenz. (Für alle die jetzt
sich jetzt fragen was das ist: hierbei ist WC3 in gewissem
Maße JAVA ähnlich, also die definierten Variablen
vom Typ handle sind Referenzen, dh. sie ermöglichen den
Zugriff auf Objekte die tatsächlich vorhanden sind (also
zB.: Einheiten, Einheitengruppen, usw.) sind jedoch selber
nicht diese Objekte (deswegen kommt es auch zu Speicherlecks,
denn die Objekte sind auch noch vorhanden wenn die Variablen
(dh. die Referenzen auf diese Objekte) mit denen man auf diese
zugegriffen hat nicht mehr existieren…)

Mittels dieses Bugs kann man also alle nicht-primitiven
Datentypen von WC3 in primitive umwandeln. Zwar kann man auch
gleich direkt in String oder Real konvertieren, allerdings ist
dies nicht empehlenswert, und man sollte zuerst immer in einen
Integer umwandeln und diesen dann in Real oder String, da es
sonst zu Datenverlust kommt (bzw. ein real hat immer den Wert
0.0 und ein String null).

zum
Anfang
3. Game-Cache

Jetzt stellt sich für viele vermutlich die Frage wie man
dies praktisch nutzen kann. Nun eines vorweg, die Probleme bei
denen man dies wirklich sehr gut einsetzen kann sind meistens
ein wenig komplexer und nur im MP anzutreffen (wo die Anzahl
von benötigten Werten im Grunde unendlich sein kann oder
könnte). Auch wird man den Game-Cache selber nur dann
brauchen wenn man den Custom-Value nicht nutzen will oder kann.
Alles in allem ist das ein Themengebiet das wohl nur
Fortgeschrittene/Profis relevant sein dürfte.

Nun ein kleines Beispiel wie man dies einsetzen
kann:

Man möchte einen Channeling Spell der während der
Wirkungsdauer einen Turm beschwört. Sobald der Spell
beendet wird (egal ob nach Ende der gesamten Zeit oder durch
vorzeitigen Abbruch) soll der Turm natürlich entfernt
werden. Die Problematik ist klar, man muss eine Zuordnung von
der castenden Einheit zu dem beschworenen Turm schaffen.

  1. Man beschafft sich die Referenznummer von Caster und
    Turm
  2. Man speichert die Nummer des Turms ( = Value) unter
    Nummer des Casters (= Key) ab
  3. Wenn nun aufgehört wird die Fähigkeit zu
    wirken, dann wandelt man die Referenz des Caster wieder in
    einen Integer um und liest dann den Wert aus dem Game
  4. Cache aus, der über dieses Schlüssel
    verfügt.
  5. Den ausgelesenen Wert (von dem wir ja wissen, dass es
    sich um eine Einheit handelt) wandeln wir wieder in eine
    Einheit um, diese wird dann entfernt.

Speichern:

    call StoreIntegerBJ(
Unit2Integer(dummy),

                      
I2S(Unit2Integer(caster)), „AoD“, udg_GC_Cache )

Laden

   local integer data =
GetStoredIntegerBJ(I2S(Unit2Integer(caster)),

                                         
„AoD“, udg_GC_Cache)

  local unit dummy = Integer2Unit(data)

bzw.

    local unit dummy =
Integer2Unit(GetStoredIntegerBJ(

                                 
I2S(Unit2Integer(caster)),

                                      „AoD“,
udg_GC_Cache))

udg_GC_Cache ist hierbei der Cache, dummy der Turm und caster
eben die castende Einheit, das „AoD“ ist eine freiwählbare
Kategorie (muss dann aber bei speichern und laden gleich sein).


Alternativen:

Man kann mit Customvalue arbeiten, in dem eben die
Referenznummer des Turmes im Customvalue des jeweiligen Casters
speichert, dies ist schneller, jedoch kann es ja sein, dass der
Customvalue schon benutzt ist oder man ihn noch für etwas
anderes braucht.

Auch kann man mit (Einheiten) Arrays arbeiten, dies ist
empfehlenswert wenn es sich zB um einen Heldenspell handelt,
und man weiß, dass eben jeder Spieler maximal 1x
über dieses Spell verfügen kann oder man braucht
überhaupt nur eine einfache Variable, da
der Spell nur von einem Held benutzt werden kann (zB. in einer
AoS).

Wenn man jedoch nicht weiß wie oft ein Spell auf einer
Map vorhanden ist (oder sonst etwas, muss ja kein Spell sein)
handelt es sich hierbei um eine sehr elegante Variante, da man
nicht umständlich erst Werte vergleichen muss, wie es der
Fall wäre wenn man trotzdem mit Arrays arbeitet, sondern
direkt auf den gewünschten Wert zugreifen kann.

zum
Anfang
4. Schlußwort

Dieses Tutorial mag ein wenig abstrakt wirken, jedoch
dürfte es ohnehin nur für die interessant sein, die
schon ein wenig Basiswissen bezüglich JASS angehäuft
haben. Diejenigen welche sich jedoch ein wenig mit der Materie
auskennen werden hoffentlich ihre Freude mit diesem Know-How
haben, da es teilweise ganz neue Möglichkeiten
eröffnet. Sollte es noch Fragen geben, dann schaut einfach
im Forum oder im IRC-Channel #inwc.de-maps vorbei.

zum Anfang

  • 28.07.2008 um 23:59
Jass Einführung Spells mit Jass