Spell Corruption

Flare

Stops copies me!
Reaction score
662
Seeing as I was bored, and felt the need to poke around with some new stuff (so the AIDS and Damage usage is questionable, and Event is only there for Damage :D)... I present to you, Corruption!

Corruption
Inflicts an agonising curse on the target, causing them to take damage over time and amplifying all damage dealt by the caster to the target. Deals 20/25/30 damage per second for 5/7/9 seconds, amplifies damage by 10/15/20%.

Visually, the spell is fairly simple - no (really) big fancy SFX, no really weird mechanics

Technical details:
Requires NewGen WE and WC3 TFT v1.24 (or above)
Coded in vJASS

Code: (I can have silly ASCII art too! :D)
JASS:
//    ______                            __  _            
//   / ____/___  ____________  ______  / /_(_)___  ____  
//  / /   / __ \/ ___/ ___/ / / / __ \/ __/ / __ \/ __ \ 
// / /___/ /_/ / /  / /  / /_/ / /_/ / /_/ / /_/ / / / / 
// \____/\____/_/  /_/   \__,_/ .___/\__/_/\____/_/ /_/  
//                           /_/                         

//Corruption, by Flare
//Requires: NewGen World Editor
//          Warcraft 3: The Frozen Throne v1.23b (or above)
//          Advanced Indexing & Data Storage (AIDS), by Jesus4Lyf
//          TimerUtils, by Vexorian
//          Event, by Jesus4Lyf
//          Damage, by Jesus4Lyf
//          Corruption [spell] - 'A000'

scope Corruption initializer Init
//CONFIGURABLE PROPERTIES
globals
    private constant integer SPELL_ID = 'A000'
    
    private constant real TICK = 1.
    
    private constant string DEBUFF_SFX = "Abilities\\Spells\\Undead\\Curse\\CurseTarget.mdl"
    private constant string DOT_TICK_SFX = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"
    private constant string DAMAGE_AMP_SFX = "Abilities\\Spells\\Undead\\DeathCoil\\DeathCoilSpecialArt.mdl"
    private constant string DEBUFF_ATTACH_PT = "overhead"
    private constant string PROC_ATTACH_PT = "chest"
    
    private constant attacktype AT = ATTACK_TYPE_MAGIC
    private constant damagetype DT = DAMAGE_TYPE_NORMAL
    private constant weapontype WT = WEAPON_TYPE_WHOKNOWS
endglobals

private function GetDuration takes unit u returns real
    return 3. + GetUnitAbilityLevel (u, SPELL_ID) * 2.
endfunction

private function GetDamageMultiplier takes unit u returns real
    return 0.05 + GetUnitAbilityLevel (u, SPELL_ID) * 0.05
endfunction

private function GetTickDamage takes unit u returns real
    return 15. + GetUnitAbilityLevel (u, SPELL_ID) * 5.
endfunction
//END OF CONFIGURABLE PROPERTIES



private struct CasterData extends array
    //!runtextmacro AIDS ()
    //Target is parent key, caster is child key
    hashtable ht
endstruct


private struct TargetData
    unit caster
    unit target
    boolean dotdamage
    real multiplier
    real tickdamage
    real dur
    effect fx
endstruct


private function TimerFunc takes nothing returns nothing
    local timer t = GetExpiredTimer ()
    local TargetData a = GetTimerData (t)
    local CasterData b
    set a.dur = a.dur - TICK
    if a.dur <= 0 or IsUnitType (a.target, UNIT_TYPE_DEAD) == true then
        call ReleaseTimer (t)
        set b = CasterData[GetUnitIndex (a.caster)]
        call FlushChildHashtable (b.ht, GetHandleId (a.target))
        call DestroyEffect (a.fx)
        call a.destroy ()
    else
        set a.dotdamage = true
        call UnitDamageTarget (a.caster, a.target, a.tickdamage, false, true, AT, DT, WT)
        call DestroyEffect (AddSpecialEffectTarget (DOT_TICK_SFX, a.target, PROC_ATTACH_PT))
        set a.dotdamage = false
    endif
endfunction


private function DamageTriggerFunc takes nothing returns boolean
    local unit dmger
    local unit dmged
    local real dmg = GetEventDamage ()
    local trigger t
    local integer id1
    local integer id2
    local CasterData a
    local TargetData b
    if dmg > 0 then
        set t = GetTriggeringTrigger ()
        set dmged = GetTriggerUnit ()
        set id1 = GetHandleId (dmged)
        set dmger = GetEventDamageSource ()
        set id2 = GetHandleId (dmger)
        set a = CasterData[GetUnitIndex (dmger)]
        if HaveSavedInteger (a.ht, id1, id2) then
            set b = LoadInteger (a.ht, id1, id2)
            if b.dotdamage == false then
                call DisableTrigger (t)
                call UnitDamageTarget (dmger, dmged, dmg * b.multiplier, false, true, AT, DT, WT)
                call DestroyEffect (AddSpecialEffectTarget (DAMAGE_AMP_SFX, dmged, PROC_ATTACH_PT))
                call EnableTrigger (t)
            endif
        endif
        set dmger = null
        set dmged = null
        set t = null
    endif
    
    return false
endfunction


private function SpellTriggerFunc takes nothing returns boolean
    local unit cast
    local unit targ
    local integer cid
    local integer tid
    local CasterData a
    local TargetData b
    local timer t
    if GetSpellAbilityId () == SPELL_ID then
        set cast = GetTriggerUnit ()
        set cid = GetHandleId (cast)
        set targ = GetSpellTargetUnit ()
        set tid = GetHandleId (targ)
        set a = CasterData[GetUnitIndex (cast)]
        
        if a.ht == null then
            set a.ht = InitHashtable ()
        endif
        
        if HaveSavedInteger (a.ht, tid, cid) then
            set b = LoadInteger (a.ht, tid, cid)
            set b.dur = GetDuration (b.caster)
            set b.multiplier = GetDamageMultiplier (b.caster)
            set b.dotdamage = false
            set b.tickdamage = GetTickDamage (b.caster)
        else
            set b = TargetData.create ()
            set b.fx = AddSpecialEffectTarget (DEBUFF_SFX, targ, DEBUFF_ATTACH_PT)
            set b.caster = cast
            set b.target = targ
            set b.tickdamage = GetTickDamage (b.caster)
            set b.multiplier = GetDamageMultiplier (b.caster)
            set b.dur = GetDuration (cast)
            set b.dotdamage = false
            call SaveInteger (a.ht, tid, cid, b)
            set t = NewTimer ()
            call SetTimerData (t, b)
            call TimerStart (t, TICK, true, function TimerFunc)
        endif
        set cast = null
        set targ = null
    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 SpellTriggerFunc))
    set t = CreateTrigger ()
    call Damage_RegisterEvent (t)
    call TriggerAddCondition (t, Condition (function DamageTriggerFunc))
endfunction

endscope

Yes, I know there's the potential for A LOT of hashtables to be created, but I couldn't think of any other way to allow association between one caster and multiple targets.

Screenshot:
attachment.php

So... comments, criticism, etc?

Changelog:
Code:
v1 - Initial release
v1.1 - Local handles nulled
v1.2 - Duration and spell stats now update on refresh
 

Attachments

  • Corruption screenshot.jpg
    Corruption screenshot.jpg
    175.1 KB · Views: 472
  • Corruption (v1.2).w3x
    51.3 KB · Views: 226

Weep

Godspeed to the sound of the pounding
Reaction score
400
I couldn't think of any other way to allow association between one caster and multiple targets.
Unit group array? (And by "array", I probably mean "struct member", but I can't be sure because I don't know vJASS. :p)
 

Flare

Stops copies me!
Reaction score
662
Unit group array? (And by "array", I probably mean "struct member", but I can't be sure because I don't know vJASS. :p)

That probably would've worked... bleh, unless it's a major improvement in efficiency, it seems like hassle :D I guess this is what happens when I don't use WE for months, then expect everything to be right...
 

Kenny

Back for now.
Reaction score
202
JASS:
// Advanced Indexing & Data Storage (AIDS), by Jesus4Lyf
// TimerUtils, by Vexorian <---
// Event, by Jesus4Lyf
// Damage, by Jesus4Lyf


Aww, booo. Use KT2 and it could be 4 from 4 for J4L's stuff :p. Then again, I'm not sure of the efficiency of KT2 compared to TimerUtils on lower frequencies.

Anywho, this spell seems cool, and it pretty well written (I don't know much about hashtables as I can't use them). Nicely done, haven't seen a spell submission from you for a while. Looks good. :thup:
 

Flare

Stops copies me!
Reaction score
662
JASS:

// Advanced Indexing & Data Storage (AIDS), by Jesus4Lyf
// TimerUtils, by Vexorian <---
// Event, by Jesus4Lyf
// Damage, by Jesus4Lyf


Aww, booo. Use KT2 and it could be 4 from 4 for J4L's stuff. Then again, I'm not sure of the efficiency of KT2 compared to TimerUtils on lower frequencies.

Anywho, this spell seems cool, and it pretty well written (I don't know much about hashtables as I can't use them). Nicely done, haven't seen a spell submission from you for a while. Looks good. :thup:
I was originally going to use KT2, then I realise that I was using a low frequency :D Just seems a bit silly to use KT2 for one 'useful' execution out of every thirty? :p
 

Kenny

Back for now.
Reaction score
202
I was originally going to use KT2, then I realise that I was using a low frequency Just seems a bit silly to use KT2 for one 'useful' execution out of every thirty?

From what I understand from that, you were going to use KT2 with a high frequency and just wait for the ticks to pass? If so then that is definately not needed. KT2 has a built in timer system for low frequencies too. So using 1.00 seconds as the interval is totally acceptable and KT2 will be deadly accurate and efficient. I was only stating that I don't think there is much of an efficiency gain (if any) from using KT2 over TimerUtils.

Anyways, my vote goes for supporting our local systems and members, so KT2 FTW.
 

Jesus4Lyf

Good Idea™
Reaction score
397
JASS:
                call DisableTrigger (t)
                call UnitDamageTarget (dmger, dmged, dmg * b.multiplier, false, true, AT, DT, WT)
                call DestroyEffect (AddSpecialEffectTarget (DAMAGE_AMP_SFX, dmged, PROC_ATTACH_PT))
                call EnableTrigger (t)

I like what you did here, disabling the trigger for the duration. :thup:

>Anyways, my vote goes for supporting our local systems and members, so KT2 FTW.
I generally agree, but kind of find TU quite acceptable. It is faster than KT2 for high periods (I still use KT2 personally).
Thanks for the vote of confidence anyway, and feel free to voice your opinion.

Hm. I wouldn't list Event in the requirements simply because it is a requirement of Damage, not of your spell...

And you could list your AIDS requirement as AIDS OR PUI, I think. I think both will work 100% without code change. :D

I haven't reviewed the spell carefully, just took a skim through.

I noticed you attach a hashtable to each unit?
>Unit group array? (And by "array", I probably mean "struct member", but I can't be sure because I don't know vJASS. )
A unit group struct member would be much nicer, but then you probably want group recycling too...

You could also use a linked list of instances. kenny! has a nice approved system for doing that. Probably all a bit too complicated for the infrequent mapper though.

Good to see you active and experimental. :thup:
Thanks for trying my systems.
 

Flare

Stops copies me!
Reaction score
662
I like what you did here, disabling the trigger for the duration.
I do hope you're being sarcastic :p The trigger is re-enabled, 3 lines down, after damage is dealt (unless Damage has some background involvement that I don't know of). Since it's my first time using Damage, I don't know if it allows for recursion, so I'd rather be safe than sorry :)

Hm. I wouldn't list Event in the requirements simply because it is a requirement of Damage, not of your spell...
Well, that's getting technical about things - it's not really a big deal, but the spell doesn't function if you choose to leave Event out, so I would regard it as a requirement :p

I noticed you attach a hashtable to each unit?
Indeed - was the only thing that sprung to mind (and worked well, I think/hope) and was a good chance to play around with hashtables. It does seem somewhat bloated although, then again, the only hashtable function call that would be removed without any replacement would be FlushChildHashtable (from a brief look), since
JASS:


A unit group struct member would be much nicer, but then you probably want group recycling too...
Yey, more system requirements :D Although, a simple
JASS:
if .group == null then
  set .group = CreateGroup ()
endif

would nearly be sufficient (if the change could be justified as a significant improvement)

Good to see you active and experimental.
Active? Nah, I was just bored and had an hour or two to kill ;)
 

Jesus4Lyf

Good Idea™
Reaction score
397
Yea, everything you said is good.
I do hope you're being sarcastic :p The trigger is re-enabled, 3 lines down, after damage is dealt (unless Damage has some background involvement that I don't know of). Since it's my first time using Damage, I don't know if it allows for recursion, so I'd rather be safe than sorry :)
Damage is built from the ground up to support recursion, 100%.

Problem is, I assume with your spell it would infinitely amplify itself? So disabling the trigger was a neat way to avoid this. Wasn't being sarcastic.

Damage exposes this Damage_EnableEvent(true/false) thing which you could have otherwise used, but it would stop other triggers from being able to detect your spell's damage. This is a much nicer way to do it, imho. :)
 

Flare

Stops copies me!
Reaction score
662
Problem is, I assume with your spell it would infinitely amplify itself? So disabling the trigger was a neat way to avoid this. Wasn't being sarcastic.
Ah, I misinterpreted what you said then... :3

I suppose I'll poke around some more and see if I find anything. It was a fairly hasty spell to make, and I get the feeling I've overlooked something :p
 

quraji

zap
Reaction score
144
Taking a look at part of your action function:

JASS:
    if GetSpellAbilityId () == SPELL_ID then
        set cast = GetTriggerUnit ()
        set cid = GetHandleId (cast)
        set targ = GetSpellTargetUnit ()
        set tid = GetHandleId (targ)
        set a = CasterData[GetUnitIndex (cast)]
        
        if a.ht == null then
            set a.ht = InitHashtable ()
        endif
        
        if HaveSavedInteger (a.ht, tid, cid) then
            set b = LoadInteger (a.ht, tid, cid)
        else
            set b = TargetData.create ()
            set b.fx = AddSpecialEffectTarget (DEBUFF_SFX, targ, DEBUFF_ATTACH_PT)
            set b.caster = cast
            set b.target = targ
            set b.tickdamage = GetTickDamage (b.caster)
            set b.multiplier = GetDamageMultiplier (b.caster)
            set b.dur = GetDuration (cast)
            set b.dotdamage = false
            call SaveInteger (a.ht, tid, cid, b)
            set t = NewTimer ()
            call SetTimerData (t, b)
            call TimerStart (t, TICK, true, function TimerFunc)
        endif
        set cast = null
        set targ = null
    endif


Why do you load the struct but do nothing to it?

Also, just wondering why you use so many hashtables instead of one.

Just as a side note, it's kind of confusing when you change the names of the variables from the SpellTriggerFunc and DamageTriggerFunc :p
 

Flare

Stops copies me!
Reaction score
662
Why do you load the struct but do nothing to it?
It was a fairly hasty spell to make, and I get the feeling I've overlooked something
Good man! Thanks for spotting that - the duration, damage and amplification is supposed to be refreshed there :p

Also, just wondering why you use so many hashtables instead of one
Well, if I had 1 hashtable altogether, it could cause problems with flushing and updating old instances - if a unit was the victim of 2 separate instances, flushing their key from the table. The instance may not have expired so, if the first caster recasted, it'd start a new spell instance on the target instead of refreshing the old one (and I want the spell to be refreshable when the same caster recasts on the same target). At least, that is if I'm thinking about it correctly.

Unless there is some limit on hashtables that I don't know about or some horrific efficiency (I haven't really seen anything suggesting they are dreadfully slow), I don't see any major reason as to why it should be changed - as I said, it was a oppurtunity to mess around with hashtables, and it was the first solution that sprung to mind :)
 

quraji

zap
Reaction score
144
Well, if I had 1 hashtable altogether, it could cause problems with flushing and updating old instances...

Maybe if you stored the targets as a child of the caster instead of the other way around:
JASS:
call SaveInteger (hashtable, targetid, casterid, data)
call SaveInteger (hashtable, casterid, targetid, data) // this instead

Then you can flush individual targets easier?

I guess I'll have to actually look at your code instead of skimming..

EDIT: Nah, too lazy =]
 

Flare

Stops copies me!
Reaction score
662
Then you can flush individual targets easier?
But if the caster casts on 3 targets... you flush the casterid key and there goes 2 unfinished instances along with the finished one :p

I suppose I could flush the key when all present instances are finished, but that means I have to track all instances for the caster - I think it'd work, just have to find the simplest solution for tracking the instances :D
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top