• 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] Multiple Instances of Struct-Based Spell Crashing WC3/Multi-Unit Enumeration

Status
Not open for further replies.
Level 4
Joined
Sep 25, 2005
Messages
71
Well, more experimentation, more problems. This time I was trying follow instructions to create abilities that are pretty much instanced in individual structs and methods. I think I screwed up to be honest.

The spell in question is supposed to make a cone of lightning balls, linked by lightning effects. The balls are supposed to do damage when they connect with an enemy and have large enough collision that even at full distance, the "beams" appear to do damage as well.

Multiple instances of this spell being cast cause the game to outright crash. Single casts work fine. I also tried to set a sort of GroupEnum loop (to deal damage) inside the "move" loop that iterates through each "ball" and moves each lightning, but that somehow just completely stopped whatever was going on and one ball would just wander off, forever moving against the border of the map.

For reference, the loop I was trying:

JASS:
                    loop
                        exitwhen enemy == null
                        set enemy = FirstOfGroup(LightningConeEnumGroup)
                        if IsUnitEnemy(enemy, GetOwningPlayer(localLightningCone.caster)) == true and IsUnitAliveBJ(enemy) == true then
                            call UnitDamageTarget(GetTriggerUnit(), enemy, 125., true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, null)
                        endif
                        call GroupRemoveUnit(LightningConeEnumGroup, enemy)
                    endloop

The "SetBoundedX/Y" functions are literally just functions meant to keep it within playable map area.

JASS:
globals
	timer LightningConeTimer = CreateTimer()
	real LightningConeInterval = .012
    real LightningConeSpeed = 12
	integer LightningConeInstances = 0
	group LightningConeEnumGroup = CreateGroup()
	LightningConeData array LightningConeArray
endglobals

struct LightningConeData

    unit caster
    unit array balls[6]
    player casterOwner
    lightning array beams[5]
    real distance
    real maxDist
    
    static method LightningConeExecution takes nothing returns nothing
        local integer i = 0
        local integer ballcount = 0
        local unit enemy
        local LightningConeData localLightningCone
        loop
            exitwhen i >= LightningConeInstances
            set localLightningCone = LightningConeArray[i]
            if localLightningCone.distance < localLightningCone.maxDist then         
            loop
                exitwhen ballcount == 6    
                
                    call MoveUnit(localLightningCone.balls[ballcount], LightningConeSpeed, GetUnitFacing(localLightningCone.balls[ballcount]))
                    if ballcount != 0 then
                        call MoveLightning(localLightningCone.beams[ballcount], true, GetUnitX(localLightningCone.balls[ballcount-1]), GetUnitY(localLightningCone.balls[ballcount-1]), GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]))
                    endif
                    
                set ballcount = ballcount + 1
            endloop
                set localLightningCone.distance = localLightningCone.distance + LightningConeSpeed
            endif
            
            if localLightningCone.distance >= localLightningCone.maxDist then
                set LightningConeInstances = LightningConeInstances - 1
                set LightningConeArray[i] = LightningConeArray[LightningConeInstances]
                call localLightningCone.destroy()
            endif		
            set i = i + 1
        endloop

        if LightningConeInstances == 0 then
            call PauseTimer(LightningConeTimer)
        endif
    endmethod
    
    static method create takes unit whichUnit, real speed, real range returns LightningConeData
        local integer i = 0
        local real facing = GetUnitFacing(whichUnit) + 30.
        local LightningConeData data = LightningConeData.allocate()
        set data.caster =  whichUnit
        set data.casterOwner = GetOwningPlayer(data.caster)
        set data.distance = speed
        set data.maxDist = range
        
        loop
            exitwhen i == 6
            
            set data.balls[i] = CreateUnit(data.casterOwner, 'o002', GetUnitX(data.caster), GetUnitY(data.caster), (facing - (10 * i)))
            
            if i != 0 then
                set data.beams[i] = AddLightning("FORK", true, GetUnitX(data.balls[i-1]), GetUnitY(data.balls[i-1]), GetUnitX(data.balls[i]), GetUnitY(data.balls[i]))
            endif
            
            set i = i + 1
        endloop
        
    	if LightningConeInstances <= 0 then
            call TimerStart(LightningConeTimer, LightningConeInterval, true, function LightningConeData.LightningConeExecution)
        endif
        
        set LightningConeArray[LightningConeInstances] = data
        set LightningConeInstances = LightningConeInstances + 1
        
        return data
    endmethod
    
    method onDestroy takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen i == 6
        call KillUnit(.balls[i])
        set i = i + 1
    endloop
    set i = 0
    loop
        exitwhen i == 6
        call DestroyLightning(.beams[i])
        set i = i + 1
    endloop
    endmethod
    
endstruct

function LightningCone takes nothing returns nothing
    local integer i = 0
	local LightningConeData localLightningCone = LightningConeData.create(GetTriggerUnit(), 25., 900.)
endfunction

//====================================/ Condition /====================================================

function checkLightningCone takes nothing returns boolean
	if GetSpellAbilityId() == 'A006' then
		call LightningCone()
	endif
	return false
endfunction

//======================================/ Init /========================================================

function InitTrig_Lightning_Cone takes nothing returns nothing
	local trigger localTrigVar = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(localTrigVar, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition( localTrigVar, Condition(function checkLightningCone))
	set localTrigVar = null
endfunction

If someone has a solution or improvements/optimizations to this, feel free to peep at me.
 

Attachments

  • MvMAbilityDump.w3x
    35.6 KB · Views: 36
Level 4
Joined
Sep 25, 2005
Messages
71
Can i say to not use IsUnitAliveBJ since it's a very evil one xD
Use GetWidgetLife(ennemy) > 0.405

What's the purpose of the function LightningCone ? You create two locals :/
Use the Destroy method more than the OnDestroy one

I'm down with replacing a BJ.

The i is a leftover. The other part creates a struct, which I can probably just do from the condition.

I now have the same problem but with some fat cut:

JASS:
globals
	timer LightningConeTimer = CreateTimer()
	real LightningConeInterval = .012
    real LightningConeSpeed = 12
	integer LightningConeInstances = 0
	group LightningConeEnumGroup = CreateGroup()
	LightningConeData array LightningConeArray
endglobals

struct LightningConeData

    unit caster
    unit array balls[6]
    player casterOwner
    lightning array beams[5]
    real distance
    real maxDist
    
    static method LightningConeExecution takes nothing returns nothing
        local integer i = 0
        local integer ballcount = 0
        local unit enemy
        local LightningConeData localLightningCone
        loop
            exitwhen i >= LightningConeInstances
            set localLightningCone = LightningConeArray[i]
            if localLightningCone.distance < localLightningCone.maxDist then         
            loop
                exitwhen ballcount == 6    
                
                    call MoveUnit(localLightningCone.balls[ballcount], LightningConeSpeed, GetUnitFacing(localLightningCone.balls[ballcount]))
                    if ballcount != 0 then
                        call MoveLightning(localLightningCone.beams[ballcount], true, GetUnitX(localLightningCone.balls[ballcount-1]), GetUnitY(localLightningCone.balls[ballcount-1]), GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]))
                    endif
                                        
                set ballcount = ballcount + 1
            endloop
                set localLightningCone.distance = localLightningCone.distance + LightningConeSpeed
            endif
            
            if localLightningCone.distance >= localLightningCone.maxDist then
                set LightningConeInstances = LightningConeInstances - 1
                set LightningConeArray[i] = LightningConeArray[LightningConeInstances]
                call localLightningCone.destroy()
            endif		
            set i = i + 1
        endloop

        if LightningConeInstances == 0 then
            call PauseTimer(LightningConeTimer)
        endif
    endmethod
    
    static method create takes unit whichUnit, real speed, real range returns LightningConeData
        local integer i = 0
        local real facing = GetUnitFacing(whichUnit) + 30.
        local LightningConeData data = LightningConeData.allocate()
        set data.caster =  whichUnit
        set data.casterOwner = GetOwningPlayer(data.caster)
        set data.distance = speed
        set data.maxDist = range
        
        loop
            exitwhen i == 6
            
            set data.balls[i] = CreateUnit(data.casterOwner, 'o002', GetUnitX(data.caster), GetUnitY(data.caster), (facing - (10 * i)))
            
            if i != 0 then
                set data.beams[i] = AddLightning("FORK", true, GetUnitX(data.balls[i-1]), GetUnitY(data.balls[i-1]), GetUnitX(data.balls[i]), GetUnitY(data.balls[i]))
            endif
            
            set i = i + 1
        endloop
                
        set LightningConeArray[LightningConeInstances] = data        
        
    	if LightningConeInstances <= 0 then
            call TimerStart(LightningConeTimer, LightningConeInterval, true, function LightningConeData.LightningConeExecution)
        endif
        
        set LightningConeInstances = LightningConeInstances + 1
        
        return data
    endmethod
    
    method onDestroy takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen i == 6
        call KillUnit(.balls[i])
        set i = i + 1
    endloop
    set i = 0
    loop
        exitwhen i == 6
        call DestroyLightning(.beams[i])
        set i = i + 1
    endloop
    endmethod
    
endstruct

//====================================/ Condition /====================================================

function checkLightningCone takes nothing returns boolean
    local LightningConeData localLightningCone
	if GetSpellAbilityId() == 'A006' then
        set localLightningCone = LightningConeData.create(GetTriggerUnit(), 25., 900.)
	endif
	return false
endfunction

//======================================/ Init /========================================================

function InitTrig_Lightning_Cone takes nothing returns nothing
	local trigger localTrigVar = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(localTrigVar, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition( localTrigVar, Condition(function checkLightningCone))
	set localTrigVar = null
endfunction

Still isn't instanced properly.
 
Level 4
Joined
Sep 25, 2005
Messages
71
If you read above, i's gone, the BJ is not actually in the code (I'd like to get it to stop crashing the game before I worry about it actually doing something beyond looking pretty,) and it's already done from a condition.

I'm much more concerned about getting instancing right and not crashing the game, and then the dealing damage stuff can come.
 
Right now, the issue is in the array of lightning. When you first made an arrayed-struct member, you probably wondered why you need to specify a size in brackets [] for the member. After all, you don't have to do that for normal arrays. This is because struct members by themselves are arrays (when jasshelper compiles it to vanilla JASS). Thus, when you try to make an array of a variable that is already an array, it has to resort to some special trickery.

By specifying a size, the compiler knows how many slots to allocate for that variable (it determines the pseudo-2D-array capacity). In your case, you defined it as "5", which means that you should only have 5 lightning, and the max index you should use is 4 (use indexes 0-4). Here is an example:
JASS:
struct Ex
    real array test[5]

    static method example takes nothing returns nothing 
        local thistype this = thistype.allocate()
        set this.test[0] = 1.2 // compiles
        set this.test[4] = 1.2 // compiles
        set this.test[5] = 1.2 // won't compile (if i recall correctly)
        set this.test[342] = 1.2 // won't compile
    endmethod
endstruct
Even though you are technically only using 4 instances, it is going to throw an error or have problems. In your case, it won't throw an error since jasshelper won't check your loops.

Now, when you use an index that they don't expect you to, it messes up the formula. Thus, some lightning are being overwritten. This can cause bugs--and it can crash if you try to move/modify a lightning that was destroyed (this is probably where the issue is).

To fix it, try setting the array sizes to 6 each instead of 6 and 5. This may be slightly inefficient since you'll end up having 1 or 2 instances unused, but it won't be that big of an issue (and you can work it out later, e.g. instead of checking if the index is != 0, you would check if the index != 5).
 
Do you mean don't give the arrays a size?
Yes and like this...
JASS:
static method create takes unit ball, unit whichUnit, real speed, real range returns LightningConeData
   local LightningConeData data = LightningConeData.allocate()
   //other datas
   set data.balls = ball
   //etc...
   return data
endmethod

function checkLightningCone takes nothing returns boolean
    local integer i
    if GetSpellAbilityId() == 'A006' then
       set i = 0
       loop
            exitwhen i == 6
            call LightningConeData.create(CreateUnit(GetTriggerPlayer(), 'o002', 0, 0, 0), GetTriggerUnit(), speed, range)
             set i = i + 1
        endloop
    endif
    return false
endfunction
 
hehe, took a while to find. You forgot to set ballcount to 0 after the loop:
JASS:
loop
    exitwhen ballcount == 6    
                
    call MoveUnit(localLightningCone.balls[ballcount], LightningConeSpeed, GetUnitFacing(localLightningCone.balls[ballcount]))
    if ballcount != 0 then
        call MoveLightning(localLightningCone.beams[ballcount], true, GetUnitX(localLightningCone.balls[ballcount-1]), GetUnitY(localLightningCone.balls[ballcount-1]), GetUnitX(localLightningCone.balls[ballcount]), GetUnitY(localLightningCone.balls[ballcount]))
    endif
                                        
    set ballcount = ballcount + 1
endloop
set ballcount = 0 // add this
When the other instances are going through their loop, ballcount is already set to 6 so they aren't actually moved.

EDIT: Yes the video works now, ty.
 
Status
Not open for further replies.
Top