Spell Death Coil

Reaction score
91
// needs a better name...

2usv3es.jpg

GUI/JASS/vJASS? vJASS
MUI? Yes
Lagless? Yes
Requires?
TimerUtils
LightLeaklessDamageDetect
PUI

Description:
Sends a coil of unholy energy that blasts an enemy unit and curses it with dark power, which causes the target to take amplified damage. Enemies that come close to the main affected unit will be affected by the curse. Bonus damage, duration and AoE increases per level.

Code:
JASS:

scope DeathCoil initializer Init 

//! runtextmacro PUI_PROPERTY("private", "real", "AMPLIFY", "0.00")
//! runtextmacro PUI_PROPERTY("private", "integer", "TIME", "0")

//==============================================================================
// C O N F I G U R A T I O N    M E N U
//==============================================================================
globals
// Raw code of the ability Death Coil.
    private constant integer AID_RAW = 'A000'

// First special effect that is going to be attached on affected units.
    private constant string SFX_ONE = "Abilities\\Spells\\Undead\\DeathandDecay\\DeathandDecayDamage.mdl"
// Attachment point of the first special effect.
    private constant string AP_ONE = "hand right"
// Second special effect that is going to be attached on affected units.
    private constant string SFX_TWO = "Abilities\\Spells\\Undead\\DeathandDecay\\DeathandDecayDamage.mdl"
// Attachment point of the second special effect.
    private constant string AP_TWO = "hand left"
// Third special effect that is going to be attached on affected units.
    private constant string SFX_THREE = "Abilities\\Spells\\NightElf\\Immolation\\ImmolationDamage.mdl"
// Attachment point of the third special effect.
    private constant string AP_THREE = "chest"
    
// This is the frequency of detecting units around the unit affected by Death Coil.
    private constant real ENUM_FREQUENCY = 0.33
// Period between attaching special effect on affected units.
    private constant real SFX_PERIOD = 1.
    private constant real MISSILE_SPEED = 950.
endglobals
    private function DAMAGE takes unit cast returns real
        return 75. * GetUnitAbilityLevel(cast, AID_RAW)
    endfunction
// How much bonus damage should the spell deal.
    private function AMPLIFIED_DAMAGE takes unit cast returns real
        return GetUnitAbilityLevel(cast, AID_RAW) * 0.1 // 10%/20%/30%/40%
    endfunction
// Area of effect of spreading the plague around a unit hit by Death Coil.
    private function AMPLIFIED_AOE takes unit cast returns real
        return 200.
    endfunction
// Duration of the plague.
    private function DURATION takes unit cast returns real
        return I2R(8 + (GetUnitAbilityLevel(cast, AID_RAW) * 2))
    endfunction
// Effect created when the missile hits its target.
    private function EFFECT takes unit cast returns string
        return "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl"
    endfunction
// Attachment point of the special effect.
    private function AP_EFFECT takes unit cast returns string
        return "chest"
    endfunction
//==============================================================================
// E N D    O F    C O N F I G U R A T I O N
//==============================================================================

globals
    private group ENUM_GROUP = CreateGroup()
    private group PLAGUED = CreateGroup()
    private unit forFilter = null
endglobals

private struct Data
    unit cast
    unit targ
    integer ticks
    
    static method create takes unit cast, unit targ returns Data
        local Data d = Data.allocate()
        set d.cast = cast
        set d.targ = targ
        set d.ticks = R2I(DURATION(d.cast) / ENUM_FREQUENCY)
        return d
    endmethod
endstruct

private function GetEnemies takes nothing returns boolean
    return IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(forFilter)) and IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE) == false and GetWidgetLife(GetFilterUnit()) > 0.405
endfunction

private function GetPlaguedUnits takes nothing returns boolean
    return AMPLIFY[GetFilterUnit()] > 0.00
endfunction

private function EffectHandler takes nothing returns nothing
    local unit fir 
    call GroupClear(ENUM_GROUP)
    call GroupEnumUnitsInRange(ENUM_GROUP, 0., 0., 999999., Condition(function GetPlaguedUnits))
    loop
        set fir = FirstOfGroup(ENUM_GROUP)
        exitwhen fir == null
        call DestroyEffect(AddSpecialEffectTarget(SFX_ONE, fir, AP_ONE))
        call DestroyEffect(AddSpecialEffectTarget(SFX_TWO, fir, AP_TWO))
        call DestroyEffect(AddSpecialEffectTarget(SFX_THREE, fir, AP_THREE))
        call GroupRemoveUnit(ENUM_GROUP, fir)
    endloop
endfunction

private function DurationHandler takes nothing returns nothing
    local unit fir 
    call GroupClear(ENUM_GROUP)
    call GroupEnumUnitsInRange(ENUM_GROUP, 0., 0., 999999., Condition(function GetPlaguedUnits))
    loop
        set fir = FirstOfGroup(ENUM_GROUP)
        exitwhen fir == null
        if TIME[fir] > 0 then
            set TIME[fir] = TIME[fir] - 1
        else
            set AMPLIFY[fir] = 0.00
            call GroupRemoveUnit(PLAGUED, fir)
        endif
        call GroupRemoveUnit(ENUM_GROUP, fir)
    endloop
endfunction
        
private function OnDamage takes nothing returns boolean
    local unit damager = GetEventDamageSource()
    local unit damaged = GetTriggerUnit()
    local real damage = GetEventDamage()
    if AMPLIFY[damaged] > 0.00 then
        call DisableDamageDetect()
        call UnitDamageTarget(damager, damaged, damage * AMPLIFY[damaged], true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
        call EnableDamageDetect()
    endif
    set damager = null
    set damaged = null
    return false
endfunction

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == AID_RAW
endfunction

private function SpreadPlague takes nothing returns nothing
    local timer tim = GetExpiredTimer()
    local Data d = Data(GetTimerData(tim))
    local unit fir
    set forFilter = d.cast
    call GroupClear(ENUM_GROUP)
    call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(d.targ), GetUnitY(d.targ), AMPLIFIED_AOE(d.cast), Condition(function GetEnemies))
    loop
        set fir = FirstOfGroup(ENUM_GROUP)
        exitwhen fir == null
        if not IsUnitInGroup(fir, PLAGUED) then
            call GroupAddUnit(PLAGUED, fir)
            if AMPLIFIED_DAMAGE(d.cast) > AMPLIFY[fir] then
                set AMPLIFY[fir] = AMPLIFIED_DAMAGE(d.cast)
            endif
            set TIME[fir] = 2
        endif
        call GroupRemoveUnit(ENUM_GROUP, fir)
    endloop
    set d.ticks = d.ticks - 1
    if d.ticks <= 0 then
        call ReleaseTimer(tim)
        call d.destroy()
    endif
    set tim = null
endfunction
    
private function Callback takes nothing returns nothing
    local timer tim = GetExpiredTimer()
    local Data d = Data(GetTimerData(tim))
    call DisableDamageDetect()
    call UnitDamageTarget(d.cast, d.targ, DAMAGE(d.cast), true, true, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
    call EnableDamageDetect()
    if AMPLIFIED_DAMAGE(d.cast) > AMPLIFY[d.targ] then 
    // Only the more powerful Death Coil will take effect.
        set AMPLIFY[d.targ] = AMPLIFIED_DAMAGE(d.cast)
    endif
    set TIME[d.targ] = R2I(DURATION(d.cast))
    if not IsUnitInGroup(d.targ, PLAGUED) then
        call GroupAddUnit(PLAGUED, d.targ)
    endif
    call DestroyEffect(AddSpecialEffectTarget(EFFECT(d.cast), d.targ, AP_EFFECT(d.cast)))
    call TimerStart(tim, ENUM_FREQUENCY, true, function SpreadPlague)
    set tim = null
endfunction

private function Actions takes nothing returns nothing
    local unit cast = GetTriggerUnit()
    local unit targ = GetSpellTargetUnit()
    local Data d = Data.create(cast, targ)
    local real x = GetUnitX(cast) - GetUnitX(targ)
    local real y = GetUnitY(cast) - GetUnitY(targ)
    local timer tim = NewTimer()
    set x = SquareRoot(x * x + y * y) / MISSILE_SPEED
    call SetTimerData(tim, integer(d))
    call TimerStart(tim, x, true, function Callback)
    set cast = null
    set targ = null
    set tim = null
endfunction

private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(trig, Condition(function Conditions))
    call TriggerAddAction(trig, function Actions)
    call TimerStart(CreateTimer(), SFX_PERIOD, true, function EffectHandler)
    call TimerStart(CreateTimer(), 1., true, function DurationHandler)
    call AddOnDamageFunc(Condition(function OnDamage))
endfunction

endscope


Screenshots:
http://i42.tinypic.com/xlz0cg.gif
http://i39.tinypic.com/2qw04tj.gif

Read Me:
JASS:

Spell created by tyrande_ma3x. If you want to use it in your map, credits are not necessary but would be appreciated.

To implement:

- Copy the TimerUtils trigger in your map if you do not have it.
- Copy the PUI trigger in your map if you do not have it.
- Copy the LightLeaklesDamageDetect trigger in your map if you do not have it.
- Create a single-target spell based on whatever you like (or just copy the one in Object Editor and edit it)
- Copy the DeathCoil trigger and configure it if you want to change something. 

Notes:
- The spell deals damage but that damage is not affected by the spell (it means it won't get amplified).
- If you cast Death Coil on some unit and that unit affects another unit, the second unit will not spread the disease. 
- The duration of the disease is individual for every unit. If the main infected unit gets away from other units
   they will be freed from the plague in two seconds.

- If you need another attachment system for this spell just contact me and I'll try to make it.
(this is in cases where you wouldn't want to implement a whole system just for one spell).

- Suggestions to improve this are welcome. 
- If you would want another effect for this spell, feel free to edit the code 
or tell me and I'll add it.
 

Attachments

  • DeathCoil v1[1].2.w3x
    38.1 KB · Views: 214

Romek

Super Moderator
Reaction score
963
Putting non-private Timer/Group recycling in your spell is ridiculous.
The function names will probably clash with about 90% of maps that want to use this spell.
 
Reaction score
91
Geez, I've forgotten the private keyword. Should I leave the recycling method or just use dynamic groups/timers?
 

Romek

Super Moderator
Reaction score
963
There's no need to create groups anyway.
For the struct, check if the group is null. If it is, create a group. Else, use GroupClear().

For the groups which you use instantly, you can also just use a single global group. Create it at map init, and don't destroy it.
(And the group gets cleared anyway from a FOG loop, as I can see)

I haven't looked over the code properly yet, but I noticed you create a timer 't2' in the actions function. And you don't seem to use it at all.
 

Tukki

is Skeleton Pirate.
Reaction score
29
I've not really check the code, just pointing out some obvious stuff. The spell looks fine and it's a nice concept :)

JASS:
    method onDestroy takes nothing returns nothing
        call ClearTriggerStructA(.trig)
        call ClearTimerStructB(.t)
        call TriggerRemoveAction(.trig, .ta)
        call DestroyTrigger(.trig)
        call PauseTimer(.t)
        call DestroyTimer(.t)
        call DestroyGroup(.gr)
    endmethod

Dynamic triggers are evil, use DisableTrigger() before destroying it. Because, sometimes if the event is triggered and the trigger is destroyed it will crash the game.

JASS:
private function SpreadPlague takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Data d = GetTimerStructB(t)

    call GroupClear(enumGroup)    
    set bj_ghoul[1000] = d.cast
    call GroupEnumUnitsInRange(enumGroup, GetUnitX(d.targ), GetUnitY(d.targ), AMPLIFIED_AOE(d.cast), Condition(function Enum))
    loop
        set bj_ghoul[1000] = FirstOfGroup(enumGroup)
        exitwhen bj_ghoul[1000] == null
        if not IsUnitInGroup(bj_ghoul[1000], d.gr) then
            call TriggerRegisterUnitEvent(d.trig, bj_ghoul[1000], EVENT_UNIT_DAMAGED)
            call GroupAddUnit(d.gr, bj_ghoul[1000])
            if not IsUnitInGroup(bj_ghoul[1000], sfx) then
                call GroupAddUnit(sfx, bj_ghoul[1000])
            endif
        endif
        call GroupRemoveUnit(enumGroup, bj_ghoul[1000])
    endloop
    set t = null
endfunction


Seriously, those bj_goul are waay back in time, when those where the only options of transfering data between functions, save the udg_ variables. Use a global unit instead.

JASS:
// How much bonus damage should the spell deal.
    private function AMPLIFIED_DAMAGE takes unit cast returns real
        local integer lvl = GetUnitAbilityLevel(cast, AID_RAW)
        return lvl * 0.1 // 10%/20%/30%/40%
    endfunction
// Area of effect of spreading the plague around a unit hit by Death Coil.
    private function AMPLIFIED_AOE takes unit cast returns real
        return 200.
    endfunction
// Duration of the plague.
    private function DURATION takes unit cast returns real
        local integer lvl = GetUnitAbilityLevel(cast, AID_RAW)
        return I2R(8 + (lvl * 2))
    endfunction

These functions are ridiculous, use a local for spell level and use it instead of the unit as an argument, and add 'constant' between 'private' and 'functions' if you decide to change the functions.

Furthermore, you should consider using a timer-recycling system. Also there's a much easier way of doing this without dynamic triggers: Add a modified Bloodlust to the affected unit; order the unit to use it; remove it.

Finally, you should read this as you are using 2 global groups.
 
Reaction score
91
> These functions are ridiculous, use a local for spell level and use it instead of the unit as an argument,
First of all, some people would like to base their damage/aoe/stuff on the Hero's attributes (like in my map) so I just put the caster in there for easier access and configuration.

> Because, sometimes if the event is triggered
Surprisingly, how would it trigger? I've removed the action and destroyed it - nothing evil should happen. But I'll disable it as you insist...

> Add a modified Bloodlust to the affected unit; order the unit to use it; remove it.
Erm, I didn't quite get the idea here. :p

> Furthermore, you should consider using a timer-recycling system.
The release version was with timer recycling, don't know why I removed it... Can someone point me a link to one, since CSSafety uses CSData and I don't like CSData (just preference).

> Finally, you should read this as you are using 2 global groups.
I read it, however Griffen says the snippet is not needed in instantaneous enumerations (like in my case).

> The spell looks fine and it's a nice concept :)
Thank you.

> Nice Spell!
Once again, thanks.

By the way, should I make it delay the plague for a bit of time, until the missile reaches its target? At the moment it is pretty much instant and it doesn't look realistic... :rolleyes:
 

Tukki

is Skeleton Pirate.
Reaction score
29
Surprisingly, how would it trigger? I've removed the action and destroyed it - nothing evil should happen. But I'll disable it as you insist...
I was a bit unclear, the trigger buggs if it is executing the moment you destroy it.

Erm, I didn't quite get the idea here.
Just pointing out another way to do this without dynamic triggers :). Bloodlust increases damage taken and you could use that in your SpreadPlauge function. Just add the ability, order the unit to cast it and then remove the ability.

The release version was with timer recycling, don't know why I removed it... Can someone point me a link to one, since CSSafety uses CSData and I don't like CSData (just preference).
Neither do I. A good one is here.

I read it, however Griffen says the snippet is not needed in instantaneous enumerations (like in my case).
*looking at 'sfx' group*

By the way, should I make it delay the plague for a bit of time, until the missile reaches its target? At the moment it is pretty much instant and it doesn't look realistic...
That would be a nice addition!
 
Reaction score
91
Various code changes as from advice - such as timer/group recycling, a few extras to the configuration, trigger disabling and delay until the missile reaches its target and then spread the plague.

Btw, Bloodlust doesn't increase/decrease damage taken, you must be mistaking it with Berserk. And I don't feel like adding 2 systems more just for this simple spell (or is it really necessary?).
 
Reaction score
91
Lots of code fixes and optimization, removed the usage of dynamic triggers (not that they're bad or gonna fuck your map... people just act stupidly with all those myths) and replaced them with a damage detection system.
 

Viikuna

No Marlo no game.
Reaction score
265
, removed the usage of dynamic triggers (not that they're bad or gonna fuck your map... people just act stupidly with all those myths) and replaced them with a damage detection system.

Either way, using a one trigger based system makes it more clear, nice and simple.
 
Reaction score
91
I guess so, but people use different damage detection systems so sometimes they wouldn't want to edit the whole spell just to fit their preferences. It would've been far more easier if there was only one attachment system, one indexing, damage detection and such, taken only from the best... (Please don't quote me here and start a topic about it).
 

Viikuna

No Marlo no game.
Reaction score
265
Yea, I´ve been thinking about this too, and since nearly all damage detection systems, unit indexing systems and systems like that, have pretty much same user functions, it would be best to include some wrappers, so your spell would work with any of those systems.

edit. Actually, Cassiel does this in his UnitProperties system for NewTimer and ReleaseTimer and GetUnitIndex. It works pretty well, I had no problems with getting that system to work with my TimerStack and Unit Recyling, Indexing and Damage Detecting -system.
 
Reaction score
91
No idea how to make those wrappers unless creating ~10 versions of this spell with support on different attaching systems. Well, as I said in the Read Me, if someone would like different system usage with this spell I'll try to make it.
 

Viikuna

No Marlo no game.
Reaction score
265
For damage detection, making wrappers is not hard at all, since they all have RegisterDamageEvent, and functions for getting, DamageSource, Type, Target and DamageAmount.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top