• 🏆 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!

[JASS] SetHeroAbilityLevel

Status
Not open for further replies.
Level 25
Joined
Feb 2, 2006
Messages
1,689
Hi,
I want to write a small system which allows me to change the level of any hero ability I want to. I use the Tome of Retraining ability for this since I've read that this is the only way to unlearn hero abilities. All ability cooldowns need to be stored and reset. Otherwise, the tome ability won't work for all hero abilities. I store all ability levels and start to reskill giving the one hero ability the highest priority.

My problem is that the tome ability seems to have some delay when I issue the immediate order. Hence, I need to create and start a timer and save all data. This requires some guard to prevent multiple concurrent calls. Is there any way to avoid this delay? Or maybe a better solution to reskilling hero abilities?

This is my current system:
JASS:
/**
 * Library which allows setting the exact level of a hero skill.
 */
library HeroAbilityLevel

globals
    private integer ABILITY_ID_UNLEARN = 'A00U'
    private integer ABILITY_ORDER_ID_UNLEARN = 852471
    private hashtable whichHashTable = InitHashtable()
    private hashtable timerHashTable = InitHashtable()
endglobals

private function PrintMsg takes string msg returns nothing
    call DisplayTextToForce(GetPlayersAll(), msg)
endfunction

function RegisterHeroTypeForAbilityLevel takes integer unitTypeId, integer abilityId0, integer abilityId1, integer abilityId2, integer abilityId3, integer abilityId4 returns nothing
    call SaveInteger(whichHashTable, unitTypeId, 0, abilityId0)
    call SaveInteger(whichHashTable, unitTypeId, 1, abilityId1)
    call SaveInteger(whichHashTable, unitTypeId, 2, abilityId2)
    call SaveInteger(whichHashTable, unitTypeId, 3, abilityId3)
    call SaveInteger(whichHashTable, unitTypeId, 4, abilityId4)
endfunction

function SupportsHeroTypeForAbilityLevel takes integer unitTypeId returns boolean
    return HaveSavedInteger(whichHashTable, unitTypeId, 0)
endfunction

private function GetHeroSkillLevel takes unit hero, integer abilityId returns integer
    if (abilityId == 0) then
        return 0
    endif
    return GetUnitAbilityLevelSwapped(abilityId, hero)
endfunction

private function SelectHeroSkillUntilLevel takes unit hero, integer abilityId, integer originalLevel, integer toBeSkilledAbilityId, integer toBeSkilledLevel, real cooldownPercentage returns nothing
    local integer i
    if (abilityId != 0) then
        //call PrintMsg("Skilling hero skill " + GetObjectName(abilityId) + " to level " + I2S(toBeSkilledLevel) + " with cooldown percentage " + R2S(cooldownPercentage))
        set i = 0
        loop
            exitwhen ((i == originalLevel) or (abilityId == toBeSkilledAbilityId and i == toBeSkilledLevel))
            call SelectHeroSkill(hero, abilityId)
            set i = i + 1
        endloop
        //call PrintMsg("Setting cooldown to " + R2S(BlzGetUnitAbilityCooldown(hero, abilityId, i - 1) * cooldownPercentage))
        call BlzStartUnitAbilityCooldown(hero, abilityId, BlzGetUnitAbilityCooldown(hero, abilityId, i - 1) * cooldownPercentage)
    endif
endfunction

private function TimerFunctionReskill takes nothing returns nothing
    local integer timerHandleId = GetHandleId(GetExpiredTimer())
    local unit hero = LoadUnitHandle(timerHashTable, timerHandleId, 0)
    local integer abilityId0 = LoadInteger(timerHashTable, timerHandleId, 1)
    local integer abilityId1 = LoadInteger(timerHashTable, timerHandleId, 2)
    local integer abilityId2 = LoadInteger(timerHashTable, timerHandleId, 3)
    local integer abilityId3 = LoadInteger(timerHashTable, timerHandleId, 4)
    local integer abilityId4 = LoadInteger(timerHashTable, timerHandleId, 5)
    local integer abilityLevel0 = LoadInteger(timerHashTable, timerHandleId, 6)
    local integer abilityLevel1 = LoadInteger(timerHashTable, timerHandleId, 7)
    local integer abilityLevel2 = LoadInteger(timerHashTable, timerHandleId, 8)
    local integer abilityLevel3 = LoadInteger(timerHashTable, timerHandleId, 9)
    local integer abilityLevel4 = LoadInteger(timerHashTable, timerHandleId, 10)
    local real abilityCooldownPercentage0 = LoadReal(timerHashTable, timerHandleId, 11)
    local real abilityCooldownPercentage1 = LoadReal(timerHashTable, timerHandleId, 12)
    local real abilityCooldownPercentage2 = LoadReal(timerHashTable, timerHandleId, 13)
    local real abilityCooldownPercentage3 = LoadReal(timerHashTable, timerHandleId, 14)
    local real abilityCooldownPercentage4 = LoadReal(timerHashTable, timerHandleId, 15)
    local integer abilityId = LoadInteger(timerHashTable, timerHandleId, 16)
    local integer level = LoadInteger(timerHashTable, timerHandleId, 17)

    // TODO the to be skilled ability has the highest priority in skilling?
    if (abilityId0 == abilityId) then
        call SelectHeroSkillUntilLevel(hero, abilityId0, abilityLevel0, abilityId, level, abilityCooldownPercentage0)
    elseif (abilityId1 == abilityId) then
         call SelectHeroSkillUntilLevel(hero, abilityId1, abilityLevel1, abilityId, level, abilityCooldownPercentage1)
    elseif (abilityId2 == abilityId) then
         call SelectHeroSkillUntilLevel(hero, abilityId2, abilityLevel2, abilityId, level, abilityCooldownPercentage2)
    elseif (abilityId3 == abilityId) then
         call SelectHeroSkillUntilLevel(hero, abilityId3, abilityLevel3, abilityId, level, abilityCooldownPercentage3)
    elseif (abilityId4 == abilityId) then
         call SelectHeroSkillUntilLevel(hero, abilityId4, abilityLevel4, abilityId, level, abilityCooldownPercentage4)
    endif

    if (abilityId0 != abilityId) then
        call SelectHeroSkillUntilLevel(hero, abilityId0, abilityLevel0, abilityId, level, abilityCooldownPercentage0)
    endif

    if (abilityId1 != abilityId) then
        call SelectHeroSkillUntilLevel(hero, abilityId1, abilityLevel1, abilityId, level, abilityCooldownPercentage1)
    endif

    if (abilityId2 != abilityId) then
        call SelectHeroSkillUntilLevel(hero, abilityId2, abilityLevel2, abilityId, level, abilityCooldownPercentage2)
    endif

    if (abilityId3 != abilityId) then
        call SelectHeroSkillUntilLevel(hero, abilityId3, abilityLevel3, abilityId, level, abilityCooldownPercentage3)
    endif

    if (abilityId4 != abilityId) then
        call SelectHeroSkillUntilLevel(hero, abilityId4, abilityLevel4, abilityId, level, abilityCooldownPercentage4)
    endif

    call UnitRemoveAbility(hero, ABILITY_ID_UNLEARN)

    call SaveBoolean(whichHashTable, GetHandleId(hero), 0, true)
    call PauseTimer(GetExpiredTimer())
    call FlushChildHashtable(timerHashTable, timerHandleId)
    call DestroyTimer(GetExpiredTimer())
endfunction

private function SetHeroAbilityLevelEx takes unit hero, integer abilityId, integer level returns nothing
    local timer whichTimer = CreateTimer()
    local integer timerHandleId = GetHandleId(whichTimer)
    local integer abilityId0 = LoadInteger(whichHashTable, GetUnitTypeId(hero), 0)
    local integer abilityId1 = LoadInteger(whichHashTable, GetUnitTypeId(hero), 1)
    local integer abilityId2 = LoadInteger(whichHashTable, GetUnitTypeId(hero), 2)
    local integer abilityId3 = LoadInteger(whichHashTable, GetUnitTypeId(hero), 3)
    local integer abilityId4 = LoadInteger(whichHashTable, GetUnitTypeId(hero), 4)
    local integer abilityLevel0 = GetHeroSkillLevel(hero, abilityId0)
    local integer abilityLevel1 = GetHeroSkillLevel(hero, abilityId1)
    local integer abilityLevel2 = GetHeroSkillLevel(hero, abilityId2)
    local integer abilityLevel3 = GetHeroSkillLevel(hero, abilityId3)
    local integer abilityLevel4 = GetHeroSkillLevel(hero, abilityId4)
    local real abilityCooldownPercentage0 = 0.0
    local real abilityCooldownPercentage1 = 0.0
    local real abilityCooldownPercentage2 = 0.0
    local real abilityCooldownPercentage3 = 0.0
    local real abilityCooldownPercentage4 = 0.0

    if (abilityId0 != 0 and BlzGetUnitAbilityCooldown(hero, abilityId0, abilityLevel0) > 0.0) then
        set abilityCooldownPercentage0 = BlzGetUnitAbilityCooldownRemaining(hero, abilityId0) / BlzGetUnitAbilityCooldown(hero, abilityId0, abilityLevel0)
    endif

    if (abilityId1 != 0 and BlzGetUnitAbilityCooldown(hero, abilityId1, abilityLevel1) > 0.0) then
        set abilityCooldownPercentage1 = BlzGetUnitAbilityCooldownRemaining(hero, abilityId1) / BlzGetUnitAbilityCooldown(hero, abilityId1, abilityLevel1)
    endif

    if (abilityId2 != 0 and BlzGetUnitAbilityCooldown(hero, abilityId2, abilityLevel2) > 0.0) then
        set abilityCooldownPercentage2 = BlzGetUnitAbilityCooldownRemaining(hero, abilityId2) / BlzGetUnitAbilityCooldown(hero, abilityId2, abilityLevel2)
    endif

    if (abilityId3 != 0 and BlzGetUnitAbilityCooldown(hero, abilityId3, abilityLevel3) > 0.0) then
        set abilityCooldownPercentage3 = BlzGetUnitAbilityCooldownRemaining(hero, abilityId3) / BlzGetUnitAbilityCooldown(hero, abilityId3, abilityLevel3)
    endif

    if (abilityId4 != 0 and BlzGetUnitAbilityCooldown(hero, abilityId4, abilityLevel4) > 0.0) then
        set abilityCooldownPercentage4 = BlzGetUnitAbilityCooldownRemaining(hero, abilityId4) / BlzGetUnitAbilityCooldown(hero, abilityId4, abilityLevel4)
    endif

    call BlzEndUnitAbilityCooldown(hero, abilityId0)
    call BlzEndUnitAbilityCooldown(hero, abilityId1)
    call BlzEndUnitAbilityCooldown(hero, abilityId2)
    call BlzEndUnitAbilityCooldown(hero, abilityId3)
    call BlzEndUnitAbilityCooldown(hero, abilityId4)

    //call PrintMsg("Adding ability " + GetObjectName(ABILITY_ID_UNLEARN) + " to unit " + GetUnitName(hero))

    call UnitAddAbility(hero, ABILITY_ID_UNLEARN)
    call IssueImmediateOrderById(hero, ABILITY_ORDER_ID_UNLEARN)

    call SaveUnitHandle(timerHashTable, timerHandleId, 0, hero)
    call SaveInteger(timerHashTable, timerHandleId, 1, abilityId0)
    call SaveInteger(timerHashTable, timerHandleId, 2, abilityId1)
    call SaveInteger(timerHashTable, timerHandleId, 3, abilityId2)
    call SaveInteger(timerHashTable, timerHandleId, 4, abilityId3)
    call SaveInteger(timerHashTable, timerHandleId, 5, abilityId4)
    call SaveInteger(timerHashTable, timerHandleId, 6, abilityLevel0)
    call SaveInteger(timerHashTable, timerHandleId, 7, abilityLevel1)
    call SaveInteger(timerHashTable, timerHandleId, 8, abilityLevel2)
    call SaveInteger(timerHashTable, timerHandleId, 9, abilityLevel3)
    call SaveInteger(timerHashTable, timerHandleId, 10, abilityLevel4)
    call SaveReal(timerHashTable, timerHandleId, 11, abilityCooldownPercentage0)
    call SaveReal(timerHashTable, timerHandleId, 12, abilityCooldownPercentage1)
    call SaveReal(timerHashTable, timerHandleId, 13, abilityCooldownPercentage2)
    call SaveReal(timerHashTable, timerHandleId, 14, abilityCooldownPercentage3)
    call SaveReal(timerHashTable, timerHandleId, 15, abilityCooldownPercentage4)
    call SaveInteger(timerHashTable, timerHandleId, 16, abilityId)
    call SaveInteger(timerHashTable, timerHandleId, 17, level)

    call TimerStart(whichTimer, 1.0, false, function TimerFunctionReskill)
endfunction

// TODO Not only guard calls but queue them?
function SetHeroAbilityLevel takes unit hero, integer abilityId, integer level returns nothing
    if (not HaveSavedBoolean(whichHashTable, GetHandleId(hero), 0) or LoadBoolean(whichHashTable, GetHandleId(hero), 0)) then
        if (not SupportsHeroTypeForAbilityLevel(GetUnitTypeId(hero))) then
            call PrintMsg("Warning: Unit type ID " + GetObjectName(GetUnitTypeId(hero)) + " is not supported for changing the hero ability level.")
        endif
        call SaveBoolean(whichHashTable, GetHandleId(hero), 0, false)
        call SetHeroAbilityLevelEx(hero, abilityId, level)
    endif
endfunction

endlibrary
 
Last edited:
Level 9
Joined
Mar 26, 2017
Messages
376
I made a similar functionality for my map. I don't think there is any way to do this better.
You can make it foolproof by reducing the timer duration to something like .03 sec, or maybe even .01 sec. (I can't remember what I put, but you can experiment a bit what the shortest possible time could be).

And I don't know exactly when you activate a reskill, but if it is by using an ability for instance, you could put a short cooldown on that ability.


Might I add that you could put the result of GetHandleId into a local to make the code a bit shorter.

And if you consider using lua it would be much simpler, as you can pass data through an anonymous function.
 
Level 25
Joined
Feb 2, 2006
Messages
1,689
Okay, well I tried something like 0.1 and it was too short I think but I can try to experiment again.
The cast time of the unskill ability is already 0.0 so I guess it is a hard coded delay.

If this is the only way I might add a queue where I store all calls in the correct order? With the current solution all calls during a call are ignored and will get lost.

The code was written only to show the functionality. It could be much cleaner :D
 
Status
Not open for further replies.
Top