• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Solved] [LUA] Change ability field for ALL abilities with abilCode

Level 3
Joined
Dec 20, 2017
Messages
13
Hello :) I'm trying to make a custom spell system and need help with a specific part of it.

Goal: I want to be able to create custom spells while touching the object editor as little as possible. I want to be to fill in as many as possible of the fields - the most important are cooldown, mana cost, range, area, duration and cast time - directly in my scripts, so I can create an entire spell without having to change things in the object editor. This is mostly cause maintaining code and updates is a lot easier then. If you have other ideas than my current approach below I would be grateful as well.

Current approach:
What's in the title ;) I want to write a function that can change an ability field for ALL instances of an ability, hopefully there are natives. Even if there aren't any natives for setting those fields just solving problems 1. or 2. below is enough for me:

Lua:
-- Assume ability and stringlevelfield are set previously

BlzSetAbilityStringLevelField(ability, abilitystringfield, 0, "New tooltip" ) --[[ The SetAbilityFields natives are problematic cause:
1. Low performance, this only sets the tooltip for one level AND one archmage at a time. Meaning dozens of calls might be needed per ability.
I could work around 1 by updating all abilities at start where performance is irrelevant. But...
2. Doesn't work before the ability is learned.
I can work around 2. by only updating the fields when the ability is learned for heroes, but this has to be done in real time (see 1. D:) and doesn't work for non-hero units.
]]

BlzSetAbilityExtendedTooltip( 'AHbz', "New tooltip", 0 ) -- exactly what I want for other fields :)

I'm aware of the native functions in the screenshot, they are exactly what I'm looking for but they can't access all I need.
wc3Question.png
PS: I don't use the forum usually. Please tell me if this is in teh wrong spot or have the wrong tags.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,562
You can only modify those fields on a unit's specific ability like this:
  • Adjust Ability
    • Events
    • Conditions
    • Actions
      • Unit - Create 1 Paladin for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
      • Set VariableSet MyUnit = (Last created unit)
      • -------- --------
      • Set VariableSet MyAbility = (Unit: MyUnit's Ability with Ability Code: Holy Light)
      • Set VariableSet MyAbilityCode = Holy Light
      • Set VariableSet MyLevel = ((Level of MyAbilityCode for MyUnit) - 1)
      • -------- ^ Levels are indexed starting at 0 which is why I subtract 1 from it. --------
      • -------- --------
      • -------- You can only modify these values on a unit's specific ability at a specific level: --------
      • Ability - Set Ability: MyAbility's Real Level Field: Casting Time ('acas') of Level: MyLevel to 10.00
      • Ability - Set Ability: MyAbility's Real Level Field: Cooldown ('acdn') of Level: MyLevel to 10.00
      • Ability - Set Ability: MyAbility's Real Level Field: Duration - Normal ('adur') of Level: MyLevel to 10.00
      • Ability - Set Ability: MyAbility's Real Level Field: Duration - Hero ('ahdu') of Level: MyLevel to 10.00
      • Ability - Set Ability: MyAbility's Real Level Field: Cast Range ('aran') of Level: MyLevel to 10.00
      • Ability - Set Ability: MyAbility's Integer Level Field: Mana Cost ('amcs') of Level: MyLevel to 10
But you can make a system that works pretty easily in Lua:
Lua:
Ability = {}

FlameStrike = {
  ManaCost = { 50, 100, 200 },
  Cooldown = { 10.0, 5.0, 1.0 },
  CastingTime = { 0.9, 0.9, 0.9 },
  DurationNormal = { 3.0, 4.0, 5.0 },
  DurationHero = { 3.0, 4.0, 5.0 },
  CastRange = { 500.0, 1000.0, 2000.0 }
}

function InitAbilities()
  -- Map the abilities to the Ability table
  Ability[FourCC("AHfs")] = FlameStrike
end

-- Takes a unit and an ability id
function UpdateUnitAbilityFields(u, id)
  local ability = BlzGetUnitAbility(u, id)
  local lvl = GetUnitAbilityLevel(u, id)
  local lvlIndexed = lvl - 1
  BlzSetAbilityIntegerLevelField( ability, ABILITY_ILF_MANA_COST, lvlIndexed, Ability[id].ManaCost[lvl] )
  BlzSetAbilityRealLevelField( ability, ABILITY_RLF_COOLDOWN, lvlIndexed, Ability[id].Cooldown[lvl] )
  BlzSetAbilityRealLevelField( ability, ABILITY_RLF_CASTING_TIME, lvlIndexed, Ability[id].CastingTime[lvl] )
  BlzSetAbilityRealLevelField( ability, ABILITY_RLF_DURATION_NORMAL, lvlIndexed, Ability[id].DurationNormal[lvl] )
  BlzSetAbilityRealLevelField( ability, ABILITY_RLF_DURATION_HERO, lvlIndexed, Ability[id].DurationHero[lvl] )
  BlzSetAbilityRealLevelField( ability, ABILITY_RLF_CAST_RANGE, lvlIndexed, Ability[id].CastRange[lvl] )
end

When the hero learns a skill you can update the fields like so:
  • Learn Skill
    • Events
      • Unit - A unit Learns a skill
    • Conditions
      • (Learned Hero Skill) Equal to Flame Strike
    • Actions
      • Custom script: UpdateUnitAbilityFields( GetTriggerUnit(), GetLearnedSkill() )
Also, there won't be any noticeable performance hits, this is very simple stuff. It's not like a Hero is learning skills 1000 times per second or something.
 
Last edited:
Level 3
Joined
Dec 20, 2017
Messages
13
Thanks for the great response! Although I feel mean for making you write so much code. :)
You can only modify those fields on a unit's specific ability like this:
I was afraid of that D: But at least I won't waste more time trying to find another way
Also, there won't be any noticeable performance hits, this is very simple stuff. It's not like a Hero is learning skills 1000 times per second or something.
I'm a bit hesitant. It it should be able to run with other systems too without causing lag since I want to submit it to the resources and spells section.

Example: Lets say we're using Blizzard with 10 levels and 12 fields causes a ~112 ms lag spike upon any unit first learning it - WORST* case, in multiplayer, using D_UV() below (your code gives the same speed per BlzSet...() call when I tried it); reasonable best case is ~17 ms.
*Best and worst case depends on how many units each player has selected. So if another player has 12 units selected when I level my blizzard, that player will also get a 110 ms lag spike, or 17 ms if only 1 unit selected.

My actual question in this post: I've got two solutions (2nd one easier to write but worse performance) and the performance expected of resources will decide which one. Is the performance for 2. reasonable for a resource, or should I go with 1. despite the cons?

1. Your solution, which sets fields for one level of a time each time an ability is leveled.
Performance: Will update blizzard in about 11 ms since it only has to do one level at a time, but done each time an ability is leveled (this HAS to be totally fine from a performance perspective even with other systems, right?)
Cons: Harder to integrate into my system. I'll have to add extra stuff to the API, for example a new setAbilityLevel() function (since A unit Learns a skill doesn't trigger on the native one) to make sure the fields of the new level is actually updated.

2. Setting all levels of the ability when it's first learned.
Performance: This gives the 110 ms spike. But it is only once, when players have many units selected AND a lot of fields updated. Is the performance too bad to be submitted?
Pros: Easier to write and requires less API functions.
Cons: Worse performance

Lua:
D_UVTime = 0 --Irrelevant for this test
D_UVRuns = 0 --Irrelevant for this test
-- Update values
function D_UV(ability)
    local maxLevel = BlzGetAbilityIntegerField(ability, ABILITY_IF_LEVELS )
    local t1 = PRINTPERFORMANCE and os.clock()
    for i = 0, maxLevel-1 do
        BlzSetAbilityIntegerLevelField(ability, ConvertAbilityIntegerLevelField(FourCC('Hbz1')), i, i )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('Hbz2')), i, 1000 )
        BlzSetAbilityIntegerLevelField(ability, ConvertAbilityIntegerLevelField(FourCC('Hbz3')), i, i )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('Hbz4')), i, 1 )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('Hbz5')), i, 0 )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('Hbz6')), i, 4+i )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('aare')), i, 200+50*i )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('aran')), i, 300+50*i )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('acas')), i, 1 )
        BlzSetAbilityRealLevelField(ability, ConvertAbilityRealLevelField(FourCC('acdn')), i, 5 )
        BlzSetAbilityIntegerLevelField(ability, ConvertAbilityIntegerLevelField(FourCC('amcs')), i, 50*i )
        BlzSetAbilityStringLevelField(ability, ConvertAbilityStringLevelField(FourCC('atp1')), i, "Blizzard lvl " .. i+1)
    end
    if PRINTPERFORMANCE then
        local t2 = os.clock()
        D_UVTime = D_UVTime + (t2 - t1) --Irrelevant for this test
        D_UVRuns = D_UVRuns + 1 --Irrelevant for this test
        print("DeltaTime: ", t2 - t1)
    end
end

The details of the performance test. I'll put it in a spoiler so you don't have to actually read all the details if you aren't interested. If you are, I can post the map so you can test map with the entire system, or a smaller one with just your code. (should I post this in another thread in case others are interested?):
I haven't tested in enough detail to know for sure if this is true for all BlzSet...(). But the results SEEM identical for BlzSetAbilityRealLevelField, BlzSetAbilityStringLevelField and BlzSetAbilityIntegerLevelField.
These BlzSet...() results in multiplaye with a total of 165 BlzSet...() calls each time. The times given are for me. Tested through D_UV, but consistent enough for me when using other functions as well.
  • The time taken to call BlzSet...() is individual for different players (although maybe it's cause I use async os.clock(), no idea if the time is the same if I avoid calling it) in the same game. My friend's results were 50-70% faster than me depending on test-
  • Performance seems independent of: Number of ability levels (for the BlzSet...LevelField() ), instances of the abilCode and which BlzSet...() is used.
    • #ability levels
    • #abilities sharing the abilCode
    • which BlzSet() is used (maybe there's a small difference)
  • Performance for each player depends on how many units are selected by that player (so if 2 players have a different number of units selected, their performance will be different).
  • Worst Case: ~1ms each, 12 units selected.
  • Good cases (how many times faster than Worst Case):
    • 0 units selected: 100*WC - 200*WC
    • 1 unit selected: 6*WC
    • Any number of units selected BUT player has hero skill learning menu open: at least 6*WC, ~12*WC when I just tried with 120 calls in single player.
      • It won't be any faster for players who don't have the skill menu open, but if they do it will be (only tested using archmage for both players).
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,562
Those are some interesting test results, I wonder if there's any gain by "preloading" the abilities first.

Anyway, based on my own personal experience, I've never noticed any spikes in performance when modifying those fields but I don't think I've ever updated that many at once. The framerate in my map and most other maps goes down because there are 200 units on the screen at once and 1000 damage events firing per second, not because someone is leveling up a skill which runs some fairly simple code. But I understand the desire to do things the best way possible.
 
Level 3
Joined
Dec 20, 2017
Messages
13
It makes sense that you haven't noticed anything, after all the solution you provided that only updates one level at a time should never run into performance issues, it's just when you want to update 10+ levels you get into problems. I'll just update all levels and leave a short warning about using it to update abilities with many levels! Thanks uncle.
 
Top