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

Maledict v2.1

This is the MALEDICT spell from DotA map.

RECOMMENDED: NewGen Editor.
REQUIRES: CTL library

Thanks to Bribe, Pharaoh_, Magtheridon96, baassee and watermelon_1234 for the corrections/tips ;)

Maledict
Targets an area with an ancient voodoo curse. Cursed heroes will not only take damage over time, but they will be dealt bonus damage every 4 seconds based on how much hp they lost since the beginning of the curse.

JASS:
// ********************************************************************************* //
//                    This spells requires the CTL library                           //
// ********************************************************************************* //

// ********************************************************************************* //
//                               HOW TO IMPLEMENT                                    //
// ********************************************************************************* //
//                                                                                   //
// 1. Copy the Maledict Ability (object editor)                                      //
// 2. Copy the Maledict Buff (object editor)                                         //
// 3. Copy this trigger                                                              //
// 4. Enjoy :), but remember, this spell requires CTL                                //
//                                                                                   //
// ********************************************************************************* //

library Maledict requires CTL
    globals
    
    // ***************************************************************************** //
    //                                CONFIGURABLES                                  //
    // ***************************************************************************** //
        // Rawcode of the curse and the curse buff
        private constant integer MALEDICT_RAWCODE = 'A001'
        private constant integer MALEDICT_BUFF_RAWCODE = 'B000'
        
        // @SPECIAL_EFFECT = Path of the special effect
        // @EFFECT_ATTACHMENT = Position of the special effect
        private constant string SPECIAL_EFFECT = "Abilities\\Spells\\Undead\\DeathPact\\DeathPactCaster.mdl"
        private constant string EFFECT_ATTACHMENT = "overhead"
        
        // @ONLY_HERO = If it is true, MALEDICT will only affect
        //              HERO type units
        private constant boolean ONLY_HERO = false
        
        // @DAMAGE_TYPE_EVERY_SECOND = Here you can choose what kind of damage
        //                             the curse will do every second
        private constant damagetype DAMAGE_TYPE_EVERY_SECOND = DAMAGE_TYPE_UNIVERSAL
        
        // @DAMAGE_TYPE = Type of damage that the spell will do
        //                in the extra damage
        
        // @ATTACK_TYPE = Type of attack that the spell will do
        //                in the extra damage and normal damage
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        
        // Area of effect of the curse (do not touch the +50)
        private constant real AoE = 165. + 50.
        
        // @EXTRA_DAMAGE_PERIOD = Seconds that needs to pass to make the extra damage
        private constant real EXTRA_DAMAGE_PERIOD = 4.
        
        // @PERIODIC_DAMAGE = Seconds that needs to pass to make the periodic damage
        private constant real PERIODIC_DAMAGE = 1.
        
        // @PRELOAD = To preload the special effect
        private constant boolean PRELOAD = true
        
        // ***************************************************************************** //
        //                                END CONFIGURABLES                              //
        // ***************************************************************************** //
        
        // @G = "Units" affected by the curse - DO NOT TOUCH THIS!
        private constant group G = bj_lastCreatedGroup
        
    endglobals
    
    // ********************************************************************************* //
    //                                  CONFIGURABLE                                     //
    // ********************************************************************************* //
    
    // Returns the extra damage
    // ((HP OF THE TARGET WHEN ADQUIERE THE CURSE - ACTUAL LIFE OF TARGET) / 100) * (10 * LEVEL OF THE CURSE)
    private function getExtraDamage takes unit target, real hp, integer level returns real
        local real damage
        set damage = ((hp - GetWidgetLife(target)) / 100) * (10. * level)
        
        if damage < 0 then
            set damage = 0
        endif
        
        return damage
    endfunction
    
    // Returns the periodic damage
    private function getPeriodicDamage takes integer level returns real
        return 5. * level
    endfunction
    
    // Returns the unit that can be affected by the curse
    private function affectedUnit takes unit target, player p returns boolean
        static if ONLY_HERO then
            return IsUnitType(target, UNIT_TYPE_HERO) and IsUnitEnemy(target, p) and not(IsUnitType(target, UNIT_TYPE_DEAD)) and not(IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE))
        else
            return IsUnitEnemy(target, p) and not(IsUnitType(target, UNIT_TYPE_DEAD)) and not(IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE))
        endif
    endfunction
    
    // ********************************************************************************* //
    
    private keyword Init
    
    private struct Spell extends array
        private static integer instanceCount = 0
        private static thistype recycle = 0
        private thistype recycleNext
    
        private unit caster                 // Caster
        private unit target                 // Curse target
        private real hp                     // HP of target
        private integer level               // Spell level
        private real period                 // @period will serve to know if PERIODIC_DAMAGE seconds has passed 
        private real period_extraDamage     // Same as period, but for the extra damage
        
        implement CTL
        implement CTLExpire
            // If the target has the curse and is not dead...
            if 0 < GetUnitAbilityLevel(this.target, MALEDICT_BUFF_RAWCODE) and not(IsUnitType(this.target, UNIT_TYPE_DEAD)) then
                // Let's see if PERIODIC_DAMAGE seconds has passed
                if PERIODIC_DAMAGE < this.period then
                    // If was, reset period to 0
                    set this.period = 0.
                    
                    // Making the periodic damage (only if the unit
                    // is not inmmune to magic)
                    if not(IsUnitType(this.target, UNIT_TYPE_MAGIC_IMMUNE)) then
                        call UnitDamageTarget(this.caster, this.target, getPeriodicDamage(this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE_EVERY_SECOND, null)
                    endif
                else
                    // If was not, add the expired seconds to period (seconds
                    // of the CTL timer)
                    set this.period = this.period + 0.031250000
                endif
                
                // Let's see if the EXTRA_DAMAGE_PERIOD seconds has passed
                if EXTRA_DAMAGE_PERIOD < this.period_extraDamage then
                    // If was, reset period_extraDamage to 0
                    set this.period_extraDamage = 0.
                    
                    // Making the damage and special effect only if the 
                    // damage is greater than 0
                    if getExtraDamage(this.target, this.hp, this.level) > 0 then
                        
                        // Only make the damage if the target is not inmmune to magic
                        if not(IsUnitType(this.target, UNIT_TYPE_MAGIC_IMMUNE)) then
                            call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
                            call UnitDamageTarget(this.caster, this.target, getExtraDamage(this.target, this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                        endif
                    endif
                else
                    // If was not (the seconds has not passed)...
                    set this.period_extraDamage = this.period_extraDamage + 0.031250000
                endif
            else
                // If the unit is dead or not has
                // the buff...
                set this.caster = null
                set this.target = null
                call this.destroy()
                
                set recycleNext = recycle
                set recycle = this
            endif
        
        implement CTLNull
        implement CTLEnd
        
        private static method run takes nothing returns boolean
            local unit j
            local thistype this
            
            // If the ability being cast is the Rupture and the target can be affected
            // (units that can be affected can be changed in the affectedUnit function)
            if GetSpellAbilityId() == MALEDICT_RAWCODE then
                call GroupEnumUnitsInRange(G, GetSpellTargetX(), GetSpellTargetY(), AoE, null)
                
                loop
                    set j = FirstOfGroup(G)
                    exitwhen j == null
                    call GroupRemoveUnit(G, j)
                    
                    if affectedUnit(j, GetTriggerPlayer()) then
                        set this = thistype.create()
                        
                        if (recycle == 0) then
                            set instanceCount = instanceCount + 1
                            set this = instanceCount
                        else
                            set this = recycle
                            set recycle = recycle.recycleNext
                        endif
                        
                        set this.caster = GetTriggerUnit()
                        set this.level = GetUnitAbilityLevel(this.caster, MALEDICT_RAWCODE)
                        set this.target = j
                        set this.hp = GetWidgetLife(j)
                    endif
                endloop
            endif
            
            return false
        endmethod
        
        implement Init
    endstruct
    
    private module Init
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.run))
            
            // Preload the special effect...
            static if PRELOAD then
                call Preload(SPECIAL_EFFECT)
            endif
        endmethod
    endmodule
endlibrary

Sorry for my english :S
Enjoy it :)

Keywords:
maledict, dota, witch, doctor
Contents

Maledict - DotA (Map)

Reviews
11 Nov 2011 Bribe: Very good resource. Nice configurables and clean code.

Moderator

M

Moderator

11 Nov 2011
Bribe: Very good resource. Nice configurables and clean code.
 
• The script in the map's header is not needed, at all. Just work with the classic natives.
• Instead of using "string hash", which generates a leak the first time it is used, and considering that you use a hashtable with vJass, you should either switch to integers or simply "keyword".
• You might want to remove the actions and use the conditions' block to perform them; they proc faster than the former ones. Return false in the conditions' block.
• GetOwningPlayer(GetTriggerUnit()) -> Can be replaced with GetTriggerPlayer().
• Set the ShowDamage to be a floating text, not a game message.
• Destroying a local timer doesn't actually mean "recycling". Take a look at TimerUtils, if you are interested in that.
• Make the following attachment point configurable: "overhead"
• Use GetWidgetLife() instead of GetUnitState().
• WEAPON_TYPE_WHOKNOWS: Could be replaced with "null".
• DAMAGE_TYPE_MAGIC: Should be configurable; a check should be made to prevent damage if the unit becomes invulnerable or magic immune.
• Change the tooltip to English :p
• You might want to use a struct to pass data more easily.
 
JASS:
if GetSpellAbilityId() == MALEDICT_RAWCODE then
            set caster = GetTriggerUnit()
            set level = GetUnitAbilityLevel(caster, MALEDICT_RAWCODE)
            
            set x = GetSpellTargetX()
            set y = GetSpellTargetY()
            
            set g = CreateGroup()
            
            // Picking every unit in MALEDICT AoE (default: 165.)
            call GroupEnumUnitsInRange(g, x, y, AoE, Condition(function filter))
            
            loop
                set target = FirstOfGroup(g)
                exitwhen target == null
                call GroupRemoveUnit(g, target)
                
                set data = Spell.create(caster, level, target)
                call SetHandleInt(data.t, "data", data)
                call TimerStart(data.t, 1., true, function tim)
            endloop
            
            call DestroyGroup(g)
        endif

Why are you creating a group then destroying it?
You could create a global group and clear it at the end instead of destroying it.

Also:

JASS:
if ONLY_HERO then
    return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer()) and IsUnitType(GetFilterUnit(), UNIT_TYPE_HERO)
endif        
return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer())

->

JASS:
static if ONLY_HERO then
    return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer()) and IsUnitType(GetFilterUnit(), UNIT_TYPE_HERO)
else
    return IsUnitEnemy(GetFilterUnit(), GetTriggerPlayer())
endif

Using static ifs will delete the unneeded code.

One last thing: Why are you still using Hashtables?
Since you're only using hashtables for Timers, I'd suggest using TimerUtils

http://www.wc3c.net/showthread.php?t=101322
 
Level 10
Joined
Sep 19, 2011
Messages
527
Why are you creating a group then destroying it?
You could create a global group and clear it at the end instead of destroying it.

With the group, I can pick the units that are in the aoe of the spell. Which would be the difference using a global variable?

Using static ifs will delete the unneeded code.
Ok ;)

One last thing: Why are you still using Hashtables?
Since you're only using hashtables for Timers, I'd suggest using TimerUtils

I don't know xD, maybe I will... maybe I will :ogre_hurrhurr:

The rest is fine? :grin:

Greetings and thanks :thumbs_up:

-----------------------------------------------------------------------------------------

EDIT : Mmm, the static if throws me an error (syntax error) - * Fix : The problem, was an old version of jasshelper
 
Last edited:
With the group, I can pick the units that are in the aoe of the spell. Which would be the difference using a global variable?

Efficiency.

EDIT : Mmm, the static if throws me an error (syntax error)

What version of JassHelper do you have?
You need 0.A.x.x

edit
lol I posted a few seconds after you did :p
You fixed everything :)
All you need to do is fix the group thing.
 
Yes exactly :)
All you have to add in the end is GroupClear(G)
Also, here's a faster way to do the group thing:

Instead of making a filter, than enumerating the units, why don't you make the filter function the actual place where you enumerate the units ^^
You could use the filter conditions in an if/then/endif statement.
Then, at the end of the filter, just return false.

The group will never have any units in it, so you dont have to clear it, and you would've already manipulated all the units ^^

If you want to use the data in the locals of the function where you're calling GroupEnumUnitsInRange, you could store them into globals :)

edit

Tip: Instead of making multiple posts, you could use the "Edit" button.
Double posting is really frowned upon here.

Instead of those lines, all you have to do is release the timer :)

But you still have to null this.t
 
Level 10
Joined
Sep 19, 2011
Messages
527
Done, I think that this is the last version xD.

Thanks man for all of your help.
ogre_hurrhurr.gif


Mmm, one more thing, can you answer me the leak question (top)?

EDIT : Uh, sorry for the double post, I can not delete my reply :S
EDIT2 : I got it, set this.t = null, done.
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
-The locals in the condition function are useless. GetSpellTargetX/Y are only called once anyway.

-The constant AoE should be configurable in a private function/m operator.

-In the filter function you set a local into filtered units but you don't use it in the if checks?

-These actions can be called from the create method instead.

JASS:
                call SetTimerData(data.t, data)
                call TimerStart(data.t, 1., true, function tim)

-Why don't you loop the action method instead of looping a seperate function? And put the destroy in an if part in the action method.

-Why are you using hpreduction? Change it to damage as if the target dies of this you cannot retrieve the killing unit nor the bounty.
JASS:
call SetWidgetLife(this.target, GetWidgetLife(this.target) - (5. * this.level))

-This should also be in the configurables, the 4 seconds.

JASS:
if this.period >= 4 then

-Why an onDestroy method? It's useless and sucks and besides that, use .deallocate() instead of .destroy()
 
It's not a leak :)
ReleaseTimer justs throws the timer back into an array for recycling.
That way, you don't have to create timers and destroy them, you could recycle them ^^

The only thing that's missing here:

JASS:
private method onDestroy takes nothing returns nothing
            call ReleaseTimer(this.t)
            //call PauseTimer(this.t)
            //call DestroyTimer(this.t)
            //set this.t = null
            set this.caster = null
            set this.target = null
        endmethod

is this:

set this.t = null

One other thing:

JASS:
method action takes nothing returns nothing
            local real damage
            
            // Making the damage every second...
            call SetWidgetLife(this.target, GetWidgetLife(this.target) - (5. * this.level))
            
            // If four seconds was pass, make the extra damage
            if this.period >= 4 then
                set this.period = 0
                set damage = ((this.hp - GetWidgetLife(this.target)) / 100) * (10 * this.level)
                
                // Special effect...
                call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
                
                // Extra damage...
                call UnitDamageTarget(this.caster, this.target, damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
            else
                // If wasn't, add 1 second to period
                set this.period = this.period + 1
            endif
        endmethod

Here, instead of SetWidgetLife, you should use UnitDamageTarget.
If you wanted the damage to be pure, you could've used DAMAGE_TYPE_UNIVERSAL

Also, you don't need the damage local.
You could use the value directly in the line where you did UnitDamageTarget.
Or better yet, you could create a function called "GetDamage" at the top of the spell near the globals for the user to configure.

This function will return the damage value given the this.hp variable and the level variable.

You could use this:

call UnitDamageTarget(this.caster, this.target, GetDamage(this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)

Here are a few other things:

if GetUnitAbilityLevel(data.target, MALEDICT_BUFF_RAWCODE) > 0 then

This may surprise you, but this:

if 0 < GetUnitAbilityLevel(data.target, MALEDICT_BUFF_RAWCODE) then

is more efficient :p

Also, I just noticed this:

private method onDestroy takes nothing returns nothing

GAH! You can use method destroy instead (efficiency)

You should call this.deallocate() at the end of it though.

Here's another tip for more performance:

Instead of calling this.allocate() and this.deallocate, you could make the struct extend an array:
private struct Spell extends array

Then, add these 3 at the top of the struct:

JASS:
private constant integer ic = 0
private constant integer ir = 0
private thistype rn

Then, instead of calling .allocate, do this:

JASS:
local thistype this
if 0==ir then
    set ic=ic+1
    set this=ic
else
    set this=ir
    set ir=.rn
endif

And at the end of the method destroy, do this instead of calling this.deallocate():

JASS:
set .rn=ir
set ir=this

EDIT
@baassee:
Y U NO WAIT 6 MINUTES? :ogre_rage:
 
Level 10
Joined
Sep 19, 2011
Messages
527
-The locals in the condition function are useless. GetSpellTargetX/Y are only called once anyway.

Ok.

-The constant AoE should be configurable in a private function/m operator.

For?, is not the same?

-In the filter function you set a local into filtered units but you don't use it in the if checks?

My bad (again) xD

-These actions can be called from the create method instead.

How?, I did like that because the newgen don't let me.

-Why are you using hpreduction? Change it to damage as if the target dies of this you cannot retrieve the killing unit nor the bounty.

I will fix that.

-This should also be in the configurables, the 4 seconds.

Ok.

-Why an onDestroy method? It's useless and sucks and besides that, use .deallocate() instead of .destroy()

I cannot use .deallocate, I can only use destroy.

Now I will post the new version.

Thx :D
 
It's only double free protection and a global array actually. You are confusing it with extending structs?

Making structs extend arrays isn't a bad thing ;D
You get:
- 2 less functions
- No Useless Double-free protection
- No function call overhead for allocation/deallocation

Efficiency is important you know :p
 
Level 10
Joined
Sep 19, 2011
Messages
527
Tell me guys, its fine? :D:

JASS:
scope Maledict initializer init
    globals
        private constant integer MALEDICT_RAWCODE = 'A001'
        private constant integer MALEDICT_BUFF_RAWCODE = 'B000'

        private constant string SPECIAL_EFFECT = "Abilities\\Spells\\Undead\\DeathPact\\DeathPactCaster.mdl"
        private constant string EFFECT_ATTACHMENT = "overhead"
        
        // @ONLY_HERO = If it is true, MALEDICT will only affect
        //              on HERO type units
        private constant boolean ONLY_HERO = false
        
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        
        private constant real AoE = 165.
        
        // @G = "Units" affected by the curse
        private constant group G = CreateGroup()
        
        private constant integer EXTRA_DAMAGE_PERIOD = 4
    endglobals
    
    private function getDamage takes unit target, real hp, integer level returns real
        return ((hp - GetWidgetLife(target)) / 100) * (10 * level)
    endfunction
    
    private struct Spell
        // @t = Timer
        timer t
        // @caster = Caster unit
        unit caster
        // @level = Level of the ability
        integer level
        // @target = Target of the cursed
        unit target
        // @hp = HP of the target unit
        real hp
        // @period = This variable will help us later
        //           to make the extra damage every 4 seconds
        integer period
        
        static method action takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            
            if 0 < GetUnitAbilityLevel(this.target, MALEDICT_BUFF_RAWCODE) then
                // Making the damage every second...
                call UnitDamageTarget(this.caster, this.target, (5. * this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE_UNIVERSAL, null)
                
                // If four seconds was pass, make the extra damage
                if this.period >= EXTRA_DAMAGE_PERIOD then
                    set this.period = 0
                    
                    // Special effect...
                    call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
                    
                    // Extra damage...
                    call UnitDamageTarget(this.caster, this.target, getDamage(this.target, this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                else
                    // If wasn't, add 1 second to period
                    set this.period = this.period + 1
                endif
            else
                call this.destroy()
            endif
        endmethod
        
        static method create takes unit caster, integer level, unit target returns thistype
            local thistype this = thistype.allocate()
            set this.t = NewTimer()
            set this.caster = caster
            set this.level = level
            set this.target = target
            set this.hp = GetWidgetLife(target)
            set this.period = 0
            
            call SetTimerData(this.t, this)
            call TimerStart(this.t, 1., true, function thistype.action)
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            call ReleaseTimer(this.t)
            set this.t = null
            set this.caster = null
            set this.target = null
            
            call this.deallocate()
        endmethod
    endstruct
    
    private function filter takes nothing returns boolean
        local unit caster = GetTriggerUnit()
        local integer level = GetUnitAbilityLevel(caster, MALEDICT_RAWCODE)
        local unit target = GetFilterUnit()
        local Spell data
        
        if ONLY_HERO then
            if IsUnitEnemy(target, GetTriggerPlayer()) and IsUnitType(target, UNIT_TYPE_HERO) then
                set data = Spell.create(caster, level, target)
            endif
        else
            if IsUnitEnemy(target, GetTriggerPlayer()) then
                set data = Spell.create(caster, level, target)
            endif
        endif
        
        set caster = null
        set target = null
        
        return false
    endfunction
    
    private function condition takes nothing returns boolean
        if GetSpellAbilityId() == MALEDICT_RAWCODE then
            // Picking every unit in MALEDICT AoE (default: 165.)
            call GroupEnumUnitsInRange(G, GetSpellTargetX(), GetSpellTargetY(), AoE, Condition(function filter))
        endif
        
        return false
    endfunction

    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function condition))
        set t = null
        
        // Preload
        call Preload(SPECIAL_EFFECT)
    endfunction
endscope

Greetings :D
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
Those 2 functions handle integers anyway so they're quite fast :p and despite the useless double-free protection (which is only a comparison anyway) the function overhead is also optional lol I found my integer array variable being more of an issue than that ;)

EDIT:

About the code:

-A constant boolean should be added to check if you want to preload or not preload. But the preloading call within a static if.

-Still the AOE should be configurable despite the fact of what it is in DOTA. Should be able to scale thru different levels.

-You still loop thru the filter function and not the action method.

-There are two damagetarget calls, each being for different kinds of attacks. The pure damage one should have a constant as well.

-It lacks of documentation.
 
Level 10
Joined
Sep 19, 2011
Messages
527
-You still loop thru the filter function and not the action method.

I cannot do that. The filter function creates the objects and the action's method is only for make the damage (to all objects created in the filter function)

-It lacks of documentation.

Ok, I will add more comments... but the code is very intuitive (I mean the names/etc)

You just have to move the destroy method above the action method

Done.

Greetins and thanks again :D
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
  • Your spell targets dead units and magic-immune units.
  • You can allow the user to configure the targets easier by putting a function at the top for the user to determine valid targets.
    Ex:
    JASS:
    // Determines which units are affected by the spell.
    function AffectedTargets takes unit target, player cOwner returns boolean
        return IsUnitEnemy(target, cOwner) and not IsUnitType(target, UNIT_TYPE_DEAD)
    endfunction
    If you don't like this advice, you should at least make the if for ONLY_HERO a static if.
  • Your getDamage function is confusing. You should explain more about how the spell calculates the extra damage.
  • Periodic damage should be configurable by the user.
  • It doesn't seem right that the spell will heal the unit if the target's hit points increases more than the hit points it had.
  • There is no point for the data variable in the filter function, just call Spell.create directly.
  • Your code documentation should state that TimerUtils needs to be implemented.
  • It's a good practice to separate globals that aren't actually configurable to avoid confusing the user. I'm talking about G.
 
Level 10
Joined
Sep 19, 2011
Messages
527
  • Your spell targets dead units and magic-immune units.
  • You can allow the user to configure the targets easier by putting a function at the top for the user to determine valid targets.
    Ex:
    JASS:
    // Determines which units are affected by the spell.
    function AffectedTargets takes unit target, player cOwner returns boolean
        return IsUnitEnemy(target, cOwner) and not IsUnitType(target, UNIT_TYPE_DEAD)
    endfunction
    If you don't like this advice, you should at least make the if for ONLY_HERO a static if.
  • Your getDamage function is confusing. You should explain more about how the spell calculates the extra damage.
  • Periodic damage should be configurable by the user.
  • It doesn't seem right that the spell will heal the unit if the target's hit points increases more than the hit points it had.
  • There is no point for the data variable in the filter function, just call Spell.create directly.
  • Your code documentation should state that TimerUtils needs to be implemented.
  • It's a good practice to separate globals that aren't actually configurable to avoid confusing the user. I'm talking about G.

Ok :)

[*]It doesn't seem right that the spell will heal the unit if the target's hit points increases more than the hit points it had.

What?, the spell doesn't heal the unit in any case.

Thank you :grin:

-------------

EDIT: Done! :)

EDIT: There's any way to that the admin/mod verify again the spell?
 
Last edited:
Level 14
Joined
Nov 18, 2007
Messages
1,084
What?, the spell doesn't heal the unit in any case.
It can if the target gets more hit points than the hit points it had when it got Maledict. You should check to see if the extra damage isn't negative since negative damage heals the target.

Your test map should make the spell have more than one level to show that the ability scales with the level.
 
Level 10
Joined
Sep 19, 2011
Messages
527
- convert your scope into a library and make it requires TimerUtils...

Ok :)

- this >>> (5. * this.level) should be set at the create

Mmm, no, because the damage is calculated in the getDamage function (so that the user can edit it without complications).

It can if the target gets more hit points than the hit points it had when it got Maledict. You should check to see if the extra damage isn't negative since negative damage heals the target.

Your test map should make the spell have more than one level to show that the ability scales with the level.

Ok :)

Just to say, if you want to rip it out of DOTA, it should be able to heal units in theory and practially :p

I do not understand

Thanks to all :)
 
Level 10
Joined
Sep 19, 2011
Messages
527
I make the spell with the CTL library:

JASS:
library Maledict requires CTL
    globals
        // Rawcode of the curse and the curse buff
        private constant integer MALEDICT_RAWCODE = 'A001'
        private constant integer MALEDICT_BUFF_RAWCODE = 'B000'
        
        // @SPECIAL_EFFECT = Path of the special effect
        // @EFFECT_ATTACHMENT = Position of the special effect
        private constant string SPECIAL_EFFECT = "Abilities\\Spells\\Undead\\DeathPact\\DeathPactCaster.mdl"
        private constant string EFFECT_ATTACHMENT = "overhead"
        
        // @ONLY_HERO = If it is true, MALEDICT will only affect
        //              on HERO type units
        private constant boolean ONLY_HERO = false
        
        // @DAMAGE_TYPE_EVERY_SECOND = Here you can choose what kind of damage
        //                             the curse will do every second
        private constant damagetype DAMAGE_TYPE_EVERY_SECOND = DAMAGE_TYPE_UNIVERSAL
        
        // @DAMAGE_TYPE = Type of damage that the spell will do
        //                in the extra damage
        
        // @ATTACK_TYPE = Type of attack that the spell will do
        //                in the extra damage and normal damage
        private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
        
        // Area of effect of the curse (do not touch the +50)
        private constant real AoE = 165. + 50.
        
        // @EXTRA_DAMAGE_PERIOD = Seconds that needs to pass to make the extra damage
        private constant real EXTRA_DAMAGE_PERIOD = 4.
        
        // @PERIODIC_DAMAGE = Seconds that needs to pass to make periodic damage
        private constant real PERIODIC_DAMAGE = 1.
        
        // @PRELOAD = To preload the special effect
        private constant boolean PRELOAD = true
        
        // @G = "Units" affected by the curse
        private constant group G = CreateGroup()
    endglobals
    
    private function getExtraDamage takes unit target, real hp, integer level returns real
        return ((hp - GetWidgetLife(target)) / 100) * (10. * level)
    endfunction
    
    private function getPeriodicDamage takes integer level returns real
        return 5. * level
    endfunction
    
    private function affectedUnit takes unit target returns boolean
        return true
    endfunction
    
    private keyword Init
    
    private struct Spell extends array
        unit caster
        integer level
        unit target
        real hp
        real period
        real period_extraDamage
        
        implement CTL
        
        implement CTLExpire
            if 0 < GetUnitAbilityLevel(this.target, MALEDICT_BUFF_RAWCODE) and not(IsUnitType(this.target, UNIT_TYPE_DEAD)) then
                if PERIODIC_DAMAGE < this.period then
                    set this.period = 0.
                    call UnitDamageTarget(this.caster, this.target, getPeriodicDamage(this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE_EVERY_SECOND, null)
                else
                    set this.period = this.period + 0.031250000
                endif
                
                if EXTRA_DAMAGE_PERIOD < this.period_extraDamage then
                    set this.period_extraDamage = 0.
                    
                    if getExtraDamage(this.target, this.hp, this.level) > 0 then
                        call DestroyEffect(AddSpecialEffectTarget(SPECIAL_EFFECT, this.target, EFFECT_ATTACHMENT))
                        call UnitDamageTarget(this.caster, this.target, getExtraDamage(this.target, this.hp, this.level), false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
                    endif
                else
                    set this.period_extraDamage = this.period_extraDamage + 0.031250000
                endif
            else
                set this.caster = null
                set this.target = null
                call this.destroy()
            endif
        
        implement CTLNull
        implement CTLEnd
        
        private static method getUnits takes nothing returns boolean
            local thistype this
            
            if affectedUnit(GetFilterUnit()) then
                set this = thistype.create()
                set this.caster = GetTriggerUnit()
                set this.level = GetUnitAbilityLevel(this.caster, MALEDICT_RAWCODE)
                set this.target = GetFilterUnit()
                set this.hp = GetWidgetLife(this.target)
            endif
        
            return false
        endmethod
        
        private static method run takes nothing returns boolean
            // If the ability being cast is the Rupture and the target can be affected
            // (units that can be affected can be changed in the affectedUnit function)
            if GetSpellAbilityId() == MALEDICT_RAWCODE then
                call GroupEnumUnitsInRange(G, GetSpellTargetX(), GetSpellTargetY(), AoE, Condition(function thistype.getUnits))
            endif
            
            return false
        endmethod
        implement Init
    endstruct
    
    private module Init
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.run))
            
            // Preload the special effect...
            static if PRELOAD then
                call Preload(SPECIAL_EFFECT)
            endif
            
            set t = null
        endmethod
    endmodule
endlibrary

Sorry for the lack of comments, I did it quickly.

Greetings :)
 
Top