TasUnitBonus

Introduction


TasUnitBonus is a unit Stat Lua system for Warcraft 3 V1.31 or higher.
It gives an one way api to inc/dec unit stats, the types of stats is "easy" expandable. Includes addons to handle bonus over Buffs, Items, LevelUps, Learning Skills & Skill-Fields.

To increase a stat of an unit using TasUnitBonus you would run TasUnitBonus(unit, key, value) key is one of the supported keys of TasUnitBonus. As said before you can add own custom keys/stats pretty easy, checkout the examples at the bottom.

The main System contains prebuilt warcraft 3 stats, the stats prebuilt only use code no ability-features -> no object Editor setup required.
Life
Mana
Armor
Attack
AttackSpeed
AttackRange (Not V1.31.1)
AttackRangeR (Not V1.31.1)
LifeReg
ManaReg
Agi
Str
Int
Primary
CastPoint
CastBackswing
TurnSpeed
UnitLevel
Ability
Ability2 Ability3 Ability4
AbilityInc
AbilityInc2 AbilityInc3 AbilityInc4
Tech
Tech2 Tech3 Tech4
Add 10 Primary Hero Stat (white numbers) TasUnitBonus(unit, "Primary", 10)
Bonus Strenght is an ability feature for such is the TasUnitBonusSkill addon, can require custom abilities and object Editor setup.

How to install


Requiers Warcraft 3 1.31+ or higher
  • Copy the TasUnitBonus Script/Folder (trigger Editor).
  • Or Copy the TasUnitBonus code from this post in your map, probably the addons aswell.
  • Without Total Init run InitTasUnitBonusHero() & InitTasUnitBonusItem() & InitTasUnitBonusLearn() inside a mapInit/function main action to create events.
  • Check the data in TasUnitBonusSkill, make sure that the skills used work for your map and are of expected base behaviour.
  • Installed

Code


Lua:
if Debug then Debug.beginFile "TasUnitBonus" end
do
   --[[ TasUnitBonus 1.9 by Tasyen
       A system to provide various bonuses to Units over one way. This only defines Types and their callbacks to increase or decrease the wanted stat.

       Apply a bonus: TasUnitBonus(unit, key, value)
           TasUnitBonus(udg_Unit, "Life", 100) -- add 100 HP to udg_Unit
           TasUnitBonus(udg_Unit, "Ability", FourCC'AHbz') -- add Blizzard
           TasUnitBonus(udg_Unit, "AttackSpeed", -0.1) -- improve base ATK cooldown by 0.1
           base-Keys
            Life
            Mana
            Armor
            Attack
            AttackSpeed
            AttackRange (Not V1.31.1)
            AttackRangeR (Not V1.31.1)
            LifeReg
            ManaReg
            Agi
            Str
            Int
            Primary
            CastPoint
            CastBackswing
            TurnSpeed
            UnitLevel
            Ability
            Ability2 Ability3 Ability4
            AbilityInc
            AbilityInc2 AbilityInc3 AbilityInc4
            Tech
            Tech2 Tech3 Tech4
            

       TasUnitBonus.UnitAdd(unit, bonusTable, add)
           apply many bonuses with one call, supports 2 formats
             {Str = x, Agi = y, Int = z ,...}
             {"Str",x, "Agi", y, "Int", z ,...}
           bonusTable is affecting unit by add Times (add needs to be an integer). Use -1 to remove bonus. 1 to add bonus
           Ignores number keys

       TasUnitBonus.NewType(key, function(unit, value, key) end)
           Adds a new supported bonus reached by key, key should be unique. 
           The function runs when calling TasUnitBonus with that key, and it needs to do the wanted effect, in most cases increase some unit stat; newValue = oldValue + value.
           instead of a function you can give an already existing key to make keyB do the same as keyA, for TasUnitBonus.UnitAdd keyA and keyB are 2 separate stats.

                TasUnitBonus.NewType("ATK", "Attack") -- now TasUnitBonus(unit, "ATK", 22) & TasUnitBonus(unit, "Attack", 22) do the same

                TasUnitBonus.NewType("MyUnitStat", function(unit, value, key)
                    local unitData = UnitData[unit]
                    unitData[key] = unitData[key] + value
                end)
           

       TasUnitBonus.AddSimpleType(key[, createArray])
           add a new supported type meant for an global array in env _G.
           Which would result into key[unit] = key[unit] + bonus
           TasUnitBonus.AddSimpleType("udg_Unit_Super_Power")
           createArray (true) -> create a new array and store it at _G[key]. This array will return 0 on default

       this.AddSimpleTypeBatch = function(...)
           add many AddSimpleType as once
           AddSimpleTypeBatch("MagicPower", "WeaponPower",...)
   ]]
   local this = {}
   TasUnitBonus = this
   this.Heal = true -- Key Life & Mana will give current amount
   this.IgnoreUnsupported = {Aktiv = true} -- do not throw erros for these unsupported keys encountered in TasUnitBonus.UnitAdd; expects Key = true, Key2 = true
   this.FourCCValueKey = {AbilityInc = true, Ability = true, Tech = true} -- for this BonusTypes the value is a Object Editor RawCode

    this.AddSimpleType = function(key, createArray)
        this.Types[key] =  this.Types.SimpleBonusType
        if createArray then
            _G[key] = __jarray(0)
        end
    end

    this.NewType = function(key, action)
        if type(action) == "function" then
            this.Types[key] =  action
        elseif type(action) == "string" then
            this.Types[key] = this.Types[action]
            if this.FourCCValueKey[action] then this.FourCCValueKey[key] = true end
        end
    end

    this.AddSimpleTypeBatch = function(...) for _, k in ipairs({...}) do this.AddSimpleType(k) end end

    local tempKeys = {}
    -- bonusTable is affecting unit by add Times. Use -1 to remove bonus. 1 to add bonus
    this.UnitAdd =  function (unit, bonusTable, add)
        if not unit then return end
        if GetUnitTypeId(unit) == 0 then return end -- this might be smart to do when you only use units
        if not add then add = 1 end

        -- which format is used, {Key = x} or Sequzenz {"Str",20 ,"Int",5}?
        if bonusTable[1] and bonusTable[2] and type(bonusTable[1]) == "string" then
            -- format Sequzenz {"Str",4,"Agi",6}
            -- loop the bonusTable and apply any key that is in TasUnitBonus.Types
            for i = 1, #bonusTable, 2 do
                local key = bonusTable[i]
                local value = bonusTable[i + 1]
                if this.IgnoreUnsupported[key] or type(key) == "number" then
                elseif this.Types[key] then
                    -- supported Type call it

                    -- That key has an Object Editor RawCode value, therefore only support +1/-1 for add
                    if this.FourCCValueKey[key] then
                        if type(value) == "string" then value = FourCC(value) end                    
                        this.Types[key](unit, value*ISignBJ(add), key)
                    else
                        this.Types[key](unit, value*add, key)
                    end
                else print("TasUnitBonus.UnitAdd - Unsuported Key", key)
                end
            end
        else
            local keyCount = 0
            for key in pairs(bonusTable) do
                -- only keys are accepted & executed
                if type(key) == "string" then
                    keyCount = keyCount + 1
                    tempKeys[keyCount] = key
                end
            end
            table.sort(tempKeys)

            -- loop the bonusTable and apply any key that is in TasUnitBonus.Types
            for __,key in ipairs(tempKeys) do
                local value = bonusTable[key]
                if this.IgnoreUnsupported[key] or type(key) == "number" then
                elseif this.Types[key] then
                    -- supported Type call it

                    -- That key has an Object Editor RawCode value, therefore only support +1/-1 for add
                    if this.FourCCValueKey[key] then
                        if type(value) == "string" then value = FourCC(value) end                    
                        this.Types[key](unit, value*ISignBJ(add), key)
                    else
                        this.Types[key](unit, value*add, key)
                    end
                else print("TasUnitBonus.UnitAdd - Unsuported Key", key)
                end
            end
            for i = 1, keyCount do
                tempKeys[i] = nil
            end
        end
    end

    setmetatable(this, {__call = function(table, unit, key, value)
        if not unit then return end
        --if string.find(tostring(unit), "unit:") and GetUnitTypeId(unit) == 0 then print("TasUnitBonus - no Unit: ",key, value) return end
        if GetUnitTypeId(unit) == 0 then print("TasUnitBonus - no Unit: ",key, value) return end
        if this.Types[key] then
            this.Types[key](unit, value, key)
    --  elseif TasUnitBonusSkill and TasUnitBonusSkill.Data[key] then
    --      TasUnitBonusSkill.Add(unit, key, value)
        else
            print("TasUnitBonus - Unsupported Key: ",key)
        end
    end})

    function this.ConvertFourCC(value)
        if type(value) == "string" then 
            if string.byte(value) == 45 then -- starts with - ? "-AHbz"
                return -FourCC(string.sub(value, 2))
            else
                return FourCC(value)
            end
        end
        return value
    end
   -- supported Types and what to do for them. the functions should work for -value and +value
   this.Types = {
       Life = function(unit, value) BlzSetUnitMaxHP(unit, BlzGetUnitMaxHP(unit) + value) if value > 0 and this.Heal then SetWidgetLife(unit, GetWidgetLife(unit) + value) end end
       ,Mana = function(unit, value) BlzSetUnitMaxMana(unit, BlzGetUnitMaxMana(unit) + value) if value > 0 and this.Heal then SetUnitState(unit, UNIT_STATE_MANA, GetUnitState(unit, UNIT_STATE_MANA) + value) end end
       ,Armor = function(unit, value) BlzSetUnitArmor(unit, BlzGetUnitArmor(unit) + value) end
       ,Attack = function(unit, value) BlzSetUnitBaseDamage(unit, BlzGetUnitBaseDamage(unit, 0) + value, 0) end
       ,AttackSpeed = function(unit, value) BlzSetUnitAttackCooldown(unit, BlzGetUnitAttackCooldown(unit, 0) + value, 0)  end

       -- AttackRange does not work in warcraft 3 v1.31
       ,AttackRange = function(unit, value) BlzSetUnitWeaponRealField(unit, UNIT_WEAPON_RF_ATTACK_RANGE, 1, value + BlzGetUnitWeaponRealField(unit, UNIT_WEAPON_RF_ATTACK_RANGE, 1)) end
       ,AttackRangeR = function(unit, value) if IsUnitIdType(GetUnitTypeId(unit), UNIT_TYPE_RANGED_ATTACKER) then this.Types.AttackRange(unit, value) end end
       ,LifeReg = function(unit, value) BlzSetUnitRealField(unit, UNIT_RF_HIT_POINTS_REGENERATION_RATE, BlzGetUnitRealField(unit, UNIT_RF_HIT_POINTS_REGENERATION_RATE) + value) end
       ,ManaReg = function(unit, value) BlzSetUnitRealField(unit, UNIT_RF_MANA_REGENERATION, BlzGetUnitRealField(unit, UNIT_RF_MANA_REGENERATION) + value) end
       ,Agi = function(unit, value) SetHeroAgi(unit, GetHeroAgi(unit, false) + value, true) end
       ,Str = function(unit, value) SetHeroStr(unit, GetHeroStr(unit, false) + value, true) end
       ,Int = function(unit, value) SetHeroInt(unit, GetHeroInt(unit, false) + value, true) end
       ,Primary = function(unit, value)
           local stat = BlzGetUnitIntegerField(unit, UNIT_IF_PRIMARY_ATTRIBUTE)
           if stat == GetHandleId(HERO_ATTRIBUTE_STR) then
               this.Types.Str(unit, value)
           elseif stat == GetHandleId(HERO_ATTRIBUTE_AGI) then
               this.Types.Agi(unit, value)
           elseif stat == GetHandleId(HERO_ATTRIBUTE_INT) then
               this.Types.Int(unit, value)
           end
       end
       -- CastPoint CastBackswing don't affect abilities the unit already has. One would have to lose and regain all to make it work.
       ,CastPoint = function(unit, value) BlzSetUnitRealField(unit, UNIT_RF_CAST_POINT, BlzGetUnitRealField(unit, UNIT_RF_CAST_POINT) + value) end
       ,CastBackswing = function(unit, value) BlzSetUnitRealField(unit, UNIT_RF_CAST_BACK_SWING, BlzGetUnitRealField(unit, UNIT_RF_CAST_BACK_SWING) + value) end
       ,TurnSpeed = function(unit, value) SetUnitTurnSpeed(unit, GetUnitTurnSpeed(unit) + value) end
       ,UnitLevel = function(unit, value)
           if not IsUnitType(unit, UNIT_TYPE_HERO) and not IsHeroUnitId(GetUnitTypeId(unit)) then
               BlzSetUnitIntegerField(unit, UNIT_IF_LEVEL, BlzGetUnitIntegerField(unit, UNIT_IF_LEVEL) + value)
           end
       end

       -- example of a custom variable setting, MagicDef should bedefined to catch nilpointers with __jarray(0)
       --,MagicDef = function(unit, value) MagicDef[unit] = MagicDef[unit] + value end
      
      ,Ability = function(unit, value)
           value = this.ConvertFourCC(value)
           if value >= 0 then
               UnitMakeAbilityPermanent(unit, true, value)
               UnitAddAbility(unit, value)
           else
               UnitRemoveAbility(unit, -value)
           end
       end
       ,AbilityInc = function(unit, value)
            value = this.ConvertFourCC(value)
            if value >= 0 then IncUnitAbilityLevel(unit, value)
            else DecUnitAbilityLevel(unit, -value) end
       end
       ,Tech = function(unit, value)
            value = this.ConvertFourCC(value)
            if value >= 0 then
                AddPlayerTechResearched(GetOwningPlayer(unit), value, 1)
            else
                BlzDecPlayerTechResearched(GetOwningPlayer(unit), -value, 1)
            end
        end

       ,SimpleBonusType = function(unit, value, key) _G[key][unit] = _G[key][unit] + value end
       -- Key+ uses skills to provide non base bonus
       -- You need to provide abilities and update the references inside TasUnitBonusSkill
       -- the "+" key iself should not be used it is the base for all TasUnitBonusSkill
       ,["+"] = function(unit, value, key) TasUnitBonusSkill.Add(unit, key, value) end
   }
   -- setup equals
    TasUnitBonus.NewType("ATK", "Attack") -- now TasUnitBonus(unit, "ATK", 22) & TasUnitBonus(unit, "Attack", 22) do the same
    TasUnitBonus.NewType("Ability2", "Ability")
    TasUnitBonus.NewType("Ability3", "Ability")
    TasUnitBonus.NewType("Ability4", "Ability")
    TasUnitBonus.NewType("AbilityInc2", "AbilityInc")
    TasUnitBonus.NewType("AbilityInc3", "AbilityInc")
    TasUnitBonus.NewType("AbilityInc4", "AbilityInc")
    TasUnitBonus.NewType("Tech2", "Tech")
    TasUnitBonus.NewType("Tech3", "Tech")
    TasUnitBonus.NewType("Tech4", "Tech")


   -- custom ones
   --this.AddSimpleTypeBatch("TasCasterLevel","WeaponPower","WeaponDef","MagicPower", "MagicDefAstral","MagicDef")
end

if Debug then Debug.endFile() end

The Addons:
TasUnitBonusSkill handles & adds TasUnitBonus-Keys that require abilities to be given like bonus str, bonus def, bonus attack, evasion, default crit and such in the default all Keys of this Section end with + like "STR+".
TasUnitBonusItem gives TasUnitBonus on pick/drop items has unit-events. You can define data-Lua-tables for specific items and of items with a given rawCode.
TasUnitBonusHero give TasUnitBonus on Levelups for any hero, that hero and Heroes of class x, Has unit-events.
TasUnitBonusBuff attach a TasUnitBonus to a Warcraft 3 BuffCode. You need to tell the types of bonuses once and then when the buff is gained by an unit tell how much of each stat.
TasUnitBonusBuffTable kinda like TasUnitBonusBuff but use bonusTables instead of multiargs
TasUnitBonusLearn give TasUnitBonus when Learning a Skill over warcraft 3 Hero Learn ui, can be called from the outside in case you heroes gain skills in a other way, Has unit-events.
TasUnitBonusItemTooltip addon for TasUnitBonusItem update tooltips to display TasUnitBonus gained by items.

Lua:
if Debug then Debug.beginFile "TasUnitBonusSkill" end
--[[
TasUnitBonusSkill 1.2a by Tasyen
Addon for TasUnitBonus handles bonus provided over an ability (green +)
needs custom map setup, make sure the Abilities in the data setup is okay in your map.
Scroll down to Data Setup
   when a skill provides more than one thing make sure the base in object Editor don't conflicts with your goal
    ^^ for abilities with multiple fields you should make a custom version with default values good for your purposes like hero stat skill 0 0 0.
   the abilities should not be used otherwise

======
function TasUnitBonusSkill.Remove(unit, whichType)
    removes the ability providing the bonus
function TasUnitBonusSkill.Set(unit, whichType, amount)
    add the ability and set the bonus provided
function TasUnitBonusSkill.Get(unit, whichType)
function TasUnitBonusSkill.Add(unit, whichType, amount)
    adds amount to the current bonus
======
--]]
do
   TasUnitBonusSkill = {Data = {}}
   local this = TasUnitBonusSkill
   this.HideSkills = true -- hide the skills gained by this system with BlzUnitHideAbility?
   local this = TasUnitBonusSkill
   local thisData = TasUnitBonusSkill.Data

   -- enforce update functions
   local function UpdateLevel(unit, data)
       IncUnitAbilityLevel(unit, data.Skill)
       DecUnitAbilityLevel(unit, data.Skill)
   end
   local function UpdateInt(unit, data)
       SetHeroInt(unit, GetHeroInt(unit, false)+1, true)
       SetHeroInt(unit, GetHeroInt(unit, false)-1, true)
   end
   local function UpdateStr(unit, data)
       SetHeroStr(unit, GetHeroStr(unit, false)+1, true)
       SetHeroStr(unit, GetHeroStr(unit, false)-1, true)
   end
   
   -- shortcuts
   local iGet = BlzGetAbilityIntegerLevelField
   local iSet = BlzSetAbilityIntegerLevelField
   local rGet = BlzGetAbilityRealLevelField
   local rSet = BlzSetAbilityRealLevelField

--[[
   define abilities providing the bonuses and which field is update using which function for Get and Set
   Skill = FourCC(abiliCode)
   Field = field to read/write
   Get = function used to read
   Set = function used to write
   Update = call a function to apply changes, needed for some values.
   
]]
--======
   -- Data Setup
--======
   --ability clone of 'Aamk' Hero Attributes
   thisData["STR+"] = {Skill = FourCC('TU00'), Field = ABILITY_ILF_STRENGTH_BONUS_ISTR, Get = iGet, Set = iSet, Update = UpdateLevel}
   thisData["AGI+"] = {Skill = FourCC('TU00'), Field = ABILITY_ILF_AGILITY_BONUS, Get = iGet, Set = iSet, Update = UpdateLevel}
   thisData["INT+"] = {Skill = FourCC('TU00'), Field = ABILITY_ILF_INTELLIGENCE_BONUS, Get = iGet, Set = iSet, Update = UpdateLevel}

   --claw of attack
   thisData["ATK+"] = {Skill = FourCC('AItg'), Field = ABILITY_ILF_ATTACK_BONUS, Get = iGet, Set = iSet, Update = UpdateLevel}
   thisData["DMG+"] = thisData["ATK+"] -- one can also use "DMG+" for attack damage
   --gloves
   thisData["ATKSP+"] = {Skill = FourCC('AIsx'), Field = ABILITY_RLF_ATTACK_SPEED_INCREASE_ISX1, Get = rGet, Set = rSet, Update = UpdateLevel}
   --armor item
   thisData["DEF+"] = {Skill = FourCC('AId1'), Field = ABILITY_ILF_DEFENSE_BONUS_IDEF, Get = iGet, Set = iSet, Update = UpdateLevel}

   --based on 'ACev'
    thisData["EVADE+"] = {Skill = FourCC('AIev'), Field = ABILITY_RLF_CHANCE_TO_EVADE_EEV1, Get = rGet, Set = rSet}
    --trunken fighter or orc crit
    thisData["KRIT_CHANCE+"] = {Skill = FourCC('AIcs'), Field = ABILITY_RLF_CHANCE_TO_CRITICAL_STRIKE, Get = rGet, Set = rSet}
    thisData["KRIT_FACTOR+"] = {Skill = FourCC('AIcs'), Field = ABILITY_RLF_DAMAGE_MULTIPLIER_OCR2, Get = rGet, Set = rSet}
    thisData["KRIT_DAMAGE+"] = {Skill = FourCC('AIcs'), Field = ABILITY_RLF_DAMAGE_BONUS_OCR3, Get = rGet, Set = rSet}
    thisData["KRIT_EVADE+"] = {Skill = FourCC('AIcs'), Field = ABILITY_RLF_CHANCE_TO_EVADE_OCR4, Get = rGet, Set = rSet}
    thisData["KRIT_TRUE_STRIKE+"] = {Skill = FourCC('AIcs'), Field = ABILITY_ILF_SUMMONED_UNIT_COUNT_NPA5, Get = iGet, Set = iSet}
    thisData["KRIT_BONUS+"] = {Skill = FourCC('AIcs'), Field = ABILITY_ILF_WAVE_COUNT_NHS6, Get = iGet, Set = iSet}

   --boots of speed skill, they don't stack anyway
    thisData["MOVE+"] = {Skill = FourCC('AIms'), Field = ABILITY_ILF_MOVEMENT_SPEED_BONUS, Get = iGet, Set = iSet, Update = UpdateLevel}

   --elunes grace 'Aegr'
    thisData["MAGIC_RES+"] = {Skill = FourCC('AIdd'), Field = ABILITY_RLF_MAGIC_DAMAGE_REDUCTION_DEF5, Get = rGet, Set = rSet}
    thisData["PIERCE_RES+"] = {Skill = FourCC('AIdd'), Field = ABILITY_RLF_DAMAGE_TAKEN_PERCENT_DEF1, Get = rGet, Set = rSet}
   -- Deflect has to be enabled inside Game constants
    thisData["DEFLECT_CHANCE+"] = {Skill = FourCC('AIdd'), Field = ABILITY_RLF_CHANCE_TO_DEFLECT, Get = rGet, Set = rSet}
    thisData["DEFLECT_DAMAGE_MAGIC+"] = {Skill = FourCC('AIdd'), Field = ABILITY_RLF_DEFLECT_DAMAGE_TAKEN_SPELLS, Get = rGet, Set = rSet}
    thisData["DEFLECT_DAMAGE_PIERCE+"] = {Skill = FourCC('AIdd'), Field = ABILITY_RLF_DEFLECT_DAMAGE_TAKEN_PIERCING, Get = rGet, Set = rSet}
    -- multiplies basic attack without UI-Feedback
    thisData["DAMAGE_FACTOR+"] = {Skill = FourCC('AIdd'), Field = ABILITY_RLF_DAMAGE_DEALT_PERCENT_DEF2, Get = rGet, Set = rSet}

    -- spell ress brace item skill
    -- beaware this can break the the Aegr magic res feature for this unit
    thisData["SPELL_RES+"] = {Skill = FourCC('AIsr'), Field = ABILITY_RLF_DAMAGE_REDUCTION_ISR2, Get = rGet, Set = rSet, Update = UpdateLevel}    

   --based on 'Ansk'
   thisData["BLOCK_AMOUNT+"] = {Skill = FourCC('Ansk'), Field = ABILITY_RLF_IGNORED_DAMAGE, Get = rGet, Set = rSet}
   thisData["BLOCK_MIN+"] = {Skill = FourCC('Ansk'), Field = ABILITY_RLF_MINIMUM_DAMAGE, Get = rGet, Set = rSet}
   thisData["BLOCK_CHANCE+"] = {Skill = FourCC('Ansk'), Field = ABILITY_RLF_CHANCE_TO_REDUCE_DAMAGE_PERCENT, Get = rGet, Set = rSet}

   --'ACce' MeleeCleave
   thisData["CLEAVE_DAMAGE+"] = {Skill = FourCC('ACce'), Field = ABILITY_RLF_DISTRIBUTED_DAMAGE_FACTOR_NCA1, Get = rGet, Set = rSet}
   thisData["CLEAVE_AOE+"] = {Skill = FourCC('ACce'), Field = ABILITY_RLF_AREA_OF_EFFECT, Get = rGet, Set = rSet}

   --'ACpv'
    thisData["PULV_CHANCE+"] = {Skill = FourCC('ACpv'), Field = ABILITY_RLF_CHANCE_TO_STOMP_PERCENT, Get = rGet, Set = rSet}
    thisData["PULV_DAMAGE+"] = {Skill = FourCC('ACpv'), Field = ABILITY_RLF_DAMAGE_DEALT_WAR2, Get = rGet, Set = rSet}
    thisData["PULV_AOE+"] = {Skill = FourCC('ACpv'), Field = ABILITY_RLF_FULL_DAMAGE_RADIUS_WAR3, Get = rGet, Set = rSet}
    thisData["PULV_AOE_HALF+"] = {Skill = FourCC('ACpv'), Field = ABILITY_RLF_HALF_DAMAGE_RADIUS_WAR4, Get = rGet, Set = rSet}
    thisData["PULV_SFX+"] = {Skill = FourCC('ACpv'), Field = ABILITY_SLF_SPECIAL, Get = BlzGetAbilityStringLevelField, Set = BlzSetAbilityStringLevelField}

   --'Afbk'
    thisData["FEEDBACK_MANA+"] = {Skill = FourCC('Afbb'), Field = ABILITY_RLF_MAX_MANA_DRAINED_UNITS, Get = rGet, Set = rSet}
    thisData["FEEDBACK_MANA_HERO+"] = {Skill = FourCC('Afbb'), Field = ABILITY_RLF_MAX_MANA_DRAINED_HEROS, Get = rGet, Set = rSet}
    thisData["FEEDBACK_DAMAGE+"] = {Skill = FourCC('Afbb'), Field = ABILITY_RLF_DAMAGE_RATIO_UNITS_PERCENT, Get = rGet, Set = rSet}
    thisData["FEEDBACK_DAMAGE_HERO+"] = {Skill = FourCC('Afbb'), Field = ABILITY_RLF_DAMAGE_RATIO_HEROS_PERCENT, Get = rGet, Set = rSet}
    thisData["FEEDBACK_DAMAGE_SUMMON+"] = {Skill = FourCC('Afbb'), Field = ABILITY_RLF_SUMMONED_DAMAGE, Get = rGet, Set = rSet}

   --'Apxf'
    thisData["AUTO_SHOT_DAMAGE+"] = {Skill = FourCC('Apxf'), Field = ABILITY_RLF_INITIAL_DAMAGE_PXF1, Get = rGet, Set = rSet, Update = UpdateLevel}
    thisData["AUTO_SHOT_DPS+"] = {Skill = FourCC('Apxf'), Field = ABILITY_RLF_DAMAGE_PER_SECOND_PXF2, Get = rGet, Set = rSet, Update = UpdateLevel}
    thisData["AUTO_SHOT_DUR+"] = {Skill = FourCC('Apxf'), Field = ABILITY_RLF_DURATION_NORMAL, Get = rGet, Set = rSet, Update = UpdateLevel}
    thisData["AUTO_SHOT_DUR_HERO+"] = {Skill = FourCC('Apxf'), Field = ABILITY_RLF_DURATION_HERO, Get = rGet, Set = rSet, Update = UpdateLevel}
    thisData["AUTO_SHOT_AOE+"] = {Skill = FourCC('Apxf'), Field = ABILITY_RLF_AREA_OF_EFFECT, Get = rGet, Set = rSet, Update = UpdateLevel}
    thisData["AUTO_SHOT_TIME+"] = {Skill = FourCC('Apxf'), Field = ABILITY_RLF_COOLDOWN, Get = rGet, Set = rSet, Update = UpdateLevel}

   --'ANss'
    thisData["SPELL_SHIELD+"] = {Skill = FourCC('ANss'), Field = ABILITY_RLF_COOLDOWN, Get = rGet, Set = rSet}

   --'ACrn'
    thisData["REINCARNATION_COOLDOWN+"] = {Skill = FourCC('ACrn'), Field = ABILITY_RLF_COOLDOWN, Get = rGet, Set = rSet}
    thisData["REINCARNATION_DUR+"] = {Skill = FourCC('ACrn'), Field = ABILITY_RLF_REINCARNATION_DELAY, Get = rGet, Set = rSet}

   --Bash
   thisData["BASH_CHANCE+"] = {Skill = FourCC('AIbx'), Field = ABILITY_RLF_CHANCE_TO_BASH, Get = rGet, Set = rSet}
   thisData["BASH_DAMAGE+"] = {Skill = FourCC('AIbx'), Field = ABILITY_RLF_DAMAGE_BONUS_HBH3, Get = rGet, Set = rSet}
   thisData["BASH_DUR+"] = {Skill = FourCC('AIbx'), Field = ABILITY_RLF_DURATION_NORMAL, Get = rGet, Set = rSet}
   thisData["BASH_DURHERO+"] = {Skill = FourCC('AIbx'), Field = ABILITY_RLF_DURATION_HERO, Get = rGet, Set = rSet}
   thisData["BASH_TRUE_STRIKE+"] = {Skill = FourCC('AIbx'), Field = ABILITY_ILF_SUMMONED_UNIT_COUNT_NPA5, Get = iGet, Set = iSet}

   -- Item Lifesteal skill 'AIva'
   thisData["LIFE_STEAL+"] = {Skill = FourCC('AIva'), Field = ABILITY_RLF_DAMAGE_DEALT_ESF1, Get = rGet, Set = rSet, Update = UpdateLevel}   

   --Poison Buff Placer 'ACvs'
    thisData["POISON_DPS+"] = {Skill = FourCC('ACvs'), Field = ABILITY_RLF_DAMAGE_PER_SECOND_POI1, Get = rGet, Set = rSet}
    thisData["POISON_SLOW_ATK+"] = {Skill = FourCC('ACvs'), Field = ABILITY_RLF_ATTACK_SPEED_FACTOR_POI2, Get = rGet, Set = rSet}
    thisData["POISON_SLOW_MOVE+"] = {Skill = FourCC('ACvs'), Field = ABILITY_RLF_MOVEMENT_SPEED_FACTOR_POI3, Get = rGet, Set = rSet}
    thisData["POISON_TYPE+"] = {Skill = FourCC('ACvs'), Field = ABILITY_ILF_STACKING_TYPE_POI4, Get = iGet, Set = iSet}
    thisData["POISON_DUR+"] = {Skill = FourCC('ACvs'), Field = ABILITY_RLF_DURATION_NORMAL, Get = rGet, Set = rSet}
    thisData["POISON_DUR_HERO+"] = {Skill = FourCC('ACvs'), Field = ABILITY_RLF_DURATION_HERO, Get = rGet, Set = rSet}

    TasUnitBonus.NewType("PRIMARY+", function(unit, value, key)
        local stat = BlzGetUnitIntegerField(unit, UNIT_IF_PRIMARY_ATTRIBUTE)
        if stat == GetHandleId(HERO_ATTRIBUTE_STR) then
            this.Add(unit, "STR+", value)
        elseif stat == GetHandleId(HERO_ATTRIBUTE_AGI) then
            this.Add(unit, "AGI+", value)
        elseif stat == GetHandleId(HERO_ATTRIBUTE_INT) then
            this.Add(unit, "INT+", value)
        end
     end)
--======
-- System functions
--======
   setmetatable(thisData, {__index = function(table, key)
       print("TasUnitBonusSkill - Error - invalid Key", key)
       return nil
   end})

   function this.Set(unit, whichType, amount)
       local data = thisData[whichType]
       
       --if not BlzGetUnitAbility(unit, data.Skill) then UnitAddAbility(unit, data.Skill) end
       UnitAddAbility(unit, data.Skill)
       UnitMakeAbilityPermanent(unit, true, data.Skill)
       if this.HideSkills then BlzUnitHideAbility(unit, data.Skill, true) end
       data.Set(BlzGetUnitAbility(unit, data.Skill), data.Field, 0, amount)
       if data.Update then
           data.Update(unit, data)
       end
   end

   function this.Get(unit, whichType)
       local spell = BlzGetUnitAbility(unit, thisData[whichType].Skill)
       if not spell then
           return 0
       else
           return thisData[whichType].Get(spell, thisData[whichType].Field, 0)
       end    
   end
   function this.Remove(unit, whichType)
       UnitRemoveAbility(unit, thisData[whichType].Skill)
   end

   function this.Add(unit, whichType, amount)
       this.Set(unit, whichType, this.Get(unit, whichType) + amount)
   end

    -- add all they keys from TasUnitBonusSkill to to TasUnitBonus   
    -- they all use the same function
    for key in pairs(this.Data) do
        TasUnitBonus.NewType(key,"+")
    end
end
if Debug then Debug.endFile() end

if Debug then Debug.beginFile "TasUnitBonusBuff" end
--[[
TasUnitBonusBuff V1c
by Tasyen

Addon for TasUnitBonus; Give bonuses as long an unit has an Ability/Buff. Has to be evoked when an unit gains the buff.


function TasUnitBonusBuff.add(unit, buffCode, ...)
    as long unit has buffCode it benefits from the given amount
    Overwrites the current boni which first is lost
    TasUnitBonusBuff.add(unit, 'Binf', 200, 5) -- Gives 200 of the first Key in TasUnitBonusBuff.DefineKeys and 5 for the second
    
function TasUnitBonusBuff.DefineKeys(buffCode, ...)
    Which keys are used By buffCode (keys are from TasUnitBonus). This will generate Tables for it, only do this once at map init before using any buff placer.
    TasUnitBonusBuff.DefineKeys('Binf', "Life", "Str")
--]]
do    
    TasUnitBonusBuff = {
       -- current Buffs
       Unit = {}
       ,BuffCode = {}
       ,Count = 0
    
       -- BuffCode Data
       ,Data = {}
       -- to interpret the given Data
       ,Keys = {}
       -- KeysWaitingForRgister
       ,Delayed = {}
    }
    local this = TasUnitBonusBuff
    
    function this.DefineKeys(buffCode, ...)
       if not buffCode then print("TasUnitBonusBuff.DefineKeys - Invalid buffCode", buffCode) return end
       if type(buffCode) == "string" then buffCode = FourCC(buffCode) end
       if this.Keys[buffCode] then if Debug then print("TasUnitBonusBuff.DefineKeys - Twice ", buffCode, string.pack(">I4", buffCode)) end return end
       this.Data[buffCode] = {}
       this.Keys[buffCode] = {}
       -- make for the buffCode one table for each Key
       -- unitspecific data is stored at --this.Data[buffCode][Key][unit]
       -- that way after each buffCode has define keys no Table has to be created anymore
       for i, key in ipairs({...}) do
           this.Data[buffCode][key] = {}
           this.Keys[buffCode][i] = key -- this is required to call the TasUnitBonus
       end
       -- has unit Buff & Time Buff Gained
       this.Data[buffCode][0] = {}
    end
    
    function this.StoreKey(...)
       if this.Delayed then
           table.insert(this.Delayed, {...})
       else
           this.DefineKeys({...})
       end
    end
    function this.ApplyStoredKeys()
       for _, v in ipairs(this.Delayed) do this.DefineKeys(table.unpack(v)) end
       this.Delayed = nil
    end
    
    function this.add(unit, buffCode, ...)
       if type(buffCode) == "string" then buffCode = FourCC(buffCode) end
    if not unit then print("TasUnitBonusBuff.add No Unit") return end
       if not this.Timer then this.Timer = CreateTimer() end
       if not this.Data[buffCode] then 
           print("TasUnitBonusBuff.add - Not Registered BuffCode", buffCode, string.pack(">I4", buffCode))
           return
       end
    
       -- new buff?
       if not this.Data[buffCode][0][unit] then
           -- the Time buff gained
           this.Data[buffCode][0][unit] = 5
    
           this.Count = this.Count + 1
           this.Unit[this.Count] = unit
           this.BuffCode[this.Count] = buffCode
    
           -- apply bonus
           local key
           for i, value in ipairs({...}) do
               key = this.Keys[buffCode][i]            
               this.Data[buffCode][key][unit] = value
               TasUnitBonus(unit, key, value)
           end
    
           if this.Count == 1 then TimerStart(this.Timer, 0.1, true, this.TimerAction) end
       else
           -- rebuff
           local dif
           local key
           for i, value in ipairs({...}) do
               key = this.Keys[buffCode][i]
               dif = value - this.Data[buffCode][key][unit]
               this.Data[buffCode][key][unit] = value
               TasUnitBonus(unit, key, dif)
           end
       end
    
    end
    
    function this.TimerAction()
       local buffCode, unit, unitCode
    --    print(this.Count)
       for index = this.Count, 1, -1 do
           -- Have buff Lost? And atleast 0.5 seconds expired
           buffCode = this.BuffCode[index]
           unit = this.Unit[index]
           unitCode = GetUnitTypeId(unit)
           --print(buffCode, GetFourCC(buffCode))
           if unitCode == 0 or ( not BlzGetUnitAbility(unit, buffCode)
               and (this.Data[buffCode][0][unit] <= 0)
           ) then
                  -- print("expired",buffCode, GetFourCC(buffCode), TimerGetElapsed(udg_SpielZeit), TasUnitBonusBuff.Data[buffCode][0][unit])
               -- revert bonus
               for _, key in ipairs(this.Keys[buffCode]) do
                   if unitCode > 0 then TasUnitBonus(unit, key, -this.Data[buffCode][key][unit]) end
                   this.Data[buffCode][key][unit] = nil
               end
               -- unmark having TasUnitBonusBuff
               this.Data[buffCode][0][unit] = nil
    
               -- reindex
               this.Unit[index] = this.Unit[this.Count]
               this.BuffCode[index] = this.BuffCode[this.Count]
    
               this.Unit[this.Count] = nil
               this.Count = this.Count - 1
    
               
               if this.Count  <= 0 then PauseTimer(this.Timer) end
             elseif this.Data[buffCode][0][unit] > 0 then 
                this.Data[buffCode][0][unit] = this.Data[buffCode][0][unit] - 1
           end
       end
       buffCode = nil
       unit =  nil
    end
end
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "TasUnitBonusBuffTable" end
--[[
TasUnitBonusBuffTable V1d
by Tasyen

Addon for TasUnitBonus; Give bonuses as long an unit has an Ability/Buff. Has to be evoked when an unit gains the buff.
This version of TasUnitBonusBuff only works with tables.

function TasUnitBonusBuffTable.add(unit, buffCode, bonusTable)
    unit benefits from  bonusTable as long it has buffCode
    one buffCode can give each unit only one bonusTable at once. When it already benefits from one the old bonus is undone.
    TasUnitBonusBuffTable.add(unit, 'Binf', {Life = 200, Str = 5})    
    TasUnitBonusBuffTable(unit, buffCode, bonusTable) is supported as well

function TasUnitBonusBuffTable.get(unit, buffCode)
    returns the current bonusTable for this unit & buffCode or nil 
--]]
do
TasUnitBonusBuffTable = {
   -- current Buffs
   Unit = {}
   ,BuffCode = {}
   ,Count = 0

   -- BuffCode Data
   ,Data = {}
   ,Protected = {}
}

local this = TasUnitBonusBuffTable
setmetatable(this, {__call = function(table, unit, buffCode, bonusTable) table.add(unit, buffCode, bonusTable) end})
function this.add(unit, buffCode, bonusTable)
   if type(buffCode) == "string" then buffCode = FourCC(buffCode) end
   if not unit then print("TasUnitBonusBuffTable.add No Unit") return end
   if not this.Timer then this.Timer = CreateTimer() end
   if not this.Data[buffCode] then
    -- has unit Buff & Time Buff Gained
       this.Data[buffCode] = {}
       this.Protected[buffCode] = __jarray(0)
   end
   -- new buff?
   if not this.Data[buffCode][unit] then
       -- the Time buff gained
       this.Protected[buffCode][unit] = 5

       this.Count = this.Count + 1
       this.Unit[this.Count] = unit
       this.BuffCode[this.Count] = buffCode
       TasUnitBonus.UnitAdd(unit, bonusTable, 1)
       this.Data[buffCode][unit] = bonusTable
       
       if this.Count == 1 then TimerStart(this.Timer, 0.1, true, this.TimerAction) end
   elseif this.Data[buffCode][unit] ~= bonusTable then
       -- rebuff, only do something when it is a new table
       TasUnitBonus.UnitAdd(unit, this.Data[buffCode][unit], -1)
       TasUnitBonus.UnitAdd(unit, bonusTable, 1)
       this.Data[buffCode][unit] = bonusTable
   end
end

function this.get(unit, buffCode)
    if not this.Data[buffCode] then return nil end
    return this.Data[buffCode][unit]
end

function this.TimerAction()
   local buffCode, unit, unitCode
--    print(this.Count)
   for index = this.Count, 1, -1 do
       -- Have buff Lost? And atleast 0.5 seconds expired
       buffCode = this.BuffCode[index]
       unit = this.Unit[index]
       unitCode = GetUnitTypeId(unit)
       --print(buffCode, GetFourCC(buffCode))
       if unitCode == 0 or ( not BlzGetUnitAbility(unit, buffCode)
           and (this.Protected[buffCode][unit] <= 0)
       ) then
           -- revert bonus
           TasUnitBonus.UnitAdd(unit, this.Data[buffCode][unit], -1)
           -- unmark having TasUnitBonusBuffTable
           this.Protected[buffCode][unit] = nil
           this.Data[buffCode][unit] = nil

           -- reindex
           this.Unit[index] = this.Unit[this.Count]
           this.BuffCode[index] = this.BuffCode[this.Count]

           this.Unit[this.Count] = nil
           this.Count = this.Count - 1

           
           if this.Count  <= 0 then PauseTimer(this.Timer) end
         elseif this.Protected[buffCode][unit] > 0 then 
            this.Protected[buffCode][unit] = this.Protected[buffCode][unit] - 1
       end
   end
   buffCode = nil
   unit =  nil
end
end
if Debug then Debug.endFile() end
if Debug then Debug.beginFile "TasUnitBonusItem" end
do
   --[[ TasUnitBonusItem 1.4c by Tasyen
       Code driven Bonus for items. Also can be used for powerups.

       requires TasUnitBonus 
       can use FourCCTable & Total Initialization
       without Total Initialization you need to run InitTasUnitBonusItem() to create the events

       example; the keys used need to be suported by TasUnitBonus, before such an item is picked Up/Droped
       TasUnitBonusItem['I000'] = {
           Attack = 20 --add 20 ATK
           ,Mana = 100
           ,Life = 30
           ,Armor = 1.5 -- + 1.5 Armor
           ,LifeReg = 2.2
           ,ManaReg = 1.0 -- +1.0 Mana/s
           ,Agi = 1
           ,Str = 3
           ,Int = 5
       }

        one can set a bonus for an specific item, both  TasUnitBonusItem[GetItemTypeId] & TasUnitBonusItem[item] are gained/lost.
        TasUnitBonusItem[udg_Item] = { Attack = 20 }
   ]]
   if CreateFourCCTable then TasUnitBonusItem = CreateFourCCTable()
   else TasUnitBonusItem = {} end   
   local this = TasUnitBonusItem
   
   local function Action(unit, item, itemCode, add)      
       if IsUnitType(unit, UNIT_TYPE_HERO) then
           if this[itemCode] then TasUnitBonus.UnitAdd(unit, this[itemCode], add) end
           if this[item] then TasUnitBonus.UnitAdd(unit, this[item], add) end
       end
   end

   function InitTasUnitBonusItem()
      InitTasUnitBonusItem = DoNothing
      local trig = CreateTrigger()
      TriggerAddAction(trig, function()
         local unit, item, itemCode = GetManipulatingUnit(), GetManipulatedItem(), GetItemTypeId(GetManipulatedItem())
       -- heroes only
       if not IsUnitType(unit, UNIT_TYPE_HERO) then return end
       -- skip the drop part for powerups, to have a permanent boni :)
       if not IsItemPowerup(item) then
           Action(unit, item, itemCode, -1)
       end
      end)
      TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DROP_ITEM)

      trig = CreateTrigger()
      TriggerAddAction(trig, function()
         local unit, item, itemCode = GetManipulatingUnit(), GetManipulatedItem(), GetItemTypeId(GetManipulatedItem())
         -- heroes only
         if not IsUnitType(unit, UNIT_TYPE_HERO) then return end
         Action(unit, item, itemCode, 1)
      end)
      TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_PICKUP_ITEM)
   end
   if OnInit then OnInit.trig(InitTasUnitBonusItem) end
end
if Debug then Debug.endFile() end

if Debug then Debug.beginFile "TasUnitBonusItemTooltip" end
--[[
TasUnitBonusItemTooltip by Tasyen
Addon for TasUnitBonusItem rewrites the tooltips with stats given by an item. 
Only does something when called onto an item. to use this is an item needs to have the placeholders inside its default extended tooltip.
$ replaces all tooltip
$item inser the StatList here
TasUnitBonusItemTooltip(item)
]]
TasUnitBonusItemTooltip = {}
do
   
   local this = TasUnitBonusItemTooltip
   PercentChar = "\x25"
   this.DisplayMulti = {
      -- multi the value for display purposes
      -- for given keys
      ["ATKSP+"] = 100
      ,ATKSP = 100
   }
   this.DisplayFormat = {
      -- rounding rules for display purposes
      ["ATKSP+"] = PercentChar..".0f"
      ,ATKSP = PercentChar..".0f"
   }
   this.Strings = {
      -- strings used in the tooltip gen
      -- the values are thrown into GetLocalizedString
      Str = "STRENGTH"
      ,Agi = "AGILITY"
      ,Int = "INTELLECT"
      ,STR = "STRENGTH"
      ,AGI = "AGILITY"
      ,INT = "INTELLECT"
   }
   setmetatable(this.Strings, {__index = function(table, key)
       return key end -- default fallback
   } )
   setmetatable(this, {__call = function(table, item) this.Apply(item) end } )

 function this.Formater(key, value)
    if this.DisplayFormat[key] then return string.format(this.DisplayFormat[key], value) end
    return value
 end
 function this.AddStat(obj, key, value)
   -- + not allowed In StringList, therefore drop it
   local keyString = key
   if string.sub(keyString, - 1) == "+" then keyString = string.sub(keyString, 1 , -2) end
   if (type(value) == "number") then 
      if not obj[keyString] then obj[keyString] = value
      else obj[keyString] = obj[keyString] + value
      end
   end
end
 
 function this.Apply(item)
     local itemCode = GetItemTypeId(item)
     if not TasUnitBonusItem[itemCode] and not TasUnitBonusItem[item] then return end

     --local text = BlzGetItemExtendedTooltip(item)
     local text = BlzGetAbilityExtendedTooltip(itemCode, 0)

     local temp = {}     
     if TasUnitBonusItem[itemCode] then for key, value in pairs(TasUnitBonusItem[itemCode]) do  this.AddStat(temp ,key, value) end end
     if TasUnitBonusItem[item] then for key, value in pairs(TasUnitBonusItem[item]) do  this.AddStat(temp ,key, value) end end
     local addText = ""
     for key, value in pairs(temp) do  
      addText = addText .. this.Formater(key, value*(this.DisplayMulti[key] or 1)) .." ".. GetLocalizedString(this.Strings[key]).."|n"
     end
     -- all dynamic
     if (text == "$") then
         BlzSetItemExtendedTooltip(item, addText)
         BlzSetItemDescription(item, addText)
     -- add all itemstats here
     elseif string.find(text, "$item") then
         local oldText = text
         text = string.gsub(oldText, "$item", addText)
         BlzSetItemExtendedTooltip(item, text)
         BlzSetItemDescription(item, text)
     end 
 end
end
if Debug then Debug.endFile() end

if Debug then Debug.beginFile "TasUnitBonusHero" end
do
   --[[ TasUnitBonusHero 1.1b by Tasyen
       Code driven Stat Bonus for HeroLevels.

       requires TasUnitBonus 
       can use FourCCTable & Total Initialization
       without Total Initialization you need to run InitTasUnitBonusHero() to create the events

       example, the keys need to be supported by TasUnitBonus
       TasUnitBonusHero[FourCC'Hpal'] = {
        Str = 1
        ,["DMG+"] = 5 -- gain 5 Bonus ATK every LevelUp
        ,[2] = { Ability = FourCC'AHbz'} -- at level 2 add Blizzard
        ,[4] = {["STR+"] = 12} -- Level 4 gain 12 bonus Str
        }

        TasUnitBonusHero[0] = { -- hero levelup bonus for all heroes
        Primary = 1
        ,Str = 1
        ,Agi = 1
        ,Int = 1
        }

        TasUnitBonusHero[udg_Unit] = { Primary = 2} -- specific levelup bonus

        Level [1] can not give a bonus.
        A Hero benefits from TasUnitBonusHero[0], TasUnitBonusHero[GetUnitTypeId] & TasUnitBonusHero[unit] at once.
   ]]
   local lastLevel = __jarray(1)
   if CreateFourCCTable then TasUnitBonusHero = CreateFourCCTable()
   else TasUnitBonusHero = {} end
   local this = TasUnitBonusHero
   
   local function apply(data, unit, newLevel, levelUps)
    if not data then return end
    TasUnitBonus.UnitAdd(unit, data, levelUps)
    for i = lastLevel[unit] + 1, newLevel do if data[i] then TasUnitBonus.UnitAdd(unit, data[i], 1) end end
   end

   function InitTasUnitBonusHero()
    InitTasUnitBonusHero = DoNothing
      local trig = CreateTrigger()
      TriggerAddAction(trig, function()
        local unit = GetTriggerUnit()
        local level = GetHeroLevel(unit)
        local levelUps = level - lastLevel[unit]
        local unitCode = GetUnitTypeId(unit)
        apply(this[unit], unit, level, levelUps) -- hero specific bonus
        apply(this[0], unit, level, levelUps)  -- any hero-class bonus
        apply(this[unitCode], unit, level, levelUps) -- this hero-class bonus
        lastLevel[unit] = level
      end)
      TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_HERO_LEVEL)
   end
   if OnInit then OnInit.trig(InitTasUnitBonusHero) end
end
if Debug then Debug.endFile() end

if Debug then Debug.beginFile "TasUnitBonusLearn" end
do
   --[[ TasUnitBonusLearn by Tasyen
       Code driven Stat Bonus for Heroes Learning Skills.

       requires TasUnitBonus 
       can use FourCCTable & Total Initialization
       without Total Initialization you need to run InitTasUnitBonusLearn() to create the events

       you can give a bonus with TasUnitBonusLearnAction(unit, spellCode, learnedLevel). 
       You could do this when a skill is not learned over the default warcraft 3 learn ui.

       example, the keys need to be suported by TasUnitBonus
       TasUnitBonusLearn[FourCC'AHhb'] = {
        ["DMG+"] = 5 -- gain 5 Bonus ATK every time Skilled
        ,[2] = { Str = 10} -- Skilling level 2 10 base Str
        }
   ]]
   if CreateFourCCTable then TasUnitBonusLearn = CreateFourCCTable()
   else TasUnitBonusLearn = {} end
   local this = TasUnitBonusLearn
   
   function TasUnitBonusLearnAction(unit, spellCode, learnedLevel)
    local data = TasUnitBonusLearn[spellCode]
    if not data then return end
    TasUnitBonus.UnitAdd(unit, data, 1)
    if data[learnedLevel] then TasUnitBonus.UnitAdd(unit, data[learnedLevel], 1) end
   end

   function InitTasUnitBonusLearn()
    InitTasUnitBonusLearn = DoNothing
      local trig = CreateTrigger()
      TriggerAddAction(trig, function() TasUnitBonusLearnAction(GetTriggerUnit(), GetLearnedSkill(), GetLearnedSkillLevel()) end)
      TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_HERO_SKILL)
   end
   if OnInit then OnInit.trig(InitTasUnitBonusLearn) end
end
if Debug then Debug.endFile() end

example: TasUnitBonusItem Simple Items
Lua:
--[[
 example use of TasUnitBonusItem
 setup what each items of RawCode gives while carried by a hero
--]]
do
    itemPower = TasUnitBonusItem -- need to write less text
    itemPower[FourCC'I000'] = {Armor = 3} -- 3 base armor
    itemPower[FourCC'I001'] = {["STR+"] = 3} -- +3 bonus str
    itemPower[FourCC'I002'] = {["MOVE+"] = 50} 
    itemPower[FourCC'I003'] = {Int = 6} -- + 6 base int
    itemPower[FourCC'I004'] = {Attack = 9} -- + 9 base dmg
    itemPower[FourCC'I005'] = {["INT+"] = 4, ["AGI+"] = 4} -- gives 4 bonus agi and int
    itemPower[FourCC'I006'] = {["DMG+"] = 6} -- + 6 bonus dmg    
    
    itemPower[FourCC'I007'] = {["PRIMARY+"] = 15}
    itemPower[FourCC'I00B'] = {["ATKSP+"] = 0.25}

    itemPower[FourCC'I00C'] = {"STR+", 4, "AGI+", 4} -- gives 4 bonus agi and str Format2 key. value, key takes less performance
end

example: TasUnitBonusLearn
Lua:
TasUnitBonusLearn[FourCC'AHhb'] = { -- HolyLight
  ["DMG+"] = 3 -- 3 Bonus ATK every time Skilled
 ,[2] = { Str = 10} -- Skilling level 2 10 base Str
}

example: TasUnitBonusItem MaskOfDeath
Lua:
-- MaskOfDeath is an example for item specific TasUnitBonus combined with itemCode bonus.
--+20 ATK|n+5 for each last hit done while holding this item, upto 100 additional ATK.

MaskOfDeathCode = FourCC'I008'
function MaskOfDeath()
    TasUnitBonusItem[MaskOfDeathCode] = {["DMG+"] = 20} -- + 20 bonus dmg

    local trig = CreateTrigger()
    TriggerAddAction(trig, function()
        local killer = GetKillingUnit()
        if not IsUnitType(killer, UNIT_TYPE_HERO) then return end

        for i = 0, bj_MAX_INVENTORY -1 do
            local item = UnitItemInSlot(killer, i)
            if GetItemTypeId(item) == MaskOfDeathCode then

                -- create item speicifc table when not existing yet
                if not TasUnitBonusItem[item] then TasUnitBonusItem[item] = {["DMG+"] = 0} end
                if TasUnitBonusItem[item]["DMG+"] < 100 then 
                    TasUnitBonusItem[item]["DMG+"] = TasUnitBonusItem[item]["DMG+"] + 5
                    
                    -- give the bonus now, because TasUnitBonusItem is only given and taken on PIckUP/drop events
                    TasUnitBonus(killer, "DMG+", 5)
                    
                    -- update item name & tooltip
                    TasUnitBonusItemTooltip(item)
                    BlzSetItemName(item, GetObjectName(MaskOfDeathCode) .. "(+".. TasUnitBonusItem[item]["DMG+"] ..")")
                end
            end
        end

    end)
    TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_DEATH)
end

example: CustomStat UndeadSlayer & Backstab
Lua:
--example of 2 custom types backstab and UndeadSlayer
-- backstab gives Bonus ATK when hitting into the back
-- UndeadSlayer gives Bonus ATK when attacking units with the UNDEAD-Class


-- needed for backstab
function AngleW2W(source, target)
    return bj_RADTODEG * Atan2(GetWidgetY(target) - GetWidgetY(source), GetWidgetX(target) - GetWidgetX(source))
end
do -- 
    function CustomStats()
        udg_BackStab = __jarray(0)
        udg_UndeadSlayer = __jarray(0)
        TasUnitBonus.AddSimpleType("udg_BackStab")
        TasUnitBonus.AddSimpleType("udg_UndeadSlayer")

        itemPower[FourCC'I009'] = {udg_UndeadSlayer = 27}
        itemPower[FourCC'I00A'] = {udg_BackStab = 35}

-- current Bonus used for reset
        local oldBackStab = __jarray(0)
        local oldUndeadSlayer = __jarray(0)
        local trig = CreateTrigger()
        TriggerAddAction(trig, function()
            local unit, attacker = GetTriggerUnit(), GetAttacker()
            local newValue = udg_BackStab[attacker]
            if oldBackStab[attacker] ~= 0 or newValue ~= 0 then
                if CosBJ(AngleW2W(attacker, unit) - GetUnitFacing(unit)) < 0 then newValue = 0 end
                TasUnitBonus(attacker, "ATK+", newValue - oldBackStab[attacker])
                oldBackStab[attacker] = newValue
            end

            newValue = udg_UndeadSlayer[attacker]
            if oldUndeadSlayer[attacker] ~= 0 or newValue ~= 0 then
                if not IsUnitType(unit, UNIT_TYPE_UNDEAD) then newValue = 0 end
                TasUnitBonus(attacker, "ATK+", newValue - oldUndeadSlayer[attacker])
                oldUndeadSlayer[attacker] = newValue
            end
        end)
        TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_ATTACKED)
    end
end

Examples for Buffing
Lua:
-- example for TasUnitBonusBuff

function InitDemoSpellCast()
    -- define once what bonus keys buffs give
    TasUnitBonusBuff.DefineKeys('Binv', "MOVE+")
    TasUnitBonusBuff.DefineKeys('Binf', "STR+")
    TasUnitBonusBuff.DefineKeys('Bblo', "Ability")

-- do not run this again
    InitDemoSpellCast = nil
end

function DemoSpellCast()
    if InitDemoSpellCast then InitDemoSpellCast() end

    -- the spellcasts tell TasUnitBonusBuff how much to add for a given buffCode

--Invis buff gives 500 movespeed
    if GetSpellAbilityId() == FourCC'Aivs' then TasUnitBonusBuff.add(GetSpellTargetUnit(), 'Binv', 500) end

-- inner fire gives hero str
-- first cast adds 15 recast give 5 more 15 20 25 30
    local buffCode = FourCC'Binf'
    if GetSpellAbilityId() == FourCC'Ainf' then
        local value = TasUnitBonusBuff.Data[buffCode]["STR+"][GetSpellTargetUnit()] or 0
 	if value  < 15 then value  = 15 else value  = value + 5 end
        TasUnitBonusBuff.add(GetSpellTargetUnit(), buffCode, value ) end

-- bloodlust gives an endurance aura
    local skill = FourCC'AIae'
    if GetSpellAbilityId() == FourCC'ACbb' then TasUnitBonusBuff.add(GetSpellTargetUnit(), 'Bblo', skill) end    
end

ChangeLog


1.9) Additonal Format for TasUnitBonus.UnitAdd
1.8) TasUnitBonus.AddSimpleType can create an array, added config TasUnitBonus.Heal, added TasUnitBonusBuffTable.get
1.7b) small error fix
1.7a) fixed an critical error of 1.7, args of TasUnitBonus.UnitAdd reordered, improve comments
1.7) fixed a desync possiblity, call TasUnitBonusBuffTable, NewType copies FourCCValueKey, +Key Ability2 to 4
1.6a) improved TasUnitBonusBuffTable rebuff
1.6) improve comments, Improved Keys Ability,AbilityInc,Tech
1.5) improve comments, ability survives morphs, seperated TasUnitBonusSkill more from TasUnitBonus, Added Key Tech inc/dec Reserach by 1
1.4) fix key AttackRange
1.3x) Added TasUnitBonusLearn
1.3) Added TasUnitBonusItemTooltip & 4 stats
1.2c) Fixed TasUnitBonusHero
1.2b) added TasUnitBonusHero
1.2a) replaced HealUnit(Mana) with native aviable functions


Tags: RPG, Hero, Stats, Unit Bonus, Str, Agi, Int, ATK​
Contents

TasUnitBonus (Map)

Reviews
Antares
A nice collection of quality-of-life libraries that make adding/removing abilities and stat bonuses much easier. Almost any stat/ability is included and the system can easily be expanded to include your own types. Approved
Level 5
Joined
Oct 31, 2011
Messages
91
Another one of Tasyen's subscription systems, I wish Blizzard had the same dedication to Warcraft as you and the community here at Hivework Shop.

Apparently it adds a skill that has a bonus that it will increase to the Hero, but how does it increase CastPoint and CastBackswing?

And another thing, will there be a Jass version?
 
And another thing, will there be a Jass version?
I will not make a vjass version of this. vjass cant do this dynamic function mapping which is a core feature.

but how does it increase CastPoint and CastBackswing?
castpoint and castbackswing are unit stats when an ability is gained the ability copies the unit's current castpoint & castbackswing values.
To make already owned abilities be cast faster you would change the unit castpoint then lose the castable spells from the unit and readd them.

Edit: would be easier when one would have the ability_RealFields for castpoint but sadly no, so only doable by readding spells.
 
Updated to V1.2b)
Added the addon TasUnitBonusHero, I forgot to port it from my map.
TasUnitBonus.UnitAdd does not use numbers as key anymore. "1" is still allowed 1 not.
TasUnitBonusHero manages TasUnitBonuses on level ups for any hero [0] and [unit] and [RawCode].
Lua:
TasUnitBonusHero[FourCC'Hpal'] = { -- paladin levelup Bonus
 Str = 1
 ,["DMG+"] = 5 -- gain 5 Bonus ATK every LevelUp
 ,[2] = { Ability = FourCC'AHbz'} -- at level 2 add Blizzard
 ,[4] = {["STR+"] = 12}
}

Edit:
V1.2c)
fixed serious bugs of TasUnitBonusHero.
 
Last edited:
Level 5
Joined
Mar 7, 2024
Messages
79
You're so talented, I also wish I knew how to make an off-screen interface like that, but this is way beyond my level, like the photo I posted, you can do for those positions is add a container Hero's items?
HOI4.PNG
 
You're so talented,
I trained many years. in gui, then java and some other none warcraft 3 stuff, after that jass, then i trained Lua and warcraft 3 custom UI (was quite bad at some point in that subject). But y, I guess have talent for some parts of math/order.
TasUnitBonus is the result of systems, I wrote before, some not public released and only for my map or as a experiment. In that systems I encountered the problem that I needed to give unit stats and so I came to this idea. My Talent system is one of these concept-ancestors it has a stat systems by Lua tables.

One can create many types of ui but warcraft 3 frames are based on rectangles -> images are easier than models. One can create a custom ui with buttons to contain: items like a bag, yes. I made such and some others did as well. Making more default item/command/hero buttons is not doable with the ui-frame api. In such a case one would need to recreate the whole feature.




The image shows TasCheatbox, if one wonders, i added that as demo to have some tester super powers like dispel buffs and such.
normaly the 3 & 4 page would create items & units but in this map the were replaced with "cheats" using TasUnitBonus.
 
Updated to V1.3)
added 4 prebuilt stats

2 over skills
"PRIMARY+"
"LIFE_STEAL+" item lifesteal

2 but they dont work in V1.31.1 cause they use Unit Weaponfields.
AttackRange
AttackRangeR

AttackRangeR is AttackRange but wont do anthing if the unit is not ranged.

improved AbilityInc
now should be able to decrease current level like key Ability with a -value.

What common important stat is missing?

Ported another addon TasUnitBonusItemTooltip. Updates item tooltips to include the stats given by that item + itemCode. Items only use this feature when you call it onto it.
TasUnitBonusItemTooltip(item)

TasUnitBonusItemTooltip replaces a placeholder: There are 2 options.
$ replaces all tooltip
$item inser the StatList here keep the rest as it is.

I think all addons for TasUnitBonus are now ported.
 
Last edited:
I think all addons for TasUnitBonus are now ported.
I was wrong. Still got one that gives TasUnitBonus when a hero learns a skill. But I cant port the version from my map, it has weird featues and was constructed for 1 level skills only that give another bonus after dozens of levels and maybe (also/only) affecting other units.

Could make an simpler version that works kinda like TasUnitBonusHero. Would then be called TasUnitBonusLearn.

Edit:
Updated, includes now the addon TasUnitBonusLearn, should be suited for the normal warcraft 3 Learning system.
TasUnitBonusLearn uses the same format as TasUnitBonusHero it does not have the any key with 0 and instead of hero level, skill levels matter, it also can be called from the outside.
TasUnitBonusLearnAction(unit, spellCode, learnedLevel)

Lua:
TasUnitBonusLearn[FourCC'AHhb'] = { -- HolyLight
  ["DMG+"] = 3 -- 3 Bonus ATK every time Skilled
 ,[2] = { Str = 10} -- Skilling level 2 10 base Str
}
 
Last edited:
Adds a new supported type by key the function runs when a request to add to this key.
The function needs to do the inc.
instead of a function you can give an already existing key to make key do the same.
I don't know what that means.

Please use an AI assistant like ChatGPT to help you with the translation or have someone help you with the documentation. I'm sure there are a lot of people who would be happy to.

The description in the OP is fine, but for the comments in the code, I just don't know what it's trying to tell me.

It doesn't help that the formatting is all messed up. It looks like the scope in TasUnitBonus is closed on line 34. Please fix the indentation and add some empty lines.

I'm sure the code itself is superb as always, but we're trying to be more stringent when it comes to documentation, as to increase the accessibility of this section.

Awaiting Update
 
Updated to V1.5)
improved api comments
key Ability; ability survives morphs
seperated TasUnitBonusSkill more from TasUnitBonus
Added Key Tech, it increases/decreases a Research by 1.

TasUnitBonusHero mentions that a Hero benefits from upto 3 tabels at once [0] [GetUnitTypeId] & [unit]
TasUnitBonusItem mentions that an item supports boh at the same time [GetItemTypeId] & [item]
 
Last edited:
Updated to V1.6)
List bases Types below the TasUnitBonus function.

Ability, AbilityInc & Tech now do the FourCC when needed and work better in TasUnitBonus.UnitAdd.

Added addon TasUnitBonusBuffTable a "clone" of TasUnitBonusBuff, but instead of multiargs it works by a bonusTable like used in TasUnitBonus.UnitAdd

TasUnitBonusBuffTable.add(unit, 'Binf', {Life = 200, Str = 5})

The demo for it
Lua:
-- example for TasUnitBonusBuffTable
do
    local bonusInvis = {["MOVE+"]=500}
    local bonusBlood = {Ability='AIae'}

    function DemoSpellCast()
        local unit = GetSpellTargetUnit()
        -- the spellcasts tell TasUnitBonusBuff how much to add for a given buffCode
    --Invis buff gives 500 movespeed
        if GetSpellAbilityId() == FourCC'Aivs' then TasUnitBonusBuffTable.add(unit, 'Binv', bonusInvis) end
    

    -- inner fire gives hero str
    -- first cast adds 15, recast give 5 more; 15 20 25 30
        local buffCode = FourCC'Binf'
        if GetSpellAbilityId() == FourCC'Ainf' then
            local value = 0
            local buffData = TasUnitBonusBuffTable.Data[buffCode]
            if buffData and buffData[unit] then value = buffData[unit]["STR+"] end
            if value  < 15 then value = 15 else value  = value + 5 end
            TasUnitBonusBuffTable.add(unit, buffCode, {["STR+"]= value}) end

    -- bloodlust gives an endurance aura
        if GetSpellAbilityId() == FourCC'ACbb' then TasUnitBonusBuffTable.add(unit, 'Bblo', bonusBlood) end    
    end    
end

Edit: Updated to V1.6a)
improved performance for TasUnitBonusBuffTable rebuffing the same thing an unit already has.

Edit: Updated to V1.7)
TasUnitBonusBuffTable can now be called directly,
fixed a posible desync reason with bonusTable
NewType copies the flag in FourCCValueKey for the copied key.
added Clone Types, they do the same as the normal but allow one bonus to apply 4 different abilities/techs
Ability2 Ability3 Ability4
AbilityInc2 AbilityInc3 AbilityInc4
Tech2 Tech3 Tech4

Edit: Updated to V1.7a)
Fixed an critical bug of V1.7 which broke TasUnitBonus.UnitAdd after an level up.

TasUnitBonus.UnitAdd(bonusTable, bonusTable, add)
-> TasUnitBonus.UnitAdd(unit, bonusTable, add)
therefore all addons were updated aswell.

changed some comments.

Edit: Updated to V1.7b)
updated a small typo in the unsupported key metatable
 
Last edited:
Level 8
Joined
Oct 20, 2010
Messages
205
What are the key differences that separate this from Cbopinski’s New Bonus? I Is there a difference in performance? I would love if a newer and more efficient alternative has become available.

What I have found is that this system modifies the base stats via natives thus changing the white numbers. While New bonus uses abilities (requiring object editor) and modifies the green numbers.
 
The key difference is one can add more easy new stats/keys to TasUnitBonus, even without touching the TasUnitBonus code. This custom stats can be any number array, it is simple to use an GUI array. While keys also could target more complicated fields like UnitData[unit].Stats.Str, although here one needs to write a add function (oneline function).

The addons handle the events to apply the stats, therefore one can setup TasUnitBonuses just as data at map init or such like the demos show. learning skills, buffs, items, level-Ups can give any TasUnitBonus. (except for buffs these need an runtime event in which the bonus is applied to an specific unit)

Level-up demo for Paladins.
Lua:
TasUnitBonusHero[FourCC'Hpal'] = { -- paladin levelup Bonus
 Str = 1
 ,["DMG+"] = 5 -- gain 5 Bonus ATK every LevelUp
 ,[2] = { Ability = FourCC'AHbz'} -- at level 2 add Blizzard
 ,[4] = {["STR+"] = 12}
}

TasUnitBonus only adds/takes away stats, it can not tell you how much you have.
The default keys of TasUnitBonus change base stats. The Addon TasUnitBonusSkill adds keys to add green stats which need abilities.

I dont know about performance diffs, wasnt my goal to perform faster than new Bonus, I wanted this data setup outside of events.

TasUnitBonus can not be used in (v)jass mode maps.
 
Last edited:
uploaded a small update V1.8)
TasUnitBonus.AddSimpleType has now an optional second arg. When provided it will create an new global array with the key in _G, this array returns 0 like the GUI arrays.
TasUnitBonus.AddSimpleType("MyUnitStat", true) -> add "MyUnitStat" to TasUnitBonus and also create such an array.
TasUnitBonus.AddSimpleType("MyUnitStat") -> only add "MyUnitStat" to TasUnitBonus.


TasUnitBonus.Heal a config when true the Key Life & Mana will increase current Life/Mana, default enabled.

Added TasUnitBonusBuffTable.get(unit, buffCode). It returns the current bonusTable for this unit & buffCode or nil.

Added a new Demo that shows buff stacking, but only 2 rebuffs make the buff better, This demo uses a finite amount of tables, made at the beginning.

Lua:
local frostArmor = {
    {}
    ,{Life = 100}
    ,{Life = 100, LifeReg = 5}
}
-- Frost armog gives with the first rebuff 100 HP with the second Rebuff also HP/s
local buffCode = FourCC'BUfa'
if GetSpellAbilityId() == FourCC'ACfa' then
    local currentBonus = TasUnitBonusBuffTable.get(unit, buffCode)
    local newBonus = currentBonus
    if not currentBonus then newBonus = frostArmor[1]
    elseif currentBonus == frostArmor[1] then newBonus = frostArmor[2]
    elseif currentBonus == frostArmor[2] then newBonus = frostArmor[3]
    end
    TasUnitBonusBuffTable(unit, buffCode, newBonus)
end
 
Last edited:
Updated to V1.9)
TasUnitBonus.UnitAdd supports now a second format for the bonusTable. instead of a map it can now be an array, it automatic chooses the currently used format when called. The array format takes less performance. The performance difference grows with the amount of stats one bonusTable includes.
map = {Str = 2, Agi = 7, Int = 5 }
array = {"Str",5, "Agi", 3, "Int", 2 }

Most of the addons use TasUnitBonus.UnitAdd. Therefore they also support the new format. The 2 formats can not be mixed in one bonusTable. As soon the conditions for array are fullfilled, then the map keys are ignored. The array conditions are the bonusTable has [1] & [2] and [1] is a string.
TasUnitBonusHero can only use the map format when you want that a hero gains bonuses at level x and also every level. With only a every level bonus you can use the array format. the bonus applied by Level x, can use the array format even when the hero bonusTable can not.
 
Last edited:
Top