Tower scripts ‐ JASS vs Godot - Praytic/youtd2 GitHub Wiki
Introduction
This page describes the differences between tower scripts written in JASS (original game) and in Godot/GDScript (youtd2).
API differences
Note that functions in the Godot engine have different names than the JASS engine. Each Godot function has a comment about it with the name of the JASS function which it implements. To match a JASS function to a Godot function, search in all files for the JASS function name. Search results will point you to the Godot function which you should use.
For example, to get a youtd2 alternative to doAttackDamage
, you would search for doAttackDamage
and find this:
# NOTE: unit.doAttackDamage() in JASS
func do_attack_damage(target: Unit, damage_base: float, crit_ratio: float):
...
Things that are not shown in original scripts
Specials ability
Towers can have an ability called "Specials".
Here's an example of how to add a special "+30% dmg to masses (+1%/lvl)":
func load_specials():
var modifier: Modifier = Modifier.new()
modifier.add_modification(Modification.Type.MOD_DMG_TO_MASS, 0.3, 0.01)
add_modifier(modifier)
Some specials do not involve modifiers. To implement them, call these functions inside load_specials()
:
Original | Godot |
---|---|
Splash Attack: 600 AoE: 10% damage | _set_attack_style_splash({600, 0.10}) |
Bounce attack: 2 targets, -10% damage per bounce | _set_attack_style_bounce(2, 0.10) |
This tower attacks 4 targets at once | _set_target_count(4) |
Adding triggers
Some towers have "triggers" with names like "On Damage", "On Kill". Before implementing the trigger function, you need to add it to the tower.
Here's an example of how you would add an "On Level up" trigger:
func load_triggers(triggers: BuffType):
triggers.add_event_on_level_up(on_level_up)
func on_level_up():
# event handler code here
For "On Damage" and "On Attack" triggers you will also need to define chance parameters. You can see chance parameters on the website in this format:
ONDAMAGE_chance: 0.008 ONDAMAGE_chanceLevelAdd: 0.0015
Add this to the event handler to implement chances:
func on_damage(event: Event):
var tower: Tower = self
if !tower.calc_chance(0.008 + tower.get_level() * 0.0015):
return
Note that the following functions don't need to be added as handlers in load_triggers()
:
init
onTowerDetails
onCreate
Misc
getUnit()
In original API, Tower and Creep classes have getUnit()
method. It returns the unit that these classes wrap. In godot API Tower and Mob(Creep) are subclasses of Unit, so getUnit() is not needed.
Buff power level pecularities
Sometimes in original tower scripts the power level parameter of BuffType's apply() function is used in a strange way. For example, consider this script for the Haunted Rubble tower:
globals
//@export
BuffType velex_slow
endglobals
//The init function
private function init takes nothing returns nothing
local Modifier slow=Modifier.create()
call slow.addModification(MOD_MOVESPEED,0,-0.001)
set velex_slow=BuffType.create(0,0,false) // apply custom timed
call velex_slow.setBuffIcon('@@0@@')
call velex_slow.setBuffModifier(slow)
call velex_slow.setStackingGroup("velex_slow1")
endfunction
function onAttack takes Tower tower returns nothing
local Unit creep = Event.getTarget()
local integer size = creep.getSize()
local boolean calc
if size == SIZE_BOSS then
set calc=tower.calcChance((.15+tower.getLevel()*0.0015)*2/3)
else
set calc=tower.calcChance(.15+tower.getLevel()*0.0015)
endif
if(calc==true) then
call velex_slow.applyCustomTimed(tower,Event.getTarget(),R2I(0.15*1000),5)
endif
endfunction
The effect of this script should be: "When this tower attacks a creep it has a 15% (10% for bosses) chance to slow it by 15% for 5 seconds. Level Bonus: +0.15% (0.1% for bosses) chance"
The important parts here are the calls to addModification() and applyCustomTimed().
addModification()
receives 0
for baseValue
and -0.001
for levelAdd
, which doesn't make sense because tower should slow the target by 15%.
applyCustomTimed()
receives R2I(0.15*1000)
for level
which makes even less sense. You would expect to see tower's level used as an argument here.
Here are all of the reasons:
- Modifier was set using
setBuffModifier()
function which means that modifier and it's modifications will scale by buff's power level. This means that the final value of slow modification will be0 + -0.001 * R2I(0.15*1000) = -0.15
which is the expected -15% slow based on effect description. - Buff power level passed to
applyCustomTimed()
is used to compare buffs when a buff of same time is already active on a mob and we need to determine whether the old or new buff has priority. This is why we can't use tower's level here because in that case Haunted Rubble of level 25 would override the effect of Haunted Debris level 10 even though the slow effect of Haunted Debris is stronger. (Haunted Debris is the second tier in the family of towers that Haunted Rubble belongs to) 0.15
is multiplied by1000
because buff power levels are integers. For the same reason, modification has to use-0.001
for thelevelAdd
, to remove multiplication by1000
.- Modification
levelAdd
is negative to make the final slow value negative. Power level can't be negative because then comparison of buff power levels would be incorrect and buff's that slowed mobs less would be considered stronger.
In youtd2, this "trick" is not necessary because buffs from higher tier towers have priority. Some tower scripts were translated with the trick and others without - both ways are ok.
How to use a single script for all tiers of a tower family
Towers in the same family are arranged into tiers and each tier differs from others by value of it's effects. For example, if towers in a family have a chance to kill a target instantly, then tier 2 will have a higher chance than tier 1. The logic of the script is the same for all towers. In original scripts, each tier had it's own script so code was duplicated with only the numerical values changing.
Here's are example scripts for tier 1 and tier 2 towers:
function onDamage takes Tower tower returns nothing
local Unit creep = Event.getTarget()
local boolean calc = tower.calcChance(0.10)
if (calc == true) then
call tower.killInstantly(creep)
endif
endfunction
function onDamage takes Tower tower returns nothing
local Unit creep = Event.getTarget()
local boolean calc = tower.calcChance(0.20)
if (calc == true) then
call tower.killInstantly(creep)
endif
endfunction
There are no changes between these two scripts except for calcChance(0.10)
changing into calcChance(0.20)
. And this way scripts would be repeated for other tiers.
Here are the above scripts translated into godot API:
func on_damage(event: Event):
var tower = self
var creep: Unit = event.get_target()
var calc: bool = tower.calc_chance(0.1)
if calc == true:
tower.kill_instantly(creep)
func on_damage(event: Event):
var tower = self
var creep: Unit = event.get_target()
var calc: bool = tower.calc_chance(0.2)
if calc == true:
tower.kill_instantly(creep)
And here is the reworked script that can be used by all tiers of the tower family:
func get_tier_stats() -> Dictionary:
return {
1: {instant_kill_chance = 0.10},
2: {instant_kill_chance = 0.20},
3: {instant_kill_chance = 0.30},
4: {instant_kill_chance = 0.40},
5: {instant_kill_chance = 0.50},
}
func on_damage(event: Event):
var tower = self
var creep: Unit = event.get_target()
var calc: bool = tower.calc_chance(_stats.instant_kill_chance)
if calc == true:
tower.kill_instantly(creep)
(Note that code for adding on_damage() event handler was omitted for clarity)
You put the value that is different for each tier into a Dictionary and place it in get_tier_stats()
. That function is called by the base class Tower and stores the stats for current tier in _stats
variable. Back in the tower script, you can access stats for current tier in _stats
. Each tower tier will use the same script but will get it's own version of _stats
.
Missing functions
Some JASS functions weren't ported for various reasons. If you search for a JASS function and fail to find the ported version - then that function wasn't ported
Saving object id's
In tower scripts you sometimes need to save object id's:
set buff.userInt = tower.getUID()
In Godot you need to use the get_instance_id() function instead.
buff.user_int = tower.get_instance_id()
Saving object references
In JASS, it is possible to save and get object references to an int variable, like this:
set buff.userInt = tower
...
local Tower tower = buff.userInt
This is not supported in Godot but you can achieve the same result by saving the object id and getting it later using the instance_from_id() function. Note that you should also check that the resulting object is valid.
buff.user_int = tower.get_instance_id()
...
local Tower tower = instance_from_id(buff.userInt)
if tower == null:
push_error("Tower is null")
return