• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] [Needs fix] Projectile API

JASS:
// function InitProjectile takes player whichPlayer, integer typeId, real x, real y, real z, real facing returns integer
// Initializes a new projectile on the map. Returns the Id of the projectile so you deal just with integers.

// function RemoveProjectile takes integer whichId, boolean killNeeded, boolean explodeNeeded returns nothing
// Removes the projectile by id.

// function AllocateNewProjectile takes nothing returns integer
// Determines the id where the projectile will take its place.

// function GetProjectile takes integer whichId returns unit
// Returns the projectile from selected id.

// function GroupEnumProjectilesInRect takes group whichGroup, rect whichRect returns nothing
// Enumerates projectiles in rect.

// function GroupEnumProjectilesInRange takes group whichGroup, real x, real y, real rangeAmount returns nothing
// Enumerates projectiles in range.

// function GroupEnumProjectilesOfPlayer takes group whichGroup, player whichPlayer returns nothing
// Enumerates projectiles of player.

function GetProjectile takes integer whichId returns unit
    return LoadUnitHandle(udg_H,0,whichId)
endfunction

function AllocateNewProjectile takes nothing returns integer
    local integer i = -2147483648
    
    loop
        exitwhen i > 2147483647 or GetProjectile(i) == null
        set i = i + 1
    endloop
    
    return i
endfunction

function GroupEnumProjectilesOfPlayer takes group whichGroup, player whichPlayer returns nothing
    // Because the projectiles have Locust, they can't be enumed in ordinary way.
    local integer currentId = -2147483648
    local unit currentProjectile
    
    loop
        exitwhen currentId > 2147483647
        set currentProjectile = GetProjectile(currentId)
        if currentProjectile != null then
            if GetOwningPlayer(currentProjectile) == whichPlayer then
                call GroupAddUnit(whichGroup,currentProjectile)
            endif
        endif
        set currentId = currentId + 1
    endloop
    
    set currentProjectile = null
endfunction 

function GroupEnumProjectilesInRect takes group whichGroup, rect whichRect returns nothing
    // Because the projectiles have Locust, they can't be enumed in ordinary way.
    local integer currentId = -2147483648
    local unit currentProjectile
    local real projectileX
    local real projectileY
    
    loop
        exitwhen currentId > 2147483647
        set currentProjectile = GetProjectile(currentId)
        if currentProjectile != null then
            set projectileX = GetUnitX(currentProjectile)
            set projectileY = GetUnitY(currentProjectile)
            if (GetRectMinX(whichRect) <= projectileX) and (projectileX <= GetRectMaxX(whichRect)) and (GetRectMinY(whichRect) <= projectileY) and (projectileY <= GetRectMaxY(whichRect)) then
                call GroupAddUnit(whichGroup,currentProjectile)
            endif
        endif
        set currentId = currentId + 1
    endloop
    
    set currentProjectile = null
endfunction 

function GroupEnumProjectilesInRange takes group whichGroup, real x, real y, real rangeAmount returns nothing
    // Because the projectiles have Locust, they can't be enumed in ordinary way.
    local integer currentId = -2147483648
    local unit currentProjectile
    local real projectileX
    local real projectileY
    local real thisDistance = 0.
    
    loop
        exitwhen currentId > 2147483647
        set currentProjectile = GetProjectile(currentId)
        if currentProjectile != null then
            set projectileX = GetUnitX(currentProjectile)
            set projectileY = GetUnitY(currentProjectile)
            set thisDistance = SquareRoot((x-projectileX)*(x-projectileX)+(y-projectileY)*(y-projectileY))
            if thisDistance <= rangeAmount then
                call GroupAddUnit(whichGroup,currentProjectile)
            endif
        endif
        set currentId = currentId + 1
    endloop
    
    set currentProjectile = null
endfunction 

function RemoveProjectile takes integer whichId, boolean killNeeded, boolean explodeNeeded returns nothing
    local unit whichProjectile = GetProjectile(whichId)
    
    call SetUnitExploded(whichProjectile,explodeNeeded)
    
    if killNeeded then
        call KillUnit(whichProjectile)
    else
        call RemoveUnit(whichProjectile)
    endif
    
    call RemoveSavedHandle(udg_H,0,whichId)
        
    set whichProjectile = null
endfunction

function InitProjectile takes player whichPlayer, integer typeId, real x, real y, real z, real facing returns integer
    local integer lastSavedID = AllocateNewProjectile()
    local unit newProjectile = CreateUnit(whichPlayer,typeId,x,y,facing)
    
    if UnitAddAbility(newProjectile,'Amrf') then
        call UnitRemoveAbility(newProjectile,'Amrf')
    endif
    
    call SetUnitFlyHeight(newProjectile,z,0.)
    call UnitAddAbility(newProjectile,'Aloc')
    
    call SaveUnitHandle(udg_H,0,lastSavedID,newProjectile)
    
    return newProjectile
endfunction

Requires initialized hashtable H.
 
Some general things:
  • Hashtable "H" is a common name. You should have some sort of name-safety if it is written in vanilla JASS.
  • It is a bit weird that this is a projectile system, but doesn't make functions for the actual movement. I suppose that can be ok depending on your goals, but I just find it a bit unorthodox. :p
  • The allocating is really weird. (1) It is better to keep a global "count" to know what the next new index is. (2) You might want to consider recycling (struct-like?). I guess it isn't necessary because of the lack of limits on keys for hashtables though (or that it is unreasonably high). (3) You can just work with positive numbers. It is very unlikely that anyone will surpass even 1 million projectiles in their map.
  • The enuming can be improved. Since it is a search through the list, it can be pretty slow. Also, it doesn't stop when it hits some "null point", so it will continue until the number cap, which most likely will just hit the op limit eventually. That is why you should have a number to track how many total projectiles there are, so you can loop up to that number. (or make a pseudo linked list)

    It works the way it is, but when you are enuming periodically it can make a difference as the projectile count increases. For this method, it may mess up when the total number of projectiles made by the system increases. (e.g. you have made 1k projectiles.. even though you destroyed them all, the system may lag with just 2-3 projectiles because it has to loop through 1k times for each to enumerate) See:
    http://www.thehelper.net/threads/mo...o-projectile-on-projectile-collisions.131008/

You might also want to add a bit more documentation to show what the system is about. :)

However, projectile systems are pretty overdone, yeah? We have one by Kenny, Berb, Nestharus probably made one already, xe by Vex, etc. I suppose there isn't one in vanilla JASS (or at least not since the old wc3jass went down), but it should probably still bring something new.
 
Seriously, enuming all units in range can be done much more efficiently, even without vJass:
At the most efficient solution, divide the whole map area in tiles (check my DestructableHider resource for a quick demonstration on how to do so) and only cycle through the list of units inside the tiles that matter.

Projectile systems are about speed. Using O(n) search operations there is a total fail of concept.
 
Signed:


Code:
00000000 00000000 00000000 00000000
^
Sign bit

(If the sign bit is 1, the number is negative. Else, it's positive.)

Range: [-2147483648 ... 2147483647]

Unsigned:


Code:
00000000 00000000 00000000 00000000
^
Magnitude bit

Range: [0 ... 4294967295]

All integers in Warcraft are signed integers.
 
Top