Time Leap v1.1

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
Description: Creates a huge wave around the caster which shifts away enemies it touches and lets them reappear at the end of the wave.

Requires: TimerUtils, any Unit Indexer (Bribe's Unit Indexer in the example)

How to import: Simply copy all of the triggers into your map. Use an ability with no target for the spell.


JASS:
native UnitAlive takes unit u returns boolean

scope TimeLeap initializer Event
    // by GIMLI_2
    // requires TimerUtils and any Unit Indexer
    // Credits to Vexorian and Bribe

    globals
        // The spell that triggers the Time Leap
        private integer Spell           = 'A000'
        // Basically the speed of the wave, dont go too low on this one
        private real    Interval        = 0.08
        // Animation of the waves
        private string  Animation       = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
        // Animation when it captures a unit
        private string  AnimationDis    = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportCaster.mdl"
        // Animation when it releases a unit
        private string  AnimationRe     = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportCaster.mdl"
        
        // Contains every unit that is currently affected by Time Leap
        private group AffectedUnits = CreateGroup()
        private group Temp          = CreateGroup()
        private group g             = CreateGroup()
        
        // Caster of Time Leap
        private unit array      Caster
        // References each unit to the Caster
        private unit array      Owner
        private boolean array   Affected
        // Position of the Caster
        private real array      X
        private real array      Y
        // Ending position of affected units
        private real array      X2
        private real array      Y2
        // Level of Time Leap
        private integer array   Level
        // A timer
        private integer array   Counter
    endglobals
    
    // The AoE of the spell
    private function MaxDistance takes integer level returns real
        return 200.00 + (200.00 * level)
    endfunction
    
    // Damage dealt at the end of the ability
    private function Damage takes integer level returns real
        return 40.00 * level
    endfunction
    
    // The number of effects around the caster per loop
    private function NumberOfBeams takes integer level returns integer
        return 4 + (4 * level)
    endfunction
    
    // The number of loops
    private function NumberOfWaves takes integer level returns integer
        return 20 + (0 * level)
    endfunction

    private function Loop takes nothing returns nothing
        local timer     t       = GetExpiredTimer()
        local integer   id      = GetTimerData(t)
        local integer   id2
        local real      x
        local real      y
        local real      angle
        local real      dist    = (MaxDistance(Level[id]) / NumberOfWaves(Level[id])) *  Counter[id]
        local unit      FoG
        local integer   index   = 0
        
        // Catches every unit in x range and lets it "disappear"
        call GroupEnumUnitsInRange(g, X[id], Y[id], dist, null)
        loop
            set FoG = FirstOfGroup(g)
            exitwhen FoG == null
            call GroupRemoveUnit(g, FoG)
            if IsUnitEnemy(FoG, GetOwningPlayer(Caster[id])) and UnitAlive(FoG) then
                set id2 = GetUnitUserData(FoG)
                if not Affected[id2] then
                    set x = GetUnitX(FoG)
                    set y = GetUnitY(FoG)
                    set Affected[id2] = true
                    call ShowUnit(FoG, false)
                    call SetUnitInvulnerable(FoG, true)
                    call PauseUnit(FoG, true)
                    call GroupAddUnit(AffectedUnits, FoG)
                    set Owner[id2] = Caster[id]
                    set angle = Atan2(y - Y[id], x - X[id])
                    set X2[id2] = X[id] + MaxDistance(Level[id]) * Cos(angle)
                    set Y2[id2] = Y[id] + MaxDistance(Level[id]) * Sin(angle)
                    call DestroyEffect(AddSpecialEffect(AnimationDis, x, y))
                endif
            endif
        endloop
        
        // Graphical effects
        loop
            set index = index + 1
            set angle = (360.00/NumberOfBeams(Level[id])) * index
            set x = X[id] + dist * Cos(angle * bj_DEGTORAD)
            set y = Y[id] + dist * Sin(angle * bj_DEGTORAD)
            call DestroyEffect(AddSpecialEffect(Animation, x, y))
            exitwhen index == NumberOfBeams(Level[id])
        endloop
        
        // Happens when spell ends, sets units back to the offset and damages them
        set Counter[id] = Counter[id] + 1
        if Counter[id] > NumberOfWaves(Level[id]) then
            loop
                set FoG = FirstOfGroup(AffectedUnits)
                exitwhen FoG == null
                set id2 = GetUnitUserData(FoG)
                call GroupRemoveUnit(AffectedUnits, FoG)
                if Owner[id2] == Caster[id] then
                    call SetUnitPosition(FoG, X2[id2], Y2[id2])
                    set Affected[id2] = false
                    call ShowUnit(FoG, true)
                    call SetUnitInvulnerable(FoG, false)
                    call PauseUnit(FoG, false)
                    call DestroyEffect(AddSpecialEffect(AnimationRe, X2[id2], Y2[id2]))
                    call UnitDamageTarget(Caster[id], FoG, Damage(Level[id]), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
                else
                    call GroupAddUnit(Temp, FoG)
                endif
            endloop
            loop
                set FoG = FirstOfGroup(Temp)
                exitwhen FoG == null
                call GroupRemoveUnit(Temp, FoG)
                call GroupAddUnit(AffectedUnits, FoG)
            endloop
            call ReleaseTimer(t)
        endif
        
    endfunction

    // Checks if the casted ability is the correct one and sets up needed variables
    private function Actions takes nothing returns boolean
        local unit      u
        local integer   id
        if GetSpellAbilityId() == Spell then
            set u = GetTriggerUnit()
            set id = GetUnitUserData(u)
            set Caster[id] = u
            set X[id] = GetUnitX(u)
            set Y[id] = GetUnitY(u)
            set Level[id] = GetUnitAbilityLevel(u, Spell)
            set Counter[id] = 1
            call TimerStart(NewTimerEx(id), Interval, true, function Loop)
            set u = null
        endif
        return false
    endfunction

    private function Event takes nothing returns nothing
        local trigger t     = CreateTrigger()
        local integer index = 0
        loop
            call TriggerRegisterPlayerUnitEvent(t, Player(index), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
            set index = index + 1
            exitwhen index == bj_MAX_PLAYER_SLOTS
        endloop
        call TriggerAddCondition(t, Condition(function Actions))
        set t = null
    endfunction

endscope


Credits:
Bribe for UnitIndexer
Vexorian for TimerUtils
IcemanBo for helping me

Keywords:
time, leap, lapse, shift, GIMLI_2, gimli, gui, vjass
Contents

Noch eine WARCRAFT-III-Karte (Map)

Reviews
19:21, 15th Mar 2016 BPower: // The spell that triggers the Time Leap private integer Spell = 'A000' // Basically the speed of the wave, dont go too low on this one private real Interval = 0.08...

Moderator

M

Moderator

19:21, 15th Mar 2016
BPower:
JASS:
        // The spell that triggers the Time Leap
        private integer Spell           = 'A000'
        // Basically the speed of the wave, dont go too low on this one
        private real    Interval        = 0.08
        // Animation of the waves
        private string  Animation       = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl"
        // Animation when it captures a unit
        private string  AnimationDis    = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportCaster.mdl"
        // Animation when it releases a unit
        private string  AnimationRe     = "Abilities\\Spells\\Human\\MassTeleport\\MassTeleportCaster.mdl"
^These variables should be constants. Furthermore the variable naming could follow the JPAG.
The JPAG itself is guided by the naming convention of the common.j and blizzard.j.

I think you should split the globals into two seperate blocks.
One for the user setting and one below your set of customizable function for the internal variables.

The global group g is not required.

I would use a group array, one per spell instance. Also I would use ForGroup instead of swapping groups via FirstOfGroup.
I'm a big supporter of inlined FoG loops, but not of group swaps. FirstOfGroup has one small weakness, it fails
for invalid unit handles ( u == null ) even if the group is not empty.
As you hide units and / or change their invulnerability status you should provide the maximum safety possible in your code.

Null the local timer t and the array variables which require nulling.

I prefer a custom filter function instead of a hardcoded one if IsUnitEnemy(FoG, GetOwningPlayer(Caster[id])) and UnitAlive(FoG) then .
Something like if FilterUnit(u, casterOwner) then .

exitwhen index == NumberOfBeams(Level[id]) place this exit condition at the top of the loop.
It's my coding philosophy to put safety over performance. The thread could break for NumberOfBeams == 0.

Your code is MUI, but can only be active once per unit.

I want to draw your attention to SpellEffectEvent. A very short, yet powerful piece of code from our JASS database.

The function name "Event" seems not fitting to me. I would recommend "Init" or "OnInit".
Not that it matters much, but slightly improves the read-ability of your code.

Overall cool spell. Good job.
 
Level 22
Joined
Feb 6, 2014
Messages
2,468
The effects are good and the spell mechanics is useful. Here are some feedback
  • I don't see a reason why you would need TimerUtils at all since you're implementing UnitIndexer. Instead of GetTimerData(t), you could add all casters to a unit group (let's call it 'casters') and in your loop actions, pick all those units where you can get the id via local integer id = GetUnitUserData(GetEnumUnit()). So when a spell is cast, add the triggering unit to the 'casters' group and if 'casters' is empty, start a global timer (let's call it 't'). It would be something like TimerStart(t, udg_TL_Interval[Level[id]], true, function Loop). That way, you only have one timer for all the spell instances runnng. When a spell instance is finished, remove the Caster[id] from 'casters' group and after removing, check if 'casters' group is empty. If it is empty, pause the timer.

  • You leak group g, you're not destroying it and you're not removing its reference.

  • Instead of local group g = CreateGroup(), create a global group and use that in GroupEnumUnitsInRange so that you don't have to Create/Destroy every loop. So, remove local group g = CreateGroup() and add private group g = CreateGroup() at the globals block.

  • GetUnitState(FoG, UNIT_STATE_LIFE) > 0 -> UnitAlive(FoG). Don't forget to native UnitAlive takes unit u returns boolean

  • if Affected[id2] != true then -> if not Affected[id2] then

  • You could re-use x, y and angle instead of assigning new variables x2, y2, and angle2 here
    JASS:
    local integer   id2
    local real      x
    local real      y
    //local real      x2 <-not needed
    //local real      y2 <-not needed
    local real      angle
    //local real      angle2 <-not needed

  • set Temp = AffectedUnits
    You're doing it wrong here. What's happening is Temp and AffectedUnits will both point to the same group handle.
    Therefore, call GroupRemoveUnit(AffectedUnits, FoG) is useless. Also, when two or more units with different
    owners uses the spell at the same time, it can bug (where affected units will be permanently hidden) because if Owner[id2] == Caster[id] then will not always run due to the Temp group mistake you made.

Name doesn't match in my opinion, because it has nothing to do with time. Should be something like SpaceLeap/SpaceWarp, just saying.
 
Last edited:

KILLCIDE

Spell Moderator
Level 36
Joined
Jul 22, 2015
Messages
3,488
Neat spell idea. The effects look great as well. The description is a little misleading because I was expecting a knockback effect :p

I have very little knowledge of how to structure vJass, but my only confusion is why you have the configurables in a GUI trigger. Why not just keep it in the global block? Regardless, it's a great first resource from you. Excited to see more.
 
Level 16
Joined
Mar 21, 2011
Messages
1,580
@Flux:
You leak group g , you're not destroying it and you're not removing its reference.
thanks, missed that ;)

Instead of local group g = CreateGroup() , create a global group and use that in GroupEnumUnitsInRange so that you don't have to Create/Destroy every loop. So, remove local group g = CreateGroup() and add private group g = CreateGroup() at the globals block.
Fixed

GetUnitState(FoG, UNIT_STATE_LIFE) > 0 -> UnitAlive(FoG) . Don't forget to native UnitAlive takes unit u returns boolean
Fixed

if Affected[id2] != true then -> if not Affected[id2] then
Fixed

You could re-use x, y and angle instead of assigning new variables x2, y2, and angle2 here
Fixed

set Temp = AffectedUnits
You're doing it wrong here. What's happening is Temp and AffectedUnits will both point to the same group handle.
Hopefully fixed

Name doesn't match in my opinion, because it has nothing to do with time. Should be something like SpaceLeap/SpaceWarp, just saying.
yeah, you're right. I'm not very creative or original in such things :D

thanks for your review ;)

@KILLCIDE:
thanks :)
Why not just keep it in the global block?
i will probably change the globals then

I will update the spell today
 
Top