• 🏆 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!

Watery Heal v1.0

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: Almia
"Water heals everything..."
Passively: Adds stacks over time to the hero with a cap of 5/7/9 stacks. One stack will be added every 5 seconds.

Actively: Places the water elemental to the targeted point that will heal all nearby targets every second by launching the projectile. All the stacks that hero had before the cast will be added to the summoned elemental and substracted from the caster. The amount of health restored with every hit is equal to 92 and increases by 14 per stack. The elemental will live for 10 seconds.

Cooldown: 18 seconds.

JASS:
// Constants

constant function WateryHeal_AbilityId takes nothing returns integer
    return 'A000'
endfunction

constant function WateryHeal_ElementalId takes nothing returns integer
    return 'h001'
endfunction

constant function WateryHeal_HealProjectileId takes nothing returns integer
    return 'h002'
endfunction

function WateryHeal_StacksCap takes integer abilLevel returns integer
    return 3 + 2 * abilLevel
endfunction

constant function WateryHeal_ElemCastAnimTime takes nothing returns real
    return 1.167
endfunction

constant function WateryHeal_ElemCastAnimName takes nothing returns string
    return "Attack"
endfunction

function WateryHeal_AnimSpeed takes nothing returns real
    return 1. / WateryHeal_ElemCastAnimTime()
endfunction

constant function WateryHeal_StackTime takes nothing returns real
    return 5.
endfunction

constant function WateryHeal_HealPeriod takes nothing returns real
    return 1.
endfunction

constant function WateryHeal_ElemLifeTime takes nothing returns real
    return 10.
endfunction

constant function WateryHeal_ElemHealRange takes nothing returns real
    return 650.
endfunction

constant function WateryHeal_ElemProjectileSpeed takes nothing returns real
    return 40.
endfunction

constant function WateryHeal_CollideDistance takes nothing returns real
    return 60.
endfunction

function WateryHeal_GetHealAmount takes integer elemStacks returns real
    return 92. + 14. * elemStacks
endfunction

function WateryHeal_IsUnitMatching takes unit abilityCaster, unit enumUnit returns boolean
    return IsUnitAlly(abilityCaster,GetOwningPlayer(enumUnit)) and not IsUnitType(enumUnit,UNIT_TYPE_DEAD) and not IsUnitType(enumUnit,UNIT_TYPE_STRUCTURE) 
endfunction

constant function WateryHeal_ElemBirthEffect takes nothing returns string
    return "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
endfunction

constant function WateryHeal_ElemDeathEffect takes nothing returns string
    return "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
endfunction

constant function WateryHeal_ElemHealTarget takes nothing returns string
    return "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl"
endfunction

constant function WateryHeal_DeScaleElem takes nothing returns string
    return "Objects\\Spawnmodels\\Naga\\NagaDeath\\NagaDeath.mdl"
endfunction

// Functions

function Trig_WateryHeal_ProjTimer takes nothing returns nothing
    local timer healTimer = GetExpiredTimer()
    local integer healTimerId = GetHandleId(healTimer)
    local unit abilityCaster = LoadUnitHandle(udg_WateryHeal_Hash,healTimerId,0)
    local unit healingProjectile = LoadUnitHandle(udg_WateryHeal_Hash,healTimerId,1)
    local unit healedUnit = LoadUnitHandle(udg_WateryHeal_Hash,healTimerId,2)
    local integer elemStacks = LoadInteger(udg_WateryHeal_Hash,healTimerId,3)
    local real projX = GetUnitX(healingProjectile)
    local real projY = GetUnitY(healingProjectile)
    local real healedX = GetUnitX(healedUnit)
    local real healedY = GetUnitY(healedUnit)
    local real curDistance = SquareRoot((healedX-projX)*(healedX-projX)+(healedY-projY)*(healedY-projY))
    local real curAngle = Atan2(healedY-projY,healedX-projX)
    local real newX = projX + WateryHeal_ElemProjectileSpeed() * Cos(curAngle)
    local real newY = projY + WateryHeal_ElemProjectileSpeed() * Sin(curAngle)
    
    if curDistance <= WateryHeal_CollideDistance() then
        call SetWidgetLife(healedUnit,GetWidgetLife(healedUnit)+WateryHeal_GetHealAmount(elemStacks))
        
        call DestroyEffect(AddSpecialEffect(WateryHeal_ElemHealTarget(),healedX,healedY))
        
        call KillUnit(healingProjectile)
        
        call FlushChildHashtable(udg_WateryHeal_Hash,healTimerId)
        call PauseTimer(healTimer)
        call DestroyTimer(healTimer)
    else
        call SetUnitX(healingProjectile,newX)
        call SetUnitY(healingProjectile,newY)
    endif
    
    set healTimer = null
    set abilityCaster = null
    set healingProjectile = null
    set healedUnit = null
endfunction

function Trig_WateryHeal_HealTimer takes nothing returns nothing
    local timer healTimer = GetExpiredTimer()
    local integer healTimerId = GetHandleId(healTimer)
    local unit abilityCaster = LoadUnitHandle(udg_WateryHeal_Hash,healTimerId,0)
    local unit waterElemental = LoadUnitHandle(udg_WateryHeal_Hash,healTimerId,1)
    local integer elemHandleId = GetHandleId(waterElemental)
    local integer elemStacks = LoadInteger(udg_WateryHeal_Hash,elemHandleId,WateryHeal_AbilityId())
    local real timePassed = LoadReal(udg_WateryHeal_Hash,healTimerId,2)+1
    local real scalePercent = LoadInteger(udg_WateryHeal_Hash,healTimerId,3)-255/WateryHeal_ElemLifeTime()
    local group healGroup
    local unit healedUnit
    local timer healProjTimer
    local integer healProjTimerId
    local unit healingProjectile

    call SaveReal(udg_WateryHeal_Hash,healTimerId,2,timePassed)
    
    if timePassed <= WateryHeal_ElemLifeTime() then
        call SaveInteger(udg_WateryHeal_Hash,healTimerId,3,R2I(scalePercent))
        call SetUnitVertexColor(waterElemental,255,255,255,R2I(scalePercent))
        call DestroyEffect(AddSpecialEffect(WateryHeal_DeScaleElem(),GetUnitX(waterElemental),GetUnitY(waterElemental)))
        
        call SetUnitAnimation(waterElemental,WateryHeal_ElemCastAnimName())
        
        set healGroup = CreateGroup()
        
        call GroupEnumUnitsInRange(healGroup,GetUnitX(waterElemental),GetUnitY(waterElemental),WateryHeal_ElemHealRange(),null)
        
        loop
            set healedUnit = FirstOfGroup(healGroup)
            exitwhen healGroup == null
            if WateryHeal_IsUnitMatching(abilityCaster,healedUnit) then
                set healProjTimer = CreateTimer()
                set healProjTimerId = GetHandleId(healProjTimer)
                set healingProjectile = CreateUnit(GetOwningPlayer(abilityCaster),WateryHeal_HealProjectileId(),GetUnitX(waterElemental),GetUnitY(waterElemental),0.)
                
                call SaveUnitHandle(udg_WateryHeal_Hash,healProjTimerId,0,abilityCaster)
                call SaveUnitHandle(udg_WateryHeal_Hash,healProjTimerId,1,healingProjectile)
                call SaveUnitHandle(udg_WateryHeal_Hash,healProjTimerId,2,healedUnit)  
                call SaveInteger(udg_WateryHeal_Hash,healProjTimerId,3,elemStacks) 
                
                call TimerStart(healProjTimer,.04,true,function Trig_WateryHeal_ProjTimer)    
            endif
            call GroupRemoveUnit(healGroup,healedUnit)
        endloop
        
        call DestroyGroup(healGroup)
    else
        call DestroyEffect(AddSpecialEffect(WateryHeal_ElemDeathEffect(),GetUnitX(waterElemental),GetUnitY(waterElemental)))
        call FlushChildHashtable(udg_WateryHeal_Hash,elemHandleId)
        call KillUnit(waterElemental)
        call FlushChildHashtable(udg_WateryHeal_Hash,healTimerId)
        call PauseTimer(healTimer)
        call DestroyTimer(healTimer)
    endif

    set healingProjectile = null
    set healProjTimer = null
    set healGroup = null
    set waterElemental = null
    set healTimer = null
    set abilityCaster = null
endfunction

function Trig_WateryHeal_Conditions takes nothing returns boolean
    local unit abilityCaster = GetTriggerUnit()
    local real stx = GetSpellTargetX()
    local real sty = GetSpellTargetY()
    local timer healTimer
    local integer healTimerId
    local unit waterElemental
    local integer heroStacks
    local integer heroHandleId
    local integer elemHandleId

    if GetSpellAbilityId() == WateryHeal_AbilityId() then
        // Creating the elemental.
        call DestroyEffect(AddSpecialEffect(WateryHeal_ElemBirthEffect(),stx,sty))
        set waterElemental = CreateUnit(GetTriggerPlayer(),WateryHeal_ElementalId(),stx,sty,GetRandomReal(0.,360.))
        // We need to set proper animation speed for this unit.
        call SetUnitTimeScale(waterElemental,WateryHeal_AnimSpeed())
        // Adding all the current stacks to the elem. 
        set heroHandleId = GetHandleId(abilityCaster)
        set elemHandleId = GetHandleId(waterElemental)
        set heroStacks = LoadInteger(udg_WateryHeal_Hash,heroHandleId,WateryHeal_AbilityId())
        call SaveInteger(udg_WateryHeal_Hash,elemHandleId,WateryHeal_AbilityId(),heroStacks)
        // Erasing the stacks from caster.
        call SaveInteger(udg_WateryHeal_Hash,heroHandleId,WateryHeal_AbilityId(),0)
        // Now, setting up the timer for periodic healing
        set healTimer = CreateTimer()
        set healTimerId = GetHandleId(healTimer)
        
        call SaveUnitHandle(udg_WateryHeal_Hash,healTimerId,0,abilityCaster)
        call SaveUnitHandle(udg_WateryHeal_Hash,healTimerId,1,waterElemental)
        call SaveReal(udg_WateryHeal_Hash,healTimerId,2,0.)
        call SaveInteger(udg_WateryHeal_Hash,healTimerId,3,255)
        
        call TimerStart(healTimer,WateryHeal_HealPeriod(),true,function Trig_WateryHeal_HealTimer)    
    endif
    
    set waterElemental = null
    set healTimer = null
    set abilityCaster = null
    return false
endfunction

function Trig_WateryHeal_Stacks takes nothing returns nothing
    local integer etId = GetHandleId(GetExpiredTimer()) 
    local group tempGroup = CreateGroup()
    local unit tempGroupUnit
    local integer curStacks
    local real timePassed
    local integer tguId
    
    call GroupEnumUnitsInRect(tempGroup,bj_mapInitialPlayableArea,null)
    
    loop
        set tempGroupUnit = FirstOfGroup(tempGroup)
        exitwhen tempGroupUnit == null
        if GetUnitAbilityLevel(tempGroupUnit,WateryHeal_AbilityId()) > 0 then
            set tguId = GetHandleId(tempGroupUnit)
            set timePassed = LoadReal(udg_WateryHeal_Hash,etId,tguId)
            if timePassed >= WateryHeal_StackTime() then
                // Stack Gain.
                set curStacks = LoadInteger(udg_WateryHeal_Hash,tguId,WateryHeal_AbilityId())
                if curStacks < WateryHeal_StacksCap(GetUnitAbilityLevel(tempGroupUnit,WateryHeal_AbilityId())) then
                    call SaveInteger(udg_WateryHeal_Hash,tguId,WateryHeal_AbilityId(),curStacks+1)
                endif 
                call SaveReal(udg_WateryHeal_Hash,etId,tguId,0.)
            else
                call SaveReal(udg_WateryHeal_Hash,etId,tguId,timePassed+1.) 
            endif
        endif
        call GroupRemoveUnit(tempGroup,tempGroupUnit)
    endloop
    
    call DestroyGroup(tempGroup)
    
    set tempGroup = null
endfunction

//===========================================================================
function InitTrig_WateryHeal takes nothing returns nothing
    local trigger tempTrigger = CreateTrigger()
    
    set udg_WateryHeal_Hash = InitHashtable()
    
    call TriggerRegisterAnyUnitEventBJ(tempTrigger,EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(tempTrigger,Condition(function Trig_WateryHeal_Conditions))
    
    call TimerStart(CreateTimer(),1.,true,function Trig_WateryHeal_Stacks)
    
    set tempTrigger = null
endfunction

Importing this spell in your map is easy!

1. Copy Watery Heal [Hero Spell] from Spells tab in Object Editor to your map;
2. Copy Water Elemental [Watery Heal] and Water Healing Projectile [Watery Heal] from Units tab in Object Editor to your map;
3. Create WateryHeal_Hash variable in your map. Place Hashtable as its type.
4. Copypaste Watery Heal folder from Trigger Editor to your map.
5. Configure the spell using constant functions inside Watery Heal trigger.
6. Add Simplar Duoson ([DUOS], Duosora, bowser499) to the Credits section of your map.
7. Enjoy using this nice spell!

Simplar Duoson © 2013


WateryHeal_AbilityId - The RAW code of Watery Heal [Hero Spell]
WateryHeal_ElementalId - The RAW code of Water Elemental [Watery Heal]
WateryHeal_HealProjectileId - The RAW code of Water Healing Projectile [Watery Heal]
WateryHeal_StacksCap - The maximal amount of stacks that can be accumulated
WateryHeal_ElemCastAnimTime - The time required for playing necessary animation
WateryHeal_ElemCastAnimName - The name of necessary animation
WateryHeal_AnimSpeed - The speed of animation (calculations placed there)
WateryHeal_StackTime - The time between accumulations of 1 stack
WateryHeal_HealPeriod - The time between two heals
WateryHeal_ElemLifeTime - The time of elemental's life
WateryHeal_ElemHealRange - The range where the units are picked for healing
WateryHeal_ElemProjectileSpeed - The amount of distance that the projectile will pass every 0.04 seconds
WateryHeal_CollideDistance - The distance between the projectile and the target that is required for killing the projectile and receiving the heal
WateryHeal_GetHealAmount - The amount that will be healed
WateryHeal_IsUnitMatching - The conditions for enumerated healing targets are there
WateryHeal_ElemBirthEffect - The effect path for elemental's birth
WateryHeal_ElemDeathEffect - The effect path for elemental's death
WateryHeal_ElemHealTarget - The effect path for healed target
WateryHeal_DeScaleElem - The effect path for every healing tick. The effect will be placed on the elemental on each releasing of heals


Keywords:
water, heal, bowser499, duos, simplar duoson, spell, passive, aoe, blue, violet, cold, opacity, math, hardcode, jesp
Contents

Watery Heal v1.0 (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 20:09, 10th Apr 2013 Magtheridon96: Comments You can get rid of the SquareRoot call in your distance calculation in the Trig_WateryHeal_ProjTimer function. Instead of comparing...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.

20:09, 10th Apr 2013
Magtheridon96:

Comments

  • You can get rid of the SquareRoot call in your distance calculation in the Trig_WateryHeal_ProjTimer function. Instead of comparing curDistance to WateryHeal_CollideDistance(), you would compare it to WateryHeal_CollideDistance()*WateryHeal_CollideDistance().
  • You should probably move these:
    JASS:
    set healingProjectile = null
    set healProjTimer = null
    set healGroup = null
    To the inside of the codeblock corresponding to the "Then" actions of the first if block because those variables are not changed unless that block of code runs.
  • If you want, you can implement a dummy recycler to allow for more efficient execution (Way better than creating a ton of units. CreateUnit is a very slow function call as we all know)
  • You should be able to use a global group variable created once instead of a local group inside the Trig_WateryHeal_ProjTimer function.
  • Since you're repeating GetOwningPlayer(abilityCaster), GetUnitX(waterElemental), and GetUnitY(waterElemental) inside the loop, you should cache them into variables. It's a waste to call them again and again and again for all created projectiles. You should probably only cache them inside the "Then" code block though. You don't need those cached variables unless that block of code actually runs.
  • In the function that runs upon casting the spell, you should only cache the triggering unit and the spell target coordinates inside the if block because you don't need them outside of it. You would also null them inside of it after caching them inside.
  • The Trig_WateryHeal_Stacks functions runs every second even if the spell was never learned. Moreover, it iterates over all units in the map. This is terribly inefficient. You can speed this up greatly by having a static global group variable. Register an Ability-Learn event. Whenever a unit learns the right ability, add him to the group. The code may still run every second, but at least it wouldn't be as intensive as it was before. In fact, you don't even need a group for this. You can use a unit array and a count. Whenever a unit learns the right ability, add him to the array. Iterate over the units in the array every second and do what you have to do. This is WAY better in performance than doing a group enumeration over every single unit in the map every second.
  • GetHandleId(GetExpiredTimer()) will return a constant value in the Stacks function because you're creating the timer once on map initialization. This means that you can totally get rid of this call and just use any constant value like $ffffff (Yes, I believe that's valid JASS)
 
I think using timers per instance is bad.You might wanna use ForGroup if you have too or just a dynamic array or linked list.

You can also use a dynamic group(one group for all instances) so that you will only run one timer(you can see examples from mine and Tank's resources)

Your math expressions are not done like that.
+,- should have spaces before/after them
*,/ dont.

Loop should be 0.03125,though that's not necessary.

RGB should be configurable.

GetRandomReal(0,360) must be 0 to 359 or 1 to 360,because in degrees,360 = 0

GetUnitAbilityLevel(tempGroupUnit,WateryHeal_AbilityId())
Cache this.
 
Top