• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[vJASS] Why does this bug?

Status
Not open for further replies.
Level 17
Joined
Feb 11, 2011
Messages
1,860
I am working on some hero spells for someone and one of them is Multi-Shot. I have coded it and it works, except sometimes some of the arrows just freeze. What is the cause of this? Also, any optimizations are welcome.

JASS:
scope MultiShot

    globals
    
        // ~~~ Configurables ~~~
        
        private constant integer SPELL_ID           = 'A002'
        private constant integer DUMMY_ID           = 'h005'        // The ID of the "Multi-Shot [arrow]" unit, not the invisible dummy unit.
        
        private constant integer ARROW_COUNT        = 10
        private constant real    ARROW_DISTANCE     = 1500
        private constant real    ARROW_SPEED        = 1000
        private constant string  ARROW_EFFECT       = "Abilities\\Weapons\\GlaiveMissile\\GlaiveMissileTarget.mdl" // The effect when the arrows are destroyed.
        private constant real    ARROW_AREA         = 150           // How close a unit has to be to an arrow to get damaged.
        private constant boolean SHOW_DAMAGE        = true
        
        // ~~~ End of Configurables ~~~
        
        private constant real    ARROW_DEGREES      = 60 / ARROW_COUNT
        private constant real    ARROW_SPEED_TRUE   = ARROW_SPEED * 0.03
        
    endglobals

    private struct MultiShot_Data
    
        group units = CreateGroup()
        integer dead = 0
        unit array arrow [ARROW_COUNT]
        unit caster
        real array distance [ARROW_COUNT]
        real damage
    
        private method destroy takes nothing returns nothing
            local integer i = 1
            call DestroyGroup(.units)
            set .units = null
            set .caster = null
            call .deallocate()
        endmethod
        
        private static method Timer_Actions takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            local integer index = 0
            local real x
            local real y
            local unit u
            local texttag tt
            loop
                if .arrow[i] != null then
                    set x = GetUnitX(.arrow[i]) + ARROW_SPEED_TRUE * Cos(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
                    set y = GetUnitY(.arrow[i]) + ARROW_SPEED_TRUE * Sin(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
                    if .distance[i] > 0 and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
                        call SetUnitX(.arrow[i], x)
                        call SetUnitY(.arrow[i], y)
                        set .distance[i] = .distance[i] - ARROW_SPEED_TRUE
                        call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, ARROW_AREA, null)
                        loop
                            set u = FirstOfGroup(bj_lastCreatedGroup)
                            exitwhen u == null
                            if not IsUnitInGroup(u, .units) and IsUnitEnemy(u, GetOwningPlayer(.caster)) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
                                call GroupAddUnit(.units, u)
                                call UnitDamageTarget(.caster, u, .damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
                                if SHOW_DAMAGE then
                                    set tt = CreateTextTag()
                                    call SetTextTagText(tt, "|cffff0000" + I2S(R2I(.damage)) + "!|r", 0.023)
                                    call SetTextTagPosUnit(tt, u, 25)
                                    call SetTextTagVelocity(tt, 0, 64 * 0.071 / 128)
                                    call SetTextTagPermanent(tt, false)
                                    call SetTextTagLifespan(tt, 2)
                                    call SetTextTagFadepoint(tt, 1)
                                    call SetTextTagVisibility(tt, true)
                                endif
                                call DestroyEffect(AddSpecialEffect(ARROW_EFFECT, GetUnitX(.arrow[i]), GetUnitY(.arrow[i])))
                                call RemoveUnit(.arrow[i])
                                set .arrow[i] = null
                                set .dead = .dead + 1
                            endif
                            call GroupRemoveUnit(bj_lastCreatedGroup, u)
                        endloop
                    else
                        call DestroyEffect(AddSpecialEffect(ARROW_EFFECT, GetUnitX(.arrow[i]), GetUnitY(.arrow[i])))
                        call RemoveUnit(.arrow[i])
                        set .arrow[i] = null
                        set .dead = .dead + 1
                    endif
                endif
                exitwhen i == ARROW_COUNT
                set i = i + 1
            endloop
            if .dead == ARROW_COUNT then
                call ReleaseTimer(GetExpiredTimer())
                call .destroy()
            endif
        endmethod
        
        private static method Actions takes nothing returns thistype
            local thistype this = thistype.allocate()
            local timer t = NewTimer()
            local integer i = 1
            local real min
            local real x
            local real y
            local unit caster = GetTriggerUnit()
            local integer agility = GetHeroAgi(caster, true)
            local integer intelligence = GetHeroInt(caster, true)
            local integer strength = GetHeroStr(caster, true)
            local integer hero_level = GetHeroLevel(caster)
            set .caster = caster
            set .damage = (5 + (3 * hero_level)) + (1.5 * agility)            
            set .caster = GetTriggerUnit()
            set min = bj_RADTODEG * Atan2(GetSpellTargetY() - GetUnitY(.caster), GetSpellTargetX() - GetUnitX(.caster)) - 30 - ARROW_DEGREES
            set x = GetUnitX(.caster)
            set y = GetUnitY(.caster)
            loop
                set .arrow[i] = CreateUnit(GetOwningPlayer(.caster), DUMMY_ID, x, y, min + (ARROW_DEGREES * i))
                set .distance[i] = ARROW_DISTANCE
                exitwhen i == ARROW_COUNT
                set i = i + 1
            endloop
            call SetTimerData(t, this)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            set caster = null
            return this
        endmethod

        private static method Conditions takes nothing returns boolean
            if GetSpellAbilityId() == SPELL_ID then
                call thistype.Actions()
            endif
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.Conditions))
            set t = null
        endmethod
        
    endstruct
    
endscope
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
Try starting your value of i at ARROW_COUNT and working down to 0 by subtracting 1 each time. That would be in your Timer_Actions method.

Actually you're doing something different than what I thought you were doing. I'm reading over the code again.

Okay well I can't see anything that sticks out at me, but I would start by putting debug messages in key places. If your arrows are stopping in mid-air then you're likely losing reference to them or you're releasing the timer prematurely.

Try putting a debug message when you release the timer and when you are releasing the unit handles from your scope of reference.
 
- Nothing wrong here, Idk what freeze are you talking about...
- Instead of bj_DEGTORAD * GetUnitFacing(.arrow[i]) all the time, just put it in the "Actions" part, something like set .angle[i] = bj_DEGTORAD * GetUnitFacing(.arrow[i]) so that you dont need to call it every time...
- Put your damage configuration in a function or method...and other configurables as well...
- Use T32 is better for this type...
- Dont destroy group, just remove the unit...anyway you really dont need that .units group...
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
What about this:

JASS:
private static method Timer_Actions takes nothing returns nothing
    local thistype this = GetTimerData(GetExpiredTimer())
    local integer i = 1
    local integer index = 0
    local real x
    local real y
    local unit u
    local texttag tt
    loop
        if .arrow[i] != null then
            set x = GetUnitX(.arrow[i]) + ARROW_SPEED_TRUE * Cos(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
            set y = GetUnitY(.arrow[i]) + ARROW_SPEED_TRUE * Sin(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
            if .distance[i] > 0 and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
                call SetUnitX(.arrow[i], x)
                call SetUnitY(.arrow[i], y)
                set .distance[i] = .distance[i] - ARROW_SPEED_TRUE
                call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, ARROW_AREA, null)
                loop
                    set u = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen u == null
                    if not IsUnitInGroup(u, .units) and IsUnitEnemy(u, GetOwningPlayer(.caster)) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
                        call GroupAddUnit(.units, u)
                        call UnitDamageTarget(.caster, u, .damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
                        if SHOW_DAMAGE then
                            set tt = CreateTextTag()
                            call SetTextTagText(tt, "|cffff0000" + I2S(R2I(.damage)) + "!|r", 0.023)
                            call SetTextTagPosUnit(tt, u, 25)
                            call SetTextTagVelocity(tt, 0, 64 * 0.071 / 128)
                            call SetTextTagPermanent(tt, false)
                            call SetTextTagLifespan(tt, 2)
                            call SetTextTagFadepoint(tt, 1)
                            call SetTextTagVisibility(tt, true)
                        endif
                        set .arrow[i] = null
                    endif
                    call GroupRemoveUnit(bj_lastCreatedGroup, u)
                endloop
            else
                call DestroyEffect(AddSpecialEffect(ARROW_EFFECT, GetUnitX(.arrow[i]), GetUnitY(.arrow[i])))
                call RemoveUnit(.arrow[i])
                set .arrow[i] = null
                set .dead = .dead + 1
            endif
        endif
        exitwhen i == ARROW_COUNT
        set i = i + 1
    endloop
    if .dead == ARROW_COUNT then
        call ReleaseTimer(GetExpiredTimer())
        call .destroy()
    endif
endmethod
EDIT: I don't think this would work...sigh...

EDIT 2: What about this:

JASS:
private static method Timer_Actions takes nothing returns nothing
    local thistype this = GetTimerData(GetExpiredTimer())
    local integer i = 1
    local integer index = 0
    local integer count = 0
    local real x
    local real y
    local unit u
    local texttag tt
    loop
        if .arrow[i] != null then
            set x = GetUnitX(.arrow[i]) + ARROW_SPEED_TRUE * Cos(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
            set y = GetUnitY(.arrow[i]) + ARROW_SPEED_TRUE * Sin(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
            if .distance[i] > 0 and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
                call SetUnitX(.arrow[i], x)
                call SetUnitY(.arrow[i], y)
                set .distance[i] = .distance[i] - ARROW_SPEED_TRUE
                call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, ARROW_AREA, null)
                loop
                    set u = FirstOfGroup(bj_lastCreatedGroup)
                    exitwhen u == null
                    if not IsUnitInGroup(u, .units) and IsUnitEnemy(u, GetOwningPlayer(.caster)) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
                        call GroupAddUnit(.units, u)
                        call UnitDamageTarget(.caster, u, .damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
                        if SHOW_DAMAGE then
                            set tt = CreateTextTag()
                            call SetTextTagText(tt, "|cffff0000" + I2S(R2I(.damage)) + "!|r", 0.023)
                            call SetTextTagPosUnit(tt, u, 25)
                            call SetTextTagVelocity(tt, 0, 64 * 0.071 / 128)
                            call SetTextTagPermanent(tt, false)
                            call SetTextTagLifespan(tt, 2)
                            call SetTextTagFadepoint(tt, 1)
                            call SetTextTagVisibility(tt, true)
                        endif
                        set count = count + 1
                    endif
                    call GroupRemoveUnit(bj_lastCreatedGroup, u)
                endloop
                if count > 0 then
                    call DestroyEffect(AddSpecialEffect(ARROW_EFFECT, GetUnitX(.arrow[i]), GetUnitY(.arrow[i])))
                    call RemoveUnit(.arrow[i])
                    set .arrow[i] = null
                    set .dead = .dead + 1
                endif
            else
                call DestroyEffect(AddSpecialEffect(ARROW_EFFECT, GetUnitX(.arrow[i]), GetUnitY(.arrow[i])))
                call RemoveUnit(.arrow[i])
                set .arrow[i] = null
                set .dead = .dead + 1
            endif
        endif
        exitwhen i == ARROW_COUNT
        set i = i + 1
    endloop
    if .dead == ARROW_COUNT then
        call ReleaseTimer(GetExpiredTimer())
        call .destroy()
    endif
endmethod
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Thanks. This is the final product:

JASS:
scope MultiShot

    globals
    
        // ~~~ Configurables ~~~
        
        private constant integer SPELL_ID           = 'A002'
        private constant integer DUMMY_ID           = 'h005'
        
        private constant integer ARROW_COUNT        = 10
        private constant real    ARROW_DISTANCE     = 1500
        private constant real    ARROW_SPEED        = 1000
        private constant string  ARROW_EFFECT       = "Abilities\\Weapons\\GlaiveMissile\\GlaiveMissileTarget.mdl"
        private constant real    ARROW_AREA         = 150
        private constant boolean SHOW_DAMAGE        = true
        
        // ~~~ End of Configurables ~~~
        
        private constant real    ARROW_DEGREES      = 60 / ARROW_COUNT
        private constant real    ARROW_SPEED_TRUE   = ARROW_SPEED * 0.03
        
    endglobals

    private struct MultiShot_Data
    
        group units = CreateGroup()
        integer dead = 0
        unit array arrow [ARROW_COUNT]
        unit caster
        real array distance [ARROW_COUNT]
        real damage
    
        private method destroy takes nothing returns nothing
            local integer i = 1
            call DestroyGroup(.units)
            set .units = null
            set .caster = null
            call .deallocate()
        endmethod
        
        private static method Timer_Actions takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            local integer i = 1
            local integer index = 0
            local integer count = 0
            local real x
            local real y
            local unit u
            local texttag tt
            loop
                set count = 0
                if .arrow[i] != null then
                    set x = GetUnitX(.arrow[i]) + ARROW_SPEED_TRUE * Cos(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
                    set y = GetUnitY(.arrow[i]) + ARROW_SPEED_TRUE * Sin(bj_DEGTORAD * GetUnitFacing(.arrow[i]))
                    if .distance[i] > 0 and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) then
                        call SetUnitX(.arrow[i], x)
                        call SetUnitY(.arrow[i], y)
                        set .distance[i] = .distance[i] - ARROW_SPEED_TRUE
                        call GroupEnumUnitsInRange(bj_lastCreatedGroup, x, y, ARROW_AREA, null)
                        loop
                            set u = FirstOfGroup(bj_lastCreatedGroup)
                            exitwhen u == null
                            if not IsUnitInGroup(u, .units) and IsUnitEnemy(u, GetOwningPlayer(.caster)) and not IsUnitType(u, UNIT_TYPE_DEAD) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) then
                                call GroupAddUnit(.units, u)
                                call UnitDamageTarget(.caster, u, .damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
                                if SHOW_DAMAGE then
                                    set tt = CreateTextTag()
                                    call SetTextTagText(tt, "|cffff0000" + I2S(R2I(.damage)) + "!|r", 0.023)
                                    call SetTextTagPosUnit(tt, u, 25)
                                    call SetTextTagVelocity(tt, 0, 64 * 0.071 / 128)
                                    call SetTextTagPermanent(tt, false)
                                    call SetTextTagLifespan(tt, 2)
                                    call SetTextTagFadepoint(tt, 1)
                                    call SetTextTagVisibility(tt, true)
                                endif
                                set count = count + 1
                            endif
                            call GroupRemoveUnit(bj_lastCreatedGroup, u)
                        endloop
                        if count > 0 then
                            call DestroyEffect(AddSpecialEffect(ARROW_EFFECT, GetUnitX(.arrow[i]), GetUnitY(.arrow[i])))
                            call RemoveUnit(.arrow[i])
                            set .arrow[i] = null
                            set .dead = .dead + 1
                        endif
                    else
                        call DestroyEffect(AddSpecialEffect(ARROW_EFFECT, GetUnitX(.arrow[i]), GetUnitY(.arrow[i])))
                        call RemoveUnit(.arrow[i])
                        set .arrow[i] = null
                        set .dead = .dead + 1
                    endif
                endif
                exitwhen i == ARROW_COUNT
                set i = i + 1
            endloop
            if .dead == ARROW_COUNT then
                call ReleaseTimer(GetExpiredTimer())
                call .destroy()
            endif
        endmethod
        
        private static method Actions takes nothing returns thistype
            local thistype this = thistype.allocate()
            local timer t = NewTimer()
            local integer i = 1
            local real min
            local real x
            local real y
            local unit caster = GetTriggerUnit()
            local integer agility = GetHeroAgi(caster, true)
            local integer intelligence = GetHeroInt(caster, true)
            local integer strength = GetHeroStr(caster, true)
            local integer hero_level = GetHeroLevel(caster)
            set .caster = caster
            set .damage = (5 + (3 * hero_level)) + (1.5 * agility)
            set .caster = GetTriggerUnit()
            set min = bj_RADTODEG * Atan2(GetSpellTargetY() - GetUnitY(.caster), GetSpellTargetX() - GetUnitX(.caster)) - 30 - ARROW_DEGREES
            set x = GetUnitX(.caster)
            set y = GetUnitY(.caster)
            loop
                set .arrow[i] = CreateUnit(GetOwningPlayer(.caster), DUMMY_ID, x, y, min + (ARROW_DEGREES * i))
                set .distance[i] = ARROW_DISTANCE
                exitwhen i == ARROW_COUNT
                set i = i + 1
            endloop
            call SetTimerData(t, this)
            call TimerStart(t, 0.03, true, function thistype.Timer_Actions)
            set t = null
            set caster = null
            return this
        endmethod

        private static method Conditions takes nothing returns boolean
            if GetSpellAbilityId() == SPELL_ID then
                call thistype.Actions()
            endif
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
            call TriggerAddCondition(t, Condition(function thistype.Conditions))
            set t = null
        endmethod
        
    endstruct
    
endscope
Looks alright?
 
its better to show in the floating text the ACTUAL damage dealt...

sample;
JASS:
set life = GetWidgetLife(u)
call UnitDamageTarget(.caster, u, .damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
set actualdamage = life - GetWidgetLife(u)
//set floating text

call SetTextTagText(tt, "|cffff0000" + I2S(R2I(actualdamage)) + "!|r", 0.023)
 
Status
Not open for further replies.
Top