[Lua] Manipulating Abilities (item abilities)

Level 3
Joined
Nov 4, 2008
Messages
22
Hey, thanks for taking the time to read my thread and trying to help out.

FYI: I am quite new to Lua and map development (Huge thanks to this incredible guide!).

So I would like to create a simple item system, in which I use code as much as possible, and rely on Object Editor as little as possible.
I was able to figure out how to have only one item created in Object Editor, and then manipulate that item ingame via code (changing name, description, icon, adding/removing abilities etc). That makes things very dynamic and simple.
My issue is with the abilities of the item. To avoid creating an item ability for each stat, I want to create one (or a few if required) item ability and manipulate it ingame via code (same as item logic).
The thing is, it seems like what I do doesn't do anything.
For our example, I created a dummy item (based of "Ring of Protection +2", "rde1"), and a dummy item ability (based of "Item Hero Stat Bonus (+10 Agility)", "AIaz") to have something to work with.
The code:
Lua:
function test()
    unit = CreateUnit(Player(0), FourCC("Hpal"), -550, -850, 0) -- Hero that will pickup the item that will be manipulated.
    local item = CreateItem(FourCC("I000"), -550, -850) -- Item that will be picked up and manipulated.
end

function triggerItem()
    
    local trig = CreateTrigger()

    TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_PICKUP_ITEM) -- Trigger that will fire when the hero picks up the item.

    TriggerAddAction(trig, function () 
        itemManipulation(GetManipulatedItem()) -- Function that will run to manipulate the item after it was picked. FYI: manipulating some of the item fields seems to only work if the item is inside a hero inventory, this is why I am working this way.
    end)
end

function itemManipulation(item)
    BlzSetItemName(item, "new name") -- Works
    BlzSetItemDescription(item, "new description") -- Works
    BlzSetItemExtendedTooltip(item, "new extended tooltip") -- Works
    BlzItemRemoveAbility(item, FourCC("AId2")) -- Works. Removing item ability (+2 armor).
    BlzItemAddAbility(item, FourCC("AIt6")) -- Works. Adding item ability (+6 damage bonus).
    BlzSetItemIconPath(item, "ReplaceableTextures\\CommandButtons\\BTNLionHorn.blp") -- Works.
    BlzItemAddAbility(item, FourCC("A000")) -- Works. Adding item ability (+10 agility).
    local itemAbility = BlzGetItemAbility(item, FourCC("A000")) -- Seems to work. This will be used for the next function, so it's required to get it.
    BlzSetAbilityIntegerLevelField(itemAbility, ABILITY_ILF_AGILITY_BONUS, 0, 44) -- Doesn't seems to work or do anything. Trying to change the new dummy stat ability ("A000") from 10 agility to 44 agility.
end

OnInit.final(function()
    test()
    triggerItem()
end)

So what I also tried, is refreshing the ability on the item in a few different ways, but none worked.
Lua:
-- After manipulating the ability (BlzSetAbilityIntegerLevelField line).
BlzItemRemoveAbility(item, FourCC("A000"))
BlzItemAddAbility(item, FourCC("A000"))
-- The ability still gives +10 agility. nothing changed.

What I also tried is; before adding this ability item to the manipulated item, adding the ability to a different dummy item, manipulating the ability stat on that item, and only then adding it to the original manipulated item, but the ability stat still gives +10 instead of +44.
I also tried (after the item ability stat manipulation) to remove the item from the hero and giving it a new one, but that didn't help either.

Not sure what I am doing here wrong, or perhaps it's just buggy, but I would appreciate getting some help on this.
If we can't figure this one, I will have to resort to creating item ability in Object Editor for each stat, which is a big overhead..

The second reason it is important for me, is because I plan to create ability system and dynamically changing the abilities will be amazing, as I don't want to have to create all the abilities and their stats in Object Editor.
One more thing regarding changing abilities dynamically ingame (via code), I would also like to change the ability hotkey (not related to item ability).
I read that people are simply creating the same ability with different hotkey multiple times, and then dynamically ingame (via code) assigning the needed ability. It sounds like it will work fine, but it's also a big overhead. I just couldn't find any documentation on how to change hotkey through code ingame. I even tried to guess native functions (BlzSetAbilityCharacterLevelField, since hotkey is a character field.. But yeah that failed :D) based on str/int native functions (e.g BlzSetAbilityIntegerLevelField).

Any help will be much appreciated! :)
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
1) To refresh an ability, particularly a passive one, you have to do this:
Lua:
    IncUnitAbilityLevel(whichUnit, abilcode)
    DecUnitAbilityLevel(whichUnit, abilcode)
Removing the ability would undo the changes. But I have no idea if this works on Items.

2) You cannot change Hotkeys at runtime. But if your abilities are all triggered then you can rely on multiple copies of the Channel ability to get around this. What you would do is create a version of Channel for each Hotkey, so one for A-Z. Then manipulate the Channel ability data fields at runtime to modify how the ability works. Channel is the only ability in the game that offers things like Targeting Options as a data field, which can change it's targeting mechanics (No Target, Point, Unit, Both), and is part of the reason as to why this works.

3) I recall issues with changing Ability Icons, which would likely be necessary for my suggested Channel solution. I think you'll need to use GetLocalPlayer() if your map is multiplayer because the Icon change is global and will affect other players.

Note that there could be easier alternatives, these are just things I've done in the past which worked for me.
 
Last edited:
Level 3
Joined
Nov 4, 2008
Messages
22
1) To refresh an ability, particularly a passive one, you have to do this:
Lua:Copy
IncUnitAbilityLevel(whichUnit, abilcode)
DecUnitAbilityLevel(whichUnit, abilcode)
Removing the ability would undo the changes. I have no idea if this works on Items.
I couldn't make it to work. I even changed the ability fields: item ability = false and hero ability = true, but I still can't change the stats.
Lua:
function abilityManipulation()
    UnitAddAbility(unit, FourCC("A000")) -- Adding the passive ability to the unit: Works
    unitAbility = BlzGetUnitAbility(unit, FourCC("A000"))
    BlzSetAbilityIntegerLevelField(unitAbility, ABILITY_ILF_INTELLIGENCE_BONUS, 0, 33) -- Changing intelligence stat: Theoretically works. I am not getting an error at least. I know this because I also ran it during game time using the -console (debug resource), and when I change the level value from 0 to 1 I am receiving false, but when I keep it on 0 it's seems fine.
    IncUnitAbilityLevel(unit, FourCC("A000")) -- Works, tried it in game as well.
    DecUnitAbilityLevel(unit, FourCC("A000")) -- Works, tried it in game as well.
end
After doing that, the value of intelligence doesn't change. Tried it with AGI stat too. I am not receiving any errors, just the values don't change.
Oh, I also tried changing the mana cost field on Holy Light ability that the paladin hero already has, and the mana value does change without even 'refreshing'. So this could be related to passive ability or stats specifically.
Sadly I am honestly quite close to just giving up on the idea and create ability in Object Editor for each stat, even though it sucks.. I appreciate your effort on this though.
2) You cannot change Hotkeys. If your abilities are all triggered then you can rely on multiple copies of the Channel ability to get around this. What you would do is create a version of Channel for each Hotkey, so one for A-Z. Then manipulate the Channel ability data fields at runtime to modify how the ability works. This works because Channel is the only ability in the game that offers things like Targeting Options as a data field which changes it's targeting mechanics -> (No Target, Point, Unit, Both), etc.
3) I recall issues with changing Ability Icons, which would likely be necessary for my suggested Channel solution. I think you'll need to use GetLocalPlayer() if your map is multiplayer because the Icon change is global and will affect other players
I wonder if that would work for me, because I would need to adjust the hero's ability as well, and know what ability he is trying to use..
I will get into it more once I finish with the item, and hopefully I can make it to work with your instructions.
Also I read somewhere (can't remember where exactly) that using GetLocalPlayer() function is very risky since it usually causes desyncs in multiplayer maps..
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
It's safe to assume that those functions only affect abilities that the Unit has and not abilities that the unit's Items have. But I recall years ago someone was messing with this and got it working, but my memory is foggy. Perhaps you could find the thread.

Anyway, GetLocalPlayer() is a working solution when used properly. The risk comes from the assumption that the code you're using with it can run locally. Just don't assume anything and ensure that what you're doing is safe.

For example, creating a Unit locally will desync the game because a Footman cannot exist on Player 1's client but not on Player 2's client. "You're somehow being attacked by a Unit that doesn't exist -> Game crashes". However, adjusting something like UI locally, like the Icon of an Ability, is safe since it has zero gameplay related consequences for Player 2's client.
 
Last edited:
Level 3
Joined
Nov 4, 2008
Messages
22
Well I did add that passive stat ability to the unit and then tried to manipulate it as it is a regular ability. Anyways, I will try to play around with it a bit more and also look for the thread you mentioned.

Your example makes it quite clear on how to use GetLocalPlayer() properly and avoid the potential risks.

Thanks again for helping me out quite a lot actually!
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
Well I did add that passive stat ability to the unit and then tried to manipulate it as it is a regular ability. Anyways, I will try to play around with it a bit more and also look for the thread you mentioned.

Your example makes it quite clear on how to use GetLocalPlayer() properly and avoid the potential risks.

Thanks again for helping me out quite a lot actually!
This worked fine for me, using the hero Attribute Bonus ability:
Lua:
function abilityManipulation(u, v)
    UnitAddAbility(u, FourCC("A000"))
    local abil = BlzGetUnitAbility(u, FourCC("A000"))
    BlzSetAbilityIntegerLevelField(abil, ABILITY_ILF_INTELLIGENCE_BONUS, 0, v)
    IncUnitAbilityLevel(u, FourCC("A000"))
    DecUnitAbilityLevel(u, FourCC("A000"))
end
  • Test
    • Events
      • Time - Elapsed game time is 0.01 seconds
    • Conditions
    • Actions
      • Unit - Create 1 Paladin for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
      • Selection - Select (Last created unit) for Player 1 (Red)
      • Custom script: local u = GetLastCreatedUnit()
      • Wait 2.00 seconds
      • Custom script: abilityManipulation(u, 5)
      • Wait 2.00 seconds
      • Custom script: abilityManipulation(u, 10)
      • Wait 2.00 seconds
      • Custom script: abilityManipulation(u, 15)
      • Wait 2.00 seconds
      • Custom script: abilityManipulation(u, 20)
      • Wait 2.00 seconds
      • Custom script: abilityManipulation(u, 25)
 

Attachments

  • Demo1.w3m
    16.6 KB · Views: 1
Level 3
Joined
Nov 4, 2008
Messages
22
@Uncle Oh man you're absolutely amazing! A magician!
Works perfectly. Even with items!!
Lua:
function itemAbilityManipulation(unit, item)
    BlzItemAddAbility(item, FourCC("A001"))
    itemAbility = BlzGetItemAbility(item, FourCC("A001"))
    BlzSetAbilityIntegerLevelField(itemAbility, ABILITY_ILF_INTELLIGENCE_BONUS, 0, 77)
    IncUnitAbilityLevel(unit, FourCC("A001")) -- I am not sure why unit native works for the item ability reset, but I suppose the item ability is attached to the hero, possibly because the ability is defined as 'Hero Ability' too, but could also be that this is the logic for every item ability..
    DecUnitAbilityLevel(unit, FourCC("A001"))
end

*I did notice that the ability manipulation does reset (the +77 is gone) if the item is dropped, but that shouldn't be an issue because I will do this change on every item pickup, so that should be seamless to the user.

Woah man, huge props for solving this!:peasant-bowing:
 
Level 45
Joined
Feb 27, 2007
Messages
5,578
Some additional information:

The HP/MP bonus abilities don’t update properly when you change their level but when you remove them they do remove the ‘correct’ amount they should have added based on the level they are. Potentially something like that could be at play here for the stat bonus abilities.

You also didn’t specify if you tried using index 1 for the bonus, since sometime there is inconsistency about which is used where. Seems unlikely here since the full attribute bonus ability does use 0 in Uncle’s code above.
 
Level 3
Joined
Nov 4, 2008
Messages
22
@Pyrogasm I wasn't sure what you meant by that, but now that I have started to mess around with the HP/MP abilities bonuses, I noticed that changing the level doesn't update the values I set, and disabling/enabling doesn't either, but when I remove the ability it actually removes the value I previously set, even though it didn't add it.
I also tried adding the ability to a dummy unit, making the changes and then adding the ability a new unit, doesn't add the ability with the new value but rather with the old default value. But again, if I remove the ability, it does remove the new value I set.
Code example of the explanation above:
Lua:
    -- Assume I have a unit with 500HP.
    BlzItemAddAbility(item, FourCC("A009")) -- Adds MAX HP ability with default value of 1HP. Unit HP is now 501.
    itemAbility2 = BlzGetItemAbility(item, FourCC("A009"))
    BlzSetAbilityIntegerLevelField(itemAbility2, ABILITY_ILF_MAX_LIFE_GAINED, 0, 11) -- Theoretically changes the value of the max hp ability from 1 to 11. Unit HP is still 501.
    BlzUnitDisableAbility(unit, FourCC("A009"), true, false) -- Doesn't seem to do anything. Unit HP is still 501.
    BlzUnitDisableAbility(unit, FourCC("A009"), false, false) -- Doesn't seem to do anything. Unit HP is still 501.
    IncUnitAbilityLevel(unit, FourCC("A009")) -- Ability level is increased by 1. Unit HP is still 501.
    DecUnitAbilityLevel(unit, FourCC("A009")) -- Ability level is decreased by 1. Unit HP is still 501.
    BlzItemRemoveAbility(item, FourCC("A009")) -- Removes the ability. Unit HP is now 490.
Any ideas on how to deal with that?

I am already working with the following abilities with no issues at all (some are hero abilities and some are actually item abilities, either way all of them are being added to an item, not a unit):
Flat Damage, Armor, Stats(str,agi,int).

Aside from that, I have been trying to use "Item Life Bonus" ("AIlz") and "Item Mana Bonus" ("AIbm") for the HP/MP flat bonuses (and having issues with it). Are there any alternative abilities (none aura/buff)?
Regarding changing the unit's HP/MP through triggers - I prefer to avoid doing that, as it would require a secondary system to implement and manage.
 
Last edited:

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,866
This likely has all of the abilities you'd ever want to manipulate to modify a unit's stats:
 
Level 3
Joined
Nov 4, 2008
Messages
22
This likely has all of the abilities you'd ever want to manipulate to modify a unit's stats:
It looks like it's using the same abilities I was using, but some of mine don't update properly.
I suppose I will have to review the entire system and all it's dependencies to hopefully understand why/how that system works and figure out why mine doesn't.
I got a bit lost from first glance, so it will definitely take some time, but I guess it will be a good learning lesson.
I will copy all the script files to VScode and start from there.
Thanks!
 
Level 45
Joined
Feb 27, 2007
Messages
5,578
The method used to properly manipulate hp/mp is to have a bunch of different hp/mp abilities all with 2 levels. Level 1 they should add 0 hp and at level 2 they should add negative health in powers of 10 or powers of 2 (1, 10, 100, etc. or 1, 2, 4, 8, 16, etc.). Thus when the ability is added it does nothing, but when it's subsequently leveled up to 2 and then removed it adds back the hp it thinks it took away. Then you just break down the value you want to add into the appropriate powers of 2 or 10 and add/level/remove the correct abilities to add that much hp/mp total. You do the same with positive health added abilities in order to remove health.

Such things gets funky when you attempt to allow % max hp bonuses, since those are 'retroactive' and need to know when HP is gained for some other reason... which is very difficult.

NewBonus likely uses this method, as is the spiritual successor to BonusMod which explicitly did.
 
Level 19
Joined
Oct 17, 2012
Messages
859
The method used to properly manipulate hp/mp is to have a bunch of different hp/mp abilities all with 2 levels. Level 1 they should add 0 hp and at level 2 they should add negative health in powers of 10 or powers of 2 (1, 10, 100, etc. or 1, 2, 4, 8, 16, etc.). Thus when the ability is added it does nothing, but when it's subsequently leveled up to 2 and then removed it adds back the hp it thinks it took away. Then you just break down the value you want to add into the appropriate powers of 2 or 10 and add/level/remove the correct abilities to add that much hp/mp total. You do the same with positive health added abilities in order to remove health.

Such things gets funky when you attempt to allow % max hp bonuses, since those are 'retroactive' and need to know when HP is gained for some other reason... which is very difficult.

NewBonus likely uses this method, as is the spiritual successor to BonusMod which explicitly did.
NewBonus uses a much simpler method via the new native functions.
 
Level 3
Joined
Nov 4, 2008
Messages
22
Thanks everyone.
After reviewing some of the code, base on my own beginner level-naive understanding of the NewBonus code, the system is indeed utilizing HP/MP natives to properly set/adjust the unit's HP, and not only using the HP/MP ability, unlike other simpler stat (like DAMAGE or ARMOR) that is only using the ability logic.

After reviewing the library and having a grasp of how in-depth and complex (in my eyes) the logic is, I have decided to instead of trying to completely re-create the wheel for every single usage regarding stats manipulation (for my own educational purposes), I will use this amazing system, and adjust it to my needs and learn by doing that.

*I did notice a few dozen places in the library that has high risk of creating desyncs (based on this amazing Lua guide), so I think I will try to refactor the code a bit to eliminate that.

I have to say that this system is just incredible.
 
Last edited:
Top