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

[vJASS] Problem with my script

Status
Not open for further replies.
Level 6
Joined
Feb 16, 2014
Messages
193
Hi, so I've been trying to make a missile system in vJASS, it's still not complete but my problem is, it doesn't create the missile unit.
JASS:
static method create takes real x, real y, real angle, player owner, string unitname, real speed, real climbrate, real maxdist, unit target, attacktype atktype, damagetype dmgtype, real dmg, real dmgradius, boolean has_deathdmg returns Projectile
    local Projectile new = Projectile.allocate()
    if maxdist < MIN_MAXDIST then
        set maxdist = MIN_MAXDIST
    endif
    set new.u = CreateUnitByName(owner, unitname, x, y, angle)
    set new.target = target
    set new.owner = owner
    set new.speed = speed
    set new.climbrate = climbrate
    set new.atktype = atktype
    set new.dmgtype = dmgtype
    set new.dmgradius = dmgradius
    set new.dmg = dmg
    set new.has_deathdmg = has_deathdmg
    set INDEX_NUM = INDEX_NUM + 1
    set TOTAL_PROJ = TOTAL_PROJ + 1
            
    if (new.u == null) then
        call BJDebugMsg("Failed to create unit")
    else
        call BJDebugMsg("Unit created!")
    endif
            
    call GroupAddUnit(ProjGroup, new.u)
    call SetUnitInvulnerable(new.u, true)
    call UnitAddAbility(new.u, 'Aloc')
            
    if INDEX_NUM == 1 then
        call TimerStart(ProjTimer, PeriodicTime, true, function ProjectileUpdate)
    endif
            
    return new
endmethod

function SpawnProjectile takes real x, real y, real angle, player owner, string unitname, real speed, real climbrate, real maxdist, unit target, attacktype atktype, damagetype dmgtype, real dmg, real dmgradius, boolean has_deathdmg returns nothing
    call Projectile.create(x, y, angle, owner, unitname, speed, climbrate, maxdist, target, atktype, dmgtype, dmg, dmgradius, has_deathdmg)
endfunction
That's the create method of my missile struct and the other function I use to call it, whenever I create a unit then the "Failed to create unit" message pops up meaning that new.u was null. I don't know what I'm doing wrong. This is how I called the function(From a trigger):
  • test
    • Events
      • Unit - A unit Begins casting an ability
    • Conditions
    • Actions
      • Custom script: call SpawnProjectile(GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()), GetUnitFacing(GetTriggerUnit()), GetOwningPlayer(GetTriggerUnit()), "Rocket", 5, 100, 600, null, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, 100, 350, false)
I know the unit name is correct because I created a custom unit with that name:
lD4v7TM.png

Anyone can help me with this problem? :ogre_rage:
 
Last edited by a moderator:
Level 6
Joined
Feb 16, 2014
Messages
193
Ok :), I used the object id of the unit and also used CreateUnit instead of CreateUnitByName, tho whenever I try to use it then I suddenly get an error: "Double free of type: Projectile"
1rEoilo.png

It's probably a problem in my update loop, code: ( Still unfinished tho D: )
JASS:
private function ProjectileUpdate takes nothing returns nothing
    local unit u
    local unit target
    local player owner
    local real speed
    local real climbrate
    local attacktype atktype
    local real dmgradius
    local real dmg
    local boolean has_deathdmg
    local real maxdist
        
    local real x
    local real y
    local real ex
    local real ey
    local real newX
    local real newY
    local real newHeight
    local real angle
    local group dmgGroup
    local integer i = 0
    loop
        exitwhen i >= TOTAL_PROJ
        set u = ProjArray[i].u
        set target = ProjArray[i].target            
        set owner = ProjArray[i].owner
        set speed = ProjArray[i].speed
        set climbrate = ProjArray[i].climbrate
        set atktype = ProjArray[i].atktype
        set dmg = ProjArray[i].dmg
        set dmgradius = ProjArray[i].dmgradius
        set has_deathdmg = ProjArray[i].has_deathdmg
        set maxdist = ProjArray[i].maxDist
            
        set x = GetUnitX(u)
        set y = GetUnitY(u)
        set ex = GetUnitX(target)
        set ey = GetUnitY(target)
            
        if not (target == null) and (IsUnitAliveBJ(target)) then
            set angle = GetUnitFacing(u)
            call SetUnitLookAt(u, "origin", target, 0, 0, 0)
            call ProjArray[i].damageTarget()
        endif
        set angle = Rad2Deg(GetUnitFacing(u))
        set newX = x + Sin(angle) * speed
        set newY = y + Cos(angle) * speed
        call SetUnitPosition(u, newX, newY)
        set ProjArray[i].dist = ProjArray[i].dist + PeriodicTime
        if ProjArray[i].dist >= maxdist then
            call ProjArray[i].damageArea()
            call ProjArray[i].destroy()
            set ProjArray[INDEX_NUM] = ProjArray[i]
        endif
        set i = i + 1
    endloop
endfunction
Help please.
 
Last edited:
Level 23
Joined
Feb 6, 2014
Messages
2,466
It would have help a lot of you post the whole code. And if it's still unfinished, then there's your problem.
Now that message means you're destroying an already destroyed struct so your dynamic indexing is what's wrong here.
I suggest using Linked list instead for faster deallocation but if you want to use dynamic indexing so badly, read more here because your indexing appears wrong.
Also, your x and y projection is wrong and use UnitAlive, SetUnitX, and SetUnitY instead.

I hate to be that guy, but it seems like this isn't even 10% done. There's a lot of things to fix.
 
JASS:
Rad2Deg(GetUnitFacing(u))
->
Deg2Rad(GetUnitFacing(u)) or, better, GetUnitFacing(u) * bj_DEGTORAD

Store ProjArray into a variable so you don't have to keep polling the array.

If you're going to use a linear stack instead of a doubly linked list, you'll need to loop top to bottom instead of bottom to top so that when you destroy indices nothing will get skipped.
 
Level 6
Joined
Feb 16, 2014
Messages
193
It would have help a lot of you post the whole code. And if it's still unfinished, then there's your problem.
Now that message means you're destroying an already destroyed struct so your dynamic indexing is what's wrong here.
I suggest using Linked list instead for faster deallocation but if you want to use dynamic indexing so badly, read more here because your indexing appears wrong.
Also, your x and y projection is wrong and use UnitAlive, SetUnitX, and SetUnitY instead.

I hate to be that guy, but it seems like this isn't even 10% done. There's a lot of things to fix.
Ok, if linked list is better then I'll try it. But how can I make one? Do I need to make a struct like: (?)
JASS:
struct List
    // stuff...
endstruct

struct Node
    /// stuff...
endstruct
And how will I be able to loop through a linked list?
 
Dynamic indexing in vjass could look like this:

JASS:
library missletest

struct myStruct

    private static thistype array Data
    private static integer MaxIndex = 0

static method create takes nothing returns thistype
    local thistype this = thistype.allocate()
    set MaxIndex = MaxIndex + 1
    set Data[MaxIndex] = this
    returns this
endmethod

method destroy takes nothing returns nothing
    set MaxIndex = MaxIndex - 1
    call this.deallocate()
endmethod

static method onLooop takes nothing returns nothing
    local thistype data
    local integer index = 1
    loop
        exitwhen index > MaxIndex
        set data = Data[i]

        if (deindex) then
            set Data[i] = Data[MaxIndex]
            set index = index - 1
            call data.destroy()
        else
            some actions with data
        endif
    
        set index = index + 1
    endloop
endmethod

endstruct
endlibrary

You see it's similar like in the GUI dynamic indexing, but a bit cooler, because you don't need to "deindex all stuff" but only the data object itself.

Double linked list is not necessarily faster, but a bit more complex and cooler.
I found this example explaination: http://www.hiveworkshop.com/forums/triggers-scripts-269/double-linked-lists-266522/#post2692776

Choose where you feel more familar with and what you personaly prefer atm.
Later you always can change something if really needed.
 
Last edited:
Level 23
Joined
Feb 6, 2014
Messages
2,466
PurgeAndFire said:
Versus a struct linked list, allocation is faster. Iteration is relatively the same.
Deallocation is almost always slower.
I think deallocation is slower in dynamic indexing because you have to transfer all the members of the last (highest) struct to another struct that you destroyed.

@louie, but if you're not comfortable with Linked List, then I guess using Dynamic Indexing is fine. And I just assumed you used dynamic indexing since I can't see the whole code.
 
I think deallocation is slower in dynamic indexing because you have to transfer all the members of the last (highest) struct to another struct that you destroyed.
By that, I'm not very sure what you mean. Because you can just move the instance[MaxIndex] to the position of the destroyed instance as I did in the example.
You don't have to think of the members of that instance.
I'm also not sure if deallocation is really slower in this method, but if than the difference should be negligable.
 
Level 6
Joined
Feb 16, 2014
Messages
193
I changed the indexing-deindexing code but there's a new problem, whenever I use it for the first time then I get an error: "Attempt to destroy a null struct of type: Projectile". And whenever I try to use it the unit just spawns then gets removed. I don't know what's causing the problem. Tho, I can remember that a similar problem like this happened before(when I was making a GUI missile system).
Full code:
JASS:
library ProjectileSystem
    globals
        private integer INDEX_NUM = 0
        private real PeriodicTime = 0.03
        private timer ProjTimer = CreateTimer()
        private group ProjGroup = CreateGroup()
        private Projectile array ProjArray
        private constant real MIN_MAXDIST = 1
        private constant real MIN_DISTANCE_HIT = 100
    endglobals
    
    private function FilterFunc takes nothing returns boolean
        local unit fUnit = GetFilterUnit()
        if (IsUnitType(fUnit, UNIT_TYPE_GROUND) == true) and (IsUnitType(fUnit, UNIT_TYPE_STRUCTURE) == false) and (IsUnitAliveBJ(fUnit) == true) then
            return true
        endif
        return false
    endfunction
    
    private function ProjectileUpdate takes nothing returns nothing
        local Projectile this
        
        local real x
        local real y
        local real ex
        local real ey
        local real newX
        local real newY
        local real newHeight
        local real angle
        local integer i = 0
        loop
            exitwhen i > INDEX_NUM
            set this = ProjArray[i]
            set x = GetUnitX(this.u)
            set y = GetUnitY(this.u)
            set ex = GetUnitX(this.target)
            set ey = GetUnitY(this.target)
            
            if not (this.target == null) and (IsUnitAliveBJ(this.target)) then
                set angle = GetUnitFacing(this.u)
                call SetUnitLookAt(this.u, "origin", this.target, 0, 0, 0)
                call this.damageTarget()
            endif
            set angle = GetUnitFacing(this.u) * bj_DEGTORAD
            set newX = x + Sin(angle) * this.speed
            set newY = y + Cos(angle) * this.speed
            call SetUnitX(this.u, newX)
            call SetUnitY(this.u, newY)
            set this.dist = this.dist + PeriodicTime
            if this.dist >= this.maxDist then
                call this.damageArea()
                set ProjArray[i] = ProjArray[INDEX_NUM]
                set INDEX_NUM = INDEX_NUM - 1
                set i = i - 1
                call this.destroy()
            endif
            set i = i + 1
        endloop
    endfunction
    
    struct Projectile
        //---------------------------- //
        //----- Projectile Data ------ //
        unit        u
        unit        target
        player      owner
        real        speed
        real        climbrate
        attacktype  atktype
        damagetype  dmgtype
        real        dmgradius
        real        dmg
        boolean     has_deathdmg
        real        dist
        real        maxDist
        
        static method create takes real x, real y, real angle, player owner, integer unitid, real speed, real climbrate, real maxdist, unit target, attacktype atktype, damagetype dmgtype, real dmg, real dmgradius, boolean has_deathdmg returns Projectile
            local Projectile this = Projectile.allocate()
            if maxdist < MIN_MAXDIST then
                set maxdist = MIN_MAXDIST
            endif
            set this.u = CreateUnit(owner, unitid, x, y, angle)
            set this.target = target
            set this.owner = owner
            set this.speed = speed
            set this.climbrate = climbrate
            set this.atktype = atktype
            set this.dmgtype = dmgtype
            set this.dmgradius = dmgradius
            set this.dmg = dmg
            set this.has_deathdmg = has_deathdmg
            set INDEX_NUM = INDEX_NUM + 1
            set ProjArray[INDEX_NUM] = this
            
            if not (this.u == null) then
                call BJDebugMsg("Unit created!")
            else
                call BJDebugMsg("Failed to create unit.")
            endif
            
            call GroupAddUnit(ProjGroup, this.u)
            call SetUnitInvulnerable(this.u, true)
            call UnitAddAbility(this.u, 'Aloc')
            
            if INDEX_NUM == 1 then
                call TimerStart(ProjTimer, PeriodicTime, true, function ProjectileUpdate)
            endif
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            if IsUnitAliveBJ(.u) then
                call RemoveUnit(.u)
            endif
            set .u = null
            set .owner = null
            set .atktype = null
            if INDEX_NUM == -1 then
                call PauseTimer(ProjTimer)
            endif
            call this.deallocate()
        endmethod
        
        method damageTarget takes nothing returns nothing
            local location projLoc
            local location targetLoc
            set projLoc = Location(GetUnitX(.u), GetUnitY(.u))
            set targetLoc = Location(GetUnitX(.target), GetUnitY(.target))
            if (DistanceBetweenPoints(projLoc, targetLoc) <= MIN_DISTANCE_HIT) then
                call UnitDamageTargetBJ(.u, .target, .dmg, .atktype, .dmgtype)
            endif
            call RemoveLocation(projLoc)
            call RemoveLocation(targetLoc)
        endmethod
        
        method damageArea takes nothing returns nothing
            local location projLoc
            local group dmgGrp
            local unit gU
            call GroupEnumUnitsInRange(dmgGrp, GetUnitX(.u), GetUnitY(.u), .dmgradius, Filter(function FilterFunc))
            
            loop
                set gU = FirstOfGroup(dmgGrp)
                exitwhen gU == null
                if GetOwningPlayer(.u) != GetOwningPlayer(gU) then
                    call UnitDamageTargetBJ(.u, gU, .dmg, .atktype, .dmgtype)
                endif
                call GroupRemoveUnit(dmgGrp, gU)
            endloop
        endmethod
    endstruct
    
    function SpawnProjectile takes real x, real y, real angle, player owner, integer unitid, real speed, real climbrate, real maxdist, unit target, attacktype atktype, damagetype dmgtype, real dmg, real dmgradius, boolean has_deathdmg returns nothing
        call Projectile.create(x, y, angle, owner, unitid, speed, climbrate, maxdist, target, atktype, dmgtype, dmg, dmgradius, has_deathdmg)
    endfunction
endlibrary
 
Level 6
Joined
Feb 16, 2014
Messages
193
Switch these lines:

set ProjArray = ProjArray[INDEX_NUM]
set INDEX_NUM = INDEX_NUM - 1

Also, loop your stack from top to bottom instead of bottom to top. That way you don't have to do set i = i - 1 when you remove a projectile.

JASS:
local integer i = INDEX_NUM
loop
    set i = i - 1
    exitwhen i < 0
endloop

Ok, I did that plus fixed other problems that I found. When I tested it:
> Fires first projectile (Works)
> Projectile reaches maximum distance and gets removed
After that I get spammed with "Attempt to destroy a null struct of type: Projectile" and whenever I try to fire another projectile it will just sit there doing nothing. :ogre_rage: I have no idea what caused that problem :(
My new code for the ProjectileUpdate function:
JASS:
    private function ProjectileUpdate takes nothing returns nothing
        local Projectile this
        local real x
        local real y
        local real ex
        local real ey
        local real newX
        local real newY
        local real newHeight
        local real angle
        local integer i = INDEX_NUM
        call BJDebugMsg("Loop...")
        loop
            exitwhen i < INDEX_NUM
            set this = ProjArray[i]
            set x = GetUnitX(this.u)
            set y = GetUnitY(this.u)
            set ex = GetUnitX(this.target)
            set ey = GetUnitY(this.target)
            
            if (this.target != null) and (IsUnitAliveBJ(this.target)) then
                set angle = GetUnitFacing(this.u)
                call SetUnitLookAt(this.u, "origin", this.target, 0, 0, 0)
                call this.damageTarget()
            endif
            set angle = GetUnitFacing(this.u) * bj_DEGTORAD
            set newX = x + Sin(angle) * this.speed
            set newY = y + Cos(angle) * this.speed
            call SetUnitX(this.u, newX)
            call SetUnitY(this.u, newY)
            set this.dist = this.dist + this.speed
            if this.dist >= this.maxDist then
                call this.damageArea()
                set INDEX_NUM = INDEX_NUM - 1
                set ProjArray[i] = ProjArray[INDEX_NUM]
                call this.destroy()
            endif
            if INDEX_NUM == 0 then
                call BJDebugMsg("Timer paused!")
                call PauseTimer(ProjTimer)
            endif
            set i = i - 1
        endloop
    endfunction
 
Level 6
Joined
Feb 16, 2014
Messages
193
Ok, now most of the projectiles that I shoot moves and gets destroyed properly, but I don't know why that sometimes a projectile will only travel a short distance then just freeze, or sometimes a projectiles will just freeze right after it spawns o_O.
Here's my full code:
JASS:
library ProjectileSystem
    globals
        private integer INDEX_NUM = 0
        private real PeriodicTime = 0.03
        private timer ProjTimer = CreateTimer()
        private group ProjGroup = CreateGroup()
        private Projectile array ProjArray
        private constant real MIN_MAXDIST = 1
        private constant real MIN_DISTANCE_HIT = 100
    endglobals
    
    private function FilterFunc takes player owner, unit u, unit target returns boolean
        if (GetOwningPlayer(u) != GetOwningPlayer(target)) and (IsUnitType(target, UNIT_TYPE_GROUND) == true) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitAliveBJ(target) == true) then
            return true
        endif
        return false
    endfunction
    
    private function ProjectileUpdate takes nothing returns nothing
        local Projectile this
        local real x
        local real y
        local real ex
        local real ey
        local real newX
        local real newY
        local real newHeight
        local real angle
        local integer i = INDEX_NUM
        call BJDebugMsg("Total projectiles: " + I2S(INDEX_NUM))
        loop
            call BJDebugMsg("Looping for ProjArray[" + I2S(i) + "]")
            exitwhen i < 0
            set this = ProjArray[i]
            set x = GetUnitX(this.u)
            set y = GetUnitY(this.u)
            set ex = GetUnitX(this.target)
            set ey = GetUnitY(this.target)
            
            if (this.target != null) and (IsUnitAliveBJ(this.target)) then
                set angle = GetUnitFacing(this.u)
                call SetUnitLookAt(this.u, "origin", this.target, 0, 0, 0)
                call this.damageTarget()
            endif
            set angle = GetUnitFacing(this.u) * bj_DEGTORAD
            set newX = x + Sin(angle) * this.speed
            set newY = y + Cos(angle) * this.speed
            call SetUnitX(this.u, newX)
            call SetUnitY(this.u, newY)
            set this.dist = this.dist + this.speed
            if (this.dist >= this.maxDist) or (IsUnitDeadBJ(this.u) == true)then
                call BJDebugMsg("Projectile detonated!")
                call this.damageArea()
                set INDEX_NUM = INDEX_NUM - 1
                set ProjArray[i] = ProjArray[INDEX_NUM]
                call this.destroy()
            endif
            set i = i - 1
        endloop
    endfunction
    
    struct Projectile
        //---------------------------- //
        //----- Projectile Data ------ //
        unit        u
        unit        target
        player      owne
        real        speed
        real        climbrate
        attacktype  atktype
        damagetype  dmgtype
        real        dmgradius
        real        dmg
        boolean     has_deathdmg
        real        dist = 0
        real        maxDist
        
        static method create takes real x, real y, real angle, player owner, integer unitid, real speed, real climbrate, real maxdist, unit target, attacktype atktype, damagetype dmgtype, real dmg, real dmgradius, boolean has_deathdmg returns Projectile
            local Projectile this = Projectile.allocate()
            set this.u = CreateUnit(owner, unitid, x, y, angle)
            set this.target = target
            set this.owner = owner
            set this.speed = speed
            set this.climbrate = climbrate
            set this.atktype = atktype
            set this.dmgtype = dmgtype
            set this.dmgradius = dmgradius
            set this.dmg = dmg
            set this.has_deathdmg = has_deathdmg
            set this.maxDist = maxdist
            set ProjArray[INDEX_NUM] = this
            set INDEX_NUM = INDEX_NUM + 1
            
            if not (this.u == null) then
                call BJDebugMsg("Unit created!")
            else
                call BJDebugMsg("Failed to create unit.")
            endif
            
            call GroupAddUnit(ProjGroup, this.u)
            call UnitAddAbility(this.u, 'Aloc')
            
            if INDEX_NUM == 1 then
                call BJDebugMsg("Timer started!")
                call TimerStart(ProjTimer, PeriodicTime, true, function ProjectileUpdate)
            endif
            
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            call RemoveUnit(.u)
            if INDEX_NUM == 0 then
                call BJDebugMsg("Timer paused!")
                call PauseTimer(ProjTimer)
            endif
            set .u = null
            set .owner = null
            set .atktype = null
            call this.deallocate()
        endmethod
        
        method damageTarget takes nothing returns nothing
            local location projLoc
            local location targetLoc
            set projLoc = Location(GetUnitX(.u), GetUnitY(.u))
            set targetLoc = Location(GetUnitX(.target), GetUnitY(.target))
            if (DistanceBetweenPoints(projLoc, targetLoc) <= MIN_DISTANCE_HIT) then
                call UnitDamageTargetBJ(.u, .target, .dmg, .atktype, .dmgtype)
            endif
            call RemoveLocation(projLoc)
            call RemoveLocation(targetLoc)
        endmethod
        
        method damageArea takes nothing returns nothing
            local location projLoc
            local group dmgGrp
            local unit gU
            call GroupEnumUnitsInRange(dmgGrp, GetUnitX(.u), GetUnitY(.u), .dmgradius, null)
            
            loop
                set gU = FirstOfGroup(dmgGrp)
                exitwhen gU == null
                if FilterFunc(.owner, .u, gU) then
                    call UnitDamageTargetBJ(.u, gU, .dmg, .atktype, .dmgtype)
                endif
                call GroupRemoveUnit(dmgGrp, gU)
            endloop
        endmethod
    endstruct
    
    function SpawnProjectile takes real x, real y, real angle, player owner, integer unitid, real speed, real climbrate, real maxdist, unit target, attacktype atktype, damagetype dmgtype, real dmg, real dmgradius, boolean has_deathdmg returns nothing 
        call Projectile.create(x, y, angle, owner, unitid, speed, climbrate, maxdist, target, atktype, dmgtype, dmg, dmgradius, has_deathdmg)
    endfunction
endlibrary
 
Level 6
Joined
Feb 16, 2014
Messages
193
It works :D I also fixed the move code:
JASS:
set newX = x + this.speed * Cos(angle)
set newY = y + this.speed * Sin(angle)
Thanks guys for the help :D
 
Status
Not open for further replies.
Top