HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Map crashing on unit group checking, help pls

01-26-2010, 12:18 AM#1
Sinnergy
Ok I have two spells with different effects but with the same method on picking units and checking if a unit group is not empty.

Basically I did this for picking the first unit in a unit group:
Collapse Zinc:
            unit u,tmp = null;
            GroupEnumUnitsInRange(g,x,y,135.0,BOOLEXPR_TRUE);
            for(u = FirstOfGroup(g) ; u != null ; u = FirstOfGroup(g)){
                if(IsUnitEnemy(u,GetOwningPlayer(d.c)) && GetWidgetLife(u) > 0.405 && ! IsUnitType(u,UNIT_TYPE_STRUCTURE) && GetUnitAbilityLevel(u, 'mark') == 0 && tmp == null){
                    tmp = u;
                    UnitDamageTarget(d.c,tmp,100*d.l,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_NORMAL,null);
                }
                GroupRemoveUnit(g,u);
            }
where tmp is used to check some situations to do this and that.

So when I used either of the two spells inside my map, I get a crash, not always but sometimes, like the first try, when the first spell hits an enemy and dies, crashes the game, and the second try when the second spell also hits an enemy and dies, it crashes the game, that is why I doubt about the two spells because of the method I used to check units in a group.

So these are the spells:

Crippling Arrow
Shoots a magical arrow towards the target point that deals damage and cripples the first enemy unit it hits, slowly cripples the unit until it may not able to move.
Lasts 4 seconds.
Expand Zinc:

Scatter Shot (uses xecollider)
Fires armor-piercing arrows to all nearby enemy units around Raven, each arrow homes to each desired target.
Expand Zinc:
01-26-2010, 03:41 AM#2
DioD
compile code and see how it actually looks.
01-26-2010, 08:32 AM#3
Anachron
I would really like to help you, but I don't like ZINC.
01-26-2010, 09:56 AM#4
Sinnergy
so how do I compile it and get the output?
01-26-2010, 10:10 AM#5
Anachron
Collapse Zinc:
for(u = FirstOfGroup(g) ; u != null ; u = FirstOfGroup(g)){
This looks somehow bugged. Use null at the last u = and try to add it to the code. Perhaps its that.
01-26-2010, 11:49 AM#6
Sinnergy
tried u = null, increases the chance to crash the map
u = u, I know, I'm stupid, causes a very heavy lag while the code is picking units, lol

while looking at vex's spell coded using zinc, here's what he does to pick one unit
Collapse Zinc:
      method onUnitHit(unit hitUnit) {
            if( (hitUnit!=owner) && ! IsUnitInGroup(hitUnit, targetLog ) ) {
                //when a new unit is hit:
                
                //  Try doing damage, if damage gets done, the target is a valid
                // one and we can contnue and show the effect and get a new target
                if(! xdam[level].damageTarget(owner, hitUnit, missileDamage(level) ) )
                    return;
                  
                flash("Abilities\\Weapons\\GreenDragonMissile\\GreenDragonMissile.mdl");
                GroupAddUnit(targetLog, hitUnit) ;
                targetCount = targetCount + 1;
                if( targetCount == maxTargets(level) ) {
                       //too many targets, terminate this missile.
                       terminate();
                       return;
                }
                if( ( targetUnit != hitUnit ) && (targetUnit!=null) ) {
                    //The assigned targetUnit was not hit yet, so continue 
                    // moving against it.
                    return;
                }
                  
                instance = this;
                targetUnit = null; //Pick a new target, start by setting target to null.
                  
                GroupEnumUnitsInRange(enumgroup, x,y  , detectDistance(level), function()->boolean{
                    thistype this = instance; //adopt-a-instance
                    unit picked = GetFilterUnit();
                    real dx = x - GetUnitX(picked), dy = y - GetUnitY(picked);
                    real dis = SquareRoot( dx*dx + dy*dy );
                      
                    //Pick the best new target.
                      
                    if ( !IsUnitInGroup(picked,targetLog)  && (picked!=owner) 
                          && ( (targetUnit == null) || (dis < targetdist) )
                          && xdam[level].allowedTarget(owner, picked)
                       )
                    {
                        targetdist = dis;
                        targetUnit = picked;
                    }
                    picked = null;
                    return false;
                });
                if( targetUnit == null) {
                    angleSpeed = 0;
                    forgetTarget();
                }
            }
                        
        }
look at the GroupEnumUnitsInRange line, he picked the unit inside the boolexpr function

and the full code
Collapse Zinc:
//! zinc
library ChainInferno requires xecollider, xedamage
{
    // config stuff:
    
    // The triggerer spell's rawcode:
    constant integer SPELL_ID = 'A01N';
    
    // A random off-set for the creation position of each missile.
    constant real NOISE = 50.0;   
    
    function missileCount(integer level)->integer {
        return 3+level;
    }
    
    function missileMaxSpeed(integer level)->real {
        return 700.0 + level * 0.0;
    }
    function missileMinSpeed(integer level)->real {
        return 100.0 + level * 0.0;
    }

    function maxTargets(integer level) -> integer {
        return 3 + level;
    }

    
    /* The following functions are used when setting up the missiles
       As a matter of fact, it is not necessary to use functions like this
       you could easily just assign the stuff in the missiles, but these
       functions are a common way to have a configuration header
       in a spell...
    */   
    function missileDamage(integer level) -> real {
        return 15.0 + level * 10.0;
    }
    function detectDistance(integer level) -> real {
        return 1000.0 + 0.0 * level;
    }
    
    function configMissile(xecollider m, integer level) {
        //asorted settings for the missiles.
        // notice a xecollider object is used, and we can mess with these fields
        // in the config section like this thanks to OOPness.
        
        // In the case of this spell, maxSpeed and minSpeed are treated in a 
        // special way, so they do not get assigned by this function.
        
        // The movement height of each missile:
        m.z = 60.0;
        //the duration of each missile
        m.expirationTime = 5.0 + 0.0 * level ;
        
        // The model used by the missile:
        m.fxpath = "Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl";
        
        m.collisionSize = 50.0 + level * 0.0;
        m.acceleration = 1200.0 + level * 0.0;
        m.angleSpeed   = bj_PI * 4;
    }
    
    //likewise, we can configure the xedamage:
    function setupDamage(xedamage xd, integer level ) {
        xd.damageAllies =false;   //
        xd.damageEnemies =true;   // Hit enemies exclusively
        xd.damageNeutral = false; //
        xd.exception = UNIT_TYPE_STRUCTURE; //do not hit buildings.
        xd.abilityFactor('mark',0.0);
        xd.dtype =  DAMAGE_TYPE_FIRE;  // fire (magical) normal damage
        xd.atype = ATTACK_TYPE_NORMAL;        
    }
    
    // -------------------------------------------------------------------------
    // code stuff:
    //
    xedamage xdam[];
    struct missile extends xecollider
    {
        integer level;
        integer targetCount = 0;
        unit owner;
        real time = 0;
        group targetLog;
        
        //Follows an example on how to have a custom create on
        // a xecollider struct...
        //
        static method create(real x, real y, real dir)->thistype {
            missile this = allocate(x,y,dir); /* allocate of xecollider children has that argument list */
            
            if ( targetLog == null) {
                targetLog = CreateGroup();
            }
            GroupClear(targetLog);
            return this;
        }
        
        static real targetdist = 0.0; //current target's distance
        static missile instance; /* to pass the current instance to the enum function */
        method onUnitHit(unit hitUnit) {
            if( (hitUnit!=owner) && ! IsUnitInGroup(hitUnit, targetLog ) ) {
                //when a new unit is hit:
                
                //  Try doing damage, if damage gets done, the target is a valid
                // one and we can contnue and show the effect and get a new target
                if(! xdam[level].damageTarget(owner, hitUnit, missileDamage(level) ) )
                    return;
                  
                flash("Abilities\\Weapons\\GreenDragonMissile\\GreenDragonMissile.mdl");
                GroupAddUnit(targetLog, hitUnit) ;
                targetCount = targetCount + 1;
                if( targetCount == maxTargets(level) ) {
                       //too many targets, terminate this missile.
                       terminate();
                       return;
                }
                if( ( targetUnit != hitUnit ) && (targetUnit!=null) ) {
                    //The assigned targetUnit was not hit yet, so continue 
                    // moving against it.
                    return;
                }
                  
                instance = this;
                targetUnit = null; //Pick a new target, start by setting target to null.
                  
                GroupEnumUnitsInRange(enumgroup, x,y  , detectDistance(level), function()->boolean{
                    thistype this = instance; //adopt-a-instance
                    unit picked = GetFilterUnit();
                    real dx = x - GetUnitX(picked), dy = y - GetUnitY(picked);
                    real dis = SquareRoot( dx*dx + dy*dy );
                      
                    //Pick the best new target.
                      
                    if ( !IsUnitInGroup(picked,targetLog)  && (picked!=owner) 
                          && ( (targetUnit == null) || (dis < targetdist) )
                          && xdam[level].allowedTarget(owner, picked)
                       )
                    {
                        targetdist = dis;
                        targetUnit = picked;
                    }
                    picked = null;
                    return false;
                });
                if( targetUnit == null) {
                    angleSpeed = 0;
                    forgetTarget();
                }
            }
                        
        }
    }
    group enumgroup; 
    
    function onSpell() {
        unit u = GetTriggerUnit();
        integer level = GetUnitAbilityLevel(u, SPELL_ID );
        integer i;
        real  tx = GetSpellTargetX(), ty=GetSpellTargetY();
        real  x = GetUnitX(u), y=GetUnitY(u);
        real f  = Atan2( ty-y, tx-x );
        missile m;
        
        if(xdam[level]==0) {
            //all array elements start at 0
            //the trick here is to init the xedamage instance for each level only when necessary.
            
            xdam[level] = xedamage.create();
            setupDamage(xdam[level], level);
        }
        
        for ( 0 <= i < missileCount(level) ) {
            //create a new missile:
            m = missile.create(x + GetRandomReal(-NOISE, NOISE), y + GetRandomReal(-NOISE, NOISE), f);
            // NOISE works just as a random off-set for the missile position, it makes the spell look better.
            
            m.owner = u;
            m.targetUnit = GetSpellTargetUnit();

            m.level = level;
                       
            // call configMissile :
            configMissile(m,level);
            
            //This one is important, the secret to the looks of the spell is with doing these tricks, each
            // missile starts at a speed slower than the previous one, then the movement is accelerated
            // by missileAcceleration(level) until all the missiles have the same speed.
            m.maxSpeed =missileMaxSpeed(level); 
            m.speed = m.maxSpeed - (m.maxSpeed - missileMinSpeed(level) )* (i+1) / missileCount(level);
            
        }
        u=null;
    }
    
    function onInit() {
        //let's init the trigger :
        // "When any unit casts SPELL_ID, call onSpelll"
        trigger t = CreateTrigger();
        TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT);
        TriggerAddAction(t, function onSpell);
        TriggerAddCondition(t, function()->boolean{
            return GetSpellAbilityId() == SPELL_ID;
        });

        //let's init the enumgroup variable:
        enumgroup = CreateGroup();
    }
    
}


//! endzinc
01-26-2010, 11:56 AM#7
Anachron
Argh, I hate these unknown functions. Btw, there is no loop at Vexs code.
01-27-2010, 12:11 AM#8
Sinnergy
yeah, I need to know on how did he do that, I'm currently reading his code, I need to learn more stuffs.
01-27-2010, 12:24 AM#9
Themerion
GroupEnumUnitsInRange is the loop.

Here's a loop which kills all units in range from (x,y):

Collapse JASS:
globals
    private group dummyGroup=null
endglobals

private function Loop takes nothing returns boolean
    call KillUnit(GetFilterUnit())

  // By using return false dummyGroup will never actually be filled.
  // We just use the group because GroupEnum wants a group.
    return false
endfunction


function KillUnitsInRange takes real x, real y, real range returns nothing
    if dummyGroup==null then
         set dummyGroup=CreateGroup()
    endif

    call GroupEnumUnitsInRange(dummyGroup, x, y, range, Condition(function Loop))
endfunction
01-30-2010, 01:48 AM#10
Sinnergy
so, that means, function Loop can be used instead of looping through the filled group?
but one thing I hate about that is, I don't know how to attach a struct to something to be used inside function Loop, the only thing I can think of is GetFilterUnit() and GetTriggerUnit().

I am also unsure if GetTriggerUnit will return the right unit (i.e. the unit that cast the spell), but when GroupEnum is inside a timer then how will I retrieve the unit that casts the spell? I know this will be possible by using structs to retrieve the data, but where should I attach the struct?

Edit:
I recoded Scatter Shot from Zinc to Normal Jass and still causes crash
Collapse JASS:
scope ScatterShot initializer onInit

    globals
        private constant integer spell = 'A01Z'
        private constant string mdl = "Abilities\\Weapons\\Arrow\\ArrowMissile.mdl"
        private constant string sfx = "Abilities\\Spells\\Other\\Stampede\\StampedeMissileDeath.mdl"
        private group f
    endglobals
    
    private struct data
        unit c
        integer l
        private method onDestroy takes nothing returns nothing
            call PauseUnit(c,false)
            call SetUnitTimeScale(c,1.0)
            call SetUnitAnimation(c,"stand ready")
            call GroupClear(f)
        endmethod
    endstruct
    
    private struct aCollider extends xecollider
        unit c
        unit h
        integer l
        
        private method onUnitHit takes unit t returns nothing
            if IsUnitEnemy(t,GetOwningPlayer(c)) and GetWidgetLife(t) > 0.405 and not IsUnitType(t,UNIT_TYPE_STRUCTURE) and GetUnitAbilityLevel(t,'mark') == 0 then
                call UnitDamageTarget(c,t,75*l,true,false,ATTACK_TYPE_NORMAL,DAMAGE_TYPE_UNIVERSAL,null)
                call DestroyEffect(AddSpecialEffectTarget(sfx,t,"chest"))
                if t == h then
                    call terminate()
                endif
            endif
        endmethod
        
        private method loopControl takes nothing returns nothing
            if GetWidgetLife(h) <= 0.405 then
                call terminate()
            endif
        endmethod
    endstruct
    
    private function filt takes nothing returns boolean
        return GetWidgetLife(GetFilterUnit()) > 0.405 and not IsUnitType(GetFilterUnit(),UNIT_TYPE_STRUCTURE) and GetUnitAbilityLevel(GetFilterUnit(),'mark') == 0  and not IsUnitInGroup(GetFilterUnit(),f)
    endfunction
    
    private function callBack takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local data d = GetTimerData(t)
        local group g = NewGroup()
        local unit u
        local unit tmp = null
        local real x = GetUnitX(d.c)
        local real y=GetUnitY(d.c)
        local real tx
        local real ty
        local real a
        local aCollider xe
        call SetUnitTimeScale(d.c,4.0)
        call PauseUnit(d.c,true)
        call GroupEnumUnitsInRange(g,GetUnitX(d.c),GetUnitY(d.c),1300.0,Condition(function filt))
        loop
            set u = FirstOfGroup(g)
            exitwhen u == null
            if IsUnitEnemy(u,GetOwningPlayer(d.c)) and tmp == null then
                set tmp = u
                set tx = GetUnitX(u)
                set ty = GetUnitY(u)
                set a = Atan3(x,y,tx,ty)
                call SetUnitFacingTimed(d.c,a*57.29582,0.0)
                call SetUnitAnimation(d.c,"attack")
                call GroupAddUnit(f,u)
                set xe = aCollider.create(x,y,a)
                set xe.c = d.c
                set xe.l = d.l
                set xe.h = tmp
                set xe.fxpath = mdl
                set xe.z = 50.0
                set xe.scale = 1.3
                set xe.collisionSize = 100.0
                set xe.speed = 1200
                set xe.angleSpeed = bj_PI * 4.0
                set xe.targetUnit = tmp
            endif
            call GroupRemoveUnit(g,u)
        endloop
        call ReleaseGroup(g)
        if tmp == null or GetWidgetLife(d.c) <= 0.405 then
            call ReleaseTimer(t)
            call d.destroy()
        endif
        set t = null
        set u = null
        set tmp = null
        set g = null
    endfunction
    
    private function act takes nothing returns nothing
        local timer t = NewTimer()
        local data d = data.create()
        local unit c = SpellEvent.CastingUnit
        local integer l = GetUnitAbilityLevel(c,spell)
        set d.c = c
        set d.l = l
        call SetTimerData(t,d)
        call TimerStart(t,0.3,true,function callBack)
        set t = null
        set c = null
    endfunction
    
    private function onInit takes nothing returns nothing
        call RegisterSpellEffectResponse(spell,act)
        set f = CreateGroup()
    endfunction
endscope