• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[JASS] Mystic Storm v1.1i

A remake of the spell Eye of the Storm from DotA with various new features, which distinguishes it from its DotA counterpart.

Note: You should set the field "Art - Projectile Launch - Z" field of the Storm Dummy to about -30.00 for better visual. It is not really a compulsory thing though.

MYSTIC STORM

The Hero calls upon a powerful storm of crackling energy, which strikes weakened enemies periodically with deadly bolts of lightning. The storm is charged with malevolent will, and will seek out only the most injured targets for its armor shattering blasts. The storm lasts for 30/40/50/60 seconds or until the caster perishes. The range and number of units attacked improves with level.

Level 1 - 60 damage every 1.1s. Reduce 1 armor per hit. Attack 1 unit.
Level 2 - 70 damage every 0.95s. Reduce 2 armor per hit. Attack 2 units.
Level 3 - 80 damage every 0.8s. Reduce 3 armor per hit. Attack 3 units.
Level 4 - 90 damage every 0.65s. Reduce 4 armor per hit. Attack 4 units.

Credit:
+ The IceFrog team for the spell idea (and probably the storm cloud model)
+ iAyanami for his awesome technique to get any value with +1 +10 and +100
+ rulerofiron99 for helping me fix the -armor technical bug. Thanks very much.
+ Dr Super Good for helping me fix the issue with the MS_LifeCheck function, optimizing some codes and writing the random cooldown snippet.
+ Geries for introducing me to the concept of code optimization.
+ Radamantus for pointing out some facts for this noob :D


Multiple new and customizable stats such as:

+ Damage intensification rate
+ Attack and damage type
+ Storm attack range
+ Can attack structures or not
+ Can reduce structures' armor or not when they are attacked
+ ...



JASS:
// CONFIGURATION

// Constants

// Main Spell raw code
constant function MS_GetSpellID takes nothing returns integer
    return 'A000'
endfunction

// Dummy raw code
constant function MS_DummyID takes nothing returns integer
    return 'h001'
endfunction

// Dummy Lightning Spell raw code
constant function MS_LightningID takes nothing returns integer
    return 'A001'
endfunction

// Armor (-1) ability raw code
constant function MS_Armor1ID takes nothing returns integer
    return 'A002'
endfunction

// Armor (-10) ability raw code
constant function MS_Armor10ID takes nothing returns integer
    return 'A003'
endfunction

// Armor (-100) ability raw code
constant function MS_Armor100ID takes nothing returns integer
    return 'A009'
endfunction

// Spell Book raw code
constant function MS_SpellBookID takes nothing returns integer
    return 'A007'
endfunction

// Dummy lightning base order string
constant function MS_LightningOrder takes nothing returns string
    return "fingerofdeath"
endfunction

// Booleans

// Can attack structures or not?
constant function MS_AttackStrucs takes nothing returns boolean
    return true 
endfunction

// Can reduce structures' armor or not?
constant function MS_ReduceStrucArmor takes nothing returns boolean
    return true 
endfunction

// True = target the unit with lowest HP percentage, false = target unit with lowest HP amount
constant function MS_HPPercentage takes nothing returns boolean
    return true
endfunction

// Enable random interval or not?
constant function MS_RandomInterval takes nothing returns boolean
    return false
endfunction

// Minimum value for random interval
constant function MS_RandomIntervalMin takes nothing returns real
    return 1.00
endfunction

// Maximum value for random interval
constant function MS_RandomIntervalMax takes nothing returns real
    return 2.00
endfunction

// Filter valid units

function MS_FilterUnit takes unit caster, unit u returns boolean
    local boolean b = IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and not IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) and IsUnitVisible(u, GetOwningPlayer(caster))
    if MS_AttackStrucs() then
        return b
    endif

    return b and not IsUnitType (u, UNIT_TYPE_STRUCTURE)
endfunction

// Get Abilities Level

// Get the spell level of the caster
function MS_SpellLvl takes unit caster returns integer
    return GetUnitAbilityLevel (caster, MS_GetSpellID()) 
endfunction

// Get the armor (-1) level of the target
function MS_Armor1Lvl takes unit u returns integer
    return GetUnitAbilityLevel (u, MS_Armor1ID()) 
endfunction

// Get the armor (-10) level of the target
function MS_Armor10Lvl takes unit u returns integer
    return GetUnitAbilityLevel (u, MS_Armor10ID()) 
endfunction

// Get the armor (-100) level of the target
function MS_Armor100Lvl takes unit u returns integer
    return GetUnitAbilityLevel (u, MS_Armor100ID()) 
endfunction

// Level dependable values

// Spell Duration
function MS_Duration takes integer level returns real
    return 20 + level*10. 
endfunction

// Damage per strike
function MS_Damage takes integer level returns real
    return 50 + level*10. 
endfunction

// Armor reduced per strike
function MS_ArmorReduced takes integer level returns integer
    return level 
endfunction

// Max attack range of the storm
function MS_Range takes integer level returns real
    return 400 + level*100. 
endfunction

// Damage intensification rate i.e the increment of damage percentage with each successive attack
function MS_DmgPlusRate takes integer level returns real
    return 0.0
endfunction

// Cooldown between each strike
function MS_Cooldown takes integer level returns real
    return 1.25 - level*0.15
endfunction

// Max amount of units attacked per strike
function MS_MaxStrike takes integer level returns integer
    return level
endfunction

// Attack - Damage - Weapon type

function MS_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_HERO
endfunction

function MS_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_NORMAL
endfunction

function MS_WeaponType takes nothing returns weapontype
    return WEAPON_TYPE_WHOKNOWS
endfunction

// END CONFIGURATION

// Preload

function MS_Preload takes nothing returns nothing
    local unit dummy
    local integer i = 0

    set dummy  = CreateUnit(Player(0), MS_DummyID(), 0, 0, 0.00)
    call UnitAddAbility (dummy, MS_GetSpellID())
    call UnitAddAbility (dummy, MS_Armor10ID())
    call UnitAddAbility (dummy, MS_Armor10ID())
    call UnitAddAbility (dummy, MS_Armor100ID())
    call UnitAddAbility (dummy, MS_SpellBookID())

    loop 
        exitwhen i > 15
        call SetPlayerAbilityAvailable(Player(i), MS_SpellBookID(), false) // Disable the spell book
        set i = i + 1
    endloop

    call RemoveUnit (dummy)
    set dummy = null
endfunction

// End preload

// SPELL FUNCTIONS

// Modify the armor reduction value of the affected unit

function MS_ModifyArmor takes unit target, integer armorreduction returns nothing
    local integer array armor
    local integer level1 = MS_Armor1Lvl(target)
    local integer level2 = MS_Armor10Lvl(target)
    local integer level3 = MS_Armor100Lvl(target)
    local real armorreduce = level1+level2*10+level3*100
    local real armorcalc
    local integer tempdivider
    local integer tempint
    local integer i

    set armor[0] = MS_Armor100ID()
    set armor[1] = MS_Armor10ID()
    set armor[2] = MS_Armor1ID()
    if armorreduce == 0 then
         call UnitAddAbility(target, MS_SpellBookID()) // Add the spell book
         set armorcalc = armorreduce + armorreduction // Set the amount of armor to be modified
         if armorcalc == 0 then
             call UnitRemoveAbility (target, MS_SpellBookID())
         endif
         set i = 0
         set tempint = 0
         set tempdivider = 100
         loop
         exitwhen i == 3
              set tempint = R2I(armorcalc/tempdivider)
              call SetUnitAbilityLevel(target, armor[i], tempint+1)
              set armorcalc = armorcalc - I2R(tempint*tempdivider)
              set tempdivider = tempdivider/10
              set i = i + 1
         endloop
    else
         set armorreduce = level1+level2*10+level3*100-111 // Calculate the current armor reduction rate
         set armorcalc = armorreduce + armorreduction
         if armorcalc == 0 then
             call UnitRemoveAbility (target, MS_SpellBookID()) // Remove the spell book if the unit's armor reduction reaches 0
         endif
         set i = 0
         set tempint = 0
         set tempdivider = 100
         loop
         exitwhen i == 3
              set tempint = R2I(armorcalc/tempdivider)
              call SetUnitAbilityLevel(target, armor[i], tempint+1)
              set armorcalc = armorcalc - I2R(tempint*tempdivider)
              set tempdivider = tempdivider/10
              set i = i + 1
         endloop
    endif
endfunction

// Damage the target and initiate the armor reduction

function MS_DamageTarget takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer timerID = GetHandleId(t)
    local unit caster = LoadUnitHandle(udg_MS_Hashtable, timerID, 0)
    local unit target = LoadUnitHandle(udg_MS_Hashtable, timerID, 1)
    local integer id = LoadInteger (udg_MS_Hashtable, timerID, 2)
    local group g = LoadGroupHandle(udg_MS_Hashtable, id, 3)
    local real dmg = LoadReal (udg_MS_Hashtable, timerID, 3)
    local integer unitId = GetHandleId(target)
    local integer count = LoadInteger (udg_MS_Hashtable, unitId, id)
    local integer i = MS_SpellLvl(caster)

    // Damage the target and check how many armor should be reduced
    call UnitDamageTarget(caster, target, dmg, false, false, MS_AttackType(), MS_DamageType(), MS_WeaponType())

    if MS_ReduceStrucArmor() and IsUnitType(target, UNIT_TYPE_STRUCTURE) then
        call MS_ModifyArmor (target, MS_ArmorReduced(i))
        set count = count + MS_ArmorReduced(i)
        call SaveInteger (udg_MS_Hashtable, unitId, id, count)
    else

        if not IsUnitType (target, UNIT_TYPE_STRUCTURE) then
            call MS_ModifyArmor (target, MS_ArmorReduced(i))
            set count = count + MS_ArmorReduced(i)
            call SaveInteger (udg_MS_Hashtable, unitId, id, count)
        endif

    endif

    if IsUnitType (target, UNIT_TYPE_DEAD) then
        call UnitRemoveAbility (target, MS_SpellBookID())
    endif

    if g != null then 
        call GroupAddUnit (g, target)
    else
        set g = CreateGroup()
        call GroupAddUnit (g, target)
    endif

    // Increase the damage for the next hit
    set dmg = dmg*(1.00 + MS_DmgPlusRate(i)/100)
    call SaveGroupHandle(udg_MS_Hashtable, id, 3, g)
    call SaveReal (udg_MS_Hashtable, id, 4, dmg)

    // Clean Up
    call FlushChildHashtable(udg_MS_Hashtable, timerID)
    call PauseTimer(t)
    call DestroyTimer(t)

    set caster = null
    set target = null
    set g = null
    set t = null
endfunction

// Function to loop through units and choose the one with the lowest HP percentage or amount

function MS_LifeCheck takes unit caster, unit stormfx, integer id, real dmg returns nothing
    local real lifecheck
    local group g = CreateGroup()
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)
    local unit target
    local timer t
    local unit u
    local integer timerID
    local real r

    // Group units for valid units
    call GroupEnumUnitsInRange(g, x, y, MS_Range(MS_SpellLvl(caster)), null)

    loop
        set target = FirstOfGroup(g)
        exitwhen target == null or MS_FilterUnit(caster, target) and not IsUnitInGroup (target, LoadGroupHandle(udg_MS_Hashtable, id, 5))
        call GroupRemoveUnit(g, target)
    endloop

    // Loops through the chosen units for the target
    if target != null then
        set lifecheck = GetUnitState(target, UNIT_STATE_LIFE)

        if MS_HPPercentage() then
            set lifecheck = lifecheck/GetUnitState(target, UNIT_STATE_MAX_LIFE)
        endif

        loop
            set u = FirstOfGroup(g)
            exitwhen u == null

            if MS_FilterUnit(caster, u) and not IsUnitInGroup (u, LoadGroupHandle(udg_MS_Hashtable, id, 5)) then
                set r = GetUnitState(u, UNIT_STATE_LIFE)

                if MS_HPPercentage() then
                    set r = r/GetUnitState(u, UNIT_STATE_MAX_LIFE)
                endif

                if r<lifecheck then
                    set lifecheck = r
                    set target = u
                endif

            endif

            call GroupRemoveUnit(g, u)
        endloop

        set t = CreateTimer ()
        set timerID = GetHandleId(t)
        call SaveUnitHandle(udg_MS_Hashtable, timerID, 0, caster)
        call SaveUnitHandle(udg_MS_Hashtable, timerID, 1, target)
        call SaveInteger(udg_MS_Hashtable, timerID, 2, id)
        call SaveReal(udg_MS_Hashtable, timerID, 3, dmg)
        call TimerStart (t, 0.3, false, function MS_DamageTarget)
        call GroupAddUnit (LoadGroupHandle(udg_MS_Hashtable, id, 5), target)
        call IssueTargetOrder(stormfx, MS_LightningOrder(), target)

        set target  = null
        set t = null
    endif

    call DestroyGroup (g)
    set g = null
endfunction

// Periodical trigger for moving the dummy and initialize stuffs when the spell ends

function MS_Periodic takes nothing returns nothing
    local trigger trig = GetTriggeringTrigger()
    local integer trigID = GetHandleId (trig)
    local unit caster = LoadUnitHandle(udg_MS_Hashtable, trigID, 1)
    local unit stormfx = LoadUnitHandle(udg_MS_Hashtable, trigID, 2)
    local integer evalcount = GetTriggerEvalCount(trig)
    local real x = GetUnitX(caster)
    local real y = GetUnitY(caster)
    local group g
    local real dmg = LoadReal (udg_MS_Hashtable, trigID, 4)
    local timer t
    local integer count
    local unit u
    local integer unitid
    local integer i = 0

    // Check if the caster is dead or the spell duration expired to remove the dummy
    if(GetTriggerEventId()==EVENT_UNIT_DEATH)then

        if(GetDyingUnit() == caster)then
            call KillUnit (stormfx) // We kill the dummy if the caster is dead
        endif

        call DestroyGroup (LoadGroupHandle(udg_MS_Hashtable, trigID, 5))
        call KillUnit (stormfx)

        // Remove the armor reduction induced by the spell instance
        set g = LoadGroupHandle(udg_MS_Hashtable, trigID, 3)

        loop
            set u = FirstOfGroup (g)
            exitwhen u == null
            set unitid = GetHandleId (u)
            set count = LoadInteger (udg_MS_Hashtable, unitid, trigID)
            call MS_ModifyArmor (u, (-count))
            call GroupRemoveUnit(g, u)
        endloop

        call DestroyGroup (g)
        call FlushChildHashtable(udg_MS_Hashtable, trigID)
        set g = null
        call DisableTrigger (trig)
        call TriggerRemoveAction (trig, LoadTriggerActionHandle(udg_MS_Hashtable, trigID, 0))
        set trig = null
        call DestroyTrigger (GetTriggeringTrigger())

    else
        call GroupClear (LoadGroupHandle(udg_MS_Hashtable, trigID, 5))

        if evalcount == 1 or evalcount >= LoadInteger(udg_MS_Hashtable, trigID, 6) then

            loop
                exitwhen i == MS_MaxStrike(MS_SpellLvl(caster))
                call MS_LifeCheck(caster, stormfx, trigID, dmg)
                set i = i + 1
            endloop

            if MS_RandomInterval() then
                call SaveInteger (udg_MS_Hashtable, trigID, 6, evalcount + GetRandomInt(R2I(MS_RandomIntervalMin()/0.03125), R2I(MS_RandomIntervalMax()/0.03125)))
            else
                call SaveInteger (udg_MS_Hashtable, trigID, 6, evalcount + R2I(MS_Cooldown(MS_SpellLvl(caster))/0.03125))
            endif
        endif

        call SetUnitPosition (stormfx, x, y)
    endif

    set caster = null
    set stormfx = null
    set trig = null
endfunction

// Initiate function

function Trig_MS_Conditions takes nothing returns boolean 
    local trigger trig
    local unit caster
    local unit stormfx
    local real dmg
    local integer trigID

    if GetSpellAbilityId() == MS_GetSpellID() then
        set caster = GetTriggerUnit()
        set trig = CreateTrigger()
        set trigID = GetHandleId (trig)
        set stormfx = CreateUnit(GetTriggerPlayer(), MS_DummyID(), GetUnitX(caster), GetUnitY(caster), bj_UNIT_FACING)
        call UnitAddAbility(stormfx, MS_LightningID())
        call UnitApplyTimedLife (stormfx, 'BTLF', MS_Duration(MS_SpellLvl(caster)))
        call TriggerRegisterTimerEvent(trig, 0.03125, true)
        call TriggerRegisterUnitEvent(trig, caster, EVENT_UNIT_DEATH)
        call TriggerRegisterUnitEvent(trig, stormfx, EVENT_UNIT_DEATH)
        call SaveTriggerActionHandle(udg_MS_Hashtable , trigID, 0, TriggerAddAction( trig, function MS_Periodic ))
        call SaveUnitHandle(udg_MS_Hashtable, trigID, 1, caster)
        call SaveUnitHandle(udg_MS_Hashtable, trigID, 2, stormfx)
        call SaveReal(udg_MS_Hashtable, trigID, 4, MS_Damage(MS_SpellLvl(caster)))
        call SaveGroupHandle(udg_MS_Hashtable, trigID, 5, CreateGroup())
        call SaveInteger(udg_MS_Hashtable, trigID, 6, GetTriggerEvalCount(trig) + GetRandomInt(R2I(MS_RandomIntervalMin()/0.03125), R2I(MS_RandomIntervalMax()/0.03125)))
        set trig = null
        set stormfx = null
    endif
 
    set caster = null
    return false
endfunction

//===========================================================================
function InitTrig_Mystic_Storm takes nothing returns nothing
    local trigger MS = CreateTrigger()
    set udg_MS_Hashtable = InitHashtable ()
    call MS_Preload()
    call TriggerRegisterAnyUnitEventBJ(MS, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition( MS, Condition( function Trig_MS_Conditions ) )
    set MS = null
endfunction


+ The spell can only reduce a unit's armor up to 999.
+ Multiple instances of the spell resolves the armor debuff independently. However once a spell instance ends it means the armor debuff will be removed. This is a bit of a technical thing and I can't find any solution as of right now. If someone can help then I will be really grateful. Fixed, credit to rulerofiron99.



+ v1.0: Initial release
+ v1.0a: Added minor stuffs here and there to assist in importing. Function prefixes also added.
+ v1.1: Updated the code to follow JESP standard plus multiple adjustments (see code for more details)
+ v1.1a: Updated the code further including fixing some minor bugs.
+ v1.1b: Fixed some more bugs, especially the technical bug mentioned in the notes above (thanks rulerofiron99). In addition the dummy model now can play the death animation but is still removed upon dying.
+ v1.1c: Fixed another bug (first strike doesn't reduce armor). Thanks to Aeroblyctos for pointing that out.
+ v1.1d: Fixed one more bug that sometimes causes the storm to not pick the unit with the lowest HP percentage/amount plus optimizing the code somewhat.
+ ==Null fix==: Fix some issues with nulling.
+ v1.1e: Updated the code following Maker's helpful review.
+ v1.1f: Fixed a tiny little thing. Sorry I didn't notice it sooner :p
+ v1.1g: Cleaned the code and optimized some minor things.
+ v1.1h: Added configuration for multiple strikes simultaneously. Also code improvement :)
+ v1.1i: Added new feature: random cooldown. (tks DSG for his help)


Keywords:
dota, eye of the storm, lightning, storm, jass
Contents

Mystic Storm v1.1i (Map)

Reviews
A fine spell that performs ok. Suggested changes: Use only one dummy unit type Merge the preload trigger with the spell trigger You're using a variable named bool. Bool is a keyword, use some other name IsUnitEnemy(u, GetOwningPlayer(caster))...

Moderator

M

Moderator

Reviewed by Maker, Mystic Storm v1.1d, 3rd Jan 2013

A fine spell that performs ok.

Suggested changes:
  • Use only one dummy unit type
  • Merge the preload trigger with the spell trigger
  • You're using a variable named bool. Bool is a keyword, use some other name
  • IsUnitEnemy(u, GetOwningPlayer(caster)) == true
    ->
    IsUnitEnemy(u, GetOwningPlayer(caster))
  • The lightning's starting point could have a bit less z offset
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
There may be some misunderstandings here. That function is used to reduce armor so it is non-customizable. And what do you mean by prefixes?

And yeah I forgot to include how to change the Attack and Damage type. I am also at a loss about indention. Can you show me how to do it properly? I will update the spell later.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
I think that pure JASS is more versatile because it can be used in maps and campaigns alike. It is just my idea though.

EDIT: Just realized that my code doesn't follow the JESP standard. Not one single bit that is. I will re-upload the spell later. I see what you mean now Radamantus, thanks.
 
Last edited:
Review #2
1)Use levels for minus armor ability so it wont have too much abilities.
2)you dont need to have a function to represent the hashtable.
3)reduct the function name lengths.
Did you know?It takes time for the computer to interpret the name of a function??so the longer the function name is,the slower it will be interpreted.
4)You can cache this:
IsUnitEnemy(u, GetOwningPlayer(caster)) == true and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) == false and IsUnitVisible(u, GetOwningPlayer(caster)) == true
in a local boolean,so you wont write it again.
5)Indention is a bit messy.You have a block w/ 5 spaces,w/c should be 4.
6)call UnitDamageTarget(caster, target, dmg, true, false, Mystic_Storm_AttackType(), Mystic_Storm_DamageType(), Mystic_Storm_WeaponType())
I think the two booleans should be false.Also,we dont need weapontypes.


That's all i can see. Good Job!
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Review #2
1)Use levels for minus armor ability so it wont have too much abilities.
2)you dont need to have a function to represent the hashtable.
3)reduct the function name lengths.
Did you know?It takes time for the computer to interpret the name of a function??so the longer the function name is,the slower it will be interpreted.
4)You can cache this:
IsUnitEnemy(u, GetOwningPlayer(caster)) == true and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) == false and IsUnitVisible(u, GetOwningPlayer(caster)) == true
in a local boolean,so you wont write it again.
5)Indention is a bit messy.You have a block w/ 5 spaces,w/c should be 4.
6)call UnitDamageTarget(caster, target, dmg, true, false, Mystic_Storm_AttackType(), Mystic_Storm_DamageType(), Mystic_Storm_WeaponType())
I think the two booleans should be false.Also,we dont need weapontypes.


That's all i can see. Good Job!

Thanks for the nice review. However, allow me to point a few things out

1/ It is a part of the system I learned from iAyanami. I don't know if there is a more effective way to do that and correct me if I am wrong but as far as I know loading a few abilities with 10 levels is still much faster than loading a single ability with 30+ levels or so.

2/ Well it won't hurt to leave it there right? People can just input the variable right there.

3/, 4/ and 5/ Ah yes. All shall be done. Currently I am updating the code

6/ Yeah well both boolean should be false then. And about the weapon type I think that it is just an optional thingy. Maybe some dude will want to have a heavy slice sound FX there =))
 
Thanks for the nice review. However, allow me to point a few things out

1/ It is a part of the system I learned from iAyanami. I don't know if there is a more effective way to do that and correct me if I am wrong but as far as I know loading a few abilities with 10 levels is still much faster than loading a single ability with 30+ levels or so.

There is a better way of doing this, which is using binary values. With 8 abilities, you'll can have up to 2^8-1 = 255 min and max armor values. Each ability would have 2 levels, one representing the positive value, and the other representing the negative value. Going with the 8 abilities example, you'll have:

1 2 4 8 16 32 64 128

These are basically binary values which are essentially powers of 2. Add up all that together, gives you maximum of 255 or minimum of -255.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
There is a better way of doing this, which is using binary values. With 8 abilities, you'll can have up to 2^8-1 = 255 min and max armor values. Each ability would have 2 levels, one representing the positive value, and the other representing the negative value. Going with the 8 abilities example, you'll have:

1 2 4 8 16 32 64 128

These are basically binary values which are essentially powers of 2. Add up all that together, gives you maximum of 255 or minimum of -255.

How to do accomplish that then? Can you show me?

And btw thanks for your technique, it is splendid :D
 
Not one bit :D Explain it to me and I will try to understand.

Binaries are basically another way of representing numbers. Binaries only consists of 1's and 0's. For example:

1 = 1
2 = 10
4 = 100
8 = 1000
9 = 1001

It basically works with powers of 2. The right most digit is 2^0. Then the power increments by one as you traverse left. If that digit is a 1, you simply add that to the result. If it's a 0, you ignore it.

For example, take 9 (1001). The right most digit would be 2^0. Since the digit value is 1, you add it to the total. Current total would now be 0 + 2^0 = 1. Move on to the next left digit, which would be 2^1. Since the digit value is 0, you ignore it. Move on to the next left digit, which is 2^2. Again, digit value is 0, ignore it. Move on to the next left digit, which is 2^3. Since the digit value is 1 here, add it to the total. Total would be 1 + 2^3 = 8 = 9. Thus, 9 = 1001.

So applying this logic in WC3 is fairly simple. Each ability would represent one digit. Thus, with 8 abilities, the values would be: 1 2 4 8 16 32 64 128.

So let's say you need to apply an armor value of 22 to a unit. Following the 8 abilities example, you'll loop through 8 times, starting from the biggest value which is 128. So it will loop in this sequence 128->64->32->16->8->4->2->1. First, make a comparison so that the value (22) is bigger or equal to the current digit value, which is currently 128. Since 128 > 22, move on to the next digit. Since 64 > 22, move on to the next. Since 32 > 22, move on to the next. Since now the digit value (16) is less than the value (22), add the ability that gives the respective digit value bonus (16) to the unit. Take the value and minus away the digit value (22 - 16 = 4). Move on to the next digit. Digit (8) > value (4), thus move on. Digit (4) == value (4), thus add the ability that gives 4 armor, and perform the reduction (4 - 4 = 0). Since value is now 0, exit the whole loop. So you'll have a total of 16 + 4 armor, which nicely adds up to 22.

Take 71 as another example. Skips all the way until digit value is 64, since 64 < 71. Add ability that provides 64 armor, perform reduction (71 - 64 = 7). Skip all the way until digit value = 4. Add ability that provides 4 armor, perform reduction (7 - 4 = 3). Next digit value = 2, since 2 < 3, add ability that provides 2 armor, perform reduction (3 - 2 = 1). Next digit value = 1, since 1 == 1, add ability that provides 1 armor, perform reduction (1 - 1 = 0). That'll will total up to 64 + 4 + 2 + 1 = 71 armor.
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
Binaries are basically another way of representing numbers. Binaries only consists of 1's and 0's. For example:

1 = 1
2 = 10
4 = 100
8 = 1000
9 = 1001

It basically works with powers of 2. The right most digit is 2^0. Then the power increments by one as you traverse left. If that digit is a 1, you simply add that to the result. If it's a 0, you ignore it.

For example, take 9 (1001). The right most digit would be 2^0. Since the digit value is 1, you add it to the total. Current total would now be 0 + 2^0 = 1. Move on to the next left digit, which would be 2^1. Since the digit value is 0, you ignore it. Move on to the next left digit, which is 2^2. Again, digit value is 0, ignore it. Move on to the next left digit, which is 2^3. Since the digit value is 1 here, add it to the total. Total would be 1 + 2^3 = 8 = 9. Thus, 9 = 1001.

So applying this logic in WC3 is fairly simple. Each ability would represent one digit. Thus, with 8 abilities, the values would be: 1 2 4 8 16 32 64 128.

So let's say you need to apply an armor value of 22 to a unit. Following the 8 abilities example, you'll loop through 8 times, starting from the biggest value which is 128. So it will loop in this sequence 128->64->32->16->8->4->2->1. First, make a comparison so that the value (22) is bigger or equal to the current digit value, which is currently 128. Since 128 > 22, move on to the next digit. Since 64 > 22, move on to the next. Since 32 > 22, move on to the next. Since now the digit value (16) is less than the value (22), add the ability that gives the respective digit value bonus (16) to the unit. Take the value and minus away the digit value (22 - 16 = 4). Move on to the next digit. Digit (8) > value (4), thus move on. Digit (4) == value (4), thus add the ability that gives 4 armor, and perform the reduction (4 - 4 = 0). Since value is now 0, exit the whole loop. So you'll have a total of 16 + 4 armor, which nicely adds up to 22.

Take 71 as another example. Skips all the way until digit value is 64, since 64 < 71. Add ability that provides 64 armor, perform reduction (71 - 64 = 7). Skip all the way until digit value = 4. Add ability that provides 4 armor, perform reduction (7 - 4 = 3). Next digit value = 2, since 2 < 3, add ability that provides 2 armor, perform reduction (3 - 2 = 1). Next digit value = 1, since 1 == 1, add ability that provides 1 armor, perform reduction (1 - 1 = 0). That'll will total up to 64 + 4 + 2 + 1 = 71 armor.

Ah thanks very much I understood now. However would you be kind enough to show me an example JASS script on how to do that? I learned stuffs mostly through triggers :D
 
Last edited:
Level 5
Joined
Dec 9, 2012
Messages
125
Well I reckon its brilliant :D The spell itself is very op which I don't mind :) The cloud effect is quite nice, perhaps slightly less flashes would be more realistic. And the effect and the end of the cloud is wow. It's like a star collapsing its soo good. You should probably make it slightly less op by reducing the amount of time it stays.
Very, very good spell.
4.9/5
 
Top