Spells mit Jass

[nocontentad]

Spells mit
JASS

zum
Anfang
1. Vorwort

In diesem Tutorial möchte ich zuerst erklären was
lokale Variablen sind, und wie man sie einsetzt und auch warum
man sie (vor allem bei Custom Spells) benutzen sollte. Danach
werde ich die grundlegende Vorgangsweise beim Erstellen von
Spells erklären und warum hierbei JASS ein (großer)
Vorteil ist, im Vergleich zum GUI. Anbei befindet sich auch
eine Beispielmap in der ihr euch den Spell nochmal ansehen und
ausprobieren könnt.

zum
Anfang
2. Lokale Variablen

Nun, was sind lokale Variablen? Lokale Variablen sind Variablen
welche innerhalb einer JASS Funktion mittels des
Schlüsselwortes local definiert werden und auch
nur innerhalb dieser JASS Funktion exisiteren, dh. man hat von
außerhalb dieser Funktion keinen Zugriff auf dieses
Variablen. Es besteht also ein wesentlicher Unterschied zu
globalen Variablen. Globale Variablen werden im GUI benutzt,
und zwar ausschließlich – man kann von jedem Trigger aus
auf diese Variablen zugreifen und sie benutzen, sie haben im
gesamten Script der Map Gültigkeit, allerdings sind sie
auch nur exakt einmal im Speicher vorhanden, dh. wenn der
gleiche Trigger (oder verschiedene) auf eine globale Variable
zugreift, dann wird der jeweils alte Wert überschrieben.

Dies ist auch der Hauptnachteil bei den globalen Variablen, da
es passieren kann, dass ein anderer (oder der gleiche Trigger)
den Wert dieser Variable verändert, obwohl man den alten
Wert noch woanders benötigt. Wenn man zB einen Trigger hat
in dem eine Einheit in einer Variable gespeichert wird, und
dann wird ein wait aufgeruft und der gleiche Wert wird nochmal
aufgerufen kann es zu Problemen kommen (dies ist nur ein
Beispiel zu Veranschaulichung des Problems, in Wirklichkeit
kann man sich die Variable sparen und man kann nur mit
<dying unit> arbeiten.:

E: A unit dies

B: ( <dying unit> is a hero ) = true

A: set toteEinheit = <dying unit>

   Wait 20 seconds

   Held – Revive toteEinheit

Wenn es nun passiert, dass ein Held stirbt, so wird
dieser Held in einer Variablen abgespeichert und nach 20
Sekunden wird der Held wiederbelebt. Stirbt jedoch nach 18
Sekunden wieder ein Held, so wird dieser in der globalen
Variable gespeichert und dann nach 2 Sekunden wiederbelebt (der
1. Aufruf des Triggers wird ja noch beendet, und am Ende wird
die Einheit die in der Variable gespeichert ist wiederbelebt),
der erste Held der gestorben ist wird hingegen nicht
wiederbelebt.Dies ist die Problematik von globalen Variablen.
Werden hingegen lokale Variablen benutzt, so haben diese ja nur
Gültigkeit innerhalb der Funktion in der sie definiert
wurden, und wenn jetzt ein Trigger (eine Funktion) mehrmals
aufgerufen wird, so verfügt jeder Aufruf auch über
eigene lokale Variablen, dh. die einzelnen Aufrufe der Trigger
können sich nicht gegenseitig die von ihnen gespeicherten
Werte überschreiben. (das gleiche Beispiel in JASS mit
lokalen Variablen um die angesprochene Problematik zu umgehen):

function Trig_asfd_Conditions takes nothing returns
boolean

  return ( IsUnitType(GetDyingUnit(),
UNIT_TYPE_HERO) == true )

endfunction

function Trig_asfd_Actions takes nothing returns nothing

  local unit toteEinheit = GetDyingUnit()

  call TriggerSleepAction( 20.00 )

  call ReviveHeroLoc( toteEinheit,
GetRectCenter(GetPlayableMapRect()), false )

endfunction

function InitTrig_asfd takes nothing returns nothing

  set gg_trg_asfd = CreateTrigger( )

  call TriggerRegisterAnyUnitEventBJ( gg_trg_asfd,
EVENT_PLAYER_UNIT_DEATH )

  call TriggerAddCondition( gg_trg_asfd, Condition(
function Trig_asfd_Conditions ) )

  call TriggerAddAction( gg_trg_asfd, function
Trig_asfd_Actions )

endfunction

In der orange markierten Zeile befindet sich die
Definition der lokalen Variable anschließend wird ihr ein
Wert zugewiesen. Dies muss nicht gleich passieren, sondern man
kann dies zB.: in der nächsten Zeile mit set toteEinheit =
GetDyingUnit() machen, allerdings sollte man Variablen gleich
bei der Initialisierung einen Wert zuweisen (entweder den
Standardwert (Leerstring, 0, null) oder eben gleich den
passenden Wert, sofern dieser schon verfügbar ist).

Dann kann die Variable eben wie eine herkömmliche globale
Variable benutzt werden.

Achtung: die Definition von lokalen Variablen
muss am Anfang der Funktion erfolgen, außer anderen
Variablen Definition darf nichts davor stehen.

Allgemeine Syntax zur Definition: local
type name [ = value ]Ein ebenfalls
prädestiniertes Einsatzgebiet von lokalen Variablen sind
Schleifen, denn standardmäßig stehen nur Integer A
und B zur Verfügung, dh. bei mehr als 2 Schleifen die
gleichzeitig laufen können wird man früher oder
später Probleme bekommen.

zum
Anfang
3. Spells mit JASS


Allgemeine Infos:
Wenn man seine Spells mittels JASS
optimiert, bzw. diese überhaupt erst richtig
funktionsfähig bekommt empfiehlt es sich oftmals das
Grundgerüst mit dem GUI zu machen, und dann eben in JASS
Code zu konvertieren, gerade für Anfänger stellst das
eine erhebliche Erleichterung dar. Doch warum überhaupt
JASS Code für Spells? Nun ganz einfach, vor allem bei
Multiplayermaps sind die lokalen Variablen ein entscheidender
Vorteil, da man sich nicht darum kümmern muss, das es
passieren kann, dass mehrere Spieler zur selben Zeit denselben
Spell benutzen (wenn man lokale Variablen einsetzt). Zum
anderen kann man einige Dinge machen, die mit dem GUI einfach
nicht möglich sind, sowie die Performance
verbessern.Beispiel Spell:Bei dem Spell den
ich für dieses Beispiel gewählt habe wählt der
Held eine Einheit oder einen Punkt an, dann verwandelt er sich
in eine Welle aus Eis, welche sich bis zu diesem Punkt
ausbreitet – dort erscheint der Held dann wieder in seiner
ursprünglichen Form. Alle Gegner die er auf dem Weg
dazwischen berührt nehmen Schaden und werden verlangsamt.

Nun – wie ist dies realisiert:

  • Schockwelle als Dummyspell (ohne Schaden, Grafik
    usw.)
  • Dann Frostnova (dieser Spell wird auf die Einheiten
    gecastet, welche von der Eiswelle getroffen werden
  • Und die Dummyeinheiten, welche die Frostnova casten
    (natürlich ohne Modell usw, sie sollen ja nicht gesehen
    werden)
  • und eben ein entsprechender Trigger

Diese Rezepte braucht man im Grunde für einen jeden
Spell den man sich selber baut.

1.
Trigger/Ereignis

function InitTrig_HS_Wave_of_Ice_JASS takes nothing returns
nothing

  set gg_trg_HS_Wave_of_Ice_JASS = CreateTrigger( )

  call TriggerRegisterAnyUnitEventBJ(
gg_trg_HS_Wave_of_Ice_JASS, EVENT_PLAYER_UNIT_SPELL_CAST )

  call TriggerAddCondition(
gg_trg_HS_Wave_of_Ice_JASS, Condition( function
Trig_HS_Wave_of_Ice_JASS_Conditions ) )

  call TriggerAddAction(
gg_trg_HS_Wave_of_Ice_JASS, function
Trig_HS_Wave_of_Ice_JASS_Actions )

endfunction

Der 1. Schritt ist wie schon gehabt, es wird einfach der
Trigger erzeugt und die entsprechenden Funktionen für die
Bedingung und die Aktionen hinzugefügt. Das hier orange
markierte Ereignis ist A unit beginnt das wirken einer
Fähigkeit, also das übliche, wenn man sich sich
selber Spells triggern möchte ;).2. Die
Bedingung

function Trig_HS_Wave_of_Ice_JASS_Conditions takes nothing
returns boolean

  return ( GetSpellAbilityId() == ‚A002‘ )

endfunction function

Es handelt sich einfach um die Abfrage, ob die gecastete
Fähigkeit diejenige ist, für die der Trigger laufen
soll, wenn der Code ein wenig optimiert wurde sollte er so
aussehen.

3. Die Aktionen

Zuerst werden alle benötigten Variablen deklariert,
Erläuterungen siehe Code:

// der caster des Spells

local unit caster = GetSpellAbilityUnit()

// Einheit die benutzt wird um Frostnova zu casten

local unit dmgUnit = null

// die Zieleinheit auf die Frostnova gecastet wird

local unit targetUnit= null

// das Ziel (wohin geht die Eiswelle)

local location targetPos = GetSpellTargetLoc()

// Startpunkt

local location sourcePos = GetUnitLoc(caster)

// Diese 2 Punktvariablen werden benötigt um
Speicherlecks zu verhindern

local location l = null

local location l1 = null

// Entfernung zwischen Start und Zielpunkt

local real distance = DistanceBetweenPoints(sourcePos,
targetPos)

// Die Einheitengruppe auf welche Frostnova gecastet werden
soll

local group targets = null

// Speichert die Effekte für die Eiswelle

local effect array dmgEffects

// Schleifendurchlauf: Der Held wird immer um 100
weiterbewegt, dann werden alle

// Einheiten in Radius gepickt und Nova drauf gecastet, hier
wird die Anzahl der

// Positionen zwischen Start und Ziel berechnet

local integer loopIndexEnd = R2I( (distance / 100) + 1)

local integer loopIndex = 0

Im nächsten Teil wird der Held zuerst für den Spieler
komplett unsichtbar gemacht, so dass er keinen Einfluss
während der Spelldauer haben kann. Danach wird erfolgt die
Schleife – diese Wird sooft durchlaufen, wie es eben die
Distanz von Start zu Ziel durch 100 zulässt. Dann wird mit
der PolarProjection der „Weg“ des Spells bestimmt, bei
jedem Schleifendurchlauf rückt die Position l um
100 weiter Richtung Ziel.

Dann wird eine Einheitengruppe erzeugt die alle Einheiten in
einem Radius von 80 um die Position l beinhaltet. Dann
wird noch ein passender Effekt an der Stelle erzeugt, damit man
erkennen kann wieweit der Spell schon fortgeschritten ist. Das
nachfolgende Zerstören der Position verhindert ein
Speicherleck (genaueres im entsprechenden Tutorial).

call ShowUnitHide( caster )

call SetUnitInvulnerable( caster, true )

call SelectUnitRemoveForPlayer( caster,
GetOwningPlayer(caster) )

loop

  exitwhen loopIndex > loopIndexEnd

  set l = PolarProjectionBJ(sourcePos, ( loopIndex
* 100.00 ), AngleBetweenPoints(sourcePos, targetPos))

  set targets = GetUnitsInRangeOfLocAll(80.00, l)

  set dmgEffects[loopIndex] =
AddSpecialEffectLocBJ( l,
„Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl“ )

  call RemoveLocation( l )

Nun folgt ein Konstrukt, welches zum Abarbeiten der
Einheitengruppe, auf die Nova gecastet werden soll, dient.
Dieses Konstrukt ist nur in JASS machbar. Wenn man auf
herkömmliche weise die Einheiten in einer Gruppe
abarbeiten will, dann wird eine eigene Funktion dafür
erzeugt die dann aufgerufen wird. Dies hätte aber den
Nachteil, dass man auf die lokalen Variablen der aufrufenden
Funktion nicht zugreifen kann und diese per Paramenter
übergeben muss oder gar gezwungen ist mit globalen
Variablen zu arbeiten.

Zuerst wird die erste Einheit der Gruppe in einer seperaten
Variable gespeichert, wenn diese Einheit keinen Wert hat (also
keine erste Einheit in der Gruppe mehr existiert), so wird mit
dem abarbeiten der folgenden Schleife aufgehört. In der
Schleife wird einfach eine Dummyeinheit an der Stelle der
Zieleinheit erzeugt die dann Nova auf diese castet (man beachte
wieder das nachfolgende Entfernen der Position). Der Einheit
wird auch ein Expiration-Timer gesetzt, was bedeutet dass sie
nach Ablauf von 3 Sekunden stirbt. (die Zeit reicht aber zum
casten des Spells).

Anschließend wird die Zieleinheit aus der Einheitengruppe
entfernt und in die Variable targetUnit wird die
nächste erste Einheit der Gruppe gespeichert, dann beginnt
die Schleife von neuem, so lange bis eben keine Einheiten mehr
in der Gruppe vorhanden sind.

Nach dieser Schleife wird die Einheitengruppe zerstört
(Speicherleck) und ein Wait (Game-Time) aufgerufen,
damit ein wenig Zeit zwischen den Effekten ist,
anschließend muss noch der Index für Schleife
erhöht werden.

Im orange markierten Teil wird noch auf eine Besonderheit von
JASS hingewiesen. Und zwar geben die meisten Funktionen Werte
zurück. In dem Fall handelt es sich um das erzeugen von
einer Einheit. Im GUI müsste man diese in der
nächsten Zeile mit <last created unit> in
einer Variable speichern, in JASS kann man dies in einer Zeile
erledigen, da die Funktion eine Einheit zurückgibt =>
man kann dies gleich in einer Variablen speichern. Auch
könnte man diesen Funktionsaufruf in einer anderen
Funktion als Parameter nutzen, die eben einen enstprechenden
Wert erwartet (Enheit in diesem Fall).

  set targetUnit = FirstOfGroup(targets)

  loop

    exitwhen targetUnit == null

    set l = GetUnitLoc(targetUnit)

    // Die Verwendeten Einheitentypen
sind in einer globalen Variable definiert. Abhängig vom
Level wird eine andere erstellt (hat auswirkungen auf den
Schaden – siehe Beispielmap)

    set dmgUnit = CreateUnitAtLoc(
GetOwningPlayer(caster), udg_E_HS_WoI_DmgEinheiten[
GetUnitAbilityLevelSwapped(‚A002‘, caster) ], l,
bj_UNIT_FACING )

    call UnitApplyTimedLifeBJ( 3.00,
‚BTLF‘, dmgUnit )

    call IssueTargetOrderBJ( dmgUnit,
„frostnova“, targetUnit )

    call RemoveLocation(l)

    call
GroupRemoveUnitSimple(targetUnit,targets)

    set targetUnit =
FirstOfGroup(targets)

  endloop

  call DestroyGroup(targets)

  call PolledWait ( 0.10 )

  set loopIndex = loopIndex + 1

endloop

Im letzten Teil wird dann der Held an die Zielposition
bewegt, sicht- und verwundbar gemacht sowie automatisch
ausgewählt. Anschließend erfolgt wieder das
Entfernen der Speicherlecks die durch die Positionen
verursacht werden würden. In der letzten Schleife werden
dann noch die Effekte zerstört.

call SetUnitPositionLoc( caster, targetPos )

call ShowUnitShow( caster )

call SetUnitInvulnerable( caster, false )

call SelectUnitAddForPlayer( caster, GetOwningPlayer(caster)
)

set loopIndex = 0

call RemoveLocation( targetPos )

call RemoveLocation( sourcePos )

loop

  exitwhen loopIndex > loopIndexEnd

  call DestroyEffectBJ( dmgEffects[loopIndex] )

  set loopIndex = loopIndex + 1

endloop

endfunction

Download
Beispielmap

zum
Anfang
4. Schlußwort

Auch wenn dieses Tutorial (also der Spellteil) doch ein wenig
spezifisch ist (Allgemein ohne praktisches Beispiel zu
erklären ist eine, meiner Meinung nach, zu abstrakte
Angelegenheit), so denke ich, dass es den meisten von euch eine
Hilfe sein wird, um zu Verstehen wie man JASS für Spells
sinnvoll einsetzen kann. Es gibt noch eine Vielzahl von anderen
Möglichkeiten wie man etwas machen kann, jedoch ist dies
eurer Kreativität und Einfallsreichtum überlassen.
Wenn ihr nun noch Fragen habt oder mehr erfahren wollt, so
besucht das Forum oder aber schaut auch die Spells von Darky
an, denn aus ihnen kann man ebenfalls viel lernen.

zum Anfang

  • 29.07.2008 um 01:52
Returnbug und Gamecache Jass Einführung (Mueslirocker)

Spells mit Jass

[nocontentad]

Spells mit
JASS

zum
Anfang
1. Vorwort

In diesem Tutorial möchte ich zuerst erklären was
lokale Variablen sind, und wie man sie einsetzt und auch warum
man sie (vor allem bei Custom Spells) benutzen sollte. Danach
werde ich die grundlegende Vorgangsweise beim Erstellen von
Spells erklären und warum hierbei JASS ein (großer)
Vorteil ist, im Vergleich zum GUI. Anbei befindet sich auch
eine Beispielmap in der ihr euch den Spell nochmal ansehen und
ausprobieren könnt.

zum
Anfang
2. Lokale Variablen

Nun, was sind lokale Variablen? Lokale Variablen sind Variablen
welche innerhalb einer JASS Funktion mittels des
Schlüsselwortes local definiert werden und auch
nur innerhalb dieser JASS Funktion exisiteren, dh. man hat von
außerhalb dieser Funktion keinen Zugriff auf dieses
Variablen. Es besteht also ein wesentlicher Unterschied zu
globalen Variablen. Globale Variablen werden im GUI benutzt,
und zwar ausschließlich – man kann von jedem Trigger aus
auf diese Variablen zugreifen und sie benutzen, sie haben im
gesamten Script der Map Gültigkeit, allerdings sind sie
auch nur exakt einmal im Speicher vorhanden, dh. wenn der
gleiche Trigger (oder verschiedene) auf eine globale Variable
zugreift, dann wird der jeweils alte Wert überschrieben.

Dies ist auch der Hauptnachteil bei den globalen Variablen, da
es passieren kann, dass ein anderer (oder der gleiche Trigger)
den Wert dieser Variable verändert, obwohl man den alten
Wert noch woanders benötigt. Wenn man zB einen Trigger hat
in dem eine Einheit in einer Variable gespeichert wird, und
dann wird ein wait aufgeruft und der gleiche Wert wird nochmal
aufgerufen kann es zu Problemen kommen (dies ist nur ein
Beispiel zu Veranschaulichung des Problems, in Wirklichkeit
kann man sich die Variable sparen und man kann nur mit
<dying unit> arbeiten.:

E: A unit dies

B: ( <dying unit> is a hero ) = true

A: set toteEinheit = <dying unit>

   Wait 20 seconds

   Held – Revive toteEinheit

Wenn es nun passiert, dass ein Held stirbt, so wird
dieser Held in einer Variablen abgespeichert und nach 20
Sekunden wird der Held wiederbelebt. Stirbt jedoch nach 18
Sekunden wieder ein Held, so wird dieser in der globalen
Variable gespeichert und dann nach 2 Sekunden wiederbelebt (der
1. Aufruf des Triggers wird ja noch beendet, und am Ende wird
die Einheit die in der Variable gespeichert ist wiederbelebt),
der erste Held der gestorben ist wird hingegen nicht
wiederbelebt.Dies ist die Problematik von globalen Variablen.
Werden hingegen lokale Variablen benutzt, so haben diese ja nur
Gültigkeit innerhalb der Funktion in der sie definiert
wurden, und wenn jetzt ein Trigger (eine Funktion) mehrmals
aufgerufen wird, so verfügt jeder Aufruf auch über
eigene lokale Variablen, dh. die einzelnen Aufrufe der Trigger
können sich nicht gegenseitig die von ihnen gespeicherten
Werte überschreiben. (das gleiche Beispiel in JASS mit
lokalen Variablen um die angesprochene Problematik zu umgehen):

function Trig_asfd_Conditions takes nothing returns
boolean

  return ( IsUnitType(GetDyingUnit(),
UNIT_TYPE_HERO) == true )

endfunction

function Trig_asfd_Actions takes nothing returns nothing

  local unit toteEinheit = GetDyingUnit()

  call TriggerSleepAction( 20.00 )

  call ReviveHeroLoc( toteEinheit,
GetRectCenter(GetPlayableMapRect()), false )

endfunction

function InitTrig_asfd takes nothing returns nothing

  set gg_trg_asfd = CreateTrigger( )

  call TriggerRegisterAnyUnitEventBJ( gg_trg_asfd,
EVENT_PLAYER_UNIT_DEATH )

  call TriggerAddCondition( gg_trg_asfd, Condition(
function Trig_asfd_Conditions ) )

  call TriggerAddAction( gg_trg_asfd, function
Trig_asfd_Actions )

endfunction

In der orange markierten Zeile befindet sich die
Definition der lokalen Variable anschließend wird ihr ein
Wert zugewiesen. Dies muss nicht gleich passieren, sondern man
kann dies zB.: in der nächsten Zeile mit set toteEinheit =
GetDyingUnit() machen, allerdings sollte man Variablen gleich
bei der Initialisierung einen Wert zuweisen (entweder den
Standardwert (Leerstring, 0, null) oder eben gleich den
passenden Wert, sofern dieser schon verfügbar ist).

Dann kann die Variable eben wie eine herkömmliche globale
Variable benutzt werden.

Achtung: die Definition von lokalen Variablen
muss am Anfang der Funktion erfolgen, außer anderen
Variablen Definition darf nichts davor stehen.

Allgemeine Syntax zur Definition: local
type name [ = value ]Ein ebenfalls
prädestiniertes Einsatzgebiet von lokalen Variablen sind
Schleifen, denn standardmäßig stehen nur Integer A
und B zur Verfügung, dh. bei mehr als 2 Schleifen die
gleichzeitig laufen können wird man früher oder
später Probleme bekommen.

zum
Anfang
3. Spells mit JASS


Allgemeine Infos:
Wenn man seine Spells mittels JASS
optimiert, bzw. diese überhaupt erst richtig
funktionsfähig bekommt empfiehlt es sich oftmals das
Grundgerüst mit dem GUI zu machen, und dann eben in JASS
Code zu konvertieren, gerade für Anfänger stellst das
eine erhebliche Erleichterung dar. Doch warum überhaupt
JASS Code für Spells? Nun ganz einfach, vor allem bei
Multiplayermaps sind die lokalen Variablen ein entscheidender
Vorteil, da man sich nicht darum kümmern muss, das es
passieren kann, dass mehrere Spieler zur selben Zeit denselben
Spell benutzen (wenn man lokale Variablen einsetzt). Zum
anderen kann man einige Dinge machen, die mit dem GUI einfach
nicht möglich sind, sowie die Performance
verbessern.Beispiel Spell:Bei dem Spell den
ich für dieses Beispiel gewählt habe wählt der
Held eine Einheit oder einen Punkt an, dann verwandelt er sich
in eine Welle aus Eis, welche sich bis zu diesem Punkt
ausbreitet – dort erscheint der Held dann wieder in seiner
ursprünglichen Form. Alle Gegner die er auf dem Weg
dazwischen berührt nehmen Schaden und werden verlangsamt.

Nun – wie ist dies realisiert:

  • Schockwelle als Dummyspell (ohne Schaden, Grafik
    usw.)
  • Dann Frostnova (dieser Spell wird auf die Einheiten
    gecastet, welche von der Eiswelle getroffen werden
  • Und die Dummyeinheiten, welche die Frostnova casten
    (natürlich ohne Modell usw, sie sollen ja nicht gesehen
    werden)
  • und eben ein entsprechender Trigger

Diese Rezepte braucht man im Grunde für einen jeden
Spell den man sich selber baut.

1.
Trigger/Ereignis

function InitTrig_HS_Wave_of_Ice_JASS takes nothing returns
nothing

  set gg_trg_HS_Wave_of_Ice_JASS = CreateTrigger( )

  call TriggerRegisterAnyUnitEventBJ(
gg_trg_HS_Wave_of_Ice_JASS, EVENT_PLAYER_UNIT_SPELL_CAST )

  call TriggerAddCondition(
gg_trg_HS_Wave_of_Ice_JASS, Condition( function
Trig_HS_Wave_of_Ice_JASS_Conditions ) )

  call TriggerAddAction(
gg_trg_HS_Wave_of_Ice_JASS, function
Trig_HS_Wave_of_Ice_JASS_Actions )

endfunction

Der 1. Schritt ist wie schon gehabt, es wird einfach der
Trigger erzeugt und die entsprechenden Funktionen für die
Bedingung und die Aktionen hinzugefügt. Das hier orange
markierte Ereignis ist A unit beginnt das wirken einer
Fähigkeit, also das übliche, wenn man sich sich
selber Spells triggern möchte ;).2. Die
Bedingung

function Trig_HS_Wave_of_Ice_JASS_Conditions takes nothing
returns boolean

  return ( GetSpellAbilityId() == ‚A002‘ )

endfunction function

Es handelt sich einfach um die Abfrage, ob die gecastete
Fähigkeit diejenige ist, für die der Trigger laufen
soll, wenn der Code ein wenig optimiert wurde sollte er so
aussehen.

3. Die Aktionen

Zuerst werden alle benötigten Variablen deklariert,
Erläuterungen siehe Code:

// der caster des Spells

local unit caster = GetSpellAbilityUnit()

// Einheit die benutzt wird um Frostnova zu casten

local unit dmgUnit = null

// die Zieleinheit auf die Frostnova gecastet wird

local unit targetUnit= null

// das Ziel (wohin geht die Eiswelle)

local location targetPos = GetSpellTargetLoc()

// Startpunkt

local location sourcePos = GetUnitLoc(caster)

// Diese 2 Punktvariablen werden benötigt um
Speicherlecks zu verhindern

local location l = null

local location l1 = null

// Entfernung zwischen Start und Zielpunkt

local real distance = DistanceBetweenPoints(sourcePos,
targetPos)

// Die Einheitengruppe auf welche Frostnova gecastet werden
soll

local group targets = null

// Speichert die Effekte für die Eiswelle

local effect array dmgEffects

// Schleifendurchlauf: Der Held wird immer um 100
weiterbewegt, dann werden alle

// Einheiten in Radius gepickt und Nova drauf gecastet, hier
wird die Anzahl der

// Positionen zwischen Start und Ziel berechnet

local integer loopIndexEnd = R2I( (distance / 100) + 1)

local integer loopIndex = 0

Im nächsten Teil wird der Held zuerst für den Spieler
komplett unsichtbar gemacht, so dass er keinen Einfluss
während der Spelldauer haben kann. Danach wird erfolgt die
Schleife – diese Wird sooft durchlaufen, wie es eben die
Distanz von Start zu Ziel durch 100 zulässt. Dann wird mit
der PolarProjection der „Weg“ des Spells bestimmt, bei
jedem Schleifendurchlauf rückt die Position l um
100 weiter Richtung Ziel.

Dann wird eine Einheitengruppe erzeugt die alle Einheiten in
einem Radius von 80 um die Position l beinhaltet. Dann
wird noch ein passender Effekt an der Stelle erzeugt, damit man
erkennen kann wieweit der Spell schon fortgeschritten ist. Das
nachfolgende Zerstören der Position verhindert ein
Speicherleck (genaueres im entsprechenden Tutorial).

call ShowUnitHide( caster )

call SetUnitInvulnerable( caster, true )

call SelectUnitRemoveForPlayer( caster,
GetOwningPlayer(caster) )

loop

  exitwhen loopIndex > loopIndexEnd

  set l = PolarProjectionBJ(sourcePos, ( loopIndex
* 100.00 ), AngleBetweenPoints(sourcePos, targetPos))

  set targets = GetUnitsInRangeOfLocAll(80.00, l)

  set dmgEffects[loopIndex] =
AddSpecialEffectLocBJ( l,
„Abilities\\Spells\\Undead\\FrostNova\\FrostNovaTarget.mdl“ )

  call RemoveLocation( l )

Nun folgt ein Konstrukt, welches zum Abarbeiten der
Einheitengruppe, auf die Nova gecastet werden soll, dient.
Dieses Konstrukt ist nur in JASS machbar. Wenn man auf
herkömmliche weise die Einheiten in einer Gruppe
abarbeiten will, dann wird eine eigene Funktion dafür
erzeugt die dann aufgerufen wird. Dies hätte aber den
Nachteil, dass man auf die lokalen Variablen der aufrufenden
Funktion nicht zugreifen kann und diese per Paramenter
übergeben muss oder gar gezwungen ist mit globalen
Variablen zu arbeiten.

Zuerst wird die erste Einheit der Gruppe in einer seperaten
Variable gespeichert, wenn diese Einheit keinen Wert hat (also
keine erste Einheit in der Gruppe mehr existiert), so wird mit
dem abarbeiten der folgenden Schleife aufgehört. In der
Schleife wird einfach eine Dummyeinheit an der Stelle der
Zieleinheit erzeugt die dann Nova auf diese castet (man beachte
wieder das nachfolgende Entfernen der Position). Der Einheit
wird auch ein Expiration-Timer gesetzt, was bedeutet dass sie
nach Ablauf von 3 Sekunden stirbt. (die Zeit reicht aber zum
casten des Spells).

Anschließend wird die Zieleinheit aus der Einheitengruppe
entfernt und in die Variable targetUnit wird die
nächste erste Einheit der Gruppe gespeichert, dann beginnt
die Schleife von neuem, so lange bis eben keine Einheiten mehr
in der Gruppe vorhanden sind.

Nach dieser Schleife wird die Einheitengruppe zerstört
(Speicherleck) und ein Wait (Game-Time) aufgerufen,
damit ein wenig Zeit zwischen den Effekten ist,
anschließend muss noch der Index für Schleife
erhöht werden.

Im orange markierten Teil wird noch auf eine Besonderheit von
JASS hingewiesen. Und zwar geben die meisten Funktionen Werte
zurück. In dem Fall handelt es sich um das erzeugen von
einer Einheit. Im GUI müsste man diese in der
nächsten Zeile mit <last created unit> in
einer Variable speichern, in JASS kann man dies in einer Zeile
erledigen, da die Funktion eine Einheit zurückgibt =>
man kann dies gleich in einer Variablen speichern. Auch
könnte man diesen Funktionsaufruf in einer anderen
Funktion als Parameter nutzen, die eben einen enstprechenden
Wert erwartet (Enheit in diesem Fall).

  set targetUnit = FirstOfGroup(targets)

  loop

    exitwhen targetUnit == null

    set l = GetUnitLoc(targetUnit)

    // Die Verwendeten Einheitentypen
sind in einer globalen Variable definiert. Abhängig vom
Level wird eine andere erstellt (hat auswirkungen auf den
Schaden – siehe Beispielmap)

    set dmgUnit = CreateUnitAtLoc(
GetOwningPlayer(caster), udg_E_HS_WoI_DmgEinheiten[
GetUnitAbilityLevelSwapped(‚A002‘, caster) ], l,
bj_UNIT_FACING )

    call UnitApplyTimedLifeBJ( 3.00,
‚BTLF‘, dmgUnit )

    call IssueTargetOrderBJ( dmgUnit,
„frostnova“, targetUnit )

    call RemoveLocation(l)

    call
GroupRemoveUnitSimple(targetUnit,targets)

    set targetUnit =
FirstOfGroup(targets)

  endloop

  call DestroyGroup(targets)

  call PolledWait ( 0.10 )

  set loopIndex = loopIndex + 1

endloop

Im letzten Teil wird dann der Held an die Zielposition
bewegt, sicht- und verwundbar gemacht sowie automatisch
ausgewählt. Anschließend erfolgt wieder das
Entfernen der Speicherlecks die durch die Positionen
verursacht werden würden. In der letzten Schleife werden
dann noch die Effekte zerstört.

call SetUnitPositionLoc( caster, targetPos )

call ShowUnitShow( caster )

call SetUnitInvulnerable( caster, false )

call SelectUnitAddForPlayer( caster, GetOwningPlayer(caster)
)

set loopIndex = 0

call RemoveLocation( targetPos )

call RemoveLocation( sourcePos )

loop

  exitwhen loopIndex > loopIndexEnd

  call DestroyEffectBJ( dmgEffects[loopIndex] )

  set loopIndex = loopIndex + 1

endloop

endfunction

Download
Beispielmap

zum
Anfang
4. Schlußwort

Auch wenn dieses Tutorial (also der Spellteil) doch ein wenig
spezifisch ist (Allgemein ohne praktisches Beispiel zu
erklären ist eine, meiner Meinung nach, zu abstrakte
Angelegenheit), so denke ich, dass es den meisten von euch eine
Hilfe sein wird, um zu Verstehen wie man JASS für Spells
sinnvoll einsetzen kann. Es gibt noch eine Vielzahl von anderen
Möglichkeiten wie man etwas machen kann, jedoch ist dies
eurer Kreativität und Einfallsreichtum überlassen.
Wenn ihr nun noch Fragen habt oder mehr erfahren wollt, so
besucht das Forum oder aber schaut auch die Spells von Darky
an, denn aus ihnen kann man ebenfalls viel lernen.

zum Anfang

  • 28.07.2008 um 23:52
Returnbug und Gamecache Jass Einführung (Mueslirocker)