HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Special Effects Bug

09-03-2008, 10:32 PM#1
Zerzax
In my spell, six effects (I'm using eyecandy units now, actually, still have the bug) are created in the pattern of a regular hexagon that remain in their locations for 4 seconds. The bug is that with multiple castings one of these effects remains permanently. To create these units, I use a loop. The angle of their position starts at 90 degrees, and each increment of the loop adds 60 degrees to the angle until the hexagon is formed. It ends up looking like a less sharp rupee from Zelda ( a little squashed though). The units are then removed 4 seconds later in a similar loop. The units are generated when a unit casts my spell, and 4 seconds later, the destruction of my struct loops through and removes the units. It appears that the bug happens when the loop does not recognize all of the remaining units... When the spell is not cast multiple times, and I wait 4 seconds to re cast, it works fine. Can anyone help?
09-03-2008, 10:54 PM#2
MaD[Lion]
bug without code is no bug. Code without bug is no code. Questioning for help without showing problem is nonsense.
and not everyone have played Zelda
09-03-2008, 10:55 PM#3
Anopob
Are you sure your spell is MUI, as your description seems like it isn't. If so, did you make sure to check each unit is set to a variable to be destroyed?

EDIT: Oh and yeah, what MaD[Lion] said.
09-03-2008, 11:21 PM#4
Zerzax
I guess the Zelda thing wasn't really important, now that I'm looking at the code I will elaborate and show the important parts. Yes, it is MUI because it is based on structural instances:

Collapse JASS:
private struct wrath
    //... struct members

    unit array flames[FLAMES_ARRAY_LIMIT] // limit is equal to 5
    real array flamesX[FLAMES_ARRAY_LIMIT]
    real array flamesY[FLAMES_ARRAY_LIMIT]

    static method create takes unit u returns wrath
        local integer i=0

        // assignments...

        set w.castx=GetUnitX(u)
        set w.casty=GetUnitY(u)

        //....
        // now to the first loop that creates them

        loop
            exitwhen i > FLAMES_ARRAY_LIMIT
            set w.flamesX[i]=w.castx + DEST_RADIUS * Cos( (pi/2) + (pi/3) * i)          // dest radius = 200
            set w.flamesY[i]=w.casty + DEST_RADIUS * Sin( (pi/2) + (pi/3) * i)
            set w.flames[i]=CreateUnit(GetOwningPlayer(u), FLAME_ID, w.flamesX[i], w.flamesY[i], 270)
            call SetUnitX(w.flames[i], w.flamesX[i])
            call SetUnitY(w.flames[i], w.flamesY[i]) // to ensure that the unit stays there even if coords unpathable
            set i=i+1
        endloop

        // ....
    endmethod

    method onDestroy takes nothing returns nothing

        local integer i=0

        loop
            exitwhen i > FLAMES_ARRAY_LIMIT
            call RemoveUnit(this.flames[i])
            set i=i+1
       endloop

        //....
    endmethod

endstruct

09-03-2008, 11:28 PM#5
Bobo_The_Kodo
The rest of the code?
09-03-2008, 11:52 PM#6
Zerzax
This is everything that deals with the spell, I've excluded anything that deals with the victim struct (it moves units around while the caster is channeling, it does not affect the 6 immobile flame units) or the hash table I am using to store caster instances. The bug will still occur without the victim struct and the hash table. One thing I have noticed: after casting twice, the last created unit (ie the 30 degrees unit) will bug. Then, the next time I cast twice the 1st unit (the 90 degrees unit) will bug
Collapse JASS:
scope VolcanicWrath initializer init

    globals
    
        // Config
        private constant integer ABILITY_ID='A001'
        private constant integer SHOCK_ID='A000'
        private constant integer DUMMY_ID='e000'
        private constant integer FLAME_ID='e001'
        private constant integer BLAST_NUMBER=6
        private constant integer FLAMES_ARRAY_NUMBER=5
        
        private constant real DRAW_IN_RADIUS=750.00
        private constant real DRAW_IN_SPEED=1.00
        private constant real DRAW_IN_INTERVAL=.04
        private constant real DRAW_INTERVAL_COUNT=25
        private constant real BLAST_INTERVAL=.50
        private constant real BLAST_RADIUS=300.00
        private constant real BLAST_BASE_DAMAGE=100.00/6.00
        private constant real REPEL_SPEED=2.00
        private constant real DEST_RADIUS=250.00
        private constant real pi=bj_PI
        
        private constant string SPELL_ANIM="spell"
        private constant string BLAST_ANIM="spell channel"
        private constant string SHOCK_ORDER="thunderclap"
        private constant string EXPLOSION_FX="Objects\\Spawnmodels\\Other\\NeutralBuildingExplosion\\NeutralBuildingExplosion.mdl"
        private constant string DOOM_BUFF_FX="Abilities\\Spells\\Other\\Doom\\DoomTarget.mdl"
        private constant string DOOM_DEATH_FX="Abilities\\Spells\\Other\\Doom\\DoomDeath.mdl"
        private constant string PHOENIX_CHEST_FX="Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
        private constant string BURN_FX="Abilities\\Spells\\Other\\BreathOfFire\\BreathOfFireDamage.mdl"
        
    endglobals    
    
    private function DamageCalc takes integer lvl returns real
        return lvl * BLAST_BASE_DAMAGE 
    endfunction
    
    private struct wrath
        unit caster
        timer t
        real castx
        real casty
        integer castlvl
        group damage_group // Damages units and prevents any damage overlap
        group filter_group // Filters units during the blasting
        group victim_group // Stores units as references to "victim" instances
        unit group_unit // A unit variable that deals with groups, is used several times, and doesnt need nulling
        unit array flames[FLAMES_ARRAY_NUMBER]
        //effect array flames[FLAMES_ARRAY_NUMBER]
        real array flamesX[FLAMES_ARRAY_NUMBER] 
        real array flamesY[FLAMES_ARRAY_NUMBER] 
        integer blastcount=1
        boolean finished=false
        boolean initiated=false
        
        static method blast takes nothing returns nothing
            local wrath w=GetTimerData(GetExpiredTimer())
            local integer i=0
            
            if w.blastcount==1 then
                call SetUnitAnimation(w.caster, BLAST_ANIM)
            elseif w.blastcount==5 then
                call SetUnitAnimation(w.caster, SPELL_ANIM) 
            endif
            
            loop
                exitwhen i > FLAMES_ARRAY_NUMBER
                call DestroyEffect(AddSpecialEffect(DOOM_DEATH_FX, w.flamesX[i], w.flamesY[i]))
                //call DestroyEffect(AddSpecialEffect(EXPLOSION_FX, w.flamesX[i], w.flamesY[i]))
                call GroupEnumUnitsInRange(w.filter_group, w.flamesX[i], w.flamesY[i], BLAST_RADIUS, null)
                loop
                    set w.group_unit=FirstOfGroup(w.filter_group)
                    exitwhen w.group_unit==null 
                    if not IsUnitInGroup(w.group_unit, w.damage_group) and IsUnitEnemy(w.group_unit, GetOwningPlayer(w.caster)) and GetUnitState(w.group_unit, UNIT_STATE_LIFE) > .405 and not IsUnitType(w.group_unit, UNIT_TYPE_STRUCTURE) then
                        call GroupAddUnit(w.damage_group, w.group_unit)
                        call UnitDamageTarget(w.caster, w.group_unit, DamageCalc(w.castlvl), true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
                    endif
                    call GroupRemoveUnit(w.filter_group, w.group_unit)
                endloop
                set i=i+1
            endloop

            call GroupClear(w.damage_group)        
            set w.blastcount=w.blastcount+1
            
            if w.blastcount > BLAST_NUMBER then
                set w.finished=true
                call w.destroy()
            endif
        endmethod
        
        static method initiate takes nothing returns nothing
            local wrath w=GetTimerData(GetExpiredTimer())
            call TimerStart(GetExpiredTimer(), BLAST_INTERVAL, true, function wrath.blast)
            set w.initiated=true
        endmethod
        
        static method create takes unit u returns wrath
            local wrath w=wrath.allocate()
            local integer i=0
            
            set w.caster=u
            set w.castlvl=GetUnitAbilityLevel(u, ABILITY_ID)
            set w.castx=GetUnitX(u)
            set w.casty=GetUnitY(u)
            set w.victim_group=CreateGroup()
            set w.damage_group=CreateGroup()
            set w.filter_group=CreateGroup()
            set w.t=NewTimer()
           
            loop
            exitwhen i > FLAMES_ARRAY_NUMBER
                set w.flamesX[i]=w.castx + DEST_RADIUS * Cos((pi/2) + (pi/3) * i)
                set w.flamesY[i]=w.casty + DEST_RADIUS * Sin((pi/2) + (pi/3) * i)
                set w.flames[i]=CreateUnit(GetOwningPlayer(w.caster), FLAME_ID, w.flamesX[i], w.flamesY[i], 270)
                call SetUnitX(w.flames[i], w.flamesX[i])
                call SetUnitY(w.flames[i], w.flamesY[i])
                //set w.flames[i]=AddSpecialEffect(DOOM_BUFF_FX, w.flamesX[i], w.flamesY[i])
                set i=i+1
            endloop
            
            call TimerStart(w.t, DRAW_IN_SPEED, false, function wrath.initiate)
            return w
        endmethod
        
        method onDestroy takes nothing returns nothing
            local integer i=0

            local unit u=CreateUnit(GetOwningPlayer(this.caster), DUMMY_ID, this.castx, this.casty, 0)

            call SetUnitPosition(this.caster, GetUnitX(this.caster), GetUnitY(this.caster))
        
            if this.finished then
                call UnitAddAbility(u, SHOCK_ID)
                call SetUnitAbilityLevel(u, SHOCK_ID, this.castlvl)
                call UnitApplyTimedLife(u, 'BTLF', 5.00)
                call IssueImmediateOrder(u, SHOCK_ORDER)
            endif
        
            loop
                exitwhen i > FLAMES_ARRAY_NUMBER
                //call DestroyEffect(this.flames[i])
                call RemoveUnit(this.flames[i])
                set i=i+1
            endloop
            
            call ReleaseTimer(this.t)
        
            call GroupClear(this.filter_group)
            call GroupClear(this.damage_group)
            call GroupClear(this.victim_group)
            call DestroyGroup(this.victim_group)
            call DestroyGroup(this.damage_group)
            call DestroyGroup(this.filter_group)
            set u=null
        endmethod
    
    endstruct
    
    private function SpellEffect takes nothing returns nothing
        local wrath w
        if GetSpellAbilityId()==ABILITY_ID then
            set w=wrath.create(GetTriggerUnit())
            call SetTimerData(w.t, w)
        endif
    endfunction

    private function init takes nothing returns nothing
        local unit u=CreateUnit(Player(15), 'e000', 0, 0, 0) 
        local trigger t=CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddAction( t, function SpellEffect )
      
        call UnitAddAbility(u, ABILITY_ID)
        call UnitAddAbility(u, SHOCK_ID)
        call UnitRemoveAbility(u, ABILITY_ID)
        call UnitRemoveAbility(u, SHOCK_ID)
        
        call DestroyEffect(AddSpecialEffectTarget(EXPLOSION_FX, u, "origin"))
        call DestroyEffect(AddSpecialEffectTarget(PHOENIX_CHEST_FX, u, "origin"))
        call DestroyEffect(AddSpecialEffectTarget(BURN_FX, u, "origin"))
        call DestroyEffect(AddSpecialEffectTarget(DOOM_DEATH_FX, u, "origin"))
        call DestroyEffect(AddSpecialEffectTarget(DOOM_BUFF_FX, u, "origin"))
      
        set t=null
        set u=null
    endfunction
    

//===========================================================================
    function InitTrig_Volcanic_Wrath takes nothing returns nothing
    endfunction

endscope

EDIT: Uses red flavor TimerUtils, forgot to mention that.
09-04-2008, 03:00 AM#7
Pyrogasm
The InitTrig is no longer required when using NewGen, by-the-way, and you don't remove 'u' in the init function.

Instead of manually removing the units, why not just give them a 4 second expiration timer?
09-04-2008, 03:33 AM#8
Zerzax
Good point about the init, forgot about it when making the spell. I'll try an expiration timer, that will definitely clear the units out, though there might still be a bug with the loops. I'll give an update soon.

Update: The expiration timer works, though the fire unit remains there for an extra second, including bugged ones. I believe the units have to run through the stand animation before they disappear. I had the same problem using effects because as I ran through the destroy loop the effects remained an extra second. I guess that problem comes from the doom buff itself, which I might have to change.

Also of note is that multiple castings apparently make the timer skip through one running of the loop, and that running deals with the bugged unit's sector. I'll experiment with the loop integers and see if something comes up. In the meantime, more tips appreciated.

EDIT: It seems that the spell doesn't have multi-instanceability and I'm thinking the 2 real arrays and the unit array within the wrath struct are the problem. But honestly, the current array limit is 5, well under the struct max of 100, and I don't think it should be doing that. At the moment, it doesn't affect how the spell works, because now if the caster stops casting the spell the struct is destroyed and everything cuts out. It's still a bug though, and I might lose points in the olympics :P.

EDIT2: After taking a look at the JassHelper Manual I realized that these arrays are actually severely limiting the max instance count. 8190 / ( 5 * 5 * 5 ) = 65. whoops. I may try using dynamic arrays or look at someone else's script.

Final Edit: The problem I think is using an index of zero in any kind of instanciated array: if you use zero for a struct array or a dynamic array, the next time that array is instanciated there will be overlap in that index. My solution was to (a make two dynamic arrays that store the x and y coordinates for easy looping and a larger instance limit and (b using only positive integers when indexing my arrays. Hope this helps someone, it took me a while to figure out.