1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  3. The 15th Mini-Mapping Contest came to an end. The Secrets of Warcraft 3 are soon to be revealed! Come and vote in the public poll for your favorite maps.
    Dismiss Notice
  4. The 12th incarnation of the Music Contest is LIVE! The theme is Synthwave. Knight Rider needs a song to listen to on his journey. You should definitely have some fun with this theme!
    Dismiss Notice
  5. Join other hivers in a friendly concept-art contest. The contestants have to create a genie coming out of its container. We wish you the best of luck!
    Dismiss Notice
  6. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

Time Leap v1.1

Submitted by GIMLI_2
This bundle is marked as substandard. It may contain bugs, not perform optimally or otherwise be in violation of the submission rules.
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.

code

Code (vJASS):

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
Moderator
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...
  1. 19:21, 15th Mar 2016
    BPower:
    Code (vJASS):
            // 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.
     
  2. Flux

    Flux

    Joined:
    Feb 6, 2014
    Messages:
    2,334
    Resources:
    28
    Maps:
    1
    Spells:
    19
    Tutorials:
    2
    JASS:
    6
    Resources:
    28
    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
      Code (vJASS):

      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: Mar 5, 2016
  3. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,491
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    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.
     
  4. zv27

    zv27

    Joined:
    Aug 21, 2010
    Messages:
    296
    Resources:
    0
    Resources:
    0
    Pause unit is a bad idea , it can be problematic , it can screw up some buffs.
     
  5. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,861
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    I hate the fact that the configurable fields are in GUI.
     
  6. GIMLI_2

    GIMLI_2

    Joined:
    Mar 21, 2011
    Messages:
    1,317
    Resources:
    2
    Maps:
    2
    Resources:
    2
    @Flux:
    thanks, missed that ;)

    Fixed

    Fixed

    Fixed

    Fixed

    Hopefully fixed

    yeah, you're right. I'm not very creative or original in such things :D

    thanks for your review ;)

    @KILLCIDE:
    thanks :)
    i will probably change the globals then

    I will update the spell today
     
  7. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,491
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Oh I just realized that you're using TriggerRegisterPlayerUnitEvent. You can just stick with TriggerRegisterAnyUnitEventBJ.
     
  8. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,861
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    or just use Magtheridon96' RegisterPlayerUnitEvent
     
  9. pred1980

    pred1980

    Joined:
    Mar 19, 2010
    Messages:
    844
    Resources:
    1
    Maps:
    1
    Resources:
    1
    Interesting Idea :)