4. The YouTD API
The API of YouTD is a set off vJASS structs and functions to help you create beautiful towereffects with very few lines of code.
Before you start reading it remember one thing:
Another thing you should always remember:
Here is an overview of the YouTD structs, as an UML class diagram.
So the main type for creeps and towers is the type Unit. It is a wrapper for a warcraft 3 unit and offers many method to get information about the unit or alter it.
Before we start talking about Projectiles, Buffs and Dummies, you should understand what you can do with the basic data structures for units and players, because you will need them almost everywhere.
Always devide between Unit with an upper case U and unit. The one is a struct member, the other one a Warcraft3 unit.
method getUnit takes nothing returns unit
This struct has two important structs derived from it: Creep and Tower. They offer additional functionalities which are special to creeps and towers.
So where do you get Units from? First, every event handling function looks like this:
function onXXX takes Tower tower returns nothing
As you see, it takes a parameter tower of type Tower. This is your tower. Since it is derived from the type Unit, it allows you to use all methods of Unit. In addition, for targeted events you can call the static method of the struct Event:
struct Event static method getTarget takes nothing returns Unit
It gets you the target of this event. So for example, for the onKill, onAttack, onDamage events, it gets you the killed/attacked/damaged unit.
You can get units by picking units in range or something like that. How do you get the struct wrapping them? Very simple:
The struct index is saved in their custom value, so you can just call GetUnitUserData(unit) to get a Unit. If the custom value is zero, then the unit is already dead or has no struct attached to it. In both cases, you should discard the unit.
So getting a Unit from a unit can be done like that:
local Unit u = GetUnitUserData(YOURUNIT)
Here, YOURUNIT must be a variable of type unit. Now you can also check if the unit is a tower or a creep. For some events, you can just cast the Unit to Creep or Tower because it is clear what it is. Example: The Event.getTarget() function will always return a creep if used on an OnAttack event (since towers don't attack each other).
If it is unclear if the Unit is a Tower or a Creep, you have two possibilities.
Either you can call these methods of type Unit:
struct Unit method isATower takes nothing returns boolean method isACreep takes nothing returns boolean
They return true if the Unit is a Tower/Creep.
You can also call these functions for a unit to determine what it is:
function isCreepAndAlive takes unit u returns boolean function isTower takes unit u returns boolean function isDummy takes unit u returns boolean
local Unit u = GetUnitUserData(YOURUNIT) if isTower(YOURUNIT) then call BJDebugMsg("This unit is a tower") endif if u.isATower() then call BJDebugMsg("This Unit (with capital U!) is a tower") endif
After having learned how to aquire a unit and test what kind it is, let's learn what you can do with it. There are many methods you can use.
struct Unit //Returns the number of kills this unit did method getKills takes nothing returns integer //Returns the owning player of this unit method getOwner takes nothing returns Playor //Calculates a spell crit chance for this tower with bonusChance additional chance to crit // and bonusDamage anditional damage on crit method calcSpellCrit takes real bonusChance, real bonusDamage returns real //Calculate a chance for this unit's with chance percent. //Use this function whenever you want something to happen for this unit on a percent base //example: use .calcChance(0.5) to get a 50% chance //==> will return true on 50% of all invokations //however, note that the given value will be modified by the unit's chance bonuses method calcChance takes real chance returns boolean //Returns the buff of BuffType bt that the unit has //If the unit has no buff of this buff type, 0 will be returned //So you can use uc.getBuffOfType(...) != 0 to check if a unit has a specific buff method getBuffOfType takes BuffType bt returns Buff ...
These two structs represent wrappers for a player and his team. You can get Playors by using the getOwner method of Units.
Here are some methods for the struct Playor (again, a complete list can be found in the API itself):
struct Playor //Creates a floating text for this player on a Unit, uses standard values //whichText: the text to be displayed //whichUnit: the unit where the text is displayed //red,green,blue: colors of your text, between 0 and 255 method displayFloatingText takes string whichText, Unit whichUnit, integer red, integer green, integer blue returns nothing //Gets the team this player belongs to method getTeam takes nothing returns Team //Gets the number of tower this player has method getNumTower takes nothing returns integer //modifies the interest rate of this player by delta. //Delta is percent based, so 0.01 = +1% interest rate method modifyInterestRate takes real delta returns nothing //Gives gold to this player. You can specify a unit for this, if you do so then //there will be an effect at the unit's position (if showEffect is true) and a //golden floating text that shows the value of gold gained (if showText is true) //If you don't want personalized effect stuff, just set u to null //You can use a negative value to substract gold (then the floating text will be red instead of gold) method giveGold takes integer value, unit u, boolean showEffect, boolean showText returns nothing ...
and here are some methods of the struct Team:
struct Team //Gets the size of this team, i.e. how many NON-LEFT(!) players it has method getSize takes nothing returns integer //Gets the level in which this team currently is method getLevel takes nothing returns integer //Retrievs the percentage of lifes this team has //Note that this is really percentage, so 40 = 40% //(in opposite to many other functions that return a real value where 0.4 is 40%) method getLivesPercent takes nothing returns integer //Gives gold to all players of this team. You can specify a unit for this, if you do so then //there will be an effect at the unit's position (if showEffect is true) and a //golden floating text that shows the value of gold gained (if showText is true) //If you don't want personalized effect stuff, just set u to null //You can use a negative value to substract gold (then the floating text will be red instead of gold) method giveGoldToEachPlayer takes integer value, unit u, boolean showEffect, boolean showText returns nothing ...
Now you have the basic knowledge of what can be done where, how to acquire Units, their owners and their teams.
Let's start with the easiest things you can do to alter a Tower / Creep. Modifying its values like speed, damage, armor and so on.
The most simple method to alter a Unit's stats is this one:
struct Unit //Modifies a property modId by value method modifyProperty takes integer modId, real value returns nothing
modId is an integer which can be one of these constants (excerpt from library Modifier).
All modifications are commented, so you know what you can do.
As you see, you can alter ALMOST everything you can imagine:
globals //************************************************************** //** This is a list of possible modIds which can be passed to ** //** the Modifier.addModification function ** //** ALWAYS USE THESE CONSTANTS WHEN INVOKING THIS FUNCTION ** //************************************************************** //**************************************** //** TOWER MODIFICATIONS ** //** The following modifications only ** //** make sense when applied to a tower ** //**************************************** //== DAMAGE_TO TABLE MODIFICATIONS == constant integer MOD_DMG_TO_MASS = 0 //Mass units (very unhealthy) constant integer MOD_DMG_TO_NORMAL = 1 //Normal units (the ones that spawn in normal levels) constant integer MOD_DMG_TO_CHAMPION = 2 //Champions are larger units that spawn with normal units constant integer MOD_DMG_TO_BOSS = 3 //Bosses are even mightier than champions (the will probably spawn alone) constant integer MOD_DMG_TO_AIR = 4 //Air levels constant integer MOD_DMG_TO_UNDEAD = 10 //Undead units constant integer MOD_DMG_TO_MAGIC = 11 //Magical units constant integer MOD_DMG_TO_NATURE = 12 //Nature units constant integer MOD_DMG_TO_ORC = 13 //Orcish units constant integer MOD_DMG_TO_HUMANOID = 14 //Humanoid units //== OFFENSIVE MODIFICATIONS == //Modifies the chance that attacks of this unit will hit critical //percent based so 0.01 = 1% crit chance //Note that every unit starts with a basic crit chance of 1% constant integer MOD_ATK_CRIT_CHANCE = 20 //Modifies the damage that critical attacks of this unit do //percent based so 0.01 = 1% crit damage //Note that every unit starts with a basic crit damage of 150% (+50%) constant integer MOD_ATK_CRIT_DAMAGE = 21 //Modifies the multicrit value of this unit. Multicrit is the chance that a //critical strike on attack crits again. The basic multicritValue of each unit //is 1, so a unit can crit only once. //If you increment its value, then it can crit two times, if you decrease it to //zero, than the unit cannot crit at all constant integer MOD_MULTICRIT_COUNT = 22 //Modifies the chance that spells of this unit will hit critical //percent based so 0.01 = 1% crit chance //Note that every unit starts with a basic crit chance of 1% constant integer MOD_SPELL_CRIT_CHANCE = 23 //Modifies the damage that critical spells of this unit do //percent based so 0.01 = 1% crit damage //Note that every unit starts with a basic crit damage of 150% (+50%) constant integer MOD_SPELL_CRIT_DAMAGE = 24 //Modifies the damage of all spells cast by this unit //percent based so 0.01 = 1% spell damage constant integer MOD_SPELL_DAMAGE_DEALT = 25 //Increases the chance for creeps killed by this tower to drop an item //percent based so 0.01 = +1% better chance to drop an item constant integer MOD_ITEM_CHANCE_ON_KILL = 26 //Increases the quality of items dropped by creeps which are killed by this unit //percent based so 0.01 = +1% better quality constant integer MOD_ITEM_QUALITY_ON_KILL = 27 //Modifies the attackspeed of the unit on a percentage base, //0.01 = 1% attackspeed, 0.2 = 20% attackspeed and so on constant integer MOD_ATTACKSPEED = 30 //Modifies the base damage of this unit (the white numbers) //1.0 = +1 damage constant integer MOD_DAMAGE_BASE = 31 //Modifies the base damage of this unit (the white numbers) //percent based so 0.01 = 1% damage constant integer MOD_DAMAGE_BASE_PERC = 32 //Modifies the add damage of this unit (the green numbers) //1.0 = +1 damage constant integer MOD_DAMAGE_ADD = 33 //Modifies the add damage of this unit (the green numbers) //percent based so 0.01 = 1% damage constant integer MOD_DAMAGE_ADD_PERC = 34 //Modifies the dps of this unit (the green numbers) //1.0 = +1 dps //In contrast to MOD_DAMAGE_ADD this value is altered by the tower's base attack speed //so a slower tower will get more bonus than a faster tower constant integer MOD_DPS_ADD = 35 //**************************************** //** CREEP MODIFICATIONS ** //** The following modifications only ** //** make sense when applied to a creep ** //**************************************** //== DAMAGE_FROM TABLE MODIFICATIONS == //Modify the damage a creep receives from the specified element constant integer MOD_DMG_FROM_ASTRAL = 40 constant integer MOD_DMG_FROM_DARKNESS = 41 constant integer MOD_DMG_FROM_NATURE = 42 constant integer MOD_DMG_FROM_FIRE = 43 constant integer MOD_DMG_FROM_ICE = 44 constant integer MOD_DMG_FROM_STORM = 45 constant integer MOD_DMG_FROM_IRON = 46 //== DEFENSIVE MODIFICATIONS == //Modifies the experience this unit grants upon death //percent based so 0.01 = +1% exp constant integer MOD_EXP_GRANTED = 60 //Modifies the bounty this unit grants upon death //percent based so 0.01 = +1% bounty constant integer MOD_BOUNTY_GRANTED = 61 //Modifies the spell damage this unit receives //percent based so 0.01 = +1% spell damage received constant integer MOD_SPELL_DAMAGE_RECEIVED = 62 //Modifies the attack damge this unit receives //percent based so 0.01 = +1% attack damage received constant integer MOD_ATK_DAMAGE_RECEIVED = 63 //Increases the chance for this creeps to drop an item on death //percent based so 0.01 = +1% better chance to drop an item constant integer MOD_ITEM_CHANCE_ON_DEATH = 64 //Increases the quality of items dropped by this creep on death //percent based so 0.01 = +1% better quality constant integer MOD_ITEM_QUALITY_ON_DEATH = 65 //Modifies the hitpoints of this unit //1.0 = +1 hitpoint constant integer MOD_HP = 70 //Modifies the hitpoints of this unit //percent based so 0.01 = +1% hitpoints constant integer MOD_HP_PERC = 71 //Modifies the hitpoint regeneration of this unit //1.0 = +1 hitpoint / sec regeneration constant integer MOD_HP_REGEN = 72 //Modifies the hitpoint regeneration of this unit //percent based so 0.01 = +1% hitpoint regeneration constant integer MOD_HP_REGEN_PERC = 73 //Modifies the armor //1.0 = +1 armor constant integer MOD_ARMOR = 74 //Modifies the armor //percent based so 0.01 = +1% armor constant integer MOD_ARMOR_PERC = 75 //**************************************** //** GENERAL MODIFICATIONS ** //** The following modifications ** //** make sense on creeps and towers ** //**************************************** //== MISC MODIFICATIONS == //Modifies the triggerchances of this unit //i.e.: Whenever the calcChance function is called for this unit it has bonus percents to return true //percent based so 0.01 = +1% trigger chances constant integer MOD_TRIGGER_CHANCES = 80 //Modifies buff durations for buffs cast by this unit //percent based so 0.01 = +1% duration constant integer MOD_BUFF_DURATION = 81 //Modifies buff durations for debuffs cast ONTO this unit //percent based so -0.01 = -1% duration constant integer MOD_DEBUFF_DURATION = 82 //Modifies the bounty this unit receives upon killing //percent based so 0.01 = +1% bounty constant integer MOD_BOUNTY_RECEIVED = 83 //Modifies the experience this unit receives upon killing //percent based so 0.01 = +1% experience constant integer MOD_EXP_RECEIVED = 84 //Modifies the mana of this unit //1.0 = +1 mana constant integer MOD_MANA = 90 //Modifies the mana of this unit //percent based so 0.01 = +1% mana constant integer MOD_MANA_PERC = 91 //Modifies the mana regeneration of this unit //1.0 = +1 mana / second regeneration constant integer MOD_MANA_REGEN = 92 //Modifies the mana regeneration of this unit //percent based so 0.01 = +1% mana regeneration constant integer MOD_MANA_REGEN_PERC = 93 //Modifies the movespeed of this unit //percent based so 0.01 = +1% movespeed constant integer MOD_MOVESPEED = 94 //Modifies the movespeed of this unit //1 = 1 movespeed (movespeed of units is between 100 and 500) constant integer MOD_MOVESPEED_ABSOLUTE = 95 endglobals
Using the modifyProperty you can directly modify a single property. This is not the only way to alter a unit's properties. For example, buffs can modify unit properties, too. If only the modifyProperty method existed, you had to modify all properties whenever that buff is created and modify them with their negative value whenever the buff is removed. YouTD offers a much more simple way to alter stuff like that, where the engine does the add and remove stuff for you: Modifiers.
A modifier is basically a set of modifications, which are also level dependant, that means they can get upgraded automatically if the tower/buff gains levels. This way you could make a modifier that gets stronger the higher the towers level is.
struct Modifier //Creates a new damage modifier that doesn't modify anything yet static method create takes nothing returns Modifier
As you see, the method takes nothing and creates a modifier that doesn't modify anything yet. Now we can add modifications:
struct Modifier method addModification takes integer modId, real baseValue, real levelAdd returns nothing
This method adds a modification to this modifier. modId is again one of the above mentioned constants which specifies which property to modify.
BaseValue is the value that is modified for a level 0 tower/buff. levelAdd is the value that is added for every level. You can add as many modifications as you wish to a modifier.
local Modifier myMod = Modifier.create() call myMod.addModification(MOD_ATK_CRIT_CHANCE,0.1,0.01)
This would create a modifier that modifies a tower's critical strike chance by 10% (0.1) on level 0 and +1% per level. So for a level 15 tower, this would give 25% more critical strike chance.
But this modifier is on no tower yet. It can be added to buffs (but that will be covered in the chapter on buffs). It can also be added to a Unit directly using this method:
struct Unit method addModifier takes Modifier m returns ListElement
This adds a modifier to the unit's modifier list and returns a reference to it. Note that the modifier's strength is automatically adjusted with the unit's level. If the unit gains a level also this modification's strength's will be adjusted.
If you want to, you can save this reference and hand it to the method:
struct Unit method removeModifier takes ListElement l returns nothing
which will remove the modifier from this unit.
So if we have a Tower myTower, we could add that modifier to it:
local ListElement modReference = myTower.addModifier(myModifier) call TriggerSleepAction(60.0) call myTower.removeModifier(modReference)
This would add the critical strike modifier to the tower and remove it again after one minute.
However the methods add/removeModifier should only be used in special cases. Most of the time you just add a modifier to a buff. Then the modifier will be on the unit as long as the unit has the buff. You don't have to care about adding and removing it than. This is covered in the chapter on buffs.
As I already told you, towers shouldn't cast their spells themselves. Always, a dummy should do this.
Okay, that was very abstract, now let me get a bit more into detail:
This should be done during map initialization, so use the init function in your Header library, create the cast in it and save it in a global variable:
library myTower initializer init uses libHeader globals Cast myCast endglobals //The init function private function init takes nothing returns nothing set myCast = Cast.create(...) endfunction endlibrary
This was how to do it. But what values have to be handed to the .create method of Cast?:
static method create takes integer abil, string order, real lifetime returns Cast
So, as you see, you need the code of an ability "abil", that is the ability that should be cast. Then you need an order to cast it, this is the orderId string for the ability, for example "chainlightning" for a chainlightning. The last parameter "lifetime" is the time the dummy will live. You should set this time to how long the spell takes. So if it is a damage over time that lasts 15 seconds, "lifetime" must be at least 15 seconds!
Okay, now we have initialized our cast. But now we want to cast it. For example we want to cast it whenever the tower attacks a creep. So we use onAttack:
function onAttack takes Tower tower returns nothing
Here we have to add the dummy cast, there are many methods for doing that:
//== immediate casts == method immediateCastFromCaster takes Unit castingUnit, real dmgRatio, real critRatio returns nothing method immediateCastFromUnit takes Unit castingUnit, Unit target, real dmgRatio, real critRatio returns nothing method immediateCastFromPoint takes Unit castingUnit, real x, real y, real dmgRatio, real critRatio returns nothing //== unit targeted casts == method targetCastFromCaster takes Unit castingUnit, Unit target, real dmgRatio, real critRatio returns nothing method targetCastFromTarget takes Unit castingUnit, Unit target, real dmgRatio, real critRatio returns nothing method targetCastFromPoint takes Unit castingUnit, Unit target, real x, real y, real dmgRatio, real critRatio returns nothing //== point targeted casts == method pointCastFromTargetOnTarget takes Unit castingUnit, Unit target, real dmgRatio, real critRatio returns nothing method pointCastFromCasterOnTarget takes Unit castingUnit, Unit target, real dmgRatio, real critRatio returns nothing method pointCastFromPointOnTarget takes Unit castingUnit, real fromX, real fromY, Unit target, real dmgRatio, real critRatio returns nothing method pointCastFromUnitOnCaster takes Unit castingUnit, Unit from, real dmgRatio, real critRatio returns nothing method pointCastFromCasterOnCaster takes Unit castingUnit, real dmgRatio, real critRatio returns nothing method pointCastFromPointOnCaster takes Unit castingUnit, real fromX, real fromY, real dmgRatio, real critRatio returns nothing method pointCastFromUnitOnPoint takes Unit castingUnit,Unit from, real toX, real toY, real dmgRatio, real critRatio returns nothing method pointCastFromCasterOnPoint takes Unit castingUnit, real toX, real toY, real dmgRatio, real critRatio returns nothing method pointCastFromPointOnPoint takes Unit castingUnit, real fromX, real fromY, real toX, real toY, real dmgRatio, real critRatio returns nothing //== no casts == method noCastFromCaster takes Unit castingUnit, real dmgRatio, real critRatio returns SpellDummy method noCastFromUnit takes Unit castingUnit, Unit from, real dmgRatio, real critRatio returns SpellDummy method noCastFromPoint takes Unit castingUnit, real fromX, real fromY, real dmgRatio, real critRatio returns SpellDummy //== AoE casts == method castOnAOEfromCenter takes Unit castingUnit, real x, real y, real radius, real dmgRatio, real critRatio, TargetType targType returns nothing endmethod method castOnAOEfromCaster takes Unit castingUnit, real x, real y, real radius, real dmgRatio, real critRatio, TargetType targType returns nothing endmethod method castOnAOEfromTarget takes Unit castingUnit, real x, real y, real radius, real dmgRatio, real critRatio, TargetType targType returns nothing endmethod
Getting a system into these function is very simple. The name is assembled like this:
CASTTYPE is the type of the order the dummy gets -> immediate: An immediate cast like warstomp -> target: A unit targeted spell like thunderbolt -> point: A point / location targeted cast like rain of fire FROMTYPE represents from where the spell is cast: -> Caster: The spell is cast from the caster's position -> Unit: The spell is cast from another unit (which has to be specified) -> Target: The spell is casted from the target's position -> Point: The spell is casted from a point which has to be specified (x,y) TARGETLOC is only used by point cast spells and specifies which point to target: -> Caster: The spell is cast onto the caster's position -> Target: The spell is casted onto a target's position (you must specify the targ) -> Point: The spell is casted onto a point which has to be specified (x,y)
So basically you have to answer two questions to choose the correct method:
The castOnAOE methods cast a unit targeted spell onto all units in a range around a target point. The target area is specified by the center (x,y) and the radius. The spell is cast onto every unit in that area which satisfies the target type.
dmgRatio is just a real percentage value of how much damage this spell should deal. So, if it should deal as much as stated in the object editor, set it to 1.0. If you set it to 3.0 for example, the spell will do 300% of the damage in the object editor. This is an easy way to enhance spells for example based on the level of the tower, for each level, the tower could get +10% spell damage without having to create an ability with many levels.
critRatio is a function from the crit System. To get a critRatio, use the tower method:
method calcSpellCrit takes real bonusChance, real bonusDamage returns real
This function calculates all crit modifiers the tower has from items and buffs and returns a value that can be inserted as critRatio.
The two parameters bonusChance and bonusDamage state values to alter the crit chance of the tower and the crit damage. Basic spell crit damage is 125-175% (depending on the tower's level). So if you insert 0.5 = 50% as bonusDamage the tower will do 120% + 50% = 170% damage upon critting (for a level 0 tower). bonusChance is a value to increase the crit chance of the tower. The basic crit chance is 1.25%. But this chance is raised by items, buffs and can also be raised for certain spells by inserting a number greater than 0 into bonusChance. If you insert 1, the tower will always crit with this spell, if you insert 0.5 there will be a 50% increased chance for critting.
Okay, so lets say, our spell is unit targeted, should do as much spell damage as stated in the object editor (dmgRatio = 1) and should have a 50% chance to hit critical with no extra bonus damage, so the call would look like this:
function onAttack takes Tower tower returns nothing call myCast.targetCastFromCaster(tower,Event.getTarget(),1.0,tower.calcSpellCrit(0.5,0.0)) endfunction
That's it! Remember from the first chapter that we can use Event.getTarget() to get the creep that is attacked.
I apologize for the complicated system with dmgRatio and critRatio. But that was the only way to implement an efficient spell damage crit and amplifying system. I know that these values, paired with the calcSpellCrit function will take you some extra time to learn. But once you have learned it, you will have easy access to let your spells do as much damage as YOU want.
So as a small recap, dummy spells a cast in two stages: Create a cast object upon map init. Then in reaction to an event, cast it!
I didn't want to make you mad in the last chapter, so I didn't tell you everything about the great stuff the API allows you to do with dummy units. So if you are quite familiar with the last chapter and know the three steps it takes to cast a dummy spell, then read on and I show you how deep the rabbit hole goes. If you have already enough for your spells, skip this chapter.
You can give your cast a custom damage table. A damage table alters the dummy's damage against specific categories or sizes. So, for example you can make a chainlightning that deals +100% damage to undead units.
The method to do so is:
method setCustomDamageTable takes DamageTable table returns nothing
but it needs a damage table. We can create an "empty damage table" i.e. a table that doesn't modify anything yet by invoking the constructor:
local DamageTable myTable = DamageTable.create()
Now we want to set the values for different sizes/categories for this table like the +100% to undead. we use these table's methods:
struct DamagaTable //Sets the bonus against a category whichCategory for this damage table to value method setBonusToCategory takes integer whichCategory, real value returns nothing //Sets the bonus against a size whichSize for this damage table to value method setBonusToSize takes integer whichSize, real value returns nothing
The first function sets the damage against a category (human, undead,...), the second one for a size (mass,boss,normal...).
They need a modId and a real value. The real value is just the value we want to (de-)amplify the cast, so it would be 1.0 for our +100%.
The modId is just one of the creep categories/sizes (You can find them in the Creep library):
//Creep size constants constant integer SIZE_MASS = 0 //Mass creeps (very unhealthy) constant integer SIZE_NORMAL = 1 //Normal creeps (the ones that spawn in normal levels) constant integer SIZE_AIR = 2 //Flying units constant integer SIZE_CHAMPION = 3 //Champions are larger units that spawn with normal creeps constant integer SIZE_BOSS = 4 //Bosses are even mightier than champions (the will spawn alone) constant integer SIZE_CHALLENGE = 7 //Challenge //Creep categorization constants constant integer CATEGORY_UNDEAD = 0 //Undead creeps constant integer CATEGORY_MAGIC = 1 //Magical creeps constant integer CATEGORY_NATURE = 2 //Nature creeps constant integer CATEGORY_ORC = 3 //Orc creeps constant integer CATEGORY_HUMANOID = 4 //Humanoid creeps
so for giving +100% to undead we would call:
Always use the constants when invoking such a method, not the raw integer values! Raw integer values will make your code unreadable (and readable code is a big issue if you want your tower in the map!). With the category constant, everybody immediatly sees that you want to alter damage to undead by +100%.
After we have altered the table we can just hand it to the setCustomDamageTable method and every dummy created from this Cast will have this table.
Putting it all together: This would be your init library with the +100% to undead modification:
library myTower initializer init uses libHeader globals Cast myCast endglobals //The init function private function init takes nothing returns nothing local DamageTable myTable set myCast = Cast.create(...) //Creating the cast set myTable = DamageTable.create() //Creating the damage table call myTable.setBonusToCategory(CATEGORY_UNDEAD,1.0) //Altering the table call myCast.setCustomDamageTable(myTable) //Setting the table for the cast endfunction endlibrary
That's it! Quite straight, isn't it? Note one last thing: In addition to this table, ALWAYS also the table of the tower applies to a cast. Their values are added. So if your tower alread deals +100% damage to undead, your spell would now deal +200% damage to undead. If you don't want your tower's modifications in your spell, just "include" the exactly opposite value's in your towers table.
There are two events you can react to for certain dummies. They are onKill (when the dummy kills a creep) and onDamage (when the dummy damages a creep). The methods for setting them are the following:
function interface EventHandler takes DummyUnit dummy returns nothing struct Cast method setDamageEvent takes EventHandler ev returns nothing method setKillEvent takes EventHandler ev returns nothing
So you need to create a function that satisfies the interface EventHandler. Then you can set this function as damage or kill event for your Cast.
Okay, for those who are not familiar with vJASS function interfaces a short explanation of what you need to know:
<NAME OF THE FUNCTION INTERFACE IT SHOULD SATISIFY>.<NAME OF OUR FUNCTION>
So if our written function was named myFunction, you would write
to entry the function as kill event.
First we create an event reaction function with the signature of EventHandler (in our myTower library):
function myKillEvent takes DummyUnit d returns nothing call BJDebugMessage("The dummy has killed a creep!") endfunction
As you see, the function MUST take a DummyUnit to satisfy the function interface (Even if we don't need it here, we just simply display a debug message as event reaction).
The next and already final step is to add this function as kill event for our cast by invoking the setKillEventMethod. This is how our myTower library would look like now:
library myTower initializer init uses libHeader globals Cast myCast endglobals //Our kill event reaction function function myKillEvent takes DummyUnit d returns nothing call BJDebugMessage("The dummy has killed a creep!") endfunction //The init function private function init takes nothing returns nothing local DamageTable myTable set myCast = Cast.create(...) //Creating the cast call myCast.setKillEvent(EventHandler.myKillEvent) //Setting the kill event reaction function endfunction endlibrary
That's it! And believe me this is a mighty tool! Examples:
To get the damaged / killed creeps, use Event.getTarget() (like for all event reactions). For the onDamage event, you can get and set the event damage with Event.damage.
The buff system allows you to create triggered buffs that modify the creep/tower values and can react to events like the buffed unit dies, the buff expires and so on. This system is very mighty, it allows you hundred times more stuff than you could do with normal untriggered buffs.
Just like each dummy belongs to a type that specifies its values, which is represented by the struct Cast, each buff belongs to a type that represents its values, represented by the struct BuffType. To create a new kind of buff, you create a BuffType in your init function, save it in a global variable and then you can apply it to units.
The create method of BuffType looks like this:
static method create takes real timeBase, real timeLevelAdd, boolean friendly returns BuffType
The values have the following meaning:
The "power level" of a buff is a measure for how strong it is. We will talk about that later.
If you set timeBase to -1.0 then the buff will last infinitly on its target. This can be used to create permanent buffs or buffs that are removed when some conditions are true, like when the unit attacks or something like that.
After invoking this method, we have a BuffType with a specific duration. However the buff doesn't do anything yet.
The easiest way to let our buff do something is setting its modifier. A modifier set this way modifies the unit values of the buff as long as the unit has the buff on it. As soon as the buff is removed, the modifications will vanish from the unit.
method setModifier takes Modifier mod returns nothing method setBuffModifier takes Modifier mod returns nothing
this will set the buff type's modifier. Whenever a buff of this type is applied to a unit, all modifications stated in the modifier will be applied. The difference between this two functions is this:
If setModifier is used the modifier gets stronger according to the TOWER LEVEL.
If setBuffModifier is used the modifier gets stronger according to the BUFF POWER LEVEL.
Example how to create a buff type and add some modifications to it: (In the header trigger)
library myTower initializer init uses libHeader globals BuffType myType endglobals //The init function private function init takes nothing returns nothing local Modifier mod = Modifier.create() //Create the modifier set myType = BuffType.create(10.0,1.0,true) //Create the bufftype call myType.setBuffModifier(mod) //Set the modifier for the bufftype call mod.addModification(MOD_DMG_TO_ORC,0.2,0.05) call mod.addModification(MOD_MANA,100,10) endfunction endlibrary
This creates a buffType that lasts 10 seconds + 1 sec/power Level on a unit, it is a positive buff (true). It adds 20% damage against orc creeps on power level 0 and additional 5% for each power level. It also adds 100 to the tower's mana and +10 mana for each power level.
Okay, now you have created a buff type. So lets use it!
You can apply a buff onto a unit by just invoking this method of BuffType:
method apply takes Unit caster, Unit target, integer level returns Buff
It takes two Units, the unit that casted the buff (caster) and the unit it was cast on (target). As a third parameter it takes the power level the buff should have (level).
Just call this function whenever you want to place a buff. The engine does the rest (i.e. checking if the unit already has the buff, doing the modifications, removing it when its time is up and so on).
As already stated, the buff's power level is used for the following things:
globals constant real ONATTACK_chance = 0.1 //10% chance to cast the buff constant real ONATTACK_chanceLevelAdd = 0.0 //No additional chance per tower level endglobals function onAttack takes Tower tower returns nothing call myType.apply(tower,tower,tower.getLevel()) //Apply the buff onto the tower, casted by the tower itself endfunction
One line of code. Easy, isn't it?
When you create a buff type, I showed you how to add modifications to it. However, modifications alone are not too flexible. Event reactions are much more powerful.
So as you can see, you can react to almost everything. This gives you limitless possibilities.
Note that not all events make sense for all kinds of units. For example, a tower will never be damaged or attacked!
Now I will tell you how this works in code. To add an event reaction handler, first write the reaction handler function. It should (must) have this signature:
function NAME takes Buff PARAMNAME returns nothing
NAME and PARAMNAME can be set arbitrarily (as names don't matter for function interfaces).
After you have written such a function you can call the appropriate setEvent or addEvent method of the BuffType to set this function as an event handler. (Note that most of these functions are not defined in the struct BuffType but in the struct EventTypeList which is a supertype of the struct BuffType.
Whenever the method's starts with "add" instead of "set", it shows that you can add more than one of those event handlers to it. However, more than one event handler for one event is not very useful. Here they are:
struct EventTypeList //Sets the event handler that is called when the event placer is somehow destroyed (expired or removed or its target dies...) method setEventOnCleanup takes EventHandler be returns nothing //Sets the modifier of this event placer method setModifier takes Modifier mod returns nothing //Sets the event handler that is called when this EventTypeList is applied onto a unit method addEventOnCreate takes EventHandler be returns nothing //Adds an event handler that is called every seconds (period can not be smaller than 0.2) method addPeriodicEvent takes EventHandler be, real period returns nothing //Adds an event handler that is called when the buffed unit casts a spell method addEventOnSpellCast takes EventHandler be returns nothing //Adds an event handler that is called when the buffed unit is targeted by a spell method addEventOnSpellTarget takes EventHandler be returns nothing //Adds an event handler that is called when the buffed unit dies //or if it is destroyed / upgraded method addEventOnDeath takes EventHandler be returns nothing //Adds an event handler that is called when the buffed unit kills a unit method addEventOnKill takes EventHandler be returns nothing //Adds an event handler that is called when the buffed unit gains a level method addEventOnLevelUp takes EventHandler be returns nothing //Adds an event handler that is called with chance% when the buff's target attacks //chanceLevelAdd increases the chance for each level the unit has, //so if it is set to 0.04 for example, a level 10 unit will have a 40% (0.04*10=0.4) higher chance than a level 0 unit method addEventOnAttack takes EventHandler be, real chance, real chanceLevelAdd returns nothing //Adds an event handler that is called with chance% when the buffed unit is attacked //chanceLevelAdd increases the chance for each level the unit has, //so if it is set to 0.04 for example, a level 10 unit will have a 40% (0.04*10=0.4) higher chance than a level 0 unit method addEventOnAttacked takes EventHandler be, real chance, real chanceLevelAdd returns nothing //Adds an event handler that is called with chance% when the buffed unit deals damage //chanceLevelAdd increases the chance for each level the unit has, //so if it is set to 0.04 for example, a level 10 unit will have a 40% (0.04*10=0.4) higher chance than a level 0 unit method addEventOnDamage takes EventHandler be, real chance, real chanceLevelAdd returns nothing //Adds an event handler that is called with chance% when the buffed unit is damaged //chanceLevelAdd increases the chance for each level the unit has, //so if it is set to 0.04 for example, a level 10 unit will have a 40% (0.04*10=0.4) higher chance than a level 0 unit method addEventOnDamaged takes EventHandler be, real chance, real chanceLevelAdd returns nothing //Adds an event handler that is called when a unit which passes the target type test against targType // comes in range range of the buffed unit. method addEventOnUnitComesInRange takes EventHandler be, real range, TargetType targType returns nothing endmethod //Adds an ability placer. The ability abilId will be added automatically //to the unit as long as is it has the event placer //If safeStacking is set to true, then the unit will keep a reference count //for this ability to check how many event placers place it on this unit //So if two placers place it and one is removed the ability is still on the unit //If safeStacking is false, then the ability will be removed upon removing the //first placer, even if there is another one that gives it. //Enable safeStacking if you know that the ability can be granted more than once //Disable it if you are sure that this event placer (and any other that grants that ability) //is never twice on a unit method addAbility takes integer abilId, boolean safeStacking returns nothing endmethod //Adds an aura of type auraType, for more infos check the struct AuraType method addAura takes AuraType auraType returns nothing struct BuffType extends EventTypeList //Sets the event function that is called when this buff is cast on a unit that already has it (with the same level) method setEventOnRefresh takes EventHandler be returns nothing //Sets the event function that is called when this buff is upgraded (i.e. cast on a unit that has it with a lower level) method setEventOnUpgrade takes EventHandler be returns nothing //Sets the event function that is called when the buff runs out of lifetime (just before it is removed) method setEventOnExpire takes EventHandler be returns nothing //Sets the event function that is called when the buff is forcibly removed from the unit (by a purge like ability for example) method setEventOnPurge takes EventHandler be returns nothing //Sets the buff modifier of this buff //The buff modifier is a modifier (just like when using a normal addModifier on EventPlacer (the parent type of this type)) //The difference is that this modifier is only modified by the BUFF LEVEL, not the unit's level method setBuffModifier takes Modifier m returns nothing
In the event reaction function itself, you can use many methods of the buff. For example getBuffedUnit() will get the unit container, on which the buff is cast.
An example will make this more clear and will show how mighty this concept is. Let's create a buff, that whenever the buffed tower attacks, there is a 10% chance that the tower will deal 100 bonus spell damage to the attacked creep.
function myEventHandler takes Buff b returns nothing call b.getBuffedUnit().doSpellDamage(Event.getTarget(),100,b.getCaster().calcSpellCrit(0.0,0.0)) endfunction
Again we use Event.getTarget() to refer to the attacked unit.
This is the function. Now just tell the bufftype to call this function in 10% of all attacks.
We do this in the init function after creating our buff type:
private function init takes nothing returns nothing set myType = BuffType.create(10.0,1.0,true) //Create the bufftype call myType.addEventOnAttack(EventHandler.myEventHandler,0.1,0.0) //Set our event handler with a 10% chance endfunction
The whole code would be:
library myTower initializer init uses libHeader globals BuffType myType endglobals function myEventHandler takes Buff b returns nothing call b.getBuffedUnit().doSpellDamage(Event.getTarget(),100,t.calcSpellCrit()) endfunction private function init takes nothing returns nothing set myType = BuffType.create(10.0,1.0,true) //Create the bufftype call myType.addEventOnAttack(EventHandler.myEventHandler,0.1,0.0) //Set our event handler with a 10% chance endfunction endlibrary
As you saw in this example we used .getBuffedUnit() on the buff to get the unit it buffs, there are some more useful methods for buffs, some are lended from its super type EventPlacer (Again, this list is incomplete, for a full list, check the API in the content creation map):
struct EventPlacer //Gets the Unit that is buffed by this eventPlacer method getBuffedUnit takes nothing returns Unit struct Buff extends EventPlacer //Gets the power of this buff (for normal buffs, this is equal to the level) method getPower takes nothing returns integer //Gets the Unit that casted this buff method getCaster takes nothing returns Unit //Returns how many seconds this buff still has until it expires method getRemainingDuration takes nothing returns real //Removes this buff from the unit method removeBuff takes nothing returns nothing
A very useful function is the removeBuff function. It removes the buff immediately. Another one is getCaster which gets you the Unit that casted the buff.
Maybe you want your buff to have a buff icon with description as normal WC3 buffs have or maybe an effect on its target. That is no problem. Just create a buff in the object editor (here you can also choose an effect). Then, you can set it as a BuffType's WC3 buff by using this method:
struct BuffType //Sets the buff icon for this buff, i.e. the buff that will //be displayed on the unit. Enter a valid buff id for your buff here like 'B000' method setBuffIcon takes integer buffId returns nothing
Example, lets assume you created an Object Editor buff with the id 'B000' and myBuffType is the buff type you want to add that buff to:
That's all! Now the buff will have a buff icon/effect as long as it is on the unit.
When using buff icons like stated in the last chapter, you are able to attach a special effect to that buff icon, hence allowing you to apply models to buffed units. The problem is that these special effects need attachment points. Many tower models don't have any attachment points since they are assembled from doodads. However, we might want to create a buff that targets tower and has a special effect. In this case (whenever you target towers), don't give your buff in the object editor a special effect. Instead add a tower effect to the buff type using one of these functions:
//Sets the effect for this buff //IMPORTANT: You should use this method only for buffs that target towers, //because the effect will NOT move with the unit. //For creeps, just set the BuffIcon and give the Buff in the object editor a model //For towers, this is not always possible since towers often lack attachment points // //Note that there are also two simpler functions below this one that use standard //parameters for some of the values // must be a valid path to the effect model //example: "Abilities\\Spells\\Human\\InnerFire\\InnerFireTarget.mdl" //x,y,z are offsets for the effect //scale, rot are the rotation and the scale of the effect //r,g,b,alpha are colors for the effect ranging from 0 to 255 (0xFF) each //pitch is the pitch angle of the effect method setSpecialEffectAdvanced takes string effectPath, real x, real y, real z, real scale, real rot, integer r, integer g, integer b, integer alpha, real pitch returns nothing //Like .setSpecialEffectAdvanced but uses standard parameters method setSpecialEffect takes string effectPath, real z, real scale returns nothing //Like .setSpecialEffectAdvanced but uses standard parameters method setSpecialEffectSimple takes string effectPath returns nothing //Like .setSpecialEffectAdvanced but uses standard parameters method setSpecialEffectColored takes string effectPath, real z, real scale, integer r, integer g, integer b, integer alpha returns nothing
As you can see the simplest way to add such a tower effect is calling "setSpecialEffectSimple" for your BuffType and just stating the path to the model.
Example, lets assume myBuffType is the buff type you want to add that tower effect to with some z offset and a scale of 2.0 (200%):
There are some common buff types already predefined in the library "Predefined Buffs". You can use them for those common tasks. Check the library, everything is explained there.
Here I will tell you some more things you can do with buffs. If you feel safe with buff handling, read on here.
Until now, each buff stacks with each other buff except for two buffs of the really same buff type. However, you might want to design a whole family of towers that apply the same buffs, but these buffs shouldn't stack, only the strongest one should be on a unit at one time.
A stacking group in YouTD is just a string. All buffs that have the same string will not stack with each other. If you want your BuffType to be in a stacking group, you first have to find its name.
If it is your own stacking group, that is only for your family of towers, you can give it an arbitrary name, but try to give it one that will not collide with other stacking groups. Including your nick for example in the group's name will make collisions very unlikely.
If you know the name of the group, just call the buff types method setStackingGroup which takes the string as its only parameter.
will set the stacking group of the buff type myType to "gexStackingGroup". If other buff types share this group, they won't stack with this buff type.
There are some problems that arise from two different buffs not stacking with each other. The first problem: If one buff is on the unit and another buff of the same level wants to be applied to the unit, should the old buff replaced or be kept?
So for buffs that belong to a stacking group, other rules apply:
The other problem with stacking groups is that for stacking groups level and power level are two different things. The power level of a buff tells, how its modifications and its duration are amplified. The level of a buff compares it with other buffs and decides if a new incoming buff is refreshed, upgraded or declined.
For normal buffs these two values were the same, since a single buff is always stronger if is modifications are more intense and vice versa. For stacking groups this is not true anymore. Imagine one buff, lets call it A, that has +100% damage base and +1% damage per power level. Another buff, called B, of the same stacking group has +10% damage and +10% damage per power level.
Imagine a unit has Buff B with a power level of 5 on it. so it does +60% damage. Then Buff A with power level 0 is applied. Buff A has a lower power level, but has better values (100% damage versus 60%). So if only the power level was used for stacking, A wouldn't overwrite B, but everybody would agree that A should overwrite B.
So we have level and power level for stacking group buffs.
method applyCustomPower takes Unit caster, Unit target, integer level, integer power returns Buff
in this function, you can state a level and a power value. The power value will alter the buff's modifications and the duration, the level will do the stacking stuff.
How to set the level for such a buff?
You already saw, that when using stacking groups, you should use the function "applyCustomPower", not only "apply". There are two other advanced apply functions that let you specify explicitly the time the buff will last:
//Applies a buff of this type, cast by caster onto the target target with level level //If the target doesn't have this buff already, then it is created (onCreate event is fired) //If the unit already has this buff, there are three possibilities //--> The unit's buff has a higher level than this one -> nothing is done //--> The unit's buff has the same level than this one -> the buff duration is refreshed and an onRefresh event is fired //--> The unit's buff has a lower level than this one -> the buff duration is refreshed and an onUpgrade event is fired method apply takes Unit caster, Unit target, integer level returns Buff //Applies a buff with custom time (rest is the same as in normal apply) method applyCustomTimed takes Unit caster, Unit target, integer level, real time returns Buff
The difference is that in the upper one, power and level are divided, so the upper one should be used for stacking group buffs.
If you use these, your buff will exactly last "time" seconds on the unit. So the values you stated in the BuffType's constructor will not matter anymore. You might want this to add buffs that last a random period of time on units or which's duration is altered by some other things.
You can use these two methods of Unit to check/get the buff a unit has of a specific type/stacking group:
//Returns the buff of BuffType that the unit has //If the unit has no buff of this buff type, 0 will be returned //So you can use uc.getBuffOfType(...) != 0 to check if a unit container has a specific buff method getBuffOfType takes BuffType bt returns Buff //Returns the buff of the stacking group named that the unit has //If the unit has no buff of this stacking group, 0 will be returned //So you can use uc.getBuffOfGroup(...) != 0 to check if a unit container has a buff of //a specific stacking group method getBuffOfGroup takes string s returns Buff
You could use these for example for a tower that has a chance to place a buff and then on each attack deals bonus damage if the creep has the buff.
You might want to create an ability that removes positive/negative buffs from units. For this, this method can be used:
//Forces to remove a buff (the first in the buff list) //positive: If set to true a positive buff is removed, else a negative one //This method returns true if there was a buff to remove, otherwise false //Buffs removed by this method will trigger a purge buff event before beeing destroyed method purgeBuff takes boolean positive returns boolean
It removes either a positive or a negative buff and fires the buff's onPurge events. It will return true if there was a buff to remove and false otherwise.
Projectiles are moving visble dummy units, that can move in many ways (home at a target, fly straight ahead, fly balistically impacting or bouncing from the ground and some more). By default, they don't do anything but moving. However, just like with buffs, you can react to many events, thus giving them influence on the gameplay.
Before I tell you how to create projectiles, I want to talk about the two major types of projectiles the YouTD engine supports.
The second type are interpolated projectiles. Interpolated projectiles follow a fixed trajectory with a fixed interpolation stepsize. That means you cannot set things as freely as you can for moving projectiles. But don't abandon them! They can easily create very cool curves like projectiles that fly a looping and such stuff.
Keep those two types in mind, I will always refer to them.
I will call the first one "normally moving projectile" or just "moving projectile" and the second one "interpolated projectile". Of couse, also interpolated projectiles are moving. But they are not really moving with some movespeed but follow an invisible curve.
This chapter will cover the basic aspects that have to be done for each type of projectile.
Creating a projectile type
Just like for dummies and buffs, projectiles are created in two steps: First creating a ProjectileType on map init and then launching projectiles of this type.
You create a new projectile by invoking one of these constructors
struct ProjectileType //Constructors for normally moving projectiles static method create takes string model, real lifetime, real speed returns ProjectileType static method createRanged takes string model, real range, real speed returns ProjectileType //Constructor for interpolated projectiles static method createInterpolate takes string model,real speed returns ProjectileType
The first constructor for example takes the following values:
As you see, an interpolated projectile has no lifetime. This is because it just interpolates between two points. As soon as it reaches the target point, it expires. No lifetime needed.
Now you have a projectile type with a model, lifetime and speed. Let's launch some of these projectiles!
Launching a projectile
After you have created a ProjectileType, you can launch projectiles from it. You do this by invoking one of the constructors of the struct Projectile. There are different constructors for normally moving and interpolated projectiles. Don't let the number of existing constructors scare you! There are many, but only differ in the start and target of that projectile. Projectiles can be launched from Units or points (x,y,z), they can target a point a Unit or just face a specific angle. There are constructors for each combination of start types and targets.
All constructors are well commented in the API. Just check the trigger "Projectiles". There you will find all necessary information about every parameter.
Let's create a tower that shoots a projectile into the direction of the attacked creep everytime it attacks.
library myTower initializer init uses libHeader globals ProjectileType myPT endglobals private function init takes nothing returns nothing set myPT = ProjectileType.create("Abilities\\Weapons\\Mortar\\MortarMissile.mdl",4.0,500.0) endfunction endlibrary
The projectile uses the mortar missle model, and flies with 500 speed for 4.0 seconds.
static method createFromUnitToUnit takes ProjectileType pt, Unit caster, real damageRatio, real critRatio, Unit from, Unit target, boolean targeted, boolean ignoreTargetZ, boolean expireWhenReached returns Projectile
Now, launch the projectile upon attack (In the "On Attack" trigger):
globals constant real ONATTACK_chance = 1.0 //Launch on every attack constant real ONATTACK_chanceLevelAdd = 0.0 endglobals function onAttack takes Tower tower returns nothing call Projectile.createFromUnitToUnit(myPT,tower,1.0,tower.calcSpellCrit(),tower,Event.getTarget(),true,false,false) endfunction
The projectile is casted by the tower, so we insert tower as caster. We give our projectile 100% damage (1.0). For the critRatio we use the tower.calcSpellCrit() method. However, if the projectile deals no damage, we can also put an arbitrary number here. The projectile should fly from our tower ("tower") to the attackted creep ("Event.getTarget()").
We set homing to "true" to make the projectile follow the target. The last two values are set to false.
This example created a simple projectile. However, that projectile doesn't do anything yet. So read on to give your projectile more possibilities.
This chapter will tell you which things can be done with EACH type of projectile. The following chapter will then narrate some details that can be done with only some specific types of projectiles.
Adding basic event reactions to the projectile
There is one basic event that you can always react to: onCleanup.
OnCleanup is fired when the projectile is somehow destroyed.
You can react to it by first writing an event reaction handler which has to satisfy this function interface:
//A function that is used to react on untargeted projectile events function interface ProjectileEvent takes Projectile p returns nothing
(This just means that your function has to take a parameter of type Projectile and has to return nothing)
After writing this function you can pass it to this method of your projectile type of your projectile to set it as an event handler:
struct ProjectileType //Sets the event on cleanup, //cleanup is executed whenever the projectile is destroyed, no matter how it was destroyed //Do your cleanup and garbage collection tasks here method setEventOnCleanup takes ProjectileEvent e returns nothing
As the event is called "onCleanup" you should do your cleanup tasks here. If you have allocated any resources, effects or other things, destroy them here.
This was only one event for your projectile and not a very mighty one!
Enabling projectile modes
There are some "modes" you can enable for all kinds of projectiles. They are:
You can enable one or many of these modes for a projectile type by calling the appropriate methods of ProjectileType:
struct ProjectileType //Enables the collision for this projectile. This means whenever a unit that satisfies the <targetType> comes into //<collRadius> range of this projectile <collEvent> will be invoked. //If <destroyOnCollision> is true, then the projectile will be destroyed upon collison //If not, then the projectile will move on. The projectile can hit every unit only once! method enableCollision takes ProjectileTargetEvent collEvent, real collRadius, TargetType targetType, endmethod //Enables a periodic events for this projectile //The event function <periodEvent> will be invoked every <period> seconds method enablePeriodic takes ProjectileEvent periodEvent, real period returns nothing endmethod
These methods are commented well, so I won't renarrate their usage again. However there are still some small things to say:
Now you have the knowledge to react to events and set modes for projectiles. However, there are some more things you can do for each type of projectile:
Setting additional values for ProjectileTypes
You can set more values for a ProjectileType than the few parameters of its constructor and the modes.
Here are some values you can change:
struct ProjectileType //Sets a custom damage table for these projectiles (check the HowTo for more information on damage tables) method setCustomDamageTable takes DamageTable dTable returns nothing endmethod //The rotation of the projectile, i.e. the rate at which the projectile //changes its direction (to create projectiles that fly a curve) method setStartRotation takes real rotation returns nothing endmethod //Enables free rotation, that means that the projectile direction //is no longer coupled with the projectile facing. //If this is enabled, projectile rotation will only be applied to the facing of the model method enableFreeRotation takes nothing returns nothing endmethod //Disable the z compensation for this projectile type //This means that if the projectile flies over a cliff, it will drop down //instead of flying straight ahead //Note that this also disables all internal z calculations, //so it is no good choice for physical and bezier projectiles with an arc! method disableZcompensation takes nothing returns nothing endmethod //Disables the death animation of the projectile if it expires //If it hits a target (On Collide, On Target Hit) or impacts (Physical) //it will stil play its death animation. method disableExplodeOnExpiration takes nothing returns nothing endmethod //Disables the projectiles death animation when it hits a target, collides or impacts method disableExplodeOnHit takes nothing returns nothing endmethod
Check the API for a full list of possibilities. There are MANY more. Note that some of these possibilities are not useful for interpolated projectiles. However, the API is well sorted by which functions you can use for which types of projectiles.
Changing projectile values during its flight
Until now, you have statically created ProjectileTypes and then launched projectiles that used their parameters.
struct Projectile extends DummyUnit //The projectiles position (note that you cannot SET these values for interpolated projectiles, just get them) real x real y real z //The projectile's flight direction (again, you can't SET it for interpolated projectiles) real direction //The projectile's speed real speed //The remaining lifetime of the Projectile in seconds real remainingLifetime //Sets the homing target for this projectile //If <targ> is 0, then the homing mode will be disabled method setHomingTarget takes Unit targ returns nothing endmethod //This function can be called in an event that would destroy the projectile //to avert this destruction. But be careful, after that, you have to resetup that projectile's movement //This method cannot be used to avert destruction on Collision. For making your //Projectile survive a collision, just set destroyOnCollision to false for its projectile type. // //If used in an OnInterpolationFinished event, the interpolation will be deactivated. //If used in an OnTargetHit event, the target homing will be deactivated. //If used in an OnImpact event, the physical mode will be deactivated. //If used in an OnInterpolationFinished or OnExpiration event, //you should set the remainingLifetime to a new value. Otherwise, the projectile //will be destroyed again in the next frame since its lifetime is still used up method avertDestruction takes nothing returns nothing //Sets if the projectile should explode (play its death animation) or not //You must set this variable right before destroying the projectile yourself //or in the event that kills it (onHit, onExpire,...) //Otherwise, it will be overwritten by the destroying event boolean explode //If enabled, the projectile direction is decoupled from its direction //By enabling this and setting the rotation of the projectile, you //can archieve projectiles that rotate their model without changing their trajectory boolean rotateFreely //Gets the time in seconds since this projectile was spawned method getAge takes nothing returns real //sets the teamcolor of the projectile method setTeamcolor takes playercolor c returns nothing //Sets the scale of the projectile method setScale takes real value returns nothing //Colors the projectile with red green blue and alpha value //These must be between 0 and 255 //a = 0 -> totally invisible projectile, a = 255 -> totally opaque projectile method color takes integer r, integer g , integer b, integer a returns nothing //Adds an ability to the projectile (use only passive abilities, a projectile //cannot cast active abilities method addAbility takes integer abilId returns nothing //Removes an ability from the projectile method removeAbility takes integer abilId returns nothing //Changes the model of the projectile, use "" for no model method setModel takes string fxpath returns nothing
So you can get and set the projectile's position, its speed, remaining lifetime and other stuff. You can also change/set/unset the homing target of your projectile. You can recolor and rescale your projectile and add/remove abilities to/from it (like phoenixfire or something like that).
Another mentionable thing is the "avertDestruction" method, that allows you, if used in an event handler for an event that would normally destroy the projectile, to keep the projectile alive. So you could make a homing projectile which onHit does not die but do something else.
In addition, you can disable and enable modes for your projectile even during flight!
struct Projectile extends DummyUnit //Collision mode method setCollisionEnabled takes boolean enable returns nothing //Periodic mode method disablePeriodic takes nothing returns nothing method enablePeriodic takes integer period returns nothing
You can also specify new parameters for these modes, like changing the target type of your collision or the collision size of your projectile. You can find the corresponding functions in the API.
Now you have the tools to alter projectiles during their flight.
There are even more methods that can be called during the projectile flight. However, these methods can only be used on certain types of projectiles. So they are stated in the chapters about these types.
Projectiles are dummy units
As you might have seen, the struct Projectile extends the struct DummyUnit. So you can use all methods of DummyUnit also on projectiles.
For example, you can set a kill or damage event for them, get the caster or let them do spell damage (for a complete list check the API for DummyUnits):
struct DummyUnit //Gets the casting Unit of this dummy unit method getCaster takes nothing returns Unit //Sets the damage event of this dummy unit //(The handed function will be executed when this dummy damages a unit) method setDamageEvent takes EventHandler ev returns nothing //Sets the kill event of this dummy unit //(The handed function will be executed when this dummy kills a unit) method setKillEvent takes EventHandler ev returns nothing //Lets this dummy do spell damage //Attention: The spell damage is modified by the dummy's crit and dmgRatio, //so if your dummy had 10.0 dmgRatio and did a crit with 2.0 times damage //a call to this function with amount = 10 would do 10*10*2 = 200 damage // //Note that in addition, the damage done by this function is modified //by the spell resistance of the target and the spell damage amplification of the tower //so if the upper 200 damage was cast from a tower with 1.5x damage multiplier //onto a creep with 50% spell damage reduction, it would cause 200*1.5*0.5 = 150 damage method doSpellDamage takes Unit target, real amount returns nothing
This chapter will tell you about the normally moving, non-interpolated projectiles.
Constructors for normally moving projectiles
There are these constructors available for normally moving projectiles:
struct Projectile extends DummyUnit //Creates a projectile at a point facing a direction static method create takes ProjectileType pt, Unit caster, real damageRatio, real critRatio, real x, real y, real z, real facing returns Projectile //Creates a projectile at the position of a unit facing a direction static method createFromUnit takes ProjectileType pt, Unit caster, real damageRatio, real critRatio, Unit from, real facing returns Projectile //Creates a projectile at a point facing another point static method createFromPointToPoint takes ProjectileType pt, Unit caster, real damageRatio, real critRatio, real startX, real startY, real startZ, real targetX, real targetY, real targetZ, boolean ignoreTargetZ, boolean expireWhenReached returns Projectile //Creates a projectile at the postion of a Unit facing a point static method createFromUnitToPoint takes ProjectileType pt, Unit caster, real damageRatio, real critRatio, Unit from, real targetX, real targetY, real targetZ, boolean ignoreTargetZ, boolean expireWhenReached returns Projectile //Creates a projectile at a point facing a Unit static method createFromPointToUnit takes ProjectileType pt, Unit caster, real damageRatio, real critRatio, real startX, real startY, real startZ, Unit target, boolean targeted, boolean ignoreTargetZ, boolean expireWhenReached returns Projectile //Creates a projectile at the position of a Unit facing another Unit static method createFromUnitToUnit takes ProjectileType pt, Unit caster, real damageRatio, real critRatio, Unit from, Unit target, boolean targeted, boolean ignoreTargetZ, boolean expireWhenReached returns Projectile
The first 4 parameters from each constructor were already explained in the first chapter about projectiles. As already stated there, the constructors just differ in the type of start points / targets. Their names can be pretty much readed as a sentence, so "createFromPointToUnit" creates a projectile from a x,y,z position (point) to a Unit.
All the constructors take either Units or points as targets and start positions. The first two take a starting angle instead of a target.
The constructors that take a unit as target always have a boolean parameter "targeted". This parameter specifies if the projectile should home to that unit or just fly to the position the unit had when starting the projectile.
Another parameter many constructors have is the ignoreTargetZ parameter. If it is set to true, then the target height value will be ignored. Instead, the projectile will fly straight without changing its height.
The last parameter is "expireWhenReached". If it is set to true, the projectile lifetime will be adjusted so that the projectile expires upon reaching the target point. Otherwise, the projectile lifetime will be taken from the projectile type.
Also note that you should not set "targeted" and "expireWhenReached" both to true. Because then, it is undefined if the projectile first expires without hitting the target or first hits the target hence dying and not expiring.
Additional parameters for normally moving projectiles
Normal moving projectiles have some more values that you can alter, either in the projectile type, in the projectile itself during its flight or both.
struct ProjectileType //The acceleration of the projectile in length units per second˛ method setAcceleration takes real acceleration returns nothing //The rotation of the projectile, i.e. the rate at which the projectile //changes its direction (to create projectiles that fly a curve) //If rotate freely is enabled, then this is not the change of direction //but the change of model facing. method setStartRotation takes real rotation returns nothing //Sets the event that is executed when the projectile reaches its maximum lifetime //(just before it dies) method setEventOnExpiration takes ProjectileEvent e returns nothing struct Projectile extends DummyUnit real accelerate real rotation
So, moving projectiles have a new event "onExpire" that is fired if the projectile dies due to using up its lifetime.
Modes for moving projectiles: Homing Mode
Moving projectiles offer two modes which can also be combined which offer some more features. One of them completely alters the projectile's behaviour while the other one just adds some utilitity. Let's start with the second one: Homing Mode.
Of course, you already had seen how to make a projectile home: By just setting the "targeted" flag in the appropriate constructor!
struct ProjectileType method enableHoming takes ProjectileTargetEvent e, real controlValue returns nothing
You can enable/disable homing by just setting the homing target (0 as target will disable homing).
Note that if the target died during the projectiles flight, the onHit event will still be called. However, then the target parameter will be 0. So check in your event reaction if the target is zero if you want to alter only living targets!
Modes for moving projectiles: Physical Mode
Physical mode will make your projectile behave like a real cannon ball. So it will start with a positive zSpeed (it flies into the air) and then it will be pulled back to ground by gravity. When it reaches the ground, it will either bounce from it or impact into it (getting destroyed of course).
You can enable physics mode in the projectile type by calling the first method:
struct ProjectileType method enablePhysics takes ProjectileEvent eventOnBounce, ProjectileEvent eventOnImpact, real zSpeed, integer numBounces, real bounceFactor returns nothing method setGravity takes real gravity returns nothing
Enable physics is used to enable the physical mode and set the needed parameters. "setGravity" can be used to give your projectile type a special gravity value. If it is not called the standard gravity of 1.0 will be used (which means the zSpeed of your projectile is decreased by 1.0 each frame).
You can set these things in the "enablePhysics" method:
As you see, you can hand a handler for a bounce and for an impact event. Then you can state the zSpeed, i.e. the speed which the projectile will have into the z direction when it is launched.
Just like with every other mode, you can enable/disable physics mode during the projectile flight or even change the physics parameters by invoking the corresponding methods.
Reaiming moving projectiles
You can reaim moving projectiles during their flight to a new point or unit by calling one of these methods:
struct Projectile extends DummyUnit method aimAtUnit takes Unit target, boolean targeted, boolean ignoreZ, boolean expireWhenReached returns nothing method aimAtPoint takes real x, real y, real z, boolean ignoreZ, boolean expireWhenReached returns nothing
The parameters are basically the same as for the constructors. Of course caster, projectile type, crit and damage ratio and starting point are missing because the projectile already has these.
Interpolated projectiles allow you to do some cool stuff that is impossible with moving projectiles.
The different types of interpolations
There are three kinds of interpolated projectiles in YouTD. They are using linear interpolation, bezier interpolation and cubic spline interpolation.
Linear interpolation allows just an interpolation between two points. As an addition a zOffset can be added which allows the projectile to fly an arc between those two points.
Common facts about interpolated projectiles
Since interpolated projectiles follow a fixed trajectory, you cannot set their coordinates or their speed after the interpolation has started. If you want to fly to new coordinates or change the speed, you have to start a new interpolation.
As you have already seen in the constructor for their ProjectileType, interpolated projectiles do not have a lifetime. Instead, they "expire" when they have finished the interpolation between two points.
For interpolated projectiles, you can react to the end of the interpolation, with this method in your projectile type:
struct ProjectileType method setEventOnInterpolationFinished takes ProjectileTargetEvent e returns nothing
This function takes a ProjectileTargetEvent, even if it does not target a unit all the time. The convention is the following: If the interpolation is finished and the projectile was homing, then it will have reached its target and the hit unit will be the target in your event handler. If the projectile was not targeted or the unit has died, the target in the event handler will be 0.
So, if compared to normally moving projectiles, this event serves as onExpire and onTargetHit event. There are no two events for interpolated projectiles since these will always appear together for en interpolation (since the finish point of an interpolation is always the target).
You do not specify in the ProjectileType which type of interpolation the projectile will use. Instead, the constructor determines which interpolation is chosen (there are constructors for each of the interpolation methods).
Linear projectiles are launched by invoking one of these constructors
struct Projecitle static method createLinearInterpolationFromPointToPoint takes ..., real zArc returns Projectile static method createLinearInterpolationFromPointToUnit takes ..., real zArc, boolean targeted returns Projectile static method createLinearInterpolationFromUnitToPoint takes ..., real zArc returns Projectile static method createLinearInterpolationFromUnitToUnit takes ..., real zArc, boolean targeted returns Projectile
The difference between these constructors is just if the start/target is a unit/point. I omited the first parameters because they are the same as for moving projectiles. The only new parameter is zArc. And that is the specialty that linear projectiles have! You can specify an arc that your projectile will fly. Just like for attacks in the object editor where you can specify a missle arc.
The values match more or less the ones of the object editor. So if you specify 0.2 as zArc, the projectile will fly the same arc as a missle of a unit that has 0.2 missle arc in the object editor. This is just an approximation, no exact match.
So, linear projectiles grant you projectiles that can hit or home at a target and fly an arc. Physical projectiles can also create arcs, but they cannot guarantee that the projectile will land at the target point. Linear projectiles always land there.
Bezier projectiles are launched by invoking one of these constructors
struct Projecitle static method createBezierInterpolationFromPointToPoint takes ..., real zArc, real sideArc, real steepness returns Projectile static method createBezierInterpolationFromPointToUnit takes ..., real zArc, real sideArc, real steepness, boolean targeted returns Projectile static method createBezierInterpolationFromUnitToPoint takes ..., real zArc, real sideArc, real steepness returns Projectile static method createBezierInterpolationFromUnitToUnit takes ..., real zArc, real sideArc, real steepness, boolean targeted returns Projectile
I have again omitted the first parameters as they do match the moving projectile constructors agian.
A bezier interpolation is mightier than a linear interpolation. Because of that, we now have two addtional parameters: sideArc and steepness.
Here are some pictures to show you the influence of the settings:
zArc: 0.4 (everything else 0)
sideArc: 0.4 (everything else 0)
zArc: 0.6, steepness: 0.17 (default) (no side arc)
zArc: 0.6, steepness: 0.9 (no side arc)
zArc: 0.6, steepness: 2.0 (no side arc)
zArc: 0.6, steepness: -1.5 (no side arc)
//Advanced function for Bezier Interpolated Projectiles (No Splines!): //Sets one control point to a position relative to startpoint and endpoint //imagine a coordinate system where the x-axis goes from start to end with start beiing at 0 and //end beiing at 1. with the other two axis it is an orthonormal basis with the y-axis having a z of 0. //<index> tells which point to set, should be either 1 or 2 to set the first or the second control point respectively //This method should be called right after starting the Bezier Interpolation //Otherwise this will look strange or have no effect at all! method setBezierPointRelative takes integer index ,real x, real y, real z returns nothing
Call this function right after the constructor (or a start interpolation method). Then you can set the coordinates relatively. This grants you the full freedom bezier curves offer. However it is not as convenient as the constructors.
Reaiming interpolated projectiles
As I told you, you cannot just change the direction or coordination of an interpolated projectile. The engine will just ignore that. But if you want to change its trajectory during flight, you have some other possibilities.
The first one is to stop the interpolation by calling this method:
method stopInterpolation takes nothing returns nothing
Then, the projectile becomes a normally moving one. You can then alter its speed, direction or coordinates and do all the stuff with it that can be done with normally moving projectiles.
You can also just start a new interpolation. There are methods for that that match the constructors. The difference is that they need less parameters (no crit ratio, damage ratio, caster, projectile type. The projectile has these values already) and take no starting point (since the projectile will just start from its own position).
Here are the methods:
//Linear interpolation method startLinearInterpolationToPoint takes real targetX, real targetY, real targetZ, real zArc returns nothing method startLinearInterpolationToUnit takes Unit target, real zArc, boolean targeted returns nothing //Bezier interpolation method startBezierInterpolationToPoint takes real targetX, real targetY, real targetZ, real zArc, real sideArc, real steepness returns nothing method startBezierInterpolationToUnit takes Unit target, real zArc, real sideArc, real steepness, boolean targeted returns nothing //Spline interpolation method startSplineInterpolationToPoint takes real targetX, real targetY, real targetZ, Spline s returns nothing method startSplineInterpolationToUnit takes Unit target, Spline s, boolean targeted returns nothing
The engine offers many different types of projectiles and modes to alter these. This chapter will tell you where the pros and cons of each mode are.
Normally moving projectiles
When to use?
Linear interpolated projectiles
When to use?
Bezier interpolated projectiles
When to use?
Spline interpolated projectiles
This projectile type offers almost arbitrary trajectories. However these are hard to code and use much computation time. So the simple rule for splines is: If you really need such a complex trajectory and cannot archieve it otherwise use splines. In all other cases, don't use them!
Physically moving projectiles
When to use?
When I talked about the projectiles, I always mentioned which one is better in computation time. Here is a quick overview how much that computation time really is. The values are based on normally moving projectiles without homing. Their computation time is set to 100%.
Computation times of the different projectile types:
So as you see, homing always costs some computation and interpolated projectiles are slower than normal ones. However, the factor is a maximum of 2.29, so this is not a too big gap! So the rule is: Use the projectile that suits your job best. Only if different kinds suit your job equally good, use the one with the least computation time.
Some methods of the API require you to hand a variable of type "TargetType to it.
Target Types should be created at initialization and then used.
A target type is created by calling its constructor:
struct TargetType static method create takes integer targetCode returns TargetType
As you can see, this method requires an integer "targetCode" as parameter.
This integer specifies which things can be targeted by this target type.
//General types constant integer TARGET_TYPE_CREEPS = 0x1 //Target only creeps constant integer TARGET_TYPE_TOWERS = 0x2 //Target friendly towers constant integer TARGET_TYPE_PLAYER_TOWERS = 0x4 //Target player towers //Race targeting constant integer TARGET_TYPE_RACE_UNDEAD = 0x8 constant integer TARGET_TYPE_RACE_MAGIC = 0x10 constant integer TARGET_TYPE_RACE_NATURE = 0x20 constant integer TARGET_TYPE_RACE_ORC = 0x40 constant integer TARGET_TYPE_RACE_HUMANOID = 0x80 //Size targeting constant integer TARGET_TYPE_SIZE_MASS = 0x100 constant integer TARGET_TYPE_SIZE_NORMAL = 0x200 constant integer TARGET_TYPE_SIZE_CHAMPION = 0x400 constant integer TARGET_TYPE_SIZE_BOSS = 0x800 constant integer TARGET_TYPE_SIZE_AIR = 0x1000 //Element targeting constant integer TARGET_TYPE_ELEMENT_ASTRAL = 0x2000 constant integer TARGET_TYPE_ELEMENT_DARKNESS = 0x4000 constant integer TARGET_TYPE_ELEMENT_NATURE = 0x8000 constant integer TARGET_TYPE_ELEMENT_FIRE = 0x10000 constant integer TARGET_TYPE_ELEMENT_ICE = 0x20000 constant integer TARGET_TYPE_ELEMENT_STORM = 0x40000 constant integer TARGET_TYPE_ELEMENT_IRON = 0x80000
The most important codes are the first three. These types devide units into three major categories. Creeps, friendly towers and player towers. The difference between friendly and player towers is that friendly towers will target all towers from your team, while player towers only targets your really own towers.
So this example call would create a target type that targets only creeps:
set myTargetType = TargetType.create(TARGET_TYPE_CREEPS)
There are more constants that can be used to further specify which things to target.
set myTargetType = TargetType.create(TARGET_TYPE_CREEPS + TARGET_TYPE_SIZE_MASS + TARGET_TYPE_SIZE_NORMAL)
Targets only mass and normal creeps.
Note that you cannot compose the three base types, target types may only target either creeps OR friendly towers OR player towers. This is because there is hardly a scenario where you want an ability to target friends AND foes.
If you want to pick units in range of some point in normal WC3, you need a call to GroupEnumUnitsInRange(...) call and then iterate over them with a ForGroup() call. These calls however are very unsatisfying. They are slow and need more coding overhead to archieve the effect that you want. They need a group which has to be cleared or removed and nulled to avoid memory leaks.
Because of that inconveniences, the YouTD engine offers you a more convenient and fast way (up to 50% faster) than those ugly calls. Whenever you want to pick all units in range of something and iterate over them, you can create an instance of the struct "Iterate", it can be created with one of these functions
static method overUnitsInRange takes Unit caster, TargetType t, real x, real y, real radius returns Iterate static method overUnitsInRangeOfCaster takes Unit caster, TargetType t, real radius returns Iterate static method overUnitsInRangeOfUnit takes Unit caster, TargetType t, Unit center, real radius returns Iterate
They all take a casting unit (the unit that casts the spell for which you do the iteration), a target type (check the chapter about Target Types).
Then, they need something to specify the center of the InRange call and the radius of the pick call.
After you have create such an Iterate, you can use its .next() method to get the next picked Unit. The next method returns 0 if no more units to iterate over exist.
Here is an example:
//Start the iteration local Iterate it = Iterate.overUnitsInRangeOfCaster(yourTower,yourTargetType,1000.0) local Unit u loop set u = it.next() //Get the next unit exitwhen u == 0 //Stop when there are no more units //Do something with the Unit u here endloop
This way, you can iterate over units conveniently and fast. If you iterate until the end (i.e. until u == 0 in this example), then you don't have to destroy the iterator afterwards. The iterator will destroy itself when there is no more unit to iterate over.
However, if you stop the iteration before, for example after 5 units, you must call .destroy() on the iterator to deallocate it. Otherwise you will have a memory leak.
So, if you stop the iteration if all units are iterated or another condition (for example after 5 units), you should check if the last unit returned by .next() was 0. If it wasn't you have to destroy it, if it was, don't destroy or you will make a double free. Here is an example with a second condition:
//Start the iteration local Iterate it = Iterate.overUnitsInRangeOfCaster(yourTower,yourTargetType,1000.0) local Unit u loop set u = it.next() //Get the next unit exitwhen u == 0 //Stop when there are no more units exitwhen ... //Second condition //Do something with the Unit u here endloop //That's the way to destroy the iteration here! if u != 0 then call it.destroy() endif
Note that those iterations can only iterate over living units. If you want to iterate over dead creeps, use these methods of iterate:
static method overCorpsesInRange takes Unit caster, real x, real y, real radius returns Iterate method nextCorpse takes nothing returns unit
So you have to create the iteration with the "overCorpsesInRange" method. Then you can get the next corpse with the .nextCorpse() function.
Note that this function returns a raw unit (no Unit), because dead units are no longer wrapped with a Unit struct in the YouTD engine.
You know normal auras from Warcraft3. YouTD allows you to create triggered auras just like Buffs. A triggered aura is basically an ability that applies a buff to every unit that matches some targeting criteria and comes into the range of the aura.
Your tower/item content creation map has the following trigger called "Tower / Item Aura":
globals real AURA_auraRange = RANGE integer AURA_targetType = TARGET TYPE boolean AURA_targetSelf = TARGET SELF integer AURA_level = LEVEL integer AURA_levelAdd = LEVEL ADD integer AURA_power = POWER integer AURA_powerAdd = POWER ADD BuffType AURA_auraEffect = AURA EFFECT endglobals
To give your tower / item a triggered aura. Just fill in the according values for these variables:
So basically, creating an aura is very simple. First create a BuffType and give it the events/modifier that the aura effect should have. Then state it as auraEffect in this trigger.
There are some points to know about auras:
You could also create own other auras and even give them to buffs (this would be a buff that grants an aura to the buffed unit). But since those kind of buffs seem very bizarre and I don't think you will ever want to create such a buff, this is not covered here.
If you really need to create a buff that gives an aura to the buffed unit, open a thread in the YouTD support forum.
Since the last chapter was very abstract I will now show you an example, that tells you how to create an aura.
Let's start with the buff type. As you might remember an aura is just an ability that automatically applies a buff onto every unit that comes in range and matches the targeting critiria.
library myTower initializer init uses libHeader globals BuffType myAuraBuff endglobals private function init takes nothing returns nothing local Modifier m = Modifier.create() // (0) set myAuraBuff = BuffType.createAuraEffectType(false) // (1) call m.addModification(MOD_MOVESPEED,-0.2,-0.01) // (2) call myAuraBuff.setBuffModifier(m) // (3) call myAuraBuff.setStackingGroup("frost_aura") // (4) call myAuraBuff.setBuffIcon('B000') // (5) endfunction endlibrary
We just created the buff type for that aura. That was the biggest part of the work. All we have to do now, is filling in the values in the aura trigger:
globals real AURA_auraRange = 1000 integer AURA_targetType = TARGET_TYPE_CREEPS boolean AURA_targetSelf = false integer AURA_level = 0 integer AURA_levelAdd = 1 integer AURA_power = 0 integer AURA_powerAdd = 1 BuffType AURA_auraEffect = myAuraBuff endglobals
That's it! Of course we should fill in the fields in the trigger header, to give the aura a nice explanation icon.
You can give your tower/item an active ability which will be cast automatically by the tower that has the ability / carries the item.
globals integer AUTOCAST_autocastType = AUTOCAST TYPE real AUTOCAST_range = RANGE real AUTOCAST_autoRange = AUTO RANGE real AUTOCAST_cooldown = COOLDOWN integer AUTOCAST_manacost = MANA COST //Only for ACs that can target towers. This flag determines if the tower can target itself. private boolean AUTOCAST_targetSelf = true //Only for NON-buffs (set to false otherwise) private boolean AUTOCAST_isExtended = IS EXTENDED //Only for buffs (set all three to 0 if this is no buff) integer AUTOCAST_targetType = TARGET TYPE integer AUTOCAST_numBuffsBeforeIdle = NUM BUFFS BEFORE IDLE BuffType AUTOCAST_buffType = BUFF TYPE endglobals function onAutocast takes Tower tower returns nothing endfunction
As you see, it contains an event reaction handling function "onAutocast" and several values you must set for your autocast.
This is just an event handler that will be called whenever your tower uses this autocast. It is called when the tower casts the spell by itself using the autocast engine. However, it is also cast if the player disables autocasts for this tower and casts the ability manually.
So this function is basically the effect of your cast, put the actions that your cast does here.
AUTOCAST_cooldown, AUTOCAST_manacost are self explanatory. These are the manacost and the cooldown of the ability.
AUTOCAST_range is the spells range. For immediate spells (which don't have a range) use 0 or an other value. This value will be displayed as ability range when you display the tower's details.
AUTOCAST_autoRange is the range where the AI will start casting the ability automatically. For non immediate spells, this range should be less than the spell range (otherwise the AI would try to cast onto targets that are out of range).
AUTOCAST_autocastType is the type of cast, it can have the following values (which are defined in the API library called "Autocast Types"):
constant integer AC_TYPE_ALWAYS_BUFF constant integer AC_TYPE_OFFENSIVE_BUFF constant integer AC_TYPE_OFFENSIVE_UNIT constant integer AC_TYPE_OFFENSIVE_IMMEDIATE constant integer AC_TYPE_OFFENSIVE_POINT
The last three are offensive spells. The tower will cast them whenever it attacks a creep and that creep is in their range, the spell is not in cooldown and the tower has enough mana. Just like for dummy spells you can have unit targeted spells (AC_TYPE_OFFENSIVE_UNIT), ground targeted spells (AC_TYPE_OFFENSIVE_POINT) and spells without a target (AC_TYPE_OFFENSIVE_IMMEDIATE). Unit targeted spells will target the attacked unit, ground targeted spells will target the position of the attacked unit.
The upper two autocast types are the buff types. Buff autocast types use a special tower AI: The tower will try to buff units in its range. However, it will NOT buff units that already have the buff on them. Only if the buff expires it will try to recast it on them.
The difference between the two types, is that AC_TYPE_ALWAYS_BUFF tries to buff units around it whenever it has enough mana.
AC_TYPE_OFFENSIVE_BUFF tries to buff units only as long as the tower is attacking (because it is no use to buff certain buffs as long as the towers are not in combat). You should always prefer AC_TYPE_OFFENSIVE_BUFF. Use AC_TYPE_ALWAYS_BUFF only if you really want your tower to buff all the time, even if there are no creeps around.
Besides these constants, also NOAC constants exist:
constant integer AC_TYPE_NOAC_CREEP constant integer AC_TYPE_NOAC_IMMEDIATE constant integer AC_TYPE_NOAC_POINT constant integer AC_TYPE_NOAC_TOWER constant integer AC_TYPE_NOAC_PLAYER_TOWER
These are used to create an autocast that is not autocasted (controversly, I know ;) ). I.e. it will just create a button that the player can press and once it is pressed, the event reaction will be triggered. The different types just tell which things can be targeted with the non-autocast ability. CREEP, TOWER and PLAYER_TOWER target creeps, team towers and player towers respectively. IMMEDIATE creates an ability with no target, POINT one with a ground target.
If you choose one of the two BUFF autocast types, you have to fill in the last three values (If you don't use a BUFF autocast type, just set the three values to 0):
Finally there are two more field that you will sometimes have to use:
AUTOCAST_isExtended works only for non buff autocasts (i.e. offensive autocasts). If it is set to true, an advanced autocasting mechanism is used for this Autocast. Use this flag if the autocast range greatly differs from the towers attack range. While normal autocasts always cast onto the target that the tower attacks, advanced autocasts choose their target themselves.
So if your tower has 500 attack range but a 1500 range for its autocast, you should use isExtended. If you don't, your tower will only cast on units it can attack, so its range will be limited to 500.
Note that extended autocasts need more computation time, so try to avert them and try to make autocasts that have the same range as the towers attack. Only use extended if you have a good reason why your autocast range differs from your attack range
The final parameter is AUTOCAST_targetSelf. This one is only needed for buffs that can target towers. It states if the tower should be able to target itself (true) or not (false).
As you know, for every event reaction trigger you may set @visible to true in its trigger comment and fill in the values there to create a dummy tooltip ability for this event.
The autocast trigger comment looks a bit different than other ones, it has the following parameters:
@icon= @name= @short_explain= @long_explain= @caster_art= @target_art=
The first four parameters are in every trigger comment. However, @caster_art and @target_art are new. Here, you can set model paths of effects that you want your spell to create on its caster / target. Since an active spell should have a cool effect, set appropriate values here.
The trigger comment could then look like this for a buff:
@icon=ReplaceableTextures\CommandButtons\BTNInnerFire.blp @name=Example buff @short_explain=Buffs towers around it increasing their damage. @long_explain=Buffs towers around it increasing their damage by 100%. The buff lasts 10 seconds. @caster_art=Abilities\Spells\Human\Heal\HealTarget.mdl @target_art=
This would add an effect with model "Abilities\Spells\Human\Heal\HealTarget.mdl" onto the caster. The target receives no effect.
Let's repeat the steps it takes to create an autocast:
The engine allows you to create special effects for your abilities.
At first you have these two basic effect functions in the header:
//Creates and removes an effect at the position of a unit //(can also be used on units which's model doesn't have attachement points) function SFXAtUnit takes string effectPath, unit onWhichUnit returns nothing //Creates and removes an effect on a unit at attachementpoint "where" //(can also be used on units which's model doesn't have attachement points) function SFXOnUnit takes string effectPath, unit onWhichUnit, string where returns nothing
They create the effect with path "effectPath" either at the position of a unit (SFXAtUnit) or attached to a unit's attachment point (SFXOnUnit). The effect is also immediately destroyed, so you don't have to garbage collect it later. If the effect is longer than just a birth or death animation, you cannot use these functions.
For more advanced and enduring effects, you can use the struct Effect. Note that those effects cannot be attached to a unit, they are only at one point and don't move with a unit. First you create an Effect with one of the constructors:
struct Effect static method createColored takes string modelPath, real x, real y, real z, real facing, real scale, integer r, integer g, integer b, integer alpha returns Effect static method createScaled takes string modelPath, real x, real y, real z, real facing, real scale returns Effect static method create takes string modelPath, real x, real y, real z, real facing returns Effect static method createSimple takes string modelPath, real x, real y returns Effect
The constructors differ in their parameter count. While the most simple version only allows you to specify the model paht, x and y position, the more advanced constructors allow you to set the height(z), the facing, scale and color. Choose the constructor that suits your parameter needs.
The methods return an instance of Effect. The effect will be shown until you destroy it (using the .destroy() method). In addition, the struct allows a convenient way to automatically destroy the effect after a given duration. For this sake, you have to call this method:
struct Effect method setLifetime takes real time returns nothing
If you have set the lifetime with this method, the effect will automatically disappear after the time has expired.
There are some additional methods in the struct Effect that you can use to alter effects during their lifetime (like increasing their scale, moving them, recoloring them, setting a specific animation or setting animation speed). If you need those, check the documentation in the API Trigger.
The YouTD engine is coded to be very robust. However, if you mess up some things in your triggers, you can cause harm. The most important things are waits in your event reactions. As soon as you do a wait like calling "TriggerSleepAction" in your event reaction function, you can no longer be sure if your lokal Units are what you suppose them to be. I will start with an example.
function onAttack takes Tower tower returns nothing call TriggerSleepAction(2.0) call myBuffType.apply(tower,Event.getTarget(),tower.getLevel()) endfunction
That this is wrong will be obvious to most of you. The engine cannot ensure (or let's rather say it is VERY UNLIKELY) that Event.getTarget() will return the real target of this event after a wait, since other events that happend in this period will have overwritten it.
So you try the next approach by saving Event.getTarget() in a local variable:
function onAttack takes Tower tower returns nothing local Unit enemy = Event.getTarget() call TriggerSleepAction(2.0) call myBuffType.apply(tower,enemy,tower.getLevel()) endfunction
This would look correct to most of you. However, it is still not correct. The problem is that struct indices are reused.
Let's pretend this happens in the 2 seconds you wait:
The creep "enemy" gets killed. After that a player builds a tower.
As soon as the creep dies, its struct id is freed. Since creep and tower share the same base struct Unit, it is possible that the tower will get the freed id of the unit. So in this situation, your trigger would now cast the buff onto the new built tower instead of the attacked creep! This would lead to strange bugs.
So the engine allows you to do a safety check on those structs, to check if they are still the same thing that they were when you aquired them.
Almost all structs that can be destoryed in the YouTD engine offer this method:
method getUID takes nothing returns integer
It returns a Unique Identifier (UID). This is an integer that is never used twice for two different instances of a struct. So if the struct variable you saved would have changed in that time and you saved its old UID, you would see that it was replaced, because the UIDs wouldn't match. Note that also the UID of a struct changes as soon as it gets destroyed (it becomes 0 or -1). So by comparing a structs UID with its old UID, you can ALWAYS be sure that it is exactly the same struct instance than before and is not destroyed yet., the old and the new UID wouldn't match.
So this would be the correct sollution:
function onAttack takes Tower tower returns nothing local Unit enemy = Event.getTarget() local integer uid = enemy.getUID() call TriggerSleepAction(2.0) if uid == enemy.getUID() then call myBuffType.apply(tower,enemy,tower.getLevel()) endif endfunction
So as you see, we save the UID before the wait and then check after the wait if the UID is still the same. That's the way to handle waits.
I am sorry that this is a bit cumbersome. However, there was no way to make it easier.
This works also for buffs and items (they have also a getUID() method).
One more thing concerning that example:
Now you might wonder: What is with buffs and the .getCaster() method? If you add a periodic event into a buff that does something with its caster, then you would use the .getCaster() method. But what if the caster already died? Wouldn't you then alter another unit?
Most of the time you won't want to create a unique tower but a whole family of towers. That means, you want to create towers with similar abilities that just differ in their goldcost and ability strength.
For example you might create a tower with 3 versions. One for 50 gold one for 400 gold and one for 2000 gold. They should all have an armor reduction attack which gets stronger for each tower (of course). So the basic way would be to create an armor reduction BuffType in each tower and use it (with a stacking group that these buffs don't stack with each other). However, that is very lame, since you are creating 3 times the same buff with just different strengths. That increases the map size and is some unwanted copy&paste work for you. You might want to just reuse the buff type from one tower in another one.
By default, you cannot use a global variable in one tower in another tower, because these global variables are distinct (they are made private by the script).
YouTD offers you a way to get around this: You can "reuse" global variables for your towers by using reference annotations. Lets say you define the buff type in the first of your three towers and safe it in a global variable:
globals BuffType YOUR_BUFF_TYPE endglobals //Initialize YOUR_BUFF_TYPE somewhere in your init function
The easiest way would be, if you could simply use this variable in your other towers as well. This is possible with annotations. In the tower where you define the buff type you use the "export" annotation to tell the script that this variable will be used by other towers:
globals //@export BuffType YOUR_BUFF_TYPE endglobals //Initialize YOUR_BUFF_TYPE somewhere in your init function
In towers where you want to use this buff type, use the import annotation, to refer to your buff type:
globals //@import BuffType YOUR_BUFF_TYPE endglobals
Now, the variables will be the same and you can use the buff type from one tower in another one.
The variable names when importing/exporting must be, of course, the same. Choose unique variable names to avoid collisions with the exports of other users. For example add your nick name into the variable name.
You should use reference annotations when you create a family of towers or items that use the same buffType / cast / ... with just different power levels. This will keep the map small and the loading time low.
You should NOT use references for towers that share the same buff "by accident". So for example if you see that a user uses an armor reduction buff for his towers and has exported it, then YOU should NOT import it, because if that user changes something for his towers, it will also be changed for your towers and you will wonder why your towers changed even if you have not changed anything about them.
References make the map small, so they are a good thing, if used for families of towers.
There are still some uncovered parts of the API that can useful often.
Often, you want your spells, towers, items or buffs to have some custom data attached to them, that should be kept even between events. For this purpose many structs offer these two members:
//A real number attached to this struct that you can use for whatever you want. //Note that its value is undefined until you define it real userReal //An integer number attached to this tower that you can use for whatever you want. //Note that its value is undefined until you define it integer userInt
These are two numbers, an integer and a real number that you can get and set arbitrarily.
Following structs have them:
So you can attach an int and a real to your stuff. If you need more variables attached, create your own struct type and then just set the struct as userInt (since structs are integers). Using this, you can add an arbitrary amount of data to your tower/item/dummies/buffs.
Note that these members have no specific value unless you define them.
You can get the time elapsed since game start by calling:
It returns an integer number that measures the time since game start in 1/25 seconds. So if it returns 1000, the game runs for 1000/25 = 40 seconds.
Inside a periodic event handling function (the function you assigned to as periodic event handler), you can call:
This function will return you a variable of type PeriodicEvent. Storing and using this variable, you can disable / reenable the periodic event or do other stuff with it. Here are the methods for PeriodicEvent:
//A periodic event can be aquired with the Event.getCurrentPeriodicEvent() method //(Only inside a periodic event reaction function) struct PeriodicEvent //Disables this periodic event. It will not fire until it is enabled again method disable takes nothing returns nothing //Enables the periodic event (if previously disabled) using its standard timeout method enable takes nothing returns nothing //Enables the periodic event (if previously disabled) using its standard timeout //It will only fire once and then never again (unless you enable it again sometime in the future) //(So the event is not really periodic but a one shot event) method enableOnce takes nothing returns nothing //Enables the periodic event using a custom timeout //If is true, then the event will fire only once (so no real periodic event) //If is false, then the event will be normally periodically method enableAdvanced takes real timeout, boolean oneShot returns nothing //Gets the time elapsed since this event last fired method getElapsed takes nothing returns real //Gets the time until this event fires the next time method getRemaining takes nothing returns real
So if you want to deactivate a periodic event, let it run first. Inside its handler, save the variable somewhere (for example in an user int of youer tower) and deactivate it. Then, you can later reenable the event
There is a trigger in the test map called "On Tower Details". It contains this function:
function onTowerDetails takes Tower tower returns MultiboardValues
It looks like the normal event reaction functions, but it returns a variable of type MultiboardValues.
This function is called whenever a user presses the "tower details" button for this tower and is shown the tower multiboard.
With this function you can display text messages with status information about your tower or even inject key/value pairs into the tower details multiboard.
To inject values, add them to the struct "MultiboardValues" and return it. For performance reason, you shouldn't create such a struct whenever the values are called. Rather create it on map init and set its keys (since these won't change). Then upon displaying it, just set the keys and return it.
If you just want to display some information with text messages in your function and not insert something into the multiboard, just return 0 in the function.
Here are the methods for the struct MultiboardValues:
struct MultiboardValues //Creates multiboard values // is the number of key,value pairs to be added to the multiboard // must be between 0 and 4 inclusively // //If you use Multiboard Values for your tower, you should create these at map init //and set the keys there. Then, just set the values correctly when the multiboard //is opened static method create takes integer size returns MultiboardValues //Resizes these MultiboardValues // must be between 0 and 4 inclusively method setSize takes integer size returns nothing //Sets the key number to //Index should be between 0 and size - 1 method setKey takes integer index, string s returns nothing //Sets the value number to //Index should be between 0 and size - 1 method setValue takes integer index, string s returns nothing
You can add up to 4 lines to the multiboard. The constructor (which should be called at map init already takes the size (i.e. the number of key value pairs). However, you can still alter the number of lines whit hte setSize(size) method. Then you can use setKey and setValue to set keys and values.
Note that keys and values shouldn't be too long, or the will not fit into the multiboard. Test your solution to check if the multiboard size is sufficient for your values.
Let's implement the above mentioned example with the tower that gains damage for killing and damaging. We want to display the damage for killing and for damaging in different rows. Let's assume that we have saved the damage through killing in the tower's userInt and the damage through damaging in the towers userInt2.
So, first on map init we create the multiboard values, set the keys and save them in a global variable:
library myTower initializer init uses libHeader globals MultiboardValues myMBValues endglobals private function init takes nothing returns nothing set myMBValues = MultiboardValues.create(2) //Create with 2 initial rows call myMBValues.setKey(0,"Bonus by DMG") call myMBValues.setKey(1,"Bonus by Kills") endfunction endlibrary
We create the mb values, save them and already set the keys. The values will now be set in the on tower details function:
function onTowerDetails takes Tower tower returns MultiboardValues call myMBValues.setValue(0,I2S(tower.userInt)) call myMBValues.setValue(1,I2S(tower.userInt2)) return myMBValues endfunction
That's it! The lines will be in the multiboard.
Here we used I2S to format the userInt to a string. However, especially for floats, we can use one of the formating functions from the header to quickly create formated strings.
They are listed here:
//*********************** FORMATING FUNCTIONS *********************** //Formats a positive integer number to a string inserting a comma each three digits function formatPositiveInt takes integer i returns string //Formats a floating point number to a string, using fraction digits function formatFloat takes real r, integer fractionDigits returns string //Same as format float, but the number is multiplied with 100 and a percent sign (%) is appended to the string function formatPercent takes real r, integer fractionDigits returns string //Same as format percent, but will color values below 100% red and values above 100% green //(Exactly 100% will not get colored at all) function formatPercentColor takes real r, integer fractionDigits returns string //Same as format percent, but will color values below 0% red and values above 0% green //(Exactly 0% will not get colored at all) function formatPercentAddColor takes real r, integer fractionDigits returns string
If the small columns of the multiboard are too small for your needs and you rather want to display a text message containing the your tower's status, you can do that conveniently by calling the .displayText(message) method of player.
This time, we want to display a message instead of putting the values into the multiboard. We erase the MultiboardValues from our header and instead use this as onTowerDetails function:
function onTowerDetails takes Tower tower returns MultiboardValues call tower.getOwner().displayText("Damage gained through damage: |cffFFFF80" + I2S(tower.userInt) + "|r") call tower.getOwner().displayText("Damage gained through kills: |cffFFFF80" + I2S(tower.userInt2) + "|r") return 0 //Return 0 to not insert any values into the multiboard endfunction
For some event types, the engine allows you to create two or more triggers that react to it. Every event reaction trigger that is surrounded by a scope can be duplicated.
At the moment, these events may be duplicated
So you could for example create a tower with two auras, two periodic events or two onAttack events with different ratios.
If you want two or more events, use this procedure:
First, copy paste the event reaction trigger that you want twice. Name the new trigger like the old one, but insert the number behind it (without a space). So if copied trigger was "On Attack", name the new trigger "On Attack2", or if it is the third trigger for onAttack, name it "On Attack3". Do not name the first trigger "On Attack1", just leave it as "On Attack".
Next rename the scope in your new trigger. I recommend just insertin the number behind it, so for on attack rename "eventOnAttack" to "eventOnAttack2".
Now you are done, just code your second event reaction.
Do not rename the event reaction function! Just copy the trigger, rename it, rename the scope and you're done.