Hey all, I recently made a test map to try and learn Lua to practice programming (I'm in my second year of computer science in uni). I made a stat tracking system that pretty much completely circumvents the Warcraft 3 stats, but for some reason whenever I cast a spell on a Lich that I created it completely breaks the system. Levelling up or picking up or dropping items no longer change the units stats.
I'm open to criticism for all of the triggers in general because I'm trying my best to learn but I'm having trouble with the lack of resources.
I attached the map below, the Lich spawns after the map spawns so it's stats may be initialized. Sorry about the long triggers if that's a problem!
Above is the trigger that manages the custom stat system.
Above is the spell manager I'm using to deal all spell damage.
Above is all of the Lich's spells.
And above is my knockback system, in case that's needed as well as it's used for one of the spells.
I'm open to criticism for all of the triggers in general because I'm trying my best to learn but I'm having trouble with the lack of resources.
I attached the map below, the Lich spawns after the map spawns so it's stats may be initialized. Sorry about the long triggers if that's a problem!
Lua:
function GetHeroStats()
local t = CreateTimer()
TimerStart(t, 0.2, true, function()
local g = CreateGroup()
local r = GetPlayableMapRect()
GroupEnumUnitsInRect(g, r, nil)
local u = FirstOfGroup(g)
while u ~= nil do
if IsHeroUnitId(GetUnitTypeId(u)) then
print("hps: " .. Hero_Total_Stats[Health_Regeneration][u])
print("mps: " .. Hero_Total_Stats[Mana_Regeneration][u])
SetUnitState(u, UNIT_STATE_LIFE, GetUnitState(u, UNIT_STATE_LIFE) + Hero_Total_Stats[Health_Regeneration][u] * 0.2)
SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA) + Hero_Total_Stats[Mana_Regeneration][u] * 0.2)
end
GroupRemoveUnit(g, u)
u = FirstOfGroup(g)
end
DestroyGroup(g)
end)
end
Hero_Base_Stats = {}
Hero_Bonus_Stats = {}
Hero_Stats_Multiplier = {}
Hero_Total_Stats = {}
Hero_Scaling = {}
Item_Stats = {}
Item_Multipliers = {}
Strength = 1
Agility = 2
Intellect = 3
Damage = 4
Armor = 5
Health = 6
Mana = 7
Attack_Speed = 8
Health_Regeneration = 9
Mana_Regeneration = 10
Movement_Speed = 11
STAT_COUNT = 11
PRIMARIES = 3
function StatsInitialization()
for i = 1, STAT_COUNT, 1 do
Hero_Base_Stats[i] = __jarray(0)
Hero_Bonus_Stats[i] = __jarray(0)
Hero_Stats_Multiplier[i] = __jarray(0)
Hero_Total_Stats[i] = __jarray(0)
Item_Stats[i] = __jarray(0)
Item_Multipliers[i] = __jarray(0)
end
for i = 1, PRIMARIES, 1 do
Hero_Scaling[i] = __jarray(0)
end
ItemInitialization()
HeroInitialization()
HeroLevelUp()
HeroCreated()
UnitGetsItem()
UnitLosesItem()
end
function ItemInitialization()
local i = FourCC('cnob')
Item_Stats[Strength][i] = 2
Item_Stats[Agility][i] = 2
Item_Stats[Intellect][i] = 2
i = FourCC('hcun')
Item_Stats[Agility][i] = 4
Item_Stats[Intellect][i] = 4
i = FourCC('ckng')
Item_Stats[Strength][i] = 175
Item_Stats[Agility][i] = 33
Item_Stats[Intellect][i] = 45
Item_Stats[Attack_Speed][i] = 1.05
Item_Multipliers[Movement_Speed][i] = 0.75
Item_Stats[Damage][i] = 200
Item_Stats[Armor][i] = 10
Item_Multipliers[Health_Regeneration][i] = 5.0
Item_Multipliers[Mana_Regeneration][i] = 5.0
Item_Multipliers[Strength][i] = 0.33
end
Hero_Tier = __jarray(0)
Hero_Previous_Level = {}
Hero_Primary_Stat = {}
function HeroInitialization()
local h = FourCC('H000')
Hero_Base_Stats[Strength][h] = 5.
Hero_Base_Stats[Agility][h] = 4.
Hero_Base_Stats[Intellect][h] = 1.
Hero_Scaling[Strength][h] = 1.
Hero_Scaling[Agility][h] = 0.8
Hero_Scaling[Intellect][h] = 0.2
Hero_Tier[h] = 1
Hero_Primary_Stat[h] = Strength
print("Militia done without bugs")
h = FourCC('Ulic')
Hero_Base_Stats[Strength][h] = 4
Hero_Base_Stats[Agility][h] = 4
Hero_Base_Stats[Intellect][h] = 22
Hero_Scaling[Strength][h] = 0.2
Hero_Scaling[Agility][h] = 0.2
Hero_Scaling[Intellect][h] = 1.6
Hero_Tier[h] = 5
Hero_Primary_Stat[h] = Intellect
end
function HeroLevelUp()
local trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_HERO_LEVEL)
TriggerAddAction(trigger, function()
local u = GetTriggerUnit()
local h = GetUnitTypeId(u)
for i = 1, PRIMARIES, 1 do
Hero_Base_Stats[i][u] = Hero_Base_Stats[i][u] + Hero_Scaling[i][h] * (GetHeroLevel(u) - Hero_Previous_Level[u])
end
Hero_Previous_Level[u] = GetHeroLevel(u)
UpdateStats(h)
end)
end
Hero_Initialized = {}
function HeroCreated()
local trigger = CreateTrigger()
TriggerRegisterEnterRectSimple(trigger, bj_mapInitialPlayableArea)
TriggerAddAction(trigger, function()
local u = GetTriggerUnit()
local h = GetUnitTypeId(u)
if Hero_Initialized[u] == nil and IsHeroUnitId(h) then
for i = 1, PRIMARIES, 1 do
Hero_Base_Stats[i][u] = Hero_Base_Stats[i][h]
Hero_Scaling[i][u] = Hero_Scaling[i][h]
end
Hero_Initialized[u] = true
Hero_Previous_Level[u] = GetHeroLevel(u)
UnitAddAbility(u, FourCC('A00E'))
SetUnitAbilityLevel(u, FourCC('A00E'), GetPlayerId(GetOwningPlayer(u)))
UpdateStats(u)
end
end)
end
function UnitGetsItem()
local trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_PICKUP_ITEM)
TriggerAddAction(trigger, function()
local h = GetTriggerUnit()
local i = GetItemTypeId(GetManipulatedItem())
for x = 1, STAT_COUNT, 1 do
if Item_Stats[x][i] > 0 then
Hero_Bonus_Stats[x][h] = Hero_Bonus_Stats[x][h] + Item_Stats[x][i]
end
if Item_Multipliers[x][i] > 0 then
Hero_Stats_Multiplier[x][h] = Hero_Stats_Multiplier[x][h] + Item_Multipliers[x][i]
end
end
UpdateStats(h)
end)
end
function UnitLosesItem()
local trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_DROP_ITEM)
TriggerAddAction(trigger, function()
local h = GetTriggerUnit()
local i = GetItemTypeId(GetManipulatedItem())
for x = 1, STAT_COUNT, 1 do
if Item_Stats[x][i] > 0 then
Hero_Bonus_Stats[x][h] = Hero_Bonus_Stats[x][h] - Item_Stats[x][i]
end
if Item_Multipliers[x][i] > 0 then
Hero_Stats_Multiplier[x][h] = Hero_Stats_Multiplier[x][h] - Item_Multipliers[x][i]
end
end
UpdateStats(h)
end)
end
function UpdateStats(u)
local h = GetUnitTypeId(u)
for i = 1, PRIMARIES, 1 do
Hero_Total_Stats[i][u] = (Hero_Base_Stats[i][u] + Hero_Bonus_Stats[i][u]) * (Hero_Stats_Multiplier[i][u] + 1.)
end
Hero_Base_Stats[Health][u] = 85 + (15 * Hero_Tier[h]) + (4 * Hero_Total_Stats[Strength][u])
Hero_Base_Stats[Mana][u] = 95 + (5 * Hero_Tier[h]) + (1 * Hero_Total_Stats[Intellect][u])
Hero_Total_Stats[Health][u] = (Hero_Base_Stats[Health][u] + Hero_Bonus_Stats[Health][u]) * (Hero_Stats_Multiplier[Health][u] + 1.)
Hero_Total_Stats[Mana][u] = (Hero_Base_Stats[Mana][u] + Hero_Bonus_Stats[Mana][u]) * (Hero_Stats_Multiplier[Mana][u] + 1.)
if GetUnitAbilityLevel(u, FourCC('A00A')) > 0 then
Hero_Total_Stats[Intellect][u] = Hero_Total_Stats[Intellect][u] + (Hero_Total_Stats[Mana][u] * 0.1)
end
Hero_Base_Stats[Health_Regeneration][u] = 0.25 + (Hero_Total_Stats[Strength][u] * 0.05)
Hero_Base_Stats[Mana_Regeneration][u] = 0.25 + (Hero_Total_Stats[Intellect][u] * 0.03)
Hero_Total_Stats[Health_Regeneration][u] = (Hero_Base_Stats[Health_Regeneration][u] + Hero_Bonus_Stats[Health_Regeneration][u]) * (Hero_Stats_Multiplier[Health_Regeneration][u] + 1.)
Hero_Total_Stats[Mana_Regeneration][u] = (Hero_Base_Stats[Mana_Regeneration][u] + Hero_Bonus_Stats[Mana_Regeneration][u]) * (Hero_Stats_Multiplier[Mana_Regeneration][u] + 1.)
Hero_Base_Stats[Damage][u] = 3 + (0.5 * Hero_Total_Stats[Hero_Primary_Stat[h]][u])
Hero_Total_Stats[Damage][u] = (Hero_Base_Stats[Damage][u] + Hero_Bonus_Stats[Damage][u]) * (Hero_Stats_Multiplier[Damage][u] + 1.)
Hero_Base_Stats[Attack_Speed][u] = 2.0
Hero_Total_Stats[Attack_Speed][u] = Hero_Base_Stats[Attack_Speed][u] / (1. + (Hero_Total_Stats[Attack_Speed][u] * 0.02) + Hero_Bonus_Stats[Attack_Speed][u])
Hero_Base_Stats[Movement_Speed][u] = 300
Hero_Total_Stats[Movement_Speed][u] = (Hero_Base_Stats[Movement_Speed][u] + Hero_Bonus_Stats[Movement_Speed][u]) * (Hero_Stats_Multiplier[Movement_Speed][u] + 1.)
SetHeroStr(u, math.floor(Hero_Total_Stats[Strength][u]), true)
SetHeroAgi(u, math.floor(Hero_Total_Stats[Agility][u]), true)
SetHeroInt(u, math.floor(Hero_Total_Stats[Intellect][u]), true)
BlzSetUnitMaxHP(u, math.floor(Hero_Total_Stats[Health][u]))
BlzSetUnitMaxMana(u, math.floor(Hero_Total_Stats[Mana][u]))
BlzSetUnitBaseDamage(u, math.floor(Hero_Total_Stats[Damage][u]), 0)
BlzSetUnitArmor(u, Hero_Bonus_Stats[Armor][u])
BlzSetUnitAttackCooldown(u, Hero_Total_Stats[Attack_Speed][u], 0)
SetUnitMoveSpeed(u, Hero_Total_Stats[Movement_Speed][u])
end
Above is the trigger that manages the custom stat system.
Lua:
PHYSICAL = 1
FIRE = 2
ICE = 3
NATURE = 4
LIGHT = 5
DARK = 6
ARCANE = 7
HEAL = 8
WHITE = 9
MANA = 10
DAMAGE_TYPE_INSTANT = 1
DAMAGE_TYPE_OVER_TIME = 2
DAMAGE_TYPE_PURE = 3
function SpellDamage(caster, target, ability, damage, damagetype, element, recursive)
Caster = caster
Target = target
Ability = ability
Damage = damage
Damage_Type = damagetype
Element = element
Critical = false
local flatReduction = 0. + SpellFlatReduction()
local damageMultiplier = 1. + SpellMultipliers()
local totalDamage = (damage - flatReduction) * damageMultiplier
Damage = totalDamage
local spellHealing = 0. + SpellManaRestoration()
local manaRestoration = 0. + SpellManaRestoration()
UnitDamageTarget(Caster, Target, totalDamage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, WEAPON_TYPE_WHOKNOWS)
if Critical then
CreateCriticalFloatingText(math.floor(totalDamage + 0.5), GetUnitX(Target), GetUnitY(Target), Element, true)
else
CreateFloatingText(math.floor(totalDamage + 0.5), GetUnitX(Target), GetUnitY(Target), Element, true)
end
Healing = spellHealing
Mana = manaRestoration
if not recursive then
local a = Ability
local d = Damage
local c = Caster
local t = Target
local dt = Damage_Type
local e = Element
if GetUnitAbilityLevel(c, FourCC('A00A')) > 0 and dt ~= DAMAGE_TYPE_OVER_TIME and (e == ICE or e == DARK) then
local t1 = CreateTimer()
TimerStart(t1, 1.5, false, function()
SpellDamage(c, t, a, d * 0.25, DAMAGE_TYPE_INSTANT, e, true)
DestroyEffect(AddSpecialEffect([[Abilities\Spells\Undead\FrostArmor\FrostArmorDamage.mdl]], GetUnitX(t), GetUnitY(t)))
PauseTimer(t1)
DestroyTimer(t1)
end)
end
end
end
function SpellFlatReduction()
local f = 0.
if UnitHasBuffBJ(Target, FourCC("A001")) and Element == FIRE then f = f + 3 end
if Damage_Type == DAMAGE_TYPE_OVER_TIME then return f * 0.33 else return f end
end
function SpellMultipliers()
local m = 0.
if UnitHasBuffBJ(Target, FourCC("BSTN")) and Ability == FourCC("ACfb") then
Critical = true
m = m + 0.5
end
return m
end
function SpellManaRestoration()
local mp = 0.
return mp
end
function SpellHealing()
local hp = 0.
return hp
end
Above is the spell manager I'm using to deal all spell damage.
Lua:
function OrbOfFrost()
local trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
TriggerAddAction(trigger, function()
if GetSpellAbilityId() == FourCC('A002') then
local caster = GetTriggerUnit()
local targetX = GetSpellTargetX()
local targetY = GetSpellTargetY()
local damage = 1. + (GetHeroLevel(caster) * 0.1) + (0.05 * GetHeroInt(caster, true))
local orb = CreateUnit(GetOwningPlayer(caster), FourCC('n000'), targetX, targetY, 0)
local ticks = 0
local damagedealt = __jarray(0)
local t1 = CreateTimer()
TimerStart(t1, 0.15, true, function()
ticks = ticks + 1
local g = CreateGroup()
GroupEnumUnitsInRange(g, GetUnitX(orb), GetUnitY(orb), 310, nil)
local u = FirstOfGroup(g)
while u ~= nil do
if (IsUnitAlly(u, GetOwningPlayer(orb)) == false) and IsUnitAliveBJ(u) then
local i = math.random(1,2)
if i == 1 then
i = 3
else
i = 6
end
SpellDamage(caster, u, GetSpellAbilityId(), damage, DAMAGE_TYPE_OVER_TIME, i, false)
damagedealt[u] = damagedealt[u] + Damage
DestroyEffect(AddSpecialEffect([[Abilities\Spells\Undead\FrostArmor\FrostArmorDamage.mdl]], GetUnitX(u), GetUnitY(u)))
Knockback(u, 2., AngleBetweenCoordinates(GetUnitX(u), GetUnitY(u), GetUnitX(orb), GetUnitY(orb)), nil, 0.04)
end
GroupRemoveUnit(g, u)
u = FirstOfGroup(g)
end
DestroyGroup(g)
if ticks == 33 then --We destroy the timer when ticks reaches 165 (After 5 seconds)
g = CreateGroup()
GroupEnumUnitsInRange(g, GetUnitX(orb), GetUnitY(orb), 310, nil)
u = FirstOfGroup(g)
while u ~= nil do
if (IsUnitAlly(u, GetOwningPlayer(orb)) == false) and IsUnitAliveBJ(u) then
if damagedealt[u] > 0 then
SpellDamage(caster, u, GetSpellAbilityId(), damagedealt[u] * 0.30, DAMAGE_TYPE_INSTANT, ICE, false)
DestroyEffect(AddSpecialEffect([[Abilities\Spells\Undead\FrostArmor\FrostArmorDamage.mdl]], GetUnitX(u), GetUnitY(u)))
Knockback(u, 7., AngleBetweenCoordinates(GetUnitX(orb), GetUnitY(orb), GetUnitX(u), GetUnitY(u)), [[Abilities\Spells\Other\Stampede\MissileDeath.mdl]], 0.3, false)
end
end
GroupRemoveUnit(g, u)
u = FirstOfGroup(g)
end
DestroyGroup(g)
PauseTimer(t1)
DestroyTimer(t1)
KillUnit(orb)
end
end)
end
end)
end
function IceFissure()
local trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
TriggerAddAction(trigger, function()
if GetSpellAbilityId() == FourCC('A005') then
local caster = GetTriggerUnit()
local x = GetSpellTargetX()
local y = GetSpellTargetY()
local damage = 9. + (0.35 * Hero_Total_Stats[Intellect][caster])
local t1 = CreateTimer() --Create our timer
TimerStart(t1, 0.75, false, function()
local g = CreateGroup()
GroupEnumUnitsInRange(g, x, y, 200, nil)
local u = FirstOfGroup(g)
while u ~= nil do
if IsUnitAlly(u, GetOwningPlayer(caster)) == false and IsUnitAliveBJ(u) then
SpellDamage(caster, u, GetSpellAbilityId(), damage, DAMAGE_TYPE_INSTANT, ICE, false)
DestroyEffect(AddSpecialEffect([[Abilities\Spells\Undead\FrostArmor\FrostArmorDamage.mdl]], GetUnitX(u), GetUnitY(u)))
end
GroupRemoveUnit(g, u)
u = FirstOfGroup(g)
end
DestroyGroup(g)
local pillar = CreateUnit(GetOwningPlayer(caster), FourCC('n002'), x, y, 0)
UnitAddAbility(pillar, FourCC('A007'))
UnitAddAbility(pillar, FourCC('A006'))
UnitApplyTimedLife(pillar, FourCC('BTLF'), 4.)
DestroyTimer(t1)
end)
end
end)
end
function GlacialSpike()
local trigger = CreateTrigger()
TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
TriggerAddAction(trigger, function()
if GetSpellAbilityId() == FourCC('A008') then
local caster = GetTriggerUnit()
local x = GetSpellTargetX()
local y = GetSpellTargetY()
local damage = 20. + (0.9 * GetHeroInt(caster, true))
local t1 = CreateTimer()
TimerStart(t1, 2.5, false, function()
DestroyEffect(AddSpecialEffect([[Units\NightElf\Wisp\WispExplode.mdl]], x, y))
DestroyEffect(AddSpecialEffect([[Abilities\Spells\Human\Thunderclap\ThunderClapCaster.mdl]], x, y))
local d = CreateUnit(GetOwningPlayer(caster), FourCC('n002'), x, y, 0)
UnitAddAbility(d, FourCC('A009'))
IssueImmediateOrder(d, 'stomp')
local g = CreateGroup()
GroupEnumUnitsInRange(g, x, y, 200, nil)
local u = FirstOfGroup(g)
while u ~= nil do
if IsUnitAlly(u, GetOwningPlayer(caster)) == false and IsUnitAliveBJ(u) then
SpellDamage(caster, u, GetSpellAbilityId(), damage * 0.5, DAMAGE_TYPE_INSTANT, ICE, false)
SpellDamage(caster, u, GetSpellAbilityId(), damage * 0.5, DAMAGE_TYPE_INSTANT, DARK, false)
DestroyEffect(AddSpecialEffect([[Abilities\Spells\Undead\FrostArmor\FrostArmorDamage.mdl]], GetUnitX(u), GetUnitY(u)))
end
GroupRemoveUnit(g, u)
u = FirstOfGroup(g)
end
DestroyGroup(g)
DestroyTimer(t1)
end)
end
end)
end
Above is all of the Lich's spells.
Lua:
TICK_INTERVAL = 0.03
function Knockback(target, ability, speed, angle, vfx, duration, collision)
local tickcounter = math.floor(duration / TICK_INTERVAL)
local t = target
local spd = speed
local a = angle
local v = vfx
local ab = ability
local tick = 0
local t1 = CreateTimer()
TimerStart(t1, TICK_INTERVAL, true, function()
tick = tick + 1
local x,y = PolarProjection(GetUnitX(t), GetUnitY(t), spd, a)
SetUnitPosition(t, x, y)
if tick % 2 == 0 then
if v ~= nil then
DestroyEffect(AddSpecialEffect(v, GetUnitX(t), GetUnitY(t)))
KnockbackTick(t, ab, collision, vfx)
end
end
if tick >= tickcounter then
KnockbackEnd(t, ab)
PauseTimer(t1)
DestroyTimer(t1)
end
end)
end
function KnockbackTick(target, ability, collision, vfx)
if collision then
local g = CreateGroup()
GroupEnumUnitsInRange(g, GetUnitX(target), GetUnitY(target), 125, nil)
local u = FirstOfGroup(g)
while u ~= nil do
if IsUnitAliveBJ(u) and IsUnitAlly(u, target) then
DestroyEffect(AddSpecialEffect([[vfx]], GetUnitX(u), GetUnitY(u)))
end
GroupRemoveUnit(g, u)
u = FirstOfGroup(g)
end
DestroyGroup(g)
end
end
function KnockbackEnd(target, ability)
end
function AngleBetweenCoordinates(x,y,x2,y2)
return bj_RADTODEG * Atan2(y2- y, x2-x)
end
function PolarProjection(x, y, distance, angle)
local x1 = x + distance * Cos(angle * bj_DEGTORAD)
local y1 = y + distance * Sin(angle * bj_DEGTORAD)
return x1,y1
end
And above is my knockback system, in case that's needed as well as it's used for one of the spells.
Attachments
Last edited: