• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] Life Break v1.0m

A DotA spell, for a change :p Inform me if you happen to find any bugs.

LIFE BREAK

By reaching into the well of his own life force, the caster heroically charges at a target with divine magical protection and sacrifices a portion of his current HP to decimate the foe and render it outcold for 2 seconds upon arrival. Charge speed improves with level.

Level 1 - Pays 35% of current HP. Deals damage equal to 50% of current enemy HP.
Level 2 - Pays 30% of current HP. Deals damage equal to 55% of current enemy HP.
Level 3 - Pays 25% of current HP. Deals damage equal to 60% of current enemy HP.
Level 4 - Pays 20% of current HP. Deals damage equal to 65% of current enemy HP.

Credit:
+ The IceFrog team for the spell idea.
+ Anitarf for IsTerrainWalkable (I converted it to a JASS version)

Notes:
+ The caster's self-inflicted damage is Direct HP Removal.
+ Cast range shouldn't be too high since I didn't pause the caster to keep the original's spirit. PropWindow and TurnSpeed solved this problem.
+ The charge stops if the target moves over 1400 units in 0.03125s
+ The new Smart Charge is experimental so there may be some bugs. Report any that you discover. There are always a chance that your unit will attempt to charge at units that are not reachable and then run out of "charge max try". It is completely coincidental. Bleh, Smart Charge nowd has an IQ of over 200.


+ udg_LB_Hashtable: Spell Hashtable
+ udg_LB_ItemArray: Item Array for item pathing check
+ udg_LB_Integer: Helper integer for item pathing check


JASS:
// CONFIGURATION

// Constants

// Main spell raw code
constant function LB_GetSpellID takes nothing returns integer
    return 'A005'
endfunction

// Stun raw code
constant function LB_GetStunID takes nothing returns integer
    return 'A000'
endfunction

// Spell book raw code
constant function LB_GetSpellBookID takes nothing returns integer
    return 'A001'
endfunction

// Caster dummy raw code
constant function LB_DummyCasterID takes nothing returns integer
    return 'h000'
endfunction

// Target effect
constant function LB_Sfx takes nothing returns string
    return "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
endfunction

// Charge effect
constant function LB_ChargeSfx takes nothing returns string
    return "Abilities\\Spells\\Orc\\Shockwave\\ShockwaveMissile.mdl"
endfunction

// Caster effect
constant function LB_Sfx1 takes nothing returns string
    return "Objects\\Spawnmodels\\Human\\HumanBlood\\BloodElfSpellThiefBlood.mdl"
endfunction

// Attachment point
constant function LB_AttachPoint takes nothing returns string
    return "origin"
endfunction

// Movement interval
constant function LB_Interval takes nothing returns real
    return 0.03125
endfunction

// Booleans

// Should Smart Charge be enabled?

// True = enable Smart Charge, the Hero will choose another nearby valid unit to charge at
// if he is unable to reach the original target.

// False = disable Smart Charge
constant function LB_SmartCharge takes nothing returns boolean
    return true
endfunction

// Filter valid units for Smart Charge

function LB_FilterUnit takes unit caster, unit u returns boolean
    return IsUnitEnemy(u, GetOwningPlayer(caster)) and GetWidgetLife(u) > 0.405 and GetUnitAbilityLevel(u, 'Avul') == 0 and not IsUnitType (u, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType (u, UNIT_TYPE_STRUCTURE)
endfunction

// AoE check for Smart Charge
constant function LB_AoE takes nothing returns real
    return 600.
endfunction

// Get Abilities Level

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

// Level dependable values

// Main target damage
function LB_DamageEnemy takes integer level returns real
    return 45. + level*5.
endfunction

// Self damage
function LB_DamageSelf takes integer level returns real
    return 40. - level*5.
endfunction

// Charge Speed
function LB_Speed takes integer level returns real
    return (1000. + level*100.)/(1/LB_Interval())
endfunction

// Attack - Damage - Weapon type

// Attack type
function LB_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_HERO
endfunction

// Damage type
function LB_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_MAGIC
endfunction

// Weapon type
function LB_WeaponType takes nothing returns weapontype
    return WEAPON_TYPE_WHOKNOWS
endfunction

// END CONFIGURATION

// PRELOAD

function LB_Preload takes nothing returns nothing
    local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), LB_DummyCasterID(), 0, 0, 0.00)
    local integer i = 0

    call UnitAddAbility(u, LB_GetSpellID())
    call UnitAddAbility(u, LB_GetStunID())
    call UnitAddAbility(u, LB_GetSpellBookID())

    loop 
        call SetPlayerAbilityAvailable(Player(i), LB_GetSpellBookID(), false)
        set i = i + 1
        exitwhen i > 15
    endloop

    call RemoveUnit (u)

    set u = null
endfunction

// END PRELOAD

// PATHING CHECK

function LB_HideItems takes nothing returns nothing
    if IsItemVisible (GetEnumItem()) then
        set udg_LB_ItemArray[udg_LB_Integer] = GetEnumItem()
        call SetItemVisible (udg_LB_ItemArray[udg_LB_Integer], false)
        set udg_LB_Integer = udg_LB_Integer + 1
    endif
endfunction

function LB_IsPathable takes real x, real y, item i returns boolean
    local real x1
    local real y1

    call MoveRectTo (LoadRectHandle (udg_LB_Hashtable, -3, -1), x, y)
    call EnumItemsInRect (LoadRectHandle (udg_LB_Hashtable, -3, -1), null, function LB_HideItems)

    call SetItemPosition (i, x, y)
    set x1 = GetItemX(i) - x
    set y1 = GetItemY(i) - y

    call SetItemVisible(i, false)

    loop
        exitwhen udg_LB_Integer <= 0
        set udg_LB_Integer = udg_LB_Integer - 1
        call SetItemVisible (udg_LB_ItemArray[udg_LB_Integer], true)
        set udg_LB_ItemArray[udg_LB_Integer] = null
    endloop

    return x1*x1 + y1*y1 < 256 and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)

endfunction

// END PATHING CHECK

// SPELL FUNCTIONS

// Here we create a list of units that can be picked from

function LB_MakeList takes nothing returns boolean
    local unit u = GetFilterUnit()
    local integer i = LoadInteger(udg_LB_Hashtable, -4, 0)
    local integer ID = LoadInteger(udg_LB_Hashtable, -4, -3)
    local boolean b = LB_FilterUnit(LoadUnitHandle(udg_LB_Hashtable, -4, -2), u) and u != LoadUnitHandle(udg_LB_Hashtable, -4, -1) and not IsUnitInGroup(u, LoadGroupHandle(udg_LB_Hashtable, ID, 8))
    
    if b then
        set i = i + 1
        call SaveUnitHandle(udg_LB_Hashtable, -4, i, u)
    endif

    call SaveInteger(udg_LB_Hashtable, -4, 0, i)
    set u = null
    return false
endfunction

// The loop where we index, deindex, and recycle everything

function LB_TimerLoop takes nothing returns nothing
    local integer ID
    local integer listmax = LoadInteger(udg_LB_Hashtable, -1, 0 )
    local unit caster
    local unit target
    local unit dummy
    local integer i = 0
    local real x1
    local real y1
    local real x2
    local real y2
    local real x3
    local real y3
    local real x4
    local real y4
    local real angle
    local integer level
    local group g
    local integer i1
    local boolean b
    local group g1

    // Now we start looping through all the spell instances
    loop
        exitwhen i >= listmax
        // First we load the saved values of a spell instance
        set i = i + 1
        set ID = LoadInteger (udg_LB_Hashtable, -1, i)
        set caster = LoadUnitHandle (udg_LB_Hashtable, ID, 1)
        set target = LoadUnitHandle (udg_LB_Hashtable, ID, 2)
        set dummy = LoadUnitHandle (udg_LB_Hashtable, ID, 4)
        set level = LB_SpellLvl (caster)
        set x1 = GetUnitX (caster)
        set y1 = GetUnitY (caster)
        set x2 = GetUnitX (target)
        set y2 = GetUnitY (target)
        set g1 = LoadGroupHandle (udg_LB_Hashtable, ID, 8)

        if (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1) < 22500 then
            call UnitRemoveAbility(caster, LB_GetSpellBookID())
            call SetUnitTurnSpeed(caster, GetUnitDefaultTurnSpeed(caster))
            call DestroyEffect(AddSpecialEffectTarget(LB_Sfx1(), caster, LB_AttachPoint()))
            call DestroyEffect(AddSpecialEffectTarget(LB_Sfx(), target, LB_AttachPoint()))
            call DestroyEffect(LoadEffectHandle(udg_LB_Hashtable, ID, 3))
            call SetUnitState(caster, UNIT_STATE_LIFE, GetUnitState(caster, UNIT_STATE_LIFE)*(1 - (LB_DamageSelf(level)/100)))
            call UnitDamageTarget(caster, target, GetUnitState(target, UNIT_STATE_LIFE)*(LB_DamageEnemy(level)/100), false, false, LB_AttackType(), LB_DamageType(), LB_WeaponType())
            call IssueTargetOrder(caster, "attack", target)
            call UnitAddAbility(dummy, LB_GetStunID())
            call SetUnitX(dummy, x2)
            call SetUnitY(dummy, y2)
            call IssueTargetOrder(dummy, "thunderbolt", target)
            call KillUnit(dummy)
            call DestroyGroup (g1)

            if listmax != i then
                call SaveInteger (udg_LB_Hashtable, LoadInteger (udg_LB_Hashtable, -1, listmax), 0, i)
                call SaveInteger (udg_LB_Hashtable, -1, i, LoadInteger (udg_LB_Hashtable, -1, listmax))
                call RemoveSavedInteger (udg_LB_Hashtable, -1, listmax)
                set i = i - 1
            endif
            set listmax = listmax - 1
            call SaveInteger (udg_LB_Hashtable, -1, 0, listmax)

            if LoadInteger (udg_LB_Hashtable, 0, 0) + 1 == LoadInteger (udg_LB_Hashtable, 0, -1) then
                call FlushChildHashtable (udg_LB_Hashtable, 0)
                call PauseTimer (LoadTimerHandle (udg_LB_Hashtable, -2, 0))
            else
                call SaveInteger (udg_LB_Hashtable, 0, 0, LoadInteger (udg_LB_Hashtable, 0, 0) + 1)
                call SaveInteger (udg_LB_Hashtable, 0, LoadInteger (udg_LB_Hashtable, 0, 0), ID)
            endif

            call FlushChildHashtable (udg_LB_Hashtable, ID)
        else
            set b = LoadBoolean (udg_LB_Hashtable, ID, 7)

            if b then
                call UnitRemoveAbility(caster, LB_GetSpellBookID())
                call SetUnitTurnSpeed(caster, GetUnitDefaultTurnSpeed(caster))
                call KillUnit(dummy)
                call DestroyEffect(LoadEffectHandle(udg_LB_Hashtable, ID, 3))
                call DestroyGroup (g1)

                if listmax != i then
                    call SaveInteger (udg_LB_Hashtable, LoadInteger (udg_LB_Hashtable, -1, listmax), 0, i)
                    call SaveInteger (udg_LB_Hashtable, -1, i, LoadInteger (udg_LB_Hashtable, -1, listmax))
                    call RemoveSavedInteger (udg_LB_Hashtable, -1, listmax)
                    set i = i - 1
                endif

                set listmax = listmax - 1
                call SaveInteger (udg_LB_Hashtable, -1, 0, listmax)

                if LoadInteger (udg_LB_Hashtable, 0, 0) + 1 == LoadInteger (udg_LB_Hashtable, 0, -1) then
                    call FlushChildHashtable (udg_LB_Hashtable, 0)
                    call PauseTimer (LoadTimerHandle (udg_LB_Hashtable, -2, 0))
                else
                    call SaveInteger (udg_LB_Hashtable, 0, 0, LoadInteger(udg_LB_Hashtable, 0, 0) + 1)
                    call SaveInteger (udg_LB_Hashtable, 0, LoadInteger(udg_LB_Hashtable, 0, 0), ID)
                endif

                call FlushChildHashtable (udg_LB_Hashtable, ID)
            else
                if IsUnitType(caster, UNIT_TYPE_DEAD) then
                    call SaveBoolean (udg_LB_Hashtable, ID, 7, true)
                else
                    set angle = Atan2(y2-y1, x2-x1)
                    set x3 = LoadReal (udg_LB_Hashtable, ID, 5)
                    set y3 = LoadReal (udg_LB_Hashtable, ID, 6)
                    set x4 = x1 + (LB_Speed(level))*Cos(angle)
                    set y4 = y1 + (LB_Speed(level))*Sin(angle)
                    call SaveReal (udg_LB_Hashtable, ID, 5, x2)
                    call SaveReal (udg_LB_Hashtable, ID, 6, y2)

                    if IsUnitType(target, UNIT_TYPE_DEAD) or (x3-x2)*(x3-x2) + (y3-y2)*(y3-y2) > 1960000 or not LB_IsPathable(x4, y4, LoadItemHandle (udg_LB_Hashtable, -3, 0)) then

                        if LB_SmartCharge() then
                            call GroupAddUnit (g1, target)
                            set g = CreateGroup()
                            call SaveUnitHandle(udg_LB_Hashtable, -4, -1, target)
                            call SaveUnitHandle(udg_LB_Hashtable, -4, -2, caster)
                            call SaveInteger(udg_LB_Hashtable, -4, -3, ID)
                            call GroupEnumUnitsInRange(g, x1, y1, LB_AoE(), Condition(function LB_MakeList))
                            call DestroyGroup(g)
                            set g = null
                            set i1 = LoadInteger(udg_LB_Hashtable, -4, 0)

                            if i1 == 0 then
                                call SaveBoolean (udg_LB_Hashtable, ID, 7, true)
                            else
                                call SaveUnitHandle(udg_LB_Hashtable, ID, 2, LoadUnitHandle(udg_LB_Hashtable, -4, GetRandomInt(1, i1)))
                                call FlushChildHashtable(udg_LB_Hashtable, -4)
                            endif
                        else
                            call SaveBoolean (udg_LB_Hashtable, ID, 7, true)
                        endif

                    else

                        // Move the caster
                        call SetUnitX (caster, x4)
                        call SetUnitY (caster, y4)
                        call SetUnitFacing (caster, angle*bj_RADTODEG)
                    endif
                endif
            endif
        endif

    set dummy = null
    set caster = null
    set target = null
    set g1 = null
    endloop
endfunction

// Initiate function

function Trig_LB_Conditions takes nothing returns boolean
    local unit caster
    local unit target
    local integer ID
    local integer listmax

    if GetSpellAbilityId() == LB_GetSpellID() then
        set caster = GetTriggerUnit()
        call UnitAddAbility(caster, LB_GetSpellBookID())
        call SetUnitTurnSpeed (caster, 0)
        set target = GetSpellTargetUnit()
        set ID = LoadInteger (udg_LB_Hashtable, 0, 0)

        // Now we start indexing the main projectiles
        if ID>0 then
            call SaveInteger (udg_LB_Hashtable, 0, 0, ID - 1)
            set ID = LoadInteger (udg_LB_Hashtable, 0, ID)
        else
            set ID = LoadInteger (udg_LB_Hashtable, 0, -1) + 1
            call SaveInteger (udg_LB_Hashtable,0 , -1, ID)
            if ID == 1 then
                call TimerStart (LoadTimerHandle (udg_LB_Hashtable, -2, 0), LB_Interval(), true, function LB_TimerLoop)
            endif
        endif

        // Save the values related to this spell instance
        call SaveUnitHandle (udg_LB_Hashtable, ID, 1, caster)
        call SaveUnitHandle (udg_LB_Hashtable, ID, 2, target)
        call SaveEffectHandle (udg_LB_Hashtable, ID, 3, AddSpecialEffectTarget(LB_ChargeSfx(), caster, LB_AttachPoint()))
        call SaveUnitHandle (udg_LB_Hashtable, ID, 4, CreateUnit(GetOwningPlayer(caster), LB_DummyCasterID(), GetUnitX(caster), GetUnitY(caster), 0.00))
        call SaveReal (udg_LB_Hashtable, ID, 5, GetUnitX(target))
        call SaveReal (udg_LB_Hashtable, ID, 6, GetUnitY(target))
        call SaveGroupHandle (udg_LB_Hashtable, ID, 8, CreateGroup())

        set listmax = LoadInteger (udg_LB_Hashtable, -1, 0) + 1
        call SaveInteger(udg_LB_Hashtable, -1, 0, listmax )
        call SaveInteger (udg_LB_Hashtable, -1, listmax, ID)
        call SaveInteger (udg_LB_Hashtable, ID, 0, listmax)

        set caster = null
        set target = null
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Life_Break takes nothing returns nothing
    local trigger t = CreateTrigger()
    set udg_LB_Hashtable = InitHashtable()
    call LB_Preload()
    call SaveTimerHandle (udg_LB_Hashtable, -2, 0, CreateTimer())
    call SaveItemHandle (udg_LB_Hashtable, -3, 0, CreateItem('afac', 0, 0))
    call SaveRectHandle (udg_LB_Hashtable, -3, -1, Rect (0, 0, 128, 128))
    call SetItemVisible (LoadItemHandle (udg_LB_Hashtable, -3, 0), false)
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition( function Trig_LB_Conditions ) )
    set t = null
endfunction


+ v1.0: Initial release
+ v1.0a: Added pathing check.
+ v1.0b: Improved pathing check method to use only 1 item for each spell instance for all instances.
+ v1.0c: Fixed some bugs.
+ v1.0d: Improved path checking method (credit goes to Maker)
+ v1.0e: Improved code.
+ v1.0f: Updated to fix the issues addressed in the review. Added a new feature: Smart Charge.
+ v1.0g: Improved Smart Charge to be smarter.
+ v1.0h: Fixed an issue with the timer and indexing method.
+ v1.0i: Now utilized PropWindow and TurnSpeed for unlimited cast range.
+ v1.0j: Fixed a bug with TurnSpeed.
+ v1.0k: Improved pathing check method to properly detect and ignore items.
+ v1.0k: Fixed a prop window bug spotted by Maker.
+ v1.0m: Removed Prop Window.


Keywords:
huskar, sacred warrior, martyr, charge, suicide, dota, spell, jass
Contents

Life Break v1.0m (Map)

Reviews
Life Break v1.0m | Reviewed by Maker | 18th Apr 2013 APPROVED The spell is MUI and leakless You could remove saved handles before flushing child hashtable Life Break v1.0e - Reviewed by Maker...

Moderator

M

Moderator


Life Break v1.0m | Reviewed by Maker | 18th Apr 2013
APPROVED


126248-albums6177-picture66521.png


  • The spell is MUI and leakless
126248-albums6177-picture66523.png


  • You could remove saved handles
    before flushing child hashtable
[tr]



Life Break v1.0e - Reviewed by Maker - 12.02.2013

Approved
TRIGGERING

  • call LB_DummyCasterID in LB_Preload instead of using the raw code directly. The raw code will be most likely different when importing the spell into another map
  • Position exitwhen after SetAbilityAvailable in the preload to make it affect neutral passive (P15) also


GENERAL

It is another charge spell, so not too original. But it works well, it feels smooth and the effects and sounds are ok. The pathing check works too.
You could improve the importing instructions, you need to copy the abilities. The tooltip could be better also.


Life Break v1.0d - Reviewed by Maker - 11.02.2013

Required changes are coloured red

TRIGGERING

  • In LB_TimerLoop you are setting variables that you might not need, only set them if you need them. For example, calculate x4 and y4 only if you are going to set the casters x and y.
  • Use dummy caster id in LB_Preload


OBJECT EDITOR

Locust makes the dummy invulnerable, remove inlvunerability ability from it


GENERAL

Add importing intructions into the map file
You could separate the test trigger from the spell folder

 
Level 33
Joined
Mar 27, 2008
Messages
8,035
I just want to know few stuffs;
1. Is the caster magic-immune while charging ?
2. If #1 is correct, when do you damage the unit ? Right after you remove the immune, or before you remove the immune ?
3. If you damage it right after the removal of immune, then you're doing the right way since in DotA, Blademail returns the damage if the target activates it, and you know that Blademail does not work when the unit is magic-immune, right ?
 
Level 6
Joined
Dec 19, 2009
Messages
226
I just want to know few stuffs;
1. Is the caster magic-immune while charging ?
2. If #1 is correct, when do you damage the unit ? Right after you remove the immune, or before you remove the immune ?
3. If you damage it right after the removal of immune, then you're doing the right way since in DotA, Blademail returns the damage if the target activates it, and you know that Blademail does not work when the unit is magic-immune, right ?

Yea, in DotA the caster didn't even get magic-immune while charging too
 
Level 16
Joined
Dec 15, 2011
Messages
1,423
I just want to know few stuffs;
1. Is the caster magic-immune while charging ?
2. If #1 is correct, when do you damage the unit ? Right after you remove the immune, or before you remove the immune ?
3. If you damage it right after the removal of immune, then you're doing the right way since in DotA, Blademail returns the damage if the target activates it, and you know that Blademail does not work when the unit is magic-immune, right ?

  1. Yes.
  2. Magic immunity is removed right when the caster reached the target and before dealing damage.
  3. Indeed my friend.

Yea, in DotA the caster didn't even get magic-immune while charging too

The tooltip from DotA said he is magic immune when charging, never get the chance to test though :p
 
Level 33
Joined
Mar 27, 2008
Messages
8,035
But i doubt in DotA the damage to the caster is direct hp removal, because the damage is reduced by hood of defiance pretty much..
You are correct.
It is damaged by trigger, Doomlord.

Also if I'm not mistaken, you can prevent the self-damage if you on BKB.

That means, the magic-immune from BKB and magic-immune from Life Break is 2 different magic-immune base spells.
 
Level 13
Joined
Jul 2, 2015
Messages
872
Find and modify these lines

vJASS:
// Main target damage
function LB_DamageEnemy takes integer level returns real
    return 45. + level*5.
endfunction

// Self damage
function LB_DamageSelf takes integer level returns real
    return 40. - level*5.
endfunction
yeah but does 45 damage mean it takes away 45% hp? Because I tested it in my map and it did different damage based on unit
 
Level 37
Joined
Jul 22, 2015
Messages
3,485
yeah but does 45 damage mean it takes away 45% hp? Because I tested it in my map and it did different damage based on unit
No it doesn't. Look at the attack type and damage type and find some credible resource that shows how damage calculations work.
JASS:
// Attack type
function LB_AttackType takes nothing returns attacktype
    return ATTACK_TYPE_HERO
endfunction

// Damage type
function LB_DamageType takes nothing returns damagetype
    return DAMAGE_TYPE_MAGIC
endfunction
 
Top