| 07-15-2010, 07:27 PM | #1 |
Well, after some investigation i found a way to check whether a hero can learn a specific ability. This uses vJass and Table. 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 |
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 | |
Quote:
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 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 |
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 |
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 |
> 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 |
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 |
Why shouldn't the following work? 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 |
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. |
