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

[Spell] Spell Feedback

Status
Not open for further replies.
Level 7
Joined
Feb 9, 2021
Messages
301
I just started to code recently, and I would really appreciate some feedback on my code. So far, everything seems to work as intended.

Spell Description:

Hero dashes to the target location and uses one of his shunpo stacks. Hero has (1 * spell level) shunpo stacks. The shunpo requires 1 stack If there is an enemy in radius of Y of the target location. Otherwise, it requires 2 stacks. After dash, the hero is turned to the closest enemy unit (hero has priority over normal unit)

If hero target unit, he will shunpo to the unit and attack him, causing T damage (requires 1 stack). If hero kills a minion from this attack, he gains 1 stack.

Note: Shunpo will not work if her shnpo location is the same as her current location.


Do not bother with the stacks numbers. I have them different in some places compared to the description for testing purposes.



JASS:
library Shunpo /*


     */ uses /*

     */ SpellFramework /*
     */ UnitDex /*
     */ BaseFunctions /*
     */ MemoryHackTestAbilityChargesHook /*
     */ GetClosestWidget /*
     */ IsRemaningDistMoreThanObject
 
     /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     *                                                  SPELL CONFIGURATION                                                           *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    private module SpellConfiguration

        static constant integer SPELL_ABILITY_ID = 'A00E'
        static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
        static constant integer STACK_COST_MIN = 1
        static constant integer STACK_COST_MAX = 2
        static constant real SPELL_PERIOD = 1.00 / 32.00
        static constant real SFX_DEATH_TIME = 1.50
        static constant real MIN_SHUNPO_DIST = 60
        static constant real SHUNPO_TARGET_OFFSET = 100
        static constant real MIN_SHUNPO_SPEED = 100
        static constant real STACK_AOE_CHECK = 150
        static constant string CAST_SFX = "n/a"
        static constant string SHUNPO_SFX = "SoiFon.mdl"
    
        static constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
        static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
        static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
    
        static method TargetsShunpoAoeCheck takes unit target, unit caster returns boolean
            return GetWidgetLife(target) > 0.405 and /*
             */ IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
             */ (IsUnitVisible(target, GetOwningPlayer(caster))) // is caster in enemy line of sight
        endmethod
    
        static method HeroShunpoAoeCheck takes unit target, unit caster returns boolean
            return GetWidgetLife(target) > 0.405 and /*
             */ IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
             */ (IsUnitVisible(target, GetOwningPlayer(caster))) and /*
             */ IsUnitType(target, UNIT_TYPE_HERO)
        endmethod
    
        static method NonHeroShunpoAoeCheck takes unit target, unit caster returns boolean
            return GetWidgetLife(target) > 0.405 and /*
             */ IsUnitEnemy(target, GetOwningPlayer(caster)) and /*
             */ (IsUnitVisible(target, GetOwningPlayer(caster))) and /*
             */ not IsUnitType(target, UNIT_TYPE_HERO)
        endmethod
    
     
        static method DMG takes integer level, unit caster returns real
            return 500. + 0. * level + 0.1 * GetHeroStr(caster, true)
        endmethod
    
        static method StackCdTime takes integer level returns real
            return 3.0
        endmethod

    endmodule


     /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
     *                                 END OF SPELL CONFIGURATION                                                                     *
     * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

     /* == == == == == == == == == == == == = SPELL CODE == == == == == == == == == == == == = */

    
    globals
        private group eG = CreateGroup()
        private group hG = CreateGroup()
        private group nonHG = CreateGroup()
    endglobals

    public struct Shunpo extends array

        implement SpellConfiguration
    
        private static unit caster
        private static unit u

        private integer maxStacks
        private integer stacks
        private integer shunpoCounter
        private boolean isTargetNull
        private boolean isStackTimerOn
        private real finalPointX
        private real finalPointY
        private real speed
        private real dist
        private real cX
        private real cY
        private real angle
        private real dmg
        private real curDist
        private unit target
        private timer t
        private CastBuff cb
        private boolean active
 
        private method onSpellStart takes nothing returns thistype
            local real angle
            local unit closestUnit
            local real targetShunpoX
            local real targetShunpoY
            local real cX = GetUnitX(Spell.triggerUnit)
            local real cY = GetUnitY(Spell.triggerUnit)
            local boolean eInRange = false
        
            set this = GetUnitId(Spell.triggerUnit)
        
            debug call BJDebugMsg("[Shunpo] Spell Start Activated")
        
             //Double Cast Protection
            if this.active then
                debug call BJDebugMsg("[Shunpo] the spell is casting already")
                return 0
            endif

            if Spell.targetUnit == null then
                call GroupEnumUnitsInRange(eG, Spell.targetX, Spell.targetY, STACK_AOE_CHECK, null)
                loop
                    set u = FirstOfGroup(eG)
                    exitwhen u == null or eInRange
                    if TargetsShunpoAoeCheck(u, Spell.triggerUnit) then
                        set eInRange = true
                        debug call BJDebugMsg("[Shunpo] Cast: Enemy Hero In Range")
                    endif
                    call GroupRemoveUnit(eG, u)
                endloop
                call GroupClear(eG)
            
                if (eInRange and this.stacks < STACK_COST_MIN) or /*
                     */ (not eInRange and this.stacks < STACK_COST_MAX) then
                    debug call BJDebugMsg("[Shunpo] Spell Point: Not Enough Stacks")
                    return 0
                endif
            
                if GetDist(cX, Spell.targetX, cY, Spell.targetY) < MIN_SHUNPO_DIST then
                    debug call BJDebugMsg("[Shunpo] The target loc is too close to the caster's cur loc - spell cancelled")
                    return 0
                else
                    set this.isTargetNull = true
                    set this.finalPointX = Spell.targetX
                    set this.finalPointY = Spell.targetY
                    if not eInRange then
                        set this.stacks = this.stacks - STACK_COST_MAX
                    else
                        set this.stacks = this.stacks - STACK_COST_MIN
                    endif
                endif
  
            else
                if this.stacks >= STACK_COST_MIN then
                    set angle = GetAngle(cX, Spell.targetX, cY, Spell.targetY)
                    set targetShunpoX = GetXWithOffset(Spell.targetX, SHUNPO_TARGET_OFFSET, angle - 180)
                    set targetShunpoY = GetYWithOffset(Spell.targetY, SHUNPO_TARGET_OFFSET, angle - 180)
                    if GetDist(cX, targetShunpoX, cY, targetShunpoY) < MIN_SHUNPO_DIST then
                        debug call BJDebugMsg("[Shunpo] The target loc is too close to the caster's cur loc - spell cancelled")
                        return 0
                    else
                        set this.isTargetNull = false
                        set this.finalPointX = targetShunpoX
                        set this.finalPointY = targetShunpoY
                        set this.target = Spell.targetUnit
                        set this.stacks = this.stacks - STACK_COST_MIN
                    endif
                else
                    debug call BJDebugMsg("[Shunpo] Spell Target: Not Enough Stacks")
                    return 0
                endif
            endif
        
            debug call BJDebugMsg("[Shunpo] Number of Stacks Before Reduction " + I2S(this.stacks))
        
            call DestroyEffect(AddSpecialEffect("Objects\\InventoryItems\\HumanCaptureFlag\\HumanCaptureFlag.mdl", this.finalPointX, this.finalPointY))
        
            call SetCustomUnitAbilityCharges(Spell.triggerUnit, SPELL_ABILITY_ID, this.stacks)
            debug call BJDebugMsg("[Shunpo] Number of Stacks After Reduction " + I2S(this.stacks))
        
            if not this.isStackTimerOn then
                set this.t = NewTimerEx(this)
                call TimerStart(this.t, StackCdTime(Spell.level), true, function thistype.StacksCd)
                set this.isStackTimerOn = true
            endif
        
            set this.active = true
            set this.cX = cX
            set this.cY = cY
            set this.dist = GetDist(cX, this.finalPointX, cY, this.finalPointY)
            set this.angle = GetAngle(cX, this.finalPointX, cY, this.finalPointY)
            set this.dmg = DMG(Spell.level, Spell.triggerUnit)
            set this.shunpoCounter = 0
            set this.curDist = 0

            debug call BJDebugMsg("[Shunpo] Dist: " + R2S(this.dist))
        
            if this.dist / MIN_SHUNPO_SPEED < 2 then
                set this.speed = this.dist
            else
                set this.speed = this.dist / R2I(this.dist / MIN_SHUNPO_SPEED + 0.5)
            endif
        
            debug call BJDebugMsg("[Shunpo] Speed: " + R2S(this.speed))
            set this.cb = CastBuff.add(null, Spell.triggerUnit)
            set this.cb.duration = 1.0
            call SetUnitFacingInstant(Spell.triggerUnit, this.angle)
            call SetUnitPathing(Spell.triggerUnit, false)
            call UnitAddAbility(Spell.triggerUnit, 'Agho')
        
            return this
        endmethod

        private method onSpellPeriodic takes nothing returns boolean
            local real moveX
            local real moveY
            local real cX
            local real cY
            local real tempAngle
            local unit closestUnit
            local effect moveSFX
 
            set caster = GetUnitById(this)
            set cX = GetUnitX(caster)
            set cY = GetUnitY(caster)
            set moveX = GetXWithOffset(cX, this.speed, this.angle)
            set moveY = GetYWithOffset(cY, this.speed, this.angle)
       
            call SetUnitAnimationByIndex(caster, 7)
            call SetUnitTimeScale(caster, 2)
        
            if this.curDist == this.dist or IsRemaningDistMoreThanObject(moveX, moveY, this.angle, this.speed, (this.dist - this.curDist)) then
                debug call BJDebugMsg("[Shunpo] Caster Reached End Point ")
            
                if not this.isTargetNull then
                    if GetDist(cX, GetUnitX(this.target), cY, GetUnitY(this.target)) < SHUNPO_TARGET_OFFSET + 20 then
                        debug call BJDebugMsg("[Shunpo] The Point was Target and He is still in Range ")
                        call SetUnitAnimationByIndex(caster, 12)
                        call UnitDamageTarget(caster, this.target, this.dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
                        if GetWidgetLife(this.target) < 0.405 then
                            set this.stacks = this.stacks + 1
                            call SetCustomUnitAbilityCharges(caster, SPELL_ABILITY_ID, this.stacks)
                            debug call BJDebugMsg("[Shunpo] The Target Died: Reset 1 stack. Total Stack: " + I2S(this.stacks))
                        endif
                    endif
                
                else
                    call GroupEnumUnitsInRange(eG, cX, cY, STACK_AOE_CHECK, null)
                    loop
                        set u = FirstOfGroup(eG)
                        exitwhen u == null
                        if HeroShunpoAoeCheck(u, caster) then
                            call GroupAddUnit(hG, u)
                        elseif NonHeroShunpoAoeCheck(u, caster) then
                            call GroupAddUnit(nonHG, u)
                        endif
                        call GroupRemoveUnit(eG, u)
                    endloop
                    call GroupClear(eG)
                
                    if GetGroupUnitCount(hG) > 0 then
                        set closestUnit = GetClosestUnitInGroup(cX, cY, hG)
                        set tempAngle = GetAngle(cX, GetUnitX(closestUnit), cY, GetUnitY(closestUnit))
                        call SetUnitFacingInstant(caster, tempAngle)
                        call IssueTargetOrder(caster, "attack", closestUnit)
                    
                        set closestUnit = null
                
                    elseif GetGroupUnitCount(nonHG) > 0 then
                        set closestUnit = GetClosestUnitInGroup(cX, cY, nonHG)
                        set tempAngle = GetAngle(cX, GetUnitX(closestUnit), cY, GetUnitY(closestUnit))
                        call SetUnitFacingInstant(caster, tempAngle)
                        call IssueTargetOrder(caster, "attack", closestUnit)
                    
                        set closestUnit = null
                    endif
                
                    call GroupClear(hG)
                    call GroupClear(nonHG)

                endif
            
                return true
            else
            
                set this.shunpoCounter = this.shunpoCounter + 1
            
                if this.shunpoCounter > 1 then
                    set moveSFX = AddSpecialEffect(SHUNPO_SFX, cX, cY)
                    call DestroyEffectTimed(moveSFX, 1.0)
                    call SetEffectTimeScale(moveSFX, 2.0)
                    call SetEffectAnimationByIndex(moveSFX, 7)
                    call SetEffectAlpha(moveSFX, 30)
                    call SetEffectFacing(moveSFX, this.angle)
                endif
            
                call SetUnitX(caster, moveX)
                call SetUnitY(caster, moveY)
                set this.curDist = this.curDist + this.speed
            
                return false
            endif

        endmethod

        private method onSpellEnd takes nothing returns nothing
            set this.active = false
    
            call SetUnitPathing(caster, true)
            call UnitRemoveAbility(caster, 'Agho')
       
            if this.isTargetNull then
                debug call BJDebugMsg("[Shunpo] Non Target = Stand Animation")
                call SetUnitTimeScale(caster, 1.0)
                call SetUnitAnimationByIndex(caster, 1)
                call this.cb.remove()
            else
                debug call BJDebugMsg("[Shunpo] Target = Timer")
                call TimerStart(NewTimerEx(this), 0.25 , false, function thistype.SpellEndAnimation)
            endif
        
            debug call BJDebugMsg("[Shunpo] Dist: " + R2S(this.curDist))
            debug call BJDebugMsg("[Shunpo] curDist: " + R2S(this.dist))
        
            if R2I(this.curDist) != R2I(this.dist) then
                debug call BJDebugMsg("[Shunpo] Got Interrpued: Reset Animation and Remove Stun")
                call SetUnitTimeScale(caster, 1.0)
                call SetUnitAnimationByIndex(caster, 1)
                call this.cb.remove()
            endif
        
            if this.stacks == this.maxStacks then
                set this.isStackTimerOn = false
                call ReleaseTimer(this.t)
                set this.t = null
            elseif this.stacks == 0 then
                call StartUnitAbilityCooldown(caster, SPELL_ABILITY_ID, TimerGetRemaining(this.t))
            endif

            set this.target = null
            set caster = null
        endmethod

        implement SpellEvent
    
        static method StacksCd takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            if this.stacks < this.maxStacks then
                set this.stacks = this.stacks + 5
                call SetCustomUnitAbilityCharges(GetUnitById(this), SPELL_ABILITY_ID, this.stacks)
                debug call BJDebugMsg("[Shunpo] CD Finished: 1 Shunpo Stack Added")
            else
                debug call BJDebugMsg("[Shunpo] CD Finished: 1 Shunpo Stack Added")
                set this.isStackTimerOn = false
                call ReleaseTimer(this.t)
                set this.t = null
            endif
        endmethod
    
        static method SpellEndAnimation takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            call SetUnitTimeScale(GetUnitById(this), 1.0)
            call SetUnitAnimationByIndex(GetUnitById(this), 1)
            call this.cb.remove()
            call ReleaseTimer(GetExpiredTimer())
        
        endmethod


     //===========================================================================
        private static method GiveShunpoStacks takes nothing returns nothing
            local unit learner
            local thistype this
        
            if GetLearnedSkill() == SPELL_ABILITY_ID then
                set learner = GetTriggerUnit()
                if GetUnitAbilityLevel(learner, SPELL_ABILITY_ID) == 1 then
                    call SetUnitAbilityCastpoint(learner, SPELL_ABILITY_ID, 0.1)
                    call InitCustomAbilityAddressChargesHook( GetUnitAbility(learner, SPELL_ABILITY_ID))
                    call EnableCustomUnitAbilityCharges(learner, SPELL_ABILITY_ID)
                    debug call BJDebugMsg("[Shunpo] Setup Finished")
                endif
            
                set this = GetUnitId(learner)
                set this.maxStacks = 5 * GetUnitAbilityLevel(learner, SPELL_ABILITY_ID)
                set this.stacks = this.stacks + 5
                call SetCustomUnitAbilityCharges(learner, SPELL_ABILITY_ID, this.stacks)
                debug call BJDebugMsg("[Shunpo] Max Stacks " + I2S(this.maxStacks))

            endif
        
        endmethod

        private static method onInit takes nothing returns nothing

            call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function thistype.GiveShunpoStacks)

        endmethod
    endstruct

endlibrary
 
Last edited:
Status
Not open for further replies.
Top