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

GUI Spell System v1.8.0.0

This bundle is marked as director's cut. It exceeds all expectations and excels in every regard.
GUI Spell System
version 1.8.0.0

About:

Spell System revolutionizes the spell making process. It handles spell indexing, end-cast events, timed triggers, configurable unit-in-range filters, memory leak management, gives you access to a part of a global hashtable and reduces your variable and handle count drastically per spell. This system unifies all spell events and runs your triggers for you, directly. The result is an efficient, fully-featured and clean spell coding experience, and my personal favorite contribution to the GUI community.

Included in the demo map is the first spell I've made in 5 years: The Big Dipper. I am pleased to present this project to the Hive community!

Features:
  • Automatic event handling of spells. The system runs your triggers for you once you have configured your spell with a couple of variables.
  • Automatic creation and destruction of spell indices.
  • Automatic variable setting: sets the cast ability, the ability level, caster, target, caster position and target position and more into Spell__ variables.
  • Automatically runs a spell instance periodically if you specify an OnLoop trigger from a spell.
  • Automatic memory management of caster/target locations.
  • Unified common variables to reduce user's need to constantly create the same, redudant variables each time per spell.
  • Automatically gets all units in a range matching pre-specified conditions.
  • You can configure which units in range are chosen from the configuration trigger of your spell.


Spell Preview: Spell System GIF - Find & Share on GIPHY


Required variables

Spell__Ability (ability)

Set in each spell's Config trigger to the ability you want a trigger for.​

Spell System <gen> (trigger)

Run this trigger, ignoring conditions, once all Config variables are set.​

At least one event trigger is required

Spell__Trigger_OnEffect (trigger)

Trigger runs when a unit starts the effect of the specified ability.​

Spell__Trigger_OnFinish (trigger)

When a unit is done casting the ability (completed/canceled).​

Spell__Trigger_OnCast/Channel (trigger)

Usually unused. For more information about what these do, see Casting events guide

Event Responses

Spell__Caster (unit)

The unit casting the spell.​

Spell__Target (unit)

The target unit of the spell, if applicable. Will be set to (no unit) if it was a point-target or no-target spell.​

Spell__CastPoint (point)

The position of the casting unit. If the caster moves, this point moves with it.​

Spell__TargetPoint (point)

The target point of the spell. If the target was a unit and the unit moves, this point moves with it.​

Commonly-extracted variables

Spell__Level (integer)

The level of the ability being cast.​

Spell__CasterOwner (player)

The caster owner is also commonly used to determine friend from foe, or to specify the owner of a dummy unit.​

MUI variables

Spell__Index (integer)

The unique index which is used in an array to store specific stuff with your spell. Using this variable as an array index ensures your spell is fully-MUI.​

Spell__Hash (hashtable)

You can store anything extra you want or need for your spell into this hashtable as long as you only use Spell__Index as the parent key.​

Common Time variables

Does your spell do something over time? If not, skip this section.

Spell__Trigger_OnLoop (trigger)

Usually set from the config trigger, but can be set or changed at any point in the spell's lifespan. This trigger runs after a specified period of time.​

Spell__Time (real)

How long (in seconds) to wait before the OnLoop trigger runs (or runs again, if used sequentially). It must be set to a value above 0.00 to be registered with the system.

Spell__Duration (real)

Specified during Config or during any phase of the spell. Will keep repeating the OnLoop trigger every Spell__Time seconds until the duration is expired.

If you've already set a duration and wish to terminate it early, simply set the Duration to 0.00.

If you need to know if the duration has ended, check if it's equal to or less than 0.00. If it will still run, it will be greater than 0.00.​

Units-in-range essential variables

Spell__InRangePoint (point)

Set to the center of where you want to pick units from.​

Spell__InRange (real)

How wide should the radius be?​

Spell__InRangeGroup (unit group)

Filled with all units in range of the given point who matched the spell filter criteria.​

Spell__Filter_AllowX (boolean)

Specify what kind of targets you allow to be added to Spell__InRangeGroup. Specify any of these filters from your spell's Config trigger if you want custom ones. What you leave unspecified defaults to whatever the filters are set to in Spell System Config <gen>​



Spell__DurationPerLevel (real)

You can specify a duration per level from the Config trigger. The formula is Spell__Duration + (Spell__Level x Spell__DurationPerLevel).​

Spell__StartDuration (boolean)

Set to True if you specified a duration from the Config trigger so the system knows when you intend to have the duration start from.​

Spell__LevelMultiplier (real)

Made available as a real so you don't have to convert the spell level into a real yourself.​



Spell__UseTargetGroup (boolean)

Set to True from the Config trigger if you want a group to keep track of your target units.​

Spell__TargetGroup (unit group)

If you specified Spell__UseTargetGroup, this group is available to you throughout the duration of the spell. It is empty until you manually add units to it.​

Spell__InRangeCount (integer)

The number of units added to Spell__InRangeGroup.​

Spell__InRangeUnits[] (unit array)

An array with indices between 1 and Spell__InRangeCount listing the units in Spell__InRangeGroup.​

Spell__Trigger_InRange (integer)

Set from your spell's Config trigger (if desired), It is used to add additional things to the unit filter that couldn't be covered otherwise.​

Spell__InRangeMax (integer)

Set before setting Spell__InRange. This integer limits the number of units added to Spell__InRangeGroup. The extra units are removed at random.​



Want to detect if a caster has finished channeling or a spell was canceled early?

Spell__Channeling (boolean)

True if the caster is still channeling the ability. Useful from an OnLoop trigger if you want to interrupt the loop if the caster stops.​

Spell__Completed (boolean)

True if the caster successfully completed the channeling of the spell without it getting interrupted. Invaluable if you want to do bonus effects upon successful execution of the ability.​



Inspiration:

  • SpellEvent by Anitarf - Without his system, GUI Spell System wouldn't be nearly what it is now. Almost all the fundamentals of this system are encapsulated by SpellEvent.
  • vJass structs by Vexorian - Create a unique integer variable which will act as an array index. Has OnDestroy functionality which is run automatically and is double-free safe.
  • Timer32 by Jesus4Lyf - One timer for all periodic events; handles iteration of instances for you behind-the-scenes.
  • Constant Timer Loop 32 by Nestharus - A combination of Timer32 and vJass struct creation/destruction which pauses timers/removes system triggers when completed.
  • GroupUtils by Rising_Dusk - Factors in a unit's collision size into the InRange check and recycles unit groups.
  • Table by Vexorian - The idea to use one hashtable with unique indexes as parent keys so it can be used for many different things.


  • Spell System Config
    • Events
    • Conditions
    • Actions
      • -------- Only one dummy unit type is needed as you can attach an effect to it of any kind --------
      • -------- --------
      • Set Spell__DummyType = Dummy (Vexorian, Anitarf, Infrane)
      • Set Spell__DummyOwner = Neutral Extra
      • Set Spell__Interval = (1.00 / 32.00)
      • -------- --------
      • -------- Configure default values for the unit filter: --------
      • -------- --------
      • Set Spell__Filter_AllowEnemy = True
      • Set Spell__Filter_AllowLiving = True
      • Set Spell__Filter_AllowHero = True
      • Set Spell__Filter_AllowNonHero = True
      • Set Spell__Filter_AllowAlly = False
      • Set Spell__Filter_AllowDead = False
      • Set Spell__Filter_AllowFlying = False
      • Set Spell__Filter_AllowMechanical = False
      • Set Spell__Filter_AllowStructure = False
      • -------- --------
      • -------- Magic immunity is a great thing to block, as it also discludes invulnerable units from being picked --------
      • -------- --------
      • Set Spell__Filter_AllowMagicImmune = False
      • -------- --------
      • -------- Normal WC3 abilities, like Channel, wake sleeping creeps - even if they don't deal damage or apply buffs. --------
      • -------- Because of this, I provided an option to wake up creeps when they are enumerated by an InRange command. --------
      • -------- --------
      • Set Spell__WakeTargets = True


JASS:
function SpellIndexGetVars takes integer i returns nothing
    set udg_Spell__Ability = udg_Spell_i_Abil[udg_Spell_i_Head[i]]
    set udg_Spell__Index = i
    set udg_Spell__Caster = udg_Spell_i_Caster[i]
    set udg_Spell__CasterOwner = GetOwningPlayer(udg_Spell__Caster)
    set udg_Spell__Level = udg_Spell_i_Level[i]
    set udg_Spell__LevelMultiplier = udg_Spell__Level //Spell__LevelMultiplier is a real variable.
    set udg_Spell__Target = udg_Spell_i_Target[i]
   
    //Magic to ensure the locations never leak.
    call MoveLocation(udg_Spell__CastPoint, GetUnitX(udg_Spell__Caster), GetUnitY(udg_Spell__Caster))
    if udg_Spell__Target == null then
        call MoveLocation(udg_Spell__TargetPoint, udg_Spell_i_TargetX[i], udg_Spell_i_TargetY[i])
    else
        call MoveLocation(udg_Spell__TargetPoint, GetUnitX(udg_Spell__Target), GetUnitY(udg_Spell__Target))
    endif
    set udg_Spell__TargetGroup = udg_Spell_i_TargetGroup[i]
    set udg_Spell__Completed = udg_Spell_i_Completed[i]
    set udg_Spell__Channeling = udg_Spell_i_Channeling[i]
endfunction
function SpellSetFilters takes integer i returns nothing
    set udg_Spell_i_AllowEnemy[i]       = udg_Spell__Filter_AllowEnemy
    set udg_Spell_i_AllowAlly[i]        = udg_Spell__Filter_AllowAlly
    set udg_Spell_i_AllowDead[i]        = udg_Spell__Filter_AllowDead
    set udg_Spell_i_AllowLiving[i]      = udg_Spell__Filter_AllowLiving
    set udg_Spell_i_AllowMagicImmune[i] = udg_Spell__Filter_AllowMagicImmune
    set udg_Spell_i_AllowMechanical[i]  = udg_Spell__Filter_AllowMechanical
    set udg_Spell_i_AllowStructure[i]   = udg_Spell__Filter_AllowStructure
    set udg_Spell_i_AllowFlying[i]      = udg_Spell__Filter_AllowFlying
    set udg_Spell_i_AllowHero[i]        = udg_Spell__Filter_AllowHero
    set udg_Spell_i_AllowNonHero[i]     = udg_Spell__Filter_AllowNonHero
endfunction
function SpellIndexDestroy takes integer i returns nothing
    local integer indexOf
    local integer index
    if udg_Spell_i_RecycleList[i] >= 0 then
        return
    endif
    //If the caster is still channeling on the spell, don't destroy until it's finished:
    if not udg_Spell_i_Channeling[i] then
        set index = udg_Spell_i_Head[i]
        set udg_Spell_i_RecycleList[i] = udg_Spell_i_Recycle
        set udg_Spell_i_Recycle = i
       
        //Reset things to defaults:
        set udg_Spell_i_Time[i] = 0.00
        set udg_Spell_i_LastTime[i] = 0.00
        set udg_Spell_i_Duration[i] = 0.00
        set udg_Spell_i_Completed[i] = false
        set udg_Spell_i_Caster[i] = null
        set udg_Spell_i_Target[i] = null
        set udg_Spell_i_OnLoopStack[i] = null
       
        //Recycle any applicable target unit group.
        if udg_Spell_i_TargetGroup[i] != null then
            call GroupClear(udg_Spell_i_TargetGroup[i])
            set udg_Spell_i_GroupStack[udg_Spell_i_GroupN] = udg_Spell_i_TargetGroup[i]
            set udg_Spell_i_GroupN = udg_Spell_i_GroupN + 1
            set udg_Spell_i_TargetGroup[i] = null
        endif
       
        //Clear any user-specified data in the hashtable:
        call FlushChildHashtable(udg_Spell__Hash, i)
        //call BJDebugMsg("Destroying index: " + I2S(i))
    endif
   
    set indexOf = udg_Spell_i_StackRef[i]
    if indexOf >= 0 then
        set index = udg_Spell_i_StackN - 1
        set udg_Spell_i_StackN = index
       
        set udg_Spell_i_StackRef[udg_Spell_i_Stack[index]] = indexOf
        set udg_Spell_i_Stack[indexOf] = udg_Spell_i_Stack[index]
        if index == 0 then
            //If no more spells require the timer, pause it.
            call PauseTimer(udg_Spell_i_Timer)
        endif
        set udg_Spell_i_StackRef[i] = -1
    endif
endfunction
function SpellTriggerExecute takes integer i, trigger t returns real
    local real d = udg_Spell_i_Duration[i]
    local boolean b = false
    set udg_Spell__Duration = d
    set udg_Spell__Time = 0.00
    if t != null then
        set udg_Spell__Trigger_OnLoop = null
        set udg_Spell__Expired = d <= 0.00 //If the duration is <= 0, the spell has expired.
        call SpellIndexGetVars(i)
        if TriggerEvaluate(t) then
            call TriggerExecute(t)
        endif
        if udg_Spell__Trigger_OnLoop != null then
            set udg_Spell_i_OnLoopStack[i] = udg_Spell__Trigger_OnLoop
        endif
        //The remaining lines in this function process the duration specified by the user.
        if udg_Spell__StartDuration then
            set udg_Spell__StartDuration = false
            set udg_Spell__Duration = udg_Spell_i_Duration[udg_Spell_i_Head[i]] + udg_Spell_i_LastTime[udg_Spell_i_Head[i]]*udg_Spell__LevelMultiplier
        elseif (udg_Spell__Expired and d > 0.00) or (udg_Spell__Duration <= 0.00) then
            set udg_Spell__Duration = 0.00
            return udg_Spell__Time
            //The user manually expired the spell or the spell duration ended on its own.
        endif
        if d != udg_Spell__Duration then
            //A new duration has been assigned
            set d = udg_Spell__Duration
            set b = true
        endif
        set udg_Spell__Duration = 0.00
        if udg_Spell__Time == 0.00 then
            if udg_Spell_i_LastTime[i] == 0.00 then
                if udg_Spell_i_Time[udg_Spell_i_Head[i]] > 0.00 then
                    //The user specified a default interval to follow:
                    set udg_Spell__Time = udg_Spell_i_Time[udg_Spell_i_Head[i]]
                else
                    //Set the spell time to the minimum.
                    set udg_Spell__Time = udg_Spell__Interval
                endif
            else
                //Otherwise, set it to what it was before.
                set udg_Spell__Time = udg_Spell_i_LastTime[i]
            endif
        //else, the user is specifying a new time for the spell.
        endif
        set udg_Spell_i_LastTime[i] = udg_Spell__Time //Whatever the case, remember this time for next time.
        if b then
            //The duration was just assigned
            set udg_Spell_i_Duration[i] = d
        else
            //The duration has been ongoing
            set udg_Spell_i_Duration[i] = d - udg_Spell__Time
        endif
    endif
    return udg_Spell__Time
endfunction
//===========================================================================
// Runs every Spell__Interval seconds and handles all of the timed events.
//
function SpellTimerLoop takes nothing returns nothing
    local integer i = udg_Spell_i_StackN
    local integer node
    local real time
    set udg_Spell__Running = true
   
    //Run stack top to bottom to avoid skipping slots when destroying.
    loop
        set i = i - 1
        exitwhen i < 0
        set node = udg_Spell_i_Stack[i]
        set time = udg_Spell_i_Time[node] - udg_Spell__Interval
        if time <= 0.00 then
            set time = SpellTriggerExecute(node, udg_Spell_i_OnLoopStack[node])
        endif
        if time <= 0.00 then
            call SpellIndexDestroy(node)
        else
            set udg_Spell_i_Time[node] = time
        endif
    endloop
    set udg_Spell__Running = false
endfunction
//===========================================================================
// This is the meat of the system as it handles the event responses.
//
function RunSpellEvent takes nothing returns boolean
    local boolean b
    local integer aid = GetSpellAbilityId()
    local integer head = LoadInteger(udg_Spell__Hash, 0, aid)
    local integer i
    local integer id
    local trigger t
    local playerunitevent eid
    if head == 0 then
        //Nothing for this ability has been registered. Skip the sequence.
        return false
    endif
    set eid = ConvertPlayerUnitEvent(GetHandleId(GetTriggerEventId()))
    set udg_Spell__Caster = GetTriggerUnit()
    set id = GetHandleId(udg_Spell__Caster)
    set i = LoadInteger(udg_Spell__Hash, aid, id)
    if i == 0 then
        //This block will almost always happen with the OnChannel event. In the
        //case of Charge Gold and Lumber, only an OnEffect event will run.
        set i = udg_Spell_i_Recycle
        if i == 0 then
            //Create a new, unique index
            set i = udg_Spell_i_Instances + 1
            set udg_Spell_i_Instances = i
        else
            //Repurpose an existing one
            set udg_Spell_i_Recycle = udg_Spell_i_RecycleList[i]
        endif
        //call BJDebugMsg("Creating index: " + I2S(i))
        set udg_Spell_i_RecycleList[i] = -1
        set udg_Spell_i_StackRef[i] = -1
        set udg_Spell_i_Head[i] = head
       
        if eid == EVENT_PLAYER_UNIT_SPELL_CHANNEL then
            set udg_Spell_i_Channeling[i] = true
            call SaveInteger(udg_Spell__Hash, aid, id, i)
            set t = udg_Spell_i_OnChannelStack[head]
        else //eid == EVENT_PLAYER_UNIT_SPELL_EFFECT
            set t = udg_Spell_i_OnEffectStack[head]
        endif
        set udg_Spell_i_Caster[i] = udg_Spell__Caster
        set udg_Spell_i_Level[i] = GetUnitAbilityLevel(udg_Spell__Caster, aid)
        set udg_Spell_i_Target[i] = GetSpellTargetUnit()
        set udg_Spell_i_TargetX[i] = GetSpellTargetX()
        set udg_Spell_i_TargetY[i] = GetSpellTargetY()
       
        set udg_Spell_i_OnLoopStack[i] = udg_Spell_i_OnLoopStack[head]
        if udg_Spell_i_UseTG[head] then
            //Get a recycled unit group or create a new one.
            set id = udg_Spell_i_GroupN - 1
            if id >= 0 then
                set udg_Spell_i_GroupN = id
                set udg_Spell_i_TargetGroup[i] = udg_Spell_i_GroupStack[id]
            else
                set udg_Spell_i_TargetGroup[i] = CreateGroup()
            endif
        endif
    elseif eid == EVENT_PLAYER_UNIT_SPELL_CAST then
        set t = udg_Spell_i_OnCastStack[head]
    elseif eid == EVENT_PLAYER_UNIT_SPELL_EFFECT then
        set t = udg_Spell_i_OnEffectStack[head]
    elseif eid == EVENT_PLAYER_UNIT_SPELL_FINISH then
        set udg_Spell_i_Completed[i] = true
        return true
    else //eid == EVENT_PLAYER_UNIT_SPELL_ENDCAST
        set udg_Spell_i_Channeling[i] = false
        call RemoveSavedInteger(udg_Spell__Hash, aid, id)
        set t = udg_Spell_i_OnFinishStack[head]
    endif
    if SpellTriggerExecute(i, t) > 0.00 then
        //Set the spell time to the user-specified one.
        set udg_Spell_i_Time[i] = udg_Spell__Time
        if udg_Spell_i_StackRef[i] < 0 then
            //Allocate the spell index onto the loop stack.
            set aid = udg_Spell_i_StackN
            set udg_Spell_i_Stack[aid] = i
            set udg_Spell_i_StackRef[i] = aid
            set udg_Spell_i_StackN = aid + 1
            if aid == 0 then
                //If this is the first spell index using the timer, start it up:
                call TimerStart(udg_Spell_i_Timer, udg_Spell__Interval, true, function SpellTimerLoop)
            endif
        endif
    elseif (not udg_Spell_i_Channeling[i]) and (t != null or udg_Spell_i_Time[i] <= 0.00) then
        call SpellIndexDestroy(i)
    endif
    set t = null
    return true
endfunction
//This function is invoked if an event was launched recursively by another event's callback.
function RunPreSpellEvent takes nothing returns nothing
    local integer i = udg_Spell__Index
    local real time = udg_Spell__Time
    local real d = udg_Spell__Duration
    local boolean expired = udg_Spell__Expired
    if udg_Spell__Trigger_OnLoop != null then
        set udg_Spell_i_OnLoopStack[i] = udg_Spell__Trigger_OnLoop
    endif
    if RunSpellEvent() then
        set udg_Spell__Time = time
        set udg_Spell__Duration = d
        set udg_Spell__Expired = expired
        call SpellIndexGetVars(i)
    endif
endfunction
//===========================================================================
// Base function of the system: runs when an ability event does something.
//
function SpellSystemEvent takes nothing returns boolean
    if udg_Spell__Running then
        call RunPreSpellEvent()
    else
        set udg_Spell__Running = true
        call RunSpellEvent()
        set udg_Spell__Running = false
    endif
    return false
endfunction
//===========================================================================
// Set Spell__Ability to your spell's ability
// Set Spell__Trigger_OnChannel/Cast/Effect/Finish/Loop to any trigger(s) you
// want to automatically run.
//
// GUI-friendly: Run Spell System <gen> (ignoring conditions)
//
function SpellSystemRegister takes nothing returns nothing
    local integer aid = udg_Spell__Ability
    local integer head = udg_Spell_i_Instances + 1
   
    if HaveSavedInteger(udg_Spell__Hash, 0, aid) or aid == 0 then
        //The system rejects duplicate or unassigned abilities.
        return
    endif
    set udg_Spell_i_Instances = head
    set udg_Spell_i_Abil[head] = aid
   
    //Preload the ability on dummy unit to help prevent first-instance lag
    call UnitAddAbility(udg_Spell_i_PreloadDummy, aid)
   
    //Save head index to the spell ability so it be referenced later.
    call SaveInteger(udg_Spell__Hash, 0, aid, head)
   
    //Set any applicable event triggers.
    set udg_Spell_i_OnChannelStack[head]= udg_Spell__Trigger_OnChannel
    set udg_Spell_i_OnCastStack[head]   = udg_Spell__Trigger_OnCast
    set udg_Spell_i_OnEffectStack[head] = udg_Spell__Trigger_OnEffect
    set udg_Spell_i_OnFinishStack[head] = udg_Spell__Trigger_OnFinish
    set udg_Spell_i_OnLoopStack[head]   = udg_Spell__Trigger_OnLoop
    set udg_Spell_i_InRangeFilter[head] = udg_Spell__Trigger_InRangeFilter
   
    //Set any customized filter variables:
    call SpellSetFilters(head)
   
    //Tell the system to automatically create target groups, if needed
    set udg_Spell_i_AutoAddTargets[head] = udg_Spell__AutoAddTargets
    set udg_Spell_i_UseTG[head] = udg_Spell__UseTargetGroup or udg_Spell__AutoAddTargets
   
    //Handle automatic buff assignment
    set udg_Spell_i_BuffAbil[head] = udg_Spell__BuffAbility
    set udg_Spell_i_BuffOrder[head] = udg_Spell__BuffOrder
   
    //Set the default time sequences if a duration is used:
    set udg_Spell_i_Time[head]     = udg_Spell__Time
    set udg_Spell_i_Duration[head] = udg_Spell__Duration
    set udg_Spell_i_LastTime[head] = udg_Spell__DurationPerLevel
   
    //Set variables back to their defaults:
    set udg_Spell__Trigger_OnChannel    = null
    set udg_Spell__Trigger_OnCast       = null
    set udg_Spell__Trigger_OnEffect     = null
    set udg_Spell__Trigger_OnFinish     = null
    set udg_Spell__Trigger_OnLoop       = null
    set udg_Spell__Trigger_InRangeFilter= null
    set udg_Spell__AutoAddTargets       = false
    set udg_Spell__UseTargetGroup       = false
    set udg_Spell__Time                 = 0.00
    set udg_Spell__Duration             = 0.00
    set udg_Spell__DurationPerLevel     = 0.00
    set udg_Spell__BuffAbility          = 0
    set udg_Spell__BuffOrder            = 0
   
    set udg_Spell__Filter_AllowEnemy        = udg_Spell_i_AllowEnemy[0]
    set udg_Spell__Filter_AllowAlly         = udg_Spell_i_AllowAlly[0]
    set udg_Spell__Filter_AllowDead         = udg_Spell_i_AllowDead[0]
    set udg_Spell__Filter_AllowMagicImmune  = udg_Spell_i_AllowMagicImmune[0]
    set udg_Spell__Filter_AllowMechanical   = udg_Spell_i_AllowMechanical[0]
    set udg_Spell__Filter_AllowStructure    = udg_Spell_i_AllowStructure[0]
    set udg_Spell__Filter_AllowFlying       = udg_Spell_i_AllowFlying[0]
    set udg_Spell__Filter_AllowHero         = udg_Spell_i_AllowHero[0]
    set udg_Spell__Filter_AllowNonHero      = udg_Spell_i_AllowNonHero[0]
    set udg_Spell__Filter_AllowLiving       = udg_Spell_i_AllowLiving[0]
endfunction
function SpellFilterCompare takes boolean is, boolean yes, boolean no returns boolean
    return (is and yes) or ((not is) and no)
endfunction
//===========================================================================
// Before calling this function, set Spell__InRangePoint to whatever point
// you need, THEN set Spell__InRange to the radius you need. The system will
// enumerate the units matching the configured filter and fill them into
// Spell_InRangeGroup.
//
function SpellGroupUnitsInRange takes nothing returns boolean
    local integer i = udg_Spell_i_Head[udg_Spell__Index]
    local integer j = 0
    local unit u
    local real padding = 64.00
    if udg_Spell_i_AllowStructure[i] then
        //A normal unit can only have up to size 64.00 collision, but if the
        //user needs to check for structures we need a padding big enough for
        //the "fattest" ones: Tier 3 town halls.
        set padding = 197.00
    endif
    call GroupEnumUnitsInRangeOfLoc(udg_Spell__InRangeGroup, udg_Spell__InRangePoint, udg_Spell__InRange + padding, null)
    loop
        set u = FirstOfGroup(udg_Spell__InRangeGroup)
        exitwhen u == null
        call GroupRemoveUnit(udg_Spell__InRangeGroup, u)
        loop
            exitwhen udg_Spell_i_AutoAddTargets[i] and IsUnitInGroup(u, udg_Spell__TargetGroup)
            exitwhen not IsUnitInRangeLoc(u, udg_Spell__InRangePoint, udg_Spell__InRange)
            exitwhen not SpellFilterCompare(IsUnitType(u, UNIT_TYPE_DEAD), udg_Spell_i_AllowDead[i], udg_Spell_i_AllowLiving[i])
            exitwhen not SpellFilterCompare(IsUnitAlly(u, udg_Spell__CasterOwner), udg_Spell_i_AllowAlly[i], udg_Spell_i_AllowEnemy[i])
            exitwhen not SpellFilterCompare(IsUnitType(u, UNIT_TYPE_HERO) or IsUnitType(u, UNIT_TYPE_RESISTANT), udg_Spell_i_AllowHero[i], udg_Spell_i_AllowNonHero[i])
            exitwhen IsUnitType(u, UNIT_TYPE_STRUCTURE) and not udg_Spell_i_AllowStructure[i]
            exitwhen IsUnitType(u, UNIT_TYPE_FLYING) and not udg_Spell_i_AllowFlying[i]
            exitwhen IsUnitType(u, UNIT_TYPE_MECHANICAL) and not udg_Spell_i_AllowMechanical[i]
            exitwhen IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and not udg_Spell_i_AllowMagicImmune[i]
            set udg_Spell__InRangeUnit = u
            //Run the user's designated filter, if one exists.
            exitwhen udg_Spell_i_InRangeFilter[i] != null and not TriggerEvaluate(udg_Spell_i_InRangeFilter[i])
            set j = j + 1
            set udg_Spell__InRangeUnits[j] = u
            exitwhen true
        endloop
    endloop
    if j > udg_Spell__InRangeMax and udg_Spell__InRangeMax > 0 then
        //The user has defined a maximum number of units allowed in the group.
        //Remove a random unit until the total does not exceed capacity.
        loop
            set i = GetRandomInt(1, j)
            set udg_Spell__InRangeUnits[i] = udg_Spell__InRangeUnits[j]
            set j = j - 1
            exitwhen j == udg_Spell__InRangeMax
        endloop
    endif
    set udg_Spell__InRangeCount = j
    set udg_Spell__InRangeMax = 0
    set udg_Spell__InRange = 0.00
    set i = udg_Spell_i_Head[udg_Spell__Index]
    loop
        exitwhen j == 0
        set u = udg_Spell__InRangeUnits[j]
        call GroupAddUnit(udg_Spell__InRangeGroup, u)
        if udg_Spell_i_AutoAddTargets[i] then
            call GroupAddUnit(udg_Spell__TargetGroup, u)
        endif
        if udg_Spell__WakeTargets and UnitIsSleeping(u) then
            call UnitWakeUp(u)
        endif
        if udg_Spell_i_BuffAbil[i] != 0 and udg_Spell_i_BuffOrder[i] != 0 then
            //Auto-buff units added to group:
            call UnitAddAbility(udg_Spell_i_PreloadDummy, udg_Spell_i_BuffAbil[i])
            call IssueTargetOrderById(udg_Spell_i_PreloadDummy, udg_Spell_i_BuffOrder[i], u)
            call UnitRemoveAbility(udg_Spell_i_PreloadDummy, udg_Spell_i_BuffAbil[i])
        endif
        set j = j - 1
    endloop
    set u = null
    return false
endfunction
function SpellPreloadEnd takes nothing returns nothing
    local integer i = udg_Spell_i_Instances
    loop
        exitwhen i == 0
        //Remove preloaded abilities so they don't interfere with orders
        call UnitRemoveAbility(udg_Spell_i_PreloadDummy, udg_Spell_i_Abil[udg_Spell_i_Head[i]])
        set i = i - 1
    endloop
endfunction
//===========================================================================
function InitTrig_Spell_System takes nothing returns nothing
    local integer i = bj_MAX_PLAYER_SLOTS
    local player p
    local trigger t
   
    if gg_trg_Spell_System != null then
        //A JASS function call already initialized the system.
        return
    endif
   
    //This runs before map init events so the hashtable is ready before then.
    set udg_Spell__Hash = InitHashtable()
   
    //Initialize these two locations which will never get removed
    set udg_Spell__CastPoint = Location(0, 0)
    set udg_Spell__TargetPoint = Location(0, 0)
   
    //Recycle existing unit groups into the recycle stack to avoid needing to destroy any extras.
    set udg_Spell_i_GroupStack[2] = udg_Spell__TargetGroup
    set udg_Spell_i_GroupStack[3] = udg_Spell_i_TargetGroup[0]
    set udg_Spell_i_GroupStack[4] = udg_Spell_i_TargetGroup[1]
    set udg_Spell_i_GroupN = 5 //There are already five valid unit groups thanks to Variable Editor.
   
    set t = CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_Spell__InRange", GREATER_THAN, 0.00)
    call TriggerAddCondition(t, Filter(function SpellGroupUnitsInRange))
   
    set t = CreateTrigger()
    call TriggerAddCondition(t, Filter(function SpellSystemEvent))
    loop
        set i = i - 1
        set p = Player(i)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CHANNEL, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_CAST, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_FINISH, null)
        call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_SPELL_ENDCAST, null)
        exitwhen i == 0
    endloop
    set p = null
    set t = null
   
    //Run the configuration trigger so its variables are ready before the
    //map initialization events run.
    call TriggerExecute(gg_trg_Spell_System_Config)
    call SpellSetFilters(0)
   
    //Create this trigger so it's GUI-friendly.
    set gg_trg_Spell_System = CreateTrigger()
    call TriggerAddAction(gg_trg_Spell_System, function SpellSystemRegister)
    set gg_trg_Spell_System_Config = gg_trg_Spell_System //In case the user accidentally picks this one
   
    //Create a dummy unit for preloading abilities and casting buffs.
    set udg_Spell_i_PreloadDummy = CreateUnit(udg_Spell__DummyOwner, udg_Spell__DummyType, 0, 0, 0)
   
    //Start the timer to remove its abilities:
    call TimerStart(udg_Spell_i_Timer, 0.00, false, function SpellPreloadEnd)
    call UnitRemoveAbility(udg_Spell_i_PreloadDummy, 'Amov') //Force it to never move to cast spells
endfunction


  • SS Holy Light Demo Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set Spell__Ability = Holy Light
      • -------- When holy light is cast on a unit, heal it each second for 4/6/8 seconds. --------
      • Set Spell__Time = 1.00
      • Set Spell__Duration = 2.00
      • Set Spell__DurationPerLevel = 2.00
      • -------- --------
      • Set Spell__Trigger_OnEffect = SS Holy Light Demo Effect <gen>
      • Set Spell__Trigger_OnLoop = SS Holy Light Demo Loop <gen>
      • Trigger - Run Spell System <gen> (ignoring conditions)
  • SS Holy Light Demo Effect
    • Events
    • Conditions
    • Actions
      • Set Spell__StartDuration = True
      • Special Effect - Create a special effect attached to the origin of Spell__Target using Abilities\Spells\NightElf\Rejuvenation\RejuvenationTarget.mdl
      • Set HolyLightBuff[Spell__Index] = (Last created special effect)
  • SS Holy Light Demo Loop
    • Events
    • Conditions
    • Actions
      • -------- Heals for 10/15/20 each second --------
      • Unit - Set life of Spell__Target to ((Life of Spell__Target) + (5.00 + (5.00 x Spell__LevelMultiplier)))
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Spell__Duration Less than or equal to 0.00
        • Then - Actions
          • -------- When the heal-over-time has ended, destroy the rejuvenation effect --------
          • Special Effect - Destroy HolyLightBuff[Spell__Index]
        • Else - Actions



JASS:
// This function is run every 1.00 for each active divine shield
function SSDivineShieldDemoLoop takes nothing returns nothing
    local integer i = 1
    set udg_Spell__InRangePoint = udg_Spell__CastPoint
    set udg_Spell__InRange = 600.00
    //Spell__CastPoint will always be the Caster's own location, even if he moves from where he originally casted
    loop
        exitwhen i > udg_Spell__InRangeCount
        call IssuePointOrderLoc(udg_Spell__InRangeUnits[i], "move", udg_Spell__CastPoint)
        set i = i + 1
    endloop
endfunction

// The next function will be run when the effect of Divine Shield starts.
function SSDivineShieldDemoEffect takes nothing returns nothing
    //Set the time until next expiration
    set udg_Spell__Time = 1.00
    //Set how long the buff will last
    set udg_Spell__Duration = 15.00*udg_Spell__Level
endfunction

//===========================================================================
function InitTrig_SS_Divine_Shield_Demo takes nothing returns nothing
    //Use of this spell requires you to copy the SpellSystem_Register function from the
    //map header or use the vJass library which contains it.
    call SpellSystem_Register('AHds', function SSDivineShieldDemoEffect, function SSDivineShieldDemoLoop)
endfunction


Keywords:
Spell, system, gui, anitarf, vexorian, jesus4lyf, timer32, spellevent, nestharus, struct
Contents

GUI Spell System (Map)

Reviews
Approved on the 11th Jan 2016 Last review update on the 4th Apr 2016 This resource has been approved by THW moderators BPower and IcemanBo. In consultation with the spell moderation team we agreed on a rating of 6/5, Director's Cut. Criticism...
Level 11
Joined
Jul 4, 2016
Messages
626
So, I find two cavets, when using the jass function to register,

one has to specifiy udg_Spell__Time and udg_Spell__Duration before executing the trigger or else loop doesn't work properly.

JASS:
   function RegisterSpellSystem takes integer abilId, code onEffect, code onLoop returns nothing
        set udg_Spell__Ability = abilId
        if onEffect != null then
            set udg_Spell__Trigger_OnEffect = CreateTrigger()
            call TriggerAddCondition(udg_Spell__Trigger_OnEffect, Filter(onEffect))
        endif
        if onLoop != null then
            set udg_Spell__Trigger_OnLoop = CreateTrigger()
            call TriggerAddCondition(udg_Spell__Trigger_OnLoop, Filter(onLoop))
        endif
        //These two lines must be added
        set udg_Spell__Time = 0.5
        set udg_Spell__Duration = 5.0
       //
        if gg_trg_Spell_System == null then
            call ExecuteFunc("InitTrig_Spell_System")
        endif
        call TriggerExecute(gg_trg_Spell_System)
    endfunction

Not sure if intended, but you can only change duration and time in the loop function, which would make the jass example now incorrect.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
The new Spell Event for Lua is nearly done. Just to give a preview of what the new API looks like:

  • Holy Light Enhancer
    • Events
      • Game - Button for ability Holy Light and order Human Spellbreaker - Spell Steal pressed.
      • Game - OnSpellEffect becomes Equal to 0.00
    • Conditions
    • Actions
      • Special Effect - Create a special effect attached to the chest of Spell__Target using Abilities\Spells\NightElf\Rejuvenation\RejuvenationTarget.mdl
      • Set VariableSet EffectArray[Spell__Index] = (Last created special effect)
      • Set VariableSet Spell__Duration = (2.00 + (2.00 x Spell__LevelMultiplier))
      • For each (Integer Spell__forDuration) from 0 to 0, do (Actions)
        • Loop - Actions
          • Set VariableSet Spell__wait = 1.00
          • Unit - Set life of Spell__Target to (((Life of Spell__Target) + 5.00) + (5.00 x Spell__LevelMultiplier))
          • Game - Display to (All players) the text: Unit is being healed
      • Special Effect - Destroy EffectArray[Spell__Index]
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
What in the holy fuck is this event, Bribe?! Did you custom make it or is that a thing in the game now?
  • Game - Button for ability Holy Light and order Human Spellbreaker - Spell Steal pressed.
That's an event visible in GUI since WarCraft 3 1.32 or 1.33, I've hooked it so that if you register that event with the Spell Steal order ID, it will directly run that trigger if and only if that specific spell is detected. I had to update CreateEvent to hack it into place, because CreateEvent really wants to run everything registered to it. I'm not convinced that I found the right approach behind the scenes yet, but as for the GUI the user has to deal with, I've made it stupid easy. The original JASS Holy Light enhancer needed something like 3 or 4 triggers to pull off with around 3-4x the length of code.
 
Is it possible to access the spell system's hashtable outside of itself for reasons like dealing damage from a value inside spell__hash upon colliding with a target by using a knockback system?

Edit: Think I'll go with a second hashtable, thought about using the spell system to handle everything however it'd be code bloat repeating the same code 50 times. I can only see trying to use spell__hash generating way too much extra overhead.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Is it possible to access the spell system's hashtable outside of itself for reasons like dealing damage from a value inside spell__hash upon colliding with a target by using a knockback system?

Edit: Think I'll go with a second hashtable, thought about using the spell system to handle everything however it'd be code bloat repeating the same code 50 times. I can only see trying to use spell__hash generating way too much extra overhead.
With the vJass version, it is possible to set the Spell__Time to be a high number, which in turn lets you access the spell index for that much extra time (it puts a hold on flushing the hashtable until the Spell__Time has expired). The Lua version works a lot differently.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Is it okay to use the same two cast/loop triggers for multiple abilities like say 50 for example?
Are you referring to spells copy/pasted 50 times, or 50 spells that all share one trigger that has a giant if/then/else on it?

If it's 50 separate triggers, you'll have much better efficiency, because the system will only run the triggers that are actually relevant.
 
Are you referring to spells copy/pasted 50 times, or 50 spells that all share one trigger that has a giant if/then/else on it?

If it's 50 separate triggers, you'll have much better efficiency, because the system will only run the triggers that are actually relevant.
50 spells using the same 2 triggers since they're mostly the same besides being either dash/projectile/shield with changing their values through hashtables.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
50 spells using the same 2 triggers since they're mostly the same besides being either dash/projectile/shield with changing their values through hashtables.
The vJass system doesn't support a sane approach to doing it that way; you might be better off using the normal "Any unit starts the effect of an ability" event. The Lua version would work fine like that.
 
The vJass system doesn't support a sane approach to doing it that way; you might be better off using the normal "Any unit starts the effect of an ability" event. The Lua version would work fine like that.
Does that mean it's more efficient to code bloat it for maximum speed in jass? I suppose overall it would be less code running instead of having to check for a few extra conditions on top of loading data from a hashtable... For the jass version, it would be better to somehow turn all action blocks into condition blocks instead right?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Does that mean it's more efficient to code bloat it for maximum speed in jass? I suppose overall it would be less code running instead of having to check for a few extra conditions on top of loading data from a hashtable... For the jass version, it would be better to somehow turn all action blocks into condition blocks instead right?
JASS is not very efficient to begin with. I don't think there would be much of a noticeable frame rate drop if you bloated up a single trigger, as long as it's not evaluating or executing 50 different triggers at once.

If you are adamant about using JASS, then I would recommend you write this 50-spell system you're proposing in JASS as well, so that you can define function parameters to handle the different logical branches.

Lua would still be the best option, though, because you can then stick to GUI and not have to code manually.
 
JASS is not very efficient to begin with. I don't think there would be much of a noticeable frame rate drop if you bloated up a single trigger, as long as it's not evaluating or executing 50 different triggers at once.

If you are adamant about using JASS, then I would recommend you write this 50-spell system you're proposing in JASS as well, so that you can define function parameters to handle the different logical branches.

Lua would still be the best option, though, because you can then stick to GUI and not have to code manually.
I know lua is superior, hopefully stays that way with directly converted code but currently am working with legacy code. I was thinking about the difference in speed between using 2 triggers to handle 50 different abilities since very similar then to having 100 triggers for 50 different abilities and is what I meant by code bloat since it's about the same code copied 98 times.
 
This is a weird one, but somehow silencing the spell_caster with soul burn on the OnEffect part breaks the onLoop, but only on every odd casts (1st, 3rd, etc.), while it works on every even casts (but sped up it feels like).

  • Lee Punch Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet Spell__Ability = Leaf Strong Fist
      • Set VariableSet Spell__Time = 0.20
      • Set VariableSet Spell__Duration = 1.40
      • Set VariableSet Spell__Filter_AllowEnemy = True
      • Set VariableSet Spell__Filter_AllowAlly = False
      • Set VariableSet Spell__Filter_AllowFlying = False
      • Set VariableSet Spell__Filter_AllowStructure = False
      • Set VariableSet Spell__Filter_AllowMagicImmune = True
      • -------- --------
      • Set VariableSet Spell__Trigger_OnEffect = Lee Punch OnEffect <gen>
      • Set VariableSet Spell__Trigger_OnLoop = Lee Punch OnLoop <gen>
      • Trigger - Run Spell System <gen> (ignoring conditions)
  • Lee Punch OnEffect
    • Events
    • Conditions
    • Actions
      • Custom script: local integer i
      • Custom script: set i = GetConvertedPlayerId(GetOwningPlayer( udg_Spell__Caster ))
      • -------- -- --------
      • Set VariableSet Spell__StartDuration = True
      • -------- -- --------
      • Set VariableSet TempL2 = (Spell__CastPoint offset by 40.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Set VariableSet TempL3 = (Spell__CastPoint offset by 100.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Special Effect - Create a special effect at TempL2 using VFX_Lee_Punches 2.mdx
      • Set VariableSet Lee_Punch_VFX[Spell__Index] = (Last created special effect)
      • Special Effect - Set Yaw of (Last created special effect) to: (Radians((Angle from Spell__CastPoint to Spell__TargetPoint)))
      • Special Effect - Set Scale of (Last created special effect) to 1.30
      • Special Effect - Create a special effect at TempL3 using VFX_Spell_Marker_200.mdx
      • Set VariableSet Lee_Punch_VFX_2[Spell__Index] = (Last created special effect)
      • Special Effect - Set Scale of (Last created special effect) to 0.65
      • Special Effect - Set Alpha of (Last created special effect) to 144
      • -------- -- --------
      • Unit - Make Spell__Caster face (Angle from Spell__CastPoint to Spell__TargetPoint)
      • Animation - Play Spell__Caster's spell animation
      • -------- -- --------
      • Custom script: set udg_RDFG_CanTurn [ i ] = false
      • Custom script: set udg_Atk_Boost_CanAtk [ i ] = false
      • Custom script: set udg_RDFG_CanWalkAnim [ i ] = false
      • -------- tenacity buff --------
      • Set VariableSet target_caster = Spell__Caster
      • Set VariableSet target_target_u = Spell__Caster
      • Set VariableSet target_duration = 8
      • Trigger - Run tenacity dummy <gen> (checking conditions)
      • Trigger - Run silence dummy <gen> (ignoring conditions)
      • -------- leaks --------
      • Custom script: call RemoveLocation ( udg_TempL2 )
      • Custom script: call RemoveLocation ( udg_TempL3 )
  • Lee Punch OnLoop
    • Events
    • Conditions
    • Actions
      • Custom script: local integer i
      • Custom script: set i = GetConvertedPlayerId(GetOwningPlayer( udg_Spell__Caster ))
      • -------- -- --------
      • -------- Pick all units within designated range of the target point --------
      • Set VariableSet Spell__InRangePoint = ((Position of Spell__Caster) offset by 100.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Set VariableSet Spell__InRange = 130.00
      • Unit Group - Pick every unit in Spell__InRangeGroup and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) has buff Dont Pick Me ) Equal to False
            • Then - Actions
              • Set VariableSet TempR1 = ((0.00 + (4.00 x Spell__LevelMultiplier)) + ((Real((Intelligence of Spell__Caster (Include bonuses)))) x 0.31))
              • Unit - Cause Spell__Caster to damage (Picked unit), dealing TempR1 damage of attack type Normal and damage type Normal
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Random integer number between 1 and 2) Equal to 1
                • Then - Actions
                  • Special Effect - Create a special effect attached to the chest of (Picked unit) using VFX_Hit_Y1_Hitstun.mdx
                  • Special Effect - Destroy (Last created special effect)
                • Else - Actions
                  • Special Effect - Create a special effect attached to the chest of (Picked unit) using VFX_Hit_Y2_soundless_hitstun.mdx
                  • Special Effect - Destroy (Last created special effect)
            • Else - Actions
      • -------- -- --------
      • Set VariableSet TempL1 = (Position of Spell__Caster)
      • Set VariableSet TempL2 = (TempL1 offset by 40.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Special Effect - Set Position of Lee_Punch_VFX[Spell__Index] to TempL2
      • Set VariableSet TempL3 = (TempL1 offset by 100.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Special Effect - Set Position of Lee_Punch_VFX_2[Spell__Index] to TempL3
      • -------- stop --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Spell__Duration Less than or equal to 0.00
        • Then - Actions
          • Special Effect - Destroy Lee_Punch_VFX[Spell__Index]
          • Special Effect - Destroy Lee_Punch_VFX_2[Spell__Index]
          • Custom script: set udg_RDFG_CanTurn [ i ] = true
          • Custom script: set udg_RDFG_CanWalkAnim [ i ] = true
          • Custom script: set udg_Atk_Boost_CanAtk [ i ] = true
          • Custom script: if ( udg_RDFG_IsMoving[ i ] == true ) then
          • Custom script: call SetUnitAnimation( udg_Spell__Caster , "walk" )
          • Custom script: else
          • Custom script: call SetUnitAnimation( udg_Spell__Caster , "stand" )
          • Custom script: endif
        • Else - Actions
      • -------- leaks --------
      • Custom script: call RemoveLocation ( udg_TempL1 )
      • Custom script: call RemoveLocation ( udg_TempL2 )
      • Custom script: call RemoveLocation ( udg_TempL3 )

  • tenacity dummy
    • Events
    • Conditions
    • Actions
      • Set VariableSet DummyTempL = (Position of target_target_u)
      • Unit - Create 1 Dummy Buffs Caster iframe, stun, slow, silence for (Owner of target_caster) at DummyTempL facing Default building facing degrees
      • Unit - Add Tenacity Buff - Dummy - slow - 40 lvl to (Last created unit)
      • Unit - Set level of Tenacity Buff - Dummy - slow - 40 lvl for (Last created unit) to target_duration
      • Unit - Order (Last created unit) to Human Sorceress - Slow target_target_u
      • Unit - Add a 0.30 second Generic expiration timer to (Last created unit)
      • Custom script: call RemoveLocation(udg_DummyTempL)
  • silence dummy
    • Events
    • Conditions
      • (target_target_u has buff Tenacity ) Equal to False
      • (target_target_u has buff Dont Pick Me ) Equal to False
      • (target_target_u is invulnerable) Equal to False
    • Actions
      • Set VariableSet DummyTempL2 = (Position of target_target_u)
      • Unit - Create 1 Dummy Buffs Caster iframe, stun, slow, silence for (Owner of target_caster) at DummyTempL2 facing Default building facing degrees
      • Unit - Set level of Silence - Dumm - 25 lvls for (Last created unit) to target_duration
      • Unit - Order (Last created unit) to Neutral Fire Lord - Soul Burn target_target_u
      • Unit - Add a 0.30 second Generic expiration timer to (Last created unit)
      • Custom script: call RemoveLocation(udg_DummyTempL2)

It works perfectly once I disable the silence.
Any ideas why that might possibly happen?
 
Last edited:
Level 6
Joined
Jul 14, 2020
Messages
128
This is a weird one, but somehow silencing the spell_caster with soul burn on the OnEffect part breaks the onLoop, but only on every odd casts (1st, 3rd, etc.) (I think it also speeds it up somehow), while it works perfectly on every even casts.

  • Lee Punch Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet Spell__Ability = Leaf Strong Fist
      • Set VariableSet Spell__Time = 0.20
      • Set VariableSet Spell__Duration = 1.40
      • Set VariableSet Spell__Filter_AllowEnemy = True
      • Set VariableSet Spell__Filter_AllowAlly = False
      • Set VariableSet Spell__Filter_AllowFlying = False
      • Set VariableSet Spell__Filter_AllowStructure = False
      • Set VariableSet Spell__Filter_AllowMagicImmune = True
      • -------- --------
      • Set VariableSet Spell__Trigger_OnEffect = Lee Punch OnEffect <gen>
      • Set VariableSet Spell__Trigger_OnLoop = Lee Punch OnLoop <gen>
      • Trigger - Run Spell System <gen> (ignoring conditions)
  • Lee Punch OnEffect
    • Events
    • Conditions
    • Actions
      • Custom script: local integer i
      • Custom script: set i = GetConvertedPlayerId(GetOwningPlayer( udg_Spell__Caster ))
      • -------- -- --------
      • Set VariableSet Spell__StartDuration = True
      • -------- -- --------
      • Set VariableSet TempL2 = (Spell__CastPoint offset by 40.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Set VariableSet TempL3 = (Spell__CastPoint offset by 100.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Special Effect - Create a special effect at TempL2 using VFX_Lee_Punches 2.mdx
      • Set VariableSet Lee_Punch_VFX[Spell__Index] = (Last created special effect)
      • Special Effect - Set Yaw of (Last created special effect) to: (Radians((Angle from Spell__CastPoint to Spell__TargetPoint)))
      • Special Effect - Set Scale of (Last created special effect) to 1.30
      • Special Effect - Create a special effect at TempL3 using VFX_Spell_Marker_200.mdx
      • Set VariableSet Lee_Punch_VFX_2[Spell__Index] = (Last created special effect)
      • Special Effect - Set Scale of (Last created special effect) to 0.65
      • Special Effect - Set Alpha of (Last created special effect) to 144
      • -------- -- --------
      • Unit - Make Spell__Caster face (Angle from Spell__CastPoint to Spell__TargetPoint)
      • Animation - Play Spell__Caster's spell animation
      • -------- -- --------
      • Custom script: set udg_RDFG_CanTurn [ i ] = false
      • Custom script: set udg_Atk_Boost_CanAtk [ i ] = false
      • Custom script: set udg_RDFG_CanWalkAnim [ i ] = false
      • -------- tenacity buff --------
      • Set VariableSet target_caster = Spell__Caster
      • Set VariableSet target_target_u = Spell__Caster
      • Set VariableSet target_duration = 8
      • Trigger - Run tenacity dummy <gen> (checking conditions)
      • Trigger - Run silence dummy <gen> (ignoring conditions)
      • -------- leaks --------
      • Custom script: call RemoveLocation ( udg_TempL2 )
      • Custom script: call RemoveLocation ( udg_TempL3 )
  • Lee Punch OnLoop
    • Events
    • Conditions
    • Actions
      • Custom script: local integer i
      • Custom script: set i = GetConvertedPlayerId(GetOwningPlayer( udg_Spell__Caster ))
      • -------- -- --------
      • -------- Pick all units within designated range of the target point --------
      • Set VariableSet Spell__InRangePoint = ((Position of Spell__Caster) offset by 100.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Set VariableSet Spell__InRange = 130.00
      • Unit Group - Pick every unit in Spell__InRangeGroup and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • ((Picked unit) has buff Dont Pick Me ) Equal to False
            • Then - Actions
              • Set VariableSet TempR1 = ((0.00 + (4.00 x Spell__LevelMultiplier)) + ((Real((Intelligence of Spell__Caster (Include bonuses)))) x 0.31))
              • Unit - Cause Spell__Caster to damage (Picked unit), dealing TempR1 damage of attack type Normal and damage type Normal
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Random integer number between 1 and 2) Equal to 1
                • Then - Actions
                  • Special Effect - Create a special effect attached to the chest of (Picked unit) using VFX_Hit_Y1_Hitstun.mdx
                  • Special Effect - Destroy (Last created special effect)
                • Else - Actions
                  • Special Effect - Create a special effect attached to the chest of (Picked unit) using VFX_Hit_Y2_soundless_hitstun.mdx
                  • Special Effect - Destroy (Last created special effect)
            • Else - Actions
      • -------- -- --------
      • Set VariableSet TempL1 = (Position of Spell__Caster)
      • Set VariableSet TempL2 = (TempL1 offset by 40.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Special Effect - Set Position of Lee_Punch_VFX[Spell__Index] to TempL2
      • Set VariableSet TempL3 = (TempL1 offset by 100.00 towards (Angle from Spell__CastPoint to Spell__TargetPoint) degrees.)
      • Special Effect - Set Position of Lee_Punch_VFX_2[Spell__Index] to TempL3
      • -------- stop --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Spell__Duration Less than or equal to 0.00
        • Then - Actions
          • Special Effect - Destroy Lee_Punch_VFX[Spell__Index]
          • Special Effect - Destroy Lee_Punch_VFX_2[Spell__Index]
          • Custom script: set udg_RDFG_CanTurn [ i ] = true
          • Custom script: set udg_RDFG_CanWalkAnim [ i ] = true
          • Custom script: set udg_Atk_Boost_CanAtk [ i ] = true
          • Custom script: if ( udg_RDFG_IsMoving[ i ] == true ) then
          • Custom script: call SetUnitAnimation( udg_Spell__Caster , "walk" )
          • Custom script: else
          • Custom script: call SetUnitAnimation( udg_Spell__Caster , "stand" )
          • Custom script: endif
        • Else - Actions
      • -------- leaks --------
      • Custom script: call RemoveLocation ( udg_TempL1 )
      • Custom script: call RemoveLocation ( udg_TempL2 )
      • Custom script: call RemoveLocation ( udg_TempL3 )

  • tenacity dummy
    • Events
    • Conditions
    • Actions
      • Set VariableSet DummyTempL = (Position of target_target_u)
      • Unit - Create 1 Dummy Buffs Caster iframe, stun, slow, silence for (Owner of target_caster) at DummyTempL facing Default building facing degrees
      • Unit - Add Tenacity Buff - Dummy - slow - 40 lvl to (Last created unit)
      • Unit - Set level of Tenacity Buff - Dummy - slow - 40 lvl for (Last created unit) to target_duration
      • Unit - Order (Last created unit) to Human Sorceress - Slow target_target_u
      • Unit - Add a 0.30 second Generic expiration timer to (Last created unit)
      • Custom script: call RemoveLocation(udg_DummyTempL)
  • silence dummy
    • Events
    • Conditions
      • (target_target_u has buff Tenacity ) Equal to False
      • (target_target_u has buff Dont Pick Me ) Equal to False
      • (target_target_u is invulnerable) Equal to False
    • Actions
      • Set VariableSet DummyTempL2 = (Position of target_target_u)
      • Unit - Create 1 Dummy Buffs Caster iframe, stun, slow, silence for (Owner of target_caster) at DummyTempL2 facing Default building facing degrees
      • Unit - Set level of Silence - Dumm - 25 lvls for (Last created unit) to target_duration
      • Unit - Order (Last created unit) to Neutral Fire Lord - Soul Burn target_target_u
      • Unit - Add a 0.30 second Generic expiration timer to (Last created unit)
      • Custom script: call RemoveLocation(udg_DummyTempL2)

It works perfectly once I disable the silence.
Any ideas why that might possibly happen?
What a strange thing, I also discovered that if you pause the unit in the StartEffect trigger, the OnLoop is never processed.
 
Level 6
Joined
Jul 14, 2020
Messages
128
Sorry you are both having issues with this. I've never tested the interaction with silenced nor with paused units, so I'll add that to the list of things I'll need to look out for.
Moving on to another topic, do you have a version of the System prior to 1.28a? And if not, does it use some new native/function that prevents me from copying the code to 1.27b?
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Moving on to another topic, do you have a version of the System prior to 1.28a? And if not, does it use some new native/function that prevents me from copying the code to 1.27b?
GetSpellTargetX/Y were new in 1.24, same with hashtables. I don't think anything the JASS Spell System uses is different in any other patches. If you can't copy the triggers though, then that would just be because the map was saved in a newer version of World Editor. Someone with an older version of World Editor would have to see if they could help with that.

One day, I might install a second copy of the game which uses an older version of War3, just to help people like yourself with legacy questions. But I don't see that happening any time in the next months as I have all of my attention on getting core Lua systems set up.
 
Last edited:
Level 6
Joined
Jul 14, 2020
Messages
128
GetSpellTargetX/Y were new in 1.24, same with hashtables. I don't think anything the JASS Spell System uses is different in any other patches. If you can't copy the triggers though, then that would just be because the map was saved in a newer version of World Editor. Someone with an older version of World Editor would have to see if they could help with that.

One day, I might install a second copy of the game which uses an older version of War3, just to help people like yourself with legacy questions. But I don't see that happening any time in the next months as I have all of my attention on getting core Lua systems set up.
I understand, thanks Bribe
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I see, in that case, does it mean the silence/pause issues won't get resolved in the short term until then?
My guess is the reason why they bug would be because WarCraft 3 may not correctly fire the remaining spell events after the "starts effect" event. If that is the case, I can do analysis on what occurs in Lua Spell Event and see if Soul Burn/Pausing screws with the sequence.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
The variables don't seem to be set if you try to reference them outside of the on-effect trigger with a function call.
I'm trying for an object approach for something and would like to set all the variables to a sort of template for every ability of the same type.
Are you using the JASS version or the Lua version?

The JASS version has a function called 'SpellIndexGetVars' that you would need to call with the parameter of the Spell__Index value.

The Lua version will just let you directly set Spell__Index to the needed value, and it will correspond to the other variables accordingly.
 
Are you using the JASS version or the Lua version?

The JASS version has a function called 'SpellIndexGetVars' that you would need to call with the parameter of the Spell__Index value.

The Lua version will just let you directly set Spell__Index to the needed value, and it will correspond to the other variables accordingly.
  • Kamehameha Config
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- --------
      • Set Spell__Ability = Kamehameha (Goku)
      • Set Spell__Trigger_OnEffect = Dat Q <gen>
      • Custom script: call SpellSystemRegister()
      • -------- --------
  • Dat Q
    • Events
    • Conditions
    • Actions
      • Custom script: call Beam()
  • Beam Class
    • Events
    • Conditions
    • Actions
      • Custom script: endfunction
      • Custom script: function Beam takes nothing returns nothing
      • -------- 2D Template --------
      • Set FK_DamageValueSet = 0.00
      • Set FK_CollideTerrainSet = 0
      • Set FK_DragDMGset = ((Real((Agility of Spell__Caster (Include bonuses)))) x 0.10)
      • Set FK_DragSet = 0
      • Custom script: set udg_FK_X = GetLocationX(udg_Spell__TargetPoint)
      • Custom script: set udg_FK_Y = GetLocationY(udg_Spell__TargetPoint)
      • Set FK_SpeedSet = 60.00
      • Set FK_FrictionSet = 2.00
      • Set FK_RangeCheck = 1350.00
      • Set FK_MinRange = 1800.00
      • Set FK_height = 300.00
      • Set FK_AoE = 400.00
      • Set FK_RotationCounterSet = 5
      • Set FK_RotationSet = 4
      • Set FK_RotationSpeedSet = 15.00
      • Set FK_SFX_Set = Abilities\Spells\Orc\FeralSpirit\feralspiritdone.mdl
      • -------- can improve by making unit at x and y coords in event --------
      • -------- then i just need a connector var for the mana... --------
      • -------- in which case check if fk_unitset is null aka for dashes --------
      • Unit - Create 1 Dummy (Vexorian, Anitarf, Infrane) for Spell__CasterOwner at Spell__CastPoint facing Default building facing degrees
      • Set TempUnit = (Last created unit)
      • Unit Group - Add TempUnit to CollisionGroup
      • Unit - Set mana of TempUnit to ((Real((Agility of Spell__Caster (Include bonuses)))) x 8.00)
      • -------- change it to jass native and set properly there --------
      • Set FK_UnitSet = TempUnit
      • Custom script: call Sound.play("war3mapImported\\teleport.wav", 50, 120)
      • Animation - Change FK_UnitSet's size to (1250.00%, 100.00%, 100.00%) of its original size
      • Animation - Change FK_UnitSet flying height to 125.00 at 0.00
      • Animation - Change FK_UnitSet's vertex coloring to (100.00%, 100.00%, 100.00%) with 25.00% transparency
      • Set FK_AngleSet = (Angle from Spell__CastPoint to Spell__TargetPoint)
      • Set FK_Want3D = False
      • Set FK_Want4D = False
      • Set FK_WantCC = True
      • Set FK_IsStopSet = True
      • Set FK_hasChain = True
      • Set FK_SegRed = 100.00
      • Set FK_SegGreen = 100.00
      • Set FK_SegBlue = 100.00
      • Set FK_SegHeightAmount = 125.00
      • Set FK_SegSizeAmount = 1200.00
      • Set FK_LinkRange = 100.00
      • -------- Are we removing the projectile instance or not? --------
      • Set FK_Window = False
      • Set FK_ExpireSet = 5.00
      • Set FK_TrailExpireSet = 3.00
      • Set FK_UserSet = Spell__Caster
      • -------- since i dont wanna pause players... need a dummy visual for anim --------
      • Animation - Play Spell__Caster's (Load (Key Q-anim) of TempInt from HeroTable) animation
      • Sound - Play (Load (Key Q-sound) of TempInt in (Last created hashtable)) at 75.00% volume, located at LocPoint1 with Z offset 0.00
      • Special Effect - Create a special effect attached to the (Load (Key Q-hand-attach) of TempInt from HeroTable) of Spell__Caster using (Load (Key Q-hand-sfx) of TempInt from HeroTable)
      • Animation - Change Spell__Caster's animation speed to (Load (Key Q-anim-speed) of TempInt from HeroTable)% of its original speed
      • Set FK_Player[FK_ID] = (Owner of FK_UserSet)
      • -------- --------
      • Set FK_Values = (Custom value of FK_UnitSet)
      • Set FK_SavedIndex[Spell__Index] = FK_SystemMatcher[FK_Values]
      • -------- should likely change to multiplier since x ki(agility) --------
      • Set FK_WantPassDMG = 100
      • Set FK_DMGamount = 1.00
      • Set FK_DMGarea = 250.00
      • Set FK_ExplodeWhen = 1
      • Set FK_ExplodeArea = 800.00
      • Set FK_ExplodeDmgAmount = 1.00
      • -------- End of 2D Template --------
      • Game - Display to (All players) the text: well hello
Plan is to sort of take an object-type approach instead of copy and pasting the same block 100 different times, this way I could just modify speed and a few other values instead of having to copy and paste every single line. However even though call isn't suppose to start a new thread, the variables are nowhere to be found despite calling the function inside the on-effect trigger linked to the spell system.

Edit: After a lot of copy then delete then paste, managed to organize my triggers for this.
Custom script: call Beam(udg_Spell__Index) and Custom script: call SpellIndexGetVars(index) works very nicely, but doesn't this mean I am setting the spell system variables twice per ability now?
  • Dat Q
    • Events
    • Conditions
    • Actions
      • Custom script: call Beam(udg_Spell__Index)
  • Beam Class
    • Events
    • Conditions
    • Actions
      • Custom script: endfunction
      • Custom script: function Beam takes integer index returns nothing
      • Custom script: call SpellIndexGetVars(index)
      • -------- 2D Template --------
      • Set FK_DamageValueSet = 0.00
      • Set FK_CollideTerrainSet = 0
      • Set FK_DragDMGset = ((Real((Agility of Spell__Caster (Include bonuses)))) x 0.10)
      • Set FK_DragSet = 0
      • Custom script: set udg_FK_X = GetLocationX(udg_Spell__TargetPoint)
      • Custom script: set udg_FK_Y = GetLocationY(udg_Spell__TargetPoint)
      • Set FK_SpeedSet = 60.00
      • Set FK_FrictionSet = 2.00
      • Set FK_RangeCheck = 1350.00
      • Set FK_MinRange = 1800.00
      • Set FK_height = 300.00
      • Set FK_AoE = 400.00
      • Set FK_RotationCounterSet = 5
      • Set FK_RotationSet = 4
      • Set FK_RotationSpeedSet = 15.00
      • Set FK_SFX_Set = Abilities\Spells\Orc\FeralSpirit\feralspiritdone.mdl
      • -------- can improve by making unit at x and y coords in event --------
      • -------- then i just need a connector var for the mana... --------
      • -------- in which case check if fk_unitset is null aka for dashes --------
      • Unit - Create 1 Dummy (Vexorian, Anitarf, Infrane) for Spell__CasterOwner at Spell__CastPoint facing Default building facing degrees
      • Set TempUnit = (Last created unit)
      • Unit Group - Add TempUnit to CollisionGroup
      • Unit - Set mana of TempUnit to ((Real((Agility of Spell__Caster (Include bonuses)))) x 8.00)
      • -------- change it to jass native and set properly there --------
      • Set FK_UnitSet = TempUnit
      • Special Effect - Create a special effect attached to the origin of FK_UnitSet using Abilities\Weapons\SpiritOfVengeanceMissile\SpiritOfVengeanceMissile.mdl
      • Custom script: call DELFX.Create("buuhanBall_500.mdl", true, udg_FK_UnitSet, "chest", 0, 0, 0, 1, 2)
      • Custom script: call DestroyEffectTimed(AddSpecialEffectTarget("buuhanBall_500.mdl", udg_FK_UnitSet, "chest"), 2)
      • Custom script: call Sound.play("war3mapImported\\teleport.wav", 50, 120)
      • Animation - Change FK_UnitSet's size to (1250.00%, 100.00%, 100.00%) of its original size
      • Animation - Change FK_UnitSet flying height to 125.00 at 0.00
      • Animation - Change FK_UnitSet's vertex coloring to (100.00%, 100.00%, 100.00%) with 25.00% transparency
      • Set FK_AngleSet = (Angle from Spell__CastPoint to Spell__TargetPoint)
      • Set FK_Want3D = False
      • Set FK_Want4D = False
      • Set FK_WantCC = True
      • Set FK_IsStopSet = True
      • Set FK_hasChain = True
      • Set FK_SegRed = 100.00
      • Set FK_SegGreen = 100.00
      • Set FK_SegBlue = 100.00
      • Set FK_SegHeightAmount = 125.00
      • Set FK_SegSizeAmount = 1200.00
      • Set FK_LinkRange = 100.00
      • -------- Are we removing the projectile instance or not? --------
      • Set FK_Window = False
      • Set FK_ExpireSet = 5.00
      • Set FK_TrailExpireSet = 3.00
      • Set FK_UserSet = Spell__Caster
      • -------- since i dont wanna pause players... need a dummy visual for anim --------
      • Animation - Play Spell__Caster's (Load (Key Q-anim) of TempInt from HeroTable) animation
      • Sound - Play (Load (Key Q-sound) of TempInt in (Last created hashtable)) at 75.00% volume, located at LocPoint1 with Z offset 0.00
      • Special Effect - Create a special effect attached to the (Load (Key Q-hand-attach) of TempInt from HeroTable) of Spell__Caster using (Load (Key Q-hand-sfx) of TempInt from HeroTable)
      • Animation - Change Spell__Caster's animation speed to (Load (Key Q-anim-speed) of TempInt from HeroTable)% of its original speed
      • Set FK_Player[FK_ID] = (Owner of FK_UserSet)
      • -------- --------
      • Set FK_Values = (Custom value of FK_UnitSet)
      • Set FK_SavedIndex[Spell__Index] = FK_SystemMatcher[FK_Values]
      • -------- should likely change to multiplier since x ki(agility) --------
      • Set FK_WantPassDMG = 100
      • Set FK_DMGamount = 1.00
      • Set FK_DMGarea = 250.00
      • Set FK_ExplodeWhen = 1
      • Set FK_ExplodeArea = 800.00
      • Set FK_ExplodeDmgAmount = 1.00
      • Custom script: call FKevent()
      • -------- End of 2D Template --------
      • Game - Display to (All players) the text: well hello
 
Last edited:
Is dynamic spell duration (e.g. adding duration based on different conditions while the loop phase is running) supported?
From my experience, I recall it does but need to be handled with care. It is also done by utilizing the spell index iirc.

EDIT:
Tsukuyomi v1.2 uses this due to the two-phase of the spells. The timer is reset to a new value on entering second phase.
 
That OnLoop part seems a bit tricky, it doesn't work as it should if you go into higher durations you'll notice it a lot easier. For example a duration and time of 2 is actually 4 instead of being in 2 seconds and ending in 2 seconds...
That's obviously a technical limitation because you have to go to the next iteration of the loop to check if the duration is over.
A workaround for your case would be setting Spell__Time to 2 and Spell__Duration to something slightly lower than 2. That way the OnLoop will end after the first 2-second Spell__Time loop.
 
That's obviously a technical limitation because you have to go to the next iteration of the loop to check if the duration is over.
A workaround for your case would be setting Spell__Time to 2 and Spell__Duration to something slightly lower than 2. That way the OnLoop will end after the first 2-second Spell__Time loop.
So that's a design flaw then? Mui Doesn't suffer from this technical limitation, actually I found it best using both this and that.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
So, I find two cavets, when using the jass function to register,

one has to specifiy udg_Spell__Time and udg_Spell__Duration before executing the trigger or else loop doesn't work properly.

JASS:
   function RegisterSpellSystem takes integer abilId, code onEffect, code onLoop returns nothing
        set udg_Spell__Ability = abilId
        if onEffect != null then
            set udg_Spell__Trigger_OnEffect = CreateTrigger()
            call TriggerAddCondition(udg_Spell__Trigger_OnEffect, Filter(onEffect))
        endif
        if onLoop != null then
            set udg_Spell__Trigger_OnLoop = CreateTrigger()
            call TriggerAddCondition(udg_Spell__Trigger_OnLoop, Filter(onLoop))
        endif
        //These two lines must be added
        set udg_Spell__Time = 0.5
        set udg_Spell__Duration = 5.0
       //
        if gg_trg_Spell_System == null then
            call ExecuteFunc("InitTrig_Spell_System")
        endif
        call TriggerExecute(gg_trg_Spell_System)
    endfunction

Not sure if intended, but you can only change duration and time in the loop function, which would make the jass example now incorrect.
I'm not sure what this change would accomplish. Those are just random numbers for spell time and duration, so I'd rather not add them to the RegisterSpellSystem. Here's how I use this function in the demo map:

JASS:
function InitTrig_SS_Divine_Shield_Demo takes nothing returns nothing
    //Use of this spell requires you to copy the RegisterSpellSystem function from the
    //map header or use the vJass library which contains it.
   
    //Set the time between expirations:
    set udg_Spell__Time = 1.00
   
    //Set how long the buff will last (15/30/45)
    set udg_Spell__DurationPerLevel = 15.00
   
    call RegisterSpellSystem('AHds', function SSDivineShieldDemoEffect, function SSDivineShieldDemoLoop)
endfunction

@BoatyMcBoatface300 @Rasamayu I am not sure what you're expecting with the 'loop' functionality. Do you want the onLoop trigger to never run at all, or do you want it to run? I would see it not running at all to be 'expected behavior' considering the auto-destructive nature of the spell indices. However, that it works half the time is just plain weird and should not be the case. I'll keep an eye out for what could be causing such inconsistencies in the code.

@Tal'Rarity could you please try this refactored version to see if it fixes the problems you described?

 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Curious, is there a plan to add the ability to get hero stats for real variables like the level multiplier variable? I think it will add additional value for users since now they don't have to deal with the typecasting in GUI
That would be more a GUIGUI project that could potentially allow integer-as-real values. Currently only possible in JASS, and such micro adjustments will just add further overhead to this system. If I add str/agi/int, I'd probably also have to add stuff like HP and MP, and for the target and source alike? Probably better to keep this specific.
 
That would be more a GUIGUI project that could potentially allow integer-as-real values. Currently only possible in JASS, and such micro adjustments will just add further overhead to this system. If I add str/agi/int, I'd probably also have to add stuff like HP and MP, and for the target and source alike? Probably better to keep this specific.
(guigui is not a trigger editor)
 
Level 21
Joined
Dec 4, 2007
Messages
1,474
This feels like an oversight on my end, but let's say you reset a spell's cooldown and cast it again while it is still running.
Like a timed aura effect or the likes.
How would you reset its duration?
Currently i'm observing, that, (indexing or not) the system only accounts for the duration of the first cast until it ends.
And sometimes even gets cut short, running only for ~5 seconds - as if the spelldurations collide somewhere (multiple units casting).
It seems to me, that some function is missing, which tells the system to reset its duration?

  • HL POR CallForthHolyMight setup
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet Spell__Ability = Paladin of Ret (CFHM)
      • Set VariableSet Spell__Time = 1.00
      • Set VariableSet Spell__Duration = 20.00
      • Set VariableSet Spell__Trigger_OnEffect = HL POR CallForthHolyMight effect <gen>
      • Set VariableSet Spell__Trigger_OnLoop = HL POR CallForthHolyMight Loop <gen>
      • Trigger - Run Spell System <gen> (ignoring conditions)
  • HL POR CallForthHolyMight effect
    • Events
    • Conditions
    • Actions
      • Set VariableSet Spell__StartDuration = True
      • Sound - Play D2handofgod <gen> at 100.00% volume, attached to Spell_i_Caster[Spell__Index]
      • Unit - Remove Paladin of Retribution (CFHM)+50) from Spell_i_Caster[Spell__Index]
      • Unit - Remove Paladin of Retribution (CFHM)+100) from Spell_i_Caster[Spell__Index]
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Level of Paladin of Ret (CFHM) for Spell_i_Caster[Spell__Index]) Equal to 1
        • Then - Actions
          • Unit - Add Paladin of Retribution (CFHM)+50) to Spell_i_Caster[Spell__Index]
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Level of Paladin of Ret (CFHM) for Spell_i_Caster[Spell__Index]) Equal to 2
            • Then - Actions
              • Unit - Add Paladin of Retribution (CFHM)+100) to Spell_i_Caster[Spell__Index]
            • Else - Actions
  • HL POR CallForthHolyMight Loop
    • Events
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Spell__Duration Less than or equal to 0.00
        • Then - Actions
          • Unit - Remove Paladin of Retribution (CFHM)+50) from Spell_i_Caster[Spell__Index]
          • Unit - Remove Paladin of Retribution (CFHM)+100) from Spell_i_Caster[Spell__Index]
        • Else - Actions
 
Last edited:
This feels like an oversight on my end, but let's say you reset a spell's cooldown and cast it again while it is still running.
Like a timed aura effect or the likes.
How would you reset its duration?
Currently i'm observing, that, (indexing or not) the system only accounts for the duration of the first cast until it ends.
And sometimes even gets cut short, running only for ~5 seconds - as if the spelldurations collide somewhere.
It seems to me, that some function is missing, which tells the system to reset its duration?

  • HL POR CallForthHolyMight setup
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set VariableSet Spell__Ability = Paladin of Ret (CFHM)
      • Set VariableSet Spell__Time = 1.00
      • Set VariableSet Spell__Duration = 20.00
      • Set VariableSet Spell__Trigger_OnEffect = HL POR CallForthHolyMight effect <gen>
      • Set VariableSet Spell__Trigger_OnLoop = HL POR CallForthHolyMight Loop <gen>
      • Trigger - Run Spell System <gen> (ignoring conditions)
  • HL POR CallForthHolyMight effect
    • Events
    • Conditions
    • Actions
      • Set VariableSet Spell__StartDuration = True
      • Sound - Play D2handofgod <gen> at 100.00% volume, attached to Spell_i_Caster[Spell__Index]
      • Unit - Remove Paladin of Retribution (CFHM)+50) from Spell_i_Caster[Spell__Index]
      • Unit - Remove Paladin of Retribution (CFHM)+100) from Spell_i_Caster[Spell__Index]
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Level of Paladin of Ret (CFHM) for Spell_i_Caster[Spell__Index]) Equal to 1
        • Then - Actions
          • Unit - Add Paladin of Retribution (CFHM)+50) to Spell_i_Caster[Spell__Index]
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Level of Paladin of Ret (CFHM) for Spell_i_Caster[Spell__Index]) Equal to 2
            • Then - Actions
              • Unit - Add Paladin of Retribution (CFHM)+100) to Spell_i_Caster[Spell__Index]
            • Else - Actions
  • HL POR CallForthHolyMight Loop
    • Events
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Spell__Duration Less than or equal to 0.00
        • Then - Actions
          • Unit - Remove Paladin of Retribution (CFHM)+50) from Spell_i_Caster[Spell__Index]
          • Unit - Remove Paladin of Retribution (CFHM)+100) from Spell_i_Caster[Spell__Index]
        • Else - Actions
I think this is the intentional behavior, since ability is tied to the unit and not the instance, thus the first instance will remove the ability before the second instance reached the end duration. An approach is to check over all instance of this specific ability, something like Assassin Hunt v1.1x checking on the ASHBuffEnd trigger. Another approach is to create a counter tied with Unit Indexer for this ability, and only remove the ability when the counter reaches 0 (increase counter on cast, reduce counter on end loop).
 
Level 21
Joined
Dec 4, 2007
Messages
1,474
I think this is the intentional behavior, since ability is tied to the unit and not the instance, thus the first instance will remove the ability before the second instance reached the end duration. An approach is to check over all instance of this specific ability, something like Assassin Hunt v1.1x checking on the ASHBuffEnd trigger. Another approach is to create a counter tied with Unit Indexer for this ability, and only remove the ability when the counter reaches 0 (increase counter on cast, reduce counter on end loop).
Appreciate the quick response!
However, i know how to index it on my own (was doing that for years, but with very low enthusiasm due to its tedious nature).
I was hoping to use this system instead of having to index 100 or so abilities of similar quality.
(Probably time to stop slacking around and just do it xD...)
Perhaps it's not what i need in this special case (sadly).
 
Last edited:
Top