1. Are you planning to upload your awesome spell or system to Hive? Please review the rules here.
    Dismiss Notice
  2. The Aftermath has been revealed for the 19th Terraining Contest! Be sure to check out the Results and see what came out of it.
    Dismiss Notice
  3. Melee Mapping Contest #3 - Results are out! Congratulate the winners and check plenty of new 4v4 melee maps designed for this competition!
    Dismiss Notice
  4. The winners of our cinematic soundtrack competition have been decided! Step by the Music Contest #11 - Results to check the entries and congratulate the winners!
    Dismiss Notice

Elune's Arrow v1.3

Submitted by Ruke
This bundle is marked as approved. It works and satisfies the submission rules.
Thx to Bribe for the tips ^^

Elune's arrow

Fires an arrow to a location with deadly precision, dealing large damage and stunning the first unit it strikes. Stun duration increases based on how far the target is, ranging from 0.5 to 5 seconds. Has 3000 range.

Inspired in this spell -> http://www.playdota.com/heroes/priestess-of-the-moon#skill178

Requires: TimerUtils and Timer32

Code (vJASS):
//  Elune's arrow by Ruke
//  ---------------------
//
//  REQUIRES: TimerUtils -> http://www.hiveworkshop.com/forums/submissions-414/system-timerutils-204500/
//  REQUIRES: Timer32 -> http://www.thehelper.net/forums/showthread.php/132538-Timer32
//
//  The spell does what?
//  --------------------
//      Fires an arrow to a location with deadly precision, dealing large damage and stunning
//      the first unit it strikes. Stun duration increases based on how far the target
//      is, ranging from 0.5 to 5 seconds. Has 3000 range.
//      More information: http://www.playdota.com/heroes/priestess-of-the-moon#skill178
//
//  How to import?
//  --------------
//      1 ) Copy this trigger
//      2 ) Copy the arrow dummy unit (Object Editor -> Unit)
//      3 ) Copy Stun Elune (Object Editor -> Spell)
//      4 ) Copy Elune's Arrow (Object Editor -> Spell)
//      5 ) Copy the dummy unit (Object Editor -> Unit)

library EluneIsArrow requires TimerUtils, T32
    //==========================================================================================//
    //                                   CONFIGURABLE                                           //
    //==========================================================================================//
    globals
        private constant integer    DUMMY           = 'h003'                // Stun's dummy
        private constant integer    DUMMY_ARROW     = 'h002'                // Arrow's dummy
        private constant integer    STUN_RAWCODE    = 'A002'                // Stun's spell
        private constant integer    STUN_BUFF       = 'BPSE'                // Stun's buff
        private constant integer    SPELL_RAWCODE   = 'A001'                // Spell's rawcode
        private constant damagetype DMG_TYPE        = DAMAGE_TYPE_MAGIC     // Damage type
        private constant attacktype ATK_TYPE        = ATTACK_TYPE_NORMAL    // Attack type
        private constant boolean    AFFECT_INVIS    = false                 // Affects the stun to invisible units?
    endglobals
   
    // Returns the spell's damage
    private constant function GetDamage takes integer level returns real
        return 90. * level
    endfunction
   
    // Returns the max distance that the arrow can cover
    // cycles * 100 to get distance (as you can see, 100 cycles = 3000 max distance)
    private constant function GetCycles takes nothing returns integer
        return 100
    endfunction
   
    // Arrow's detection range
    private constant function GetArrowDetect takes nothing returns real
        return 115.
    endfunction
   
    // Returns the stun's max duration
    private constant function GetStunMaxDuration takes integer level returns integer
        return 5 * level
    endfunction
   
    // Returns the units that can be affected by the spell
    private function GetAffectedUnits takes unit caster, unit target returns boolean
        return IsUnitEnemy(target, GetOwningPlayer(caster)) and not(IsUnitType(target, UNIT_TYPE_DEAD)) and not(IsUnitType(target, UNIT_TYPE_STRUCTURE)) and GetUnitAbilityLevel(target, 'Aloc') == 0
    endfunction
   
    //==========================================================================================//
    //                             CONFIGURABLE's END                                           //
    //==========================================================================================//
   
    globals
        private constant group G = bj_lastCreatedGroup
    endglobals
   
    private struct Spell extends array
        private static integer instanceCount = 0
        private static thistype recycle = 0
        private thistype recycleNext
       
        private unit caster     // Caster
        private unit target     // Target of stun
        private unit dummy      // Arrow
        private integer level   // Level of spell
        private real a          // Angle for arrow
        private integer cycles  // Distance of arrow -> To get the distance: cycles * 30 (30 is how much the arrow's dummy moves in every period)
       
        private method destroy takes nothing returns nothing
            set this.caster = null
            set this.target = null
            set this.level = 0
            set this.a = 0
            set this.cycles = 0
           
            set recycleNext = recycle
            set recycle = this
        endmethod
       
        static method stun takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            call UnitRemoveAbility(this.target, STUN_BUFF)
           
            call PauseTimer(GetExpiredTimer())
            call DestroyTimer(GetExpiredTimer())
           
            call this.destroy()
        endmethod
       
        private method periodic takes nothing returns nothing
            local unit j
            local unit dummy
            local timer t
           
            // Moving the arrow
            call SetUnitPosition(this.dummy, GetUnitX(this.dummy) + 30. * Cos(this.a), GetUnitY(this.dummy) + 30. * Sin(this.a))
            set this.cycles = this.cycles + 1
           
            // Checking if the arrow has already covered the max distance
            if this.cycles < GetCycles() then
                call GroupEnumUnitsInRange(G, GetUnitX(this.dummy), GetUnitY(this.dummy), GetArrowDetect(), null)
               
                loop
                    set j = FirstOfGroup(G)
                    exitwhen j == null
                    call GroupRemoveUnit(G, j)
                   
                    // If we've a target
                    if GetAffectedUnits(this.caster, j) then
                        // The stun can only stay for STUN_M_DURATION seconds
                        if (((this.cycles * 30) / 150) * 0.5) > GetStunMaxDuration(this.level) then
                            set this.cycles = GetCycles() / 2
                        endif
                   
                        // Invis check...
                        if IsUnitInvisible(j, GetOwningPlayer(j)) then
                            static if AFFECT_INVIS then
                                // If the unit is invisible we will only put
                                // the stun if AFFECT_INVIS is true
                                set dummy = CreateUnit(GetOwningPlayer(this.caster), DUMMY, GetUnitX(j), GetUnitY(j), 0)
                               
                                call UnitApplyTimedLife(dummy, 'BTLF', 1.)
                                call UnitAddAbility(dummy, STUN_RAWCODE)
                                call IssueTargetOrder(dummy, "thunderbolt", j)
                            endif
                        else
                            set dummy = CreateUnit(GetOwningPlayer(this.caster), DUMMY, GetUnitX(j), GetUnitY(j), 0)
                               
                            call UnitApplyTimedLife(dummy, 'BTLF', 1.)
                            call UnitAddAbility(dummy, STUN_RAWCODE)
                            call IssueTargetOrder(dummy, "thunderbolt", j)
                        endif
                       
                        call KillUnit(this.dummy)
                        set this.dummy = null
                        set this.target = j
                       
                        // Making damage...
                        call UnitDamageTarget(this.caster, j, GetDamage(this.level), false, true, ATK_TYPE, DMG_TYPE, null)
                       
                        // Timer of stun...
                        set t = NewTimer()
                        call SetTimerData(t, this)
                        call TimerStart(t, ((this.cycles * 30) / 150) * 0.5, false, function thistype.stun)
                        set t = null
                       
                        call this.stopPeriodic()
                       
                        call GroupClear(G)
                        exitwhen true
                    endif
                endloop
            else
                call KillUnit(this.dummy)
                set this.dummy = null                
                call this.stopPeriodic()
               
                call this.destroy()
            endif
           
            set dummy = null
            set j = null
        endmethod
       
        implement T32x
       
        private static method create takes nothing returns thistype
            local thistype this
           
            if (recycle == 0) then
                set instanceCount = instanceCount + 1
                set this = instanceCount
            else
                set this = recycle
                set recycle = recycle.recycleNext
            endif
           
            set this.caster = GetTriggerUnit()
            set this.level = GetUnitAbilityLevel(this.caster, SPELL_RAWCODE)
            set this.a = Atan2(GetSpellTargetY() - GetUnitY(this.caster), GetSpellTargetX() - GetUnitX(this.caster))
            set this.dummy = CreateUnit(GetTriggerPlayer(), DUMMY_ARROW, GetUnitX(this.caster), GetUnitY(this.caster), this.a *  bj_RADTODEG)
           
            call this.startPeriodic()
           
            return this
        endmethod
       
        private static method condition takes nothing returns boolean
            if (GetSpellAbilityId() == SPELL_RAWCODE) then
                call thistype.create()
            endif
           
            return false
        endmethod
   
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local unit u
           
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.condition))
           
            // Preloading ability and dummy unit
            set u = CreateUnit(Player(15), DUMMY, 0, 0, 0)
            call UnitAddAbility(u, STUN_RAWCODE)
            call RemoveUnit(u)
           
            set u = null
        endmethod
    endstruct
endlibrary


Changelog:

v1.3
Redesigned

v1.2
Now the spell works with T32 and TimerUtils
"create" method modified
"Conditions" function modified

v1.1
Added more "documentation"
Added configurable's block
Following the JPAG's rules (http://www.hiveworkshop.com/forums/tutorial-submission-283/jpag-jass-principles-guidelines-204383/)

v1.0
Spell's release

Greetings ^^

Keywords:
elune's arrow, elune, is, arrow, dota, mirana, Nightshade
Contents

Elune's Arrow without Handle Vars (Map)

Reviews
Moderator
9th Nov 2011 Bribe: You don't need to initialize values to null/0 from the "create" method, they are already those values. Only set to null/0 from the "destroy" method. You do not require a "t" member to remember the timer. It's a waste of an...
  1. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,746
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    SpellEffectEvent requires only RegisterPlayerUnitEvent, the "Table" library is optional (doesn't have to be included).

    RegisterPlayerUnitEvent should be found in any vJass script that uses TriggerRegisterAnyUnitEventBJ, because of all the event/trigger handles it saves. SpellEffectEvent makes it so your function only runs if it's the right spell, that way you don't have to perform an if/else check yourself which saves development time and dynamic game run-time.
     
  2. Ruke

    Ruke

    Joined:
    Sep 19, 2011
    Messages:
    517
    Resources:
    7
    Tools:
    1
    Spells:
    5
    Wurst:
    1
    Resources:
    7
    Umm, yes I know that part, but, I need to add two more libraries to the spell (already has two), who makes 4 (5 if I add tables) libraries only for 1 spell.

    If you want, I can add the library...
    Greetings :D
     
  3. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    7,746
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    It really doesn't depend on approval to me as I already approved it, it's just that map makers should definitely be using those systems because it saves tons of handles, performance and map file size when there are many spells imported. Having them already required might be a deterrent if they don't have them imported, however it makes it easier for them if they do have them imported already. I recommend just doing whatever you think looks more appropriate for you.
     
  4. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    166
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    @Ruke

    Hey man seems there's a pretty serious bug.

    Units can get permastunned if the ability is cast right next to the target.

    Code (Text):
    call TimerStart(t, ((this.cycles * 30) / 150) * 0.5, false, function thistype.stun)
    If cycles if less than 5 then the stun duration parameter will be set to 0 which means perma stun.

    Also why did you multiply stun max duration by 5 * level? At level 4 this implies that the stun can last for 20 seconds.

    Code (Text):
    private constant function GetStunMaxDuration takes integer level returns integer
            return 5 * level
    endfunction
     
  5. Pyrogasm

    Pyrogasm

    Joined:
    Feb 27, 2007
    Messages:
    1,934
    Resources:
    0
    Resources:
    0
    Actually it's < 4 because 120/150 = 1 in integer division. You can fix this error by changing 30 to 30.00 or 150 to 150.00. And honestly anything duration-related that's an integer... should be a real instead. Like the return value of that stun function you posted.

    Sounds like this should be moved to substandard if not altered/updated.
     
  6. coolyoshi1

    coolyoshi1

    Joined:
    Dec 6, 2009
    Messages:
    166
    Resources:
    2
    Maps:
    1
    Spells:
    1
    Resources:
    2
    120/150 = 1? Interesting... anyways yeah this should probably be moved to substandard. Permastun is no joke
     
  7. Pyrogasm

    Pyrogasm

    Joined:
    Feb 27, 2007
    Messages:
    1,934
    Resources:
    0
    Resources:
    0
    No I was totally wrong, and you're correct that it bugs with duration < 5. I think I thought I typed 150/120, which actually does = 1 in integer division. Int division truncates and never rounds:
    Code (vJASS):
    1/10 = 0
    9/10 = 0
    11/10 = 1
    199/100 = 1

    //can fix it with periods so that each division involves at least one real:
    1./10 = 0.10
    9/10. = 0.90
    11.00/10 = 1.10
    199/100.00 = 1.99