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

[JASS] Projectile System

Status
Not open for further replies.
I have been working on a projectile system which will launch a projectile from the specified point and it will travel a certain distance ('dist' argument) travelling at a certain speed ('speed' argument, not yet functioning correctly, but should be easy to fix, not my aim for this post). The projectile will, whenever it comes within a certain radius ('radius' argument) of an enemy, damage it. Now, I have including 2 boolean arguments: 'StopOnHit' which should cause the missile to stop once it hits something, but doesn't work properly, and if flagged true, just destroys the projectile almost as soon as I create it. The other boolean, 'DamageOwnUnits' also doesn't work. Any unit seems to be damaged by the missile. I believe this is caused by bad 'if' statements in the bold section of code (or at least the section wrapped in bold tags, which don't seem to function within JASS tags). Following is my code:
JASS:
library ProjectileSystem initializer Init

globals
    private timer Tim = CreateTimer()
    private integer Total = 0
    private boolexpr Cond = null

    private integer array Ar

    private real MAX_X
    private real MAX_Y
    private real MIN_X
    private real MIN_Y
endglobals

private constant function Interval takes nothing returns real
    return 0.04
endfunction

private constant function ProjectilePlayer takes nothing returns integer
    return 13
endfunction

public struct Data
    unit u
    unit au
    group hit
    
    real d1
    real d2
    real d3

    real sin
    real cos

    real dmg
    real r
    
    boolean soh
    boolean ds

    static method create takes unit au, integer dummyUnit, real x, real y, real rad, integer q, real d, real a, real dmg, boolean soh, boolean ds returns Data
        local Data dat = Data.allocate()

        set dat.au = au
        set dat.u = CreateUnit(Player(ProjectilePlayer()), dummyUnit, x, y, a * bj_RADTODEG)
        set dat.hit = CreateGroup()
        
        set dat.d1 = 2 * d / (q + 1)
        set dat.d2 = dat.d1
        set dat.d3 = dat.d1 / q

        set dat.sin = Sin(a)
        set dat.cos = Cos(a)

        set dat.dmg = dmg
        set dat.r = rad
        
        set dat.soh = soh
        set dat.ds = ds

        call UnitAddAbility(dat.u, 'Aloc')

        if Total == 0 then
            call TimerStart(Tim, Interval(), true, function Data.Execute)
        endif

        set Total = Total + 1
        set Ar[Total - 1] = dat

        return dat
    endmethod

    static method Execute takes nothing returns nothing
        local Data dat
        local integer i = 0
        local real x
        local real y
        local group g = null
        local unit u = null
        local boolean Bool = false

        loop
            exitwhen i >= Total
            set dat = Ar[i]

            set dat.d2 = dat.d2 - dat.d3
            set x = GetUnitX(dat.u) + dat.d1 * dat.cos
            set y = GetUnitY(dat.u) + dat.d1 * dat.sin
            
            [B]set g = CreateGroup()
            call GroupEnumUnitsInRange(g, x, y, dat.r, null) 
            set u = FirstOfGroup(g)
            debug call BJDebugMsg("checking conditions")
            if (not dat.ds) then
                if (not (GetOwningPlayer(u) == GetOwningPlayer(dat.au))) then
                    debug call BJDebugMsg("Conditions passed, proceeding to test if unit has already been hit")
                    if (not IsUnitInGroup(u, dat.hit) and u != null) then
                        debug call BJDebugMsg("Unit has not already been hit, proceeding to damage unit")
                        call UnitDamageTarget(dat.au, u, dat.dmg, true, true, ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_METAL_LIGHT_SLICE)
                        call GroupAddUnit(dat.hit, u)
                        if (dat.soh) then
                            debug call BJDebugMsg("Unit has been hit, destroying projectile")
                            set Bool = true
                        endif
                    endif
                endif
            else
                debug call BJDebugMsg("Conditions passed, proceeding to test if unit has already been hit")
                if (not IsUnitInGroup(u, dat.hit) and u != null) then
                    debug call BJDebugMsg("Unit has not already been hit, proceeding to damage unit")
                    call UnitDamageTarget(dat.au, u, dat.dmg, true, true, ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_METAL_LIGHT_SLICE)
                    call GroupAddUnit(dat.hit, u)
                    if (dat.soh) then
                        debug call BJDebugMsg("Unit has been hit, destroying projectile")
                        set Bool = true
                    endif
                endif
            endif
            call DestroyGroup(g)
            set g = null
            set u = null[/B]

            if (x < MAX_X and y < MAX_Y and x > MIN_X and y > MIN_Y) and not Bool then
                call SetUnitX(dat.u, x)
                call SetUnitY(dat.u, y)
            endif

            if dat.d2 <= 0 or (x > MAX_X or y > MAX_Y or x < MIN_X or y < MIN_Y) or Bool then
                set Ar[i] = Ar[Total - 1]
                set Total = Total - 1

                call dat.destroy()
            endif

            set Bool = false
            set i = i + 1
        endloop

        if Total == 0 then
            call PauseTimer(Tim)
        endif
    endmethod

method onDestroy takes nothing returns nothing
        call KillUnit(.u)
        set .u = null
        call DestroyGroup(.hit)
        set .hit = null
    endmethod
endstruct

function LaunchProjectile takes unit attacker, integer dummyUnit, real startx, real starty, real radius, real dist, real angle, real speed, real damage, boolean stopOnHit, boolean DamageOwnUnits returns nothing
    call Data.create(attacker, dummyUnit, startx, starty, radius, R2I(speed / 1000 / Interval()), dist, bj_DEGTORAD * angle, damage, stopOnHit, DamageOwnUnits)
endfunction

private function Init takes nothing returns nothing
    set MAX_X = GetRectMaxX(bj_mapInitialPlayableArea) - 64
    set MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea) - 64
    set MIN_X = GetRectMinX(bj_mapInitialPlayableArea) + 64
    set MIN_Y = GetRectMinY(bj_mapInitialPlayableArea) + 64
endfunction

endlibrary
Yes, I did take ideas from Silvenon's knockback code, as you may have noticed
I hope someone can help me!

Thanks
 
Last edited:
Level 11
Joined
Feb 18, 2004
Messages
394
JASS:
call GroupEnumUnitsInRange(g, x, y, dat.r, null)
you never filter out the projectile itself, so it's registering a collision with itself.

Your code is a mess, and your collision detection is primitive at best. (You do not solve the ball-though-a-sheet-of-paper issue.)

JASS:
private constant function Interval takes nothing returns real
    return 0.04
endfunction

private constant function ProjectilePlayer takes nothing returns integer
    return 13
endfunction
use private constant variables...
JASS:
globals
    private constant real INTERVAL =  0.04
    // etc.
endglobals

You really should be using vectors for this.
 
ok...
But I just cleared up the code, made a filter and changed sin, cos and d1/d2/d3 to 2 vectors, v and v2, but now it either destroys the projectile as soon as I create it or just leaves it there and does nothing to it!
JASS:
library ProjectileSystem initializer Init

globals
    private timer Tim = CreateTimer()
    private integer Total = 0
    private integer i = 0
    private boolexpr Cond

    private Data array Ar

    private real MAX_X
    private real MAX_Y
    private real MIN_X
    private real MIN_Y
    
    private constant real Interval = 0.04
    private constant integer ProjectilePlayer = 13
endglobals

struct Vector
    real x
    real y
endstruct

struct Data
    unit u
    unit au
    group hit

    Vector v
    Vector v2

    real dmg
    real r
    
    boolean soh
    boolean ds

    static method create takes unit au, integer dummyUnit, real x, real y, real rad, integer q, real d, real a, real dmg, boolean soh, boolean ds returns Data
        local Data dat = Data.allocate()

        set dat.au = au
        set dat.u = CreateUnit(Player(ProjectilePlayer), dummyUnit, x, y, a * bj_DEGTORAD)
        set dat.hit = CreateGroup()

        set dat.v.x = x + d/I2R(q) * Cos(a)
        set dat.v.y = y + d/I2R(q) * Sin(a)
        
        set dat.v2.x = x + d * Cos(a)
        set dat.v2.y = y + d * Sin(a)

        set dat.dmg = dmg
        set dat.r = rad
        
        set dat.soh = soh
        set dat.ds = ds

        call UnitAddAbility(dat.u, 'Aloc')

        if Total == 0 then
            call TimerStart(Tim, Interval, true, function Data.Execute)
        endif

        set Total = Total + 1
        set Ar[Total - 1] = dat

        return dat
    endmethod
    
    static method EnumFilter takes nothing returns boolean
        return (Ar[i].ds and GetOwningPlayer(GetEnumUnit()) != GetOwningPlayer(Ar[i].au)) and GetEnumUnit() != Ar[i].u and not IsUnitInGroup(GetEnumUnit(),Ar[i].hit)
    endmethod

    static method Execute takes nothing returns nothing
        local Data dat
        local real x
        local real y
        local group g = null
        local unit u = null
        local boolean Bool = false

        loop
            exitwhen i >= Total
            set dat = Ar[i]

            set dat.v2.x = dat.v2.x - dat.v.x
            set dat.v2.y = dat.v2.y - dat.v.y
            
            set x = GetUnitX(dat.u) + dat.v.x
            set y = GetUnitY(dat.u) + dat.v.y
            
            set g = CreateGroup()
            call GroupEnumUnitsInRange(g, x, y, dat.r, Cond) 
            set u = FirstOfGroup(g)
            call UnitDamageTarget(dat.au, u, dat.dmg, true, true, ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_METAL_LIGHT_SLICE)
            call GroupAddUnit(dat.hit, u)
            if (dat.soh) then
                debug call BJDebugMsg("Unit has been hit, destroying projectile")
                set Bool = true
            endif

            call DestroyGroup(g)
            set g = null
            set u = null

            if (x < MAX_X and y < MAX_Y and x > MIN_X and y > MIN_Y) and not Bool then
                call SetUnitX(dat.u, x)
                call SetUnitY(dat.u, y)
            endif

            if dat.v2.x <= 0 or dat.v2.y <= 0 or (x > MAX_X or y > MAX_Y or x < MIN_X or y < MIN_Y) or Bool then
                set Ar[i] = Ar[Total - 1]
                set Total = Total - 1

                call dat.destroy()
            endif

            set Bool = false
            set i = i + 1
        endloop

        if Total == 0 then
            call PauseTimer(Tim)
        endif
    endmethod

method onDestroy takes nothing returns nothing
        call KillUnit(.u)
        set .u = null
        call DestroyGroup(.hit)
        set .hit = null
    endmethod
endstruct

function LaunchProjectile takes unit attacker, integer dummyUnit, real startx, real starty, real radius, real dist, real angle, real speed, real damage, boolean stopOnHit, boolean DamageOwnUnits returns nothing
    call Data.create(attacker, dummyUnit, startx, starty, radius, R2I(speed / 1000 / Interval), dist, bj_DEGTORAD * angle, damage, stopOnHit, DamageOwnUnits)
endfunction

private function Init takes nothing returns nothing
    set Cond = Condition(function Data.EnumFilter)
    
    set MAX_X = GetRectMaxX(bj_mapInitialPlayableArea) - 64
    set MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea) - 64
    set MIN_X = GetRectMinX(bj_mapInitialPlayableArea) + 64
    set MIN_Y = GetRectMinY(bj_mapInitialPlayableArea) + 64
endfunction

endlibrary
 
Last edited:
Level 11
Joined
Feb 18, 2004
Messages
394
Ok... Looks like I'm gonna need to go over this with you...

Firstly, your Data structs member variables have appallingly bad names. Variable names should instantly identify what a variable is and does. You should never have to read code to understand the use of a variable.

Secondly, the static Execute() method really shouldn't be part of the Data struct... separate state-based code, and simulation-based code, or your code will quickly become a mess.

Following my suggestions so far (Only structural changes) Gives:
JASS:
library ProjectileSystem initializer Init
globals
    private timer Tim = CreateTimer()
    private integer Total = 0
    private integer i = 0
    private boolexpr Cond
    private code MainFunc

    private Data array Ar

    private real MAX_X
    private real MAX_Y
    private real MIN_X
    private real MIN_Y

    private constant real Interval = 0.04
    private constant integer ProjectilePlayer = 13
endglobals

struct Vector
    real x
    real y
endstruct

struct Data
    unit u
    unit attacker
    group hit
    
    Vector v
    Vector v2
    
    real damage
    real radius
    
    boolean soh
    boolean ds
    
    static method create takes unit attacker, integer dummyUnitType, real x, real y, real radius, integer q, real d, real a, real damage, boolean soh, boolean ds returns Data
        local Data dat = Data.allocate()

        set dat.attacker = attacker
        set dat.u = CreateUnit(Player(ProjectilePlayer), dummyUnitType, x, y, a * bj_DEGTORAD)
        set dat.hit = CreateGroup()

        set dat.v.x = x + d/I2R(q) * Cos(a)
        set dat.v.y = y + d/I2R(q) * Sin(a)

        set dat.v2.x = x + d * Cos(a)
        set dat.v2.y = y + d * Sin(a)

        set dat.damage = damage
        set dat.radius = radius

        set dat.soh = soh
        set dat.ds = ds

        call UnitAddAbility(dat.u, 'Aloc')

        if Total == 0 then
            call TimerStart(Tim, Interval, true, MainFunc)
        endif

        set Total = Total + 1
        set Ar[Total - 1] = dat

        return dat
    endmethod
    
    method onDestroy takes nothing returns nothing
        call KillUnit(.u)
        set .u = null
        call DestroyGroup(.hit)
        set .hit = null
    endmethod
endstruct

private function EnumFilter takes nothing returns boolean
    return (Ar[i].ds and GetOwningPlayer(GetEnumUnit()) != GetOwningPlayer(Ar[i].attacker)) and GetEnumUnit() != Ar[i].u and not IsUnitInGroup(GetEnumUnit(),Ar[i].hit)
endfunction

private function Execute takes nothing returns nothing
    local Data dat
    local real x
    local real y
    local group g = null
    local unit u = null
    local boolean Bool = false

    loop
        exitwhen i >= Total
        set dat = Ar[i]

        set dat.v2.x = dat.v2.x - dat.v.x
        set dat.v2.y = dat.v2.y - dat.v.y

        set x = GetUnitX(dat.u) + dat.v.x
        set y = GetUnitY(dat.u) + dat.v.y

        set g = CreateGroup()
        call GroupEnumUnitsInRange(g, x, y, dat.r, Cond)
        set u = FirstOfGroup(g)
        call UnitDamageTarget(dat.attacker, u, dat.damage, true, true, ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_METAL_LIGHT_SLICE)
        call GroupAddUnit(dat.hit, u)
        if (dat.soh) then
            debug call BJDebugMsg("Unit has been hit, destroying projectile")
            set Bool = true
        endif

        call DestroyGroup(g)
        set g = null
        set u = null

        if (x < MAX_X and y < MAX_Y and x > MIN_X and y > MIN_Y) and not Bool then
            call SetUnitX(dat.u, x)
            call SetUnitY(dat.u, y)
        endif

        if dat.v2.x <= 0 or dat.v2.y <= 0 or (x > MAX_X or y > MAX_Y or x < MIN_X or y < MIN_Y) or Bool then
            set Ar[i] = Ar[Total - 1]
            set Total = Total - 1

            call dat.destroy()
        endif

        set Bool = false
        set i = i + 1
    endloop

    if Total == 0 then
        call PauseTimer(Tim)
    endif
endfunction

function LaunchProjectile takes unit attacker, integer dummyUnit, real startx, real starty, real radius, real dist, real angle, real speed, real damage, boolean stopOnHit, boolean DamageOwnUnits returns nothing
    call Data.create(attacker, dummyUnit, startx, starty, radius, R2I(speed / 1000 / Interval), dist, bj_DEGTORAD * angle, damage, stopOnHit, DamageOwnUnits)
endfunction

private function Init takes nothing returns nothing
    set Cond = Condition(function EnumFilter)
    set MainFunc = function Execute
    
    set MAX_X = GetRectMaxX(bj_mapInitialPlayableArea) - 64
    set MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea) - 64
    set MIN_X = GetRectMinX(bj_mapInitialPlayableArea) + 64
    set MIN_Y = GetRectMinY(bj_mapInitialPlayableArea) + 64
endfunction
endlibrary

Now, on to logic and design problems.

You remove Data objects from the Ar[] array in code, instead of in the onDestroy() method. As well, that's a horrible name for it. My favoured way of keeping a list of all objects of a type is as so:
JASS:
struct Data
    unit u
    unit attacker
    group hit
    
    Vector v
    Vector v2
    
    real damage
    real radius
    
    boolean soh
    boolean ds
    
    // Keep an array of all objects:
    static Data array all
    static integer count
    integer id
    
    static method create takes unit attacker, integer dummyUnitType, real x, real y, real radius, integer q, real d, real a, real damage, boolean soh, boolean ds returns Data
        local Data dat = Data.allocate()

        set dat.attacker = attacker
        set dat.u = CreateUnit(Player(ProjectilePlayer), dummyUnitType, x, y, a * bj_DEGTORAD)
        set dat.hit = CreateGroup()

        set dat.v.x = x + d/I2R(q) * Cos(a)
        set dat.v.y = y + d/I2R(q) * Sin(a)

        set dat.v2.x = x + d * Cos(a)
        set dat.v2.y = y + d * Sin(a)

        set dat.damage = damage
        set dat.radius = radius

        set dat.soh = soh
        set dat.ds = ds

        call UnitAddAbility(dat.u, 'Aloc')

        if Total == 0 then
            call TimerStart(Tim, Interval, true, MainFunc)
        endif

        // Add to the array...
        set Data.all[Data.count] = dat
        set dat.id = Data.count
        set Data.count = Data.count + 1

        return dat
    endmethod
    
    method onDestroy takes nothing returns nothing
        call KillUnit(.u)
        set .u = null
        call DestroyGroup(.hit)
        set .hit = null
        
        // Remove from the array...
        set Data.all[this.id] = Data.count - 1
        set Data.count = Data.count - 1
    endmethod
endstruct
But that's just how I roll. Using globals instead of object properties is fine too.

Logic error: In a GroupEnum's filter, it is GetFilterUnit(), not GetEnumUnit(). (Don't ask me why.)
JASS:
private function EnumFilter takes nothing returns boolean
    return (Data.all[i].ds and GetOwningPlayer(GetEnumUnit()) != GetOwningPlayer(Data.all[i].attacker)) and GetEnumUnit() != Data.all[i].u and not IsUnitInGroup(GetEnumUnit(), Data.all[i].hit)
endfunction
// to...
private function EnumFilter takes nothing returns boolean
    return (Data.all[i].ds and GetOwningPlayer(GetFilterUnit()) != GetOwningPlayer(Data.all[i].attacker)) and GetFilterUnit() != Data.all[i].u and not IsUnitInGroup(GetFilterUnit(), Data.all[i].hit)
endfunction

Is it just me, or do you never set i to 0?

Now for the hardest thing to explain... How to use vectors efficiently and well in movement-based code.

Vectors define a direction and magnitude without a location. This makes them ideal to hold the velocity (Speed and direction; or rather, change in position over time) of an object. A velocity vector determines the offset an object will move over a period of time. (Traditionally, 1 second)

Code speaks better than words:
JASS:
struct Object
    unit u
    Vector position
    Vector velocity
endstruct

// In the loop that is run for all objects every Interval time...
obj.position.x = obj.position.x + (obj.velocity.x * Interval)
obj.position.y = obj.position.y + (obj.velocity.y * Interval)

// After everything else in the loop is complete, especially ensuring the object's new x/y coordinates are inside the map's bounds:
call SetUnitX(obj.u, obj.position.x)
call SetUnitY(obj.u, obj.position.y)

We can use the Vector struct to hold a point, as well as a vector. (Mainly for convenience) Holding the objects position, instead of getting it via GetUnitX/GetUnitY means that the object will not be effected by things like being ordered to move somewhere. (If the period is fast enough, of course.)

This is one reasons vectors rock for this use: Accounting for velocity is a simple addition, with no trigonometry. If you get in to more complex simulation of forces, like gravity, air drag, lift, etc., the math is also much simpler using vectors.

Anyway, more code changes:
JASS:
library ProjectileSystem initializer Init
globals
    private timer Tim = CreateTimer()
    private integer i = 0
    private boolexpr Cond
    private code MainFunc

    private real MAX_X
    private real MAX_Y
    private real MIN_X
    private real MIN_Y

    private constant real Interval = 0.04
    private constant integer ProjectilePlayer = 13
endglobals

private struct Vector
    real x
    real y
    
    static method create takes real x, real y returns Vector
        local Vector v = Vector.allocate()
        
        set v.x = x
        set v.y = y
        
        return v
    endmethod
endstruct

private struct Data
    unit u
    unit attacker
    
    Vector position
    Vector velocity
    
    real damage
    real radius
    
    group hit = CreateGroup()
    
    boolean stopOnHit
    boolean damageAlly
    
    // Keep an array of all objects:
    static Data array all
    static integer count
    integer id
    
    boolean markedDead = false
    
    static method create takes unit attacker, integer dummyUnitType, real x, real y, real radius, real speed, real angle, real damage, boolean stopOnHit, boolean damageAlly returns Data
        local Data dat = Data.allocate()

        set dat.u = CreateUnit(Player(ProjectilePlayer), dummyUnitType, x, y, angle)
        
        call UnitAddAbility(dat.u, 'Aloc')
        
        set dat.position = Vector.create(x, y)
        
        set dat.velocity = Vector.create(0, 0)
        set dat.velocity.x = Cos(angle * bj_DEGTORAD) * speed
        set dat.velocity.y = Sin(angle * bj_DEGTORAD) * speed

        set dat.attacker = attacker
        set dat.damage = damage
        set dat.radius = radius

        set dat.stopOnHit = stopOnHit
        set dat.damageAlly = damageAlly

        if Data.count == 0 then
            call TimerStart(Tim, Interval, true, MainFunc)
        endif

        set Data.all[Data.count] = dat
        set dat.id = Data.count
        set Data.count = Data.count + 1
        
        return dat
    endmethod
    
    method onDestroy takes nothing returns nothing
        call KillUnit(this.u)
        call DestroyGroup(this.hit)
        
        // Remove from the array...
        set Data.all[this.id] = Data.count - 1
        set Data.count = Data.count - 1
    endmethod
endstruct

private function EnumFilter takes nothing returns boolean
    return (not Data.all[i].damageAlly and GetOwningPlayer(GetFilterUnit()) != GetOwningPlayer(Data.all[i].attacker)) and GetFilterUnit() != Data.all[i].u and not IsUnitInGroup(GetFilterUnit(), Data.all[i].hit)
endfunction

private function Execute takes nothing returns nothing
    local Data dat
    local real x
    local real y
    local group g = null
    local unit u = null
    
    local Data array dead
    local integer deadCount = 0
    
    local integer j

    set i = 0
    loop
        exitwhen i == Data.count
        set dat = Data.all[i]

        set dat.position.x = dat.position.x + (dat.velocity.x * Interval)
        set dat.position.y = dat.position.y + (dat.velocity.y * Interval)

        set g = CreateGroup()
        call GroupEnumUnitsInRange(g, dat.position.x, dat.position.y, dat.radius, Cond)
        set u = FirstOfGroup(g)
        
        if u != null then
            call UnitDamageTarget(dat.attacker, u, dat.damage, true, true, ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_METAL_LIGHT_SLICE)
            call GroupAddUnit(dat.hit, u)
            
            if dat.stopOnHit then
                debug call BJDebugMsg("Unit has been hit, destroying projectile")
                set dat.markedDead = true
                set dead[deadCount] = dat
                set deadCount = deadCount + 1
            endif
        endif

        call DestroyGroup(g)
        
        if not dat.markedDead and (x > MAX_X or y > MAX_Y or x < MIN_X or y < MIN_Y) then
            set dat.markedDead = true
            set dead[deadCount] = dat
            set deadCount = deadCount + 1
        endif

        if not dat.markedDead then
            call SetUnitX(dat.u, dat.position.x)
            call SetUnitY(dat.u, dat.position.y)
        endif
        
        set i = i + 1
    endloop

    // Because we need to reorder the array that we are iterating above
    // when we remove an object, we actually do removals down here.
    loop
        exitwhen deadCount == 0
        
        call dead[deadCount].destroy()
        
        set deadCount = deadCount - 1
    endloop
    
    if Data.count == 0 then
        call PauseTimer(Tim)
    endif
    
    set g = null
    set u = null
endfunction

public function LaunchProjectile takes unit source, integer projectileUnitType, real startX, real startY, real radius, real speed, real angle, real damage, boolean stopOnHit, boolean damageAlly returns nothing
    call Data.create(source, projectileUnitType, startX, startY, radius, speed, angle, damage, stopOnHit, damageAlly)
endfunction

public function UnitLaunchProjectile takes unit source, integer projectileUnitType, real radius, real speed, real angle, real damage, boolean stopOnHit, boolean damageAlly returns nothing
    call Data.create(source, projectileUnitType, GetUnitX(source), GetUnitY(source), radius, speed, angle, damage, stopOnHit, damageAlly)
endfunction

private function Init takes nothing returns nothing
    set Cond = Condition(function EnumFilter)
    set MainFunc = function Execute
    
    set MAX_X = GetRectMaxX(bj_mapInitialPlayableArea) - 64
    set MAX_Y = GetRectMaxY(bj_mapInitialPlayableArea) - 64
    set MIN_X = GetRectMinX(bj_mapInitialPlayableArea) + 64
    set MIN_Y = GetRectMinY(bj_mapInitialPlayableArea) + 64
endfunction
endlibrary

That should about work. However, the API still sucks. That's way too many parameters for a function. It's not nearly as flexible as it could be.

As well, you could just rename the Data struct to something more appealing, and use it directly. eg:

Projectile.create(projectileTypeId, attackingUnit, posx, posy).launchAt(destx, desty, speed) Projectile.create(...).launch(speed, angle) Projectile.create(...).launchAtOver(destx, desty, travelTime)

Anyway... yeah. My brain is now tired.
 
Thanks you took the time to go over it with me. Most people wouldn't have done that. And about the whole bad structure thing: like I said, I based the structure on Silvenon's Knockback function. I just structured everything the way he did... I can't believe I forgot to use GetFilterUnit()... thanks for pointing that out. I'll take into account what you said in the future. And thanks again!

EDIT: Yours wasn't perfect, but after some debugging I got it to work. Thanks again.
 
Last edited:
Status
Not open for further replies.
Top