Well, heres a little spellpack i started 12 hours before the deadline of the fourth hero contest. Needless to say that i didnt finish until today. Its still missing the item, but whatever.
I am proud to present to you the Disciple of Life, a supporting hero with a bit of a nuke side.
The spells themselves are rather unoriginal, i admit. Then again, i havent seen many submissions using DamageModifiers yet, so these may be a good demonstration.
First off: I used custom icons, because none of the Blizzard ones fit decently (imo). One of those icons is from WoW, i take permission as a given for that icon.
As for the other three icons, heres the permission:
TimedHandles (optionally, the spells can be configured to not require it)
Credits
- KelThuzad for the icons of Prolonged Life, Smiting Zeal and Reverse Damage
- Blizzard Entertainment for the icon of Seed of Life
- Anitarf for SpellEvent, Stack, DamageEvent and DamageModifiers
- Rising_Dusk for GroupUtils and IntuitiveBuffSystem
- grim001 for AutoIndex and AbilityPreload
- Captain_Griffen for LightLeaklessDamageDetect
- TriggerHappy187 for TimedHandles
- Vexorian for JassHelper, Table and TimerUtils
- Pipedream for Grimoire
- Pitzermike and MindWorX for JassNewGenPack
Changelog
25/12/2009 - Version 1.0.0
- initial release
28/12/2009 - Version 1.0.1
- multiple bugfixes to all abilities
- Prolonged life now restores to exactly the amount specified in the settings, not to the amount specified plus the units current life
- multiple Reverse Damage instances on the same unit stack (correctly) now, and dont overwrite each other.
- multiple instances of Prolonged Life on the same unit overwrite each other now. The instance with the highest level now stays on the unit.
- all spells based on buffs correctly terminate now if the unit was killed by KillUnit() (or some other way of killing a unit without damaging it).
25/01/2011 - Version 1.1.0
- made TimedHandles an optional requirement
- moved array setup functions into one function
- removed periodic checks for buffs, ProlongedLife and ReverseDamage will now leak a spell instance (which also is an instance of DamageModifier) when the user removes or kills a unit with an active instance of the spell.
- SeedOfLife now has more calibration options eg. a chance to trigger or healing a fraction of the damage dealt to the target of the spell.
- more/better documentation
- few more minor changes
28/01/2011 - Version 1.2.0
- Seed of Life, Prolonged Life and Reverse Damage are now the same spell, configured differently.
- Smiting Zeal now supports enforcing the mana cost. It will not trigger if the caster cant pay the additional cost, if you configured it accordingly.
- Reverse Damage now has a minimum damage, damage dealt to the target of this spell must be greater than or equal to 20.
08/02/2011 - Version 1.2.1
- fixed a bug with how chances are handled, the spells should actually work now.
And here are the spells:
Seed of Life
Learn Tooltip
Infuses the target with the seed of life. Units around it get healed whenever the target takes damage.
Lasts 7 seconds.
Level 1 - Units in a 200 radius get healed by 15 HP.
Level 2 - Units in a 275 radius get healed by 20 HP.
Level 3 - Units in a 350 radius get healed by 25 HP.
Mana Cost: 60/75/90
Cooldown: 10/10/10
Spell Code:
librarySeedOfLifeinitializerInitusesGroupUtils, DamageModifiers, SpellEvent, AutoIndex, IntuitiveBuffSystem, optionalTimedHandlesprivatekeywordData// DO NOT TOUCH!globalsprivateconstantintegerAID = 'A001'// ability triggering this. Any unit targeting ability is fine. Used BloodlustprivateconstantintegerBUFF_PLACER_AID = 'A004'// Ability placing BID on the unit this ability is added to. Based off of Slow Aura (Tornado).privateconstantintegerBID = 'B000'// buff placed by BUFF_PLACER_AID on the target unitprivateconstantstringFX = "Abilities\\Spells\\Human\\Heal\\HealTarget.mdl"// displayed on the target unit when the effect of this ability is triggered.privateconstantstringFX_ATTPT = "origin"privateconstantrealFX_DURATION = 1.833// 0 or less only plays the death animation // ignored if TimedHandles is not present in the mapprivateconstantstringTARGET_FX = ""// displayed on the target of this spellprivateconstantstringTARGET_FX_ATTPT = ""// where to display the effect on the target of this spellprivateconstantintegerPRIORITY = 0// when to trigger the effects of an instance of this spell when a unit receives damage // refer to DamageModifiers documentationprivaterealarrayDURATION// how long does the buff last?privaterealarrayAOE// Heals all units around the damaged target in this areaprivaterealarrayCHANCE// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%privateintegerarrayMAX_HEALINGS// heal at most this many times // zero or less for no limitendglobals// how long does the buff last?privatefunctionDurationtakesintegerlevelreturnsrealreturnDURATION[level]
endfunction// Heals all units around the damaged target in this areaprivatefunctionAoetakesintegerlevelreturnsrealreturnAOE[level]
endfunction// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%privatefunctionChancetakesintegerlevelreturnsrealreturnCHANCE[level]
endfunction// heal at most this many times // zero or less for no limitprivatefunctionMax_HealingstakesintegerlevelreturnsintegerreturnMAX_HEALINGS[level]
endfunctionprivatefunctionSetUpSpellDatatakesnothingreturnsnothing// how long does the buff last?setDURATION[1]=7setDURATION[2]=7setDURATION[3]=7// Heals all units around the damaged target in this areasetAOE[1]=200setAOE[2]=275setAOE[3]=350// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%setCHANCE[1]=1setCHANCE[2]=1setCHANCE[3]=1// heal at most this many times // zero or less for no limitsetMAX_HEALINGS[1]=0setMAX_HEALINGS[2]=0setMAX_HEALINGS[3]=0endfunction// if you want to get the targets current HP, use GetUnitLife(whichUnit)// damage dealt to a unit with this buff active needs to be greater than what this function returns to be blockedprivatefunctionMinimum_Damagetakesintegerlevel, unittargetreturnsrealreturn0.endfunction// how much damage to blockprivatefunctionDamage_Blockedtakesintegerlevel, unittarget, realdamagereturnsrealreturn0.endfunction// how much health to restore after blockingprivatefunctionHealth_Restoredtakesintegerlevel, unittarget, realdamagereturnsrealreturn10.+level*10.endfunction// how much health to heal units in the areaprivatefunctionAoe_Healtakesintegerlevel, unittarget, realdamagereturnsrealreturn10.+level*5.endfunction// only units matching these criteria will get healedprivatefunctionValidTargettakesunitu, unitcasterreturnsbooleanreturnIsUnitAlly(u, GetOwningPlayer(caster))/*
*/andIsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/andIsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/andIsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/endfunction//globalsprivateintegerBuffTypeendglobalsprivatestructDataextendsDamageModifierunitcasterunittargeteffecttargetFxintegerlevelintegerhealings=0staticthistypeTmpsstaticrealCurrentDamagestaticthistypearrayInstanceprivatemethodonDestroytakesnothingreturnsnothingsetInstance[GetUnitId(.target)]=0set.caster=nullset.target=nullifTARGET_FX!=""thencallDestroyEffect(.targetFx)
set.targetFx=nullendifendmethodprivatestaticmethodAoeHealEnumtakesnothingreturnsbooleanlocalunitu=GetFilterUnit()
ifu!=.Tmps.targetandValidTarget(u, .Tmps.caster) thencallSetUnitLife(u, GetUnitLife(u)+Aoe_Heal(.Tmps.level, .Tmps.target, .CurrentDamage))
staticifLIBRARY_TimedHandlesthenifFX_DURATION>0thencallDestroyEffectTimed(AddSpecialEffectTarget(FX, u, FX_ATTPT), FX_DURATION)
elsecallDestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endifelsecallDestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endifendifsetu=nullreturnfalseendmethodprivatemethodonDamageTakentakesunitorigin, realdamagereturnsreallocalrealblocked=0ifGetRandomReal(0,1)<=Chance(.level) anddamage+0.406>=Minimum_Damage(.level, .target) thenifValidTarget(.target, .caster) thencallSetUnitLife(.target, GetUnitLife(.target)+Health_Restored(.level, .target, damage))
staticifLIBRARY_TimedHandlesthenifFX_DURATION>0thencallDestroyEffectTimed(AddSpecialEffectTarget(FX, .target, FX_ATTPT), FX_DURATION)
elsecallDestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endifelsecallDestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endifsetblocked=Damage_Blocked(.level, .target, damage)
endififAoe(.level)>0.thenset.Tmps=thisset.CurrentDamage=damagecallGroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(.target), GetUnitY(.target), Aoe(.level), Condition(functionthistype.AoeHealEnum))
endifset.healings=.healings+1ifMax_Healings(.level)>0and.healings>=Max_Healings(.level) thencallUnitRemoveBuff(.target, BuffType)
endifreturn -blockedendifreturn0.endmethodstaticmethodcreatetakesunitcaster, unittargetreturnsthistypelocalthistypes=Instance[GetUnitId(target)]
ifs==0thensets=.allocate(target, PRIORITY)
sets.caster=castersets.target=targetifTARGET_FX!=""thensets.targetFx=AddSpecialEffectTarget(TARGET_FX, target, TARGET_FX_ATTPT)
endifsets.level=GetUnitAbilityLevel(caster, AID)
setInstance[GetUnitId(target)]=selseifs.level<GetUnitAbilityLevel(caster, AID) thensets.caster=castersets.healings=0sets.level=GetUnitAbilityLevel(caster, AID)
endifendifsetUnitAddBuff(caster, target, BuffType, Duration(s.level), s.level).data=sreturnsendmethodstaticmethodBuffRemovedtakesnothingreturnsnothingcallthistype(GetEventBuff().data).destroy()
endmethodendstructprivatefunctionCastResponsetakesnothingreturnsnothingcallData.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit)
endfunctionprivatefunctionInittakesnothingreturnsnothingcallRegisterSpellEffectResponse(AID, CastResponse)
setBuffType=DefineBuffType(BUFF_PLACER_AID, BID, 0, false, true, 0,0,Data.BuffRemoved)
callSetUpSpellData()
endfunctionendlibrary
Prolonged Life
Learn Tooltip
Prolongs the target units life once over the next 8 seconds.
Level 1 - Restores 10% of the targets maximum health.
Level 2 - Restores 20% on the targets maximum health.
Level 3 - Restores 30% on the targets maximum health.
Mana Cost: 100/120/140
Cooldown: 5/5/5
Spell Code:
libraryProlongedLifeinitializerInitusesGroupUtils, DamageModifiers, SpellEvent, AutoIndex, IntuitiveBuffSystem, optionalTimedHandlesprivatekeywordData// DO NOT TOUCH!globalsprivateconstantintegerAID = 'A000'// ability triggering this. Any unit targeting ability is fine. Used ChannelprivateconstantintegerBUFF_PLACER_AID = 'A005'// Ability placing BID on the unit this ability is added to. Based off of Slow Aura (Tornado).privateconstantintegerBID = 'B001'// buff placed by BUFF_PLACER_AID on the target unitprivateconstantstringFX = "Abilities\\Spells\\Items\\AIre\\AIreTarget.mdl"// displayed on the target unit when the effect of this ability is triggered.privateconstantstringFX_ATTPT = "origin"privateconstantrealFX_DURATION = 0.// 0 or less only plays the death animation // ignored if TimedHandles is not present in the mapprivateconstantstringTARGET_FX = "Abilities\\Spells\\Items\\HealingSalve\\HealingSalveTarget.mdl"// displayed on the target of this spellprivateconstantstringTARGET_FX_ATTPT = "head"// where to display the effect on the target of this spellprivateconstantintegerPRIORITY = 0// when to trigger the effects of an instance of this spell when a unit receives damage // refer to DamageModifiers documentationprivaterealarrayDURATION// how long does the buff last?privaterealarrayAOE// Heals all units around the damaged target in this areaprivaterealarrayCHANCE// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%privateintegerarrayMAX_HEALINGS// heal at most this many times // zero or less for no limitendglobals// how long does the buff last?privatefunctionDurationtakesintegerlevelreturnsrealreturnDURATION[level]
endfunction// Heals all units around the damaged target in this areaprivatefunctionAoetakesintegerlevelreturnsrealreturnAOE[level]
endfunction// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%privatefunctionChancetakesintegerlevelreturnsrealreturnCHANCE[level]
endfunction// heal at most this many times // zero or less for no limitprivatefunctionMax_HealingstakesintegerlevelreturnsintegerreturnMAX_HEALINGS[level]
endfunctionprivatefunctionSetUpSpellDatatakesnothingreturnsnothing// how long does the buff last?setDURATION[1]=8setDURATION[2]=8setDURATION[3]=8// Heals all units around the damaged target in this areasetAOE[1]=0setAOE[2]=0setAOE[3]=0// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%setCHANCE[1]=1setCHANCE[2]=1setCHANCE[3]=1// heal at most this many times // zero or less for no limitsetMAX_HEALINGS[1]=1setMAX_HEALINGS[2]=1setMAX_HEALINGS[3]=1endfunction// if you want to get the targets current HP, use GetUnitLife(whichUnit)// damage dealt to a unit with this buff active needs to be greater than what this function returns to be blockedprivatefunctionMinimum_Damagetakesintegerlevel, unittargetreturnsrealreturnGetUnitLife(target)
endfunction// how much damage to blockprivatefunctionDamage_Blockedtakesintegerlevel, unittarget, realdamagereturnsrealreturndamageendfunction// how much health to restore after blockingprivatefunctionHealth_Restoredtakesintegerlevel, unittarget, realdamagereturnsrealreturnGetUnitLife(target)*0.1*levelendfunction// how much health to heal units in the areaprivatefunctionAoe_Healtakesintegerlevel, unittarget, realdamagereturnsrealreturn0.endfunction// only units matching these criteria will get healedprivatefunctionValidTargettakesunitu, unitcasterreturnsbooleanreturnIsUnitAlly(u, GetOwningPlayer(caster))/*
*/andIsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/andIsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/andIsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/endfunction//globalsprivateintegerBuffTypeendglobalsprivatestructDataextendsDamageModifierunitcasterunittargeteffecttargetFxintegerlevelintegerhealings=0staticthistypeTmpsstaticrealCurrentDamagestaticthistypearrayInstanceprivatemethodonDestroytakesnothingreturnsnothingsetInstance[GetUnitId(.target)]=0set.caster=nullset.target=nullifTARGET_FX!=""thencallDestroyEffect(.targetFx)
set.targetFx=nullendifendmethodprivatestaticmethodAoeHealEnumtakesnothingreturnsbooleanlocalunitu=GetFilterUnit()
ifu!=.Tmps.targetandValidTarget(u, .Tmps.caster) thencallSetUnitLife(u, GetUnitLife(u)+Aoe_Heal(.Tmps.level, .Tmps.target, .CurrentDamage))
staticifLIBRARY_TimedHandlesthenifFX_DURATION>0thencallDestroyEffectTimed(AddSpecialEffectTarget(FX, u, FX_ATTPT), FX_DURATION)
elsecallDestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endifelsecallDestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endifendifsetu=nullreturnfalseendmethodprivatemethodonDamageTakentakesunitorigin, realdamagereturnsreallocalrealblocked=0ifGetRandomReal(0,1)<=Chance(.level) anddamage+0.406>=Minimum_Damage(.level, .target) thenifValidTarget(.target, .caster) thencallSetUnitLife(.target, GetUnitLife(.target)+Health_Restored(.level, .target, damage))
staticifLIBRARY_TimedHandlesthenifFX_DURATION>0thencallDestroyEffectTimed(AddSpecialEffectTarget(FX, .target, FX_ATTPT), FX_DURATION)
elsecallDestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endifelsecallDestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endifsetblocked=Damage_Blocked(.level, .target, damage)
endififAoe(.level)>0.thenset.Tmps=thisset.CurrentDamage=damagecallGroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(.target), GetUnitY(.target), Aoe(.level), Condition(functionthistype.AoeHealEnum))
endifset.healings=.healings+1ifMax_Healings(.level)>0and.healings>=Max_Healings(.level) thencallUnitRemoveBuff(.target, BuffType)
endifreturn -blockedendifreturn0.endmethodstaticmethodcreatetakesunitcaster, unittargetreturnsthistypelocalthistypes=Instance[GetUnitId(target)]
ifs==0thensets=.allocate(target, PRIORITY)
sets.caster=castersets.target=targetifTARGET_FX!=""thensets.targetFx=AddSpecialEffectTarget(TARGET_FX, target, TARGET_FX_ATTPT)
endifsets.level=GetUnitAbilityLevel(caster, AID)
setInstance[GetUnitId(target)]=selseifs.level<GetUnitAbilityLevel(caster, AID) thensets.caster=castersets.healings=0sets.level=GetUnitAbilityLevel(caster, AID)
endifendifsetUnitAddBuff(caster, target, BuffType, Duration(s.level), s.level).data=sreturnsendmethodstaticmethodBuffRemovedtakesnothingreturnsnothingcallthistype(GetEventBuff().data).destroy()
endmethodendstructprivatefunctionCastResponsetakesnothingreturnsnothingcallData.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit)
endfunctionprivatefunctionInittakesnothingreturnsnothingcallRegisterSpellEffectResponse(AID, CastResponse)
setBuffType=DefineBuffType(BUFF_PLACER_AID, BID, 0, false, true, 0,0,Data.BuffRemoved)
callSetUpSpellData()
endfunctionendlibrary
Smiting Zeal
Learn Tooltip
Every time the hero casts a spell, all unit surrounding the target point get damaged. Drains 20 additional Mana Points with every cast.
Level 1 - Damages for 2 times the hero's intelligence.
Level 2 - Damages for 3 times the hero's intelligence.
Level 3 - Damages for 4 times the hero's intelligence.
Spell Code:
librarySmitingZealinitializerInitusesSpellEvent, GroupUtilsglobalsprivateconstantintegerAID = 'A002'// any passive ability should do fine, preferably one that doesnt place a buff.privateconstantstringDAMAGE_FX = "Abilities\\Spells\\Human\\Feedback\\SpellBreakerAttack.mdl"// effect displayed when a unit gets damaged by this abilityprivateconstantstringDAMAGE_FX_ATTPT = "overhead"// where to display the effectprivateconstantattacktypeATTACK_TYPE = ATTACK_TYPE_MAGIC// what type of damage is dealt to enemy units surrounding the caster (magic, physical, pure, ...)privateconstantdamagetypeDAMAGE_TYPE = DAMAGE_TYPE_MAGIC// what type of damage is dealt to enemy units surrounding the caster (magic, physical, pure, ...)privateconstantweapontypeWEAPON_TYPE = WEAPON_TYPE_WHOKNOWS// sound played when a unit is damaged by this ability, WHOKNOWS or null for no sound.privaterealarrayAOE// enemy units within this radius get damagedprivaterealarrayMANA_COST// this ability drains this much additional mana from the caster, values >0 but <1 are values relative to the maximum mana of the casterprivateconstantbooleanENFORCE_MANA_COST = false// if true, the effects of this ability will only be triggered if the hero has enough mana,// if false, it will be triggered whether or not the caster has enough mana, and if he doesnt,// it will subtract the remaining mana pointsendglobalsprivatefunctionAoetakesintegerlevelreturnsrealreturnAOE[level]
endfunctionprivatefunctionMana_CosttakesintegerlevelreturnsrealreturnMANA_COST[level]
endfunctionprivatefunctionDamagetakesunitcaster, integerlevelreturnsrealreturnGetHeroInt(caster, true)*(1.+level)
endfunctionprivatefunctionSetUpSpellDatatakesnothingreturnsnothingsetAOE[1]=300.setAOE[2]=375.setAOE[3]=450.setMANA_COST[1]=20.setMANA_COST[2]=20.setMANA_COST[3]=20.endfunctionprivatefunctionValidateTargettakesunitu, unitcasterreturnsbooleanreturnIsUnitEnemy(u, GetOwningPlayer(caster))/*
*/andIsUnitType(u, UNIT_TYPE_DEAD)==false/*
*/andIsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/andIsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/andIsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/andIsUnitVisible(u, GetOwningPlayer(caster))/*
*/endfunction//globalsprivateboolexprDamageFilterprivaterealtmpdamendglobalsprivatefunctionDamageFilterFunctakesnothingreturnsbooleanlocalunitu=GetFilterUnit()
ifValidateTarget(u, SpellEvent.CastingUnit) thencallUnitDamageTarget(SpellEvent.CastingUnit, u, tmpdam, true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
callDestroyEffect(AddSpecialEffectTarget(DAMAGE_FX, u, DAMAGE_FX_ATTPT))
endifsetu=nullreturnfalseendfunctionprivatefunctionCastResponsetakesnothingreturnsnothinglocalintegerlevel = GetUnitAbilityLevel(SpellEvent.CastingUnit, AID)
localrealcost=0iflevel>0thenifMana_Cost(level)>=1thensetcost=Mana_Cost(level)
elseifMana_Cost(level)>0thensetcost=Mana_Cost(level)*GetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA)
endififnotENFORCE_MANA_COSTorGetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA)>=costthencallSetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA, RMaxBJ(GetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA)-Mana_Cost(level), 0))
settmpdam=Damage(SpellEvent.CastingUnit, level)
callGroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(SpellEvent.CastingUnit), GetUnitY(SpellEvent.CastingUnit), Aoe(level), DamageFilter)
endifendifendfunctionprivatefunctionInittakesnothingreturnsnothingcallRegisterSpellEffectResponse(0, CastResponse)
setDamageFilter=Condition(functionDamageFilterFunc)
callSetUpSpellData()
endfunctionendlibrary
Reverse Damage
Learn Tooltip
Surrounds the target unit with a shield which reverses the next damage greater than 20 within 5 seconds the unit would receive and instead heals the unit for the same amount.
Maximum reversed damage is 100.
Mana Cost: 25
Cooldown: 2
Spell Code:
libraryReverseDamageinitializerInitusesGroupUtils, DamageModifiers, SpellEvent, AutoIndex, IntuitiveBuffSystem, optionalTimedHandlesprivatekeywordData// DO NOT TOUCH!globalsprivateconstantintegerAID = 'A003'// ability triggering this. Any unit targeting ability is fine. Used BloodlustprivateconstantintegerBUFF_PLACER_AID = 'A006'// Ability placing BID on the unit this ability is added to. Based off of Slow Aura (Tornado).privateconstantintegerBID = 'B002'// buff placed by BUFF_PLACER_AID on the target unitprivateconstantstringFX = "Abilities\\Spells\\Undead\\ReplenishHealth\\ReplenishHealthCasterOverhead.mdl"// displayed on the target unit when the effect of this ability is triggered.privateconstantstringFX_ATTPT = "head"privateconstantrealFX_DURATION = 0.// 0 or less only plays the death animation // ignored if TimedHandles is not present in the mapprivateconstantstringTARGET_FX = "Abilities\\Spells\\Items\\HealingSalve\\HealingSalveTarget.mdl"// displayed on the target of this spellprivateconstantstringTARGET_FX_ATTPT = "head"// where to display the effect on the target of this spellprivateconstantintegerPRIORITY = 0// when to trigger the effects of an instance of this spell when a unit receives damage // refer to DamageModifiers documentationprivaterealarrayDURATION// how long does the buff last?privaterealarrayAOE// Heals all units around the damaged target in this areaprivaterealarrayCHANCE// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%privateintegerarrayMAX_HEALINGS// heal at most this many times // zero or less for no limitendglobals// how long does the buff last?privatefunctionDurationtakesintegerlevelreturnsrealreturnDURATION[level]
endfunction// Heals all units around the damaged target in this areaprivatefunctionAoetakesintegerlevelreturnsrealreturnAOE[level]
endfunction// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%privatefunctionChancetakesintegerlevelreturnsrealreturnCHANCE[level]
endfunction// heal at most this many times // zero or less for no limitprivatefunctionMax_HealingstakesintegerlevelreturnsintegerreturnMAX_HEALINGS[level]
endfunctionprivatefunctionSetUpSpellDatatakesnothingreturnsnothing// how long does the buff last?setDURATION[1]=5setDURATION[2]=5setDURATION[3]=5// Heals all units around the damaged target in this areasetAOE[1]=0setAOE[2]=0setAOE[3]=0// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%setCHANCE[1]=1setCHANCE[2]=1setCHANCE[3]=1// heal at most this many times // zero or less for no limitsetMAX_HEALINGS[1]=1setMAX_HEALINGS[2]=1setMAX_HEALINGS[3]=1endfunction// if you want to get the targets current HP, use GetUnitLife(whichUnit)// damage dealt to a unit with this buff active needs to be greater than what this function returns to be blockedprivatefunctionMinimum_Damagetakesintegerlevel, unittargetreturnsrealreturn20.endfunction// how much damage to blockprivatefunctionDamage_Blockedtakesintegerlevel, unittarget, realdamagereturnsrealreturnRMinBJ(damage, 100.)
endfunction// how much health to restore after blockingprivatefunctionHealth_Restoredtakesintegerlevel, unittarget, realdamagereturnsrealreturnRMinBJ(damage, 100.)
endfunction// how much health to heal units in the areaprivatefunctionAoe_Healtakesintegerlevel, unittarget, realdamagereturnsrealreturn0.endfunction// only units matching these criteria will get healedprivatefunctionValidTargettakesunitu, unitcasterreturnsbooleanreturnIsUnitAlly(u, GetOwningPlayer(caster))/*
*/andIsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/andIsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/andIsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/endfunction//globalsprivateintegerBuffTypeendglobalsprivatestructDataextendsDamageModifierunitcasterunittargeteffecttargetFxintegerlevelintegerhealings=0staticthistypeTmpsstaticrealCurrentDamagestaticthistypearrayInstanceprivatemethodonDestroytakesnothingreturnsnothingsetInstance[GetUnitId(.target)]=0set.caster=nullset.target=nullifTARGET_FX!=""thencallDestroyEffect(.targetFx)
set.targetFx=nullendifendmethodprivatestaticmethodAoeHealEnumtakesnothingreturnsbooleanlocalunitu=GetFilterUnit()
ifu!=.Tmps.targetandValidTarget(u, .Tmps.caster) thencallSetUnitLife(u, GetUnitLife(u)+Aoe_Heal(.Tmps.level, .Tmps.target, .CurrentDamage))
staticifLIBRARY_TimedHandlesthenifFX_DURATION>0thencallDestroyEffectTimed(AddSpecialEffectTarget(FX, u, FX_ATTPT), FX_DURATION)
elsecallDestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endifelsecallDestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endifendifsetu=nullreturnfalseendmethodprivatemethodonDamageTakentakesunitorigin, realdamagereturnsreallocalrealblocked=0ifGetRandomReal(0,1)<=Chance(.level) anddamage+0.406>=Minimum_Damage(.level, .target) thenifValidTarget(.target, .caster) thencallSetUnitLife(.target, GetUnitLife(.target)+Health_Restored(.level, .target, damage))
staticifLIBRARY_TimedHandlesthenifFX_DURATION>0thencallDestroyEffectTimed(AddSpecialEffectTarget(FX, .target, FX_ATTPT), FX_DURATION)
elsecallDestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endifelsecallDestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endifsetblocked=Damage_Blocked(.level, .target, damage)
endififAoe(.level)>0.thenset.Tmps=thisset.CurrentDamage=damagecallGroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(.target), GetUnitY(.target), Aoe(.level), Condition(functionthistype.AoeHealEnum))
endifset.healings=.healings+1ifMax_Healings(.level)>0and.healings>=Max_Healings(.level) thencallUnitRemoveBuff(.target, BuffType)
endifreturn -blockedendifreturn0.endmethodstaticmethodcreatetakesunitcaster, unittargetreturnsthistypelocalthistypes=Instance[GetUnitId(target)]
ifs==0thensets=.allocate(target, PRIORITY)
sets.caster=castersets.target=targetifTARGET_FX!=""thensets.targetFx=AddSpecialEffectTarget(TARGET_FX, target, TARGET_FX_ATTPT)
endifsets.level=GetUnitAbilityLevel(caster, AID)
setInstance[GetUnitId(target)]=selseifs.level<GetUnitAbilityLevel(caster, AID) thensets.caster=castersets.healings=0sets.level=GetUnitAbilityLevel(caster, AID)
endifendifsetUnitAddBuff(caster, target, BuffType, Duration(s.level), s.level).data=sreturnsendmethodstaticmethodBuffRemovedtakesnothingreturnsnothingcallthistype(GetEventBuff().data).destroy()
endmethodendstructprivatefunctionCastResponsetakesnothingreturnsnothingcallData.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit)
endfunctionprivatefunctionInittakesnothingreturnsnothingcallRegisterSpellEffectResponse(AID, CastResponse)
setBuffType=DefineBuffType(BUFF_PLACER_AID, BID, 0, false, true, 0,0,Data.BuffRemoved)
callSetUpSpellData()
endfunctionendlibrary
The way you code the buff using a WC3 buff spell and then checking periodically if it is still there is very inelegant. An entirely triggered buff would be preferred.
TimedHandles could be made into an optional requirement.
The redundancy in the calibration section is a bit excessive. Either make your calibration array-based or function-based, not both.
If you make it array-based, there is no need for each property to have its own calibration function.
The spell documentation should state that the base spell is expected to be a single target buff spell (or whatever it is expected to be). Just naming two constants AID and BID is not particularly descriptive. In general, the calibration sections could use better documentation.
The spells are quite simple.
Seed of Life:
u!=s.target This should not be a part of the calibration function, but a part of the code, since the unit does in fact get healed, you just do it through the damage modifier instead of through the GroupEnum. (there is no particular reason for this complication, though)
Prolonged Life:
Considering you cleanup the damage modifier in the damage callback anyway, the whole periodic checking is not really needed.
Smiting Zeal:
Here is where I think function-based calibration beats array-based: it makes it easy for the user to either have attribute-based or absolute damage values simply by editing the formula. In your case, as long as the caster is a hero, the calibration only allows attribute-based damage.
Reverse damage:
Considering you cleanup the damage modifier in the damage callback anyway, the whole periodic checking is not really needed.
The way you code the buff using a WC3 buff spell and then checking periodically if it is still there is very inelegant. An entirely triggered buff would be preferred.
My thinking was that native WC3 buffs can be purged, something you cant do with auras (Tornado Slow Aura is the base for triggered buffs, right?)Will be done
Quote:
TimedHandles could be made into an optional requirement.
What do you mean, coding the delayed destruction myself?
Quote:
The redundancy in the calibration section is a bit excessive. Either make your calibration array-based or function-based, not both.
I know a lot of people prefer function calibration. I just wanted to make transition between each way easier. Ill move all SetUp functions into a single one, though.
Quote:
The spell documentation should state that the base spell is expected to be a single target buff spell (or whatever it is expected to be). Just naming two constants AID and BID is not particularly descriptive. In general, the calibration sections could use better documentation.
Okay, gonna add a few sentences.
Quote:
This should not be a part of the calibration function, but a part of the code
Okay, will change that. Btw, i remember not healing the target of Seed of Life separately. I also remember that that led to certain bugs, so that distinction is necessary.
Quote:
Considering you cleanup the damage modifier in the damage callback anyway, the whole periodic checking is not really needed.
There are certain situations (ie. the user removes/kill a target of Prolonged Life), where not checking periodically would leak the instance of Prolonged Life. Same applies to Reverse Damage.
@Smiting Zeal: Ill see what i can do to improve it.
The ValidHealTarget function, being a calibration function, should not take an argument of type Data since that's part of internal spell code and not necessarily understandable to someone doing the calibrating. Instead, the function should take the casting unit of the spell as an argument directly (as well as other Data members, like level, if needed).
I am not sure this spell really requires a damage modifier, since it is thematically a healing spell rather than a damage modifying spell. Damage modifying spells restore the unit's life in order to make it seem like the damage was modified, so they must consider all sorts of special cases. Healing spells just restore the unit's life; there's no other requirement, so using a damage modifier for a healing spell seems like overkill, you could just use a regular damage event instead. (however, if you do end up using DamageModifiers anyway, you should use The SetUnitLife and GetUnitLife functions provided by that library instead of SetWidgetLife because other units in range could already be taking damage at that time)
When I suggested you didn't need the periodic checks, I did not realize units could die without taking damage, in which case, as you note, the spell will leak a damage modifier. This is not just a problem with mapmakers using RemoveUnit or KillUnit, it could also happen when summoned unit timers expire. This should still be avoidable without the need for periodic checks. One method would be to utilize AutoIndex's AutoDestroy feature.(I realize now you already use this solution for this spell; why can't you do the same thing for the other two spells that have this problem?) Another method would be to use a system like ABuff instead of a DamageModifier, which would also address my other point about DamageModifiers being an overkill, since ABuff uses a simple damage event.
Minor comment: something like SetUpSpellData might make a for a more descriptive function name than SetUpArrays.
Prolonged Life
Similar comments to above. I suppose damage modifiers are more justified here, since the spell does thematically block an attack, but at the same time it does also restore the unit's life so it's also a healing spell in addition to being a damage blocking spell. Maybe the code should reflect that by blocking the damage using the return value of the onDamageTaken method while restoring the rest of the unit's life by using SetUnitLife. The reason I feel that this distinction is important is because modifying the damage into the negative using DamageModifiers won't just heal the unit, it will also change the damage value for any damage callbacks that run afterwards, so it might seem like the attacker is healing the target when in fact it's the prolong life spell that's doing it, the attacker is just having its damage blocked.
Smiting Zeal
If the hero does not have enough mana to pay the additional cost, should the ability still activate? I think it shouldn't.
Similar to Seed of Life, the ValidateTarget function should take the casting unit as an argument instead of expecting users to use outside code like SpellEvent.CastingUnit in it.
Reverse Damage
FX_PLAY_DEATH_ANIM is not really needed, it can just cause odd compile errors if it is set to false and the user doesn't have TimedHandles in his map. You should use [ljass]LIBRARY_TimedHandles
in the static if statement instead and simply mention in the documentation of the FX_DURATION constant that it will be ignored if the TimedHandles library is not present.[*]The spell seems mostly useless right now, since it will in most cases reverse a single regular attack and players can't really get it to reverse stronger spells unless they make a lucky guess about when the opponent will cast them. Even then, the spell is limited in how much damage it can reverse. I can't open the map right now, but I'm guessing the spell is meant to be a weak innate ability rather than an ultimate. That's fine then I guess, but the code should support more useful spells as well, for example a simple value for minimum damage to trigger it in addition to the existing maximum damage reversed would allow users to make this into an effective damage spell blocker.[/list]
Reverse Damage, Prolonged Life and to a lesser extent Seed of Life are very similar spells. This is more of a hero design issue than a problem for a spell submission; after all, we accept single spells, so a spellpack with multiple similar spells is fine. Still, I wish the spells were a bit more diverse, if not in their default setups then at least in what their calibration sections allow you to do with them.
For example, with a bit more calibration options, Reverse Damage and Prolong Life could be the same spell. You'd just need a constant to tell if the spell should only trigger on lethal damage or not, another to determine the damage reduction percentage (so you can just block damage or fully reverse it or anything in between) in addition to the calibration values of the existing two spells. A single spell like that (you could still have two separate spells in the map to showcase the flexibility of the code) would be worth more than those two spells currently are.
Since these are meant to be buff spells, they should support dispel functionality. I realize the old periodic checks in combination with buff spells did this already, but that's a bad solution for multiple reasons: the periodic checks are wasteful and each such spell must be based on a different base spell (the number of which is limited) because buff spells based on the same spell don't stack. The latter is a problem even if you don't intend to support dispel, so instead, since you're already triggering large portions of the spell, it would be easy to use a self-targetting aura (auras, unlike buff spells, do stack even when based on the same ability) to display the buff. That still leaves the problem of detecting dispel, though, which could be solved by using a buff system like ABuff or IBS.
Another thing that comes to mind (and I am getting slightly off-topic here) is that there is use for a healing library. With a healing event, we could make more interesting spells for a healer-type hero. There's this, but it's terribly old and should be replaced by something newer.
This should fix almost every issue you brought up, Anitarf.
The only one it doesnt, is using a library dedicated to healing. And currently, i dont think i need to submit another library.
This should fix almost every issue you brought up, Anitarf.
The only one it doesnt, is using a library dedicated to healing. And currently, i dont think i need to submit another library.
That's all right, like I said, it was an off-topic thought, not an approval requirement.