HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

[script] Skill Learnable

07-15-2010, 07:27 PM#1
Deaod
Well, after some investigation i found a way to check whether a hero can learn a specific ability.

This uses vJass and Table.

Collapse Library Code:
/**
 *                  SKILL LEARNABLE
 *                     by Deaod
 *
 * A library to check whether an ability can be learned by
 * a specific unit type.
 *
 * CREDITS:
 *  - Vexorian (Table, JassHelper)
 *  - MindWorX (JassNewGenPack)
 *  - PitzerMike (JassNewGenPack)
 *  - PipeDream (Grimoire)
 *  - SFilip (TESH)
 *
 * API:
 *
 *  function HeroCanLearnSkill takes unit hero, integer abilityId returns boolean
 *  Parameters:
 *      hero - The unit you want to check whether its able to learn abilityId.
 *      abilityId - The ability you want to check whether hero is able to learn it.
 *
 *  Returns:
 *      true if hero is able to learn abilityId
 *      false otherwise.
 *
 *  Note: 
 *      If CACHE_UNITS is true, don't use this function in
 *      struct or module initializers. It won't work.
 *
 *  function IsCheckingHeroSkill takes nothing returns boolean
 *  Returns:
 *      true if HeroCanLearnSkill is currently running
 *      false otherwise
 *
 *  Note:
 *      This was added to prevent endless recursion which would crash WC3.
 *      HeroCanLearnSkill uses the following natives, make sure you dont
 *      get endless recursion when for example hooking one of these:
 *      GetUnitTypeId, HaveSavedBoolean, LoadBoolean, PauseUnit, CreateUnit, Player,
 *      ShowUnit, UnitAddAbility, IssueImmediateOrderById, UnitRemoveAbility,
 *      SaveBoolean, RemoveUnit
 * 
 */
library SkillLearnable initializer Init requires Table
    
    // CONFIGURATION CONSTANTS
    // It's okay, go ahead and modify those.
    globals
        private constant    boolean             CACHE_UNITS                 = true
        // If true, only one unit is created for each ID thats checked if it can learn an ability.
        // 
        // If false, a unit is created every time you need to check if a type ID can learn an ability.
        // After the check the unit is removed.
        
        private constant    boolean             CACHE_RESULTS               = true
        // If true, the result for HeroCanLearnSkill is only calculated once and then stored for future use.
        //
        // If false, the result for HeroCanLearnSkill is calculated every time you request it.
        // I recommend setting this to false only if youre VERY short on hashtable instances
        
        private constant    real                SAFE_X                      = 0.
        private constant    real                SAFE_Y                      = 0.
        // indicates a position on the map where the dummy units are to be stored, if CACHE_UNITS is true.
        // choose a location where hidden units wont affect gameplay.
        
        private constant    integer             DUMMY_PLAYER_ID             = 15
        // 
        
        private constant    integer             RETRAINING_ABILITY          = 'Aret'
        // As custom abilities based off of 'Aret' wont work, you should never change this.
        private constant    integer             RETRAINING_ORDER            = 852471
        // The order doesnt have a string associated to it, and probably cant be changed in 'Aret',
        // so leave it as it is.
    endglobals
    
    // Don't touch anything blow.
    
    globals
        private Table UnitCacheTable=0
        private unit array UnitCache
        private integer CacheSize=0
        
        private boolean Checking=false
        
        private hashtable ResultsTable=null
    endglobals
    
    // Dont use before library initialization
    function HeroCanLearnSkill takes unit hero, integer abilityId returns boolean
    local integer heroUnitId=GetUnitTypeId(hero)
    local unit dummy
    local boolean result
        static if CACHE_RESULTS then
            if HaveSavedBoolean(ResultsTable, heroUnitId, abilityId) then
                return LoadBoolean(ResultsTable, heroUnitId, abilityId)
            endif
        endif
        static if CACHE_UNITS then
            set Checking=true
            if UnitCacheTable[heroUnitId]>0 then
                set dummy=UnitCache[UnitCacheTable[heroUnitId]]
                call PauseUnit(dummy, false) // paused units cant be issued orders, so unpause the dummy
            else
                set dummy=CreateUnit(Player(DUMMY_PLAYER_ID), heroUnitId, SAFE_X, SAFE_Y, 0)
                call ShowUnit(dummy, false)
                call UnitAddAbility(dummy, 'Aloc') // turn off collision, avoid detection by GroupEnumUnitsInRect/Range, make unit invulnerable
                call UnitAddAbility(dummy, RETRAINING_ABILITY) // Add the Tome of Retraining ability
                set CacheSize=CacheSize+1 // this ensures UnitCache[0] is never filled
                set UnitCache[CacheSize]=dummy
                set UnitCacheTable[heroUnitId]=CacheSize
            endif
            call UnitAddAbility(dummy, abilityId)
            set result=IssueImmediateOrderById(dummy, RETRAINING_ORDER) // try using the Tome of Retraining ability.
            // IssueImmediateOrderById returns false if the unit doesnt have an ability it can unlearn.
            if not result then
                call UnitRemoveAbility(dummy, abilityId)
            endif
            call PauseUnit(dummy, true) // make sure dummy units don't interact with actual units
            static if CACHE_RESULTS then
                call SaveBoolean(ResultsTable, heroUnitId, abilityId, result)
            endif
            set Checking=false
            return result
        else
            set Checking=true
            set dummy=CreateUnit(Player(DUMMY_PLAYER_ID), heroUnitId, SAFE_X, SAFE_Y, 0)
            call ShowUnit(dummy, false)
            call UnitAddAbility(dummy, abilityId)
            call UnitAddAbility(dummy, RETRAINING_ABILITY)
            set result=IssueImmediateOrderById(dummy, RETRAINING_ORDER)
            call RemoveUnit(dummy) // because killing isnt enough for heroes.
            set dummy=null
            static if CACHE_RESULTS then
                call SaveBoolean(ResultsTable, heroUnitId, abilityId, result)
            endif
            set Checking=false
            return result
        endif
    endfunction
    
    function IsCheckingHeroSkill takes nothing returns boolean
        return Checking
    endfunction
    
    private function Init takes nothing returns nothing
        set UnitCacheTable=Table.create()
        static if CACHE_RESULTS then
            set ResultsTable=InitHashtable()
        endif
    endfunction
    
endlibrary
07-16-2010, 03:37 PM#2
Tyrande_ma3x
I can't understand what this check consists of - it checks if, for example, a Hero has remaining levels of a specific spell to learn or what?
07-16-2010, 08:03 PM#3
Michael Peppers
Quote:
Originally Posted by Tyrande_ma3x
I can't understand what this check consists of - it checks if, for example, a Hero has remaining levels of a specific spell to learn or what?
It's clearly explained in the comments.

First, this script creates a dummy hero of the same kind you want to check, then he gives it the skill you decided and the "Tome Of Retraining" skill, then
Collapse JASS:
            set result=IssueImmediateOrderById(dummy, RETRAINING_ORDER) // try using the Tome of Retraining ability.
            // IssueImmediateOrderById returns false if the unit doesnt have an ability it can unlearn.

If it can't unlearn the ability, it can't learn it as well.

Good job on discovering this, Deaod!
07-16-2010, 08:20 PM#4
DioD
it checks hero for specific learnable skill (if hero not learned skill yet and it level is zero you cant check with ability level.


BUT this completely useless, for such tasks databasing exists.
07-16-2010, 08:25 PM#5
Anitarf
DioD has a point, if for some reason you need this functionality in a map (which I am not really convinced you do), it would be considerably more efficient and not all that difficult to just create a database of all the hero types and their hero skills used in the map.
07-16-2010, 08:38 PM#6
Tyrande_ma3x
> It's clearly explained in the comments.
If it were clear for me, I wouldn't have had to ask for a better explanation. But now I see what it does, thanks for clarifying.
07-17-2010, 09:02 AM#7
Deaod
Anitarf, check AbilityLearning for a use of this library. Creating a database of all abilities any hero in a map can learn is not really an option when youre not writing code for a specific map.

I could have used ODE. Maybe ill make a version of this that uses ODE.

EDIT: Updated!
04-26-2011, 06:27 AM#8
Jewel
Why shouldn't the following work?
Collapse Zinc:
//! zinc
library AbilityLearnable requires xebasic {
    
    unit abilityTester = null

    public function IsAbilityLearnable(integer whichAbility) -> boolean {
        if (UnitAddAbility(abilityTester, whichAbility) == true) {
            UnitRemoveAbility(abilityTester, whichAbility);
            return true;
        }
        return false;
    }
    
    function onInit() {
        abilityTester = CreateUnit(XE_DUMMY_UNITID, PLAYER_NEUTRAL_PASSIVE, 0.0, 0.0, 0.0);
        if (GetUnitAbilityLevel(abilityTester, 'Aloc') == 0) { 
            UnitAddAbility(abilityTester, 'Aloc');
        }
        PauseUnit(abilityTester, true);
    }
}
//! endzinc

I haven't tested how UnitAddAbility returns its boolean, but I don't see why it wouldn't work.

But I like your script nonetheless.
04-30-2011, 08:42 AM#9
Deaod
Because UnitAddAbility always returns true (in all cases ive tested, anyway, inlcuding the case youre using).
Also, learnability means that the unit is a hero and can actually learn the ability over the hero learn menu.