HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

criticism of code

12-30-2008, 12:53 PM#1
fX_
Made this script for my map. My map uses triggered effects for abilities as a convention so everything is 'under control'. This script just detects when a unit has been hit by the missile of a spell so that the effects of the ability of the missile can be applied upon impact. It works only for single target spells like Cold Arrows and Drunken Haze. It requires that all single-unit-target-missile-spells are based-off some default single-unit-target-missile-spell with a buff - preferable without any stuns/disables. The buff must last >= CHECK_INTERVAL_DURATION. The spell itself isn't supposed to have any effect and the buff is used only for identification; since the effects are triggered.

And, yeah, there are very few abilities that have missiles, and apply buffs effects that can be eliminated (set to 0 effect values). Examples are Drunken Haze and Acid Bomb; uou can also use orb effects - or some of them - and just keep them from being auto-casted (trigger that issues "unautocast" ["poisonarrowson", etc.] when it the bearing unit is issued "autocast" ["poisonarrowsoff", etc.]). That's 2, or 7 (there are 5 castable orb abilities) if you include the orbs. This is enough for my map since any unit in my map will have a max of only 5 abilities.

EDIT: Apparently, Black Arrow doesn't work on heroes, so that's -1 usable ability.
EDIT2: After further testing, I have found out that only Drunken Haze, Acid Bomb, Cold Arrow, Poison Arrow, and Incinerate (Arrow) can be used; and the last 3 deal attack damage, too... suggestions?
EDIT3: +Parasite, +Shadow Strike (doesn't show "(dmg)!" if all effect values are set to 0.00)

This is an alternative to dummy-missiles-moved-by-triggers. It is to be used for my 'simpler' spells.

Criticism?

I have not figured out how to do this for AoE missiles (like Cluster Rocket or Pocket Factory). Suggestions?

Collapse JASS:
library SpellMissileImpact initializer Init

globals
    private constant real CHECK_INTERVAL_DURATION = 0.033
    private constant integer MAX_IDS = 100
    private constant integer MAX_INSTANCES = 8190

    //Ability-type (Id) indexes.
    private integer array gINT_AbilityId[MAX_IDS]
    private integer array gINT_BuffId[MAX_IDS]
    private SpellMissileImpactFunc array gFUNC[MAX_IDS]
    private real array gR_MaxTravelDuration[MAX_IDS]
    private integer gINT_CountId = 0
    //Missile (instance) indexes.
    private integer array gINT_IdIndex[MAX_INSTANCES]
    private integer array gINT_Level[MAX_INSTANCES]
    private unit array gU_Source[MAX_INSTANCES]
    private unit array gU_Target[MAX_INSTANCES]
    private real array gR_TravelDuration[MAX_INSTANCES]
    private integer gINT_CountInst = 0
    //Functional objects.
    private trigger gTRIG_Cast = null
    private timer gTIM = null
endglobals

    function CreateSpellMissileImpactFunction takes integer abilityId, integer buffId, SpellMissileImpactFunc func, real maxDuration returns boolean
        local integer INT_Index = 0
        
        if gINT_CountId < MAX_IDS then
            loop
                exitwhen INT_Index == gINT_CountId
                if gINT_AbilityId[INT_Index] == abilityId then
                    return false
                endif
                set INT_Index = INT_Index + 1
            endloop
        
            set gINT_AbilityId[gINT_CountId] = abilityId
            set gINT_BuffId[gINT_CountId] = buffId
            set gFUNC[gINT_CountId] = func
            set gR_MaxTravelDuration[gINT_CountId] = maxDuration
            set gINT_CountId = gINT_CountId + 1
            
            return true
        endif
        
        return false
    endfunction
    
    function DestroySpellMissileImpactFunction takes integer abilityId, integer buffId, SpellMissileImpactFunc func, real maxDuration returns boolean
        local integer INT_Index = 0
        local integer INT_Index2 = 0

        //Find a matching Id.
        loop
            exitwhen INT_Index == gINT_CountId
            if gINT_AbilityId[INT_Index] == abilityId then
                //Destroy the Id.
                set gINT_CountId = gINT_CountId - 1
                set gINT_AbilityId[INT_Index] = gINT_AbilityId[gINT_CountId]
                set gINT_BuffId[INT_Index] = gINT_BuffId[gINT_CountId]
                set gFUNC[INT_Index] = gFUNC[gINT_CountId]
                set gR_MaxTravelDuration[INT_Index] = gR_MaxTravelDuration[gINT_CountId]

                //Destroy all missile instances using the Id.
                loop
                    exitwhen INT_Index2 == gINT_CountInst
                    if gINT_IdIndex[INT_Index2] == INT_Index then
                        set gINT_CountInst = gINT_CountInst - 1
                        set gINT_IdIndex[INT_Index2] = gINT_IdIndex[gINT_CountInst]
                        set gINT_Level[INT_Index2] = gINT_Level[gINT_CountInst]
                        set gU_Source[INT_Index2] = gU_Source[gINT_CountInst]
                        set gU_Target[INT_Index2] = gU_Target[gINT_CountInst]
                        set gR_TravelDuration[INT_Index2] = gR_TravelDuration[gINT_CountInst]
                        set gU_Source[gINT_CountInst] = null
                        set gU_Target[gINT_CountInst] = null
                        
                        if INT_Index2 < gINT_CountInst - 1 then
                            set INT_Index2 = INT_Index2 - 1
                        endif
                    endif
                    //Match the gINT_IdIndex of instances using the last Id, which was adjusted, to the Id's index (which is INT_Index).
                    if gINT_IdIndex[INT_Index2] == gINT_CountId then
                        set gINT_IdIndex[INT_Index2] = INT_Index
                    endif
                    set INT_Index2 = INT_Index2 + 1
                endloop
                
                return true
            endif
            set INT_Index = INT_Index + 1
        endloop

        return false
    endfunction

    function interface SpellMissileImpactFunc takes unit source, unit target, integer level returns nothing

    private function UpdateMissile takes nothing returns nothing
        local integer INT_Index = 0

        loop
            exitwhen INT_Index >= gINT_CountInst
            set gR_TravelDuration[INT_Index] = gR_TravelDuration[INT_Index] + CHECK_INTERVAL_DURATION
            if gR_TravelDuration[INT_Index] <= gR_MaxTravelDuration[gINT_IdIndex[INT_Index]] then
                //Execute.
                if GetUnitAbilityLevel(gU_Target[INT_Index], gINT_BuffId[gINT_IdIndex[INT_Index]]) > 0 then
                    //Run function.
                    call UnitRemoveAbility(gU_Target[INT_Index], gINT_BuffId[gINT_IdIndex[INT_Index]])
                    call gFUNC[gINT_IdIndex[INT_Index]].execute(gU_Source[INT_Index], gU_Target[INT_Index], gINT_Level[INT_Index])
                    //Destroy.
                    set gINT_CountInst = gINT_CountInst - 1
                    set gINT_IdIndex[INT_Index] = gINT_IdIndex[gINT_CountInst]
                    set gINT_Level[INT_Index] = gINT_Level[gINT_CountInst]
                    set gU_Source[INT_Index] = gU_Source[gINT_CountInst]
                    set gU_Target[INT_Index] = gU_Target[gINT_CountInst]
                    set gR_TravelDuration[INT_Index] = gR_TravelDuration[gINT_CountInst]
                    set gU_Source[gINT_CountInst] = null
                    set gU_Target[gINT_CountInst] = null

                    if INT_Index < gINT_CountInst - 1 then
                        set INT_Index = INT_Index - 1
                    endif
                endif
            else
                //Destroy.
                set gINT_IdIndex[gINT_CountInst] = gINT_IdIndex[gINT_CountInst] - 1
                set gINT_IdIndex[INT_Index] = gINT_IdIndex[gINT_CountInst]
                set gINT_Level[INT_Index] = gINT_Level[gINT_CountInst]
                set gU_Source[INT_Index] = gU_Source[gINT_CountInst]
                set gU_Target[INT_Index] = gU_Target[gINT_CountInst]
                set gR_TravelDuration[INT_Index] = gR_TravelDuration[gINT_CountInst]
                set gU_Source[gINT_CountInst] = null
                set gU_Target[gINT_CountInst] = null
                
                if INT_Index < gINT_CountInst - 1 then
                    set INT_Index = INT_Index - 1
                endif
            endif
            set INT_Index = INT_Index + 1
        endloop
    endfunction

    private function SetupImpactFunction takes nothing returns boolean
        local integer INT_AbilityId = GetSpellAbilityId()
        local integer INT_Index = 0

        if gINT_CountInst < MAX_INSTANCES then
            loop
                exitwhen INT_Index == gINT_CountId
                if gINT_AbilityId[INT_Index] == INT_AbilityId then
    
                    set gINT_IdIndex[gINT_CountInst] = INT_Index
                    set gU_Source[gINT_CountInst] = GetTriggerUnit()
                    set gU_Target[gINT_CountInst] = GetSpellTargetUnit()
                    set gINT_Level[gINT_CountInst] = GetUnitAbilityLevel(gU_Source[gINT_CountInst], INT_AbilityId)
                    set gR_TravelDuration[gINT_CountInst] = 0.00
                    set gINT_CountInst = gINT_CountInst + 1

                    return false
                endif
                set INT_Index = INT_Index + 1
            endloop
        endif

        return false
    endfunction

    private function Init takes nothing returns nothing
        set gTRIG_Cast = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(gTRIG_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gTRIG_Cast, Condition(function SetupImpactFunction))

        set gTIM = CreateTimer()
        call TimerStart(gTIM, CHECK_INTERVAL_DURATION, true, function UpdateMissile)
    endfunction

endlibrary
12-30-2008, 03:34 PM#2
fX_
For AoE effects (a la Cluster Rockets), I am considering using Pocket Factory: make the factory life duration 0.01 and detect when it dies, then execute functions from this prompt... But this is only 1 ability.

Maybe I can also use Healing Spray and check when units in the target AoE get the buff... but this entails working with groups...
12-30-2008, 07:34 PM#3
Themerion
Quote:
Originally Posted by fX_
This script just detects when a unit has been hit by the missile of a spell so that the effects of the ability of the missile can be applied upon impact.
I've not analyzed the code, but it looks like you want this to be MUI. How do you prevent trouble when two casters casts the same spell on the same unit?

Oh, and also, there's a lot of arrays there. Perhaps you could use structs instead?
12-31-2008, 02:50 AM#4
fX_
Each missile instance is identified by a caster, a target, and a level.

It is MUI among casters since the functions can discriminate by the gU_Caster[] element.

Now, I consider a caster having casted two missiles simultaneously (both are yet traveling at the some time). But each can be identified by level - if they are so different as by level - and which hits first and which hits next doesn't really matter, I think, as long as the caster gets its ability effect on the target.

And I considered using structs are first, but I figured that these aren't really LEGO-block-type objects. But, recently, I have again considered using structs since each abilityId can have multiple impact functions associated with it, and these can be 'put-in-a-box'...
12-31-2008, 10:42 AM#5
Themerion
Quote:
Originally Posted by me
How do you prevent trouble when two casters casts the same spell on the same unit?

Quote:
Originally Posted by fX_
It is MUI among casters since the functions can discriminate by the gU_Caster[] element.

I cannot find the part where it does that differentiation. The only check it does seems to be this:

Collapse JASS:
GetUnitAbilityLevel(gU_Target[INT_Index], gINT_BuffId[gINT_IdIndex[INT_Index]]) > 0

If both caster A and caster B uses the same spell, looking for the same buff... (then caster A's function might be run, despite the fact the unit was hit by caster B's missile). Right?
12-31-2008, 03:04 PM#6
fX_
Oh, you're right. I thought you meant differentiation in the impact function. I didn't consider discriminating in missile hits. Any suggestions on how to account?

What if I use a trigger that detects when the targets take damage and check if they have the spell's buff on them? That way, I will be able to identify the source by GetEventDamageSource().

Most of the 'usable' abilities - all but Drunken Haze - have 'Damage Dealt' fields. Do damage events register for these spells even when these are set to 0.00?
01-01-2009, 07:09 AM#7
fX_
This works. But is it good?

EDIT: Figured that it isn't MUI in casts from the same unit. Suggestions on how to account for this?

EDIT2: Changed moments of calling the SpellMissile.RefreshDamageTrigger(). It is no longer called in SpellMissile.Impact() method which was just contradictory to its purpose and it is now mutually independent of SpellMissile.Cancel() and SpellMissile.Create().

Collapse JASS:
library AbilityMissileImpact initializer Init

    //////////////////////////////////////////////////////////////////////////////////////////////////////
    //  AbilityMissile
    //====================================================================================================
    //
    //  Proven list of base Warcraft III abilities that have missiles, that apply buffs that do not
    //  disable their targets and can be nullified of effects, and that deal damage.
    //
    //      1) Acid Bomb
    //      2) Parasite (Autocast)
    //      3) Shadow Strike (doesn't show "(Dmg)!" if everything is set to 0.00)
    //      4) Cold/Frost Arrow (Arrow) (Autocast)
    //      5) Poison Arrow (Arrow) (Autocast)
    //      6) Incinerate (Arrow) (Autocast)
    //
    //  * (Arrow)'s deal the casting unit's attack damage.
    //  * Impact functions of (Arrow) (Autocasts) will not be effected when the
    //    bearing unit uses the ability by attacking.
    //////////////////////////////////////////////////////////////////////////////////////////////////////

globals
    private constant integer MAX_DATA = 8190
    private constant integer MAX_MISSILES = 8190
    private constant real DAMAGE_TRIGGER_REFRESH_INTERVAL_DURATION = 0.033
endglobals

    function interface AbilityMissileImpactFunc takes unit caster, unit target, integer level returns nothing

struct AbilityMissileData[MAX_DATA]

    public static AbilityMissileData array Inst[MAX_DATA]
    public static integer intCountInst = 0
    private integer intInstIndex

    public integer intAbilityId
    public integer intBuffId
    public AbilityMissileImpactFunc func

    //Constructor.
    public static method Create takes integer abilityId, integer buffId, AbilityMissileImpactFunc func returns boolean
        local integer INT_Index = 0

        //Check if the ability already has a missile impact function. If it does, do not register another one for it.
        loop
            exitwhen INT_Index == AbilityMissileData.intCountInst
            if AbilityMissileData.Inst[INT_Index].intAbilityId == abilityId then
                return false
            endif
            set INT_Index = INT_Index + 1
        endloop

        //Create and allocate.
        set AbilityMissileData.Inst[AbilityMissileData.intCountInst] = AbilityMissileData.allocate()
        set AbilityMissileData.Inst[AbilityMissileData.intCountInst].intAbilityId = abilityId
        set AbilityMissileData.Inst[AbilityMissileData.intCountInst].intBuffId = buffId
        set AbilityMissileData.Inst[AbilityMissileData.intCountInst].func = func

        set AbilityMissileData.Inst[AbilityMissileData.intCountInst].intInstIndex = AbilityMissileData.intCountInst
        set AbilityMissileData.intCountInst = AbilityMissileData.intCountInst + 1

        return true
    endmethod

    //Deconstructor.
    public static method Destroy takes integer abilityId returns boolean
        local integer INT_Index = 0

        //Find the instance that matches the query, if any, and destroy it.
        loop
            exitwhen INT_Index == AbilityMissileData.intCountInst
            if AbilityMissileData.Inst[INT_Index].intAbilityId == abilityId then
                //Cancel all pending missile impact function executions of the destroyed.
                call AbilityMissile.Cancel(AbilityMissileData.Inst[INT_Index])
                //Destroy and recycle index.
                set AbilityMissileData.intCountInst = AbilityMissileData.intCountInst - 1
                set AbilityMissileData.Inst[AbilityMissileData.intCountInst].intInstIndex = AbilityMissileData.Inst[INT_Index].intInstIndex
                set AbilityMissileData.Inst[INT_Index] = AbilityMissileData.Inst[AbilityMissileData.intCountInst]
                set AbilityMissileData.Inst[AbilityMissileData.intCountInst] = 0
            endif
            set INT_Index = INT_Index + 1
        endloop

        return false
    endmethod

endstruct

struct AbilityMissile[MAX_MISSILES]

    private static AbilityMissile array Inst[MAX_MISSILES]
    private static integer intCountInst = 0
    private integer intInstIndex

    private unit uCaster
    private unit uTarget
    private integer intLevel
    private AbilityMissileData data

    //Destroys all AbilityMissile instances of the specified AbilityMissile type.
    public static method Cancel takes integer data returns nothing
        local integer INT_Index = 0

        loop
            exitwhen INT_Index >= AbilityMissile.intCountInst
            
            if AbilityMissile.Inst[INT_Index].data == data then
                set AbilityMissile.intCountInst = AbilityMissile.intCountInst - 1
                set AbilityMissile.Inst[AbilityMissile.intCountInst].intInstIndex = AbilityMissile.Inst[INT_Index].intInstIndex
                set AbilityMissile.Inst[INT_Index] = AbilityMissile.Inst[AbilityMissile.intCountInst]
                set AbilityMissile.Inst[AbilityMissile.intCountInst] = 0
                
                //Have the loop not bypass the new object in the instance.
                set INT_Index = INT_Index - 1
            endif
            
            set INT_Index = INT_Index + 1
        endloop
    endmethod
    
    //Applies Ability missile impact functions when a Ability missile acquires its target.
    private static method Impact takes nothing returns boolean
        local unit U_Target = GetTriggerUnit()
        local unit U_Caster = GetEventDamageSource()
        local integer INT_Index = 0
        
        //Find the Ability missile that has impacted - if the damage event is, indeed, that of a Ability missile impact -
        //and apply the impact function on the target, from the caster, and by the level.
        loop
            exitwhen INT_Index == AbilityMissile.intCountInst
            //If the damage source is the query instance's .uCaster; and the target, the query instance's .uTarget; and the target
            //has the buff of the Ability; then the event is a Ability missile impact event and the associated impact function
            //is to be applied.
            if U_Caster == AbilityMissile.Inst[INT_Index].uCaster and U_Target == AbilityMissile.Inst[INT_Index].uTarget and GetUnitAbilityLevel(U_Target, AbilityMissile.Inst[INT_Index].data.intBuffId) > 0 then
                //Remove the buff. The ability's buff is not really the effect; it is only a marker. After this use, it is useless.
                call UnitRemoveAbility(U_Target, AbilityMissile.Inst[INT_Index].data.intBuffId)
                //Run the impact function on the target, from the caster, and by the level.
                call AbilityMissile.Inst[INT_Index].data.func.execute(U_Caster, U_Target, AbilityMissile.Inst[INT_Index].intLevel)
                
                //Destroy the AbilityMissile instance and recycle it.
                set AbilityMissile.intCountInst = AbilityMissile.intCountInst - 1
                set AbilityMissile.Inst[AbilityMissile.intCountInst].intInstIndex = AbilityMissile.Inst[INT_Index].intInstIndex
                set AbilityMissile.Inst[INT_Index] = AbilityMissile.Inst[AbilityMissile.intCountInst]
                set AbilityMissile.Inst[AbilityMissile.intCountInst] = 0
                
                set U_Target = null
                set U_Caster = null
        
                return false
            endif
            set INT_Index = INT_Index + 1
        endloop
        
        set U_Target = null
        set U_Caster = null
        
        return false
    endmethod

    //Creates Ability missile impact functions when abilities that have them are casted.
    private static method Create takes nothing returns boolean
        local integer INT_AbilityId = GetSpellAbilityId()
        local integer INT_Index = 0

        //Find the Ability missile impact function that corresponds to the casted ability, if any, and create an executor
        //(AbilityMissile instance) for it.
        loop
            exitwhen INT_Index == AbilityMissileData.intCountInst
            if AbilityMissileData.Inst[INT_Index].intAbilityId == INT_AbilityId then

                set AbilityMissile.Inst[AbilityMissile.intCountInst] = AbilityMissile.allocate()
                set AbilityMissile.Inst[AbilityMissile.intCountInst].uCaster = GetTriggerUnit()
                set AbilityMissile.Inst[AbilityMissile.intCountInst].uTarget = GetSpellTargetUnit()
                call TriggerRegisterUnitEvent(AbilityMissile.trigDamage, AbilityMissile.Inst[AbilityMissile.intCountInst].uTarget, EVENT_UNIT_DAMAGED)
                set AbilityMissile.Inst[AbilityMissile.intCountInst].intLevel = GetUnitAbilityLevel(AbilityMissile.Inst[AbilityMissile.intCountInst].uCaster, INT_AbilityId)
                set AbilityMissile.Inst[AbilityMissile.intCountInst].data = AbilityMissileData.Inst[INT_Index]

                set AbilityMissile.Inst[AbilityMissile.intCountInst].intInstIndex = AbilityMissile.intCountInst
                set AbilityMissile.intCountInst = AbilityMissile.intCountInst + 1

                return false
            endif
            set INT_Index = INT_Index + 1
        endloop

        return false
    endmethod

    private static trigger trigDamage = CreateTrigger()
    
    //Clears the damage-detecting trigger of events for units that are no longer targets of the
    //AbilityMissiles, so that AbilityMissile.Impact() will not fire and loop redundantly whenever these take damage.
    //This function is done outside destroy-actions in the and AbilityMissile.Impact() to minimize crashes - people say
    //that destroying a trigger during its execution can crash the game... I don't know...
    private static method RefreshDamageTrigger takes nothing returns nothing
        local integer INT_Index = 0

        call DestroyTrigger(AbilityMissile.trigDamage)
        set AbilityMissile.trigDamage = CreateTrigger()
        loop
            exitwhen INT_Index == AbilityMissile.intCountInst
            call TriggerRegisterUnitEvent(AbilityMissile.trigDamage, AbilityMissile.Inst[INT_Index].uTarget, EVENT_UNIT_DAMAGED)
            set INT_Index = INT_Index + 1
        endloop
        call TriggerAddCondition(AbilityMissile.trigDamage, Condition(function AbilityMissile.Impact))
    endmethod

    public static method Initialize takes nothing returns nothing
        local trigger TRIG_Cast = CreateTrigger()
        local timer TIM = CreateTimer()

        call TriggerRegisterAnyUnitEventBJ(TRIG_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(TRIG_Cast, Condition(function AbilityMissile.Create))

        call AbilityMissile.RefreshDamageTrigger()
        call TimerStart(TIM, DAMAGE_TRIGGER_REFRESH_INTERVAL_DURATION, true, function AbilityMissile.RefreshDamageTrigger)

        set TRIG_Cast = null
        set TIM = null
    endmethod

endstruct

    private function Init takes nothing returns nothing
        call AbilityMissile.Initialize()
    endfunction

endlibrary
01-01-2009, 07:16 AM#8
TriggerHappy
couldn't you just change

Collapse JASS:
    private function Init takes nothing returns nothing
        call SpellMissile.Initialize()
    endfunction

to

Collapse JASS:
private function Init takes nothing returns nothing
        local trigger TRIG_Cast = CreateTrigger()
        local timer TIM = CreateTimer()

        call TriggerRegisterAnyUnitEventBJ(TRIG_Cast, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(TRIG_Cast, Condition(function SpellMissile.Create))

        call SpellMissile.RefreshDamageTrigger()
        call TimerStart(TIM, DAMAGE_TRIGGER_REFRESH_INTERVAL_DURATION, true, function SpellMissile.RefreshDamageTrigger)

        set TRIG_Cast = null
        set TIM = null
endfunction

Then remove Spellsjdksajdksjd.initialize()
01-01-2009, 08:22 AM#9
fX_
I did that for the sake of keeping the stuff private to the struct.
01-01-2009, 10:26 AM#10
Themerion
Quote:
Originally Posted by fX_
Figured that it isn't MUI in casts from the same unit. Suggestions on how to account for this?

I think it is, actually. Unless you have the same detection buff for multiple abilities, that is (if you use level 3 storm bolt four times, it doesn't matter which storm bolt function that runs, since they all do the same thing).
01-09-2009, 07:54 AM#11
fX_
Changed how it is scripted. And now it actually works with Cluster Rockets and Healing Spray. The missiles of these 2 abilities don't work like ordinary missiles, it seems; they ALWAYS fly for a certain duration (0.75s, estimated).

I don't know if I should require the indication of whether or not the abilityId of a type of missile is based of either of these two abilities on 'Register...'. Currently, it just checks if this is true for a missile when the missile is casted by getting the caster's current orderId and comparing it with those of the two abilities. Which method should I adopt?

Collapse JASS:
library AbilityMissileImpact initializer Init

globals
    //Changeable constants.
    private constant integer MAX_INSTANCES = 8190
    private constant real COUNTDOWN_INTERVAL_DURATION = 0.033
    private constant integer MAX_DATA = 8190
    private integer CLUSTER_ROCKETS_ORDER_ID = 852652
    private integer HEALING_SPRAY_ORDER_ID = 852664
    private constant real CLUSTERROCKETS_HEALINGSPRAY_MISSILE_DURATION = 0.75
endglobals

//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//
//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//
//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//
//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//

    //Wrappers.
    private keyword Data

    //Basic wrapper.
    function RegisterAbilityMissileImpactFunction takes integer abilityId, boolean unitTarget, integer buffId, real missileSpeed, AbilityMissileImpactFunc func returns boolean
        return Data.Create(abilityId, unitTarget, buffId, missileSpeed, func)
    endfunction

    //Specialized wrapper for unit target abilities.
    function RegisterAbilityMissileImpactFunctionUnitTarget takes integer abilityId, integer buffId, AbilityMissileImpactFunc func returns boolean
        return Data.Create(abilityId, true, buffId, 0.00, func)
    endfunction

    //Specialized wrapper for point target abilities.
    function RegisterAbilityMissileImpactFunctionPointTarget takes integer abilityId, real missileSpeed, AbilityMissileImpactFunc func returns boolean
        return Data.Create(abilityId, false, 0, missileSpeed, func)
    endfunction
    
    //Specialized wrapper for point target abilities ***based of Cluster Rockets or Healing Spray***.
    function RegisterAbilityMissileImpactFunctionPointTargetEx takes integer abilityId, AbilityMissileImpactFunc func returns boolean
        return Data.Create(abilityId, false, 0, 0.00, func)
    endfunction

    //Deconstructor.
    function UnregisterAbilityMissileImpactFunction takes integer abilityId returns boolean
        return Data.Destroy(abilityId)
    endfunction

//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//
//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//
//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//
//IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII//

    private keyword Unit
    private keyword Point
    
private struct Missile[MAX_INSTANCES]

    public unit uCaster
    public integer intLevel
    public Data data

    private static method Prepare takes nothing returns boolean
        local integer INT_AbilityId = GetSpellAbilityId()
        local unit U_Target = GetSpellTargetUnit()
        local location LOC_Target = GetSpellTargetLoc()
        local integer INT_Index = 0

        if Unit.intCountInst + Point.intCountInst < MAX_INSTANCES then
            loop
                exitwhen INT_Index == Data.intCountInst
                if Data.Inst[INT_Index].intAbilityId == INT_AbilityId then
                    if Data.Inst[INT_Index].boolUnitTarget then
                        call Unit.Create(GetTriggerUnit(), U_Target, Data.Inst[INT_Index])
                    else
                        call Point.Create(GetTriggerUnit(), GetLocationX(LOC_Target), GetLocationY(LOC_Target), Data.Inst[INT_Index])
                    endif

                    set U_Target = null
                    call RemoveLocation(LOC_Target)
                    set LOC_Target = null

                    return false
                endif
                set INT_Index = INT_Index + 1
            endloop
        endif

        set U_Target = null
        call RemoveLocation(LOC_Target)
        set LOC_Target = null

        return false
    endmethod

    public static method Initialize takes nothing returns nothing
        local trigger TRIG = CreateTrigger()

        call TriggerRegisterAnyUnitEventBJ(TRIG, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(TRIG, Condition(function Missile.Prepare))

        set TRIG = null
    endmethod

endstruct

private struct Unit extends Missile

    private static Unit array Inst
    public static integer intCountInst = 0

    public static method Cancel takes Data data returns nothing
        local integer INT_Index = 0

        loop
            exitwhen INT_Index >= Unit.intCountInst
            if Unit.Inst[INT_Index].data == data then
                set Unit.intCountInst = Unit.intCountInst - 1
                set Unit.Inst[INT_Index] = Unit.Inst[Unit.intCountInst]
                set Unit.Inst[Unit.intCountInst] = 0

                call Unit.RefreshTrigger()

                set INT_Index = INT_Index - 1
            endif
            set INT_Index = INT_Index + 1
        endloop
    endmethod

    private static method Impact takes nothing returns boolean
        local unit U_Source = GetEventDamageSource()
        local unit U_Target = GetTriggerUnit()
        local integer INT_Index = 0

        loop
            exitwhen INT_Index >= Unit.intCountInst
            if Unit.Inst[INT_Index].uCaster == U_Source and Unit.Inst[INT_Index].uTarget == U_Target and GetUnitAbilityLevel(U_Target, Unit.Inst[INT_Index].data.intBuffId) > 0 then
                call UnitRemoveAbility(U_Target, Unit.Inst[INT_Index].data.intBuffId)
                call Unit.Inst[INT_Index].data.func.execute(U_Source, U_Target, 0.00, 0.00, Unit.Inst[INT_Index].intLevel)
                
                set Unit.intCountInst = Unit.intCountInst - 1
                set Unit.Inst[INT_Index] = Unit.Inst[Unit.intCountInst]
                set Unit.Inst[Unit.intCountInst] = 0

                call Unit.RefreshTrigger.execute()
                
                set INT_Index = INT_Index - 1
            endif
            set INT_Index = INT_Index + 1
        endloop
        
        return false
    endmethod

    private static trigger trig = CreateTrigger()

    private static method RefreshTrigger takes nothing returns nothing
        local integer INT_Index = 0
        
        call TriggerSleepAction(0.00)
        call DestroyTrigger(Unit.trig)
        set Unit.trig = CreateTrigger()
        loop
            exitwhen INT_Index == Unit.intCountInst
            call TriggerRegisterUnitEvent(Unit.trig, Unit.Inst[INT_Index].uTarget, EVENT_UNIT_DAMAGED)
            set INT_Index = INT_Index + 1
        endloop
        call TriggerAddCondition(Unit.trig, Condition(function Unit.Impact))
    endmethod

    public static method PrepareTrigger takes nothing returns nothing
        call TriggerAddCondition(Unit.trig, Condition(function Unit.Impact))
    endmethod

    private unit uTarget

    public static method Create takes unit caster, unit target, Data data returns nothing
        set Unit.Inst[Unit.intCountInst] = Unit.allocate()
        set Unit.Inst[Unit.intCountInst].uCaster = caster
        set Unit.Inst[Unit.intCountInst].intLevel = GetUnitAbilityLevel(caster, data.intAbilityId)
        set Unit.Inst[Unit.intCountInst].uTarget = target
        set Unit.Inst[Unit.intCountInst].data = data

        set Unit.intCountInst = Unit.intCountInst + 1
        call TriggerRegisterUnitEvent(Unit.trig, target, EVENT_UNIT_DAMAGED)
    endmethod
    
endstruct

private struct Point extends Missile

    private static Point array Inst
    public static integer intCountInst = 0
    
    public static method Cancel takes Data data returns nothing
        local integer INT_Index = 0
        
        loop
            exitwhen INT_Index >= Unit.intCountInst
            if Point.Inst[INT_Index].data == data then
                set Point.intCountInst = Point.intCountInst - 1
                set Point.Inst[INT_Index] = Point.Inst[Point.intCountInst]
                set Point.Inst[Point.intCountInst] = 0

                call Point.ManageTimer(false)
                
                set INT_Index = INT_Index - 1
            endif
            set INT_Index = INT_Index + 1
        endloop
    endmethod

    private static method Impact takes nothing returns nothing
        local integer INT_Index = 0
        
        loop
            exitwhen INT_Index >= Point.intCountInst
            set Point.Inst[INT_Index].rDuration = Point.Inst[INT_Index].rDuration - COUNTDOWN_INTERVAL_DURATION
            if Point.Inst[INT_Index].rDuration <= 0.00 then
                call Point.Inst[INT_Index].data.func.execute(Point.Inst[INT_Index].uCaster, null, Point.Inst[INT_Index].rXTarget, Point.Inst[INT_Index].rYTarget, Point.Inst[INT_Index].intLevel)

                set Point.intCountInst = Point.intCountInst - 1
                set Point.Inst[INT_Index] = Point.Inst[Point.intCountInst]
                set Point.Inst[Point.intCountInst] = 0

                call Point.ManageTimer(false)

                set INT_Index = INT_Index - 1
            endif
            set INT_Index = INT_Index + 1
        endloop
    endmethod

    private static timer tim = CreateTimer()

    private static method ManageTimer takes boolean create returns nothing
        if Point.intCountInst == 0 then
            call PauseTimer(Point.tim)
        elseif create and Point.intCountInst == 1 then
            call TimerStart(Point.tim, COUNTDOWN_INTERVAL_DURATION, true, function Point.Impact)
        endif
    endmethod

    private real rXTarget
    private real rYTarget
    private real rDuration

    public static method Create takes unit caster, real x, real y, Data data returns nothing
        local integer INT_OrderId = GetUnitCurrentOrder(caster)

        set Point.Inst[Point.intCountInst] = Point.allocate()
        set Point.Inst[Point.intCountInst].uCaster = caster
        set Point.Inst[Point.intCountInst].intLevel = GetUnitAbilityLevel(caster, data.intAbilityId)
        set Point.Inst[Point.intCountInst].rXTarget = x
        set Point.Inst[Point.intCountInst].rYTarget = y
        if INT_OrderId == CLUSTER_ROCKETS_ORDER_ID or INT_OrderId == HEALING_SPRAY_ORDER_ID then
            set Point.Inst[Point.intCountInst].rDuration = CLUSTERROCKETS_HEALINGSPRAY_MISSILE_DURATION
        else
            set Point.Inst[Point.intCountInst].rDuration = SquareRoot(Pow(GetUnitX(caster) - x, 2) + Pow(GetUnitY(caster) - y, 2))/data.rMissileSpeed
        endif
        set Point.Inst[Point.intCountInst].data = data

        set Point.intCountInst = Point.intCountInst + 1
        call Point.ManageTimer(true)
    endmethod

endstruct

    function interface AbilityMissileImpactFunc takes unit caster, unit target, real x, real y, integer level returns nothing

private struct Data[MAX_DATA]

    public static Data array Inst[MAX_DATA]
    public static integer intCountInst = 0

    public integer intAbilityId
    public boolean boolUnitTarget
    public integer intBuffId = 0
    public real rMissileSpeed = 0.00
    public AbilityMissileImpactFunc func

    public static method Create takes integer abilityId, boolean unitTarget, integer buffId, real missileSpeed, AbilityMissileImpactFunc func returns boolean
        local integer INT_Index = 0

        if Data.intCountInst < MAX_DATA then
            loop
                exitwhen INT_Index == Data.intCountInst
                if Data.Inst[INT_Index].intAbilityId == abilityId then
                    return false
                endif
                set INT_Index = INT_Index + 1
            endloop

            set Data.Inst[Data.intCountInst] = Data.allocate()
            set Data.Inst[Data.intCountInst].intAbilityId = abilityId
            set Data.Inst[Data.intCountInst].boolUnitTarget = unitTarget
            if unitTarget then
                set Data.Inst[Data.intCountInst].intBuffId = buffId
            else
                set Data.Inst[Data.intCountInst].rMissileSpeed = missileSpeed
            endif
            set Data.Inst[Data.intCountInst].func = func

            set Data.intCountInst = Data.intCountInst + 1

            return true
        endif

        return false
    endmethod

    public static method Destroy takes integer abilityId returns boolean
        local integer INT_Index = 0

        loop
            exitwhen INT_Index == Data.intCountInst
            if Data.Inst[INT_Index].intAbilityId == abilityId then
                if Data.Inst[INT_Index].boolUnitTarget then
                    call Unit.Cancel(Data.Inst[INT_Index])
                else
                    call Point.Cancel(Data.Inst[INT_Index])
                endif

                set Data.intCountInst = Data.intCountInst - 1
                set Data.Inst[INT_Index] = Data.Inst[Data.intCountInst]
                set Data.Inst[Data.intCountInst] = 0

                return true
            endif
            set INT_Index = INT_Index + 1
        endloop

        return false
    endmethod

endstruct

    private function Init takes nothing returns nothing
        call Missile.Initialize()
        call Unit.PrepareTrigger()
    endfunction

endlibrary
01-09-2009, 08:37 PM#12
Anitarf
I think that triggered projectiles are a better way to go.
01-10-2009, 08:28 AM#13
fX_
I made this script for my simpler spells... to keep it simple.
01-10-2009, 09:11 AM#14
Anitarf
How is it simpler if a triggered projectile system would likely take less lines of code?
01-10-2009, 11:58 AM#15
fX_
'Coz you just rely on the system to move the missiles instead of moving it manually.