• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Trigger] Map Huge Lag (Can't find issue)

Status
Not open for further replies.

sentrywiz

S

sentrywiz

First of all let me say that this is a map that needs checking
and there are lot of triggers. The problem persists over-time
and the map lags until its unplayable.

Map Link: Weapons Arena

I asked people to check my missile triggers since this map has
skillshot spells in it but they reported that the triggers were
okay, no leaks or anything.



So MAJOR credits and rep to anyone who can help solve this.
The map is open so no permissions are necessary.

I really invested time and effort in making all the custom systems
good. Map has item pickup, masteries, upgrades, skillshots and
something like lag kills it. Its sad really :(
 
Level 25
Joined
May 11, 2007
Messages
4,651
I usually use the Tree of Life Upgrade Art for my dummy units, could it be that the bugger model doesn't have any death animation and thus stays in the world forever?

Tree of Life model:
Abilities\Spells\NightElf\TreeofLifeUpgrade\TreeofLifeUpgradeTargetArt.mdl

Same for the weapon models, etc.
 

sentrywiz

S

sentrywiz

EDIT: All of the dummy units including weapon models and pickup models have "Can't raise, doesn't decay"

What does the Tree of Life upgrade has to do with dummy staying in world forever?

I also kill all the dummies of course.
 
It would be easier if you posted the triggers in this thread, most people will not be bothered to download and open your map to check.

With missile systems, you often need to do a bunch of optimizations in order to make them work fluently. If you have triggers running once every 0.03 seconds, you may otherwise hit operation limit - this can be something that piles up for a period of time. If you are using GUI triggers, your system has inherent problems with optimization, but there are other things you could fix.

For starters, creating units is one of the most costly operations you can do in warcraft, and it also causes leaks you cannot fix. If you need to have a lot of units created and destroyed, i recommend you use a recycling approach instead. For instance, you can have a group which contains unused dummies. Whenever you need a dummy, you check if the group is empty - if it is, create a new one, otherwise, pick one of the unused dummies and remove it from the group. When you are done with it, add it to the group again. This way, you will only have to create as many dummy units as you would have at one point maximum, and it is much faster.

Second, try keeping trigonometry to a minimum. Sin and Cos functions are slow, and so are square roots. If you ever have to use the distance formula to find out if a unit is in range, try comparing the square distance with a square range value instead. Instead of trigonometry, try to learn linear algebra and vectors to work in carthesian space.



EDIT: Forget everything i just said, i tried your map, and the lag piles up as you fire more missiles, not as the game progresses it seems (type /fps in chat to see framerate). I thought the reason was that spears were not being removed from the group, thus resulting in ghost handles, but i added some debug text and found that this was not the case. The trigger is being disabled just as it should. I will come back to you once i have done some more testing.
 
Last edited:

sentrywiz

S

sentrywiz

It would be easier if you posted the triggers in this thread, most people will not be bothered to download and open your map to check.

With missile systems, you often need to do a bunch of optimizations in order to make them work fluently. If you have triggers running once every 0.03 seconds, you may otherwise hit operation limit - this can be something that piles up for a period of time. If you are using GUI triggers, your system has inherent problems with optimization, but there are other things you could fix.

For starters, creating units is one of the most costly operations you can do in warcraft, and it also causes leaks you cannot fix. If you need to have a lot of units created and destroyed, i recommend you use a recycling approach instead. For instance, you can have a group which contains unused dummies. Whenever you need a dummy, you check if the group is empty - if it is, create a new one, otherwise, pick one of the unused dummies and remove it from the group. When you are done with it, add it to the group again. This way, you will only have to create as many dummy units as you would have at one point maximum, and it is much faster.

Second, try keeping trigonometry to a minimum. Sin and Cos functions are slow, and so are square roots. If you ever have to use the distance formula to find out if a unit is in range, try comparing the square distance with a square range value instead. Instead of trigonometry, try to learn linear algebra and vectors to work in carthesian space.

There are a lot of triggers to post, I'd have the same result if I copied all triggers here. If people don't want to bother they won't bother checking out the triggers either. So its just easier for me to link the map. If someone genuinely wants to assist me, they'd have no issue downloading and opening the map.

That's the thing, I clean my leaks and destroy my dummies. I have taken as many precautions as I can think of. Though, it still piles up and lags.

That's why I asked for help: I cannot find the issue.

I don't even use trigonometry, no sin, cos or square root functions.
 
Yes, i noticed your triggers are all very well made. The map seems really fun overall, kinda like medieval toy soldiers.

Do you ever remove units from forbidden_group? Because CountUnitsInGroup loops through all the units in the group when it counts them, so you could once again have issues with ghost handles. EDIT: I noticed you do. Nevermind. Just keep adding debug messages controling stuff, or you can try to disable parts of your code one by one until you find what is wrong. For instance, you could try removing the part of the spear script where it checks for nearby enemies (make a backup copy first ofcourse), and try if it still generates lag.
 

sentrywiz

S

sentrywiz

Yes, i noticed your triggers are all very well made. The map seems really fun overall, kinda like medieval toy soldiers.

Do you ever remove units from forbidden_group? Because CountUnitsInGroup loops through all the units in the group when it counts them, so you could once again have issues with ghost handles. EDIT: I noticed you do. Nevermind. Just keep adding debug messages controling stuff, or you can try to disable parts of your code one by one until you find what is wrong. For instance, you could try removing the part of the spear script where it checks for nearby enemies (make a backup copy first ofcourse), and try if it still generates lag.

Thanks for taking time to check out the map and try see what's wrong.

Thank you, I do try to keep my triggers neat and clean.

Well, that's the thing, I don't know where the issue might be. I do add debug messages, but its difficult when you don't know what you are looking for and hoping that the map will "crash" or start to lag when a debug message is shown.

Still, thanks. I'll keep trying. Do tell if you find something. +rep for trying

It would be easier if you posted the triggers in this thread, most people will not be bothered to download and open your map to check.

With missile systems, you often need to do a bunch of optimizations in order to make them work fluently. If you have triggers running once every 0.03 seconds, you may otherwise hit operation limit - this can be something that piles up for a period of time. If you are using GUI triggers, your system has inherent problems with optimization, but there are other things you could fix.

For starters, creating units is one of the most costly operations you can do in warcraft, and it also causes leaks you cannot fix. If you need to have a lot of units created and destroyed, i recommend you use a recycling approach instead. For instance, you can have a group which contains unused dummies. Whenever you need a dummy, you check if the group is empty - if it is, create a new one, otherwise, pick one of the unused dummies and remove it from the group. When you are done with it, add it to the group again. This way, you will only have to create as many dummy units as you would have at one point maximum, and it is much faster.

Second, try keeping trigonometry to a minimum. Sin and Cos functions are slow, and so are square roots. If you ever have to use the distance formula to find out if a unit is in range, try comparing the square distance with a square range value instead. Instead of trigonometry, try to learn linear algebra and vectors to work in carthesian space.



EDIT: Forget everything i just said, i tried your map, and the lag piles up as you fire more missiles, not as the game progresses it seems (type /fps in chat to see framerate). I thought the reason was that spears were not being removed from the group, thus resulting in ghost handles, but i added some debug text and found that this was not the case. The trigger is being disabled just as it should. I will come back to you once i have done some more testing.

EDIT: I see, so the lag is most likely at the missile spells or possibly my handling of them.

DOUBLE EDIT: I didn't know of the /fps command. So many years of wc3 and not knowing that I feel silly.

The game fps was around 64 when using all weapons. But when I started using the throwing knives the rate went down to 47 and that's when I hit alt f4. But I made debug messages about units. No units go uncleaned, so its not about leaving dummy units behind or whatever. But after about 3-4 minutes of using the Throwing Knives, fps goes down and keeps going down when using the knives. No other weapon makes the game goes down in fps

So I still don't know the issue but its around the knives I believe...
 
Last edited by a moderator:
Strage, i noticed fps going down even when only using throwing spears. It seems like it is limited to missile weapons though. About what i mentioned with creating units being slow: this only applies if you are creating units every 0.03 seconds, so it is not a problem here. They only way your code could leak at the magnitude we see is if something in the periodic loop which moves the missiles leaks. It seems unlikely that you would get problems with operation limit too since you are clever enough to disable the trigger when there are no missiles in flight.

EDIT: I found a leak. In "Sword Hit", you loop through the sword_group, and set loc_move = position of picked unit in each loop - but you don't remove it until the loop is done. If there is more than one unit, all locations except the last will leak. You need to put the DestroyLocation inside the loop at the end. Stay put for more.

EDIT 2: In the last "else" of "Move Ball", you remove the moving_ball unit before you remove it from staff_group.

EDIT 3: I did some testing with nhocklanhox6's memory leak checker, and it claims your main culprit is location leaks, and that they originate from your group loops. This leak checker can not always be trusted, but it registered some 400 location leaks after a few minutes of gameplay, so i would say it is safe to assume this is it. The leaks appear while missiles are in motion. Hope this will set you straight.


If this was vJass, it would be so easy to solve. You should try out learning it, it is not as hard as it looks from the stuff you get when converting GUI, and i can help you if you want.
 
Last edited:

sentrywiz

S

sentrywiz

Strage, i noticed fps going down even when only using throwing spears. It seems like it is limited to missile weapons though. About what i mentioned with creating units being slow: this only applies if you are creating units every 0.03 seconds, so it is not a problem here. They only way your code could leak at the magnitude we see is if something in the periodic loop which moves the missiles leaks. It seems unlikely that you would get problems with operation limit too since you are clever enough to disable the trigger when there are no missiles in flight.

EDIT: I found a leak. In "Sword Hit", you loop through the sword_group, and set loc_move = position of picked unit in each loop - but you don't remove it until the loop is done. If there is more than one unit, all locations except the last will leak. You need to put the DestroyLocation inside the loop at the end. Stay put for more.

EDIT 2: In the last "else" of "Move Ball", you remove the moving_ball unit before you remove it from staff_group.

EDIT 3: I did some testing with nhocklanhox6's memory leak checker, and it claims your main culprit is location leaks, and that they originate from your group loops. This leak checker can not always be trusted, but it registered some 400 location leaks after a few minutes of gameplay, so i would say it is safe to assume this is it. The leaks appear while missiles are in motion. Hope this will set you straight.


If this was vJass, it would be so easy to solve. You should try out learning it, it is not as hard as it looks from the stuff you get when converting GUI, and i can help you if you want.

Awesome.

1: Fixed.

2: Fixed. I also found the same in all missile triggers, spear, ball and knife. Now its first remove from group then remove the unit.

3: I will re-check all the groups and see if I am doing something else that fails on logic too.

Ironically I started learning jass a few days ago and found a good tutorial on vJass and I want to learn vJass now because its awesome. Can you suggest a good program, I already have JassCraft but I heard it doesn't support vJass, just ordinary Jass.
 
Just download Jass Newgen Pack, and update JassHelper as instructed. It has great highlighting, a function list with search function, and you can see the API for any function by ctrl-clicking it.

I am going to make a short example/tutorial on you on how you could make this script in vJass, stay put.

EDIT: Alright, let's go.

The most central part about vJass is that it introduces something called Object Oriented Programming. I am going to explain what this means.

A great example of an object is how units are handled in warcraft. A unit is basically a collection of data which has some functionality associated with it. You can extort information from it, such as position and facing, and you can use different methods to manipulate it, such as ordering it to move here, face there, etc. vJass allows you to replicate this.

Firstly, to keep your code sorted, you use something called libraries. A library is kinda like what paper is for a book, and you can use them to decide in what order your code should be compiled. You define your libraries much like you do with everything in jass, you use a start and end block which you paste inside an empty trigger:

JASS:
library Missiles
//CODE GOES HERE
endlibrary

I will explain more about libraries later, for now all you need to know is that it is the page on which you write your code. Next, you may want to define some global variables. You can still do that in the variable editor, but using a global block gets rid of the ugly "udg_" prefixes:

JASS:
library Missiles
    globals
        private constant real INTERVAL = 0.03
    endglobals
endlibrary

There are a few keywords here you might want to note. "Private" means that this global can only be accessed by functions inside this library! This is called encapsulation and is a good way to protect your code from yourself, making sure that things can only be accessed in a context where they are needed. "Constant" means that the variable is fixed to the value you assigned to it and cannot be changed ingame. It may be hard to see the point of this, but it is good coding practice to point out which data is meant to be changed and which is not. Constant variable names are written in upper case to clearify.

Next, we have the structs. We can start by defining a struct and some of its members we might find useful:

JASS:
library Missiles
    globals
        private constant real INTERVAL = 0.03
    endglobals
    
    struct missile

        unit dummy
        effect sfx
        
        real x = 0
        real y = 0
        
        real vel_x = 0
        real vel_y = 0
    
    endstruct
endlibrary

This means that each missile will have a unit, an sfx, coordinates for x and y, and values for its velocity. To make a new missile, we need a constructor method, which is assigned like this:

JASS:
library Missiles
    globals
        private constant real INTERVAL = 0.03
        private constant integer DUMMY_ID = 'dumm'
    endglobals
    
    struct missile
    
        unit dummy
        effect sfx
        
        real x = 0
        real y = 0
        
        real vel_x = 0
        real vel_y = 0
        
        static method create takes real x, real y, real facing, real velocity, player owner returns missile
            local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
            
            set .dummy = CreateUnit(owner, DUMMY_ID, x, y, facing)
            set .x = x
            set .y = y
            
            set .vel_x = velocity * Cos(angle*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
            set .vel_y = velocity * Sin(angle*bj_DEGTORAD)
            
            return this
        endmethod
    
    endstruct
endlibrary

There is a lot to explain here - lets start from the top. "Static" is a keyword which means that this function does not refer to any specific missile. You will understand this once i explain normal methods, and you will understand how the constructor cannot refer to a specific missile since it creates them. If you have studied math, you are familiar with how functions work - you input some numbers or data, and get something in return, like the case y(x) = 2x where you input x and get y in return. In this case, you input coordinates, owner, etc and get a missile in return.

At the head of the function you have what we call a local variable. Locals exist only within the function you created them, so this minimises risk of other parts of your code interfering with their values. In this case we are declaring a variable called "this" for reasons i will explain later - the next function, called "allocate", gives it a unique ID. You will notice that we can now use "missile" as a variable type, same as you would use "real" or "unit", since it is now a class of its own. Members of a struct are refered to inside methods by placing a dot before them. Outside the method, you will have to place the variable name before the dot, like this:

JASS:
local missile m = missile.create(0, 0, 0, 0, Player(0))

set m.x = 320 //YOU CAN SET THE COORDINATES OF THE MISSILES LIKE THIS
set m.y = 560

Anyhow, there is some more functionality we need to add to our missile before it is done. For instance, we need to decide what is done with the missiles when they die and we no longer need them. To do this, we add something called a destructor:

JASS:
method onDestroy takes nothing returns nothing
    call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
            
    if .sfx != null then
        call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
    endif
endmethod

The != sign means "not equal to". There is lots more stuff you could learn in other jass tutorials, but suffice to say this method cleans all the leaks and frees the ID so that another instance can use it. Since structs are basically just arrays with some make-up on them, and the ID of each struct is just the array index, we are limited by the same things as arrays, such as having a maximum of 8191 instances. Therefore it is important to destroy instances when we are done with them.

Lets now say we want to have a way of looping through all misiles on the map, in order to make them move or check if they have hit a wall. To do this, we can use a clever construct known as a linked list. What it means is basically that each instance of your struct has a "prev" and "next" member, which points to the struct above and below it in the list. Think of it as a chain, where each instance is a link. We also have a "first" and possibly a "last" member. Thankfully, someone else made an easy way to implement this into any struct: ListModule. Just paste it into an empty trigger in your map.

I am gonna increase the pace now, but i trust that you can learn the basics from other sources, such as the jasshelper manual which you can find in the install directory of Jass Newgen Pack. I added an update method for our struct, and an initializer function to our library. Initializers work just like triggers that run on initialization - you can use them to do stuff on startup in order to set up your system. Inside it, i created a timer, which we will use to periodically run the update function. I also added a region called "Walls".

Now, a region in jass is not the same as a region in GUI, which we in jass just call a rect. While a rect can only be a single rectangle, a region is bitmap-based and can include multiple rectangles - this means we can register all our wall rects into a single region, and then simply check if a point is inside this region to see if it is inside a wall!! Genious!

JASS:
library Missiles initializer Init requires ListModule
    globals
        private constant real INTERVAL = 0.03
        private constant integer DUMMY_ID = 'dumm' //THE UNIT ID OF THE DUMMY UNIT. YOU CAN ALSO ADD THIS AS A PARAMETER TO THE FUNCTION IF YOU WANT TO USE SEVERAL DUMMIES. 
        private timer UpdateTimer
        private region Walls
    endglobals
    
    struct missile
    
        implement List //THIS ADDS THE LIST MEMBERS INTO OUR STRUCT
    
        unit dummy
        effect sfx
        
        real x = 0
        real y = 0
        
        real vel_x = 0
        real vel_y = 0
        
        static method update takes nothing returns nothing
            local missile this = .first //RETRIEVES THE FIRST INSTANCE IN THE LIST. 
            local missile temp
        
            if .count == 0 then //THIS MEMBER BECOMES INCLUDED WHEN YOU USE THE LINKED LIST LIBRARY
                call PauseTimer(GetExpiredTimer()) //PAUSE IF THERE ARE NO MISSILES
            else
                loop
                    exitwhen this == 0 //EXIT WHEN THERE IS NO NEXT INSTANCE
                    set temp = .next //FOR SAFETY, SINCE WE ARE REMOVING INSTANCES WITHIN THE LOOP
                    
                    set .x = .x + .vel_x //ADD VELOCITY TO POSITION
                    set .y = .y + .vel_y
                    
                    call SetUnitX(.dummy, .x) //UPDATE THE POSITION OF THE DUMMY
                    call SetUnitY(.dummy, .y)
                    
                    if IsPointInRegion(Walls, .x, .y) then //IF THE MISSILE IS INSIDE A WALL, DESTROY IT!
                        call .destroy()
                    endif
                    
                    
                    set this = temp //SET 'THIS' TO BE THE NEXT INSTANCE IN THE LIST
                endloop
            endif
        endmethod
        
        static method create takes real x, real y, real facing, real velocity, player owner returns missile
            local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
            
            call .listAdd() //THIS REGISTERS THAT WE HAVE A NEW LINK IN THE CHAIN
            
            set .dummy = CreateUnit(owner, DUMMY_ID, x, y, facing)
            set .x = x
            set .y = y
            
            set .vel_x = velocity * Cos(angle*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
            set .vel_y = velocity * Sin(angle*bj_DEGTORAD)
            
            if .count == 1 then //IF THERE USED TO BE NO INSTANCES, WE NEED TO START THE TIMER AGAIN
                call TimerStart(UpdateTimer, INTERVAL, true, function missile.update)
            endif
            
            return this
        endmethod
        
        method onDestroy takes nothing returns nothing
            call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
            
            if .sfx != null then
                call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
            endif
            
            call .listRemove() //MAKE SURE THIS INSTANCE IS NO LONGER A PART OF THE CHAIN
        endmethod
    
    endstruct
    
    private function Init takes nothing returns nothing
        set UpdateTimer = CreateTimer()
        set Walls = CreateRegion()
        
        call RegionAddRect(Walls, gg_rct_Wall1) //ADD ALL YOUR WALLS LIKE THIS
        call RegionAddRect(Walls, gg_rct_Wall2)
        call RegionAddRect(Walls, gg_rct_Wall3)
        //....
    endfunction
endlibrary

Next is to deal damage to any enemies we encounter. You will find that we now need some more data, like what type of missile we are dealing with, and how much damage it does. For this, we can simply create a "missiletype" struct to contain this information!

Now we have a code which looks something like this:

JASS:
library Missiles initializer Init requires ListModule
    globals
        private constant real INTERVAL      = 0.03
        private constant real RADIUS        = 100 //THE RADIUS OF COLLISION
        private constant integer FIGHTER_ID = 'figh' //UNIT ID OF THE PLAYER UNIT
        
        private timer UpdateTimer
        private region Walls
        
        missiletype Spear
        missiletype Dagger
    endglobals
    
    struct missiletype 
        real damage = 10 //IN THIS CASE, 10 IS JUST A DEFAULT VALUE. I JUST WANTED TO DEMONSTRATE THAT YOU CAN ASSIGN DEFAULT VALUES. 
        real max_age = 5 //NUMBER OF SECONDS BEFORE THE MISSILE IS AUTOMATICALLY DESTROYED, THE 'RANGE' OF THE MISSILE
        real velocity //I MOVED THIS VARIABLE HERE SINCE IT MADE MORE SENSE
        integer dummy_id //I ADDED THIS AS A MEMBER SINCE I KNOW YOU USE UNIQUE DUMMIES FOR EACH MISSILE TYPE
        
        static method create takes real dmg, real lifetime, real id, real vel returns missiletype
            local missiletype this  = missiletype.allocate()
            set .damage             = dmg
            set .max_age            = lifetime
            set .dummy_id           = id
            set .velocity           = vel * INTERVAL //I MULTIPLY IT BY THE LOOP RATE SO THAT YOU CAN USE REGULAR WARCRAFT SPEEDS FOR THIS FIELD
            return this
        endmethod
    endstruct
    
    struct missile
    
        implement List //THIS ADDS THE LIST MEMBERS INTO OUR STRUCT
        
        missiletype class //WE NEED TO STORE THE MISSILETYPE INSIDE THE MISSILE
    
        unit dummy
        effect sfx
        
        real age = 0 //THIS IS THE EXPIRATION TIME OF THE MISSILE
        
        real x = 0
        real y = 0
        
        real vel_x = 0
        real vel_y = 0
        
        static method targetFilter takes nothing returns boolean
            //HERE YOU PUT THE CONDITIONS FOR WHICH TARGETS ARE ALLOWED. 
            return GetUnitTypeId(GetFilterUnit()) == FIGHTER_ID //RETURNS TRUE IF UNIT IS A FIGHTER, FALSE IF NOT. 
        endmethod
        
        method hit takes unit u returns nothing
            call UnitDamageTarget(.dummy, u, .class.damage, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //ADD ANY EFFECTS YOU NEED
            call .destroy()
        endmethod
        
        static method update takes nothing returns nothing
            local missile this = .first //RETRIEVES THE FIRST INSTANCE IN THE LIST. 
            local missile temp
            local group g = CreateGroup()
        
            if .count == 0 then //THIS MEMBER BECOMES INCLUDED WHEN YOU USE THE LINKED LIST LIBRARY
                call PauseTimer(GetExpiredTimer()) //PAUSE IF THERE ARE NO MISSILES
            else
                loop
                    exitwhen this == 0 //EXIT WHEN THERE IS NO NEXT INSTANCE
                    set temp = .next //FOR SAFETY, SINCE WE ARE REMOVING INSTANCES WITHIN THE LOOP
                    
                    set .x = .x + .vel_x //ADD VELOCITY TO POSITION
                    set .y = .y + .vel_y
                    
                    call SetUnitX(.dummy, .x) //UPDATE THE POSITION OF THE DUMMY
                    call SetUnitY(.dummy, .y)
                    
                    set .age = .age - INTERVAL //IF THE LOOP RUNS EVERY 1/30 SECONDS, WE SUBSTRACT 1/30 FROM THE AGE EVERY LOOP
                    
                    if .age < 0 then
                        call .destroy() //DESTROY IT IF TIME RUNS OUT
                    endif
                    
                    if IsPointInRegion(Walls, .x, .y) then //IF THE MISSILE IS INSIDE A WALL, DESTROY IT!
                        call .destroy()
                    endif
                    
                    call GroupEnumUnitsInRange(g, .x, .y, RADIUS, Filter(function missile.targetFilter)) //GROUP ALL UNITS WITHIN THE RADIUS
                    if FirstOfGroup(g) != null then //IF THE GROUP IS NOT EMPTY
                        call .hit(FirstOfGroup(g)) //CALL THE DAMAGE METHOD ON THE FIRST UNIT WE HIT
                    endif
                    call GroupClear(g) //EMPTY THE GROUP, FOR NEXT LOOP.
                    
                    
                    set this = temp //SET 'THIS' TO BE THE NEXT INSTANCE IN THE LIST
                endloop
            endif
            
            call DestroyGroup(g) //CLEAN GROUP LEAK
            set g = null //YOU NEED TO NULL THE HANDLES OF LOCAL VARIABLES, OTHERWISE THEY LEAK
        endmethod
        
        static method create takes missiletype m, real x, real y, real facing, player owner returns missile
            local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
            
            call .listAdd() //THIS REGISTERS THAT WE HAVE A NEW LINK IN THE CHAIN
            
            set .dummy = CreateUnit(owner, DUMMY_ID, x, y, facing)
            set .class = m
            set .age = .class.max_age
            
            set .x = x
            set .y = y
            
            set .vel_x = .class.velocity * Cos(angle*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
            set .vel_y = .class.velocity * Sin(angle*bj_DEGTORAD)
            
            if .count == 1 then //IF THERE USED TO BE NO INSTANCES, WE NEED TO START THE TIMER AGAIN
                call TimerStart(UpdateTimer, INTERVAL, true, function missile.update)
            endif
            
            return this
        endmethod
        
        method onDestroy takes nothing returns nothing
            call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
            
            if .sfx != null then
                call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
            endif
            
            call .listRemove() //MAKE SURE THIS INSTANCE IS NO LONGER A PART OF THE CHAIN
        endmethod
    
    endstruct
    
    private function Init takes nothing returns nothing
        set UpdateTimer = CreateTimer()
        set Walls = CreateRegion()
        
        set Spear  = missiletype.create(18, 2.5, 'dagg', 900)
        set Dagger = missiletype.create(10, 1.8, 'spea', 900)
        
        call RegionAddRect(Walls, gg_rct_Wall1) //ADD ALL YOUR WALLS LIKE THIS
        call RegionAddRect(Walls, gg_rct_Wall2)
        call RegionAddRect(Walls, gg_rct_Wall3)
        //....
    endfunction
endlibrary

To create a missile, you now only need to call this function:

JASS:
//PUT THIS STUFF INSIDE A TRIGGER THAT FIRES WHEN A UNIT CASTS THE SPEAR SPELL:
local unit u = GetTriggerUnit()
call missile.create(Spear, GetUnitX(u), GetUnitY(u),GetUnitFacing(u), GetOwningPlayer(u))
set u = null

There are ofcourse still many things you could include which i will not explain here. For instance, it is not hard to make projectiles bounce, or to add air friction (simply multiply vel_x and vel_y with 0.98 in the update loop), or to adjust the height of the missile - right now it has a constant height which might look weird if you are shooting it off a cliff. Adding gravity is just as simple as adding a vel_z member and substracting it with a constant number each update loop. You are free to ask any questions about these things, but i thought you might want to get more into jass before we dig any deeper.

Good luck!
 
Last edited:

sentrywiz

S

sentrywiz

Just download Jass Newgen Pack, and update JassHelper as instructed. It has great highlighting, a function list with search function, and you can see the API for any function by ctrl-clicking it.

I am going to make a short example/tutorial on you on how you could make this script in vJass, stay put.

I downloaded JNGP and tried the script you said about finding leaks. There are lot of leaks yes but I still don't know where exactly they originate.

I tried the -lf and -lc commands. The tool is great for finding leaks. Its definitely the groups and locations, that's where most of the leaks are. It also said the effects leak, but I destroy each of those, not sure why they leak.
 
Alright, so i just posted a huge sort of tutorial in the post above. It might be a bit much at the same time, but i just want to show you how the code structure looks for a system like this, i trust that you can learn the basics through other tutorials. Once you learn how to understand it you will find it is much more readable and intuitive than the mess you have to construct with GUI triggers, and several times faster aswell.
 

sentrywiz

S

sentrywiz

I know what OOP is :)

But thanks anyway. That's a nice bunch of vJass you posted there. I'll take a good look at it.

Ok, so the big vjass code goes in a separate trigger and then I just make one trigger per missile (one for knives, one for staff and one for spear) right?

Is this a full solution and I just need to edit the effects, the damage etc? Or is this just to show how it will look?
 
Well, i have not tested it yet, but it should work, except you need to change DUMMY_ID with .class.dummy_id in the part where you create the unit (forgot to change that), and "angle" with "facing" in the Cos and Sin functions. You also need to change the unit IDs to the ones you have in your map, you can find them by pressing ctrl+D (as you propably know). Paste the missile library in one empty trigger, and LinkedList in another.

You simply make a trigger which fires when a unit uses the "spear throw" ability, and in the actions, add this:

JASS:
local unit u = GetTriggerUnit()
call missile.create(Spear, GetUnitX(u), GetUnitY(u), GetUnitFacing(u), GetOwningPlayer(u))
set u = null

And the same for all other spells.

I made some changes to accomodate for velocity on the Z axis, so that you can use gravity. This also corrects the missiles height when it is thrown off a ledge etc so that its fly height makes sense. I also added a flag for wether or not the missile class uses pathing - so that you can keep your frost wand shooting through walls. Not sure if it will look weird or not though. It is quite easy to add effects to this, such as making the missile semi-transparent and tinted when it is passing the wall, or to hide it alltogether until it comes out the other end. Just experiment and you will see. It is also possible to add a flag for wether missiles are affected by gravity, but you can add that yourself if you need it. Here is the new code:

JASS:
library Missiles initializer Init requires ListModule
    globals
        private constant real INTERVAL      = 0.03 //SYSTEM UPDATE INTERVAL
        private constant real RADIUS        = 100 //THE RADIUS OF COLLISION
        private constant real GRAVITY       = 9.82 //ADJUST TO YOUR NEED
        private constant integer FIGHTER_ID = 'figh' //UNIT ID OF THE PLAYER UNIT, CHANGE TO YOUR NEED
        
        private timer UpdateTimer
        private region Walls
        private location Loc
        
        missiletype Spear
        missiletype Dagger
    endglobals
    
    private function GetTerrainZ takes real x, real y returns real
        call MoveLocation(Loc,x,y)
        return GetLocationZ(Loc)
    endfunction
    
    struct missiletype 
        real damage = 10 //IN THIS CASE, 10 IS JUST A DEFAULT VALUE. I JUST WANTED TO DEMONSTRATE THAT YOU CAN ASSIGN DEFAULT VALUES. 
        real max_age = 5 //NUMBER OF SECONDS BEFORE THE MISSILE IS AUTOMATICALLY DESTROYED, THE 'RANGE' OF THE MISSILE
        real velocity //I MOVED THIS VARIABLE HERE SINCE IT MADE MORE SENSE
        integer dummy_id //I ADDED THIS AS A MEMBER SINCE I KNOW YOU USE UNIQUE DUMMIES FOR EACH MISSILE TYPE
        boolean usePathing = true
        
        static method create takes real dmg, real lifetime, real id, real vel, boolean pathing returns missiletype
            local missiletype this  = missiletype.allocate()
            set .damage             = dmg
            set .max_age            = lifetime
            set .dummy_id           = id
            set .velocity           = vel * INTERVAL //I MULTIPLY IT BY THE LOOP RATE SO THAT YOU CAN USE REGULAR WARCRAFT SPEEDS FOR THIS FIELD
            set .usePathing         = pathing
            return this
        endmethod
    endstruct
    
    struct missile
    
        implement List //THIS ADDS THE LIST MEMBERS INTO OUR STRUCT
        
        missiletype class //WE NEED TO STORE THE MISSILETYPE INSIDE THE MISSILE
    
        unit dummy
        effect sfx //THIS MEMBER MIGHT BE USELESS... JUST ADDED INCASE YOU WANT TO ATTACH STUFF TO YOUR MISSILE
        
        real age = 0 //THIS IS THE EXPIRATION TIME OF THE MISSILE
        
        real x = 0
        real y = 0
        real z = 0
        
        real vel_x = 0
        real vel_y = 0
        real vel_z = 0 //WE NOW HAVE VELOCITY ON THE Z AXIS!
        
        static method targetFilter takes nothing returns boolean
            //HERE YOU PUT THE CONDITIONS FOR WHICH TARGETS ARE ALLOWED. 
            return GetUnitTypeId(GetFilterUnit()) == FIGHTER_ID //RETURNS TRUE IF UNIT IS A FIGHTER, FALSE IF NOT. 
        endmethod
        
        method hit takes unit u returns nothing
            call UnitDamageTarget(.dummy, u, .class.damage, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //ADD ANY EFFECTS YOU NEED, SUCH AS FLOATING TEXT AND BLOOD
            call .destroy()
        endmethod
        
        static method update takes nothing returns nothing
            local missile this = .first //RETRIEVES THE FIRST INSTANCE IN THE LIST. 
            local missile temp
            local group g = CreateGroup()
            local real tz
        
            if .count == 0 then //THIS MEMBER BECOMES INCLUDED WHEN YOU USE THE LINKED LIST LIBRARY
                call PauseTimer(GetExpiredTimer()) //PAUSE IF THERE ARE NO MISSILES
            else
                loop
                    exitwhen this == 0 //EXIT WHEN THERE IS NO NEXT INSTANCE
                    set temp = .next //FOR SAFETY, SINCE WE ARE REMOVING INSTANCES WITHIN THE LOOP
                    
                    set .vel_z = .vel_z-GRAVITY //ADD GRAVITY TO VELOCITY, REMOVE IF YOU WANT THE MISSILE TO TRAVEL IN A STRAIGHT LINE. 
                    
                    set .x = .x + .vel_x //ADD VELOCITY TO POSITION
                    set .y = .y + .vel_y
                    set .z = .z + .vel_z
                    
                    set tz = GetTerrainZ(.x, .y) //GET THE TERRAIN HEIGHT AT THESE COORDINATES
                    
                    call SetUnitX(.dummy, .x) //UPDATE THE POSITION OF THE DUMMY
                    call SetUnitY(.dummy, .y)
                    call SetUnitFlyHeight(.dummy, .z-tz, 0) //WE NEED TO ACCOUNT FOR TERRAIN HEIGHT WHEN SETTING FLY HEIGHT
                    
                    set .age = .age - INTERVAL //IF THE LOOP RUNS EVERY 1/30 SECONDS, WE SUBSTRACT 1/30 FROM THE AGE EVERY LOOP
                    
                    if .age < 0 then
                        call .destroy() //DESTROY IT IF TIME RUNS OUT
                    endif
                    
                    if .z < tz then //IF THE MISSILE HITS THE GROUND, DESTROY IT!
                        call .destroy()
                    endif
                    
                    if IsPointInRegion(Walls, .x, .y) and .class.usePathing then //IF THE MISSILE IS INSIDE A WALL, DESTROY IT!
                        call .destroy() //BUT ONLY IF THE MISSILE USES PATHING, UNLIKE THE ICE WAND
                    endif
                    
                    call GroupEnumUnitsInRange(g, .x, .y, RADIUS, Filter(function missile.targetFilter)) //GROUP ALL UNITS WITHIN THE RADIUS
                    if FirstOfGroup(g) != null then //IF THE GROUP IS NOT EMPTY
                        call .hit(FirstOfGroup(g)) //CALL THE DAMAGE METHOD ON THE FIRST UNIT WE HIT
                    endif
                    call GroupClear(g) //EMPTY THE GROUP, FOR NEXT LOOP.
                    
                    
                    set this = temp //SET 'THIS' TO BE THE NEXT INSTANCE IN THE LIST
                endloop
            endif
            
            call DestroyGroup(g) //CLEAN GROUP LEAK
            set g = null //YOU NEED TO NULL THE HANDLES OF LOCAL VARIABLES, OTHERWISE THEY LEAK
        endmethod
        
        static method create takes missiletype m, real x, real y, real z, real facing, player owner returns missile
            local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
            
            call .listAdd() //THIS REGISTERS THAT WE HAVE A NEW LINK IN THE CHAIN
            
            set .dummy = CreateUnit(owner, .class.dummy_id, x, y, facing)
            set .class = m
            set .age = .class.max_age
            
            set .x = x
            set .y = y
            set .z = z + GetTerrainZ(x,y) //WE NEED Z IN ABSOLUTE COORDINATES, FROM MAP FLOOR, NOT TERRAIN FLOOR
            
            call UnitAddAbility(.dummy, 'Amrf') //THIS IS JUST A SMALL HACK WE CAN USE TO ENABLE US TO SET THE FLY HEIGHT OF UNITS WITH 'GROUND' MOVEMENT TYPE:
            call UnitRemoveAbility(.dummy, 'Amrf') //UNITS WITH GROUND MOVEMENT TYPE ACT MUCH MORE RELIABLY THAN THOSE WITH AIR MOVEMENT, SO WE WANT TO USE THEM. 
            
            call SetUnitFlyHeight(.dummy, z, 0) //THIS SPEAKS FOR ITSELF
            
            set .vel_x = .class.velocity * Cos(facing*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
            set .vel_y = .class.velocity * Sin(facing*bj_DEGTORAD)
            
            if .count == 1 then //IF THERE USED TO BE NO INSTANCES, WE NEED TO START THE TIMER AGAIN
                call TimerStart(UpdateTimer, INTERVAL, true, function missile.update)
            endif
            
            return this
        endmethod
        
        method onDestroy takes nothing returns nothing
            call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
            
            if .sfx != null then
                call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
            endif
            
            call .listRemove() //MAKE SURE THIS INSTANCE IS NO LONGER A PART OF THE CHAIN
        endmethod
    
    endstruct
    
    private function Init takes nothing returns nothing
        set UpdateTimer = CreateTimer()
        set Walls = CreateRegion()
        set Loc = Location(0,0) //THIS IS USED TO FIND TERRAIN Z
        
        set Spear  = missiletype.create(18, 2.5, 'dagg', 900, true) //YOU NEED TO CHANGE 'dagg' AND 'spea' TO THE ID:S OF YOUR DUMMIES
        set Dagger = missiletype.create(10, 1.8, 'spea', 900, true)
        
        call RegionAddRect(Walls, gg_rct_Wall1) //ADD ALL YOUR WALLS LIKE THIS
        call RegionAddRect(Walls, gg_rct_Wall2)
        call RegionAddRect(Walls, gg_rct_Wall3)
        //....
    endfunction
endlibrary

One cosmetic issue right now is that the missile is always pointing straight forward, while a missile in real life would have an incline. You can fix this quite easily by adding a small hack - you can use an invisible dummy unit, use "lock body part facing" to make it look at itself, but with an offset with that of the velocity! To make the missile visible, you will instead attach it as an effect on this dummy, and that has the positive side effect of you not having to create a dummy for each missile type. I made another version of the code to allow this - you can use it, or the old one as you please. It will require you to download the custom dummy model, and i recommend you set its movement type to "ground" (the script fixes this), since it will not make it tilt when turning the way flying units do. You can find the dummy in the attachments, and the new code here:

JASS:
library Missiles initializer Init requires ListModule
    globals
        private constant real INTERVAL      = 0.03 //SYSTEM UPDATE INTERVAL
        private constant real RADIUS        = 100 //THE RADIUS OF COLLISION
        private constant real GRAVITY       = 9.82 //ADJUST TO YOUR NEED
        private constant integer DUMMY_ID   = 'dumm' //ID OF THE DUMMY UNIT
        private constant integer FIGHTER_ID = 'figh' //UNIT ID OF THE PLAYER UNIT, CHANGE TO YOUR NEED
        
        private timer UpdateTimer
        private region Walls
        private location Loc
        
        missiletype Spear
        missiletype Dagger
    endglobals
    
    private function GetTerrainZ takes real x, real y returns real
        call MoveLocation(Loc,x,y)
        return GetLocationZ(Loc)
    endfunction
    
    struct missiletype 
        real damage = 10 //IN THIS CASE, 10 IS JUST A DEFAULT VALUE. I JUST WANTED TO DEMONSTRATE THAT YOU CAN ASSIGN DEFAULT VALUES. 
        real max_age = 5 //NUMBER OF SECONDS BEFORE THE MISSILE IS AUTOMATICALLY DESTROYED, THE 'RANGE' OF THE MISSILE
        real velocity //I MOVED THIS VARIABLE HERE SINCE IT MADE MORE SENSE
        string path //WE NOW ONLY ATTACH AN EFFECT TO THE DUMMY, AND LET THE DUMMY ITSELF BE INVISIBLE
        boolean usePathing = true
        
        static method create takes string path, real dmg, real lifetime, real vel, boolean pathing returns missiletype
            local missiletype this  = missiletype.allocate()
            set .damage             = dmg
            set .max_age            = lifetime
            set .path               = path
            set .velocity           = vel * INTERVAL //I MULTIPLY IT BY THE LOOP RATE SO THAT YOU CAN USE REGULAR WARCRAFT SPEEDS FOR THIS FIELD
            set .usePathing         = pathing
            return this
        endmethod
    endstruct
    
    struct missile
    
        implement List //THIS ADDS THE LIST MEMBERS INTO OUR STRUCT
        
        missiletype class //WE NEED TO STORE THE MISSILETYPE INSIDE THE MISSILE
    
        unit dummy
        effect sfx //THIS MEMBER MIGHT BE USELESS... JUST ADDED INCASE YOU WANT TO ATTACH STUFF TO YOUR MISSILE
        
        real age = 0 //THIS IS THE EXPIRATION TIME OF THE MISSILE
        
        real x = 0
        real y = 0
        real z = 0
        
        real vel_x = 0
        real vel_y = 0
        real vel_z = 0 //WE NOW HAVE VELOCITY ON THE Z AXIS!
        
        static method targetFilter takes nothing returns boolean
            //HERE YOU PUT THE CONDITIONS FOR WHICH TARGETS ARE ALLOWED. 
            return GetUnitTypeId(GetFilterUnit()) == FIGHTER_ID //RETURNS TRUE IF UNIT IS A FIGHTER, FALSE IF NOT. 
        endmethod
        
        method hit takes unit u returns nothing
            call UnitDamageTarget(.dummy, u, .class.damage, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            //ADD ANY EFFECTS YOU NEED, SUCH AS FLOATING TEXT AND BLOOD
            call .destroy()
        endmethod
        
        static method update takes nothing returns nothing
            local missile this = .first //RETRIEVES THE FIRST INSTANCE IN THE LIST. 
            local missile temp
            local group g = CreateGroup()
            local real tz
        
            if .count == 0 then //THIS MEMBER BECOMES INCLUDED WHEN YOU USE THE LINKED LIST LIBRARY
                call PauseTimer(GetExpiredTimer()) //PAUSE IF THERE ARE NO MISSILES
            else
                loop
                    exitwhen this == 0 //EXIT WHEN THERE IS NO NEXT INSTANCE
                    set temp = .next //FOR SAFETY, SINCE WE ARE REMOVING INSTANCES WITHIN THE LOOP
                    
                    set .vel_z = .vel_z-GRAVITY //ADD GRAVITY TO VELOCITY, REMOVE IF YOU WANT THE MISSILE TO TRAVEL IN A STRAIGHT LINE. 
                    
                    set .x = .x + .vel_x //ADD VELOCITY TO POSITION
                    set .y = .y + .vel_y
                    set .z = .z + .vel_z
                    
                    set tz = GetTerrainZ(.x, .y) //GET THE TERRAIN HEIGHT AT THESE COORDINATES
                    
                    call SetUnitLookAt(.dummy, "bone_Head", .dummy, .vel_x*100, .vel_y*100, .vel_z*100) //MAKES THE MISSILE FACE THE DIRECTION IT FLIES
                    call SetUnitPosition(.dummy, .x, .y) //WE NEED TO USE A DIFFERENT METHOD TO MOVE THE UNIT FOR THIS TO WORK!
                    call SetUnitFlyHeight(.dummy, .z-tz, 0) //WE NEED TO ACCOUNT FOR TERRAIN HEIGHT WHEN SETTING FLY HEIGHT
                    
                    set .age = .age - INTERVAL //IF THE LOOP RUNS EVERY 1/30 SECONDS, WE SUBSTRACT 1/30 FROM THE AGE EVERY LOOP
                    
                    if .age < 0 then
                        call .destroy() //DESTROY IT IF TIME RUNS OUT
                    endif
                    
                    if .z < tz then //IF THE MISSILE HITS THE GROUND, DESTROY IT!
                        call .destroy()
                    endif
                    
                    if IsPointInRegion(Walls, .x, .y) and .class.usePathing then //IF THE MISSILE IS INSIDE A WALL, DESTROY IT!
                        call .destroy() //BUT ONLY IF THE MISSILE USES PATHING, UNLIKE THE ICE WAND
                    endif
                    
                    call GroupEnumUnitsInRange(g, .x, .y, RADIUS, Filter(function missile.targetFilter)) //GROUP ALL UNITS WITHIN THE RADIUS
                    if FirstOfGroup(g) != null then //IF THE GROUP IS NOT EMPTY
                        call .hit(FirstOfGroup(g)) //CALL THE DAMAGE METHOD ON THE FIRST UNIT WE HIT
                    endif
                    call GroupClear(g) //EMPTY THE GROUP, FOR NEXT LOOP.
                    
                    
                    set this = temp //SET 'THIS' TO BE THE NEXT INSTANCE IN THE LIST
                endloop
            endif
            
            call DestroyGroup(g) //CLEAN GROUP LEAK
            set g = null //YOU NEED TO NULL THE HANDLES OF LOCAL VARIABLES, OTHERWISE THEY LEAK
        endmethod
        
        static method create takes missiletype m, real x, real y, real z, real facing, player owner returns missile
            local missile this = missile.allocate() //THIS CREATES A NEW INDEX FOR THE STRUCT INSTANCE
            
            call .listAdd() //THIS REGISTERS THAT WE HAVE A NEW LINK IN THE CHAIN
            
            set .dummy  = CreateUnit(owner, DUMMY_ID, x, y, facing)
            set .class  = m
            set .age    = .class.max_age
            
            set .x      = x
            set .y      = y
            set .z      = z + GetTerrainZ(x,y) //WE NEED Z IN ABSOLUTE COORDINATES, FROM MAP FLOOR, NOT TERRAIN FLOOR
            set .sfx    = AddSpecialEffectTarget(.class.path, .dummy, "origin")
            
            call UnitAddAbility(.dummy, 'Amrf') //THIS IS JUST A SMALL HACK WE CAN USE TO ENABLE US TO SET THE FLY HEIGHT OF UNITS WITH 'GROUND' MOVEMENT TYPE:
            call UnitRemoveAbility(.dummy, 'Amrf') //UNITS WITH GROUND MOVEMENT TYPE ACT MUCH MORE RELIABLY THAN THOSE WITH AIR MOVEMENT, SO WE WANT TO USE THEM. 
            call SetUnitAnimationByIndex(.dummy, 0) //THIS IS JUST TO PREVENT THE CUSTOM DUMMY MODEL FROM BUGGING
            call SetUnitTimeScale(.dummy, 0) //SAME AS ABOVE, MAKES THE DUMMYS ANIMATION FREEZE
            
            call SetUnitFlyHeight(.dummy, z, 0) //THIS SPEAKS FOR ITSELF
            
            set .vel_x = .class.velocity * Cos(facing*bj_DEGTORAD) //THIS IS THE SAME AS POLAR PROJECTION
            set .vel_y = .class.velocity * Sin(facing*bj_DEGTORAD)
            
            if .count == 1 then //IF THERE USED TO BE NO INSTANCES, WE NEED TO START THE TIMER AGAIN
                call TimerStart(UpdateTimer, INTERVAL, true, function missile.update)
            endif
            
            return this
        endmethod
        
        method onDestroy takes nothing returns nothing
            if .sfx != null then
                call DestroyEffect(.sfx) //DESTROY ANY EFFECTS
            endif
            
            call RemoveUnit(.dummy) //REMOVE THE DUMMY UNIT
            
            call .listRemove() //MAKE SURE THIS INSTANCE IS NO LONGER A PART OF THE CHAIN
        endmethod
    
    endstruct
    
    private function Init takes nothing returns nothing
        set UpdateTimer = CreateTimer()
        set Walls = CreateRegion()
        set Loc = Location(0,0) //THIS IS USED TO FIND TERRAIN Z
        
        //                                                                     (PATH, DMG, LIFE, SPEED, OBEYS PATHING)
        set Spear  = missiletype.create("Abilities\\Weapons\\Banditmissile\\Banditmissile.mdl", 18, 2.5, 900, true) //YOU NEED TO CHANGE 'dagg' AND 'spea' TO THE ID:S OF YOUR DUMMIES
        set Dagger = missiletype.create("Abilities\\Weapons\\WardenMissile\\WardenMissile.mdl", 10, 1.8, 900, true) //ALSO ADJUST THE SPEED AND TIME VALUES TO WHATEVER YOU WANT
        
        call RegionAddRect(Walls, gg_rct_Wall1) //ADD ALL YOUR WALLS LIKE THIS
        call RegionAddRect(Walls, gg_rct_Wall2)
        call RegionAddRect(Walls, gg_rct_Wall3)
        //....
    endfunction
endlibrary

Note that the "create" method now requires another parameter - a Z value. This is the height above ground you want your missile to have. Typically it would be around 80.

You can also add some slight upward velocity to your missile by simply manipulating its Z velocity, like this:

JASS:
local unit u = GetTriggerUnit()
local missile m = missile.create(Spear, GetUnitX(u), GetUnitY(u), 80., GetUnitFacing(u), GetOwningPlayer(u))
set m.vel_z = 20
set u = null
//YOU DO NOT NEED TO NULL STRUCT VARIABLES, SINCE THEY ARE ACTUALLY INTEGERS
 

Attachments

  • dummyRoll.mdx
    48.9 KB · Views: 97

sentrywiz

S

sentrywiz

Well this will be very useful to me when I understand how to do it myself.
Until then this will be a pipe dream :)

Because I don't know enough vJass to code this thing myself, later on I won't be able to even fix something broken or even change something without mocking it up and ending up confused. I need more experience in vJass before I start to use it to code complex things like these. Until then I feel like knowing GUI is better than doing crappy job at vJass.

however, I made a different missile system that's way cleaner and better than the last one. Unfortunately, using the Leak Finder it still lags later in the game. I'm starting to wonder if the missile system was the fault here, because now I only use 1 group and I clean that one.

Not sure what is the fault here, but this is really strange.
I will open up a different thread to ask if people see something I don't because the Leak Finder sees it but I fail to do so.

Here are the new triggers btw:


  • Missile Launch
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • Or - Any (Conditions) are true
        • Conditions
          • (Ability being cast) Equal to Attack (Staff)
          • (Ability being cast) Equal to Attack (Spear)
          • (Ability being cast) Equal to Attack (Knife)
    • Actions
      • Set missile_PlayerNumber = (Player number of (Owner of (Triggering unit)))
      • Set missile_launcher[missile_PlayerNumber] = (Casting unit)
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Ability being cast) Equal to Attack (Staff)
        • Then - Actions
          • Set missile_model[missile_PlayerNumber] = Staff Dummy
          • Set missile_speed[missile_PlayerNumber] = 35.00
          • Set missile_damage[missile_PlayerNumber] = 50.00
          • Set missile_hit_range[missile_PlayerNumber] = ITEM_Staff_Hit[missile_PlayerNumber]
          • Set missile_travel_range[missile_PlayerNumber] = ITEM_Staff_Range[missile_PlayerNumber]
          • Set missile_loc_start[missile_PlayerNumber] = (Position of missile_launcher[missile_PlayerNumber])
          • Set missile_loc_end[missile_PlayerNumber] = (Target point of ability being cast)
          • Unit - Create 1 missile_model[missile_PlayerNumber] for (Owner of missile_launcher[missile_PlayerNumber]) at missile_loc_start[missile_PlayerNumber] facing (Facing of missile_launcher[missile_PlayerNumber]) degrees
          • Set missile_projectile[missile_PlayerNumber] = (Last created unit)
          • Unit Group - Add missile_projectile[missile_PlayerNumber] to Missile_Group
          • Set total_missiles = (total_missiles + 1)
          • Trigger - Turn on Move Missiles <gen>
          • Custom script: call RemoveLocation ( udg_missile_loc_start [udg_missile_PlayerNumber] )
          • Custom script: call RemoveLocation ( udg_missile_loc_end [udg_missile_PlayerNumber] )
        • Else - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Ability being cast) Equal to Attack (Spear)
            • Then - Actions
              • Set missile_model[missile_PlayerNumber] = Spear Dummy
              • Set missile_speed[missile_PlayerNumber] = 35.00
              • Set missile_damage[missile_PlayerNumber] = 40.00
              • Set missile_critical_chance[missile_PlayerNumber] = ITEM_Spear_Crit[missile_PlayerNumber]
              • Set missile_hit_range[missile_PlayerNumber] = ITEM_Spear_Hit[missile_PlayerNumber]
              • Set missile_travel_range[missile_PlayerNumber] = ITEM_Spear_Range[missile_PlayerNumber]
              • Set missile_loc_start[missile_PlayerNumber] = (Position of missile_launcher[missile_PlayerNumber])
              • Set missile_loc_end[missile_PlayerNumber] = (Target point of ability being cast)
              • Unit - Create 1 missile_model[missile_PlayerNumber] for (Owner of missile_launcher[missile_PlayerNumber]) at missile_loc_start[missile_PlayerNumber] facing (Facing of missile_launcher[missile_PlayerNumber]) degrees
              • Set missile_projectile[missile_PlayerNumber] = (Last created unit)
              • Unit Group - Add missile_projectile[missile_PlayerNumber] to Missile_Group
              • Set total_missiles = (total_missiles + 1)
              • Trigger - Turn on Move Missiles <gen>
              • Custom script: call RemoveLocation ( udg_missile_loc_start [udg_missile_PlayerNumber] )
              • Custom script: call RemoveLocation ( udg_missile_loc_end [udg_missile_PlayerNumber] )
            • Else - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Ability being cast) Equal to Attack (Knife)
                • Then - Actions
                  • Set missile_model[missile_PlayerNumber] = Knife Dummy
                  • Set missile_speed[missile_PlayerNumber] = 45.00
                  • Set missile_damage[missile_PlayerNumber] = 20.00
                  • Set missile_slow_ratio[missile_PlayerNumber] = ITEM_Knife_Slow[missile_PlayerNumber]
                  • Set missile_hit_range[missile_PlayerNumber] = ITEM_Knife_Hit[missile_PlayerNumber]
                  • Set missile_travel_range[missile_PlayerNumber] = ITEM_Knife_Range[missile_PlayerNumber]
                  • Set missile_loc_start[missile_PlayerNumber] = (Position of missile_launcher[missile_PlayerNumber])
                  • Set missile_loc_end[missile_PlayerNumber] = (Target point of ability being cast)
                  • Unit - Create 1 missile_model[missile_PlayerNumber] for (Owner of missile_launcher[missile_PlayerNumber]) at missile_loc_start[missile_PlayerNumber] facing (Facing of missile_launcher[missile_PlayerNumber]) degrees
                  • Set missile_projectile[missile_PlayerNumber] = (Last created unit)
                  • Unit Group - Add missile_projectile[missile_PlayerNumber] to Missile_Group
                  • Set total_missiles = (total_missiles + 1)
                  • Trigger - Turn on Move Missiles <gen>
                  • Custom script: call RemoveLocation ( udg_missile_loc_start [udg_missile_PlayerNumber] )
                  • Custom script: call RemoveLocation ( udg_missile_loc_end [udg_missile_PlayerNumber] )
                • Else - Actions
  • Move Missiles
    • Events
      • Time - Every 0.05 seconds of game time
    • Conditions
    • Actions
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • total_missiles Equal to 0
        • Then - Actions
          • Trigger - Turn off (This trigger)
        • Else - Actions
          • For each (Integer A) from 1 to total_missiles, do (Actions)
            • Loop - Actions
              • Set missile_loc_current[(Integer A)] = (Position of missile_projectile[(Integer A)])
              • Set missile_hit_group[(Integer A)] = (Units within missile_hit_range[(Integer A)] of missile_loc_current[(Integer A)] matching ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to an enemy of (Owner of missile_launcher[(Integer A)])) Equal to True)))
              • Set missile_hit_random_unit[(Integer A)] = (Random unit from missile_hit_group[(Integer A)])
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Number of units in missile_hit_group[(Integer A)]) Greater than 0
                • Then - Actions
                  • Trigger - Run Missle Damage <gen> (ignoring conditions)
                  • Unit - Remove missile_projectile[(Integer A)] from the game
                  • Set total_missiles = (total_missiles - 1)
                  • Set missile_current_range[(Integer A)] = 0.00
                • Else - Actions
                  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                    • If - Conditions
                      • missile_current_range[(Integer A)] Less than missile_travel_range[(Integer A)]
                      • (missile_projectile[(Integer A)] is in forbidden_group) Not equal to True
                    • Then - Actions
                      • Set missile_current_range[(Integer A)] = (missile_current_range[(Integer A)] + missile_speed[(Integer A)])
                      • Unit - Move missile_projectile[(Integer A)] instantly to (missile_loc_current[(Integer A)] offset by missile_speed[(Integer A)] towards (Facing of missile_projectile[(Integer A)]) degrees), facing (Facing of missile_projectile[(Integer A)]) degrees
                      • Game - Display to (All players) the text: (String(missile_current_range[(Integer A)]))
                    • Else - Actions
                      • Unit Group - Remove missile_projectile[(Integer A)] from forbidden_group
                      • Unit - Remove missile_projectile[(Integer A)] from the game
                      • Set total_missiles = (total_missiles - 1)
                      • Set missile_current_range[(Integer A)] = 0.00
              • Custom script: call RemoveLocation ( udg_missile_loc_current [GetForLoopIndexA()] )
              • Custom script: call DestroyGroup ( udg_missile_hit_group [GetForLoopIndexA()] )
 
The polar projection, which i think is called "point with offset" or something in GUI, might leak a location. Basically, it takes one location, and returns a new one, so you need to destroy both the location you input and the one you get.

Do this instead:
  • Set temp_loc to missile_loc_current offset by missile_speed towards facing of missile_projectile
  • unit - move missile instantly to temp loc
  • Custom script: call RemoveLocation(udg_temp_loc)
Also, you are running the Missile Damage trigger in the middle of your loop, if it changes udg_missile_loc_current or udg_missile_hit_group to a new value, the old value will leak. Just a heads up.

Btw, this new method looks much cleaner. However, you need to realize this:

Imagine that you have 10 missiles. If missile number 5 dies before missile number 10, the game will register num_missiles as 9, and hence not loop all the way to missile number 10, who still has the same index. You will just get a hole in index number 5 where the dead unit used to be. What you need to do is to set missiles[5] = missiles[num_missiles] before you set num_missiles = num_missiles-1. Or, like this to make it short:

num_missiles = 0 by default

new missile:
set missile[num_missiles] = new unit
set num_missiles = num_missiles+1 //num_missiles will always be one index above the last created missile

remove missile:
set num_missiles = num_missiles-1 //now we are at the same index as the last missile
set missile[index of removed missile] = missile[num_missiles] //move the last missile to the empty slot
 

sentrywiz

S

sentrywiz

You're right about the temp_loc, that's a leak I missed.
I will fix it right away.

I thought about this, however I believe that the cooldowns of the missile spells prevents missile spam. The lowest cooldown of the items are the knives, which are low range and you can't spam multiple knives at the same time.

The best this can have are 4 missiles at a time.

I am slightly confused about the solution you suggest. Do you mind explaining it again?

EDIT: It still leaks. One shot leaked 13 "Pick random unit" 4 Something and 3 Something.

  • Set missile_hit_random_unit[(Integer A)] = (Random unit from missile_hit_group[(Integer A)])
Can this leak? Because this is the only random unit I use and the Leak Finder says it leaked 13 times in one spell cast.
 
No, it can not. Like i said, that leack check system is not perfect, and it only shows stuff it think leaks. In "pick random unit", you have an option of destroying the group you picked a unit from if you put the following code in front of it:

  • Custom Script: set bj_wantDestroyGroup = true
And i think the system is simply reacting to the fact that you didn't, even though you destroy it later. If you are only using one unit from the group though, you can always use this action to destroy the group right away, for safety.

About the stack issue:

Once a unit dies, it creates a "hole" in the array, while num_missiles is reduced by one. What you want to do is fill that hole with the missile you created last, which will have the last spot in the array. I may try to visualise it with some gibberish metacode:

num_missiles = 0

create missile:
set missiles[num_missiles] = new unit <= the array now has a unit in index 0, and number of missiles is 1.
set num_missiles = num_missiles + 1 (= 1)

create missile:
set missiles[num_missiles] = new unit <= the array now has units in index 0 and 1, and number of missiles is 2
set num_missiles = num_missiles + 1 (= 2)

create missile:
set missiles[num_missiles] = new unit <= the array now has units in index 0, 1 and 2, and number of missiles is 3
set num_missiles = num_missiles + 1 (= 3)

create missile:
set missiles[num_missiles] = new unit <= the array now has units in index 0, 1, 2 and 3, and number of missiles is 4
set num_missiles = num_missiles + 1 (= 4)

remove missile with index 1:
remove unit: missiles[1]
set num_missiles = num_missiles-1 <= number of missiles is now 3

AND HERE IS THE CRITICAL PART, We move the LAST unit to this empty hole:

set missiles[1] = missiles[num_missiles]
set missiles[num_missiles] = nothing

In other words, we are moving the missile with index no. 3 to index no. 1, so that the HIGHEST index is now 2. There is no longer any missile in index no. 3, so we can safely loop all the way up to num_missiles without skipping any missiles due to counting empty space.
 

sentrywiz

S

sentrywiz

So I do it like this?

  • Set missile_loc_current[(Integer A)] = (Position of missile_projectile[(Integer A)])
  • Set missile_hit_group[(Integer A)] = (Units within missile_hit_range[(Integer A)] of missile_loc_current[(Integer A)] matching ((((Matching unit) is alive) Equal to True) and (((Matching unit) belongs to an enemy of (Owner of missile_launcher[(Integer A)])) Equal to True)))
  • Custom script: set bj_wantDestroyGroup = true
  • Set missile_hit_random_unit[(Integer A)] = (Random unit from missile_hit_group[(Integer A)])
About the stack solution, I just noticed that you think I put missiles in an array n + 1.
I do, but I also know that the array will never exceed 4 because there can be a maximum of only 4 players at one time.
I save the missiles based on player number, and I just use total_missiles to turn off the loop trigger if there are no missiles flying.
And every player can only have one missile flying at one time because of the item cooldowns.

In other words, your solution is great if I store missiles in an array counting +1 for every missile fired.
But I am storing them based on player number, so each player has its own place for a missile.
The worst that can happen is a player launching a missile, then acquiring another weapon and launching
another one while the first one is flying. I have a plan to solve that by destroying the first missile before
the player can launch another one. But this is about the lag
 
Well, it seems kinda lame to me to have a system that only allows one missile per player. It is really easy to do it the way i showed, where each missile is just a unit in an array ranging from 0 to the number of missiles on the map. You'd be saving yourself a lot of trouble going for this more flexible approach than building a bunch of failsafes to prevent players from firing more missiles. Essentially, structs work the same way - all the struct members are just arrays which take the struct ID to find its value. The struct ID is the variable called "this" in the methods (in regular methods it exists by default, while in static methods you have to declare it yourself).

And yes, that is the correct use of wantDestroyGroup. The missile_hit_group will be destroyed once a random unit has been picked. This works for all actions which use a group.
 

sentrywiz

S

sentrywiz

Haha. Indeed is kinda lame.

But I also know the cooldowns around the weapons so designing something better isn't necessary. Because its almost always one missile per player, its easier to prevent multiple missiles rather than design a better system for those 1-5% of the cases another missile might pop-up.

Still I understand your logic, and its okay. Its just that I had this system in mind when I was recreating the system. Its nothing wrong with what you suggest, it's actually better.

Think of this as balance. Imagine I have a throwing knife and you come at me with a hammer. I am standing infront of the spear. I can pull a wombo-combo against you, shoot you once with the knife to slow you and then grab the spear and shoot a possible crit missile to 1 shot you.

My way will destroy the knife missile before it hits so its just the spear as current missile.
That way I cannot 1 shot you and you can catch me with the hammer to 1 shot me.

But this isn't the real issue. We can talk about this after we play a game.
The real issue is the lag. That's what needs to be solved

EDIT:

Here is with the destroying group after selecting a random.
Its still showing leaks. Here's an image from the Leak Finder:

Untitled.jpg

I only fired one knife, so this is only from that.
 
Status
Not open for further replies.
Top