HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Projectiles randomly stopping/starting movement

09-18-2008, 09:18 PM#1
the-thingy
For some weird reason, projectiles from random instances of my spell freeze (in mid-air or on their spawn point), and sometimes their movement is (re)started by subsequent spell instances - at one point, about 4 spell instances (5 projectiles each) were frozen on the ground, and after spamming the spell, those projectiles started moving again, and I can't figure out why.

I would make it with TimerUtils because it's getting really frustrating, but I'd really like to keep the spell system-independent if possible

Collapse JASS:
//Phantasmal Barrage
//By Flare
//Requires: Projectile dummy (h000)
//          Launcher Dummy (h001)
//          Phantasmal Barrage ability (A000)
//          NewGen WE
//          dummy.mdx


//Thanks to:
//Shadow1500 - Parabola function
//???        - dummy.mdx
//Please let me know who the creator of the dummy.mdx model is, so I can credit them :)

struct PB
//Configurables

//Ability rawcode
    private static constant integer SID = 'AOsh'
//Launcher Dummy rawcode
    private static constant integer LID = 'h001'
//Projectile Dummy rawcode
    private static constant integer PID = 'h000'
//Timer interval
    private static constant real TIMER_INT = 0.03125
//Number of projectiles used
    private static constant integer PROJCOUNT = 5
//Projectile effect
    private static constant string PROJ_FX = "Abilities\\Spells\\Other\\BlackArrow\\BlackArrowMissile.mdl"
//Explosion effect
    private static constant string END_FX = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl"
//Number of effects spawned per ring
    private static constant integer FX_COUNT = 5
//Number of rings spawned
    private static constant integer RING_COUNT = 1
//Ring offset
    private static constant real RING_OFFSET = 75.
//Spawn offset
    private static constant real SPAWN_OFFSET = 300.
//Attachment point for projectile
    private static constant string ATTACH_PT = "origin"
//Low bound for projectile speed - projectile speed will be somewhere between SPEED_LOW and SPEED_HIGH
    private static constant real SPEED_LOW = 300
//High bound for projectile speed
    private static constant real SPEED_HIGH = 1000
//Projectile's target offset - base value for how far from the target point the projectile will land
    private static constant real TARGET_OFFSET = 0.
//Intensity of curve - lower values result in a "sharper" curve, higher values result in a bow-shaped curve
    private static constant real CURVE = 2.
//Accurate range
    private static constant real ACC_RANGE = 800.
//Accuracy discrepancy - higher values result in a less accurate barrage at longer distance
//Discrepancy only takes effect if the target point if more than ACC_RANGE units away from the caster
    private static constant real ACC_DISCREP_MULTI = 75
//For every ACC_DISCREP_MULTI units beyond ACC_RANGE, the projectiles' targeting will be off by up to ACC_DISCREP units
//e.g. (using current values) if you cast the spell 1400 units away
//the projectiles will land up to ((1400-800)/50)*25 (300) units away from their original target
//NOTE: Projectile's original target is the target point offset by TARGET_OFFSET towards a random angle
    private static constant real ACC_DISCREP = 25
//Minimum damage percentage - percentage of whole damage that is dealt at the edge of damage radius
    private static constant real MIN_DMG_PERCENT = 0.25
//Attack type, damage type, weapon type
    private static constant attacktype ATYPE = ATTACK_TYPE_MAGIC
    private static constant damagetype DTYPE = DAMAGE_TYPE_NORMAL
    private static constant weapontype WTYPE = WEAPON_TYPE_WHOKNOWS
//Animation played by the launcher dummies
    private static constant string ANIM = "attack"
//Lifespan of launcher dummies
    private static constant real LIFESPAN = 1.
    
    
//Damage radius calculation
    private static method DamageRadius takes unit u returns real
        local real base = 150.
        local real m1 = I2R (GetUnitAbilityLevel (u, .SID))
        local real m2 = 50.
        return base + m1*m2
    endmethod
    
    
//Damage calculation
    private static method Damage takes unit u returns real
        local real base = 15
        local real m1 = GetUnitAbilityLevel (u, .SID)
        local real m2 = 5
        return base + m1*m2
    endmethod
    
    
//Required globals    
    private static location tempLoc = Location (0,0)
    private static group genGroup = CreateGroup ()
    private static timer spellTimer = CreateTimer ()
    private static PB array data
    private static PB gdata
    private static integer N = 0
    private static real groupX = 0
    private static real groupY = 0
    private static real doublePi = bj_PI * 2
    private static boolean grounded = false
    
    
//Required struct members
    unit caster
    unit array proj[.PROJCOUNT]
    real array speed[.PROJCOUNT]
    real array pcos[.PROJCOUNT]
    real array psin[.PROJCOUNT]
    real array curdist[.PROJCOUNT]
    real array maxdist[.PROJCOUNT]
    boolean array done[.PROJCOUNT]
    effect array pfx[.PROJCOUNT]
    integer destroyed = 0
    
    
    
//Required helper functions
    private static method RingEffect takes string fxString, real ringCount, real effectCount, real offset, real x, real y returns nothing
        local integer i1 = 1
        local integer i2 = 1
        local real angle = GetRandomReal (0, .doublePi)
        local real x2
        local real y2
        loop
        exitwhen i1 > ringCount
            loop
            exitwhen i2 > effectCount
                set angle = angle + .doublePi/effectCount
                set x2 = x + Cos (angle) * (i1 * offset)
                set y2 = y + Sin (angle) * (i1 * offset)
                call DestroyEffect(AddSpecialEffect (fxString, x2, y2))
                set i2 = i2 + 1
            endloop
            set i1 = i1 + 1
        endloop
    endmethod


    private static constant method JumpParabola takes real dist, real maxdist,real curve returns real
        local real t = (dist*2)/maxdist-1
        return (-t*t+1)*(maxdist/curve)
    endmethod
    
    
    private static method SetUnitXY takes unit u,real x,real y returns nothing
        if x<GetRectMaxX(bj_mapInitialPlayableArea) and x>GetRectMinX(bj_mapInitialPlayableArea) and y<GetRectMaxY(bj_mapInitialPlayableArea) and y>GetRectMinY(bj_mapInitialPlayableArea) then
            call SetUnitX(u,x)
            call SetUnitY(u,y)
        endif
    endmethod
    
    
    private static method GetCoordZ takes real x, real y returns real
        call MoveLocation (.tempLoc, x, y)
        return GetLocationZ (.tempLoc)
    endmethod

    
    private static method SetUnitZ takes unit u, real h returns boolean
        local real z = h - .GetCoordZ (GetUnitX (u), GetUnitY (u))
        if z > 0 then
            call SetUnitFlyHeight (u, z, 0)
            return false
        else
            return true
        endif
    endmethod
    
    private static method GetUnitZ takes unit u returns real
        return .GetCoordZ (GetUnitX (u), GetUnitY (u)) + GetUnitFlyHeight (u)
    endmethod
    
    
    private static method GetRandOffsetX takes real x, real dist returns real
        local real discrepdist = ((dist - .ACC_RANGE)/.ACC_DISCREP_MULTI) * .ACC_DISCREP
        local real angle = Cos (GetRandomReal (0, .doublePi))
        return x + angle * discrepdist
    endmethod
    
    
    private static method GetRandOffsetY takes real y, real dist returns real
        local real discrepdist = ((dist - .ACC_RANGE)/.ACC_DISCREP_MULTI) * .ACC_DISCREP
        local real angle = Sin (GetRandomReal (0, .doublePi))
        return y + angle * discrepdist
    endmethod
    
    
//Spell functions
    private static method GroupFunc takes nothing returns boolean
        local PB a = .gdata
        local unit u = GetFilterUnit ()
        local real ux = GetUnitX (u)
        local real uy = GetUnitY (u)
        local real x = .groupX - ux
        local real y = .groupY - uy
        local real dist = SquareRoot (x*x + y*y)
        local real percent = (.DamageRadius (a.caster) - dist)/.DamageRadius (a.caster)
        local real dmg
        if percent < .MIN_DMG_PERCENT then
            set percent = .MIN_DMG_PERCENT
        endif
        set dmg = .Damage (a.caster) * percent
        call UnitDamageTarget (a.caster, u, dmg, false, true, .ATYPE, .DTYPE, .WTYPE)
        return false
    endmethod

    
    private static method Move takes nothing returns nothing
        local integer i = 0
        local integer i2 = 0
        local real fxAngle = 0
        local real x = 0
        local real y = 0
        local real x2 = 0
        local real y2 = 0
        local real z = 0
        local real uz = 0
        local real ex = 0
        local real ey = 0
        local boolean b = false
        local real hDist = 0
        local real zDif = 0
        local integer animindex = 0
        local PB a
        loop
        exitwhen i > .N
        set i = i + 1
        set a = .data[i]
            loop
            exitwhen i2 > .PROJCOUNT
            //Projectile handling
                if a.done[i2] == false then
                    set x = GetUnitX (a.proj[i2])
                    set y = GetUnitY (a.proj[i2])
                    set x2 = x + a.pcos[i2]*a.speed[i2]
                    set y2 = y + a.psin[i2]*a.speed[i2]
                    set z = .JumpParabola (a.curdist[i2], a.maxdist[i2], .CURVE)
                    set uz = .GetUnitZ (a.proj[i2])
                    set hDist = SquareRoot ((x2-x)*(x2-x) + (y2-y)*(y2-y))
                    set zDif = z - uz
                    set animindex = R2I (Atan2 (zDif, hDist) * bj_RADTODEG + 90)
                    if animindex >= 0 and animindex <= 180 then
                        call SetUnitAnimationByIndex (a.proj[i2], animindex)
                    endif
                    call .SetUnitXY (a.proj[i2], x2, y2)
                    set b = .SetUnitZ (a.proj[i2], z)
                    //Clears individual projectiles
                    if b and a.curdist[i2] > 0 then
                        set a.destroyed = a.destroyed + 1
                        call DestroyEffect (a.pfx[i2])
                        set a.done[i2] = true
                        set a.curdist[i2] = 0
                        call KillUnit (a.proj[i2])
                        set .gdata = a
                        call GroupEnumUnitsInRange (.genGroup, x, y, .DamageRadius (a.caster), Condition (function PB.GroupFunc))
                        call .RingEffect (.END_FX, .RING_COUNT, .FX_COUNT, .RING_OFFSET, x, y)
                    endif
                    set a.curdist[i2] = a.curdist[i2] + a.speed[i2]
                endif
                set i2 = i2 + 1
            endloop
            set i2 = 0
            //Clears instances
            if a.destroyed >= .PROJCOUNT then
                call a.destroy ()
                set .data[i] = .data[.N]
                set .N = .N - 1
                if .N == 0 then
                    call PauseTimer (.spellTimer)
                endif
                set i = i - 1
            endif
            //call BJDebugMsg ("The " + I2S (i) + "th instance of " + I2S (.N) + " instances has ended")
        endloop
        //call BJDebugMsg ("i = " + I2S (i) + ", N = " + I2S (.N))
    endmethod

    
    private static method SpellCond takes nothing returns boolean
        return GetSpellAbilityId () == .SID
    endmethod
    
    
    private static method SpellActions takes nothing returns nothing
        local PB a = PB.create ()
        local integer i = 0
        local unit u
        local unit c = GetTriggerUnit ()
        local real cx = GetUnitX (c)
        local real cy = GetUnitY (c)
        local real sx
        local real sy
        local location l = GetSpellTargetLoc ()
        local real tx = GetLocationX (l)
        local real ty = GetLocationY (l)
        local real x = tx - cx
        local real y = ty - cy
        local real rx
        local real ry
        local real angle = Atan2 (y, x)
        local real dist = SquareRoot (x*x + y*y)
        set a.caster = c
        set c = null
        set rx = cx + Cos (angle) * dist
        set ry = cy + Sin (angle) * dist
        call RemoveLocation (l)
        set l = null
        loop
        exitwhen i == .PROJCOUNT
           set sx = cx + Cos (GetRandomReal (0, .doublePi)) * GetRandomReal (0, .SPAWN_OFFSET)
           set sy = cy + Sin (GetRandomReal (0, .doublePi)) * GetRandomReal (0, .SPAWN_OFFSET)
           set u = CreateUnit (GetOwningPlayer (a.caster), .LID, sx, sy, angle * bj_RADTODEG)
           call SetUnitAnimation (u, .ANIM)
           call QueueUnitAnimation (u, "stand")
           call UnitApplyTimedLife (u, 'BTLF', .LIFESPAN)
           if dist <= .ACC_RANGE then
                set rx = rx + Cos (GetRandomReal (0, .doublePi)) * GetRandomReal (0, .TARGET_OFFSET)
                set ry = ry + Sin(GetRandomReal (0, .doublePi)) * GetRandomReal (0, .TARGET_OFFSET)
           else
                set rx = .GetRandOffsetX (rx, dist)
                set ry = .GetRandOffsetY (ry, dist)
           endif
           set x = rx-cx
           set y = ry-cy
           set angle = Atan2 (y, x)
           set a.pcos[i] = Cos (angle)
           set a.psin[i] = Sin (angle)
           set a.maxdist[i] = SquareRoot (x*x + y*y)
           set a.proj[i] = CreateUnit (GetOwningPlayer (a.caster), .PID, sx, sy, angle * bj_RADTODEG)
           call UnitAddAbility (a.proj[i], 'Arav')
           call UnitRemoveAbility (a.proj[i], 'Arav')
           set a.curdist[i] = 0
           set a.speed[i] = GetRandomReal (.SPEED_LOW, .SPEED_HIGH) * .TIMER_INT
           set a.done[i] = false
           set a.pfx[i] = AddSpecialEffectTarget (.PROJ_FX, a.proj[i], .ATTACH_PT)
           set i = i + 1
        endloop
        
        if .N == 0 then
            call TimerStart (.spellTimer, .TIMER_INT, true, function PB.Move)
        endif
        
        set .N = .N + 1
        set .data[.N] = a
    
        //call BJDebugMsg (I2S (.N))
        set u = null
    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 PB.SpellCond))
        call TriggerAddAction (t, function PB.SpellActions)
    endmethod
endstruct
09-18-2008, 10:58 PM#2
Zerzax
For one thing, your array members within each struct instance are destroying your maximum instance limit. Take 8190 (which is struct maximum instance limit) and divide it by 5 ^ 8 (8 is the number of arrays), which yields less than one instance that your spell can sustain. I would reccommend using dynamic arrays instead that each have a limit of five and you can then instanciate them quite a few times as members of your struct. Even if my calculation or reasoning is off, you still won't be able to make many stable projectiles with your current system.

EDIT: Whoops, my reasoning was way off, sorry about that, your arrays aren't limiting much...