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

[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.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
- 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
It does not bug everytime, just occasionally. Try casting the spell with lots of enemies in front and see if it bugs.

When it bugs, three or four of the arrows just freeze and the rest continue normally. I will do some testing to see what is happening.
 
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?
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
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